From 9c650883c28e9f4e4044e2d15cf3d02649216346 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 10 Nov 2021 16:33:59 -0800 Subject: [PATCH 01/34] implement context item in node view --- app/node/CMakeLists.txt | 2 + app/node/group.cpp | 30 +++++++ app/node/group.h | 43 +++++++++ app/node/output/track/track.h | 45 ++++++++-- app/widget/nodeview/CMakeLists.txt | 2 + app/widget/nodeview/nodeview.cpp | 43 ++++++++- app/widget/nodeview/nodeview.h | 5 ++ app/widget/nodeview/nodeviewcontext.cpp | 114 ++++++++++++++++++++++++ app/widget/nodeview/nodeviewcontext.h | 39 ++++++++ app/widget/nodeview/nodeviewitem.cpp | 4 + app/widget/nodeview/nodeviewscene.cpp | 25 ++++++ app/widget/nodeview/nodeviewscene.h | 6 ++ 12 files changed, 347 insertions(+), 11 deletions(-) create mode 100644 app/node/group.cpp create mode 100644 app/node/group.h create mode 100644 app/widget/nodeview/nodeviewcontext.cpp create mode 100644 app/widget/nodeview/nodeviewcontext.h 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_; From 603303904627cfd98b79e4aca9d557f1d6791b5e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 10 Nov 2021 21:59:54 -0800 Subject: [PATCH 02/34] improved connector UI in node view --- app/node/output/track/track.cpp | 3 +- app/node/output/track/track.h | 15 +- app/node/output/track/tracklist.cpp | 2 + app/widget/nodeview/CMakeLists.txt | 2 + app/widget/nodeview/nodeview.cpp | 5 +- app/widget/nodeview/nodeviewcontext.cpp | 80 +++++++++- app/widget/nodeview/nodeviewcontext.h | 9 ++ app/widget/nodeview/nodeviewedge.cpp | 8 +- app/widget/nodeview/nodeviewitem.cpp | 140 ++++++++++++------ app/widget/nodeview/nodeviewitem.h | 22 ++- app/widget/nodeview/nodeviewitemconnector.cpp | 81 ++++++++++ app/widget/nodeview/nodeviewitemconnector.h | 40 +++++ app/widget/nodeview/nodeviewscene.cpp | 61 ++------ app/widget/nodeview/nodeviewscene.h | 26 ---- app/widget/timelinewidget/tool/import.cpp | 6 +- 15 files changed, 352 insertions(+), 148 deletions(-) create mode 100644 app/widget/nodeview/nodeviewitemconnector.cpp create mode 100644 app/widget/nodeview/nodeviewitemconnector.h diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 41eda30f2..e1cb208d6 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -41,7 +41,8 @@ const QString Track::kMutedInput = QStringLiteral("muted_in"); Track::Track() : track_type_(Track::kNone), index_(-1), - locked_(false) + locked_(false), + sequence_(nullptr) { AddInput(kBlockInput, NodeValue::kNone, InputFlags(kInputFlagArray | kInputFlagNotKeyframable)); diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index d6179eaf4..2e9b44ab3 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -26,6 +26,8 @@ namespace olive { +class Sequence; + /** * @brief A time traversal Node for sorting through one channel/track of Blocks */ @@ -388,9 +390,18 @@ public: bool IsLocked() const; - int GetArrayIndexFromBlock(Block* block) const; + Sequence *sequence() const + { + return sequence_; + } + + void set_sequence(Sequence *sequence) + { + sequence_ = sequence; + } + static const double kTrackHeightDefault; static const double kTrackHeightMinimum; static const double kTrackHeightInterval; @@ -472,6 +483,8 @@ private: bool locked_; + Sequence *sequence_; + private slots: void BlockLengthChanged(); diff --git a/app/node/output/track/tracklist.cpp b/app/node/output/track/tracklist.cpp index c9f35e26f..f5d233515 100644 --- a/app/node/output/track/tracklist.cpp +++ b/app/node/output/track/tracklist.cpp @@ -83,6 +83,7 @@ void TrackList::TrackConnected(Node *node, int element) connect(track, &Track::TrackLengthChanged, this, &TrackList::UpdateTotalLength); track->set_type(type_); + track->set_sequence(parent()); emit TrackListChanged(); @@ -121,6 +122,7 @@ void TrackList::TrackDisconnected(Node *node, int element) track->SetIndex(-1); track->set_type(Track::kNone); + track->set_sequence(nullptr); disconnect(track, &Track::TrackLengthChanged, this, &TrackList::UpdateTotalLength); diff --git a/app/widget/nodeview/CMakeLists.txt b/app/widget/nodeview/CMakeLists.txt index e7f94c762..12512695f 100644 --- a/app/widget/nodeview/CMakeLists.txt +++ b/app/widget/nodeview/CMakeLists.txt @@ -25,6 +25,8 @@ set(OLIVE_SOURCES widget/nodeview/nodeviewedge.h widget/nodeview/nodeviewitem.cpp widget/nodeview/nodeviewitem.h + widget/nodeview/nodeviewitemconnector.cpp + widget/nodeview/nodeviewitemconnector.h widget/nodeview/nodeviewminimap.cpp widget/nodeview/nodeviewminimap.h widget/nodeview/nodeviewscene.cpp diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index b694992b3..86b1c5711 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -847,7 +847,7 @@ void NodeView::UpdateSelectionCache() void NodeView::ShowContextMenu(const QPoint &pos) { - if (!graph_) { + if (filter_nodes_.isEmpty()) { return; } @@ -1023,7 +1023,6 @@ void NodeView::RemoveNode(Node *node) scene_.RemoveEdge(it->second, it->first); } positions_.remove(scene_.item_map().value(node)); - scene_.RemoveNode(node); } void NodeView::AddEdge(Node *output, const NodeInput &input) @@ -1645,8 +1644,6 @@ NodeViewItem *NodeView::UpdateNodeItem(Node *node, bool ignore_own_context) // Get UI item or create if it doesn't exist NodeViewItem *item = scene_.item_map().value(node); if (!item) { - item = scene_.AddNode(node); - for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { if (scene_.item_map().contains(it->second)) { scene_.AddEdge(it->second, it->first); diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index ad1bdc6ac..0e8262b3e 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -6,8 +6,11 @@ #include #include +#include "core.h" #include "node/block/block.h" +#include "node/graph.h" #include "node/output/track/track.h" +#include "node/project/sequence/sequence.h" #include "nodeviewitem.h" #include "ui/colorcoding.h" @@ -31,11 +34,12 @@ void NodeViewContext::SetContext(Node *node) if (context_) { if (Block *block = dynamic_cast(node)) { + rational timebase = block->track()->sequence()->GetVideoParams().frame_rate_as_time_base(); lbl_ = QCoreApplication::translate("NodeViewContext", - "%1 [%2] :: %3 - %4").arg(block->GetLabelAndName(), - Track::Reference::TypeToTranslatedString(block->track()->type()), - block->in().toString(), - block->out().toString()); + "%1 [%2] :: %3 - %4").arg(block->GetLabelAndName(), + Track::Reference::TypeToTranslatedString(block->track()->type()), + Timecode::time_to_timecode(block->in(), timebase, Core::instance()->GetTimecodeDisplay()), + Timecode::time_to_timecode(block->out(), timebase, Core::instance()->GetTimecodeDisplay())); } else { lbl_ = node->GetLabelAndName(); } @@ -52,8 +56,38 @@ void NodeViewContext::SetContext(Node *node) void NodeViewContext::AddChild(Node *node) { + if (!context_) { + return; + } + NodeViewItem *item = new NodeViewItem(this); item->SetNode(node); + item->SetNodePosition(context_->parent()->GetNodesForContext(context_).value(node)); + item->SetFlowDirection(flow_dir_); + + if (node == context_) { + item->SetLabelAsOutput(true); + } + + for (auto it=node->output_connections().cbegin(); it!=node->output_connections().cend(); it++) { + foreach (auto child, childItems()) { + if (NodeViewItem *other_item = dynamic_cast(child)) { + if (other_item->GetNode() == it->second.node()) { + AddEdgeInternal(node, it->second, item, other_item); + } + } + } + } + + for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { + foreach (auto child, childItems()) { + if (NodeViewItem *other_item = dynamic_cast(child)) { + if (it->second == other_item->GetNode()) { + AddEdgeInternal(it->second, it->first, other_item, item); + } + } + } + } } qreal GetTextOffset(const QFontMetricsF &fm) @@ -75,12 +109,31 @@ void NodeViewContext::UpdateRect() void NodeViewContext::SetFlowDirection(NodeViewCommon::FlowDirection dir) { - auto children = childItems(); - foreach (auto child, children) { + flow_dir_ = dir; + + foreach (auto child, childItems()) { if (NodeViewItem *item = dynamic_cast(child)) { item->SetFlowDirection(dir); } } + + foreach (auto child, childItems()) { + if (NodeViewEdge *edge = dynamic_cast(child)) { + edge->SetFlowDirection(dir); + } + } +} + +void NodeViewContext::SetCurvedEdges(bool e) +{ + curved_edges_ = e; + + const QList &children = childItems(); + foreach (auto child, children) { + if (NodeViewEdge *edge = dynamic_cast(child)) { + edge->SetCurved(e); + } + } } void NodeViewContext::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) @@ -97,7 +150,7 @@ void NodeViewContext::paint(QPainter *painter, const QStyleOptionGraphicsItem *o int rounded = painter->fontMetrics().height(); painter->drawRoundedRect(rect(), rounded, rounded); - painter->setPen(context_ ? ColorCoding::GetUISelectorColor(context_->color()) : Qt::white); + painter->setPen(widget->palette().text().color()); int offset = GetTextOffset(painter->fontMetrics()); @@ -111,4 +164,17 @@ QVariant NodeViewContext::itemChange(GraphicsItemChange change, const QVariant & return super::itemChange(change, value); } +NodeViewEdge* NodeViewContext::AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to) +{ + NodeViewEdge* edge_ui = new NodeViewEdge(output, input, from, to, this); + + edge_ui->SetFlowDirection(flow_dir_); + edge_ui->SetCurved(curved_edges_); + + from->AddEdge(edge_ui); + to->AddEdge(edge_ui); + + return edge_ui; +} + } diff --git a/app/widget/nodeview/nodeviewcontext.h b/app/widget/nodeview/nodeviewcontext.h index 4e1a8a866..a5141e32b 100644 --- a/app/widget/nodeview/nodeviewcontext.h +++ b/app/widget/nodeview/nodeviewcontext.h @@ -6,6 +6,7 @@ #include "node/node.h" #include "nodeviewcommon.h" +#include "nodeviewedge.h" namespace olive { @@ -22,16 +23,24 @@ public: void SetFlowDirection(NodeViewCommon::FlowDirection dir); + void SetCurvedEdges(bool e); + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; protected: virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; private: + NodeViewEdge *AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to); + Node *context_; QString lbl_; + NodeViewCommon::FlowDirection flow_dir_; + + bool curved_edges_; + }; } diff --git a/app/widget/nodeview/nodeviewedge.cpp b/app/widget/nodeview/nodeviewedge.cpp index 42dd19e06..5498fbefb 100644 --- a/app/widget/nodeview/nodeviewedge.cpp +++ b/app/widget/nodeview/nodeviewedge.cpp @@ -128,9 +128,11 @@ void NodeViewEdge::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti painter->drawPath(path()); // Draw arrow - painter->setPen(Qt::NoPen); - painter->setBrush(edge_color); - painter->drawPolygon(arrow_); + if (!connected_) { + painter->setPen(Qt::NoPen); + painter->setBrush(edge_color); + painter->drawPolygon(arrow_); + } } void NodeViewEdge::Init() diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index fa362af72..2eb766863 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -46,7 +46,8 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) : hide_titlebar_(false), highlighted_index_(-1), flow_dir_(NodeViewCommon::kLeftToRight), - prevent_removing_(false) + prevent_removing_(false), + label_as_output_(false) { // Set flags for this widget setFlag(QGraphicsItem::ItemIsMovable); @@ -66,7 +67,8 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) : title_bar_rect_ = QRectF(-widget_width/2, -widget_height/2, widget_width, widget_height); setRect(title_bar_rect_); - output_triangle_.resize(3); + input_connector_ = new NodeViewItemConnector(this); + output_connector_ = new NodeViewItemConnector(this); } QPointF NodeViewItem::GetNodePosition() const @@ -208,6 +210,11 @@ int NodeViewItem::GetIndexAt(QPointF pt) const void NodeViewItem::SetNode(Node *n) { + if (node_) { + disconnect(n, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); + disconnect(n, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); + } + node_ = n; node_inputs_.clear(); @@ -220,6 +227,11 @@ void NodeViewItem::SetNode(Node *n) node_inputs_.append(input); } } + + input_connector_->setVisible(!node_inputs_.isEmpty()); + + connect(n, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); + connect(n, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); } update(); @@ -234,6 +246,7 @@ void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) expanded_ = e; hide_titlebar_ = hide_titlebar; + input_connector_->setVisible(!expanded_); if (expanded_ && !node_inputs_.isEmpty()) { // Create new rect @@ -252,7 +265,11 @@ void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) update(); + UpdateConnectorPositions(); + ReadjustAllEdges(); + + UpdateContextRect(); } void NodeViewItem::ToggleExpanded() @@ -298,8 +315,14 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti painter->setPen(app_pal.color(QPalette::Text)); - QString node_label = node_->GetLabel(); - QString node_shortname = node_->ShortName(); + QString node_label, node_shortname; + + if (label_as_output_) { + node_shortname = QCoreApplication::translate("NodeViewItem", "Output"); + } else { + node_label = node_->GetLabel(); + node_shortname = node_->ShortName(); + } int icon_size = painter->fontMetrics().height()/2; @@ -335,41 +358,6 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti painter->setBrush(Qt::NoBrush); painter->drawRect(rect()); - - // Draw output triangle - painter->setPen(Qt::NoPen); - painter->setBrush(app_pal.color(QPalette::Text)); - int triangle_sz = title_bar_rect_.height() / 2; - int triangle_sz_half = triangle_sz / 2; - - switch (flow_dir_) { - case NodeViewCommon::kLeftToRight: - // Triangle pointing right - output_triangle_[0] = QPointF(rect().right(), rect().center().y() - triangle_sz_half); - output_triangle_[1] = QPointF(rect().right() + triangle_sz_half, rect().center().y()); - output_triangle_[2] = QPointF(rect().right(), rect().center().y() + triangle_sz_half); - break; - case NodeViewCommon::kTopToBottom: - // Triangle pointing down - output_triangle_[0] = QPointF(rect().center().x() - triangle_sz_half, rect().bottom()); - output_triangle_[1] = QPointF(rect().center().x(), rect().bottom() + triangle_sz_half); - output_triangle_[2] = QPointF(rect().center().x() + triangle_sz_half, rect().bottom()); - break; - case NodeViewCommon::kBottomToTop: - // Triangle pointing up - output_triangle_[0] = QPointF(rect().center().x() - triangle_sz_half, rect().top()); - output_triangle_[1] = QPointF(rect().center().x(), rect().top() - triangle_sz_half); - output_triangle_[2] = QPointF(rect().center().x() + triangle_sz_half, rect().top()); - break; - case NodeViewCommon::kRightToLeft: - // Triangle pointing left - output_triangle_[0] = QPointF(rect().left(), rect().center().y() - triangle_sz_half); - output_triangle_[1] = QPointF(rect().left() - triangle_sz_half, rect().center().y()); - output_triangle_[2] = QPointF(rect().left(), rect().center().y() + triangle_sz_half); - break; - } - - painter->drawPolygon(output_triangle_); } void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event) @@ -407,9 +395,7 @@ QVariant NodeViewItem::itemChange(QGraphicsItem::GraphicsItemChange change, cons if (change == ItemPositionHasChanged && node_) { ReadjustAllEdges(); - if (NodeViewContext *ctx = dynamic_cast(parentItem())) { - ctx->UpdateRect(); - } + UpdateContextRect(); } return QGraphicsItem::itemChange(change, value); @@ -422,6 +408,13 @@ void NodeViewItem::ReadjustAllEdges() } } +void NodeViewItem::UpdateContextRect() +{ + if (NodeViewContext *ctx = dynamic_cast(parentItem())) { + ctx->UpdateRect(); + } +} + void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& rect, Qt::Alignment vertical_align, int icon_size, bool draw_arrow) { QFontMetrics fm = painter->fontMetrics(); @@ -487,6 +480,13 @@ void NodeViewItem::SetHighlightedIndex(int index) update(); } +void NodeViewItem::SetLabelAsOutput(bool e) +{ + label_as_output_ = e; + output_connector_->setVisible(!e); + update(); +} + QRectF NodeViewItem::GetInputRect(int index) const { QRectF r = title_bar_rect_; @@ -504,28 +504,45 @@ QRectF NodeViewItem::GetInputRect(int index) const QPointF NodeViewItem::GetInputPoint(const QString &input, int element, const QPointF& source_pos) const { - return pos() + GetInputPointInternal(node_inputs_.indexOf(input), source_pos); + if (expanded_) { + return pos() + GetInputPointInternal(node_inputs_.indexOf(input), source_pos); + } else { + return pos() + input_connector_->pos(); + } } QPointF NodeViewItem::GetOutputPoint() const { + QPointF p = pos() + output_connector_->pos(); + QRectF r = output_connector_->boundingRect(); + switch (flow_dir_) { case NodeViewCommon::kLeftToRight: default: - return pos() + QPointF(rect().right(), rect().center().y()); + p.setX(p.x() + r.width()); + break; case NodeViewCommon::kRightToLeft: - return pos() + QPointF(rect().left(), rect().center().y()); + p.setX(p.x() - r.width()); + break; case NodeViewCommon::kTopToBottom: - return pos() + QPointF(rect().center().x(), rect().bottom()); + p.setY(p.y() + r.height()); + break; case NodeViewCommon::kBottomToTop: - return pos() + QPointF(rect().center().x(), rect().top()); + p.setY(p.y() - r.height()); + break; } + + return p; } void NodeViewItem::SetFlowDirection(NodeViewCommon::FlowDirection dir) { flow_dir_ = dir; + input_connector_->SetFlowDirection(dir); + output_connector_->SetFlowDirection(dir); + + UpdateConnectorPositions(); UpdateNodePosition(); } @@ -556,4 +573,33 @@ void NodeViewItem::UpdateNodePosition() setPos(NodeToScreenPoint(cached_node_pos_, flow_dir_)); } +void NodeViewItem::UpdateConnectorPositions() +{ + QRectF output_rect = output_connector_->boundingRect(); + + switch (flow_dir_) { + case NodeViewCommon::kLeftToRight: + input_connector_->setPos(rect().left() - output_rect.width(), rect().center().y()); + output_connector_->setPos(rect().right(), rect().center().y()); + break; + case NodeViewCommon::kRightToLeft: + input_connector_->setPos(rect().right() + output_rect.width(), rect().center().y()); + output_connector_->setPos(rect().left(), rect().center().y()); + break; + case NodeViewCommon::kTopToBottom: + input_connector_->setPos(rect().center().x(), rect().top() - output_rect.height()); + output_connector_->setPos(rect().center().x(), rect().bottom()); + break; + case NodeViewCommon::kBottomToTop: + input_connector_->setPos(rect().center().x(), rect().bottom() + output_rect.height()); + output_connector_->setPos(rect().center().x(), rect().top()); + break; + } +} + +void NodeViewItem::NodeAppearanceChanged() +{ + update(); +} + } diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index 3c611d481..bd2cd7ac1 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -28,6 +28,7 @@ #include "node/node.h" #include "nodeviewcommon.h" +#include "nodeviewitemconnector.h" namespace olive { @@ -40,8 +41,9 @@ class NodeViewEdge; * * To retrieve the NodeViewItem for a certain Node, use NodeView::NodeToUIObject(). */ -class NodeViewItem : public QGraphicsRectItem +class NodeViewItem : public QObject, public QGraphicsRectItem { + Q_OBJECT public: NodeViewItem(QGraphicsItem* parent = nullptr); @@ -125,11 +127,13 @@ public: return prevent_removing_; } - const QPolygonF &GetOutputTriangle() const + QPolygonF GetOutputTriangle() const { - return output_triangle_; + return output_connector_->polygon(); } + void SetLabelAsOutput(bool e); + protected: virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; @@ -143,6 +147,8 @@ protected: private: void ReadjustAllEdges(); + void UpdateContextRect(); + void DrawNodeTitle(QPainter *painter, QString text, const QRectF &rect, Qt::Alignment vertical_align, int icon_size, bool draw_arrow); /** @@ -160,6 +166,8 @@ private: */ void UpdateNodePosition(); + void UpdateConnectorPositions(); + /** * @brief Reference to attached Node */ @@ -195,7 +203,13 @@ private: bool prevent_removing_; - QPolygonF output_triangle_; + NodeViewItemConnector *input_connector_; + NodeViewItemConnector *output_connector_; + + bool label_as_output_; + +private slots: + void NodeAppearanceChanged(); }; diff --git a/app/widget/nodeview/nodeviewitemconnector.cpp b/app/widget/nodeview/nodeviewitemconnector.cpp new file mode 100644 index 000000000..650c33ccf --- /dev/null +++ b/app/widget/nodeview/nodeviewitemconnector.cpp @@ -0,0 +1,81 @@ +/*** + + 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 "nodeviewitemconnector.h" + +#include +#include +#include +#include + +#include "nodeviewitem.h" + +namespace olive { + +NodeViewItemConnector::NodeViewItemConnector(QGraphicsItem *parent) : + QGraphicsPolygonItem(parent) +{ + QColor c = qApp->palette().text().color(); + setPen(QPen(c, NodeViewItem::DefaultItemBorder())); + setBrush(c); +} + +void NodeViewItemConnector::SetFlowDirection(NodeViewCommon::FlowDirection dir) +{ + QFont f; + QFontMetricsF fm(f); + + int triangle_sz = fm.height()/2; + int triangle_sz_half = triangle_sz / 2; + + QPolygonF p; + p.resize(3); + + switch (dir) { + case NodeViewCommon::kLeftToRight: + // Triangle pointing right + p[0] = QPointF(0, -triangle_sz_half); + p[1] = QPointF(triangle_sz_half, 0); + p[2] = QPointF(0, triangle_sz_half); + break; + case NodeViewCommon::kTopToBottom: + // Triangle pointing down + p[0] = QPointF(-triangle_sz_half, 0); + p[1] = QPointF(0, triangle_sz_half); + p[2] = QPointF(triangle_sz_half, 0); + break; + case NodeViewCommon::kBottomToTop: + // Triangle pointing up + p[0] = QPointF(-triangle_sz_half, 0); + p[1] = QPointF(0, -triangle_sz_half); + p[2] = QPointF(triangle_sz_half, 0); + break; + case NodeViewCommon::kRightToLeft: + // Triangle pointing left + p[0] = QPointF(0, -triangle_sz_half); + p[1] = QPointF(-triangle_sz_half, 0); + p[2] = QPointF(0, triangle_sz_half); + break; + } + + setPolygon(p); +} + +} diff --git a/app/widget/nodeview/nodeviewitemconnector.h b/app/widget/nodeview/nodeviewitemconnector.h new file mode 100644 index 000000000..6dfc7d9d3 --- /dev/null +++ b/app/widget/nodeview/nodeviewitemconnector.h @@ -0,0 +1,40 @@ +/*** + + 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 NODEVIEWITEMCONNECTOR_H +#define NODEVIEWITEMCONNECTOR_H + +#include + +#include "nodeviewcommon.h" + +namespace olive { + +class NodeViewItemConnector : public QGraphicsPolygonItem +{ +public: + NodeViewItemConnector(QGraphicsItem *parent = nullptr); + + void SetFlowDirection(NodeViewCommon::FlowDirection dir); +}; + +} + +#endif // NODEVIEWITEMCONNECTOR_H diff --git a/app/widget/nodeview/nodeviewscene.cpp b/app/widget/nodeview/nodeviewscene.cpp index 56de3d250..feb2598b3 100644 --- a/app/widget/nodeview/nodeviewscene.cpp +++ b/app/widget/nodeview/nodeviewscene.cpp @@ -38,19 +38,13 @@ void NodeViewScene::SetFlowDirection(NodeViewCommon::FlowDirection direction) { direction_ = direction; - { - // Iterate over node items setting direction - QHash::const_iterator i; - for (i=item_map_.constBegin(); i!=item_map_.constEnd(); i++) { - i.value()->SetFlowDirection(direction_); - } + foreach (NodeViewContext *ctx, context_map_) { + ctx->SetFlowDirection(direction_); } - { - // Iterate over edge items setting direction - foreach (NodeViewEdge* edge, edges_) { - edge->SetFlowDirection(direction_); - } + // Iterate over edge items setting direction + foreach (NodeViewEdge* edge, edges_) { + edge->SetFlowDirection(direction_); } } @@ -66,7 +60,6 @@ void NodeViewScene::clear() selectedItems(); for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) { - DisconnectNode(it.key()); delete it.value(); } item_map_.clear(); @@ -150,28 +143,6 @@ QVector NodeViewScene::GetSelectedEdges() const return edges; } -NodeViewItem* NodeViewScene::AddNode(Node* node) -{ - NodeViewItem* item = new NodeViewItem(); - - item->SetFlowDirection(direction_); - item->SetNode(node); - - addItem(item); - item_map_.insert(node, item); - - ConnectNode(node); - - return item; -} - -void NodeViewScene::RemoveNode(Node *node) -{ - DisconnectNode(node); - - delete item_map_.take(node); -} - NodeViewEdge* NodeViewScene::AddEdge(Node *output, const NodeInput &input) { NodeViewEdge *edge = EdgeToUIObject(output, input); @@ -202,6 +173,8 @@ NodeViewContext *NodeViewScene::AddContext(Node *node) context_item = new NodeViewContext(); context_item->SetContext(node); context_item->setPos(0, 0); + context_item->SetFlowDirection(GetFlowDirection()); + context_item->SetCurvedEdges(GetEdgesAreCurved()); addItem(context_item); const NodeGraph::PositionMap &map = node->parent()->GetNodesForContext(node); @@ -209,6 +182,8 @@ NodeViewContext *NodeViewScene::AddContext(Node *node) context_item->AddChild(it.key()); } context_item->UpdateRect(); + + context_map_.insert(node, context_item); } return context_item; @@ -250,18 +225,6 @@ NodeViewEdge* NodeViewScene::AddEdgeInternal(Node *output, const NodeInput& inpu return edge_ui; } -void NodeViewScene::ConnectNode(Node *n) -{ - connect(n, &Node::LabelChanged, this, &NodeViewScene::NodeAppearanceChanged); - connect(n, &Node::ColorChanged, this, &NodeViewScene::NodeAppearanceChanged); -} - -void NodeViewScene::DisconnectNode(Node *n) -{ - disconnect(n, &Node::ColorChanged, this, &NodeViewScene::NodeAppearanceChanged); - disconnect(n, &Node::LabelChanged, this, &NodeViewScene::NodeAppearanceChanged); -} - Qt::Orientation NodeViewScene::GetFlowOrientation() const { return NodeViewCommon::GetFlowOrientation(direction_); @@ -283,10 +246,4 @@ void NodeViewScene::SetEdgesAreCurved(bool curved) } } -void NodeViewScene::NodeAppearanceChanged() -{ - // Force item to update - item_map_.value(static_cast(sender()))->update(); -} - } diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index 4f6357dee..f37ebbdcd 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -81,22 +81,6 @@ public: } public slots: - /** - * @brief Slot when a Node is added to a graph (SetGraph() connects this) - * - * This should NEVER be called directly, only connected to a NodeGraph. To add a Node to the NodeGraph - * use NodeGraph::AddNode(). - */ - NodeViewItem *AddNode(Node* node); - - /** - * @brief Slot when a Node is removed from a graph (SetGraph() connects this) - * - * This should NEVER be called directly, only connected to a NodeGraph. To remove a Node from the NodeGraph - * use NodeGraph::RemoveNode(). - */ - void RemoveNode(Node* node); - NodeViewEdge *AddEdge(Node *output, const NodeInput& input); void RemoveEdge(Node *output, const NodeInput& input); @@ -113,10 +97,6 @@ private: NodeViewEdge* AddEdgeInternal(Node *output, const NodeInput &input, NodeViewItem* from, NodeViewItem* to); - void ConnectNode(Node *n); - - void DisconnectNode(Node *n); - QHash context_map_; QHash item_map_; @@ -129,12 +109,6 @@ private: bool curved_edges_; -private slots: - /** - * @brief Receiver for when a node's label has changed - */ - void NodeAppearanceChanged(); - }; } diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index 5f379679f..9ad1a6a74 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -397,7 +397,7 @@ void ImportTool::DropGhosts(bool insert) command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0), false)); // Position footage in its context - command->add_child(new NodeSetPositionCommand(footage_stream.footage, clip, QPointF(-2, 0), false)); + command->add_child(new NodeSetPositionCommand(footage_stream.footage, clip, QPointF(-3, 0), false)); switch (Track::Reference::TypeFromString(footage_stream.output)) { case Track::kVideo: @@ -409,7 +409,7 @@ void ImportTool::DropGhosts(bool insert) command->add_child(new NodeEdgeAddCommand(footage_stream.footage, NodeInput(transform, TransformDistortNode::kTextureInput))); command->add_child(new NodeEdgeAddCommand(transform, NodeInput(clip, ClipBlock::kBufferIn))); - command->add_child(new NodeSetPositionCommand(transform, clip, QPointF(-1, 0), false)); + command->add_child(new NodeSetPositionCommand(transform, clip, QPointF(-2, 0), false)); break; } case Track::kAudio: @@ -421,7 +421,7 @@ void ImportTool::DropGhosts(bool insert) command->add_child(new NodeEdgeAddCommand(footage_stream.footage, NodeInput(volume_node, VolumeNode::kSamplesInput))); command->add_child(new NodeEdgeAddCommand(volume_node, NodeInput(clip, ClipBlock::kBufferIn))); - command->add_child(new NodeSetPositionCommand(volume_node, clip, QPointF(-1, 0), false)); + command->add_child(new NodeSetPositionCommand(volume_node, clip, QPointF(-2, 0), false)); break; } default: From 0c92bc3f08110b63265eed8a44285c6634c1478a Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 10 Nov 2021 22:32:44 -0800 Subject: [PATCH 03/34] fixed some compile issues on non-msvc --- app/node/output/track/track.h | 2 ++ app/widget/nodeview/nodeview.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 2e9b44ab3..a7953ff09 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -189,6 +189,7 @@ public: case kSubtitle: return QStringLiteral("s"); case kCount: + case kNone: break; } @@ -206,6 +207,7 @@ public: case kSubtitle: return tr("S"); case kCount: + case kNone: break; } diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 86b1c5711..818f55095 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -1636,7 +1636,7 @@ void NodeView::GroupNodes() void NodeView::UngroupNodes() { - static_cast(selected_nodes_.first()); + //static_cast(selected_nodes_.first()); } NodeViewItem *NodeView::UpdateNodeItem(Node *node, bool ignore_own_context) From 3c6bc80c8f9db19508097cf298ac96cecb11f1d3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 12 Nov 2021 11:06:06 -0800 Subject: [PATCH 04/34] further improved new node view --- app/widget/nodeview/nodeview.cpp | 2 +- app/widget/nodeview/nodeviewcommon.h | 10 ++ app/widget/nodeview/nodeviewcontext.cpp | 19 +++- app/widget/nodeview/nodeviewcontext.h | 4 + app/widget/nodeview/nodeviewedge.cpp | 2 +- app/widget/nodeview/nodeviewitem.cpp | 121 ++++++++++++++++-------- app/widget/nodeview/nodeviewitem.h | 18 ++-- 7 files changed, 124 insertions(+), 52 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 818f55095..d5e85fcfb 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -1535,7 +1535,7 @@ void NodeView::PositionNewEdge(const QPoint &pos) if (highlight_index >= 0) { create_edge_dst_input_ = create_edge_dst_->GetInputAtIndex(highlight_index); create_edge_->SetPoints(create_edge_src_->GetOutputPoint(), - create_edge_dst_->GetInputPoint(create_edge_dst_input_.input(), create_edge_dst_input_.element(), create_edge_src_->pos()), + create_edge_dst_->GetInputPoint(create_edge_dst_input_.input(), create_edge_dst_input_.element()), true); } else { create_edge_dst_input_.Reset(); diff --git a/app/widget/nodeview/nodeviewcommon.h b/app/widget/nodeview/nodeviewcommon.h index 03c5f8dcf..d0541fba4 100644 --- a/app/widget/nodeview/nodeviewcommon.h +++ b/app/widget/nodeview/nodeviewcommon.h @@ -44,6 +44,16 @@ public: } } + static bool IsFlowVertical(FlowDirection dir) + { + return dir == kTopToBottom || dir == kBottomToTop; + } + + static bool IsFlowHorizontal(FlowDirection dir) + { + return dir == kLeftToRight || dir == kRightToLeft; + } + static bool DirectionsAreOpposing(FlowDirection a, FlowDirection b) { return ((a == NodeViewCommon::kLeftToRight && b == NodeViewCommon::kRightToLeft) || (a == NodeViewCommon::kRightToLeft && b == NodeViewCommon::kLeftToRight) diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index 0e8262b3e..db22be143 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -21,9 +22,6 @@ namespace olive { NodeViewContext::NodeViewContext(QGraphicsItem *item) : super(item) { - setFlag(ItemIsMovable); - setFlag(ItemIsSelectable); - // Set default label text SetContext(nullptr); } @@ -101,10 +99,13 @@ void NodeViewContext::UpdateRect() QFontMetricsF fm(f); qreal lbl_offset = GetTextOffset(fm); - QRectF rect = childrenBoundingRect(); + QRectF cbr = childrenBoundingRect(); + QRectF rect = cbr; int pad = NodeViewItem::DefaultItemHeight(); rect.adjust(-pad, - lbl_offset*2 - fm.height() - pad, pad, pad); setRect(rect); + + last_titlebar_height_ = rect.y() + (cbr.y() - rect.y()); } void NodeViewContext::SetFlowDirection(NodeViewCommon::FlowDirection dir) @@ -164,6 +165,16 @@ QVariant NodeViewContext::itemChange(GraphicsItemChange change, const QVariant & return super::itemChange(change, value); } +void NodeViewContext::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + bool clicked_inside_titlebar = (event->pos().y() < last_titlebar_height_); + + setFlag(ItemIsMovable, clicked_inside_titlebar); + setFlag(ItemIsSelectable, clicked_inside_titlebar); + + super::mousePressEvent(event); +} + NodeViewEdge* NodeViewContext::AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to) { NodeViewEdge* edge_ui = new NodeViewEdge(output, input, from, to, this); diff --git a/app/widget/nodeview/nodeviewcontext.h b/app/widget/nodeview/nodeviewcontext.h index a5141e32b..cf35b6fd2 100644 --- a/app/widget/nodeview/nodeviewcontext.h +++ b/app/widget/nodeview/nodeviewcontext.h @@ -30,6 +30,8 @@ public: protected: virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; + virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + private: NodeViewEdge *AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to); @@ -41,6 +43,8 @@ private: bool curved_edges_; + int last_titlebar_height_; + }; } diff --git a/app/widget/nodeview/nodeviewedge.cpp b/app/widget/nodeview/nodeviewedge.cpp index 5498fbefb..f6df8ce73 100644 --- a/app/widget/nodeview/nodeviewedge.cpp +++ b/app/widget/nodeview/nodeviewedge.cpp @@ -60,7 +60,7 @@ void NodeViewEdge::Adjust() { // Draw a line between the two SetPoints(from_item()->GetOutputPoint(), - to_item()->GetInputPoint(input_.input(), input_.element(), from_item()->pos()), + to_item()->GetInputPoint(input_.input(), input_.element()), to_item()->IsExpanded()); } diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 2eb766863..520d2c961 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -67,7 +67,6 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) : title_bar_rect_ = QRectF(-widget_width/2, -widget_height/2, widget_width, widget_height); setRect(title_bar_rect_); - input_connector_ = new NodeViewItemConnector(this); output_connector_ = new NodeViewItemConnector(this); } @@ -219,6 +218,8 @@ void NodeViewItem::SetNode(Node *n) node_inputs_.clear(); + ClearInputConnectors(); + if (node_) { node_->Retranslate(); @@ -228,7 +229,7 @@ void NodeViewItem::SetNode(Node *n) } } - input_connector_->setVisible(!node_inputs_.isEmpty()); + UpdateInputConnectors(); connect(n, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); connect(n, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); @@ -246,7 +247,6 @@ void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) expanded_ = e; hide_titlebar_ = hide_titlebar; - input_connector_->setVisible(!expanded_); if (expanded_ && !node_inputs_.isEmpty()) { // Create new rect @@ -263,13 +263,15 @@ void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) setRect(title_bar_rect_); } - update(); + UpdateInputConnectorFlowDirections(); UpdateConnectorPositions(); ReadjustAllEdges(); UpdateContextRect(); + + update(); } void NodeViewItem::ToggleExpanded() @@ -403,6 +405,8 @@ QVariant NodeViewItem::itemChange(QGraphicsItem::GraphicsItemChange change, cons void NodeViewItem::ReadjustAllEdges() { + UpdateInputConnectorFlowDirections(); + UpdateInputConnectorPositions(); foreach (NodeViewEdge* edge, edges_) { edge->Adjust(); } @@ -502,13 +506,15 @@ QRectF NodeViewItem::GetInputRect(int index) const return r; } -QPointF NodeViewItem::GetInputPoint(const QString &input, int element, const QPointF& source_pos) const +QPointF NodeViewItem::GetInputPoint(const QString &input, int element) const { - if (expanded_) { - return pos() + GetInputPointInternal(node_inputs_.indexOf(input), source_pos); - } else { - return pos() + input_connector_->pos(); + int index = node_inputs_.indexOf(input); + + if (index < 0 || index >= int(input_connectors_.size())) { + return QPointF(); } + + return pos() + input_connectors_[index]->pos(); } QPointF NodeViewItem::GetOutputPoint() const @@ -539,64 +545,101 @@ void NodeViewItem::SetFlowDirection(NodeViewCommon::FlowDirection dir) { flow_dir_ = dir; - input_connector_->SetFlowDirection(dir); + UpdateInputConnectorFlowDirections(); output_connector_->SetFlowDirection(dir); UpdateConnectorPositions(); UpdateNodePosition(); } -QPointF NodeViewItem::GetInputPointInternal(int index, const QPointF& source_pos) const -{ - QRectF input_rect = GetInputRect(index); - - Qt::Orientation flow_orientation = NodeViewCommon::GetFlowOrientation(flow_dir_); - - if (flow_orientation == Qt::Horizontal || IsExpanded()) { - if (flow_dir_ == NodeViewCommon::kLeftToRight - || (flow_orientation == Qt::Vertical && source_pos.x() < pos().x())) { - return QPointF(input_rect.left(), input_rect.center().y()); - } else { - return QPointF(input_rect.right(), input_rect.center().y()); - } - } else { - if (flow_dir_ == NodeViewCommon::kTopToBottom) { - return QPointF(input_rect.center().x(), input_rect.top()); - } else { - return QPointF(input_rect.center().x(), input_rect.bottom()); - } - } -} - void NodeViewItem::UpdateNodePosition() { setPos(NodeToScreenPoint(cached_node_pos_, flow_dir_)); } +void NodeViewItem::UpdateInputConnectors() +{ + int old_sz = input_connectors_.size(); + + input_connectors_.resize(node_inputs_.size()); + for (size_t i=old_sz; i(this); + } +} + +void NodeViewItem::ClearInputConnectors() +{ + input_connectors_.clear(); +} + void NodeViewItem::UpdateConnectorPositions() { - QRectF output_rect = output_connector_->boundingRect(); + UpdateInputConnectorPositions(); switch (flow_dir_) { case NodeViewCommon::kLeftToRight: - input_connector_->setPos(rect().left() - output_rect.width(), rect().center().y()); - output_connector_->setPos(rect().right(), rect().center().y()); + output_connector_->setPos(rect().right(), title_bar_rect_.center().y()); break; case NodeViewCommon::kRightToLeft: - input_connector_->setPos(rect().right() + output_rect.width(), rect().center().y()); - output_connector_->setPos(rect().left(), rect().center().y()); + output_connector_->setPos(rect().left(), title_bar_rect_.center().y()); break; case NodeViewCommon::kTopToBottom: - input_connector_->setPos(rect().center().x(), rect().top() - output_rect.height()); output_connector_->setPos(rect().center().x(), rect().bottom()); break; case NodeViewCommon::kBottomToTop: - input_connector_->setPos(rect().center().x(), rect().bottom() + output_rect.height()); output_connector_->setPos(rect().center().x(), rect().top()); break; } } +void NodeViewItem::UpdateInputConnectorPositions() +{ + QRectF output_rect = output_connector_->boundingRect(); + + // Input connector flow directions change conditionally + for (size_t i=0; isetPos(rect().left() - output_rect.width(), GetInputRect(i).center().y()); + break; + case NodeViewCommon::kRightToLeft: + input_connectors_[i]->setPos(rect().right() + output_rect.width(), GetInputRect(i).center().y()); + break; + case NodeViewCommon::kTopToBottom: + input_connectors_[i]->setPos(rect().center().x(), rect().top() - output_rect.height()); + break; + case NodeViewCommon::kBottomToTop: + input_connectors_[i]->setPos(rect().center().x(), rect().bottom() + output_rect.height()); + break; + } + } +} + +void NodeViewItem::UpdateInputConnectorFlowDirections() +{ + for (size_t i=0; iSetFlowDirection(GetFlowDirectionForInput(i)); + } +} + +NodeViewCommon::FlowDirection NodeViewItem::GetFlowDirectionForInput(int index) +{ + if (!expanded_ || NodeViewCommon::IsFlowHorizontal(flow_dir_)) { + return flow_dir_; + } else { + foreach (NodeViewEdge *edge, edges_) { + if (edge->input().input() == node_inputs_.at(index)) { + if (edge->from_item()->x() < this->x()) { + return NodeViewCommon::kLeftToRight; + } else { + return NodeViewCommon::kRightToLeft; + } + } + } + return NodeViewCommon::kLeftToRight; + } +} + void NodeViewItem::NodeAppearanceChanged() { update(); diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index bd2cd7ac1..c1ff35387 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -80,7 +80,7 @@ public: /** * @brief Returns GLOBAL point that edges should connect to for any NodeParam member of this object */ - QPointF GetInputPoint(const QString& input, int element, const QPointF &source_pos) const; + QPointF GetInputPoint(const QString& input, int element) const; QPointF GetOutputPoint() const; @@ -156,18 +156,22 @@ private: */ QRectF GetInputRect(int index) const; - /** - * @brief Returns local point that edges should connect to for a NodeInput in array node_inputs_[index] - */ - QPointF GetInputPointInternal(int index, const QPointF &source_pos) const; - /** * @brief Internal update function when logical position changes */ void UpdateNodePosition(); + void UpdateInputConnectors(); + + void ClearInputConnectors(); + void UpdateConnectorPositions(); + void UpdateInputConnectorPositions(); + void UpdateInputConnectorFlowDirections(); + + NodeViewCommon::FlowDirection GetFlowDirectionForInput(int index); + /** * @brief Reference to attached Node */ @@ -203,7 +207,7 @@ private: bool prevent_removing_; - NodeViewItemConnector *input_connector_; + std::vector > input_connectors_; NodeViewItemConnector *output_connector_; bool label_as_output_; From 854ca0a7a9f77a42211a6fd826645b77f01b5752 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 12 Nov 2021 11:51:13 -0800 Subject: [PATCH 05/34] reimplemented edge connect/disconnect --- app/widget/nodeview/nodeview.cpp | 35 +++++------ app/widget/nodeview/nodeviewedge.cpp | 23 +------ app/widget/nodeview/nodeviewedge.h | 11 ---- app/widget/nodeview/nodeviewitem.cpp | 61 ++++++++++++------- app/widget/nodeview/nodeviewitem.h | 11 ++-- app/widget/nodeview/nodeviewitemconnector.cpp | 5 +- app/widget/nodeview/nodeviewitemconnector.h | 11 +++- 7 files changed, 75 insertions(+), 82 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index d5e85fcfb..31be310c6 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -473,30 +473,27 @@ void NodeView::mousePressEvent(QMouseEvent *event) { if (HandPress(event)) return; + QGraphicsItem* item = itemAt(event->pos()); + if (event->button() == Qt::LeftButton) { - // See if we're dragging the arrow of an edge - QPointF scene_pt = mapToScene(event->pos()); - - for (NodeViewEdge *edge_item : scene_.edges()) { - if (edge_item->arrow_bounding_rect().contains(scene_pt)) { - create_edge_src_ = scene_.NodeToUIObject(edge_item->output()); - create_edge_ = edge_item; - create_edge_already_exists_ = true; + // Determine if user clicked on a connector + if (NodeViewItemConnector *connector = dynamic_cast(item)) { + NodeViewItem *attached_item = static_cast(connector->parentItem()); + if (connector->IsOutput()) { + CreateNewEdge(attached_item, event->pos()); return; - } - } - - // See if we're dragging the arrow of a node - for (NodeViewItem *node_item : scene_.item_map()) { - if (node_item->GetOutputTriangle().boundingRect().translated(node_item->pos()).contains(scene_pt)) { - CreateNewEdge(node_item, event->pos()); + } else { + NodeViewEdge *edge_item = attached_item->GetEdgeFromInputConnector(connector); + if (edge_item) { + create_edge_src_ = edge_item->from_item(); + create_edge_ = edge_item; + create_edge_already_exists_ = true; + } return; } } } - QGraphicsItem* item = itemAt(event->pos()); - if (event->button() == Qt::RightButton) { if (!item || !item->isSelected()) { // Qt doesn't do this by default for some reason @@ -668,13 +665,13 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } // Update contexts - if (!removed_edges.empty()) { + /*if (!removed_edges.empty()) { UpdateContextsFromEdgeRemove(command, removed_edges); } if (added_edge.first) { UpdateContextsFromEdgeAdd(command, added_edge, removed_edges); - } + }*/ Core::instance()->undo_stack()->pushIfHasChildren(command); return; diff --git a/app/widget/nodeview/nodeviewedge.cpp b/app/widget/nodeview/nodeviewedge.cpp index f6df8ce73..977b88cbd 100644 --- a/app/widget/nodeview/nodeviewedge.cpp +++ b/app/widget/nodeview/nodeviewedge.cpp @@ -126,13 +126,6 @@ void NodeViewEdge::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti painter->setPen(QPen(edge_color, edge_width_)); painter->setBrush(Qt::NoBrush); painter->drawPath(path()); - - // Draw arrow - if (!connected_) { - painter->setPen(Qt::NoPen); - painter->setBrush(edge_color); - painter->drawPolygon(arrow_); - } } void NodeViewEdge::Init() @@ -149,7 +142,6 @@ void NodeViewEdge::Init() // Use font metrics to set edge width for basic high DPI support edge_width_ = QFontMetrics(QFont()).height() / 12; - arrow_size_ = QFontMetrics(QFont()).height() / 2; } void NodeViewEdge::UpdateCurve() @@ -185,7 +177,7 @@ void NodeViewEdge::UpdateCurve() path.cubicTo(cp1, cp2, end); if (!qFuzzyCompare(start.x(), end.x())) { - double continue_x = end.x() - qCos(angle)*arrow_size_; + double continue_x = end.x() - qCos(angle); double x1 = start.x(); double x2 = cp1.x(); @@ -215,18 +207,7 @@ void NodeViewEdge::UpdateCurve() } - setPath(path); - - const double arrow_angle = 150.0 * M_PI / 180.0; - QVector arrow_points(4); - arrow_points[0] = end; - arrow_points[1] = end + QPointF(qCos(angle + arrow_angle) * arrow_size_, qSin(angle + arrow_angle) * arrow_size_); - arrow_points[2] = end + QPointF(qCos(angle - arrow_angle) * arrow_size_, qSin(angle - arrow_angle) * arrow_size_); - arrow_points[3] = end; - - arrow_ = QPolygonF(arrow_points); - arrow_bounding_rect_ = arrow_.boundingRect(); - arrow_bounding_rect_.adjust(-arrow_size_, -arrow_size_, arrow_size_, arrow_size_); + setPath(mapFromScene(path)); } } diff --git a/app/widget/nodeview/nodeviewedge.h b/app/widget/nodeview/nodeviewedge.h index 6240aeb64..de8d10720 100644 --- a/app/widget/nodeview/nodeviewedge.h +++ b/app/widget/nodeview/nodeviewedge.h @@ -70,11 +70,6 @@ public: return to_item_; } - const QRectF arrow_bounding_rect() const - { - return arrow_bounding_rect_; - } - void Adjust(); /** @@ -144,12 +139,6 @@ private: bool curved_; - QPolygonF arrow_; - - int arrow_size_; - - QRectF arrow_bounding_rect_; - QPointF cached_start_; QPointF cached_end_; bool cached_input_is_expanded_; diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 520d2c961..b3e4ee4e9 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -67,7 +67,7 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) : title_bar_rect_ = QRectF(-widget_width/2, -widget_height/2, widget_width, widget_height); setRect(title_bar_rect_); - output_connector_ = new NodeViewItemConnector(this); + output_connector_ = new NodeViewItemConnector(true, this); } QPointF NodeViewItem::GetNodePosition() const @@ -196,7 +196,7 @@ void NodeViewItem::RemoveEdge(NodeViewEdge *edge) int NodeViewItem::GetIndexAt(QPointF pt) const { - pt -= pos(); + pt -= this->scenePos(); for (int i=0; iRetranslate(); @@ -473,6 +472,17 @@ void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& text); } +NodeViewEdge *NodeViewItem::GetEdgeFromInputIndex(int index) +{ + foreach (NodeViewEdge *edge, edges_) { + if (edge->input().input() == node_inputs_.at(index)) { + return edge; + } + } + + return nullptr; +} + void NodeViewItem::SetHighlightedIndex(int index) { if (highlighted_index_ == index) { @@ -491,6 +501,23 @@ void NodeViewItem::SetLabelAsOutput(bool e) update(); } +NodeViewEdge *NodeViewItem::GetEdgeFromInputConnector(NodeViewItemConnector *connector) +{ + ssize_t index = -1; + for (ssize_t i=0; ipos(); + return input_connectors_[index]->scenePos(); } QPointF NodeViewItem::GetOutputPoint() const { - QPointF p = pos() + output_connector_->pos(); + QPointF p = output_connector_->scenePos(); QRectF r = output_connector_->boundingRect(); switch (flow_dir_) { @@ -563,15 +590,10 @@ void NodeViewItem::UpdateInputConnectors() input_connectors_.resize(node_inputs_.size()); for (size_t i=old_sz; i(this); + input_connectors_[i] = std::make_unique(false, this); } } -void NodeViewItem::ClearInputConnectors() -{ - input_connectors_.clear(); -} - void NodeViewItem::UpdateConnectorPositions() { UpdateInputConnectorPositions(); @@ -627,16 +649,13 @@ NodeViewCommon::FlowDirection NodeViewItem::GetFlowDirectionForInput(int index) if (!expanded_ || NodeViewCommon::IsFlowHorizontal(flow_dir_)) { return flow_dir_; } else { - foreach (NodeViewEdge *edge, edges_) { - if (edge->input().input() == node_inputs_.at(index)) { - if (edge->from_item()->x() < this->x()) { - return NodeViewCommon::kLeftToRight; - } else { - return NodeViewCommon::kRightToLeft; - } - } + NodeViewEdge *edge = GetEdgeFromInputIndex(index); + + if (!edge || edge->from_item()->x() < this->x()) { + return NodeViewCommon::kLeftToRight; + } else { + return NodeViewCommon::kRightToLeft; } - return NodeViewCommon::kLeftToRight; } } diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index c1ff35387..0f11574ed 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -127,13 +127,10 @@ public: return prevent_removing_; } - QPolygonF GetOutputTriangle() const - { - return output_connector_->polygon(); - } - void SetLabelAsOutput(bool e); + NodeViewEdge *GetEdgeFromInputConnector(NodeViewItemConnector *connector); + protected: virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; @@ -151,6 +148,8 @@ private: void DrawNodeTitle(QPainter *painter, QString text, const QRectF &rect, Qt::Alignment vertical_align, int icon_size, bool draw_arrow); + NodeViewEdge *GetEdgeFromInputIndex(int index); + /** * @brief Returns local rect of a NodeInput in array node_inputs_[index] */ @@ -163,8 +162,6 @@ private: void UpdateInputConnectors(); - void ClearInputConnectors(); - void UpdateConnectorPositions(); void UpdateInputConnectorPositions(); diff --git a/app/widget/nodeview/nodeviewitemconnector.cpp b/app/widget/nodeview/nodeviewitemconnector.cpp index 650c33ccf..2ab919d77 100644 --- a/app/widget/nodeview/nodeviewitemconnector.cpp +++ b/app/widget/nodeview/nodeviewitemconnector.cpp @@ -29,8 +29,9 @@ namespace olive { -NodeViewItemConnector::NodeViewItemConnector(QGraphicsItem *parent) : - QGraphicsPolygonItem(parent) +NodeViewItemConnector::NodeViewItemConnector(bool is_output, QGraphicsItem *parent) : + QGraphicsPolygonItem(parent), + output_(is_output) { QColor c = qApp->palette().text().color(); setPen(QPen(c, NodeViewItem::DefaultItemBorder())); diff --git a/app/widget/nodeview/nodeviewitemconnector.h b/app/widget/nodeview/nodeviewitemconnector.h index 6dfc7d9d3..4da1e2fca 100644 --- a/app/widget/nodeview/nodeviewitemconnector.h +++ b/app/widget/nodeview/nodeviewitemconnector.h @@ -30,9 +30,18 @@ namespace olive { class NodeViewItemConnector : public QGraphicsPolygonItem { public: - NodeViewItemConnector(QGraphicsItem *parent = nullptr); + NodeViewItemConnector(bool is_output, QGraphicsItem *parent = nullptr); void SetFlowDirection(NodeViewCommon::FlowDirection dir); + + bool IsOutput() const + { + return output_; + } + +private: + bool output_; + }; } From 93be94bc2cb89fdf97e7b4131ac0da22c4e1390e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 16 Nov 2021 23:58:34 -0800 Subject: [PATCH 06/34] more work --- app/core.cpp | 2 +- app/node/graph.cpp | 53 +- app/node/graph.h | 53 -- app/node/node.cpp | 189 ++--- app/node/node.h | 178 +---- app/node/nodecopypaste.cpp | 6 +- app/node/output/track/track.cpp | 2 +- app/node/param.cpp | 9 + app/node/param.h | 2 + app/node/project/folder/folder.cpp | 22 +- app/node/project/folder/folder.h | 8 +- app/node/project/project.cpp | 27 +- app/node/project/sequence/sequence.cpp | 2 +- app/panel/node/node.cpp | 1 + app/panel/node/node.h | 20 +- app/widget/nodeview/nodeview.cpp | 661 ++---------------- app/widget/nodeview/nodeview.h | 57 +- app/widget/nodeview/nodeviewcontext.cpp | 162 +++-- app/widget/nodeview/nodeviewcontext.h | 24 +- app/widget/nodeview/nodeviewedge.cpp | 14 + app/widget/nodeview/nodeviewedge.h | 2 + app/widget/nodeview/nodeviewitem.cpp | 12 +- app/widget/nodeview/nodeviewitem.h | 4 +- app/widget/nodeview/nodeviewminimap.cpp | 10 +- app/widget/nodeview/nodeviewminimap.h | 2 + app/widget/nodeview/nodeviewscene.cpp | 60 +- app/widget/nodeview/nodeviewscene.h | 5 - app/widget/timelinewidget/tool/add.cpp | 6 +- app/widget/timelinewidget/tool/import.cpp | 14 +- app/widget/timelinewidget/tool/tool.cpp | 2 + app/widget/timelinewidget/tool/tool.h | 2 + app/widget/timelinewidget/tool/transition.cpp | 8 +- .../undo/timelineundogeneral.cpp | 171 ++--- .../timelinewidget/undo/timelineundogeneral.h | 10 +- .../undo/timelineundopointer.cpp | 21 - .../timelinewidget/undo/timelineundopointer.h | 1 - .../timelinewidget/undo/timelineundosplit.cpp | 8 - .../timelinewidget/undo/timelineundosplit.h | 6 +- app/window/mainwindow/mainwindow.cpp | 14 +- 39 files changed, 493 insertions(+), 1357 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index 48830651a..82a6b98fc 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -437,7 +437,7 @@ void Core::CreateNewSequence() command->add_child(new NodeAddCommand(active_project, new_sequence)); command->add_child(new FolderAddChild(GetSelectedFolderInActiveProject(), new_sequence)); - command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, QPointF(0, 0), false)); + command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, QPointF(0, 0))); // Create and connect default nodes to new sequence new_sequence->add_default_nodes(command); diff --git a/app/node/graph.cpp b/app/node/graph.cpp index a2d04b233..f652340fb 100644 --- a/app/node/graph.cpp +++ b/app/node/graph.cpp @@ -44,45 +44,6 @@ void NodeGraph::Clear() } } -qreal NodeGraph::GetNodeContextHeight(Node *context) -{ - const PositionMap &map = position_map_.value(context); - - qreal top = 0, bottom = 0; - - foreach (const QPointF &pt, map) { - top = qMin(pt.y(), top); - bottom = qMax(pt.y(), bottom); - } - - return bottom - top; -} - -int NodeGraph::GetNumberOfContextsNodeIsIn(Node *node) const -{ - int count = 0; - - for (auto it=position_map_.cbegin(); it!=position_map_.cend(); it++) { - if (it.value().contains(node)) { - count++; - } - } - - return count; -} - -bool NodeGraph::NodeOutputsToContext(Node *node) const -{ - for (auto it=position_map_.cbegin(); it!=position_map_.cend(); it++) { - const PositionMap &pm = it.value(); - if (pm.contains(node) && node->OutputsTo(it.key(), true)) { - return true; - } - } - - return false; -} - void NodeGraph::childEvent(QChildEvent *event) { super::childEvent(event); @@ -116,18 +77,10 @@ void NodeGraph::childEvent(QChildEvent *event) emit NodeRemoved(node); emit node->RemovedFromGraph(this); - for (auto it=position_map_.begin(); it!=position_map_.end(); it++) { - PositionMap &map = it.value(); - for (auto jt=map.begin(); jt!=map.end(); ) { - if (jt.key() == node) { - jt = map.erase(jt); - emit NodePositionRemoved(node, it.key()); - } else { - jt++; - } - } + // Remove from any contexts + foreach (Node *context, node_children_) { + context->RemoveNodeFromContext(node); } - } } } diff --git a/app/node/graph.h b/app/node/graph.h index 5e0aa5be2..21c8f023e 100644 --- a/app/node/graph.h +++ b/app/node/graph.h @@ -63,55 +63,6 @@ public: return default_nodes_; } - bool NodeMapContainsNode(Node* node, Node* context) const - { - return position_map_.value(context).contains(node); - } - - QPointF GetNodePosition(Node* node, Node* context) - { - return position_map_.value(context).value(node); - } - - void SetNodePosition(Node* node, Node* context, const QPointF& pos) - { - position_map_[context].insert(node, pos); - emit NodePositionAdded(node, context, pos); - } - - void RemoveNodePosition(Node* node, Node* context) - { - PositionMap& map = position_map_[context]; - map.remove(node); - if (map.isEmpty()) { - position_map_.remove(context); - } - emit NodePositionRemoved(node, context); - } - - bool ContextContainsNode(Node *node, Node *context) - { - return position_map_[context].contains(node); - } - - qreal GetNodeContextHeight(Node *context); - - using PositionMap = QHash; - - const PositionMap &GetNodesForContext(Node *context) - { - return position_map_[context]; - } - - const QMap &GetPositionMap() const - { - return position_map_; - } - - int GetNumberOfContextsNodeIsIn(Node *node) const; - - bool NodeOutputsToContext(Node *node) const; - signals: /** * @brief Signal emitted when a Node is added to the graph @@ -148,10 +99,6 @@ private: QVector default_nodes_; - QMap position_map_; - - PositionMap root_position_map_; - }; } diff --git a/app/node/node.cpp b/app/node/node.cpp index d79710962..b1b45ccf6 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -243,6 +243,36 @@ QIcon Node::icon() const return icon::New; } +QPointF Node::GetNodePositionInContext(Node *node) +{ + return context_positions_.value(node); +} + +bool Node::SetNodePositionInContext(Node *node, const QPointF &pos) +{ + bool added = !ContextContainsNode(node); + context_positions_.insert(node, pos); + + if (added) { + emit NodeAddedToContext(node); + } + + emit NodePositionInContextChanged(node, pos); + + return added; +} + +bool Node::RemoveNodeFromContext(Node *node) +{ + if (ContextContainsNode(node)) { + context_positions_.remove(node); + emit NodeRemovedFromContext(node); + return true; + } else { + return false; + } +} + Color Node::color() const { int c; @@ -429,6 +459,11 @@ void Node::SaveInput(QXmlStreamWriter *writer, const QString &id) const writer->writeEndElement(); // subelements } +bool Node::IsInputHidden(const QString &input) const +{ + return (GetInputFlags(input) & kInputFlagHidden); +} + bool Node::IsInputConnectable(const QString &input) const { return !(GetInputFlags(input) & kInputFlagNotConnectable); @@ -1196,13 +1231,10 @@ Node *Node::CopyNodeAndDependencyGraphMinusItemsInternal(QMap& cre command->add_child(new NodeSetValueHintCommand(copied_input, node->GetValueHintForInput(input.input(), input.element()))); } - if (node->parent()->GetPositionMap().contains(node)) { - // This node is a context, copy the context - const NodeGraph::PositionMap &map = node->parent()->GetPositionMap().value(node); - for (auto it=map.cbegin(); it!=map.cend(); it++) { - // Add either the copy (if it exists) or the original node to the context - command->add_child(new NodeSetPositionCommand(created.value(it.key(), it.key()), copy, it.value(), false)); - } + const PositionMap &map = node->GetContextPositions(); + for (auto it=map.cbegin(); it!=map.cend(); it++) { + // Add either the copy (if it exists) or the original node to the context + command->add_child(new NodeSetPositionCommand(created.value(it.key(), it.key()), copy, it.value())); } return copy; @@ -1229,13 +1261,10 @@ Node *Node::CopyNodeInGraph(Node *node, MultiUndoCommand *command) command->add_child(new NodeCopyInputsCommand(node, copy, true)); - if (node->parent()->GetPositionMap().contains(node)) { - // This node is a context, copy the context - const NodeGraph::PositionMap &map = node->parent()->GetPositionMap().value(node); - for (auto it=map.cbegin(); it!=map.cend(); it++) { - // Add to the context - command->add_child(new NodeSetPositionCommand(it.key(), copy, it.value(), false)); - } + const PositionMap &map = node->GetContextPositions(); + for (auto it=map.cbegin(); it!=map.cend(); it++) { + // Add to the context + command->add_child(new NodeSetPositionCommand(it.key(), copy, it.value())); } } @@ -2318,119 +2347,58 @@ Project *Node::ArrayResizeCommand::GetRelevantProject() const return node_->project(); } -void NodeSetPositionAndShiftSurroundingsCommand::redo() -{ - if (commands_.isEmpty()) { - // Move first node - NodeSetPositionCommand* set_pos_command = new NodeSetPositionCommand(node_, relative_, position_, move_dependencies_); - set_pos_command->redo_now(); - commands_.append(set_pos_command); - - // Get bounding rect - qreal bounding_rect_sz = 1.0; - qreal bounding_rect_half_sz = bounding_rect_sz * 0.5; - QRectF bounding_rect(position_.x() - bounding_rect_half_sz, position_.y() - bounding_rect_half_sz, bounding_rect_sz, bounding_rect_sz); - - // Start moving other nodes - foreach (Node* surrounding, node_->parent()->nodes()) { - if (surrounding != node_) { - QPointF surrounding_position = node_->parent()->GetNodePosition(surrounding, relative_); - if (bounding_rect.contains(surrounding_position)) { - QPointF new_pos = surrounding_position; - - qreal move_rate = 0.50; - - if (surrounding_position.y() < position_.y()) { - move_rate = -move_rate; - } - - new_pos.setY(new_pos.y() + move_rate); - - auto sur_command = new NodeSetPositionAndShiftSurroundingsCommand(surrounding, relative_, new_pos, true); - sur_command->redo(); - commands_.append(sur_command); - } - } - } - } else { - for (int i=0; iredo_now(); - } - } -} - void NodeSetPositionCommand::redo() { - graph_ = node_->parent(); - if (!(added_ = !graph_->NodeMapContainsNode(node_, relevant_))) { - old_pos_ = graph_->GetNodePosition(node_, relevant_); + if (!(added_ = !context_->ContextContainsNode(node_))) { + old_pos_ = context_->GetNodePositionInContext(node_); + } + + if (added_) { + context_->SetNodePositionInContext(node_, pos_); + } else { + move(context_, node_, pos_ - old_pos_, move_deps_); } - graph_->SetNodePosition(node_, relevant_, pos_); } void NodeSetPositionCommand::undo() { if (added_) { - graph_->RemoveNodePosition(node_, relevant_); + context_->RemoveNodeFromContext(node_); } else { - graph_->SetNodePosition(node_, relevant_, old_pos_); + move(context_, node_, old_pos_ - pos_, move_deps_); } } -void NodeSetPositionAsChildCommand::redo() +void NodeSetPositionCommand::move(Node *context, Node *node, const QPointF &diff, bool recursive) { - if (!sub_command_) { - // Calculate position of node - NodeGraph *graph = parent_->parent(); - QPointF pos = graph->GetNodePosition(parent_, relative_); + QPointF p = context->GetNodePositionInContext(node); + p += diff; + context->SetNodePositionInContext(node, p); - // This is a dependency, so we'll place it one X before - pos.setX(pos.x() - 1); - - // The Y will be calculated using the index and child count - pos.setY(pos.y() - (double(child_count_)*0.5) + this_index_ + 0.5); - - sub_command_ = new MultiUndoCommand(); - if (shift_surroundings_) { - sub_command_->add_child(new NodeSetPositionAndShiftSurroundingsCommand(node_, relative_, pos, true)); - } else { - sub_command_->add_child(new NodeSetPositionCommand(node_, relative_, pos, true)); + if (recursive) { + for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { + Node *output = it->second; + if (context->ContextContainsNode(output)) { + move(context, output, diff, recursive); + } } } - - sub_command_->redo_now(); -} - -void NodeSetPositionToOffsetOfAnotherNodeCommand::redo() -{ - NodeGraph *graph = node_->parent(); - old_pos_ = graph->GetNodePosition(node_, relative_); - graph->SetNodePosition(node_, relative_, graph->GetNodePosition(other_node_, relative_) + offset_); -} - -void NodeSetPositionToOffsetOfAnotherNodeCommand::undo() -{ - NodeGraph *graph = node_->parent(); - graph->SetNodePosition(node_, relative_, old_pos_); } void NodeRemovePositionFromContextCommand::redo() { - NodeGraph *graph = node_->parent(); - - contained_ = graph->ContextContainsNode(node_, context_); + contained_ = context_->ContextContainsNode(node_); if (contained_) { - old_pos_ = graph->GetNodePosition(node_, context_); - graph->RemoveNodePosition(node_, context_); + old_pos_ = context_->GetNodePositionInContext(node_); + context_->RemoveNodeFromContext(node_); } } void NodeRemovePositionFromContextCommand::undo() { if (contained_) { - NodeGraph *graph = node_->parent(); - graph->SetNodePosition(node_, context_, old_pos_); + context_->SetNodePositionInContext(node_, old_pos_); } } @@ -2438,28 +2406,21 @@ void NodeRemovePositionFromAllContextsCommand::redo() { NodeGraph *graph = node_->parent(); - if (points_.empty()) { - // No points yet, let's see what points we should remove - auto map = graph->GetPositionMap(); - for (auto it=map.cbegin(); it!=map.cend(); it++) { - if (it.value().contains(node_)) { - points_.insert({it.key(), it.value().value(node_)}); - } + foreach (Node* context, graph->nodes()) { + if (context->ContextContainsNode(node_)) { + contexts_.insert({context, context->GetNodePositionInContext(node_)}); + context->RemoveNodeFromContext(node_); } } - - for (auto it=points_.cbegin(); it!=points_.cend(); it++) { - graph->RemoveNodePosition(node_, it->first); - } } void NodeRemovePositionFromAllContextsCommand::undo() { - NodeGraph *graph = node_->parent(); - - for (auto it=points_.crbegin(); it!=points_.crend(); it++) { - graph->SetNodePosition(node_, it->first, it->second); + for (auto it = contexts_.crbegin(); it != contexts_.crend(); it++) { + it->first->SetNodePositionInContext(node_, it->second); } + + contexts_.clear(); } void Node::ValueHint::Hash(QCryptographicHash &hash) const diff --git a/app/node/node.h b/app/node/node.h index b22af3fff..fceb08d1c 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -208,6 +208,23 @@ public: return HasInputWithID(id); } + using PositionMap = QHash; + const PositionMap &GetContextPositions() const + { + return context_positions_; + } + + bool ContextContainsNode(Node *node) const + { + return context_positions_.contains(node); + } + + QPointF GetNodePositionInContext(Node *node); + + bool SetNodePositionInContext(Node *node, const QPointF &pos); + + bool RemoveNodeFromContext(Node *node); + /** * @brief Retrieve the color of this node */ @@ -248,6 +265,7 @@ public: void LoadInput(QXmlStreamReader* reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled); void SaveInput(QXmlStreamWriter* writer, const QString& id) const; + bool IsInputHidden(const QString& input) const; bool IsInputConnectable(const QString& input) const; bool IsInputKeyframable(const QString& input) const; @@ -867,7 +885,8 @@ protected: kInputFlagNormal = 0x0, kInputFlagArray = 0x1, kInputFlagNotKeyframable = 0x2, - kInputFlagNotConnectable = 0x4 + kInputFlagNotConnectable = 0x4, + kInputFlagHidden = 0x8 }; class InputFlags { @@ -1037,6 +1056,12 @@ signals: void RemovedFromGraph(NodeGraph* graph); + void NodeAddedToContext(Node *node); + + void NodePositionInContextChanged(Node *node, const QPointF &pos); + + void NodeRemovedFromContext(Node *node); + private: class ArrayInsertCommand : public UndoCommand { @@ -1258,6 +1283,8 @@ private: QMap value_hints_; + PositionMap context_positions_; + private slots: /** * @brief Slot when a keyframe's time changes to keep the keyframes correctly sorted by time @@ -1367,10 +1394,10 @@ using NodePtr = std::shared_ptr; class NodeSetPositionCommand : public UndoCommand { public: - NodeSetPositionCommand(Node* node, Node* relevant, const QPointF& pos, bool move_dependencies_relatively) + NodeSetPositionCommand(Node* node, Node* context, const QPointF& pos, bool move_dependencies_relatively = false) { node_ = node; - relevant_ = relevant; + context_ = context; pos_ = pos; move_deps_ = move_dependencies_relatively; } @@ -1386,151 +1413,14 @@ protected: virtual void undo() override; private: + static void move(Node *context, Node *node, const QPointF &diff, bool recursive); + Node* node_; - Node* relevant_; + Node* context_; QPointF pos_; QPointF old_pos_; bool added_; bool move_deps_; - NodeGraph *graph_; - -}; - -class NodeSetPositionAndShiftSurroundingsCommand : public UndoCommand -{ -public: - NodeSetPositionAndShiftSurroundingsCommand(Node* node, Node *relative, const QPointF& pos, bool move_dependencies_relatively) : - node_(node), - relative_(relative), - position_(pos), - move_dependencies_(move_dependencies_relatively) - {} - - virtual ~NodeSetPositionAndShiftSurroundingsCommand() override - { - qDeleteAll(commands_); - } - - virtual Project * GetRelevantProject() const override - { - return node_->project(); - } - -protected: - virtual void redo() override; - - virtual void undo() override - { - for (int i=commands_.size()-1; i>=0; i--) { - commands_.at(i)->undo_now(); - } - } - -private: - Node* node_; - - Node *relative_; - - QPointF position_; - - bool move_dependencies_; - - QVector commands_; - -}; - -class NodeSetPositionAsChildCommand : public UndoCommand -{ -public: - NodeSetPositionAsChildCommand(Node* node, Node* parent, Node *relative, double this_index, int child_count, bool shift_surroundings) : - node_(node), - parent_(parent), - relative_(relative), - this_index_(this_index), - child_count_(child_count), - shift_surroundings_(shift_surroundings), - sub_command_(nullptr) - { - } - - virtual ~NodeSetPositionAsChildCommand() override - { - delete sub_command_; - } - - virtual Project * GetRelevantProject() const override - { - return node_->project(); - } - -protected: - virtual void redo() override; - - virtual void undo() override - { - sub_command_->undo_now(); - } - -private: - Node* node_; - Node* parent_; - Node *relative_; - - double this_index_; - int child_count_; - - bool shift_surroundings_; - - MultiUndoCommand* sub_command_; - -}; - -class NodePositionCloseChildGapCommand : public UndoCommand -{ -public: - NodePositionCloseChildGapCommand(Node *parent, void *relative, int remove_index, int child_count, bool shift_surroundings); - - virtual Project * GetRelevantProject() const override - { - return parent_->project(); - } - -protected: - virtual void redo() override; - - virtual void undo() override; - -private: - Node *parent_; - -}; - -class NodeSetPositionToOffsetOfAnotherNodeCommand : public UndoCommand -{ -public: - NodeSetPositionToOffsetOfAnotherNodeCommand(Node* node, Node* other_node, Node *relative, const QPointF& offset) : - node_(node), - other_node_(other_node), - relative_(relative), - offset_(offset) - {} - - virtual Project * GetRelevantProject() const override - { - return node_->project(); - } - -protected: - virtual void redo() override; - - virtual void undo() override; - -private: - Node* node_; - Node* other_node_; - Node *relative_; - QPointF offset_; - QPointF old_pos_; }; @@ -1585,7 +1475,7 @@ protected: private: Node *node_; - std::map points_; + std::map contexts_; }; diff --git a/app/node/nodecopypaste.cpp b/app/node/nodecopypaste.cpp index 2d66d72ae..0de513820 100644 --- a/app/node/nodecopypaste.cpp +++ b/app/node/nodecopypaste.cpp @@ -53,11 +53,11 @@ void NodeCopyPasteService::CopyNodesToClipboard(const QVector &nodes, vo writer.writeStartElement(QStringLiteral("contexts")); foreach (Node* n, nodes) { // Determine if this node is a context - if (n->parent()->GetPositionMap().contains(n)) { + if (!n->GetContextPositions().isEmpty()) { writer.writeStartElement(QStringLiteral("context")); writer.writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(n))); - const NodeGraph::PositionMap &map = n->parent()->GetNodesForContext(n); + const Node::PositionMap &map = n->GetContextPositions(); for (auto it=map.cbegin(); it!=map.cend(); it++) { writer.writeStartElement(QStringLiteral("node")); Project::SavePosition(&writer, it.key(), it.value()); @@ -220,7 +220,7 @@ QVector NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph, for (auto jt=map.cbegin(); jt!=map.cend(); jt++) { Node *subnode = xml_node_data.node_ptrs.value(jt.key()); if (subnode) { - command->add_child(new NodeSetPositionCommand(subnode, context, jt.value(), false)); + command->add_child(new NodeSetPositionCommand(subnode, context, jt.value())); } } } diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index e1cb208d6..9506f4513 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -44,7 +44,7 @@ Track::Track() : locked_(false), sequence_(nullptr) { - AddInput(kBlockInput, NodeValue::kNone, InputFlags(kInputFlagArray | kInputFlagNotKeyframable)); + AddInput(kBlockInput, NodeValue::kNone, InputFlags(kInputFlagArray | kInputFlagNotKeyframable | kInputFlagHidden)); // Since blocks are time based, we can handle the invalidate timing a little more intelligently // on our end diff --git a/app/node/param.cpp b/app/node/param.cpp index 9f6bd8c4d..f0a685abe 100644 --- a/app/node/param.cpp +++ b/app/node/param.cpp @@ -33,6 +33,15 @@ QString NodeInput::name() const } } +bool NodeInput::IsHidden() const +{ + if (IsValid()) { + return node_->IsInputHidden(input_); + } else { + return false; + } +} + bool NodeInput::IsConnected() const { if (IsValid()) { diff --git a/app/node/param.h b/app/node/param.h index 63aa413f7..3de4b6727 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -115,6 +115,8 @@ public: return node_ && !input_.isEmpty() && element_ >= -1; } + bool IsHidden() const; + bool IsConnected() const; bool IsKeyframing() const; diff --git a/app/node/project/folder/folder.cpp b/app/node/project/folder/folder.cpp index feae6d612..7b017923f 100644 --- a/app/node/project/folder/folder.cpp +++ b/app/node/project/folder/folder.cpp @@ -113,19 +113,12 @@ void Folder::InputDisconnectedEvent(const QString &input, int element, Node *out } } -FolderAddChild::FolderAddChild(Folder *folder, Node *child, bool autoposition) : +FolderAddChild::FolderAddChild(Folder *folder, Node *child) : folder_(folder), - child_(child), - autoposition_(autoposition), - position_command_(nullptr) + child_(child) { } -FolderAddChild::~FolderAddChild() -{ - delete position_command_; -} - Project *FolderAddChild::GetRelevantProject() const { return folder_->project(); @@ -136,21 +129,10 @@ void FolderAddChild::redo() int array_index = folder_->InputArraySize(Folder::kChildInput); folder_->InputArrayAppend(Folder::kChildInput, false); Node::ConnectEdge(child_, NodeInput(folder_, Folder::kChildInput, array_index)); - - if (autoposition_) { - if (!position_command_) { - position_command_ = new NodeSetPositionAsChildCommand(child_, folder_, folder_->project()->root(), array_index, array_index+1, true); - } - position_command_->redo_now(); - } } void FolderAddChild::undo() { - if (position_command_) { - position_command_->undo_now(); - } - Node::DisconnectEdge(child_, NodeInput(folder_, Folder::kChildInput, folder_->InputArraySize(Folder::kChildInput)-1)); folder_->InputArrayRemoveLast(Folder::kChildInput); } diff --git a/app/node/project/folder/folder.h b/app/node/project/folder/folder.h index 1303ce7d7..12677e57b 100644 --- a/app/node/project/folder/folder.h +++ b/app/node/project/folder/folder.h @@ -208,9 +208,7 @@ private: class FolderAddChild : public UndoCommand { public: - FolderAddChild(Folder* folder, Node* child, bool autoposition = true); - - virtual ~FolderAddChild() override; + FolderAddChild(Folder* folder, Node* child); virtual Project * GetRelevantProject() const override; @@ -224,10 +222,6 @@ private: Node* child_; - bool autoposition_; - - NodeSetPositionAsChildCommand* position_command_; - }; } diff --git a/app/node/project/project.cpp b/app/node/project/project.cpp index f865e28c8..64d7cb107 100644 --- a/app/node/project/project.cpp +++ b/app/node/project/project.cpp @@ -44,19 +44,16 @@ Project::Project() : root_->setParent(this); root_->SetLabel(tr("Root")); root_->SetCanBeDeleted(false); - SetNodePosition(root_, root_, QPointF(0, 0)); // Adds a color manager "node" to this project so that it synchronizes color_manager_ = new ColorManager(); color_manager_->setParent(this); - SetNodePosition(color_manager_, root_, QPointF(1, 0)); color_manager_->SetCanBeDeleted(false); AddDefaultNode(color_manager_); // Same with project settings settings_ = new ProjectSettingsNode(); settings_->setParent(this); - SetNodePosition(settings_, root_, QPointF(2, 0)); settings_->SetCanBeDeleted(false); AddDefaultNode(settings_); @@ -167,7 +164,7 @@ void Project::Load(QXmlStreamReader *reader, MainWindowLayoutInfo* layout, uint Node *node = xml_node_data.node_ptrs.value(node_ptr); if (node) { - SetNodePosition(node, context, node_pos); + context->SetNodePositionInContext(node, node_pos); } else { qWarning() << "Failed to find pointer for node position"; reader->skipCurrentElement(); @@ -230,20 +227,22 @@ void Project::Save(QXmlStreamWriter *writer) const writer->writeStartElement(QStringLiteral("positions")); - for (auto it=GetPositionMap().cbegin(); it!=GetPositionMap().cend(); it++) { - writer->writeStartElement(QStringLiteral("context")); + foreach (Node* context, nodes()) { + const Node::PositionMap &map = context->GetContextPositions(); - writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(it.key()))); + if (!map.isEmpty()) { + writer->writeStartElement(QStringLiteral("context")); - const PositionMap &map = it.value(); + writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(context))); - for (auto jt=map.cbegin(); jt!=map.cend(); jt++) { - writer->writeStartElement(QStringLiteral("node")); - SavePosition(writer, jt.key(), jt.value()); - writer->writeEndElement(); // node + for (auto jt=map.cbegin(); jt!=map.cend(); jt++) { + writer->writeStartElement(QStringLiteral("node")); + SavePosition(writer, jt.key(), jt.value()); + writer->writeEndElement(); // node + } + + writer->writeEndElement(); // context } - - writer->writeEndElement(); // context } writer->writeEndElement(); // positions diff --git a/app/node/project/sequence/sequence.cpp b/app/node/project/sequence/sequence.cpp index 27b17708f..0cb443188 100644 --- a/app/node/project/sequence/sequence.cpp +++ b/app/node/project/sequence/sequence.cpp @@ -41,7 +41,7 @@ Sequence::Sequence() // Create track input QString track_input_id = kTrackInputFormat.arg(i); - AddInput(track_input_id, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable | kInputFlagArray)); + AddInput(track_input_id, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); IgnoreInvalidationsFrom(track_input_id); diff --git a/app/panel/node/node.cpp b/app/panel/node/node.cpp index 591f7a867..3f0e3a4a2 100644 --- a/app/panel/node/node.cpp +++ b/app/panel/node/node.cpp @@ -38,6 +38,7 @@ NodePanel::NodePanel(QWidget *parent) : // Create NodeView widget node_view_ = new NodeView(this); outer_layout->addWidget(node_view_); + connect(this, &NodePanel::visibilityChanged, node_view_, &NodeView::CenterOnItemsBoundingRect); // Connect toolbar to NodeView connect(toolbar_, &NodeViewToolBar::MiniMapEnabledToggled, node_view_, &NodeView::SetMiniMapEnabled); diff --git a/app/panel/node/node.h b/app/panel/node/node.h index d145bb91d..20eccc79a 100644 --- a/app/panel/node/node.h +++ b/app/panel/node/node.h @@ -36,20 +36,15 @@ class NodePanel : public PanelWidget public: NodePanel(QWidget* parent); - NodeGraph* GetGraph() const + void SetContexts(const QVector &nodes) { - return node_view_->GetGraph(); + node_view_->SetContexts(nodes); + toolbar_->setEnabled(!nodes.isEmpty()); } - void SetGraph(NodeGraph *graph, const QVector &nodes) + void CloseContextsBelongingToProject(Project *project) { - node_view_->SetGraph(graph, nodes); - toolbar_->setEnabled(graph); - } - - void ClearGraph() - { - node_view_->ClearGraph(); + node_view_->CloseContextsBelongingToProject(project); } const QVector &GetCurrentContexts() const @@ -113,11 +108,6 @@ public slots: node_view_->Select(nodes, center_view_on_item); } - void SelectWithDependencies(const QVector& nodes, bool center_view_on_item) - { - node_view_->SelectWithDependencies(nodes, center_view_on_item); - } - signals: void NodesSelected(const QVector& nodes); diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 31be310c6..4c8385d9d 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -42,15 +42,13 @@ const double NodeView::kMinimumScale = 0.1; NodeView::NodeView(QWidget *parent) : HandMovableView(parent), - graph_(nullptr), drop_edge_(nullptr), create_edge_(nullptr), create_edge_dst_(nullptr), create_edge_dst_temp_expanded_(false), paste_command_(nullptr), - filter_mode_(kFilterShowSelective), scale_(1.0), - queue_reposition_contexts_(false) + first_show_(true) { setScene(&scene_); SetDefaultDragMode(RubberBandDrag); @@ -84,10 +82,10 @@ NodeView::~NodeView() ClearGraph(); } -void NodeView::SetGraph(NodeGraph *graph, const QVector &nodes) +void NodeView::SetContexts(const QVector &nodes) { // Remove contexts that are no longer in the list - foreach (Node *n, filter_nodes_) { + foreach (Node *n, contexts_) { if (!nodes.contains(n)) { scene_.RemoveContext(n); } @@ -95,86 +93,38 @@ void NodeView::SetGraph(NodeGraph *graph, const QVector &nodes) // Add contexts that are now in the list foreach (Node *n, nodes) { - if (!filter_nodes_.contains(n)) { + if (!contexts_.contains(n)) { scene_.AddContext(n); } } - filter_nodes_ = nodes; + contexts_ = nodes; - /*bool graph_changed = graph_ != graph; - bool context_changed = last_set_filter_nodes_ != nodes; + CenterOnItemsBoundingRect(); +} - if (graph_changed || context_changed) { - // Clear nodes if necessary - bool refresh_required = (graph_changed && filter_mode_ == kFilterShowAll) - || (context_changed && filter_mode_ == kFilterShowSelective); - bool nodes_visible = (graph && filter_mode_ == kFilterShowAll) - || (!nodes.isEmpty() && filter_mode_ == kFilterShowSelective); +void NodeView::CloseContextsBelongingToProject(Project *project) +{ + QVector new_contexts = contexts_; - if (refresh_required) { - DeselectAll(); - positions_.clear(); - scene_.clear(); - context_offsets_.clear(); + for (auto it = new_contexts.begin(); it != new_contexts.end(); ) { + if ((*it)->project() == project) { + it = new_contexts.erase(it); + } else { + it++; } + } - // Handle graph change - if (graph_changed) { - if (graph_) { - // Disconnect from current graph - disconnect(graph_, &NodeGraph::NodeRemoved, this, &NodeView::RemoveNode); - disconnect(graph_, &NodeGraph::InputConnected, this, &NodeView::AddEdge); - disconnect(graph_, &NodeGraph::InputDisconnected, this, &NodeView::RemoveEdge); - disconnect(graph_, &NodeGraph::NodePositionAdded, this, &NodeView::AddNodePosition); - disconnect(graph_, &NodeGraph::NodePositionRemoved, this, &NodeView::RemoveNodePosition); - } - - graph_ = graph; - - if (graph_) { - // Connect to new graph - connect(graph_, &NodeGraph::NodeRemoved, this, &NodeView::RemoveNode); - connect(graph_, &NodeGraph::InputConnected, this, &NodeView::AddEdge); - connect(graph_, &NodeGraph::InputDisconnected, this, &NodeView::RemoveEdge); - connect(graph_, &NodeGraph::NodePositionAdded, this, &NodeView::AddNodePosition); - connect(graph_, &NodeGraph::NodePositionRemoved, this, &NodeView::RemoveNodePosition); - } - } - - if (context_changed) { - last_set_filter_nodes_ = nodes; - - if (filter_mode_ == kFilterShowSelective) { - filter_nodes_ = nodes; - } - } - - if (refresh_required && nodes_visible) { - if (filter_mode_ == kFilterShowAll) { - // Just make the filter nodes all of the graph's contexts - filter_nodes_ = graph->GetPositionMap().keys().toVector(); - } - - RepositionContexts(); - - // Center on something - QMetaObject::invokeMethod(this, &NodeView::CenterOnItemsBoundingRect, Qt::QueuedConnection); - } - }*/ + SetContexts(new_contexts); } void NodeView::ClearGraph() { - SetGraph(nullptr, QVector()); + SetContexts(QVector()); } void NodeView::DeleteSelected() { - if (!graph_) { - return; - } - MultiUndoCommand* command = new MultiUndoCommand(); { @@ -189,9 +139,6 @@ void NodeView::DeleteSelected() command->add_child(new NodeEdgeRemoveCommand(edge->output(), edge->input())); removed_connections[i] = {edge->output(), edge->input()}; } - - // Update contexts - UpdateContextsFromEdgeRemove(command, removed_connections); } } @@ -219,10 +166,6 @@ void NodeView::DeleteSelected() void NodeView::SelectAll() { - if (!graph_) { - return; - } - // Optimization: rather than respond to every single item being selected, ignore the signal and // then handle them all at the end. DisconnectSelectionChangedSignal(); @@ -249,7 +192,7 @@ void NodeView::SelectAll() void NodeView::DeselectAll() { - if (!graph_ || selected_nodes_.isEmpty()) { + if (selected_nodes_.isEmpty()) { return; } @@ -268,10 +211,6 @@ void NodeView::DeselectAll() void NodeView::Select(QVector nodes, bool center_view_on_item) { - if (!graph_) { - return; - } - // Optimization: rather than respond to every single item being selected, ignore the signal and // then handle them all at the end. DisconnectSelectionChangedSignal(); @@ -331,39 +270,13 @@ void NodeView::Select(QVector nodes, bool center_view_on_item) selected_nodes_ = nodes; } -void NodeView::SelectWithDependencies(QVector nodes, bool center_view_on_item) -{ - if (!graph_) { - return; - } - - int original_length = nodes.size(); - for (int i=0;i dependencies = nodes.at(i)->GetDependencies(); - - foreach (Node *d, dependencies) { - if (scene_.item_map().contains(d) && !nodes.contains(d)) { - nodes.append(d); - } - } - } - - Select(nodes, center_view_on_item); -} - void NodeView::CopySelected(bool cut) { - if (!graph_) { + if (selected_nodes_.isEmpty()) { return; } - QVector selected = scene_.GetSelectedNodes(); - - if (selected.isEmpty()) { - return; - } - - CopyNodesToClipboard(selected); + CopyNodesToClipboard(selected_nodes_); if (cut) { DeleteSelected(); @@ -409,43 +322,41 @@ void NodeView::keyPressEvent(QKeyEvent *event) case Qt::Key_Up: case Qt::Key_Down: { - if (graph_) { - MultiUndoCommand *pos_command = new MultiUndoCommand(); - for (Node *n : qAsConst(selected_nodes_)) { - for (Node *context : qAsConst(filter_nodes_)) { - if (graph_->GetNodesForContext(context).contains(n)) { - QPointF old_pos = graph_->GetNodePosition(n, context); + MultiUndoCommand *pos_command = new MultiUndoCommand(); + for (Node *n : qAsConst(selected_nodes_)) { + for (Node *context : qAsConst(contexts_)) { + if (context->ContextContainsNode(n)) { + QPointF old_pos = context->GetNodePositionInContext(n); - // Determine one pixel in scene units - double movement_amt = 1.0 / scale_; + // Determine one pixel in scene units + double movement_amt = 1.0 / scale_; - // Translate to 2D movement - QPointF node_movement; - switch (event->key()) { - case Qt::Key_Left: - node_movement.setX(-movement_amt); - break; - case Qt::Key_Right: - node_movement.setX(movement_amt); - break; - case Qt::Key_Up: - node_movement.setY(-movement_amt); - break; - case Qt::Key_Down: - node_movement.setY(movement_amt); - break; - } - - // Translate from screen units into node units - node_movement = NodeViewItem::ScreenToNodePoint(node_movement, scene_.GetFlowDirection()); - - // Move command - pos_command->add_child(new NodeSetPositionCommand(n, context, old_pos + node_movement, false)); + // Translate to 2D movement + QPointF node_movement; + switch (event->key()) { + case Qt::Key_Left: + node_movement.setX(-movement_amt); + break; + case Qt::Key_Right: + node_movement.setX(movement_amt); + break; + case Qt::Key_Up: + node_movement.setY(-movement_amt); + break; + case Qt::Key_Down: + node_movement.setY(movement_amt); + break; } + + // Translate from screen units into node units + node_movement = NodeViewItem::ScreenToNodePoint(node_movement, scene_.GetFlowDirection()); + + // Move command + pos_command->add_child(new NodeSetPositionCommand(n, context, old_pos + node_movement)); } } - Core::instance()->undo_stack()->pushIfHasChildren(pos_command); } + Core::instance()->undo_stack()->pushIfHasChildren(pos_command); break; } case Qt::Key_Escape: @@ -664,15 +575,6 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) create_edge_dst_ = nullptr; } - // Update contexts - /*if (!removed_edges.empty()) { - UpdateContextsFromEdgeRemove(command, removed_edges); - } - - if (added_edge.first) { - UpdateContextsFromEdgeAdd(command, added_edge, removed_edges); - }*/ - Core::instance()->undo_stack()->pushIfHasChildren(command); return; } @@ -689,38 +591,6 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } } - { - // If any node positions changed, set them in their contexts now - MultiUndoCommand *set_pos_command = new MultiUndoCommand(); - for (auto it=positions_.begin(); it!=positions_.end(); it++) { - NodeViewItem *item = it.key(); - Position &pos_data = it.value(); - QPointF current_item_pos = item->GetNodePosition(); - Node *node = pos_data.node; - - if (pos_data.original_item_pos != current_item_pos) { - QPointF diff = current_item_pos - pos_data.original_item_pos; - - for (Node *context : qAsConst(filter_nodes_)) { - if (graph_->ContextContainsNode(node, context)) { - QPointF current_node_pos_in_context = graph_->GetNodePosition(node, context); - current_node_pos_in_context += diff; - set_pos_command->add_child(new NodeSetPositionCommand(node, context, current_node_pos_in_context, false)); - } - } - - pos_data.original_item_pos = current_item_pos; - } - } - if (set_pos_command->child_count()) { - set_pos_command->redo_now(); - command->add_child(set_pos_command); - } else { - delete set_pos_command; - } - } - - if (!attached_items_.isEmpty()) { { // Dropped attached item onto an edge, connect it between them @@ -757,7 +627,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) bool removed = false; QVector relevant_contexts; - for (Node *context : qAsConst(filter_nodes_)) { + for (Node *context : qAsConst(contexts_)) { if (attached_node->OutputsTo(context, true)) { relevant_contexts.append(context); } else { @@ -768,7 +638,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) if (removed && !relevant_contexts.isEmpty()) { for (Node *relevant : qAsConst(relevant_contexts)) { - remove_pos_subcommand->add_child(new NodeSetPositionCommand(attached_node, relevant, GetEstimatedPositionForContext(attached.item, relevant), false)); + remove_pos_subcommand->add_child(new NodeSetPositionCommand(attached_node, relevant, GetEstimatedPositionForContext(attached.item, relevant))); } remove_pos_command->add_child(remove_pos_subcommand); @@ -844,7 +714,7 @@ void NodeView::UpdateSelectionCache() void NodeView::ShowContextMenu(const QPoint &pos) { - if (filter_nodes_.isEmpty()) { + if (contexts_.isEmpty()) { return; } @@ -896,17 +766,6 @@ void NodeView::ShowContextMenu(const QPoint &pos) m.addSeparator(); - Menu* filter_menu = new Menu(tr("Filter"), &m); - m.addMenu(filter_menu); - - filter_menu->AddActionWithData(tr("Show All Nodes"), kFilterShowAll, filter_mode_); - - filter_menu->AddActionWithData(tr("Show Selected"), kFilterShowSelective, filter_mode_); - - connect(filter_menu, &Menu::triggered, this, &NodeView::ContextMenuFilterChanged); - - - Menu* direction_menu = new Menu(tr("Direction"), &m); m.addMenu(direction_menu); @@ -940,23 +799,20 @@ void NodeView::ShowContextMenu(const QPoint &pos) void NodeView::CreateNodeSlot(QAction *action) { - if (!graph_) { - return; - } - - Node* new_node = NodeFactory::CreateFromMenuAction(action); + qDebug() << "STUB!"; + /*Node* new_node = NodeFactory::CreateFromMenuAction(action); if (new_node) { paste_command_ = new MultiUndoCommand(); paste_command_->add_child(new NodeAddCommand(graph_, new_node)); - for (Node *context : qAsConst(filter_nodes_)) { + for (Node *context : qAsConst(contexts_)) { paste_command_->add_child(new NodeSetPositionCommand(new_node, context, QPointF(0, 0), false)); } paste_command_->add_child(new NodeViewAttachNodesToCursor(this, {new_node})); paste_command_->redo_now(); this->setFocus(); - } + }*/ } void NodeView::ContextMenuSetDirection(QAction *action) @@ -964,26 +820,6 @@ void NodeView::ContextMenuSetDirection(QAction *action) SetFlowDirection(static_cast(action->data().toInt())); } -void NodeView::ContextMenuFilterChanged(QAction *action) -{ - FilterMode mode = static_cast(action->data().toInt()); - - if (filter_mode_ != mode) { - // Store temporary graph variables - NodeGraph *graph = graph_; - QVector nodes = last_set_filter_nodes_; - - // Unset graph with current filter mode - ClearGraph(); - - // Change filter mode - filter_mode_ = mode; - - // Re-set graph with new filter mode - SetGraph(graph, nodes); - } -} - void NodeView::OpenSelectedNodeInViewer() { QVector selected = scene_.GetSelectedNodes(); @@ -994,100 +830,6 @@ void NodeView::OpenSelectedNodeInViewer() } } -// Commenting out because there shouldn't be any situations where a node would be added without -// being in a context. We're keeping RemoveNode as a fail-safe because it could provide crash -// resistance where RemoveNodePosition might be missed. -//void NodeView::AddNode(Node *node) -//{ -// if (filter_mode_ == kFilterShowAll) { -// scene_.AddNode(node); -// } -//} - -void NodeView::RemoveNode(Node *node) -{ - for (auto it=attached_items_.begin(); it!=attached_items_.end(); ) { - if (it->item->GetNode() == node) { - it = attached_items_.erase(it); - } else { - it++; - } - } - for (const Node::OutputConnection &oc : node->output_connections()) { - scene_.RemoveEdge(oc.first, oc.second); - } - for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - scene_.RemoveEdge(it->second, it->first); - } - positions_.remove(scene_.item_map().value(node)); -} - -void NodeView::AddEdge(Node *output, const NodeInput &input) -{ - Node *output_node = output; - Node *input_node = input.node(); - - if (scene_.item_map().contains(output_node) && scene_.item_map().contains(input_node)) { - scene_.AddEdge(output, input); - } -} - -void NodeView::RemoveEdge(Node *output, const NodeInput &input) -{ - scene_.RemoveEdge(output, input); -} - -void NodeView::AddNodePosition(Node *node, Node *relative) -{ - bool listening_to_node = filter_nodes_.contains(relative); - - if (!listening_to_node) { - if (filter_mode_ == kFilterShowAll) { - // We're not listening to this context, but because we're showing all, add it - filter_nodes_.append(relative); - } else { - // Ignore signal - return; - } - } - - // Reposition contexts because one of their heights may have changed or a new one may have been - // added - UpdateNodeItem(node); - - if (filter_mode_ == kFilterShowAll) { - queue_reposition_contexts_ = true; - viewport()->update(); - } -} - -void NodeView::RemoveNodePosition(Node *node, Node *relative) -{ - if (filter_nodes_.contains(relative)) { - NodeViewItem *item = scene_.item_map().value(node); - - if (item && !item->GetPreventRemoving()) { - // Determine if any other contexts have this node - bool found = false; - - for (Node *context : qAsConst(filter_nodes_)) { - if (graph_->ContextContainsNode(node, context)) { - found = true; - break; - } - } - - if (!found) { - RemoveNode(node); - } - } - - if (filter_mode_ == kFilterShowAll) { - RepositionContexts(); - } - } -} - void NodeView::UpdateSceneBoundingRect() { // Get current items bounding rect @@ -1229,13 +971,6 @@ bool NodeView::event(QEvent *event) bool NodeView::eventFilter(QObject *object, QEvent *event) { - if (object == viewport() && event->type() == QEvent::Paint) { - if (queue_reposition_contexts_) { - RepositionContexts(); - queue_reposition_contexts_ = false; - } - } - return super::eventFilter(object, event); } @@ -1259,7 +994,8 @@ void NodeView::CopyNodesToClipboardInternal(QXmlStreamWriter *writer, const QVec void NodeView::PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data, void *userdata) { - NodeGraph::PositionMap *map = static_cast(userdata); + qDebug() << "STUB!"; + /*NodeGraph::PositionMap *map = static_cast(userdata); while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("pos")) { @@ -1295,7 +1031,7 @@ void NodeView::PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNode } else { reader->skipCurrentElement(); } - } + }*/ } void NodeView::ZoomFromKeyboard(double multiplier) @@ -1310,148 +1046,6 @@ void NodeView::ZoomFromKeyboard(double multiplier) ZoomIntoCursorPosition(nullptr, multiplier, cursor_pos); } -bool NodeView::DetermineIfNodeIsFloatingInContext(Node *node, Node *context, Node *source, const Node::OutputConnections &removed_edges, const Node::OutputConnection &added_edge) -{ - // Determines whether `node` outputs to another node in `context` besides `source` - for (const Node::OutputConnection &conn : node->output_connections()) { - Node *output_candidate = conn.second.node(); - - if (output_candidate == source) { - continue; - } - - if (graph_->ContextContainsNode(output_candidate, context)) { - if (!output_candidate->OutputsTo(source, true, removed_edges, added_edge)) { - return true; - } - } - } - - return false; -} - -void NodeView::UpdateContextsFromEdgeRemove(MultiUndoCommand *command, const Node::OutputConnections &remove_edges) -{ - // For each edge we remove, determine if we should remove the node from a context as well - for (const Node::OutputConnection &edge : remove_edges) { - Node *output_node = edge.first; - QVector contexts_to_remove_from; - int contexts_containing = 0; - - for (auto it=graph_->GetPositionMap().cbegin(); it!=graph_->GetPositionMap().cend(); it++) { - Node *context = it.key(); - - if (it.value().contains(output_node)) { - bool currently_outputs = output_node->OutputsTo(context, true); - bool will_output_after_operation = output_node->OutputsTo(context, true, remove_edges); - - if (currently_outputs && !will_output_after_operation) { - // Will remove - contexts_to_remove_from.append(context); - } - - contexts_containing++; - } - } - - // Removing from all current contexts, convert to a floating node (i.e. don't remove from the context) - if (contexts_to_remove_from.size() != contexts_containing) { - // Not removing from all contexts, can remove - bool removing_from_all_current_contexts = true; - - for (Node *context : qAsConst(filter_nodes_)) { - if (graph_->ContextContainsNode(output_node, context)) { - if (!contexts_to_remove_from.contains(context)) { - removing_from_all_current_contexts = false; - break; - } - } - } - - for (Node *context : qAsConst(contexts_to_remove_from)) { - RecursivelyRemoveFloatingNodeFromContext(command, output_node, context, output_node, remove_edges, Node::OutputConnection(), removing_from_all_current_contexts); - } - } - } -} - -void NodeView::RecursivelyRemoveFloatingNodeFromContext(MultiUndoCommand *command, Node *node, Node *context, Node *source, const Node::OutputConnections &removed_edges, const Node::OutputConnection &added_edge, bool prevent_removing) -{ - if (prevent_removing) { - command->add_child(new NodeViewItemPreventRemovingCommand(this, node, true)); - } - - command->add_child(new NodeRemovePositionFromContextCommand(node, context)); - - // Remove any dependency from the context that's also floating - for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - Node *dependency = it->second; - - // Determine if this node happens to output to anything else in the context (which may be - // another floating node that won't be removed by this operation) - if (!DetermineIfNodeIsFloatingInContext(dependency, context, source, removed_edges, added_edge)) { - RecursivelyRemoveFloatingNodeFromContext(command, dependency, context, source, removed_edges, added_edge, prevent_removing); - } - } -} - -void NodeView::RecursivelyAddNodeToContext(MultiUndoCommand *command, Node *node, Node *context) -{ - NodeViewItem *item = scene_.item_map().value(node); - - if (item) { - command->add_child(new NodeSetPositionCommand(node, context, GetEstimatedPositionForContext(item, context), false)); - - // Add dependency - for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - Node *dependency = it->second; - RecursivelyAddNodeToContext(command, dependency, context); - } - } -} - -void NodeView::UpdateContextsFromEdgeAdd(MultiUndoCommand *command, const Node::OutputConnection &added_edge, const Node::OutputConnections &removed_edges) -{ - // Determine if node currently does NOT output to a context that it WILL after this operation - QVector contexts_to_add_to; - Node *connecting_node = added_edge.first; - Node *input_node = added_edge.second.node(); - for (auto it=graph_->GetPositionMap().cbegin(); it!=graph_->GetPositionMap().cend(); it++) { - if (it.value().contains(input_node)) { - contexts_to_add_to.append(it.key()); - } - } - - if (!contexts_to_add_to.isEmpty()) { - // Determine whether the node is currently "floating", i.e. it outputs to none of the contexts - // that it currently belongs to. If so, we will take ownership of it with this node. - bool node_is_floating = true; - QVector current_contexts; - for (auto it=graph_->GetPositionMap().cbegin(); it!=graph_->GetPositionMap().cend(); it++) { - if (it.value().contains(connecting_node)) { - if (connecting_node->OutputsTo(it.key(), true, removed_edges)) { - node_is_floating = false; - break; - } else { - current_contexts.append(it.key()); - } - } - } - - if (node_is_floating) { - // This action will unfloat this node, so remove it from all current contexts - for (Node *context : qAsConst(current_contexts)) { - RecursivelyRemoveFloatingNodeFromContext(command, connecting_node, context, connecting_node, removed_edges, added_edge, false); - } - } - - // Add nodes to contexts - for (Node *context : qAsConst(contexts_to_add_to)) { - RecursivelyAddNodeToContext(command, connecting_node, context); - } - } -} - QPointF NodeView::GetEstimatedPositionForContext(NodeViewItem *item, Node *context) const { return item->GetNodePosition() - context_offsets_.value(context); @@ -1545,86 +1139,6 @@ void NodeView::PositionNewEdge(const QPoint &pos) create_edge_->SetConnected(create_edge_dst_input_.IsValid()); } -void NodeView::RepositionContexts() -{ - // Determine which contexts are root-level - QVector processing_filters = filter_nodes_; - - // Level counter as we iterate through the list a few times - int level = 0; - - // Root-level positioning variables - qreal last_offset = 0; - int additional_spacing = 0; - - while (!processing_filters.isEmpty()) { - QVector contexts_on_this_level; - - for (int i=0; iContextContainsNode(context, other_context)) { - this_level = false; - break; - } - } - - if (this_level) { - contexts_on_this_level.append(context); - } - } - - for (Node *context : qAsConst(contexts_on_this_level)) { - if (level == 0) { - const NodeGraph::PositionMap &map = graph_->GetNodesForContext(context); - - // First determine the total "height" of this graph and how much we need to offset it - qreal top = 0; - qreal bottom = 0; - for (auto it=map.cbegin(); it!=map.cend(); it++) { - const QPointF &node_pos_in_context = it.value(); - top = qMin(node_pos_in_context.y(), top); - bottom = qMax(node_pos_in_context.y(), bottom); - } - - last_offset += (additional_spacing + (bottom - top)); - additional_spacing = 1; - context_offsets_.insert(context, QPointF(0, last_offset)); - } else { - // Create/update item - NodeViewItem *item = UpdateNodeItem(context, true); - - // Get position generated by UpdateNodeItem - QPointF context_pos = item->GetNodePosition(); - - // Adjust by the context node's position in its own context (this will usually be 0,0) - context_pos -= graph_->GetNodesForContext(context).value(context); - - // Insert this context's offset - context_offsets_.insert(context, context_pos); - } - - // Remove from list so we don't process again - processing_filters.removeOne(context); - } - - level++; - } - - // Now that we've positioned all the contexts, position all other nodes relative to those contexts - for (Node *context : qAsConst(filter_nodes_)) { - const NodeGraph::PositionMap &map = graph_->GetNodesForContext(context); - for (auto it=map.cbegin(); it!=map.cend(); it++) { - UpdateNodeItem(it.key()); - } - } -} - void NodeView::GroupNodes() { /*NodeGroup *group = new NodeGroup(); @@ -1636,54 +1150,11 @@ 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 - NodeViewItem *item = scene_.item_map().value(node); - if (!item) { - for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - if (scene_.item_map().contains(it->second)) { - scene_.AddEdge(it->second, it->first); - } - } - - for (auto it=node->output_connections().cbegin(); it!=node->output_connections().cend(); it++) { - if (scene_.item_map().contains(it->second.node())) { - scene_.AddEdge(it->first, it->second); - } - } - } - - // Determine "view" position by averaging the Y value and "min"ing the X value of all contexts - QPointF item_pos(std::numeric_limits::max(), 0.0); - int average_count = 0; - for (Node *context : qAsConst(filter_nodes_)) { - if (context == node && ignore_own_context) { - continue; - } - - if (graph_->GetNodesForContext(context).contains(node)) { - QPointF this_context_pos = graph_->GetNodePosition(node, context); - this_context_pos += context_offsets_.value(context); - - item_pos.setX(qMin(item_pos.x(), this_context_pos.x())); - item_pos.setY(item_pos.y() + this_context_pos.y()); - average_count++; - } - } - item_pos.setY(item_pos.y() / average_count); - - // Set position - item->SetNodePosition(item_pos); - positions_.insert(item, {node, item_pos}); - - return item; -} - void NodeView::PasteNodesInternal(const QVector &duplicate_nodes) { + /* // If no graph, do nothing - if (!graph_) { + if (contexts_.isEmpty()) { return; } @@ -1696,7 +1167,7 @@ void NodeView::PasteNodesInternal(const QVector &duplicate_nodes) new_nodes = PasteNodesFromClipboard(graph_, paste_command_, &map); for (auto it=new_nodes.cbegin(); it!=new_nodes.cend(); it++) { - for (Node *context : qAsConst(filter_nodes_)) { + for (Node *context : qAsConst(contexts_)) { paste_command_->add_child(new NodeSetPositionCommand(*it, context, map.value(*it), false)); } } @@ -1707,7 +1178,7 @@ void NodeView::PasteNodesInternal(const QVector &duplicate_nodes) Node *src = duplicate_nodes.at(i); Node *copy = new_nodes.at(i); - for (Node *context : qAsConst(filter_nodes_)) { + for (Node *context : qAsConst(contexts_)) { QPointF p = scene_.item_map().value(src)->GetNodePosition(); paste_command_->add_child(new NodeSetPositionCommand(copy, context, p, false)); } @@ -1725,6 +1196,7 @@ void NodeView::PasteNodesInternal(const QVector &duplicate_nodes) paste_command_->add_child(new NodeViewAttachNodesToCursor(this, new_nodes)); paste_command_->redo_now(); + */ } NodeView::NodeViewAttachNodesToCursor::NodeViewAttachNodesToCursor(NodeView *view, const QVector &nodes) : @@ -1745,8 +1217,7 @@ void NodeView::NodeViewAttachNodesToCursor::undo() Project *NodeView::NodeViewAttachNodesToCursor::GetRelevantProject() const { - // Will either return a project or a nullptr which is also acceptable - return dynamic_cast(view_->graph_); + return nullptr; } void NodeView::NodeViewItemPreventRemovingCommand::redo() diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 941120361..8616974f5 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -49,12 +49,9 @@ public: virtual ~NodeView() override; - NodeGraph* GetGraph() const - { - return graph_; - } + void SetContexts(const QVector &nodes); - void SetGraph(NodeGraph *graph, const QVector &nodes); + void CloseContextsBelongingToProject(Project *project); void ClearGraph(); @@ -67,7 +64,6 @@ public: void DeselectAll(); void Select(QVector nodes, bool center_view_on_item); - void SelectWithDependencies(QVector nodes, bool center_view_on_item); void CopySelected(bool cut); void Paste(); @@ -82,7 +78,7 @@ public: const QVector &GetCurrentContexts() const { - return filter_nodes_; + return contexts_; } public slots: @@ -98,6 +94,8 @@ public slots: delete m; } + void CenterOnItemsBoundingRect(); + signals: void NodesSelected(const QVector& nodes); @@ -137,12 +135,6 @@ private: void ZoomFromKeyboard(double multiplier); - bool DetermineIfNodeIsFloatingInContext(Node *node, Node *context, Node *source, const Node::OutputConnections &removed_edges, const Node::OutputConnection &added_edge); - void UpdateContextsFromEdgeRemove(MultiUndoCommand *command, const Node::OutputConnections &remove_edges); - void UpdateContextsFromEdgeAdd(MultiUndoCommand *command, const Node::OutputConnection &added_edge, const Node::OutputConnections &removed_edges = Node::OutputConnections()); - void RecursivelyAddNodeToContext(MultiUndoCommand *command, Node *node, Node *context); - void RecursivelyRemoveFloatingNodeFromContext(MultiUndoCommand *command, Node *node, Node *context, Node *source, const Node::OutputConnections &removed_edges, const Node::OutputConnection &added_edge, bool prevent_removing); - QPointF GetEstimatedPositionForContext(NodeViewItem *item, Node *context) const; Menu *CreateAddMenu(Menu *parent); @@ -151,8 +143,6 @@ private: void PositionNewEdge(const QPoint &pos); - NodeViewItem *UpdateNodeItem(Node *node, bool ignore_own_context = false); - void PasteNodesInternal(const QVector &duplicate_nodes = QVector()); class NodeViewAttachNodesToCursor : public UndoCommand @@ -176,8 +166,6 @@ private: NodeViewMiniMap *minimap_; - NodeGraph* graph_; - struct AttachedItem { NodeViewItem* item; QPointF original_pos; @@ -227,21 +215,7 @@ private: QVector selected_nodes_; - enum FilterMode { - kFilterShowAll, - kFilterShowSelective - }; - - struct Position { - Node *node; - QPointF original_item_pos; - }; - - QMap positions_; - - FilterMode filter_mode_; - - QVector filter_nodes_; + QVector contexts_; QVector last_set_filter_nodes_; QMap context_offsets_; @@ -249,7 +223,7 @@ private: bool create_edge_already_exists_; - bool queue_reposition_contexts_; + bool first_show_; static const double kMinimumScale; @@ -274,36 +248,19 @@ private slots: */ void ContextMenuSetDirection(QAction* action); - /** - * @brief Receiver for the user changing the filter - */ - void ContextMenuFilterChanged(QAction* action); - /** * @brief Opens the selected node in a Viewer */ void OpenSelectedNodeInViewer(); - //void AddNode(Node *node); - void RemoveNode(Node *node); - void AddEdge(Node *output, const NodeInput& input); - void RemoveEdge(Node *output, const NodeInput& input); - - void AddNodePosition(Node *node, Node *relative); - void RemoveNodePosition(Node *node, Node *relative); - void UpdateSceneBoundingRect(); - void CenterOnItemsBoundingRect(); - void RepositionMiniMap(); void UpdateViewportOnMiniMap(); void MoveToScenePoint(const QPointF &pos); - void RepositionContexts(); - void GroupNodes(); void UngroupNodes(); diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index db22be143..1b52624a6 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -19,37 +19,29 @@ namespace olive { #define super QGraphicsRectItem -NodeViewContext::NodeViewContext(QGraphicsItem *item) : - super(item) +NodeViewContext::NodeViewContext(Node *context, QGraphicsItem *item) : + super(item), + context_(context) { - // Set default label text - SetContext(nullptr); -} - -void NodeViewContext::SetContext(Node *node) -{ - context_ = node; - - if (context_) { - if (Block *block = dynamic_cast(node)) { - rational timebase = block->track()->sequence()->GetVideoParams().frame_rate_as_time_base(); - lbl_ = QCoreApplication::translate("NodeViewContext", - "%1 [%2] :: %3 - %4").arg(block->GetLabelAndName(), - Track::Reference::TypeToTranslatedString(block->track()->type()), - Timecode::time_to_timecode(block->in(), timebase, Core::instance()->GetTimecodeDisplay()), - Timecode::time_to_timecode(block->out(), timebase, Core::instance()->GetTimecodeDisplay())); - } else { - lbl_ = node->GetLabelAndName(); - } - - Color c = node->color(); - setPen(QPen(c.toQColor(), 2)); - - c.set_alpha(0.5f); - setBrush(c.toQColor()); + if (Block *block = dynamic_cast(context_)) { + rational timebase = block->track()->sequence()->GetVideoParams().frame_rate_as_time_base(); + lbl_ = QCoreApplication::translate("NodeViewContext", + "%1 [%2] :: %3 - %4").arg(block->GetLabelAndName(), + Track::Reference::TypeToTranslatedString(block->track()->type()), + Timecode::time_to_timecode(block->in(), timebase, Core::instance()->GetTimecodeDisplay()), + Timecode::time_to_timecode(block->out(), timebase, Core::instance()->GetTimecodeDisplay())); } else { - lbl_ = QCoreApplication::translate("NodeViewContext", "(None)"); + lbl_ = context_->GetLabelAndName(); } + + const Node::PositionMap &map = context_->GetContextPositions(); + for (auto it=map.cbegin(); it!=map.cend(); it++) { + AddChild(it.key()); + } + + connect(context_, &Node::NodeAddedToContext, this, &NodeViewContext::AddChild, Qt::DirectConnection); + connect(context_, &Node::NodePositionInContextChanged, this, &NodeViewContext::SetChildPosition, Qt::DirectConnection); + connect(context_, &Node::NodeRemovedFromContext, this, &NodeViewContext::RemoveChild, Qt::DirectConnection); } void NodeViewContext::AddChild(Node *node) @@ -59,33 +51,73 @@ void NodeViewContext::AddChild(Node *node) } NodeViewItem *item = new NodeViewItem(this); - item->SetNode(node); - item->SetNodePosition(context_->parent()->GetNodesForContext(context_).value(node)); + item->SetNode(node, context_); + item->SetNodePosition(context_->GetNodePositionInContext(node)); item->SetFlowDirection(flow_dir_); + connect(node, &Node::InputConnected, this, &NodeViewContext::ChildInputConnected); + connect(node, &Node::InputDisconnected, this, &NodeViewContext::ChildInputDisconnected); + + item_map_.insert(node, item); + if (node == context_) { item->SetLabelAsOutput(true); } for (auto it=node->output_connections().cbegin(); it!=node->output_connections().cend(); it++) { - foreach (auto child, childItems()) { - if (NodeViewItem *other_item = dynamic_cast(child)) { - if (other_item->GetNode() == it->second.node()) { - AddEdgeInternal(node, it->second, item, other_item); - } + if (!it->second.IsHidden()) { + if (NodeViewItem *other_item = item_map_.value(it->second.node())) { + AddEdgeInternal(node, it->second, item, other_item); } } } for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - foreach (auto child, childItems()) { - if (NodeViewItem *other_item = dynamic_cast(child)) { - if (it->second == other_item->GetNode()) { - AddEdgeInternal(it->second, it->first, other_item, item); - } + if (!it->first.IsHidden()) { + if (NodeViewItem *other_item = item_map_.value(it->second)) { + AddEdgeInternal(it->second, it->first, other_item, item); } } } + + UpdateRect(); +} + +void NodeViewContext::SetChildPosition(Node *node, const QPointF &pos) +{ + item_map_.value(node)->SetNodePosition(pos); +} + +void NodeViewContext::RemoveChild(Node *node) +{ + disconnect(node, &Node::InputConnected, this, &NodeViewContext::ChildInputConnected); + disconnect(node, &Node::InputDisconnected, this, &NodeViewContext::ChildInputDisconnected); + + delete item_map_.take(node); +} + +void NodeViewContext::ChildInputConnected(Node *output, const NodeInput &input) +{ + // Add edge + if (!input.IsHidden()) { + if (NodeViewItem* output_item = item_map_.value(output)) { + AddEdgeInternal(output, input, output_item, item_map_.value(input.node())); + } + } +} + +bool NodeViewContext::ChildInputDisconnected(Node *output, const NodeInput &input) +{ + // Remove edge + for (int i=0; ioutput() == output && edges_.at(i)->input() == input) { + delete edges_.at(i); + edges_.removeAt(i); + return true; + } + } + + return false; } qreal GetTextOffset(const QFontMetricsF &fm) @@ -105,23 +137,19 @@ void NodeViewContext::UpdateRect() rect.adjust(-pad, - lbl_offset*2 - fm.height() - pad, pad, pad); setRect(rect); - last_titlebar_height_ = rect.y() + (cbr.y() - rect.y()); + last_titlebar_height_ = rect.y() + (cbr.y() - rect.y()) - pad; } void NodeViewContext::SetFlowDirection(NodeViewCommon::FlowDirection dir) { flow_dir_ = dir; - foreach (auto child, childItems()) { - if (NodeViewItem *item = dynamic_cast(child)) { - item->SetFlowDirection(dir); - } + foreach (NodeViewItem *item, item_map_) { + item->SetFlowDirection(dir); } - foreach (auto child, childItems()) { - if (NodeViewEdge *edge = dynamic_cast(child)) { - edge->SetFlowDirection(dir); - } + foreach (NodeViewEdge *edge, edges_) { + edge->SetFlowDirection(dir); } } @@ -129,29 +157,40 @@ void NodeViewContext::SetCurvedEdges(bool e) { curved_edges_ = e; - const QList &children = childItems(); - foreach (auto child, children) { - if (NodeViewEdge *edge = dynamic_cast(child)) { - edge->SetCurved(e); - } + foreach (NodeViewEdge *edge, edges_) { + edge->SetCurved(e); } } void NodeViewContext::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { - QPen p = pen(); - + // Set pen and brush + Color color = context_->color(); + QColor c = color.toQColor(); + QPen pen(c, 2); if (option->state & QStyle::State_Selected) { - p.setStyle(Qt::DotLine); + pen.setStyle(Qt::DotLine); } + painter->setPen(pen); - painter->setPen(p); - painter->setBrush(brush()); + QColor bg = c; + bg.setAlpha(128); + painter->setBrush(bg); + // Draw semi-transparent rect for whole item int rounded = painter->fontMetrics().height(); painter->drawRoundedRect(rect(), rounded, rounded); - painter->setPen(widget->palette().text().color()); + // Draw solid background for titlebar + QRectF titlebar_rect = rect(); + titlebar_rect.setHeight(last_titlebar_height_ - rect().top()); + painter->setClipRect(titlebar_rect); + painter->setBrush(c); + painter->drawRoundedRect(rect(), rounded, rounded); + painter->setClipping(false); + + // Draw titlebar text + painter->setPen(ColorCoding::GetUISelectorColor(color)); int offset = GetTextOffset(painter->fontMetrics()); @@ -182,8 +221,7 @@ NodeViewEdge* NodeViewContext::AddEdgeInternal(Node *output, const NodeInput& in edge_ui->SetFlowDirection(flow_dir_); edge_ui->SetCurved(curved_edges_); - from->AddEdge(edge_ui); - to->AddEdge(edge_ui); + edges_.append(edge_ui); return edge_ui; } diff --git a/app/widget/nodeview/nodeviewcontext.h b/app/widget/nodeview/nodeviewcontext.h index cf35b6fd2..4ef21f68f 100644 --- a/app/widget/nodeview/nodeviewcontext.h +++ b/app/widget/nodeview/nodeviewcontext.h @@ -10,14 +10,11 @@ namespace olive { -class NodeViewContext : public QGraphicsRectItem +class NodeViewContext : public QObject, public QGraphicsRectItem { + Q_OBJECT public: - NodeViewContext(QGraphicsItem *item = nullptr); - - void SetContext(Node *node); - - void AddChild(Node *node); + NodeViewContext(Node *context, QGraphicsItem *item = nullptr); void UpdateRect(); @@ -27,6 +24,17 @@ public: virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; +public slots: + void AddChild(Node *node); + + void SetChildPosition(Node *node, const QPointF &pos); + + void RemoveChild(Node *node); + + void ChildInputConnected(Node *output, const NodeInput& input); + + bool ChildInputDisconnected(Node *output, const NodeInput& input); + protected: virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; @@ -45,6 +53,10 @@ private: int last_titlebar_height_; + QMap item_map_; + + QVector edges_; + }; } diff --git a/app/widget/nodeview/nodeviewedge.cpp b/app/widget/nodeview/nodeviewedge.cpp index 977b88cbd..caf629007 100644 --- a/app/widget/nodeview/nodeviewedge.cpp +++ b/app/widget/nodeview/nodeviewedge.cpp @@ -46,6 +46,9 @@ NodeViewEdge::NodeViewEdge(Node *output, const NodeInput &input, { Init(); SetConnected(true); + + from_item_->AddEdge(this); + to_item_->AddEdge(this); } NodeViewEdge::NodeViewEdge(QGraphicsItem *parent) : @@ -56,6 +59,17 @@ NodeViewEdge::NodeViewEdge(QGraphicsItem *parent) : Init(); } +NodeViewEdge::~NodeViewEdge() +{ + if (from_item_) { + from_item_->RemoveEdge(this); + } + + if (to_item_) { + to_item_->RemoveEdge(this); + } +} + void NodeViewEdge::Adjust() { // Draw a line between the two diff --git a/app/widget/nodeview/nodeviewedge.h b/app/widget/nodeview/nodeviewedge.h index de8d10720..31a56664e 100644 --- a/app/widget/nodeview/nodeviewedge.h +++ b/app/widget/nodeview/nodeviewedge.h @@ -45,6 +45,8 @@ public: NodeViewEdge(QGraphicsItem* parent = nullptr); + virtual ~NodeViewEdge() override; + Node *output() const { return output_; diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index b3e4ee4e9..1c73f8ddd 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -42,6 +42,7 @@ namespace olive { NodeViewItem::NodeViewItem(QGraphicsItem *parent) : QGraphicsRectItem(parent), node_(nullptr), + context_(nullptr), expanded_(false), hide_titlebar_(false), highlighted_index_(-1), @@ -207,7 +208,7 @@ int NodeViewItem::GetIndexAt(QPointF pt) const return -1; } -void NodeViewItem::SetNode(Node *n) +void NodeViewItem::SetNode(Node *n, Node *context) { if (node_) { disconnect(n, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); @@ -215,6 +216,7 @@ void NodeViewItem::SetNode(Node *n) } node_ = n; + context_ = context; node_inputs_.clear(); input_connectors_.clear(); @@ -223,7 +225,7 @@ void NodeViewItem::SetNode(Node *n) node_->Retranslate(); foreach (const QString& input, node_->inputs()) { - if (node_->IsInputConnectable(input)) { + if (node_->IsInputConnectable(input) && !node_->IsInputHidden(input)) { node_inputs_.append(input); } } @@ -327,9 +329,11 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti int icon_size = painter->fontMetrics().height()/2; + bool draw_arrow = !node_inputs_.isEmpty(); + if (node_label.isEmpty()) { // Draw shortname only - DrawNodeTitle(painter, node_shortname, title_bar_rect_, Qt::AlignVCenter, icon_size, true); + DrawNodeTitle(painter, node_shortname, title_bar_rect_, Qt::AlignVCenter, icon_size, draw_arrow); } else { int text_pad = DefaultTextPadding()/2; QRectF safe_label_bounds = title_bar_rect_.adjusted(text_pad, text_pad, -text_pad, -text_pad); @@ -337,7 +341,7 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti qreal font_sz = f.pointSizeF(); f.setPointSizeF(font_sz * 0.8); painter->setFont(f); - DrawNodeTitle(painter, node_label, safe_label_bounds, Qt::AlignTop, icon_size, true); + DrawNodeTitle(painter, node_label, safe_label_bounds, Qt::AlignTop, icon_size, draw_arrow); f.setPointSizeF(font_sz * 0.6); painter->setFont(f); DrawNodeTitle(painter, node_shortname, safe_label_bounds, Qt::AlignBottom, icon_size, false); diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index 0f11574ed..b92f531c7 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -53,7 +53,7 @@ public: /** * @brief Set the Node to correspond to this widget */ - void SetNode(Node* n); + void SetNode(Node* n, Node *context); /** * @brief Get currently attached node @@ -174,6 +174,8 @@ private: */ Node* node_; + Node *context_; + /** * @brief Cached list of node inputs */ diff --git a/app/widget/nodeview/nodeviewminimap.cpp b/app/widget/nodeview/nodeviewminimap.cpp index d1e20791c..8dc99c914 100644 --- a/app/widget/nodeview/nodeviewminimap.cpp +++ b/app/widget/nodeview/nodeviewminimap.cpp @@ -38,6 +38,7 @@ NodeViewMiniMap::NodeViewMiniMap(NodeViewScene *scene, QWidget *parent) : setViewportUpdateMode(FullViewportUpdate); setFrameShape(QFrame::Panel); setFrameShadow(QFrame::Plain); + setMouseTracking(true); QMetaObject::invokeMethod(this, &NodeViewMiniMap::SetDefaultSize, Qt::QueuedConnection); @@ -87,7 +88,7 @@ void NodeViewMiniMap::resizeEvent(QResizeEvent *event) void NodeViewMiniMap::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { - if (event->pos().x() <= resize_triangle_sz_ && event->pos().y() <= resize_triangle_sz_) { + if (MouseInsideResizeTriangle(event)) { // Resizing! resizing_ = true; resize_anchor_ = QCursor::pos(); @@ -107,6 +108,8 @@ void NodeViewMiniMap::mouseMoveEvent(QMouseEvent *event) } else { EmitMoveSignal(event); } + } else { + setCursor(MouseInsideResizeTriangle(event) ? Qt::SizeFDiagCursor : Qt::ArrowCursor); } } @@ -135,6 +138,11 @@ void NodeViewMiniMap::SetDefaultSize() } } +bool NodeViewMiniMap::MouseInsideResizeTriangle(QMouseEvent *event) +{ + return event->pos().x() <= resize_triangle_sz_ && event->pos().y() <= resize_triangle_sz_; +} + void NodeViewMiniMap::EmitMoveSignal(QMouseEvent *event) { emit MoveToScenePoint(mapToScene(event->pos())); diff --git a/app/widget/nodeview/nodeviewminimap.h b/app/widget/nodeview/nodeviewminimap.h index a63db1993..e155b54f2 100644 --- a/app/widget/nodeview/nodeviewminimap.h +++ b/app/widget/nodeview/nodeviewminimap.h @@ -57,6 +57,8 @@ private slots: void SetDefaultSize(); private: + bool MouseInsideResizeTriangle(QMouseEvent *event); + void EmitMoveSignal(QMouseEvent *event); int resize_triangle_sz_; diff --git a/app/widget/nodeview/nodeviewscene.cpp b/app/widget/nodeview/nodeviewscene.cpp index feb2598b3..2e04a2ae6 100644 --- a/app/widget/nodeview/nodeviewscene.cpp +++ b/app/widget/nodeview/nodeviewscene.cpp @@ -143,45 +143,25 @@ QVector NodeViewScene::GetSelectedEdges() const return edges; } -NodeViewEdge* NodeViewScene::AddEdge(Node *output, const NodeInput &input) -{ - NodeViewEdge *edge = EdgeToUIObject(output, input); - - if (!edge) { - edge = AddEdgeInternal(output, input, NodeToUIObject(output), NodeToUIObject(input.node())); - } - - return edge; -} - -void NodeViewScene::RemoveEdge(Node *output, const NodeInput &input) -{ - NodeViewEdge* edge = EdgeToUIObject(output, input); - if (edge) { - edge->from_item()->RemoveEdge(edge); - edge->to_item()->RemoveEdge(edge); - edges_.removeOne(edge); - delete edge; - } -} - 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); + context_item = new NodeViewContext(node); + context_item->SetFlowDirection(GetFlowDirection()); context_item->SetCurvedEdges(GetEdgesAreCurved()); - 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()); + QPointF pos(0, 0); + QRectF item_rect = context_item->rect(); + while (!items(item_rect).isEmpty()) { + pos.setY(pos.y() + item_rect.height()); + item_rect = context_item->rect().translated(pos); } - context_item->UpdateRect(); + context_item->setPos(pos); + + addItem(context_item); context_map_.insert(node, context_item); } @@ -209,22 +189,6 @@ int NodeViewScene::DetermineWeight(Node *n) return qMax(1, weight); } -NodeViewEdge* NodeViewScene::AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to) -{ - NodeViewEdge* edge_ui = new NodeViewEdge(output, input, from, to); - - edge_ui->SetFlowDirection(direction_); - edge_ui->SetCurved(curved_edges_); - - from->AddEdge(edge_ui); - to->AddEdge(edge_ui); - - addItem(edge_ui); - edges_.append(edge_ui); - - return edge_ui; -} - Qt::Orientation NodeViewScene::GetFlowOrientation() const { return NodeViewCommon::GetFlowOrientation(direction_); @@ -240,8 +204,8 @@ void NodeViewScene::SetEdgesAreCurved(bool curved) if (curved_edges_ != curved) { curved_edges_ = curved; - foreach (NodeViewEdge* e, edges_) { - e->SetCurved(curved_edges_); + foreach (NodeViewContext *ctx, context_map_) { + ctx->SetCurvedEdges(curved_edges_); } } } diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index f37ebbdcd..db49aca89 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -81,9 +81,6 @@ public: } public slots: - NodeViewEdge *AddEdge(Node *output, const NodeInput& input); - void RemoveEdge(Node *output, const NodeInput& input); - NodeViewContext *AddContext(Node *node); void RemoveContext(Node *node); @@ -95,8 +92,6 @@ public slots: private: static int DetermineWeight(Node* n); - NodeViewEdge* AddEdgeInternal(Node *output, const NodeInput &input, NodeViewItem* from, NodeViewItem* to); - QHash context_map_; QHash item_map_; diff --git a/app/widget/timelinewidget/tool/add.cpp b/app/widget/timelinewidget/tool/add.cpp index 07343ccd3..a9c9d3615 100644 --- a/app/widget/timelinewidget/tool/add.cpp +++ b/app/widget/timelinewidget/tool/add.cpp @@ -116,7 +116,7 @@ void AddTool::MouseRelease(TimelineViewMouseEvent *event) NodeGraph* graph = static_cast(parent()->GetConnectedNode()->parent()); command->add_child(new NodeAddCommand(graph, clip)); - command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0), false)); + command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0))); command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track.type()), track.index(), clip, @@ -155,10 +155,10 @@ void AddTool::MouseRelease(TimelineViewMouseEvent *event) } if (node_to_add) { - QPointF extra_node_offset(-1, 0); + QPointF extra_node_offset(kDefaultDistanceFromOutput, 0); command->add_child(new NodeAddCommand(graph, node_to_add)); command->add_child(new NodeEdgeAddCommand(node_to_add, NodeInput(clip, ClipBlock::kBufferIn))); - command->add_child(new NodeSetPositionCommand(node_to_add, clip, extra_node_offset, false)); + command->add_child(new NodeSetPositionCommand(node_to_add, clip, extra_node_offset)); } Core::instance()->undo_stack()->push(command); diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index 9ad1a6a74..f9ab4b6f8 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -355,7 +355,7 @@ void ImportTool::DropGhosts(bool insert) command->add_child(new NodeAddCommand(dst_graph, new_sequence)); command->add_child(new FolderAddChild(Core::instance()->GetSelectedFolderInActiveProject(), new_sequence)); - command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, QPointF(0, 0), false)); + command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, QPointF(0, 0))); new_sequence->add_default_nodes(command); FootageToGhosts(0, dragged_footage_, new_sequence->GetVideoParams().time_base(), 0); @@ -394,10 +394,14 @@ void ImportTool::DropGhosts(bool insert) command->add_child(new NodeAddCommand(dst_graph, clip)); // Position clip in its own context - command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0), false)); + command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0))); + + int dep_pos = kDefaultDistanceFromOutput; // Position footage in its context - command->add_child(new NodeSetPositionCommand(footage_stream.footage, clip, QPointF(-3, 0), false)); + command->add_child(new NodeSetPositionCommand(footage_stream.footage, clip, QPointF(dep_pos, 0))); + + dep_pos++; switch (Track::Reference::TypeFromString(footage_stream.output)) { case Track::kVideo: @@ -409,7 +413,7 @@ void ImportTool::DropGhosts(bool insert) command->add_child(new NodeEdgeAddCommand(footage_stream.footage, NodeInput(transform, TransformDistortNode::kTextureInput))); command->add_child(new NodeEdgeAddCommand(transform, NodeInput(clip, ClipBlock::kBufferIn))); - command->add_child(new NodeSetPositionCommand(transform, clip, QPointF(-2, 0), false)); + command->add_child(new NodeSetPositionCommand(transform, clip, QPointF(dep_pos, 0))); break; } case Track::kAudio: @@ -421,7 +425,7 @@ void ImportTool::DropGhosts(bool insert) command->add_child(new NodeEdgeAddCommand(footage_stream.footage, NodeInput(volume_node, VolumeNode::kSamplesInput))); command->add_child(new NodeEdgeAddCommand(volume_node, NodeInput(clip, ClipBlock::kBufferIn))); - command->add_child(new NodeSetPositionCommand(volume_node, clip, QPointF(-2, 0), false)); + command->add_child(new NodeSetPositionCommand(volume_node, clip, QPointF(dep_pos, 0))); break; } default: diff --git a/app/widget/timelinewidget/tool/tool.cpp b/app/widget/timelinewidget/tool/tool.cpp index 752409b78..2612b9697 100644 --- a/app/widget/timelinewidget/tool/tool.cpp +++ b/app/widget/timelinewidget/tool/tool.cpp @@ -24,6 +24,8 @@ namespace olive { +const int TimelineTool::kDefaultDistanceFromOutput = -4; + TimelineTool::TimelineTool(TimelineWidget *parent) : dragging_(false), parent_(parent) diff --git a/app/widget/timelinewidget/tool/tool.h b/app/widget/timelinewidget/tool/tool.h index d229e1ad3..c627f542e 100644 --- a/app/widget/timelinewidget/tool/tool.h +++ b/app/widget/timelinewidget/tool/tool.h @@ -84,6 +84,8 @@ protected: TimelineCoordinate drag_start_; + static const int kDefaultDistanceFromOutput; + private: TimelineWidget* parent_; diff --git a/app/widget/timelinewidget/tool/transition.cpp b/app/widget/timelinewidget/tool/transition.cpp index f1a1f4fdd..594659ccb 100644 --- a/app/widget/timelinewidget/tool/transition.cpp +++ b/app/widget/timelinewidget/tool/transition.cpp @@ -113,7 +113,7 @@ void TransitionTool::MouseRelease(TimelineViewMouseEvent *event) command->add_child(new NodeAddCommand(static_cast(parent()->GetConnectedNode()->parent()), transition)); - command->add_child(new NodeSetPositionCommand(transition, transition, QPointF(0, 0), false)); + command->add_child(new NodeSetPositionCommand(transition, transition, QPointF(0, 0))); command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track.type()), track.index(), @@ -138,8 +138,8 @@ void TransitionTool::MouseRelease(TimelineViewMouseEvent *event) command->add_child(new NodeEdgeAddCommand(in_block, NodeInput(transition, TransitionBlock::kInBlockInput))); - command->add_child(new NodeSetPositionCommand(out_block, transition, QPointF(-1, -0.5), false)); - command->add_child(new NodeSetPositionCommand(in_block, transition, QPointF(-1, 0.5), false)); + command->add_child(new NodeSetPositionCommand(out_block, transition, QPointF(-1, -0.5))); + command->add_child(new NodeSetPositionCommand(in_block, transition, QPointF(-1, 0.5))); } else { Block* block_to_transition = Node::ValueToPtr(ghost_->GetData(TimelineViewGhostItem::kAttachedBlock)); QString transition_input_to_connect; @@ -154,7 +154,7 @@ void TransitionTool::MouseRelease(TimelineViewMouseEvent *event) command->add_child(new NodeEdgeAddCommand(block_to_transition, NodeInput(transition, transition_input_to_connect))); - command->add_child(new NodeSetPositionCommand(block_to_transition, transition, QPointF(-1, 0), false)); + command->add_child(new NodeSetPositionCommand(block_to_transition, transition, QPointF(-1, 0))); } Core::instance()->undo_stack()->push(command); diff --git a/app/widget/timelinewidget/undo/timelineundogeneral.cpp b/app/widget/timelinewidget/undo/timelineundogeneral.cpp index 63958f250..9c8bbae14 100644 --- a/app/widget/timelinewidget/undo/timelineundogeneral.cpp +++ b/app/widget/timelinewidget/undo/timelineundogeneral.cpp @@ -73,131 +73,121 @@ void BlockSetMediaInCommand::undo() // // TimelineAddTrackCommand // -TimelineAddTrackCommand::TimelineAddTrackCommand(TrackList *timeline, bool automerge_tracks) +TimelineAddTrackCommand::TimelineAddTrackCommand(TrackList *timeline, bool automerge_tracks) : + timeline_(timeline), + merge_(nullptr), + position_command_(nullptr) { - timeline_ = timeline; - position_command_ = nullptr; - + // Create new track track_ = new Track(); track_->setParent(&memory_manager_); - if (timeline->GetTrackCount() > 0 && automerge_tracks) { - if (timeline_->type() == Track::kVideo) { - merge_ = new MergeNode(); - base_ = NodeInput(merge_, MergeNode::kBaseIn); - blend_ = NodeInput(merge_, MergeNode::kBlendIn); - } else if (timeline_->type() == Track::kAudio) { - merge_ = new MathNode(); - base_ = NodeInput(merge_, MathNode::kParamAIn); - blend_ = NodeInput(merge_, MathNode::kParamBIn); - } else { - merge_ = nullptr; - } - } else { - merge_ = nullptr; + // Determine what input to connect it to + QString relevant_input; + + if (timeline_->type() == Track::kVideo) { + relevant_input = Sequence::kTextureInput; + } else if (timeline_->type() == Track::kAudio) { + relevant_input = Sequence::kSamplesInput; } - if (merge_) { - merge_->setParent(&memory_manager_); + // If we have an input to connect to, set it as our `direct` connection + if (!relevant_input.isEmpty()) { + direct_ = NodeInput(timeline_->parent(), relevant_input); + + // If we're automerging and something is already connected, determine if/how to merge it + if (automerge_tracks && direct_.IsConnected()) { + if (timeline_->type() == Track::kVideo) { + // Use merge for video + merge_ = new MergeNode(); + base_ = NodeInput(merge_, MergeNode::kBaseIn); + blend_ = NodeInput(merge_, MergeNode::kBlendIn); + } else if (timeline_->type() == Track::kAudio) { + // Use math (add) for audio + merge_ = new MathNode(); + base_ = NodeInput(merge_, MathNode::kParamAIn); + blend_ = NodeInput(merge_, MathNode::kParamBIn); + } + + if (merge_) { + // If we got created a merge node, ensure it's parented + merge_->setParent(&memory_manager_); + } + } } } void TimelineAddTrackCommand::redo() { - // Add track + // Get sequence + Sequence* sequence = timeline_->parent(); + + // Add track to sequence track_->setParent(timeline_->GetParentGraph()); timeline_->ArrayAppend(); Node::ConnectEdge(track_, timeline_->track_input(timeline_->ArraySize() - 1)); + qreal position_factor = 0.5; + if (timeline_->type() == Track::kVideo) { + position_factor = -position_factor; + } + bool create_pos_command = (!position_command_ && (timeline_->type() == Track::kVideo || timeline_->type() == Track::kAudio)); + if (create_pos_command) { + position_command_ = new MultiUndoCommand(); + } + // Add merge if applicable - Track* last_track = nullptr; if (merge_) { + // Determine what was previously connected + Node *previous_connection = direct_.GetConnectedOutput(); + + // Add merge to graph merge_->setParent(timeline_->GetParentGraph()); - last_track = timeline_->GetTrackAt(timeline_->GetTrackCount()-2); - - // Whatever this track used to be connected to, connect the merge instead - const Node::OutputConnections edges = last_track->output_connections(); - for (const Node::OutputConnection& ic : edges) { - const NodeInput& i = ic.second; - - // Ignore the track input, but funnel everything else through our merge - if (i.node() != timeline_->parent() || i.input() != timeline_->track_input()) { - Node::DisconnectEdge(last_track, i); - Node::ConnectEdge(merge_, i); - } - } - - // Connect this as the "blend" track + // Connect merge between what used to be here + Node::DisconnectEdge(previous_connection, direct_); + Node::ConnectEdge(merge_, direct_); + Node::ConnectEdge(previous_connection, base_); Node::ConnectEdge(track_, blend_); - Node::ConnectEdge(last_track, base_); - } else if (timeline_->GetTrackCount() == 1) { - // If this was the first track we added, - QString relevant_input; - if (timeline_->type() == Track::kVideo) { - relevant_input = ViewerOutput::kTextureInput; - } else if (timeline_->type() == Track::kAudio) { - relevant_input = ViewerOutput::kSamplesInput; + if (create_pos_command) { + position_command_->add_child(new NodeSetPositionCommand(track_, sequence, sequence->GetNodePositionInContext(sequence) + QPointF(-1, -position_factor))); + position_command_->add_child(new NodeSetPositionCommand(merge_, sequence, sequence->GetNodePositionInContext(sequence))); + position_command_->add_child(new NodeSetPositionCommand(merge_, sequence, sequence->GetNodePositionInContext(sequence) + QPointF(-1, position_factor * timeline_->GetTrackCount()), true)); } + } else if (direct_.IsValid() && !direct_.IsConnected()) { + // If no merge, we have a direct connection, and nothing else is connected, connect this + Node::ConnectEdge(track_, direct_); - if (!relevant_input.isEmpty() && !timeline_->parent()->IsInputConnected(relevant_input)) { - direct_ = NodeInput(timeline_->parent(), relevant_input); - - Node::ConnectEdge(track_, direct_); - } else { - direct_ = NodeInput(); + if (create_pos_command) { + // Just position directly next to the context node + position_command_->add_child(new NodeSetPositionCommand(track_, sequence, sequence->GetNodePositionInContext(sequence) + QPointF(-1, position_factor))); } } - // Position track in context - if (!position_command_) { - int track_count = timeline_->parent()->GetTracks().size(); - position_command_ = new MultiUndoCommand(); - - // Position either the merge or the track as an "element" - Node *node_to_position = merge_ ? merge_ : track_; - double node_index = track_count - 1; - if (merge_) { - node_index -= 1; - position_command_->add_child(new NodeRemovePositionFromContextCommand(last_track, timeline_->parent())); - } - - position_command_->add_child(new NodeSetPositionAsChildCommand(node_to_position, timeline_->parent(), timeline_->parent(), node_index, track_count, true)); - - // If we positioned a merge, position the tracks as children of the merge - if (merge_) { - // `last_track` should be non-null if `merge_` is non-null - position_command_->add_child(new NodeSetPositionAsChildCommand(last_track, merge_, timeline_->parent(), 0, 2, true)); - position_command_->add_child(new NodeSetPositionAsChildCommand(track_, merge_, timeline_->parent(), 1, 2, true)); - } + // Run position command if we created one + if (position_command_) { + position_command_->redo_now(); } - position_command_->redo_now(); } void TimelineAddTrackCommand::undo() { - position_command_->undo_now(); + if (position_command_) { + position_command_->undo_now(); + } // Remove merge if applicable if (merge_) { - // Assume whatever this merge is connected to USED to be connected to the last track - Track* last_track = timeline_->GetTrackAt(timeline_->GetTrackCount()-2); + Node *previous_connection = base_.GetConnectedOutput(); Node::DisconnectEdge(track_, blend_); - Node::DisconnectEdge(last_track, base_); - - // Make copy of edges since the node's internal array will change as we disconnect things - const Node::OutputConnections edges = merge_->output_connections(); - for (const Node::OutputConnection& ic : edges) { - const NodeInput& i = ic.second; - - Node::DisconnectEdge(merge_, i); - Node::ConnectEdge(last_track, i); - } + Node::DisconnectEdge(previous_connection, base_); + Node::DisconnectEdge(merge_, direct_); + Node::ConnectEdge(previous_connection, direct_); merge_->setParent(&memory_manager_); - } else if (direct_.IsValid()) { + } else if (direct_.IsValid() && direct_.GetConnectedOutput() == track_) { Node::DisconnectEdge(track_, direct_); } @@ -496,11 +486,6 @@ void TrackReplaceBlockWithGapCommand::redo() our_gap_->setParent(track_->parent()); track_->ReplaceBlock(block_, our_gap_); - - if (!position_command_) { - position_command_ = new NodeSetPositionAsChildCommand(our_gap_, track_, track_, our_gap_->index(), track_->Blocks().size(), true); - } - position_command_->redo_now(); } track_->EndOperation(); @@ -533,8 +518,6 @@ void TrackReplaceBlockWithGapCommand::undo() track_->ReplaceBlock(our_gap_, block_); our_gap_->setParent(&memory_manager_); - position_command_->undo_now(); - } else { // If we're here, assume that we extended an existing gap diff --git a/app/widget/timelinewidget/undo/timelineundogeneral.h b/app/widget/timelinewidget/undo/timelineundogeneral.h index 423f4da84..490d9b082 100644 --- a/app/widget/timelinewidget/undo/timelineundogeneral.h +++ b/app/widget/timelinewidget/undo/timelineundogeneral.h @@ -203,16 +203,10 @@ public: existing_gap_(nullptr), existing_merged_gap_(nullptr), our_gap_(nullptr), - handle_transitions_(handle_transitions), - position_command_(nullptr) + handle_transitions_(handle_transitions) { } - virtual ~TrackReplaceBlockWithGapCommand() override - { - delete position_command_; - } - virtual Project* GetRelevantProject() const override { return block_->project(); @@ -236,8 +230,6 @@ private: bool handle_transitions_; - NodeSetPositionAsChildCommand* position_command_; - QObject memory_manager_; QVector transition_remove_commands_; diff --git a/app/widget/timelinewidget/undo/timelineundopointer.cpp b/app/widget/timelinewidget/undo/timelineundopointer.cpp index 4f5c448b6..0f6501b19 100644 --- a/app/widget/timelinewidget/undo/timelineundopointer.cpp +++ b/app/widget/timelinewidget/undo/timelineundopointer.cpp @@ -324,7 +324,6 @@ TrackPlaceBlockCommand::~TrackPlaceBlockCommand() { delete ripple_remove_command_; qDeleteAll(add_track_commands_); - qDeleteAll(position_commands_); } void TrackPlaceBlockCommand::redo() @@ -367,14 +366,6 @@ void TrackPlaceBlockCommand::redo() } track->AppendBlock(insert_); - - if (position_commands_.isEmpty()) { - // Create position commands for insert and gap if necessary - if (gap_) { - position_commands_.append(new NodeSetPositionAsChildCommand(gap_, track, track, gap_->index(), track->Blocks().size(), true)); - } - position_commands_.append(new NodeSetPositionAsChildCommand(insert_, track, track, insert_->index(), track->Blocks().size(), true)); - } } else { // Place the Block at this point if (!ripple_remove_command_) { @@ -384,10 +375,6 @@ void TrackPlaceBlockCommand::redo() ripple_remove_command_->redo_now(); track->InsertBlockAfter(insert_, ripple_remove_command_->GetInsertionIndex()); - - if (position_commands_.isEmpty()) { - position_commands_.append(new NodeSetPositionAsChildCommand(insert_, track, track, insert_->index(), track->Blocks().size(), true)); - } } track->EndOperation(); @@ -397,18 +384,10 @@ void TrackPlaceBlockCommand::redo() foreach (const TimeRange &r, ranges_to_invalidate) { track->Node::InvalidateCache(r, Track::kBlockInput); } - - for (int i=0; iredo_now(); - } } void TrackPlaceBlockCommand::undo() { - for (int i=position_commands_.size()-1; i>=0; i--) { - position_commands_.at(i)->undo_now(); - } - Track* t = timeline_->GetTrackAt(track_index_); TimeRange insert_range(insert_->in(), insert_->out()); diff --git a/app/widget/timelinewidget/undo/timelineundopointer.h b/app/widget/timelinewidget/undo/timelineundopointer.h index fc7cf6566..4483d81d2 100644 --- a/app/widget/timelinewidget/undo/timelineundopointer.h +++ b/app/widget/timelinewidget/undo/timelineundopointer.h @@ -198,7 +198,6 @@ private: QVector add_track_commands_; QObject memory_manager_; TrackRippleRemoveAreaCommand* ripple_remove_command_; - QVector position_commands_; }; diff --git a/app/widget/timelinewidget/undo/timelineundosplit.cpp b/app/widget/timelinewidget/undo/timelineundosplit.cpp index 05c3b2707..06cdd1006 100644 --- a/app/widget/timelinewidget/undo/timelineundosplit.cpp +++ b/app/widget/timelinewidget/undo/timelineundosplit.cpp @@ -61,12 +61,6 @@ void BlockSplitCommand::redo() // Insert new block track->InsertBlockAfter(new_block(), block_); - // Position the block - if (!position_command_) { - position_command_ = new NodeSetPositionAsChildCommand(new_block(), track, track, new_block()->index(), track->Blocks().size(), true); - } - position_command_->redo_now(); - // If the block had an out transition, we move it to the new block moved_transition_ = NodeInput(); @@ -96,8 +90,6 @@ void BlockSplitCommand::undo() Node::ConnectEdge(block_, moved_transition_); } - position_command_->undo_now(); - block_->set_length_and_media_out(old_length_); track->RippleRemoveBlock(new_block()); diff --git a/app/widget/timelinewidget/undo/timelineundosplit.h b/app/widget/timelinewidget/undo/timelineundosplit.h index 82b57ebca..6984377f8 100644 --- a/app/widget/timelinewidget/undo/timelineundosplit.h +++ b/app/widget/timelinewidget/undo/timelineundosplit.h @@ -31,15 +31,13 @@ public: block_(block), new_block_(nullptr), point_(point), - reconnect_tree_command_(nullptr), - position_command_(nullptr) + reconnect_tree_command_(nullptr) { } virtual ~BlockSplitCommand() override { delete reconnect_tree_command_; - delete position_command_; } virtual Project* GetRelevantProject() const override @@ -70,8 +68,6 @@ private: NodeInput moved_transition_; - NodeSetPositionAsChildCommand* position_command_; - }; class BlockSplitPreservingLinksCommand : public UndoCommand { diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 8f1ace9ef..47c23c3cb 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -378,9 +378,7 @@ void MainWindow::ProjectClose(Project *p) } // Close project from NodeView - if (node_panel_->GetGraph() == p) { - node_panel_->ClearGraph(); - } + node_panel_->CloseContextsBelongingToProject(p); } void MainWindow::SetApplicationProgressStatus(ProgressStatus status) @@ -731,11 +729,7 @@ void MainWindow::UpdateNodePanelContextFromTimelinePanel(TimelinePanel *panel) context.append(viewer); } - QVector old_contexts = node_panel_->GetCurrentContexts(); - node_panel_->SetGraph(viewer ? viewer->parent() : nullptr, context); - if (viewer && context != old_contexts) { - node_panel_->SelectAll(); - } + node_panel_->SetContexts(context); } void MainWindow::FocusedPanelChanged(PanelWidget *panel) @@ -753,10 +747,6 @@ void MainWindow::FocusedPanelChanged(PanelWidget *panel) } else if (ProjectPanel* project = dynamic_cast(panel)) { // Signal project panel focus UpdateTitle(); - if (Project *p = project->project()) { - node_panel_->SetGraph(p, {p->root()}); - node_panel_->Select({p->color_manager(), p->settings()}, true); - } } } From a6af473f5322479838cee10898c001aca6a22983 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 17 Nov 2021 12:09:41 -0800 Subject: [PATCH 07/34] reimplemented node positioning and deleting --- app/node/graph.cpp | 13 ++++ app/node/graph.h | 2 + app/task/project/loadotio/loadotio.cpp | 14 ++-- app/widget/nodeview/nodeview.cpp | 52 ++++---------- app/widget/nodeview/nodeview.h | 2 + app/widget/nodeview/nodeviewcontext.cpp | 34 ++++++++- app/widget/nodeview/nodeviewcontext.h | 5 ++ app/widget/nodeview/nodeviewitem.cpp | 60 +++++++--------- app/widget/nodeview/nodeviewitem.h | 12 ++-- app/widget/nodeview/nodeviewscene.cpp | 55 +++++--------- app/widget/nodeview/nodeviewscene.h | 11 +-- app/widget/nodeview/nodeviewundo.cpp | 96 +++++++++++++++++++++++++ app/widget/nodeview/nodeviewundo.h | 36 ++++++++++ 13 files changed, 255 insertions(+), 137 deletions(-) diff --git a/app/node/graph.cpp b/app/node/graph.cpp index f652340fb..bdae43570 100644 --- a/app/node/graph.cpp +++ b/app/node/graph.cpp @@ -44,6 +44,19 @@ void NodeGraph::Clear() } } +int NodeGraph::GetNumberOfContextsNodeIsIn(Node *node, bool except_itself) const +{ + int count = 0; + + foreach (Node *ctx, node_children_) { + if (ctx->ContextContainsNode(node) && (!except_itself || ctx != node)) { + count++; + } + } + + return count; +} + void NodeGraph::childEvent(QChildEvent *event) { super::childEvent(event); diff --git a/app/node/graph.h b/app/node/graph.h index 21c8f023e..fdf923b56 100644 --- a/app/node/graph.h +++ b/app/node/graph.h @@ -63,6 +63,8 @@ public: return default_nodes_; } + int GetNumberOfContextsNodeIsIn(Node *node, bool except_itself = false) const; + signals: /** * @brief Signal emitted when a Node is added to the graph diff --git a/app/task/project/loadotio/loadotio.cpp b/app/task/project/loadotio/loadotio.cpp index bc1983c41..66d2fddf9 100644 --- a/app/task/project/loadotio/loadotio.cpp +++ b/app/task/project/loadotio/loadotio.cpp @@ -210,7 +210,7 @@ bool LoadOTIOTask::Run() block->setParent(sequence->parent()); // Position transition in its own context - sequence->parent()->SetNodePosition(block, block, QPointF(0, 0)); + block->SetNodePositionInContext(block, QPointF(0, 0)); } if (otio_block->schema_name() == "Gap") { @@ -218,7 +218,7 @@ bool LoadOTIOTask::Run() block->setParent(sequence->parent()); // Position transition in its own context - sequence->parent()->SetNodePosition(block, block, QPointF(0, 0)); + block->SetNodePositionInContext(block, QPointF(0, 0)); } // Update this after it's used but before any continue statements @@ -246,7 +246,7 @@ bool LoadOTIOTask::Run() QFileInfo info(probed_item->filename()); probed_item->SetLabel(info.fileName()); - FolderAddChild add(sequence_footage, probed_item, true); + FolderAddChild add(sequence_footage, probed_item); add.redo_now(); } @@ -254,10 +254,10 @@ bool LoadOTIOTask::Run() block->setParent(sequence->parent()); // Position clip in its own context - sequence->parent()->SetNodePosition(block, block, QPointF(0, 0)); + block->SetNodePositionInContext(block, QPointF(0, 0)); // Position footage in its context - sequence->parent()->SetNodePosition(probed_item, block, QPointF(-2, 0)); + block->SetNodePositionInContext(probed_item, QPointF(-2, 0)); if (track->type() == Track::kVideo) { @@ -266,14 +266,14 @@ bool LoadOTIOTask::Run() Node::ConnectEdge(probed_item, NodeInput(transform, TransformDistortNode::kTextureInput)); Node::ConnectEdge(transform, NodeInput(block, ClipBlock::kBufferIn)); - sequence->parent()->SetNodePosition(transform, block, QPointF(-1, 0)); + block->SetNodePositionInContext(transform, QPointF(-1, 0)); } else { VolumeNode* volume_node = new VolumeNode(); volume_node->setParent(sequence->parent()); Node::ConnectEdge(probed_item, NodeInput(volume_node, VolumeNode::kSamplesInput)); Node::ConnectEdge(volume_node, NodeInput(block, ClipBlock::kBufferIn)); - sequence->parent()->SetNodePosition(volume_node, block, QPointF(-1, 0)); + block->SetNodePositionInContext(volume_node, QPointF(-1, 0)); } } } diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 4c8385d9d..3da2c70f1 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -125,43 +125,7 @@ void NodeView::ClearGraph() void NodeView::DeleteSelected() { - MultiUndoCommand* command = new MultiUndoCommand(); - - { - // First remove any selected edges - QVector selected_edges = scene_.GetSelectedEdges(); - - if (!selected_edges.isEmpty()) { - Node::OutputConnections removed_connections(selected_edges.size()); - - for (int i=0; iadd_child(new NodeEdgeRemoveCommand(edge->output(), edge->input())); - removed_connections[i] = {edge->output(), edge->input()}; - } - } - } - - { - // Secondly remove any nodes - QVector selected_nodes = scene_.GetSelectedNodes(); - - // Ensure no nodes are "undeletable" - for (int i=0;iCanBeDeleted()) { - selected_nodes.removeAt(i); - i--; - } - } - - if (!selected_nodes.isEmpty()) { - for (Node* node : qAsConst(selected_nodes)) { - command->add_child(new NodeRemoveAndDisconnectCommand(node)); - } - } - } - - Core::instance()->undo_stack()->pushIfHasChildren(command); + scene_.DeleteSelected(); } void NodeView::SelectAll() @@ -428,6 +392,11 @@ void NodeView::mousePressEvent(QMouseEvent *event) } super::mousePressEvent(event); + + auto selected_items = scene_.GetSelectedItems(); + foreach (NodeViewItem *i, selected_items) { + dragging_nodes_.insert(i, i->GetNodePosition()); + } } void NodeView::mouseMoveEvent(QMouseEvent *event) @@ -658,6 +627,15 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) DetachItemsFromCursor(); } + for (auto it=dragging_nodes_.cbegin(); it!=dragging_nodes_.cend(); it++) { + NodeViewItem *i = it.key(); + QPointF current_pos = i->GetNodePosition(); + if (it.value() != current_pos) { + command->add_child(new NodeSetPositionCommand(i->GetNode(), i->GetContext(), current_pos)); + } + } + dragging_nodes_.clear(); + Core::instance()->undo_stack()->pushIfHasChildren(command); super::mouseReleaseEvent(event); diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 8616974f5..c9d7e0e43 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -219,6 +219,8 @@ private: QVector last_set_filter_nodes_; QMap context_offsets_; + QMap dragging_nodes_; + double scale_; bool create_edge_already_exists_; diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index 1b52624a6..564e6552a 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -50,9 +50,7 @@ void NodeViewContext::AddChild(Node *node) return; } - NodeViewItem *item = new NodeViewItem(this); - item->SetNode(node, context_); - item->SetNodePosition(context_->GetNodePositionInContext(node)); + NodeViewItem *item = new NodeViewItem(node, context_, this); item->SetFlowDirection(flow_dir_); connect(node, &Node::InputConnected, this, &NodeViewContext::ChildInputConnected); @@ -162,6 +160,36 @@ void NodeViewContext::SetCurvedEdges(bool e) } } +void NodeViewContext::DeleteSelected(NodeViewDeleteCommand *command) +{ + // Delete any selected edges + foreach (NodeViewEdge *edge, edges_) { + if (edge->isSelected()) { + command->AddEdge(edge->output(), edge->input()); + } + } + + // Delete any selected nodes + foreach (NodeViewItem *node, item_map_) { + if (node->isSelected()) { + command->AddNode(node->GetNode(), context_); + } + } +} + +QVector NodeViewContext::GetSelectedItems() const +{ + QVector items; + + for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) { + if (it.value()->isSelected()) { + items.append(it.value()); + } + } + + return items; +} + void NodeViewContext::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { // Set pen and brush diff --git a/app/widget/nodeview/nodeviewcontext.h b/app/widget/nodeview/nodeviewcontext.h index 4ef21f68f..8a84751de 100644 --- a/app/widget/nodeview/nodeviewcontext.h +++ b/app/widget/nodeview/nodeviewcontext.h @@ -7,6 +7,7 @@ #include "node/node.h" #include "nodeviewcommon.h" #include "nodeviewedge.h" +#include "nodeviewundo.h" namespace olive { @@ -22,6 +23,10 @@ public: void SetCurvedEdges(bool e); + void DeleteSelected(NodeViewDeleteCommand *command); + + QVector GetSelectedItems() const; + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; public slots: diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 1c73f8ddd..009ea45ac 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -39,10 +39,10 @@ namespace olive { -NodeViewItem::NodeViewItem(QGraphicsItem *parent) : +NodeViewItem::NodeViewItem(Node* n, Node *context, QGraphicsItem *parent) : QGraphicsRectItem(parent), - node_(nullptr), - context_(nullptr), + node_(n), + context_(context), expanded_(false), hide_titlebar_(false), highlighted_index_(-1), @@ -69,6 +69,24 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) : setRect(title_bar_rect_); output_connector_ = new NodeViewItemConnector(true, this); + + // Set up node + node_->Retranslate(); + + foreach (const QString& input, node_->inputs()) { + if (node_->IsInputConnectable(input) && !node_->IsInputHidden(input)) { + node_inputs_.append(input); + } + } + + UpdateInputConnectors(); + + connect(node_, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); + connect(node_, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); + + SetNodePosition(context_->GetNodePositionInContext(node_)); + + SetExpanded(node_->property("expanded").toBool()); } QPointF NodeViewItem::GetNodePosition() const @@ -208,37 +226,6 @@ int NodeViewItem::GetIndexAt(QPointF pt) const return -1; } -void NodeViewItem::SetNode(Node *n, Node *context) -{ - if (node_) { - disconnect(n, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); - disconnect(n, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); - } - - node_ = n; - context_ = context; - - node_inputs_.clear(); - input_connectors_.clear(); - - if (node_) { - node_->Retranslate(); - - foreach (const QString& input, node_->inputs()) { - if (node_->IsInputConnectable(input) && !node_->IsInputHidden(input)) { - node_inputs_.append(input); - } - } - - UpdateInputConnectors(); - - connect(n, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); - connect(n, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); - } - - update(); -} - void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) { if (node_inputs_.isEmpty() @@ -248,6 +235,7 @@ void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) expanded_ = e; hide_titlebar_ = hide_titlebar; + node_->setProperty("expanded", e); if (expanded_ && !node_inputs_.isEmpty()) { // Create new rect @@ -507,8 +495,8 @@ void NodeViewItem::SetLabelAsOutput(bool e) NodeViewEdge *NodeViewItem::GetEdgeFromInputConnector(NodeViewItemConnector *connector) { - ssize_t index = -1; - for (ssize_t i=0; iSetFlowDirection(direction_); } - - // Iterate over edge items setting direction - foreach (NodeViewEdge* edge, edges_) { - edge->SetFlowDirection(direction_); - } } void NodeViewScene::clear() @@ -63,9 +59,6 @@ void NodeViewScene::clear() delete it.value(); } item_map_.clear(); - - qDeleteAll(edges_); - edges_.clear(); } void NodeViewScene::SelectAll() @@ -86,22 +79,22 @@ void NodeViewScene::DeselectAll() } } +void NodeViewScene::DeleteSelected() +{ + NodeViewDeleteCommand* command = new NodeViewDeleteCommand(); + + foreach (NodeViewContext *ctx, context_map_) { + ctx->DeleteSelected(command); + } + + Core::instance()->undo_stack()->push(command); +} + NodeViewItem *NodeViewScene::NodeToUIObject(Node *n) { return item_map_.value(n); } -NodeViewEdge *NodeViewScene::EdgeToUIObject(Node *output, const NodeInput& input) -{ - foreach (NodeViewEdge* edge, edges_) { - if (edge->output() == output && edge->input() == input) { - return edge; - } - } - - return nullptr; -} - QVector NodeViewScene::GetSelectedNodes() const { QHash::const_iterator iterator; @@ -118,29 +111,13 @@ QVector NodeViewScene::GetSelectedNodes() const QVector NodeViewScene::GetSelectedItems() const { - QHash::const_iterator iterator; - QVector selected; + QVector items; - for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) { - if (iterator.value()->isSelected()) { - selected.append(iterator.value()); - } + foreach (NodeViewContext *ctx, context_map_) { + items.append(ctx->GetSelectedItems()); } - return selected; -} - -QVector NodeViewScene::GetSelectedEdges() const -{ - QVector edges; - - foreach (NodeViewEdge* e, edges_) { - if (e->isSelected()) { - edges.append(e); - } - } - - return edges; + return items; } NodeViewContext *NodeViewScene::AddContext(Node *node) diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index db49aca89..0ef1adfab 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -42,6 +42,8 @@ public: void SelectAll(); void DeselectAll(); + void DeleteSelected(); + /** * @brief Retrieve the graphical widget corresponding to a specific Node * @@ -54,22 +56,15 @@ public: * in this view/scene), this function returns nullptr. */ NodeViewItem* NodeToUIObject(Node* n); - NodeViewEdge *EdgeToUIObject(Node *output, const NodeInput &input); QVector GetSelectedNodes() const; QVector GetSelectedItems() const; - QVector GetSelectedEdges() const; const QHash& item_map() const { return item_map_; } - const QVector& edges() const - { - return edges_; - } - Qt::Orientation GetFlowOrientation() const; NodeViewCommon::FlowDirection GetFlowDirection() const; @@ -96,8 +91,6 @@ private: QHash item_map_; - QVector edges_; - NodeGraph* graph_; NodeViewCommon::FlowDirection direction_; diff --git a/app/widget/nodeview/nodeviewundo.cpp b/app/widget/nodeview/nodeviewundo.cpp index 5d7af0263..54106070f 100644 --- a/app/widget/nodeview/nodeviewundo.cpp +++ b/app/widget/nodeview/nodeviewundo.cpp @@ -193,4 +193,100 @@ void NodeOverrideColorCommand::undo() node_->SetOverrideColor(old_index_); } +NodeViewDeleteCommand::NodeViewDeleteCommand() +{ +} + +void NodeViewDeleteCommand::AddNode(Node *node, Node *context) +{ + foreach (const NodePair &pair, nodes_) { + if (pair.first == node && pair.second == context) { + return; + } + } + + nodes_.append(NodePair({node, context})); + + for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { + if (context->ContextContainsNode(it->second)) { + AddEdge(it->second, it->first); + } + } + + for (auto it=node->output_connections().cbegin(); it!=node->output_connections().cend(); it++) { + if (context->ContextContainsNode(it->second.node())) { + AddEdge(it->first, it->second); + } + } +} + +void NodeViewDeleteCommand::AddEdge(Node *output, const NodeInput &input) +{ + foreach (const Node::OutputConnection &edge, edges_) { + if (edge.first == output && edge.second == input) { + return; + } + } + + edges_.append({output, input}); +} + +Project *NodeViewDeleteCommand::GetRelevantProject() const +{ + if (!nodes_.isEmpty()) { + return nodes_.first().first->project(); + } + + if (!edges_.isEmpty()) { + return edges_.first().first->project(); + } + + return nullptr; +} + +void NodeViewDeleteCommand::redo() +{ + foreach (const Node::OutputConnection &edge, edges_) { + Node::DisconnectEdge(edge.first, edge.second); + } + + foreach (const NodePair &pair, nodes_) { + RemovedNode rn; + + rn.node = pair.first; + rn.context = pair.second; + rn.pos = rn.context->GetNodePositionInContext(rn.node); + + rn.context->RemoveNodeFromContext(rn.node); + + // If node is no longer in any contexts and is not connected to anything, remove it + if (rn.node->parent()->GetNumberOfContextsNodeIsIn(rn.node, true) == 0 + && rn.node->input_connections().empty() + && rn.node->output_connections().empty()) { + rn.removed_from_graph = rn.node->parent(); + rn.node->setParent(&memory_manager_); + } else { + rn.removed_from_graph = nullptr; + } + + removed_nodes_.append(rn); + } +} + +void NodeViewDeleteCommand::undo() +{ + for (auto rn=removed_nodes_.crbegin(); rn!=removed_nodes_.crend(); rn++) { + if (rn->removed_from_graph) { + rn->node->setParent(rn->removed_from_graph); + } + + rn->context->SetNodePositionInContext(rn->node, rn->pos); + } + removed_nodes_.clear(); + + for (auto edge=edges_.crbegin(); edge!=edges_.crend(); edge++) { + Node::ConnectEdge(edge->first, edge->second); + } +} + } diff --git a/app/widget/nodeview/nodeviewundo.h b/app/widget/nodeview/nodeviewundo.h index 7ed3e434e..567198b5b 100644 --- a/app/widget/nodeview/nodeviewundo.h +++ b/app/widget/nodeview/nodeviewundo.h @@ -363,6 +363,42 @@ private: }; +class NodeViewDeleteCommand : public UndoCommand +{ +public: + NodeViewDeleteCommand(); + + void AddNode(Node *node, Node *context); + + void AddEdge(Node *output, const NodeInput &input); + + virtual Project * GetRelevantProject() const override; + +protected: + virtual void redo() override; + + virtual void undo() override; + +private: + using NodePair = QPair; + + QVector nodes_; + + QVector edges_; + + struct RemovedNode { + Node *node; + Node *context; + QPointF pos; + NodeGraph *removed_from_graph; + }; + + QVector removed_nodes_; + + QObject memory_manager_; + +}; + } #endif // NODEVIEWUNDO_H From 40783bcc6d55ff12349a05bb5440e727b6bb633e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 19 Nov 2021 15:47:31 -0800 Subject: [PATCH 08/34] reimplemented adding/removing/connecting/disconnecting --- app/widget/nodeview/nodeview.cpp | 322 ++++++++++++------------ app/widget/nodeview/nodeview.h | 47 +--- app/widget/nodeview/nodeviewcontext.cpp | 20 +- app/widget/nodeview/nodeviewcontext.h | 7 + app/widget/nodeview/nodeviewedge.cpp | 3 +- app/widget/nodeview/nodeviewitem.cpp | 15 +- app/widget/nodeview/nodeviewitem.h | 6 + app/widget/nodeview/nodeviewscene.h | 5 + 8 files changed, 233 insertions(+), 192 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 3da2c70f1..b27b0b862 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "core.h" #include "nodeviewundo.h" @@ -44,7 +45,8 @@ NodeView::NodeView(QWidget *parent) : HandMovableView(parent), drop_edge_(nullptr), create_edge_(nullptr), - create_edge_dst_(nullptr), + create_edge_output_item_(nullptr), + create_edge_input_item_(nullptr), create_edge_dst_temp_expanded_(false), paste_command_(nullptr), scale_(1.0), @@ -87,14 +89,14 @@ void NodeView::SetContexts(const QVector &nodes) // Remove contexts that are no longer in the list foreach (Node *n, contexts_) { if (!nodes.contains(n)) { - scene_.RemoveContext(n); + RemoveContext(n); } } // Add contexts that are now in the list foreach (Node *n, nodes) { if (!contexts_.contains(n)) { - scene_.AddContext(n); + AddContext(n); } } @@ -346,29 +348,56 @@ void NodeView::keyPressEvent(QKeyEvent *event) void NodeView::mousePressEvent(QMouseEvent *event) { + // Handle mouse press event if (HandPress(event)) return; + // Get the item that the user clicked on, if any QGraphicsItem* item = itemAt(event->pos()); if (event->button() == Qt::LeftButton) { // Determine if user clicked on a connector - if (NodeViewItemConnector *connector = dynamic_cast(item)) { - NodeViewItem *attached_item = static_cast(connector->parentItem()); - if (connector->IsOutput()) { - CreateNewEdge(attached_item, event->pos()); - return; - } else { - NodeViewEdge *edge_item = attached_item->GetEdgeFromInputConnector(connector); - if (edge_item) { - create_edge_src_ = edge_item->from_item(); - create_edge_ = edge_item; + NodeViewItemConnector *connector = dynamic_cast(item); + + // If the user clicked on a connector OR the user is holding Ctrl + if (connector || (event->modifiers() & Qt::ControlModifier)) { + // Get the relevant item, either the one attached to the connector or the item if Ctrl+Clicked + NodeViewItem *attached_item = connector ? static_cast(connector->parentItem()) : dynamic_cast(item); + + if (attached_item) { + if (connector && !connector->IsOutput() && (create_edge_ = attached_item->GetEdgeFromInputConnector(connector))) { + // Since inputs can only have one edge connected, we grab the existing edge, if one exists + create_edge_output_item_ = create_edge_->from_item(); create_edge_already_exists_ = true; + create_edge_from_output_ = true; + } else { + // Create a new edge from this output + create_edge_ = new NodeViewEdge(); + create_edge_->SetCurved(scene_.GetEdgesAreCurved()); + create_edge_->SetFlowDirection(scene_.GetFlowDirection()); + + // Set source and declare that we created this edge + if ((create_edge_from_output_ = (!connector || connector->IsOutput()))) { + // Edge is being created from output + create_edge_output_item_ = attached_item; + } else { + // Edge is being created from input + create_edge_input_item_ = attached_item; + create_edge_input_ = attached_item->GetInputFromInputConnector(connector); + } + create_edge_already_exists_ = false; + + // Add edge to scene + scene_.addItem(create_edge_); + + // Position edge to mouse cursor + PositionNewEdge(event->pos()); } return; } } } + // Handle selections with the right mouse button if (event->button() == Qt::RightButton) { if (!item || !item->isSelected()) { // Qt doesn't do this by default for some reason @@ -383,19 +412,16 @@ void NodeView::mousePressEvent(QMouseEvent *event) } } - if (event->modifiers() & Qt::ControlModifier) { - NodeViewItem* node_item = dynamic_cast(item); - if (node_item) { - CreateNewEdge(node_item, event->pos()); - return; - } - } - + // Default QGraphicsView functionality (selecting, dragging, etc.) super::mousePressEvent(event); + // For any selected item, store its position in case the user is dragging it somewhere else auto selected_items = scene_.GetSelectedItems(); foreach (NodeViewItem *i, selected_items) { - dragging_nodes_.insert(i, i->GetNodePosition()); + // Ignore items attached to the cursor + if (!IsItemAttachedToCursor(i)) { + dragging_nodes_.insert(i, i->GetNodePosition()); + } } } @@ -487,23 +513,17 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) if (HandRelease(event)) return; if (create_edge_) { - // We are creating a new edge or moving an existing one + // Check if the edge was reconnected to the same place as before MultiUndoCommand* command = new MultiUndoCommand(); - Node::OutputConnections removed_edges; - Node::OutputConnection added_edge; - bool reconnected_to_itself = false; if (create_edge_already_exists_) { - if (create_edge_dst_input_ == create_edge_->input()) { + if (create_edge_output_item_ == create_edge_->from_item() && create_edge_->input() == create_edge_input_) { reconnected_to_itself = true; } else { // We are moving (or removing) an existing edge command->add_child(new NodeEdgeRemoveCommand(create_edge_->output(), create_edge_->input())); - - // Update contexts for edge removal - removed_edges.push_back({create_edge_->output(), create_edge_->input()}); } } else { // We're creating a new edge, which means this UI object is only temporary @@ -512,36 +532,35 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) create_edge_ = nullptr; - if (create_edge_dst_) { - // Clear highlight - create_edge_dst_->SetHighlightedIndex(-1); + if (create_edge_output_item_ && create_edge_input_item_) { + // Clear highlight if we set one + create_edge_input_item_->SetHighlightedIndex(-1); // Collapse if we expanded it if (create_edge_dst_temp_expanded_) { - create_edge_dst_->SetExpanded(false); - create_edge_dst_->setZValue(0); + create_edge_input_item_->SetExpanded(false); + create_edge_input_item_->setZValue(0); } - NodeInput &creating_input = create_edge_dst_input_; + NodeInput &creating_input = create_edge_input_; if (creating_input.IsValid()) { // Make connection if (!reconnected_to_itself) { - Node *creating_output = create_edge_src_->GetNode(); + Node *creating_output = create_edge_output_item_->GetNode(); if (creating_input.IsConnected()) { Node::OutputConnection existing_edge_to_remove = {creating_input.GetConnectedOutput(), creating_input}; command->add_child(new NodeEdgeRemoveCommand(existing_edge_to_remove.first, existing_edge_to_remove.second)); - removed_edges.push_back(existing_edge_to_remove); } command->add_child(new NodeEdgeAddCommand(creating_output, creating_input)); - added_edge = {creating_output, creating_input}; } creating_input.Reset(); } - create_edge_dst_ = nullptr; + create_edge_output_item_ = nullptr; + create_edge_input_item_ = nullptr; } Core::instance()->undo_stack()->pushIfHasChildren(command); @@ -551,6 +570,21 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) MultiUndoCommand* command = new MultiUndoCommand(); if (!attached_items_.isEmpty()) { + Node *context = nullptr; + + QList items_at_cursor = this->items(event->pos()); + foreach (QGraphicsItem *i, items_at_cursor) { + if (NodeViewContext *context_item = dynamic_cast(i)) { + context = context_item->GetContext(); + break; + } + } + + if (!context) { + QToolTip::showText(QCursor::pos(), tr("Nodes must be placed inside a context.")); + return; + } + if (paste_command_) { // We've already "done" this command, but MultiUndoCommand prevents "redoing" twice, so we // add it to this command (which may have extra commands added too) so that it all gets undone @@ -558,9 +592,26 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) command->add_child(paste_command_); paste_command_ = nullptr; } - } - if (!attached_items_.isEmpty()) { + { + MultiUndoCommand *add_command = new MultiUndoCommand(); + + foreach (const AttachedItem &ai, attached_items_) { + // Add node to the same graph that the context is in + add_command->add_child(new NodeAddCommand(context->parent(), ai.item->GetNode())); + + // Add node to the context + add_command->add_child(new NodeSetPositionCommand(ai.item->GetNode(), context, scene_.context_map().value(context)->MapScenePosToNodePosInContext(ai.item->pos()))); + } + + if (add_command->child_count()) { + add_command->redo_now(); + command->add_child(add_command); + } else { + delete add_command; + } + } + { // Dropped attached item onto an edge, connect it between them MultiUndoCommand *drop_edge_command = new MultiUndoCommand(); @@ -586,44 +637,6 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } } - { - // Remove from context any nodes that don't specifically output to said context - MultiUndoCommand *remove_pos_command = new MultiUndoCommand(); - - for (const AttachedItem &attached : qAsConst(attached_items_)) { - MultiUndoCommand *remove_pos_subcommand = new MultiUndoCommand(); - Node *attached_node = scene_.item_map().key(attached.item); - - bool removed = false; - QVector relevant_contexts; - for (Node *context : qAsConst(contexts_)) { - if (attached_node->OutputsTo(context, true)) { - relevant_contexts.append(context); - } else { - remove_pos_subcommand->add_child(new NodeRemovePositionFromContextCommand(attached_node, context)); - removed = true; - } - } - - if (removed && !relevant_contexts.isEmpty()) { - for (Node *relevant : qAsConst(relevant_contexts)) { - remove_pos_subcommand->add_child(new NodeSetPositionCommand(attached_node, relevant, GetEstimatedPositionForContext(attached.item, relevant))); - } - - remove_pos_command->add_child(remove_pos_subcommand); - } else { - delete remove_pos_subcommand; - } - } - - if (remove_pos_command->child_count()) { - remove_pos_command->redo_now(); - command->add_child(remove_pos_command); - } else { - delete remove_pos_command; - } - } - DetachItemsFromCursor(); } @@ -777,20 +790,14 @@ void NodeView::ShowContextMenu(const QPoint &pos) void NodeView::CreateNodeSlot(QAction *action) { - qDebug() << "STUB!"; - /*Node* new_node = NodeFactory::CreateFromMenuAction(action); + Node* new_node = NodeFactory::CreateFromMenuAction(action); if (new_node) { - paste_command_ = new MultiUndoCommand(); - paste_command_->add_child(new NodeAddCommand(graph_, new_node)); - for (Node *context : qAsConst(contexts_)) { - paste_command_->add_child(new NodeSetPositionCommand(new_node, context, QPointF(0, 0), false)); - } - paste_command_->add_child(new NodeViewAttachNodesToCursor(this, {new_node})); - paste_command_->redo_now(); - - this->setFocus(); - }*/ + NodeViewItem *new_item = new NodeViewItem(new_node, nullptr); + new_item->SetFlowDirection(scene_.GetFlowDirection()); + scene_.addItem(new_item); + AttachItemsToCursor({new_item}); + } } void NodeView::ContextMenuSetDirection(QAction *action) @@ -859,6 +866,15 @@ void NodeView::MoveToScenePoint(const QPointF &pos) centerOn(pos); } +void NodeView::NodeRemovedFromGraph() +{ + Node *context = static_cast(sender()); + + RemoveContext(context); + + contexts_.removeOne(context); +} + void NodeView::AttachNodesToCursor(const QVector &nodes) { QVector items(nodes.size()); @@ -885,6 +901,10 @@ void NodeView::AttachItemsToCursor(const QVector& items) void NodeView::DetachItemsFromCursor() { + foreach (const AttachedItem &ai, attached_items_) { + delete ai.item; + } + attached_items_.clear(); } @@ -1037,20 +1057,6 @@ Menu *NodeView::CreateAddMenu(Menu *parent) return add_menu; } -void NodeView::CreateNewEdge(NodeViewItem *output_item, const QPoint &mouse_pos) -{ - create_edge_ = new NodeViewEdge(); - create_edge_src_ = output_item; - create_edge_already_exists_ = false; - - create_edge_->SetCurved(scene_.GetEdgesAreCurved()); - create_edge_->SetFlowDirection(scene_.GetFlowDirection()); - - scene_.addItem(create_edge_); - - PositionNewEdge(mouse_pos); -} - void NodeView::PositionNewEdge(const QPoint &pos) { // Determine scene coordinate @@ -1059,62 +1065,66 @@ void NodeView::PositionNewEdge(const QPoint &pos) // Find if the cursor is currently inside an item NodeViewItem* item_at_cursor = dynamic_cast(itemAt(pos)); + NodeViewItem *source_item = create_edge_from_output_ ? create_edge_output_item_ : create_edge_input_item_; + NodeViewItem *&opposing_item = create_edge_from_output_ ? create_edge_input_item_ : create_edge_output_item_; + // Filter out connecting to self - if (item_at_cursor == create_edge_src_) { + if (item_at_cursor == source_item) { item_at_cursor = nullptr; } // Filter out connecting to a node that connects to us - if (item_at_cursor && item_at_cursor->GetNode()->OutputsTo(create_edge_src_->GetNode(), true)) { + if (item_at_cursor + && ((create_edge_from_output_ && item_at_cursor->GetNode()->OutputsTo(source_item->GetNode(), true)) + || (!create_edge_from_output_ && item_at_cursor->GetNode()->InputsFrom(source_item->GetNode(), true)))) { item_at_cursor = nullptr; } // If the item has changed - if (item_at_cursor != create_edge_dst_) { + if (item_at_cursor != opposing_item) { // If we had a destination active, disconnect from it since the item has changed - if (create_edge_dst_) { - create_edge_dst_->SetHighlightedIndex(-1); + if (opposing_item) { + opposing_item->SetHighlightedIndex(-1); if (create_edge_dst_temp_expanded_) { // We expanded this item, so we can un-expand it - create_edge_dst_->SetExpanded(false); - create_edge_dst_->setZValue(0); + opposing_item->SetExpanded(false); + opposing_item->setZValue(0); } } // Set destination - create_edge_dst_ = item_at_cursor; + opposing_item = item_at_cursor; // If our destination is an item, ensure it's expanded - if (create_edge_dst_) { - if ((create_edge_dst_temp_expanded_ = (!create_edge_dst_->IsExpanded()))) { - create_edge_dst_->SetExpanded(true, true); - create_edge_dst_->setZValue(100); // Ensure item is in front + if (opposing_item) { + if (create_edge_from_output_ && (create_edge_dst_temp_expanded_ = (!create_edge_input_item_->IsExpanded()))) { + create_edge_input_item_->SetExpanded(true, true); + create_edge_input_item_->setZValue(100); // Ensure item is in front } } } // If we have a destination, highlight the appropriate input - int highlight_index = -1; - if (create_edge_dst_) { - highlight_index = create_edge_dst_->GetIndexAt(scene_pt); - create_edge_dst_->SetHighlightedIndex(highlight_index); + if (create_edge_from_output_) { + int highlight_index = -1; + if (create_edge_input_item_) { + highlight_index = create_edge_input_item_->GetIndexAt(scene_pt); + create_edge_input_item_->SetHighlightedIndex(highlight_index); + } + + if (highlight_index >= 0) { + create_edge_input_ = create_edge_input_item_->GetInputAtIndex(highlight_index); + } else { + create_edge_input_.Reset(); + } } - if (highlight_index >= 0) { - create_edge_dst_input_ = create_edge_dst_->GetInputAtIndex(highlight_index); - create_edge_->SetPoints(create_edge_src_->GetOutputPoint(), - create_edge_dst_->GetInputPoint(create_edge_dst_input_.input(), create_edge_dst_input_.element()), - true); - } else { - create_edge_dst_input_.Reset(); - create_edge_->SetPoints(create_edge_src_->GetOutputPoint(), - scene_pt, - false); - } + QPointF output_point = create_edge_output_item_ ? create_edge_output_item_->GetOutputPoint() : scene_pt; + QPointF input_point = create_edge_input_.IsValid() ? create_edge_input_item_->GetInputPoint(create_edge_input_.input(), create_edge_input_.element()) : scene_pt; - // Set connected to whether we have a valid input destination - create_edge_->SetConnected(create_edge_dst_input_.IsValid()); + create_edge_->SetPoints(output_point, input_point, create_edge_input_item_ && create_edge_input_item_->IsExpanded()); + create_edge_->SetConnected(create_edge_output_item_ && create_edge_input_.IsValid()); } void NodeView::GroupNodes() @@ -1177,6 +1187,29 @@ void NodeView::PasteNodesInternal(const QVector &duplicate_nodes) */ } +void NodeView::AddContext(Node *n) +{ + scene_.AddContext(n); + connect(n, &Node::RemovedFromGraph, this, &NodeView::NodeRemovedFromGraph); +} + +void NodeView::RemoveContext(Node *n) +{ + scene_.RemoveContext(n); + disconnect(n, &Node::RemovedFromGraph, this, &NodeView::NodeRemovedFromGraph); +} + +bool NodeView::IsItemAttachedToCursor(NodeViewItem *item) const +{ + foreach (const AttachedItem &ai, attached_items_) { + if (ai.item == item) { + return true; + } + } + + return false; +} + NodeView::NodeViewAttachNodesToCursor::NodeViewAttachNodesToCursor(NodeView *view, const QVector &nodes) : view_(view), nodes_(nodes) @@ -1198,23 +1231,4 @@ Project *NodeView::NodeViewAttachNodesToCursor::GetRelevantProject() const return nullptr; } -void NodeView::NodeViewItemPreventRemovingCommand::redo() -{ - NodeViewItem *item = view_->scene_.item_map().value(node_); - - if (item) { - old_prevent_removing_ = item->GetPreventRemoving(); - item->SetPreventRemoving(new_prevent_removing_); - } -} - -void NodeView::NodeViewItemPreventRemovingCommand::undo() -{ - NodeViewItem *item = view_->scene_.item_map().value(node_); - - if (item) { - item->SetPreventRemoving(old_prevent_removing_); - } -} - } diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index c9d7e0e43..c2259a780 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -139,12 +139,16 @@ private: Menu *CreateAddMenu(Menu *parent); - void CreateNewEdge(NodeViewItem *output_item, const QPoint &mouse_pos); - void PositionNewEdge(const QPoint &pos); void PasteNodesInternal(const QVector &duplicate_nodes = QVector()); + void AddContext(Node *n); + + void RemoveContext(Node *n); + + bool IsItemAttachedToCursor(NodeViewItem *item) const; + class NodeViewAttachNodesToCursor : public UndoCommand { public: @@ -171,43 +175,18 @@ private: QPointF original_pos; }; - class NodeViewItemPreventRemovingCommand : public UndoCommand - { - public: - NodeViewItemPreventRemovingCommand(NodeView *view, Node *node, bool prevent_removing) : - view_(view), - node_(node), - new_prevent_removing_(prevent_removing) - {} - - virtual Project * GetRelevantProject() const override - { - return node_->project(); - } - - protected: - virtual void redo() override; - - virtual void undo() override; - - private: - NodeView *view_; - Node *node_; - bool new_prevent_removing_; - bool old_prevent_removing_; - - }; - QList attached_items_; NodeViewEdge* drop_edge_; NodeInput drop_input_; NodeViewEdge* create_edge_; - NodeViewItem* create_edge_src_; - NodeViewItem* create_edge_dst_; - NodeInput create_edge_dst_input_; + NodeViewItem* create_edge_output_item_; + NodeViewItem* create_edge_input_item_; + NodeInput create_edge_input_; bool create_edge_dst_temp_expanded_; + bool create_edge_already_exists_; + bool create_edge_from_output_; NodeViewScene scene_; @@ -223,8 +202,6 @@ private: double scale_; - bool create_edge_already_exists_; - bool first_show_; static const double kMinimumScale; @@ -263,6 +240,8 @@ private slots: void MoveToScenePoint(const QPointF &pos); + void NodeRemovedFromGraph(); + void GroupNodes(); void UngroupNodes(); diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index 564e6552a..947762ee1 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -91,7 +91,16 @@ void NodeViewContext::RemoveChild(Node *node) disconnect(node, &Node::InputConnected, this, &NodeViewContext::ChildInputConnected); disconnect(node, &Node::InputDisconnected, this, &NodeViewContext::ChildInputDisconnected); - delete item_map_.take(node); + NodeViewItem *item = item_map_.take(node); + + // Delete edges first because the edge destructor will try to reference item (maybe that should + // be changed...) + QVector edges_to_remove = item->edges(); + foreach (NodeViewEdge *edge, edges_to_remove) { + ChildInputDisconnected(edge->output(), edge->input()); + } + + delete item; } void NodeViewContext::ChildInputConnected(Node *output, const NodeInput &input) @@ -190,6 +199,15 @@ QVector NodeViewContext::GetSelectedItems() const return items; } +QPointF NodeViewContext::MapScenePosToNodePosInContext(const QPointF &pos) const +{ + for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) { + QPointF pos_inside_parent = it.value()->mapToParent(it.value()->mapFromScene(pos)); + return NodeViewItem::ScreenToNodePoint(pos_inside_parent, flow_dir_); + } + return QPointF(0, 0); +} + void NodeViewContext::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { // Set pen and brush diff --git a/app/widget/nodeview/nodeviewcontext.h b/app/widget/nodeview/nodeviewcontext.h index 8a84751de..62be15974 100644 --- a/app/widget/nodeview/nodeviewcontext.h +++ b/app/widget/nodeview/nodeviewcontext.h @@ -17,6 +17,11 @@ class NodeViewContext : public QObject, public QGraphicsRectItem public: NodeViewContext(Node *context, QGraphicsItem *item = nullptr); + Node *GetContext() const + { + return context_; + } + void UpdateRect(); void SetFlowDirection(NodeViewCommon::FlowDirection dir); @@ -27,6 +32,8 @@ public: QVector GetSelectedItems() const; + QPointF MapScenePosToNodePosInContext(const QPointF &pos) const; + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; public slots: diff --git a/app/widget/nodeview/nodeviewedge.cpp b/app/widget/nodeview/nodeviewedge.cpp index caf629007..794c98c52 100644 --- a/app/widget/nodeview/nodeviewedge.cpp +++ b/app/widget/nodeview/nodeviewedge.cpp @@ -162,7 +162,6 @@ void NodeViewEdge::UpdateCurve() { const QPointF &start = cached_start_; const QPointF &end = cached_end_; - const bool input_is_expanded = cached_input_is_expanded_; QPainterPath path; path.moveTo(start); @@ -182,7 +181,7 @@ void NodeViewEdge::UpdateCurve() cp1 = QPointF(start.x(), half_y); } - if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal || input_is_expanded) { + if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal || cached_input_is_expanded_) { cp2 = QPointF(half_x, end.y()); } else { cp2 = QPointF(end.x(), half_y); diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 009ea45ac..57f67ddaf 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -84,7 +84,9 @@ NodeViewItem::NodeViewItem(Node* n, Node *context, QGraphicsItem *parent) : connect(node_, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); connect(node_, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); - SetNodePosition(context_->GetNodePositionInContext(node_)); + if (context_) { + SetNodePosition(context_->GetNodePositionInContext(node_)); + } SetExpanded(node_->property("expanded").toBool()); } @@ -493,6 +495,17 @@ void NodeViewItem::SetLabelAsOutput(bool e) update(); } +NodeInput NodeViewItem::GetInputFromInputConnector(NodeViewItemConnector *connector) +{ + for (int i=0; i &edges() const + { + return edges_; + } + /** * @brief Set expanded state */ @@ -129,6 +134,7 @@ public: void SetLabelAsOutput(bool e); + NodeInput GetInputFromInputConnector(NodeViewItemConnector *connector); NodeViewEdge *GetEdgeFromInputConnector(NodeViewItemConnector *connector); protected: diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index 0ef1adfab..b9949d59c 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -60,6 +60,11 @@ public: QVector GetSelectedNodes() const; QVector GetSelectedItems() const; + const QHash &context_map() const + { + return context_map_; + } + const QHash& item_map() const { return item_map_; From a391e6325268f46e629a2526a6b286f50d38f149 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 19 Nov 2021 16:46:39 -0800 Subject: [PATCH 09/34] cleaned up some no longer used functions --- app/widget/nodeview/nodeview.cpp | 160 +++++++----------------- app/widget/nodeview/nodeview.h | 25 +--- app/widget/nodeview/nodeviewcontext.cpp | 7 ++ app/widget/nodeview/nodeviewcontext.h | 2 + app/widget/nodeview/nodeviewitem.cpp | 1 - app/widget/nodeview/nodeviewitem.h | 12 -- app/widget/nodeview/nodeviewscene.cpp | 64 +--------- app/widget/nodeview/nodeviewscene.h | 31 +---- 8 files changed, 65 insertions(+), 237 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index b27b0b862..5cb1d767c 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -140,20 +140,7 @@ void NodeView::SelectAll() ConnectSelectionChangedSignal(); - // Determine which nodes aren't selected and add them to a separate vector - QVector new_selection; - for (auto it=scene_.item_map().cbegin(); it!=scene_.item_map().cend(); it++) { - Node *n = it.key(); - if (!selected_nodes_.contains(n)) { - new_selection.append(n); - } - } - - // Add this vector to our total selection vector - selected_nodes_.append(new_selection); - - // Signal new nodes - emit NodesSelected(new_selection); + UpdateSelectionCache(); } void NodeView::DeselectAll() @@ -175,7 +162,7 @@ void NodeView::DeselectAll() selected_nodes_.clear(); } -void NodeView::Select(QVector nodes, bool center_view_on_item) +void NodeView::Select(const QVector &nodes, bool center_view_on_item) { // Optimization: rather than respond to every single item being selected, ignore the signal and // then handle them all at the end. @@ -186,54 +173,21 @@ void NodeView::Select(QVector nodes, bool center_view_on_item) scene_.DeselectAll(); - // Remove any duplicates - QVector processed; - - NodeViewItem *first_item = nullptr; - - for (Node* n : qAsConst(nodes)) { - if (processed.contains(n)) { - continue; - } - - processed.append(n); - - NodeViewItem* item = scene_.NodeToUIObject(n); - - if (item) { - item->setSelected(true); - - if (!first_item) { - first_item = item; - } - - if (deselections.contains(n)) { - deselections.removeOne(n); - } else { - new_selections.append(n); - } - } + foreach (NodeViewContext *context, scene_.context_map()) { + context->Select(nodes); } + /* // Center on something + Node *first_item = nodes.isEmpty() ? nullptr : nodes.first(); if (center_view_on_item && first_item) { centerOn(first_item); } + */ ConnectSelectionChangedSignal(); - // Emit deselect signal for any nodes that weren't in the list - if (!deselections.isEmpty()) { - emit NodesDeselected(deselections); - } - - // Emit select signal for any nodes that weren't in the list - if (!new_selections.isEmpty()) { - emit NodesSelected(new_selections); - } - - // Update selected list to the list we received - selected_nodes_ = nodes; + UpdateSelectionCache(); } void NodeView::CopySelected(bool cut) @@ -256,7 +210,7 @@ void NodeView::Paste() void NodeView::Duplicate() { - PasteNodesInternal(scene_.GetSelectedNodes()); + PasteNodesInternal(selected_nodes_); } void NodeView::SetColorLabel(int index) @@ -292,7 +246,7 @@ void NodeView::keyPressEvent(QKeyEvent *event) for (Node *n : qAsConst(selected_nodes_)) { for (Node *context : qAsConst(contexts_)) { if (context->ContextContainsNode(n)) { - QPointF old_pos = context->GetNodePositionInContext(n); + Node::Position old_pos = context->GetNodePositionInContext(n); // Determine one pixel in scene units double movement_amt = 1.0 / scale_; @@ -420,7 +374,7 @@ void NodeView::mousePressEvent(QMouseEvent *event) foreach (NodeViewItem *i, selected_items) { // Ignore items attached to the cursor if (!IsItemAttachedToCursor(i)) { - dragging_nodes_.insert(i, i->GetNodePosition()); + dragging_items_.insert(i, i->GetNodePosition()); } } } @@ -554,6 +508,11 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } command->add_child(new NodeEdgeAddCommand(creating_output, creating_input)); + + // If the output is not in the input's context, add it now + if (!create_edge_input_item_->GetContext()->ContextContainsNode(creating_output)) { + command->add_child(new NodeSetPositionCommand(creating_output, create_edge_input_item_->GetContext(), scene_.context_map().value(create_edge_input_item_->GetContext())->MapScenePosToNodePosInContext(create_edge_output_item_->scenePos()))); + } } creating_input.Reset(); @@ -640,14 +599,14 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) DetachItemsFromCursor(); } - for (auto it=dragging_nodes_.cbegin(); it!=dragging_nodes_.cend(); it++) { + for (auto it=dragging_items_.cbegin(); it!=dragging_items_.cend(); it++) { NodeViewItem *i = it.key(); QPointF current_pos = i->GetNodePosition(); if (it.value() != current_pos) { command->add_child(new NodeSetPositionCommand(i->GetNode(), i->GetContext(), current_pos)); } } - dragging_nodes_.clear(); + dragging_items_.clear(); Core::instance()->undo_stack()->pushIfHasChildren(command); @@ -663,37 +622,42 @@ void NodeView::resizeEvent(QResizeEvent *event) void NodeView::UpdateSelectionCache() { - QVector current_selection = scene_.GetSelectedNodes(); + QVector current_selection = scene_.GetSelectedItems(); QVector selected; QVector deselected; // Determine which nodes are newly selected - if (selected_nodes_.isEmpty()) { - // All nodes in the current selection have just been selected - selected = current_selection; - } else { - for (Node* n : qAsConst(current_selection)) { - if (!selected_nodes_.contains(n)) { - selected.append(n); - } + foreach (NodeViewItem* i, current_selection) { + Node *n = i->GetNode(); + if (!selected_nodes_.contains(n)) { + selected.append(n); + selected_nodes_.append(n); } } // Determine which nodes are newly deselected if (current_selection.isEmpty()) { - // All nodes that were selected have been deselected + // All nodes that were selected have been deselected, so we'll just set them all to `deselected` deselected = selected_nodes_; } else { - for (Node* n : qAsConst(selected_nodes_)) { - if (!current_selection.contains(n)) { + foreach (Node* n, selected_nodes_) { + bool still_selected = false; + + foreach (NodeViewItem *i, current_selection) { + if (i->GetNode() == n) { + still_selected = true; + break; + } + } + + if (still_selected) { deselected.append(n); + selected_nodes_.removeOne(n); } } } - selected_nodes_ = current_selection; - if (!selected.isEmpty()) { emit NodesSelected(selected); } @@ -722,7 +686,7 @@ void NodeView::ShowContextMenu(const QPoint &pos) // Label node action QAction* label_action = m.addAction(tr("Label")); connect(label_action, &QAction::triggered, this, [this](){ - Core::instance()->LabelNodes(scene_.GetSelectedNodes()); + Core::instance()->LabelNodes(selected_nodes_); }); // Grouping @@ -807,11 +771,12 @@ void NodeView::ContextMenuSetDirection(QAction *action) void NodeView::OpenSelectedNodeInViewer() { - QVector selected = scene_.GetSelectedNodes(); - ViewerOutput* viewer = selected.isEmpty() ? nullptr : dynamic_cast(selected.first()); - - if (viewer) { - Core::instance()->OpenNodeInViewer(viewer); + // Find first viewer in list of selected nodes and open it + foreach (Node *n, selected_nodes_) { + if (ViewerOutput* viewer = dynamic_cast(n)) { + Core::instance()->OpenNodeInViewer(viewer); + break; + } } } @@ -875,17 +840,6 @@ void NodeView::NodeRemovedFromGraph() contexts_.removeOne(context); } -void NodeView::AttachNodesToCursor(const QVector &nodes) -{ - QVector items(nodes.size()); - - for (int i=0; i& items) { DetachItemsFromCursor(); @@ -974,7 +928,8 @@ bool NodeView::eventFilter(QObject *object, QEvent *event) void NodeView::CopyNodesToClipboardInternal(QXmlStreamWriter *writer, const QVector &nodes, void *userdata) { - writer->writeStartElement(QStringLiteral("pos")); + qDebug() << "STUB!"; + /*writer->writeStartElement(QStringLiteral("pos")); for (Node *n : nodes) { NodeViewItem *item = scene_.item_map().value(n); @@ -987,7 +942,7 @@ void NodeView::CopyNodesToClipboardInternal(QXmlStreamWriter *writer, const QVec writer->writeEndElement(); // node } - writer->writeEndElement(); // pos + writer->writeEndElement(); // pos*/ } void NodeView::PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data, void *userdata) @@ -1210,25 +1165,4 @@ bool NodeView::IsItemAttachedToCursor(NodeViewItem *item) const return false; } -NodeView::NodeViewAttachNodesToCursor::NodeViewAttachNodesToCursor(NodeView *view, const QVector &nodes) : - view_(view), - nodes_(nodes) -{ -} - -void NodeView::NodeViewAttachNodesToCursor::redo() -{ - view_->AttachNodesToCursor(nodes_); -} - -void NodeView::NodeViewAttachNodesToCursor::undo() -{ - view_->DetachItemsFromCursor(); -} - -Project *NodeView::NodeViewAttachNodesToCursor::GetRelevantProject() const -{ - return nullptr; -} - } diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index c2259a780..fd5e2c0c9 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -63,7 +63,7 @@ public: void SelectAll(); void DeselectAll(); - void Select(QVector nodes, bool center_view_on_item); + void Select(const QVector &nodes, bool center_view_on_item); void CopySelected(bool cut); void Paste(); @@ -120,8 +120,6 @@ protected: virtual void PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data, void* userdata) override; private: - void AttachNodesToCursor(const QVector &nodes); - void AttachItemsToCursor(const QVector &items); void DetachItemsFromCursor(); @@ -149,25 +147,6 @@ private: bool IsItemAttachedToCursor(NodeViewItem *item) const; - class NodeViewAttachNodesToCursor : public UndoCommand - { - public: - NodeViewAttachNodesToCursor(NodeView* view, const QVector& nodes); - - virtual Project * GetRelevantProject() const override; - - protected: - virtual void redo() override; - - virtual void undo() override; - - private: - NodeView* view_; - - QVector nodes_; - - }; - NodeViewMiniMap *minimap_; struct AttachedItem { @@ -198,7 +177,7 @@ private: QVector last_set_filter_nodes_; QMap context_offsets_; - QMap dragging_nodes_; + QMap dragging_items_; double scale_; diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index 947762ee1..b608ee58b 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -186,6 +186,13 @@ void NodeViewContext::DeleteSelected(NodeViewDeleteCommand *command) } } +void NodeViewContext::Select(const QVector &nodes) +{ + foreach (Node *n, nodes) { + item_map_.value(n)->setSelected(true); + } +} + QVector NodeViewContext::GetSelectedItems() const { QVector items; diff --git a/app/widget/nodeview/nodeviewcontext.h b/app/widget/nodeview/nodeviewcontext.h index 62be15974..cf49b332d 100644 --- a/app/widget/nodeview/nodeviewcontext.h +++ b/app/widget/nodeview/nodeviewcontext.h @@ -30,6 +30,8 @@ public: void DeleteSelected(NodeViewDeleteCommand *command); + void Select(const QVector &nodes); + QVector GetSelectedItems() const; QPointF MapScenePosToNodePosInContext(const QPointF &pos) const; diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 57f67ddaf..1c645601e 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -47,7 +47,6 @@ NodeViewItem::NodeViewItem(Node* n, Node *context, QGraphicsItem *parent) : hide_titlebar_(false), highlighted_index_(-1), flow_dir_(NodeViewCommon::kLeftToRight), - prevent_removing_(false), label_as_output_(false) { // Set flags for this widget diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index 974cb1b3e..60d1f3874 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -122,16 +122,6 @@ public: void SetHighlightedIndex(int index); - void SetPreventRemoving(bool e) - { - prevent_removing_ = e; - } - - bool GetPreventRemoving() const - { - return prevent_removing_; - } - void SetLabelAsOutput(bool e); NodeInput GetInputFromInputConnector(NodeViewItemConnector *connector); @@ -210,8 +200,6 @@ private: QPointF cached_node_pos_; - bool prevent_removing_; - std::vector > input_connectors_; NodeViewItemConnector *output_connector_; diff --git a/app/widget/nodeview/nodeviewscene.cpp b/app/widget/nodeview/nodeviewscene.cpp index 2e422c24b..37a226710 100644 --- a/app/widget/nodeview/nodeviewscene.cpp +++ b/app/widget/nodeview/nodeviewscene.cpp @@ -44,37 +44,16 @@ void NodeViewScene::SetFlowDirection(NodeViewCommon::FlowDirection direction) } } -void NodeViewScene::clear() -{ - // Deselect everything (prevents signals that a selection has changed after deleting an object) - DeselectAll(); - - // HACK: QGraphicsScene contains some sort of internal caching of the selected items which doesn't update unless - // we call a function like this. That means even though we deselect all items above, QGraphicsScene will - // continue to incorrectly signal selectionChanged() when items that were selected (but are now not) get - // deleted. Calling this function appears to update the internal cache and prevent this. - selectedItems(); - - for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) { - delete it.value(); - } - item_map_.clear(); -} - void NodeViewScene::SelectAll() { - QList all_items = this->items(); - - foreach (QGraphicsItem* i, all_items) { + foreach (QGraphicsItem* i, items()) { i->setSelected(true); } } void NodeViewScene::DeselectAll() { - QList selected_items = this->selectedItems(); - - foreach (QGraphicsItem* i, selected_items) { + foreach (QGraphicsItem* i, items()) { i->setSelected(false); } } @@ -90,25 +69,6 @@ void NodeViewScene::DeleteSelected() Core::instance()->undo_stack()->push(command); } -NodeViewItem *NodeViewScene::NodeToUIObject(Node *n) -{ - return item_map_.value(n); -} - -QVector NodeViewScene::GetSelectedNodes() const -{ - QHash::const_iterator iterator; - QVector selected; - - for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) { - if (iterator.value()->isSelected()) { - selected.append(iterator.key()); - } - } - - return selected; -} - QVector NodeViewScene::GetSelectedItems() const { QVector items; @@ -151,31 +111,11 @@ void NodeViewScene::RemoveContext(Node *node) delete context_map_.take(node); } -int NodeViewScene::DetermineWeight(Node *n) -{ - QVector inputs = n->GetImmediateDependencies(); - - int weight = 0; - - foreach (Node* i, inputs) { - if (i->GetNumberOfRoutesTo(n) == 1) { - weight += DetermineWeight(i); - } - } - - return qMax(1, weight); -} - Qt::Orientation NodeViewScene::GetFlowOrientation() const { return NodeViewCommon::GetFlowOrientation(direction_); } -NodeViewCommon::FlowDirection NodeViewScene::GetFlowDirection() const -{ - return direction_; -} - void NodeViewScene::SetEdgesAreCurved(bool curved) { if (curved_edges_ != curved) { diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index b9949d59c..467109923 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -37,27 +37,11 @@ class NodeViewScene : public QGraphicsScene public: NodeViewScene(QObject *parent = nullptr); - void clear(); - void SelectAll(); void DeselectAll(); void DeleteSelected(); - /** - * @brief Retrieve the graphical widget corresponding to a specific Node - * - * In situations where you know what Node you're working with but need the UI object (e.g. for positioning), this - * static function will retrieve the NodeViewItem (Node UI representation) connected to this Node in a certain - * QGraphicsScene. This can be called from any other UI object, since it'll have a reference to the QGraphicsScene - * through QGraphicsItem::scene(). - * - * If the scene does not contain a widget for this node (usually meaning the node's graph is not the active graph - * in this view/scene), this function returns nullptr. - */ - NodeViewItem* NodeToUIObject(Node* n); - - QVector GetSelectedNodes() const; QVector GetSelectedItems() const; const QHash &context_map() const @@ -65,14 +49,13 @@ public: return context_map_; } - const QHash& item_map() const - { - return item_map_; - } - Qt::Orientation GetFlowOrientation() const; - NodeViewCommon::FlowDirection GetFlowDirection() const; + NodeViewCommon::FlowDirection GetFlowDirection() const + { + return direction_; + } + void SetFlowDirection(NodeViewCommon::FlowDirection direction); bool GetEdgesAreCurved() const @@ -90,12 +73,8 @@ public slots: void SetEdgesAreCurved(bool curved); private: - static int DetermineWeight(Node* n); - QHash context_map_; - QHash item_map_; - NodeGraph* graph_; NodeViewCommon::FlowDirection direction_; From a94c7169f2f81d2a7a9ca96614dc8499e602ddb2 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 19 Nov 2021 17:14:56 -0800 Subject: [PATCH 10/34] store expanded status in context --- app/core.cpp | 2 +- app/node/node.cpp | 18 +++-- app/node/node.h | 71 ++++++++++++++++--- app/node/nodecopypaste.cpp | 8 +-- app/node/project/project.cpp | 17 +++-- app/node/project/project.h | 4 +- app/widget/nodeview/nodeview.cpp | 100 +++++++++++++-------------- app/widget/nodeview/nodeviewitem.cpp | 8 ++- 8 files changed, 143 insertions(+), 85 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index 82a6b98fc..1db15b4fa 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -437,7 +437,7 @@ void Core::CreateNewSequence() command->add_child(new NodeAddCommand(active_project, new_sequence)); command->add_child(new FolderAddChild(GetSelectedFolderInActiveProject(), new_sequence)); - command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, QPointF(0, 0))); + command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, Node::Position())); // Create and connect default nodes to new sequence new_sequence->add_default_nodes(command); diff --git a/app/node/node.cpp b/app/node/node.cpp index b1b45ccf6..ddabc5a61 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -243,12 +243,16 @@ QIcon Node::icon() const return icon::New; } -QPointF Node::GetNodePositionInContext(Node *node) +bool Node::SetNodePositionInContext(Node *node, const QPointF &pos) { - return context_positions_.value(node); + Position p = context_positions_.value(node); + + p.position = pos; + + return SetNodePositionInContext(node, p); } -bool Node::SetNodePositionInContext(Node *node, const QPointF &pos) +bool Node::SetNodePositionInContext(Node *node, const Position &pos) { bool added = !ContextContainsNode(node); context_positions_.insert(node, pos); @@ -257,7 +261,7 @@ bool Node::SetNodePositionInContext(Node *node, const QPointF &pos) emit NodeAddedToContext(node); } - emit NodePositionInContextChanged(node, pos); + emit NodePositionInContextChanged(node, pos.position); return added; } @@ -2350,13 +2354,13 @@ Project *Node::ArrayResizeCommand::GetRelevantProject() const void NodeSetPositionCommand::redo() { if (!(added_ = !context_->ContextContainsNode(node_))) { - old_pos_ = context_->GetNodePositionInContext(node_); + old_pos_ = context_->GetNodePositionDataInContext(node_); } if (added_) { context_->SetNodePositionInContext(node_, pos_); } else { - move(context_, node_, pos_ - old_pos_, move_deps_); + move(context_, node_, pos_.position - old_pos_.position, move_deps_); } } @@ -2365,7 +2369,7 @@ void NodeSetPositionCommand::undo() if (added_) { context_->RemoveNodeFromContext(node_); } else { - move(context_, node_, old_pos_ - pos_, move_deps_); + move(context_, node_, old_pos_.position - pos_.position, move_deps_); } } diff --git a/app/node/node.h b/app/node/node.h index fceb08d1c..ca3f7f43c 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -208,21 +208,77 @@ public: return HasInputWithID(id); } - using PositionMap = QHash; + struct Position + { + Position(const QPointF &p = QPointF(0, 0), bool e = false) + { + position = p; + expanded = e; + } + + QPointF position; + bool expanded; + + inline Position &operator+=(const Position &p) + { + position += p.position; + return *this; + } + + inline Position &operator-=(const Position &p) + { + position -= p.position; + return *this; + } + + friend inline const Position operator+(Position a, const Position &b) + { + a += b; + return a; + } + + friend inline const Position operator-(Position a, const Position &b) + { + a -= b; + return a; + } + }; + + using PositionMap = QHash; const PositionMap &GetContextPositions() const { return context_positions_; } + bool IsNodeExpandedInContext(Node *node) const + { + return context_positions_.value(node).expanded; + } + bool ContextContainsNode(Node *node) const { return context_positions_.contains(node); } - QPointF GetNodePositionInContext(Node *node); + Position GetNodePositionDataInContext(Node *node) + { + return context_positions_.value(node); + } + + QPointF GetNodePositionInContext(Node *node) + { + return GetNodePositionDataInContext(node).position; + } bool SetNodePositionInContext(Node *node, const QPointF &pos); + bool SetNodePositionInContext(Node *node, const Position &pos); + + void SetNodeExpandedInContext(Node *node, bool e) + { + context_positions_[node].expanded = e; + } + bool RemoveNodeFromContext(Node *node); /** @@ -1006,11 +1062,6 @@ protected: } signals: - /** - * @brief Signal emitted whenever the position is set through SetPosition() - */ - void PositionChanged(const QPointF& pos); - /** * @brief Signal emitted when SetLabel() is called */ @@ -1394,7 +1445,7 @@ using NodePtr = std::shared_ptr; class NodeSetPositionCommand : public UndoCommand { public: - NodeSetPositionCommand(Node* node, Node* context, const QPointF& pos, bool move_dependencies_relatively = false) + NodeSetPositionCommand(Node* node, Node* context, const Node::Position& pos, bool move_dependencies_relatively = false) { node_ = node; context_ = context; @@ -1417,8 +1468,8 @@ private: Node* node_; Node* context_; - QPointF pos_; - QPointF old_pos_; + Node::Position pos_; + Node::Position old_pos_; bool added_; bool move_deps_; diff --git a/app/node/nodecopypaste.cpp b/app/node/nodecopypaste.cpp index 0de513820..e9c0a4d0e 100644 --- a/app/node/nodecopypaste.cpp +++ b/app/node/nodecopypaste.cpp @@ -92,7 +92,7 @@ QVector NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph, QVector pasted_nodes; XMLNodeData xml_node_data; - QMap > pasted_contexts; + QMap > pasted_contexts; while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("olive")) { @@ -131,7 +131,7 @@ QVector NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph, while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("context")) { // Get context ptr - QMap map; + QMap map; quintptr context_ptr = 0; XMLAttributeLoop((&reader), attr) { if (attr.name() == QStringLiteral("ptr")) { @@ -144,7 +144,7 @@ QVector NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph, while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("node")) { quintptr node_ptr; - QPointF node_pos; + Node::Position node_pos; if (Project::LoadPosition(&reader, &node_ptr, &node_pos)) { map.insert(node_ptr, node_pos); @@ -216,7 +216,7 @@ QVector NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph, for (auto it=pasted_contexts.cbegin(); it!=pasted_contexts.cend(); it++) { Node *context = xml_node_data.node_ptrs.value(it.key()); if (context) { - const QMap &map = it.value(); + auto map = it.value(); for (auto jt=map.cbegin(); jt!=map.cend(); jt++) { Node *subnode = xml_node_data.node_ptrs.value(jt.key()); if (subnode) { diff --git a/app/node/project/project.cpp b/app/node/project/project.cpp index 64d7cb107..ba118ba50 100644 --- a/app/node/project/project.cpp +++ b/app/node/project/project.cpp @@ -158,7 +158,7 @@ void Project::Load(QXmlStreamReader *reader, MainWindowLayoutInfo* layout, uint while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("node")) { quintptr node_ptr; - QPointF node_pos; + Node::Position node_pos; if (LoadPosition(reader, &node_ptr, &node_pos)) { Node *node = xml_node_data.node_ptrs.value(node_ptr); @@ -350,7 +350,7 @@ void Project::RegenerateUuid() uuid_ = QUuid::createUuid(); } -bool Project::LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, QPointF *pos) +bool Project::LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, Node::Position *pos) { bool got_node_ptr = false; bool got_pos_x = false; @@ -366,11 +366,13 @@ bool Project::LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, QPointF while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("x")) { - pos->setX(reader->readElementText().toDouble()); + pos->position.setX(reader->readElementText().toDouble()); got_pos_x = true; } else if (reader->name() == QStringLiteral("y")) { - pos->setY(reader->readElementText().toDouble()); + pos->position.setY(reader->readElementText().toDouble()); got_pos_y = true; + } else if (reader->name() == QStringLiteral("expanded")) { + pos->expanded = reader->readElementText().toInt(); } else { reader->skipCurrentElement(); } @@ -379,12 +381,13 @@ bool Project::LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, QPointF return got_node_ptr && got_pos_x && got_pos_y; } -void Project::SavePosition(QXmlStreamWriter *writer, Node *node, const QPointF &pos) +void Project::SavePosition(QXmlStreamWriter *writer, Node *node, const Node::Position &pos) { writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(node))); - writer->writeTextElement(QStringLiteral("x"), QString::number(pos.x())); - writer->writeTextElement(QStringLiteral("y"), QString::number(pos.y())); + writer->writeTextElement(QStringLiteral("x"), QString::number(pos.position.x())); + writer->writeTextElement(QStringLiteral("y"), QString::number(pos.position.y())); + writer->writeTextElement(QStringLiteral("expanded"), QString::number(pos.expanded)); } void Project::ColorManagerValueChanged(const NodeInput &input, const TimeRange &range) diff --git a/app/node/project/project.h b/app/node/project/project.h index 3dbceb2bf..ef2e3e192 100644 --- a/app/node/project/project.h +++ b/app/node/project/project.h @@ -82,8 +82,8 @@ public: void RegenerateUuid(); - static bool LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, QPointF *pos); - static void SavePosition(QXmlStreamWriter *writer, Node *node, const QPointF &pos); + static bool LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, Node::Position *pos); + static void SavePosition(QXmlStreamWriter *writer, Node *node, const Node::Position &pos); signals: void NameChanged(); diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 5cb1d767c..e805d7eda 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -523,7 +523,6 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } Core::instance()->undo_stack()->pushIfHasChildren(command); - return; } MultiUndoCommand* command = new MultiUndoCommand(); @@ -539,64 +538,63 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } } - if (!context) { - QToolTip::showText(QCursor::pos(), tr("Nodes must be placed inside a context.")); - return; - } - - if (paste_command_) { - // We've already "done" this command, but MultiUndoCommand prevents "redoing" twice, so we - // add it to this command (which may have extra commands added too) so that it all gets undone - // in the same action - command->add_child(paste_command_); - paste_command_ = nullptr; - } - - { - MultiUndoCommand *add_command = new MultiUndoCommand(); - - foreach (const AttachedItem &ai, attached_items_) { - // Add node to the same graph that the context is in - add_command->add_child(new NodeAddCommand(context->parent(), ai.item->GetNode())); - - // Add node to the context - add_command->add_child(new NodeSetPositionCommand(ai.item->GetNode(), context, scene_.context_map().value(context)->MapScenePosToNodePosInContext(ai.item->pos()))); + if (context) { + if (paste_command_) { + // We've already "done" this command, but MultiUndoCommand prevents "redoing" twice, so we + // add it to this command (which may have extra commands added too) so that it all gets undone + // in the same action + command->add_child(paste_command_); + paste_command_ = nullptr; } - if (add_command->child_count()) { - add_command->redo_now(); - command->add_child(add_command); - } else { - delete add_command; - } - } + { + MultiUndoCommand *add_command = new MultiUndoCommand(); - { - // Dropped attached item onto an edge, connect it between them - MultiUndoCommand *drop_edge_command = new MultiUndoCommand(); - if (attached_items_.size() == 1) { - Node* dropping_node = attached_items_.first().item->GetNode(); + foreach (const AttachedItem &ai, attached_items_) { + // Add node to the same graph that the context is in + add_command->add_child(new NodeAddCommand(context->parent(), ai.item->GetNode())); - if (drop_edge_) { - // Remove old edge - drop_edge_command->add_child(new NodeEdgeRemoveCommand(drop_edge_->output(), drop_edge_->input())); - - // Place new edges - drop_edge_command->add_child(new NodeEdgeAddCommand(drop_edge_->output(), drop_input_)); - drop_edge_command->add_child(new NodeEdgeAddCommand(dropping_node, drop_edge_->input())); + // Add node to the context + add_command->add_child(new NodeSetPositionCommand(ai.item->GetNode(), context, scene_.context_map().value(context)->MapScenePosToNodePosInContext(ai.item->pos()))); } - drop_edge_ = nullptr; + if (add_command->child_count()) { + add_command->redo_now(); + command->add_child(add_command); + } else { + delete add_command; + } } - if (drop_edge_command->child_count()) { - drop_edge_command->redo_now(); - command->add_child(drop_edge_command); - } else { - delete drop_edge_command; - } - } - DetachItemsFromCursor(); + { + // Dropped attached item onto an edge, connect it between them + MultiUndoCommand *drop_edge_command = new MultiUndoCommand(); + if (attached_items_.size() == 1) { + Node* dropping_node = attached_items_.first().item->GetNode(); + + if (drop_edge_) { + // Remove old edge + drop_edge_command->add_child(new NodeEdgeRemoveCommand(drop_edge_->output(), drop_edge_->input())); + + // Place new edges + drop_edge_command->add_child(new NodeEdgeAddCommand(drop_edge_->output(), drop_input_)); + drop_edge_command->add_child(new NodeEdgeAddCommand(dropping_node, drop_edge_->input())); + } + + drop_edge_ = nullptr; + } + if (drop_edge_command->child_count()) { + drop_edge_command->redo_now(); + command->add_child(drop_edge_command); + } else { + delete drop_edge_command; + } + } + + DetachItemsFromCursor(); + } else { + QToolTip::showText(QCursor::pos(), tr("Nodes must be placed inside a context.")); + } } for (auto it=dragging_items_.cbegin(); it!=dragging_items_.cend(); it++) { diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 1c645601e..3ea29ff5a 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -85,9 +85,8 @@ NodeViewItem::NodeViewItem(Node* n, Node *context, QGraphicsItem *parent) : if (context_) { SetNodePosition(context_->GetNodePositionInContext(node_)); + SetExpanded(context_->IsNodeExpandedInContext(node_)); } - - SetExpanded(node_->property("expanded").toBool()); } QPointF NodeViewItem::GetNodePosition() const @@ -236,7 +235,10 @@ void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) expanded_ = e; hide_titlebar_ = hide_titlebar; - node_->setProperty("expanded", e); + + if (context_) { + context_->SetNodeExpandedInContext(node_, e); + } if (expanded_ && !node_inputs_.isEmpty()) { // Create new rect From 495b07fc5578978e9e462038c730286f40952e8c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 24 Nov 2021 17:58:48 -0800 Subject: [PATCH 11/34] implemented node groups --- app/dialog/CMakeLists.txt | 1 + app/dialog/nodegroup/CMakeLists.txt | 22 ++ app/dialog/nodegroup/nodegroupdialog.cpp | 99 ++++++++ .../nodegroup/nodegroupdialog.h} | 31 ++- .../nodeproperties/nodepropertiesdialog.cpp | 2 +- app/node/CMakeLists.txt | 3 +- app/node/group.cpp | 30 --- app/node/group/CMakeLists.txt | 22 ++ app/node/group/group.cpp | 177 +++++++++++++++ app/node/group/group.h | 176 ++++++++++++++ app/node/node.cpp | 19 +- app/node/node.h | 41 +--- app/node/output/viewer/viewer.h | 2 - app/node/param.cpp | 18 ++ app/node/param.h | 37 ++- app/render/previewautocacher.cpp | 3 + app/render/previewautocacher.h | 4 +- app/widget/nodeparamview/nodeparamview.cpp | 214 +++++++++++------- app/widget/nodeparamview/nodeparamview.h | 29 ++- .../nodeparamview/nodeparamviewdockarea.h | 2 + .../nodeparamview/nodeparamviewitem.cpp | 68 +++++- app/widget/nodeparamview/nodeparamviewitem.h | 29 ++- app/widget/nodeview/nodeview.cpp | 96 +++++++- app/widget/nodeview/nodeview.h | 2 +- app/widget/nodeview/nodeviewcontext.cpp | 4 +- app/widget/nodeview/nodeviewcontext.h | 5 + app/widget/nodeview/nodeviewitem.cpp | 2 +- app/window/mainwindow/mainwindow.cpp | 10 - app/window/mainwindow/mainwindow.h | 2 - 29 files changed, 947 insertions(+), 203 deletions(-) create mode 100644 app/dialog/nodegroup/CMakeLists.txt create mode 100644 app/dialog/nodegroup/nodegroupdialog.cpp rename app/{node/group.h => dialog/nodegroup/nodegroupdialog.h} (61%) delete mode 100644 app/node/group.cpp create mode 100644 app/node/group/CMakeLists.txt create mode 100644 app/node/group/group.cpp create mode 100644 app/node/group/group.h diff --git a/app/dialog/CMakeLists.txt b/app/dialog/CMakeLists.txt index ff82a6b2c..01bbfd5ec 100644 --- a/app/dialog/CMakeLists.txt +++ b/app/dialog/CMakeLists.txt @@ -23,6 +23,7 @@ add_subdirectory(diskcache) add_subdirectory(export) add_subdirectory(footagerelink) add_subdirectory(keyframeproperties) +add_subdirectory(nodegroup) add_subdirectory(nodeproperties) add_subdirectory(preferences) add_subdirectory(progress) diff --git a/app/dialog/nodegroup/CMakeLists.txt b/app/dialog/nodegroup/CMakeLists.txt new file mode 100644 index 000000000..0f48edfc9 --- /dev/null +++ b/app/dialog/nodegroup/CMakeLists.txt @@ -0,0 +1,22 @@ +# 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 . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + dialog/nodegroup/nodegroupdialog.cpp + dialog/nodegroup/nodegroupdialog.h + PARENT_SCOPE +) diff --git a/app/dialog/nodegroup/nodegroupdialog.cpp b/app/dialog/nodegroup/nodegroupdialog.cpp new file mode 100644 index 000000000..2d07a4338 --- /dev/null +++ b/app/dialog/nodegroup/nodegroupdialog.cpp @@ -0,0 +1,99 @@ +/*** + + 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 "nodegroupdialog.h" + +#include +#include +#include +#include + +#include "widget/nodeparamview/nodeparamview.h" +#include "widget/nodeview/nodeview.h" + +namespace olive { + +#define super QDialog + +NodeGroupDialog::NodeGroupDialog(NodeGroup *group, QWidget *parent) : + super(parent), + group_(group), + parent_undo_(nullptr) +{ + QGridLayout *layout = new QGridLayout(this); + + int row = 0; + + layout->addWidget(new QLabel(tr("Name:")), row, 0); + + name_edit_ = new QLineEdit(); + layout->addWidget(name_edit_, row, 1); + + row++; + + QSplitter *splitter = new QSplitter(Qt::Horizontal); + layout->addWidget(splitter, row, 0, 1, 2); + + NodeParamView *param_view = new NodeParamView(false); + param_view->SetCreateCheckBoxes(kCheckBoxesOnNonConnected); + splitter->addWidget(param_view); + + NodeView *node_view = new NodeView(); + node_view->SetContexts({group}); + QMetaObject::invokeMethod(node_view, &NodeView::CenterOnItemsBoundingRect, Qt::QueuedConnection); + splitter->addWidget(node_view); + + for (auto it=group->GetInputPassthroughs().cbegin(); it!=group->GetInputPassthroughs().cend(); it++) { + param_view->SetInputChecked(it.value(), true); + } + + connect(node_view, &NodeView::NodesSelected, param_view, &NodeParamView::SelectNodes); + connect(node_view, &NodeView::NodesDeselected, param_view, &NodeParamView::DeselectNodes); + node_view->SelectAll(); + + row++; + + QDialogButtonBox *btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + btns->setCenterButtons(true); + connect(btns, &QDialogButtonBox::accepted, this, &NodeGroupDialog::accept); + connect(btns, &QDialogButtonBox::rejected, this, &NodeGroupDialog::reject); + layout->addWidget(btns, row, 0, 1, 2); + + setWindowTitle(tr("Group Editor")); +} + +void NodeGroupDialog::accept() +{ + MultiUndoCommand *command = new MultiUndoCommand(); + + if (name_edit_->text() != group_->GetCustomName()) { + command->add_child(new NodeGroupSetCustomNameCommand(group_, name_edit_->text())); + } + + if (parent_undo_) { + parent_undo_->add_child(command); + } else { + Core::instance()->undo_stack()->push(command); + } + + super::accept(); +} + +} diff --git a/app/node/group.h b/app/dialog/nodegroup/nodegroupdialog.h similarity index 61% rename from app/node/group.h rename to app/dialog/nodegroup/nodegroupdialog.h index 2f15bbb71..741f0ac55 100644 --- a/app/node/group.h +++ b/app/dialog/nodegroup/nodegroupdialog.h @@ -18,26 +18,41 @@ ***/ -#ifndef NODEGROUP_H -#define NODEGROUP_H +#ifndef NODEGROUPDIALOG_H +#define NODEGROUPDIALOG_H -#include "node.h" +#include +#include + +#include "node/group/group.h" namespace olive { -class NodeGroup : public Node +class NodeGroupDialog : public QDialog { Q_OBJECT public: - NodeGroup(); + explicit NodeGroupDialog(NodeGroup *group, QWidget *parent = nullptr); - void SetNodes(Node *nodes); + void SetParentUndoCommand(MultiUndoCommand *c) + { + parent_undo_ = c; + } + +public slots: + virtual void accept() override; + +signals: private: - QVector nodes_; + NodeGroup *group_; + + QLineEdit *name_edit_; + + MultiUndoCommand *parent_undo_; }; } -#endif // NODEGROUP_H +#endif // NODEGROUPDIALOG_H diff --git a/app/dialog/nodeproperties/nodepropertiesdialog.cpp b/app/dialog/nodeproperties/nodepropertiesdialog.cpp index 123b6c69b..fca1935ba 100644 --- a/app/dialog/nodeproperties/nodepropertiesdialog.cpp +++ b/app/dialog/nodeproperties/nodepropertiesdialog.cpp @@ -46,7 +46,7 @@ NodePropertiesDialog::NodePropertiesDialog(Node *node, const rational &timebase, label_edit_->setText(node->GetLabel()); label_layout->addWidget(label_edit_); - NodeParamViewItem *item = new NodeParamViewItem(node); + NodeParamViewItem *item = new NodeParamViewItem(node, kNoCheckBoxes); item->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); item->SetTimebase(timebase); item->setTitleBarWidget(new QWidget()); diff --git a/app/node/CMakeLists.txt b/app/node/CMakeLists.txt index 6cd34ca5b..508bf00bf 100644 --- a/app/node/CMakeLists.txt +++ b/app/node/CMakeLists.txt @@ -20,6 +20,7 @@ add_subdirectory(color) add_subdirectory(distort) add_subdirectory(filter) add_subdirectory(generator) +add_subdirectory(group) add_subdirectory(input) add_subdirectory(math) add_subdirectory(output) @@ -34,8 +35,6 @@ 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 deleted file mode 100644 index 7b7ab0c87..000000000 --- a/app/node/group.cpp +++ /dev/null @@ -1,30 +0,0 @@ -/*** - - 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/CMakeLists.txt b/app/node/group/CMakeLists.txt new file mode 100644 index 000000000..3c0bce4d6 --- /dev/null +++ b/app/node/group/CMakeLists.txt @@ -0,0 +1,22 @@ +# 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 . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + node/group/group.cpp + node/group/group.h + PARENT_SCOPE +) diff --git a/app/node/group/group.cpp b/app/node/group/group.cpp new file mode 100644 index 000000000..fbe785bd9 --- /dev/null +++ b/app/node/group/group.cpp @@ -0,0 +1,177 @@ +/*** + + 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" + +#include "node/graph.h" + +namespace olive { + +NodeGroup::NodeGroup() : + output_passthrough_(nullptr) +{ + graph_ = new NodeGraph(); + graph_->setParent(this); +} + +QString NodeGroup::Name() const +{ + if (custom_name_.isEmpty()) { + return tr("Group"); + } else { + return custom_name_; + } +} + +QString NodeGroup::id() const +{ + return QStringLiteral("org.olivevideoeditor.Olive.group"); +} + +QVector NodeGroup::Category() const +{ + return {kCategoryGeneral}; +} + +QString NodeGroup::Description() const +{ + return tr("A group of nodes that is represented as a single node."); +} + +void NodeGroup::Retranslate() +{ + foreach (Node *n, graph_->nodes()) { + n->Retranslate(); + } +} + +void NodeGroup::AddNode(Node *node) +{ + node->setParent(graph_); +} + +void NodeGroup::RemoveNode(Node *node, QObject *new_parent) +{ + if (node->parent() == graph_) { + node->setParent(new_parent); + } +} + +void NodeGroup::AddInputPassthrough(const NodeInput &input) +{ + Q_ASSERT(graph_->nodes().contains(input.node())); + + for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) { + if (it.value() == input) { + // Already passing this input through + return; + } + } + + // Add input + QString id = GetGroupInputIDFromInput(input); + + AddInput(id, input.GetDataType(), input.GetDefaultValue(), input.GetFlags()); + + input_passthroughs_.insert(id, input); +} + +void NodeGroup::RemoveInputPassthrough(const NodeInput &input) +{ + for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) { + if (it.value() == input) { + RemoveInput(it.key()); + input_passthroughs_.erase(it); + break; + } + } +} + +void NodeGroup::SetOutputPassthrough(Node *node) +{ + Q_ASSERT(graph_->nodes().contains(node)); + + output_passthrough_ = node; +} + +QString NodeGroup::GetGroupInputIDFromInput(const NodeInput &input) +{ + QCryptographicHash hash(QCryptographicHash::Sha1); + + hash.addData(input.node()->GetUUID().toByteArray()); + + hash.addData(input.input().toUtf8()); + + hash.addData((const char*) &input.element(), sizeof(input.element())); + + return QString::fromLatin1(hash.result().toHex()); +} + +bool NodeGroup::ContainsInputPassthrough(const NodeInput &input) const +{ + for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) { + if (it.value() == input) { + return true; + } + } + + return false; +} + +void NodeAddToGroupCommand::redo() +{ + previous_parent_ = node_->parent(); + group_->AddNode(node_); +} + +void NodeAddToGroupCommand::undo() +{ + group_->RemoveNode(node_, previous_parent_); +} + +void NodeGroupSetCustomNameCommand::redo() +{ + old_name_ = group_->GetCustomName(); + group_->SetCustomName(new_name_); +} + +void NodeGroupSetCustomNameCommand::undo() +{ + group_->SetCustomName(old_name_); +} + +void NodeGroupAddInputPassthrough::redo() +{ + if (!group_->ContainsInputPassthrough(input_)) { + group_->AddInputPassthrough(input_); + actually_added_ = true; + } else { + actually_added_ = false; + } +} + +void NodeGroupAddInputPassthrough::undo() +{ + if (actually_added_) { + group_->RemoveInputPassthrough(input_); + } +} + +} diff --git a/app/node/group/group.h b/app/node/group/group.h new file mode 100644 index 000000000..a0aa9cee3 --- /dev/null +++ b/app/node/group/group.h @@ -0,0 +1,176 @@ +/*** + + 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/node.h" + +namespace olive { + +class NodeGroup : public Node +{ + Q_OBJECT +public: + NodeGroup(); + + NODE_DEFAULT_DESTRUCTOR(NodeGroup) + NODE_COPY_FUNCTION(NodeGroup) + + virtual QString Name() const override; + virtual QString id() const override; + virtual QVector Category() const override; + virtual QString Description() const override; + + virtual void Retranslate() override; + + void AddNode(Node *node); + + void RemoveNode(Node *node, QObject *new_parent = nullptr); + + void AddInputPassthrough(const NodeInput &input); + + void RemoveInputPassthrough(const NodeInput &input); + + void SetOutputPassthrough(Node *node); + + const QString &GetCustomName() const + { + return custom_name_; + } + + void SetCustomName(const QString &name) + { + custom_name_ = name; + + // NOTE: Not technically the right signal, but should achieve the right goal + emit LabelChanged(custom_name_); + } + + void ClearCustomName() + { + custom_name_.clear(); + } + + static QString GetGroupInputIDFromInput(const NodeInput &input); + + const QHash &GetInputPassthroughs() const + { + return input_passthroughs_; + } + + bool ContainsInputPassthrough(const NodeInput &input) const; + +private: + NodeGraph *graph_; + + QHash input_passthroughs_; + + Node *output_passthrough_; + + QString custom_name_; + +}; + +class NodeAddToGroupCommand : public UndoCommand +{ +public: + NodeAddToGroupCommand(Node *node, NodeGroup *group) : + node_(node), + group_(group) + {} + + virtual Project * GetRelevantProject() const override + { + return node_->project(); + } + +protected: + virtual void redo() override; + + virtual void undo() override; + +private: + Node *node_; + + NodeGroup *group_; + + QObject *previous_parent_; + +}; + +class NodeGroupSetCustomNameCommand : public UndoCommand +{ +public: + NodeGroupSetCustomNameCommand(NodeGroup *group, const QString &name) : + group_(group), + new_name_(name) + {} + + virtual Project * GetRelevantProject() const override + { + return group_->project(); + } + +protected: + virtual void redo() override; + + virtual void undo() override; + +private: + NodeGroup *group_; + + QString old_name_; + + QString new_name_; + +}; + +class NodeGroupAddInputPassthrough : public UndoCommand +{ +public: + NodeGroupAddInputPassthrough(NodeGroup *group, const NodeInput &input) : + group_(group), + input_(input), + actually_added_(false) + {} + + virtual Project * GetRelevantProject() const override + { + return group_->project(); + } + +protected: + virtual void redo() override; + + virtual void undo() override; + +private: + NodeGroup *group_; + + NodeInput input_; + + bool actually_added_; + +}; + +} + +#endif // NODEGROUP_H diff --git a/app/node/node.cpp b/app/node/node.cpp index ddabc5a61..1d0316e8b 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -49,6 +49,7 @@ Node::Node() : operation_stack_(0), cache_result_(false) { + uuid_ = QUuid::createUuid(); } Node::~Node() @@ -88,6 +89,8 @@ void Node::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, uint versi xml_node_data.node_ptrs.insert(reader->readElementText().toULongLong(), this); } else if (reader->name() == QStringLiteral("label")) { SetLabel(reader->readElementText()); + } else if (reader->name() == QStringLiteral("uuid")) { + SetUUID(QUuid::fromString(reader->readElementText())); } else if (reader->name() == QStringLiteral("color")) { override_color_ = reader->readElementText().toInt(); } else if (reader->name() == QStringLiteral("links")) { @@ -169,6 +172,7 @@ void Node::Save(QXmlStreamWriter *writer) const { writer->writeTextElement(QStringLiteral("ptr"), QString::number(reinterpret_cast(this))); + writer->writeTextElement(QStringLiteral("uuid"), uuid_.toString()); writer->writeTextElement(QStringLiteral("label"), GetLabel()); writer->writeTextElement(QStringLiteral("color"), QString::number(override_color_)); @@ -219,7 +223,16 @@ void Node::Save(QXmlStreamWriter *writer) const Project* Node::project() const { - return dynamic_cast(parent()); + QObject *t = this->parent(); + + while (t) { + if (Project *p = dynamic_cast(t)) { + return p; + } + t = t->parent(); + } + + return nullptr; } QString Node::ShortName() const @@ -1089,7 +1102,7 @@ NodeInputImmediate *Node::GetImmediate(const QString &input, int element) const return nullptr; } -Node::InputFlags Node::GetInputFlags(const QString &input) const +InputFlags Node::GetInputFlags(const QString &input) const { const Input* i = GetInternalInputData(input); @@ -1343,7 +1356,7 @@ void Node::HashAddNodeSignature(QCryptographicHash &hash) const hash.addData(id().toUtf8()); } -void Node::InsertInput(const QString &id, NodeValue::Type type, const QVariant &default_value, Node::InputFlags flags, int index) +void Node::InsertInput(const QString &id, NodeValue::Type type, const QVariant &default_value, InputFlags flags, int index) { if (id.isEmpty()) { qWarning() << "Rejected adding input with an empty ID on node" << this->id(); diff --git a/app/node/node.h b/app/node/node.h index ca3f7f43c..3a0fa5130 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "codec/frame.h" @@ -112,6 +113,9 @@ public: Project* project() const; + const QUuid &GetUUID() const {return uuid_;} + void SetUUID(const QUuid &uuid) {uuid_ = uuid;} + /** * @brief Clear current node variables and replace them with */ @@ -935,38 +939,9 @@ public: }; + InputFlags GetInputFlags(const QString& input) const; + protected: - enum InputFlag { - /// By default, inputs are keyframable, connectable, and NOT arrays - kInputFlagNormal = 0x0, - kInputFlagArray = 0x1, - kInputFlagNotKeyframable = 0x2, - kInputFlagNotConnectable = 0x4, - kInputFlagHidden = 0x8 - }; - - class InputFlags { - public: - explicit InputFlags() - { - f_ = kInputFlagNormal; - } - - explicit InputFlags(uint64_t flags) - { - f_ = flags; - } - - bool operator&(const InputFlag& f) const - { - return f_ & f; - } - - private: - uint64_t f_; - - }; - virtual void Hash(QCryptographicHash& hash, const NodeGlobals &globals, const VideoParams& video_params) const; void HashAddNodeSignature(QCryptographicHash &hash) const; @@ -1216,8 +1191,6 @@ private: return input_ids_.indexOf(input); } - InputFlags GetInputFlags(const QString& input) const; - Input* GetInternalInputData(const QString& input) { int i = GetInternalInputIndex(input); @@ -1336,6 +1309,8 @@ private: PositionMap context_positions_; + QUuid uuid_; + private slots: /** * @brief Slot when a keyframe's time changes to keep the keyframes correctly sorted by time diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 56a89908c..5c516000c 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -254,8 +254,6 @@ private: AudioPlaybackCache audio_playback_cache_; - int operation_stack_; - VideoParams cached_video_params_; AudioParams cached_audio_params_; diff --git a/app/node/param.cpp b/app/node/param.cpp index f0a685abe..2e579f613 100644 --- a/app/node/param.cpp +++ b/app/node/param.cpp @@ -69,6 +69,15 @@ bool NodeInput::IsArray() const } } +InputFlags NodeInput::GetFlags() const +{ + if (IsValid()) { + return node_->GetInputFlags(input_); + } else { + return InputFlags(kInputFlagNormal); + } +} + Node *NodeInput::GetConnectedOutput() const { if (IsValid()) { @@ -87,6 +96,15 @@ NodeValue::Type NodeInput::GetDataType() const } } +QVariant NodeInput::GetDefaultValue() const +{ + if (IsValid()) { + return node_->GetDefaultValue(input_); + } else { + return QVariant(); + } +} + QStringList NodeInput::GetComboBoxStrings() const { if (IsValid()) { diff --git a/app/node/param.h b/app/node/param.h index 3de4b6727..2e2276187 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -31,6 +31,37 @@ namespace olive { class Node; class NodeKeyframe; +enum InputFlag { + /// By default, inputs are keyframable, connectable, and NOT arrays + kInputFlagNormal = 0x0, + kInputFlagArray = 0x1, + kInputFlagNotKeyframable = 0x2, + kInputFlagNotConnectable = 0x4, + kInputFlagHidden = 0x8 +}; + +class InputFlags { +public: + explicit InputFlags() + { + f_ = kInputFlagNormal; + } + + explicit InputFlags(uint64_t flags) + { + f_ = flags; + } + + bool operator&(const InputFlag& f) const + { + return f_ & f; + } + +private: + uint64_t f_; + +}; + struct NodeInputPair { bool operator==(const NodeInputPair& rhs) const { @@ -98,7 +129,7 @@ public: return input_; } - int element() const + const int &element() const { return element_; } @@ -123,10 +154,14 @@ public: bool IsArray() const; + InputFlags GetFlags() const; + Node *GetConnectedOutput() const; NodeValue::Type GetDataType() const; + QVariant GetDefaultValue() const; + QStringList GetComboBoxStrings() const; QVariant GetProperty(const QString& key) const; diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 2c121745d..75b78c3c5 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -373,6 +373,9 @@ void PreviewAutoCacher::AddNode(Node *node) // Add to project copy->setParent(&copied_project_); + // Copy UUID + copy->SetUUID(node->GetUUID()); + // Insert into map InsertIntoCopyMap(node, copy); diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index ad4665da3..f770c776f 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -24,8 +24,9 @@ #include #include "config/config.h" -#include "node/graph.h" #include "node/color/colormanager/colormanager.h" +#include "node/graph.h" +#include "node/group/group.h" #include "node/node.h" #include "node/output/viewer/viewer.h" #include "node/project/project.h" @@ -172,6 +173,7 @@ private: QVector graph_update_queue_; QHash copy_map_; + QHash graph_map_; ViewerOutput* copied_viewer_node_; ColorManager* copied_color_manager_; QVector created_nodes_; diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 9d3e77000..52b4407fc 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -32,10 +32,12 @@ namespace olive { #define super TimeBasedWidget -NodeParamView::NodeParamView(QWidget *parent) : +NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : super(true, false, parent), last_scroll_val_(0), - focused_node_(nullptr) + focused_node_(nullptr), + create_checkboxes_(kNoCheckBoxes), + time_target_(nullptr) { // Create horizontal layout to place scroll area in (and keyframe editing eventually) QHBoxLayout* layout = new QHBoxLayout(this); @@ -46,16 +48,16 @@ NodeParamView::NodeParamView(QWidget *parent) : layout->addWidget(splitter); // Set up scroll area for params - QScrollArea* scroll_area = new QScrollArea(); - scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); - scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scroll_area->setWidgetResizable(true); - splitter->addWidget(scroll_area); + param_scroll_area_ = new QScrollArea(); + param_scroll_area_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + param_scroll_area_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + param_scroll_area_->setWidgetResizable(true); + splitter->addWidget(param_scroll_area_); // Param widget param_widget_container_ = new NodeParamViewParamContainer(); connect(param_widget_container_, &NodeParamViewParamContainer::Resized, this, &NodeParamView::UpdateGlobalScrollBar); - scroll_area->setWidget(param_widget_container_); + param_scroll_area_->setWidget(param_widget_container_); param_widget_area_ = new NodeParamViewDockArea(); @@ -74,56 +76,63 @@ NodeParamView::NodeParamView(QWidget *parent) : param_widget_container_layout->addStretch(INT_MAX); - // Set up keyframe view - QWidget* keyframe_area = new QWidget(); - QVBoxLayout* keyframe_area_layout = new QVBoxLayout(keyframe_area); - keyframe_area_layout->setSpacing(0); - keyframe_area_layout->setMargin(0); - - // Create ruler object - keyframe_area_layout->addWidget(ruler()); - - // Create keyframe view - keyframe_view_ = new KeyframeView(); - keyframe_view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - ConnectTimelineView(keyframe_view_); - keyframe_area_layout->addWidget(keyframe_view_); - - // Connect ruler and keyframe view together - connect(ruler(), &TimeRuler::TimeChanged, keyframe_view_, &KeyframeView::SetTime); - connect(keyframe_view_, &KeyframeView::TimeChanged, ruler(), &TimeRuler::SetTime); - connect(keyframe_view_, &KeyframeView::TimeChanged, this, &NodeParamView::SetTime); - connect(keyframe_view_, &KeyframeView::Dragged, this, &NodeParamView::KeyframeViewDragged); - - // Connect keyframe view scaling to this - connect(keyframe_view_, &KeyframeView::ScaleChanged, this, &NodeParamView::SetScale); - - splitter->addWidget(keyframe_area); - - // Set both widgets to 50/50 - splitter->setSizes({INT_MAX, INT_MAX}); - // Disable collapsing param view (but collapsing keyframe view is permitted) splitter->setCollapsible(0, false); + if (create_keyframe_view) { + // Set up keyframe view + QWidget* keyframe_area = new QWidget(); + QVBoxLayout* keyframe_area_layout = new QVBoxLayout(keyframe_area); + keyframe_area_layout->setSpacing(0); + keyframe_area_layout->setMargin(0); + + // Create ruler object + keyframe_area_layout->addWidget(ruler()); + + // Create keyframe view + keyframe_view_ = new KeyframeView(); + keyframe_view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ConnectTimelineView(keyframe_view_); + keyframe_area_layout->addWidget(keyframe_view_); + + // Connect ruler and keyframe view together + connect(ruler(), &TimeRuler::TimeChanged, keyframe_view_, &KeyframeView::SetTime); + connect(keyframe_view_, &KeyframeView::TimeChanged, ruler(), &TimeRuler::SetTime); + connect(keyframe_view_, &KeyframeView::TimeChanged, this, &NodeParamView::SetTime); + connect(keyframe_view_, &KeyframeView::Dragged, this, &NodeParamView::KeyframeViewDragged); + + // Connect keyframe view scaling to this + connect(keyframe_view_, &KeyframeView::ScaleChanged, this, &NodeParamView::SetScale); + + splitter->addWidget(keyframe_area); + + // Set both widgets to 50/50 + splitter->setSizes({INT_MAX, INT_MAX}); + } else { + keyframe_view_ = nullptr; + } + // Create global vertical scrollbar on the right vertical_scrollbar_ = new QScrollBar(); vertical_scrollbar_->setMaximum(0); layout->addWidget(vertical_scrollbar_); // Connect scrollbars together - connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); - connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, scroll_area->verticalScrollBar(), &QScrollBar::setValue); - connect(scroll_area->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); - connect(scroll_area->verticalScrollBar(), &QScrollBar::valueChanged, keyframe_view_->verticalScrollBar(), &QScrollBar::setValue); - connect(vertical_scrollbar_, &QScrollBar::valueChanged, scroll_area->verticalScrollBar(), &QScrollBar::setValue); - connect(vertical_scrollbar_, &QScrollBar::valueChanged, keyframe_view_->verticalScrollBar(), &QScrollBar::setValue); + connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); + connect(vertical_scrollbar_, &QScrollBar::valueChanged, param_scroll_area_->verticalScrollBar(), &QScrollBar::setValue); - // TimeBasedWidget's scrollbar has extra functionality that we can take advantage of - keyframe_view_->setHorizontalScrollBar(scrollbar()); - keyframe_view_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + if (keyframe_view_) { + connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); + connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, param_scroll_area_->verticalScrollBar(), &QScrollBar::setValue); + connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::valueChanged, keyframe_view_->verticalScrollBar(), &QScrollBar::setValue); + connect(vertical_scrollbar_, &QScrollBar::valueChanged, keyframe_view_->verticalScrollBar(), &QScrollBar::setValue); - connect(keyframe_view_->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll); + // TimeBasedWidget's scrollbar has extra functionality that we can take advantage of + keyframe_view_->setHorizontalScrollBar(scrollbar()); + keyframe_view_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + + connect(keyframe_view_->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll); + } // Set a default scale - FIXME: Hardcoded SetScale(120); @@ -192,6 +201,14 @@ void NodeParamView::DeselectNodes(const QVector &nodes) } } +void NodeParamView::SetInputChecked(const NodeInput &input, bool e) +{ + input_checked_.insert(input, e); + if (NodeParamViewItem *item = items_.value(input.node())) { + item->SetInputChecked(input, e); + } +} + void NodeParamView::resizeEvent(QResizeEvent *event) { super::resizeEvent(event); @@ -205,17 +222,21 @@ void NodeParamView::ScaleChangedEvent(const double &scale) { super::ScaleChangedEvent(scale); - keyframe_view_->SetScale(scale); + if (keyframe_view_) { + keyframe_view_->SetScale(scale); + } } void NodeParamView::TimebaseChangedEvent(const rational &timebase) { super::TimebaseChangedEvent(timebase); - keyframe_view_->SetTimebase(timebase); + if (keyframe_view_) { + keyframe_view_->SetTimebase(timebase); + } foreach (NodeParamViewItem* item, items_) { - item->SetTimebase(timebase); + item->SetTimebase(timebase); } UpdateItemTime(GetTime()); @@ -225,29 +246,37 @@ void NodeParamView::TimeChangedEvent(const rational &time) { super::TimeChangedEvent(time); - keyframe_view_->SetTime(time); + if (keyframe_view_) { + keyframe_view_->SetTime(time); + } UpdateItemTime(time); } void NodeParamView::ConnectedNodeChangeEvent(ViewerOutput *n) { - // Set viewer as a time target - keyframe_view_->SetTimeTarget(n); + if (keyframe_view_) { + // Set viewer as a time target + keyframe_view_->SetTimeTarget(n); + } foreach (NodeParamViewItem* item, items_) { item->SetTimeTarget(n); } + + time_target_ = n; } Node *NodeParamView::GetTimeTarget() const { - return keyframe_view_->GetTimeTarget(); + return time_target_; } void NodeParamView::DeleteSelected() { - keyframe_view_->DeleteSelected(); + if (keyframe_view_) { + keyframe_view_->DeleteSelected(); + } } void NodeParamView::UpdateItemTime(const rational &time) @@ -293,14 +322,16 @@ void NodeParamView::SignalNodeOrder() void NodeParamView::AddNode(Node *n) { - NodeParamViewItem* item = new NodeParamViewItem(n, param_widget_area_); + NodeParamViewItem* item = new NodeParamViewItem(n, create_checkboxes_, param_widget_area_); item->setAllowedAreas(Qt::LeftDockWidgetArea); item->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable); item->SetExpanded(node_expanded_state_.value(n, true)); - connect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); - connect(n, &Node::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe); + if (keyframe_view_) { + connect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); + connect(n, &Node::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe); + } connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::SetTimeAndSignal); connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::RequestSelectNode); @@ -310,6 +341,15 @@ void NodeParamView::AddNode(Node *n) connect(item, &NodeParamViewItem::ArrayExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate); connect(item, &NodeParamViewItem::ExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate); connect(item, &NodeParamViewItem::Moved, this, &NodeParamView::QueueKeyframePositionUpdate); + connect(item, &NodeParamViewItem::InputCheckedChanged, this, &NodeParamView::SetInputChecked); + + if (create_checkboxes_) { + for (auto it=input_checked_.cbegin(); it!=input_checked_.cend(); it++) { + if (it.key().node() == n) { + item->SetInputChecked(it.key(), it.value()); + } + } + } // Set time target item->SetTimeTarget(GetTimeTarget()); @@ -327,15 +367,19 @@ void NodeParamView::AddNode(Node *n) emit FocusedNodeChanged(focused_node_); } - keyframe_view_->AddKeyframesOfNode(n); + if (keyframe_view_) { + keyframe_view_->AddKeyframesOfNode(n); + } } void NodeParamView::RemoveNode(Node *n) { - keyframe_view_->RemoveKeyframesOfNode(n); + if (keyframe_view_) { + keyframe_view_->RemoveKeyframesOfNode(n); - disconnect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); - disconnect(n, &Node::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe); + disconnect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); + disconnect(n, &Node::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe); + } delete items_.take(n); @@ -358,8 +402,10 @@ void NodeParamView::UpdateGlobalScrollBar() { int height_offscreen = param_widget_container_->height() - ruler()->height() + scrollbar()->height(); - keyframe_view_->SetMaxScroll(height_offscreen); - vertical_scrollbar_->setRange(0, height_offscreen - keyframe_view_->height()); + if (keyframe_view_) { + keyframe_view_->SetMaxScroll(height_offscreen); + } + vertical_scrollbar_->setRange(0, height_offscreen - param_scroll_area_->height()); } void NodeParamView::PinNode(bool pin) @@ -390,18 +436,20 @@ void NodeParamView::FocusChanged(QWidget* old, QWidget* now) item = dynamic_cast(parent); if (item) { - // Found it! - if (item->GetNode() != focused_node_) { - if (focused_node_) { - // De-focus current node - items_.value(focused_node_)->SetHighlighted(false); + if (item->parent() == param_widget_area_) { + // Found it! + if (item->GetNode() != focused_node_) { + if (focused_node_) { + // De-focus current node + items_.value(focused_node_)->SetHighlighted(false); + } + + focused_node_ = item->GetNode(); + + item->SetHighlighted(true); + + emit FocusedNodeChanged(focused_node_); } - - focused_node_ = item->GetNode(); - - item->SetHighlighted(true); - - emit FocusedNodeChanged(focused_node_); } break; @@ -421,15 +469,17 @@ void NodeParamView::KeyframeViewDragged(int x, int y) void NodeParamView::UpdateElementY() { - for (auto it=items_.cbegin(); it!=items_.cend(); it++) { - foreach (const QString& input, it.key()->inputs()) { - int arr_sz = it.key()->InputArraySize(input); + if (keyframe_view_) { + for (auto it=items_.cbegin(); it!=items_.cend(); it++) { + foreach (const QString& input, it.key()->inputs()) { + int arr_sz = it.key()->InputArraySize(input); - for (int i=-1; iGetElementY(ic); - keyframe_view_->SetElementY(ic, y); + int y = it.value()->GetElementY(ic); + keyframe_view_->SetElementY(ic, y); + } } } } diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 1718f445e..677c4dce5 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -58,11 +58,25 @@ class NodeParamView : public TimeBasedWidget { Q_OBJECT public: - NodeParamView(QWidget* parent = nullptr); + NodeParamView(bool create_keyframe_view, QWidget* parent = nullptr); + NodeParamView(QWidget* parent = nullptr) : + NodeParamView(true, parent) + { + } void SelectNodes(const QVector &nodes); void DeselectNodes(const QVector& nodes); + void SetCreateCheckBoxes(NodeParamViewCheckBoxBehavior e) + { + create_checkboxes_ = e; + } + + bool IsInputChecked(const NodeInput &input) const + { + return input_checked_.value(input); + } + const QMap& GetItemMap() const { return items_; @@ -82,6 +96,9 @@ public: keyframe_view_->DeselectAll(); } +public slots: + void SetInputChecked(const NodeInput &input, bool e); + signals: void RequestSelectNode(const QVector& target); @@ -117,10 +134,10 @@ private: int last_scroll_val_; + QScrollArea* param_scroll_area_; + NodeParamViewParamContainer* param_widget_container_; - // This may look weird, but QMainWindow is just a QWidget with a fancy layout that allows - // docking windows NodeParamViewDockArea* param_widget_area_; QVector pinned_nodes_; @@ -131,6 +148,12 @@ private: Node* focused_node_; + NodeParamViewCheckBoxBehavior create_checkboxes_; + + Node *time_target_; + + QHash input_checked_; + private slots: void UpdateGlobalScrollBar(); diff --git a/app/widget/nodeparamview/nodeparamviewdockarea.h b/app/widget/nodeparamview/nodeparamviewdockarea.h index 90a00c1a0..c313236cd 100644 --- a/app/widget/nodeparamview/nodeparamviewdockarea.h +++ b/app/widget/nodeparamview/nodeparamviewdockarea.h @@ -25,6 +25,8 @@ namespace olive { +// This may look weird, but QMainWindow is just a QWidget with a fancy layout that allows +// for docking QDockWidgets class NodeParamViewDockArea : public QMainWindow { Q_OBJECT diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 3c2d5bbb0..3347551ef 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -38,12 +38,14 @@ const int NodeParamViewItemBody::kArrayInsertColumn = kKeyControlColumn-1; const int NodeParamViewItemBody::kArrayRemoveColumn = kArrayInsertColumn-1; const int NodeParamViewItemBody::kExtraButtonColumn = kKeyControlColumn-1; -// 0 is for the array collapse button, 1 is for the main label, widgets start at 2 -const int NodeParamViewItemBody::kWidgetStartColumn = 2; +const int NodeParamViewItemBody::kOptionalCheckBox = 0; +const int NodeParamViewItemBody::kArrayCollapseBtnColumn = 1; +const int NodeParamViewItemBody::kLabelColumn = 2; +const int NodeParamViewItemBody::kWidgetStartColumn = 3; #define super QDockWidget -NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) : +NodeParamViewItem::NodeParamViewItem(Node *node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget *parent) : super(parent), node_(node), highlighted_(false) @@ -55,10 +57,11 @@ NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) : this->setTitleBarWidget(title_bar_); // Create and add contents widget - body_ = new NodeParamViewItemBody(node_); + body_ = new NodeParamViewItemBody(node_, create_checkboxes); connect(body_, &NodeParamViewItemBody::RequestSelectNode, this, &NodeParamViewItem::RequestSelectNode); connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime); connect(body_, &NodeParamViewItemBody::ArrayExpandedChanged, this, &NodeParamViewItem::ArrayExpandedChanged); + connect(body_, &NodeParamViewItemBody::InputCheckedChanged, this, &NodeParamViewItem::InputCheckedChanged); connect(title_bar_, &NodeParamViewItemTitleBar::ExpandedStateChanged, this, &NodeParamViewItem::SetExpanded); connect(title_bar_, &NodeParamViewItemTitleBar::PinToggled, this, &NodeParamViewItem::PinToggled); @@ -165,6 +168,11 @@ int NodeParamViewItem::GetElementY(const NodeInput &c) const } } +void NodeParamViewItem::SetInputChecked(const NodeInput &input, bool e) +{ + body_->SetInputChecked(input, e); +} + void NodeParamViewItem::ToggleExpanded() { SetExpanded(!IsExpanded()); @@ -222,9 +230,10 @@ void NodeParamViewItemTitleBar::mouseDoubleClickEvent(QMouseEvent *event) collapse_btn_->click(); } -NodeParamViewItemBody::NodeParamViewItemBody(Node* node, QWidget *parent) : +NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget *parent) : QWidget(parent), - node_(node) + node_(node), + create_checkboxes_(create_checkboxes) { QGridLayout* root_layout = new QGridLayout(this); @@ -277,11 +286,22 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const ui_objects.layout = layout; ui_objects.row = row; + // Create optional checkbox if requested + if (create_checkboxes_) { + ui_objects.optional_checkbox = new QCheckBox(); + connect(ui_objects.optional_checkbox, &QCheckBox::clicked, this, &NodeParamViewItemBody::OptionalCheckBoxClicked); + layout->addWidget(ui_objects.optional_checkbox, row, kOptionalCheckBox); + + if (create_checkboxes_ == kCheckBoxesOnNonConnected && input_ref.IsConnected()) { + ui_objects.optional_checkbox->setVisible(false); + } + } + // Add descriptor label ui_objects.main_label = new QLabel(); - // Label always goes into column 1 (array collapse button goes into 0 if applicable) - layout->addWidget(ui_objects.main_label, row, 1); + // Create input label + layout->addWidget(ui_objects.main_label, row, kLabelColumn); if (node->InputIsArray(input)) { if (element == -1) { @@ -292,8 +312,8 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const // Default to collapsed array_collapse_btn->setChecked(false); - // Collapse button always goes into column 0 - layout->addWidget(array_collapse_btn, row, 0); + // Add collapse button to layout + layout->addWidget(array_collapse_btn, row, kArrayCollapseBtnColumn); // Connect signal to show/hide array params when toggled connect(array_collapse_btn, &CollapseButton::toggled, this, &NodeParamViewItemBody::ArrayCollapseBtnPressed); @@ -448,6 +468,11 @@ void NodeParamViewItemBody::UpdateUIForEdgeConnection(const NodeInput& input) if (ui_objects.key_control) { ui_objects.key_control->setVisible(!input.IsConnected()); } + + // Show/hide optional checkbox if requested + if (create_checkboxes_ == kCheckBoxesOnNonConnected) { + ui_objects.optional_checkbox->setVisible(!input.IsConnected()); + } } } @@ -575,6 +600,16 @@ void NodeParamViewItemBody::SetTimebase(const rational& timebase) } } +void NodeParamViewItemBody::SetInputChecked(const NodeInput &input, bool e) +{ + if (input_ui_map_.contains(input)) { + QCheckBox *cb = input_ui_map_.value(input).optional_checkbox; + if (cb) { + cb->setChecked(e); + } + } +} + void NodeParamViewItemBody::ReplaceWidgets(const NodeInput &input) { InputUI ui = input_ui_map_.value(input); @@ -588,12 +623,25 @@ void NodeParamViewItemBody::ShowSpeedDurationDialogForNode() sdd.exec(); } +void NodeParamViewItemBody::OptionalCheckBoxClicked(bool e) +{ + QCheckBox *cb = static_cast(sender()); + + for (auto it=input_ui_map_.cbegin(); it!=input_ui_map_.cend(); it++) { + if (it.value().optional_checkbox == cb) { + emit InputCheckedChanged(it.key(), e); + break; + } + } +} + NodeParamViewItemBody::InputUI::InputUI() : main_label(nullptr), widget_bridge(nullptr), connected_label(nullptr), key_control(nullptr), extra_btn(nullptr), + optional_checkbox(nullptr), array_insert_btn(nullptr), array_remove_btn(nullptr) { diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index bbd31c9f4..55a0a3174 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -21,6 +21,7 @@ #ifndef NODEPARAMVIEWITEM_H #define NODEPARAMVIEWITEM_H +#include #include #include #include @@ -38,6 +39,12 @@ namespace olive { +enum NodeParamViewCheckBoxBehavior { + kNoCheckBoxes, + kCheckBoxesOn, + kCheckBoxesOnNonConnected +}; + class NodeParamViewItemTitleBar : public QWidget { Q_OBJECT @@ -75,7 +82,7 @@ private: class NodeParamViewItemBody : public QWidget { Q_OBJECT public: - NodeParamViewItemBody(Node* node, QWidget* parent = nullptr); + NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget* parent = nullptr); void SetTimeTarget(Node* target); @@ -86,7 +93,9 @@ public: int GetElementY(NodeInput c) const; // Set the timebase of any timebased widgets contained here - void SetTimebase(const rational& timebase); + void SetTimebase(const rational& timebase); + + void SetInputChecked(const NodeInput &input, bool e); signals: void RequestSetTime(const rational& time); @@ -95,6 +104,8 @@ signals: void ArrayExpandedChanged(bool e); + void InputCheckedChanged(const NodeInput &input, bool e); + private: void CreateWidgets(QGridLayout *layout, Node* node, const QString& input, int element, int row_index); @@ -114,6 +125,7 @@ private: QGridLayout* layout; int row; QPushButton *extra_btn; + QCheckBox *optional_checkbox; NodeParamViewArrayButton* array_insert_btn; NodeParamViewArrayButton* array_remove_btn; @@ -135,6 +147,8 @@ private: rational timebase_; + NodeParamViewCheckBoxBehavior create_checkboxes_; + /** * @brief The column to place the keyframe controls in * @@ -147,6 +161,9 @@ private: static const int kArrayRemoveColumn; static const int kExtraButtonColumn; + static const int kOptionalCheckBox; + static const int kArrayCollapseBtnColumn; + static const int kLabelColumn; static const int kWidgetStartColumn; private slots: @@ -168,13 +185,15 @@ private slots: void ShowSpeedDurationDialogForNode(); + void OptionalCheckBoxClicked(bool e); + }; class NodeParamViewItem : public QDockWidget { Q_OBJECT public: - NodeParamViewItem(Node* node, QWidget* parent = nullptr); + NodeParamViewItem(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget* parent = nullptr); void SetTimeTarget(Node* target); @@ -196,6 +215,8 @@ public: int GetElementY(const NodeInput& c) const; + void SetInputChecked(const NodeInput &input, bool e); + public slots: void SetExpanded(bool e); @@ -214,6 +235,8 @@ signals: void Moved(); + void InputCheckedChanged(const NodeInput &input, bool e); + protected: virtual void changeEvent(QEvent *e) override; diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index e805d7eda..9b3619e0e 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -21,16 +21,18 @@ #include "nodeview.h" #include +#include #include #include #include #include "core.h" +#include "dialog/nodegroup/nodegroupdialog.h" #include "nodeviewundo.h" #include "node/audio/volume/volume.h" #include "node/distort/transform/transformdistortnode.h" #include "node/factory.h" -#include "node/group.h" +#include "node/group/group.h" #include "node/traverser.h" #include "widget/menu/menushared.h" #include "widget/timebased/timebasedview.h" @@ -638,6 +640,7 @@ void NodeView::UpdateSelectionCache() if (current_selection.isEmpty()) { // All nodes that were selected have been deselected, so we'll just set them all to `deselected` deselected = selected_nodes_; + selected_nodes_.clear(); } else { foreach (Node* n, selected_nodes_) { bool still_selected = false; @@ -649,20 +652,20 @@ void NodeView::UpdateSelectionCache() } } - if (still_selected) { + if (!still_selected) { deselected.append(n); selected_nodes_.removeOne(n); } } } - if (!selected.isEmpty()) { - emit NodesSelected(selected); - } - if (!deselected.isEmpty()) { emit NodesDeselected(deselected); } + + if (!selected.isEmpty()) { + emit NodesSelected(selected); + } } void NodeView::ShowContextMenu(const QPoint &pos) @@ -1082,13 +1085,88 @@ void NodeView::PositionNewEdge(const QPoint &pos) void NodeView::GroupNodes() { - /*NodeGroup *group = new NodeGroup(); - selected_nodes_*/ + // Get items + QVector items = scene_.GetSelectedItems(); + if (items.isEmpty()) { + return; + } + + // Get node context + Node *context = items.first()->GetContext(); + QPointF avg_pos = items.first()->GetNodePosition(); + for (int i=1; iGetContext() != context) { + QMessageBox::critical(this, tr("Failed to group nodes"), tr("Nodes can only be grouped if they're in the same context.")); + return; + } + + avg_pos += items.at(i)->GetNodePosition(); + } + avg_pos /= items.size(); + + // Create group + NodeGroup *group = new NodeGroup(); + + // Add group to graph and context + MultiUndoCommand *command = new MultiUndoCommand(); + + command->add_child(new NodeAddCommand(context->parent(), group)); + command->add_child(new NodeSetPositionCommand(group, context, avg_pos)); + + // Add nodes to group + QVector nodes_to_group = selected_nodes_; + DeselectAll(); + foreach (Node *n, nodes_to_group) { + for (auto it=n->input_connections().cbegin(); it!=n->input_connections().cend(); it++) { + Node *output = it->second; + const NodeInput &input = it->first; + + if (!nodes_to_group.contains(output)) { + command->add_child(new NodeEdgeRemoveCommand(output, input)); + command->add_child(new NodeEdgeAddCommand(output, NodeInput(group, input.input(), input.element()))); + } + } + + for (auto it=n->output_connections().cbegin(); it!=n->output_connections().cend(); it++) { + Node *output = it->first; + const NodeInput &input = it->second; + + if (!nodes_to_group.contains(input.node())) { + command->add_child(new NodeEdgeRemoveCommand(output, input)); + command->add_child(new NodeEdgeAddCommand(group, input)); + } + } + + command->add_child(new NodeRemovePositionFromContextCommand(n, context)); + command->add_child(new NodeAddToGroupCommand(n, group)); + command->add_child(new NodeSetPositionCommand(n, group, scene_.context_map().value(context)->GetItemFromMap(n)->GetNodePosition())); + + for (auto it=n->inputs().cbegin(); it!=n->inputs().cend(); it++) { + NodeInput input(n, *it, -1); + + if (!input.IsConnected() || !nodes_to_group.contains(input.GetConnectedOutput())) { + command->add_child(new NodeGroupAddInputPassthrough(group, input)); + } + } + } + + // Do command + command->redo_now(); + + NodeGroupDialog ngd(group, this); + if (ngd.exec() == QDialog::Accepted) { + // Push to stack so it can be undone (MultiUndoCommand will ignore the request to redo again) + Core::instance()->undo_stack()->push(command); + } else { + // Undo command and delete + command->undo_now(); + delete command; + } } void NodeView::UngroupNodes() { - //static_cast(selected_nodes_.first()); + //NodeGroup *group = static_cast(selected_nodes_.first()); } void NodeView::PasteNodesInternal(const QVector &duplicate_nodes) diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index fd5e2c0c9..5f7c802bb 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -45,7 +45,7 @@ class NodeView : public HandMovableView, public NodeCopyPasteService { Q_OBJECT public: - NodeView(QWidget* parent); + NodeView(QWidget* parent = nullptr); virtual ~NodeView() override; diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index b608ee58b..c2c516c60 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -189,7 +189,9 @@ void NodeViewContext::DeleteSelected(NodeViewDeleteCommand *command) void NodeViewContext::Select(const QVector &nodes) { foreach (Node *n, nodes) { - item_map_.value(n)->setSelected(true); + if (NodeViewItem *item = item_map_.value(n)) { + item->setSelected(true); + } } } diff --git a/app/widget/nodeview/nodeviewcontext.h b/app/widget/nodeview/nodeviewcontext.h index cf49b332d..7cdc638a7 100644 --- a/app/widget/nodeview/nodeviewcontext.h +++ b/app/widget/nodeview/nodeviewcontext.h @@ -36,6 +36,11 @@ public: QPointF MapScenePosToNodePosInContext(const QPointF &pos) const; + NodeViewItem *GetItemFromMap(Node *node) const + { + return item_map_.value(node); + } + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; public slots: diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 3ea29ff5a..1a9b6c516 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -544,7 +544,7 @@ QPointF NodeViewItem::GetInputPoint(const QString &input, int element) const int index = node_inputs_.indexOf(input); if (index < 0 || index >= int(input_connectors_.size())) { - return QPointF(); + return pos(); } return input_connectors_[index]->scenePos(); diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 47c23c3cb..db245f005 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -467,15 +467,6 @@ void MainWindow::TimelinePanelSelectionChanged(const QVector &blocks) } } -void MainWindow::ProjectPanelSelectionChanged(const QVector &nodes) -{ - ProjectPanel *panel = static_cast(sender()); - - if (PanelManager::instance()->CurrentlyFocused(false) == panel) { - node_panel_->Select(nodes, true); - } -} - void MainWindow::ShowWelcomeDialog() { if (Config::Current()[QStringLiteral("ShowWelcomeDialog")].toBool()) { @@ -572,7 +563,6 @@ ProjectPanel *MainWindow::AppendProjectPanel() connect(panel, &PanelWidget::CloseRequested, this, &MainWindow::ProjectCloseRequested); connect(panel, &ProjectPanel::ProjectNameChanged, this, &MainWindow::UpdateTitle); - connect(panel, &ProjectPanel::SelectionChanged, this, &MainWindow::ProjectPanelSelectionChanged); return panel; } diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index f8cf36d98..719652200 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -193,8 +193,6 @@ private slots: void TimelinePanelSelectionChanged(const QVector &blocks); - void ProjectPanelSelectionChanged(const QVector &nodes); - void ShowWelcomeDialog(); }; From 239a019690bdfd807181ed69a39ac522cf3a40e3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 29 Nov 2021 21:22:49 -0800 Subject: [PATCH 12/34] many improvements and reimplementations --- app/dialog/nodegroup/nodegroupdialog.cpp | 2 + app/node/graph.cpp | 16 + app/node/graph.h | 7 +- app/node/group/group.cpp | 52 +- app/node/group/group.h | 83 ++- app/node/node.cpp | 67 ++- app/node/node.h | 40 +- app/node/param.cpp | 9 + app/node/param.h | 12 + app/widget/nodeview/nodeview.cpp | 268 ++++++---- app/widget/nodeview/nodeview.h | 10 +- app/widget/nodeview/nodeviewcommon.h | 1 + app/widget/nodeview/nodeviewcontext.cpp | 116 ++-- app/widget/nodeview/nodeviewcontext.h | 9 +- app/widget/nodeview/nodeviewedge.cpp | 64 ++- app/widget/nodeview/nodeviewedge.h | 14 +- app/widget/nodeview/nodeviewitem.cpp | 503 ++++++++++-------- app/widget/nodeview/nodeviewitem.h | 90 ++-- app/widget/nodeview/nodeviewitemconnector.cpp | 2 + .../undo/timelineundogeneral.cpp | 2 +- app/window/mainwindow/mainwindow.cpp | 2 +- 21 files changed, 874 insertions(+), 495 deletions(-) diff --git a/app/dialog/nodegroup/nodegroupdialog.cpp b/app/dialog/nodegroup/nodegroupdialog.cpp index 2d07a4338..9fd1fa4af 100644 --- a/app/dialog/nodegroup/nodegroupdialog.cpp +++ b/app/dialog/nodegroup/nodegroupdialog.cpp @@ -60,6 +60,8 @@ NodeGroupDialog::NodeGroupDialog(NodeGroup *group, QWidget *parent) : QMetaObject::invokeMethod(node_view, &NodeView::CenterOnItemsBoundingRect, Qt::QueuedConnection); splitter->addWidget(node_view); + splitter->setSizes({splitter->width() / 4, splitter->width() / 4 * 3}); + for (auto it=group->GetInputPassthroughs().cbegin(); it!=group->GetInputPassthroughs().cend(); it++) { param_view->SetInputChecked(it.value(), true); } diff --git a/app/node/graph.cpp b/app/node/graph.cpp index bdae43570..024cebf98 100644 --- a/app/node/graph.cpp +++ b/app/node/graph.cpp @@ -74,6 +74,12 @@ void NodeGraph::childEvent(QChildEvent *event) connect(node, &Node::ValueChanged, this, &NodeGraph::ValueChanged, Qt::DirectConnection); connect(node, &Node::InputValueHintChanged, this, &NodeGraph::InputValueHintChanged, Qt::DirectConnection); + if (NodeGroup *group = dynamic_cast(node)) { + connect(group, &NodeGroup::InputPassthroughAdded, this, &NodeGraph::GroupAddedInputPassthrough, Qt::DirectConnection); + connect(group, &NodeGroup::InputPassthroughRemoved, this, &NodeGraph::GroupRemovedInputPassthrough, Qt::DirectConnection); + connect(group, &NodeGroup::OutputPassthroughChanged, this, &NodeGraph::GroupChangedOutputPassthrough, Qt::DirectConnection); + } + emit NodeAdded(node); emit node->AddedToGraph(this); @@ -87,12 +93,22 @@ void NodeGraph::childEvent(QChildEvent *event) disconnect(node, &Node::ValueChanged, this, &NodeGraph::ValueChanged); disconnect(node, &Node::InputValueHintChanged, this, &NodeGraph::InputValueHintChanged); + if (NodeGroup *group = dynamic_cast(node)) { + disconnect(group, &NodeGroup::InputPassthroughAdded, this, &NodeGraph::GroupAddedInputPassthrough); + disconnect(group, &NodeGroup::InputPassthroughRemoved, this, &NodeGraph::GroupRemovedInputPassthrough); + disconnect(group, &NodeGroup::OutputPassthroughChanged, this, &NodeGraph::GroupChangedOutputPassthrough); + } + emit NodeRemoved(node); emit node->RemovedFromGraph(this); // Remove from any contexts foreach (Node *context, node_children_) { context->RemoveNodeFromContext(node); + + if (NodeGroup *group = dynamic_cast(context)) { + group->RemoveNode(node); + } } } } diff --git a/app/node/graph.h b/app/node/graph.h index fdf923b56..443e20748 100644 --- a/app/node/graph.h +++ b/app/node/graph.h @@ -21,6 +21,7 @@ #ifndef NODEGRAPH_H #define NODEGRAPH_H +#include "node/group/group.h" #include "node/node.h" namespace olive { @@ -84,9 +85,11 @@ signals: void InputValueHintChanged(const NodeInput& input); - void NodePositionAdded(Node *node, Node *relative, const QPointF &position); + void GroupAddedInputPassthrough(NodeGroup *group, const NodeInput &input); - void NodePositionRemoved(Node *node, Node *relative); + void GroupRemovedInputPassthrough(NodeGroup *group, const NodeInput &input); + + void GroupChangedOutputPassthrough(NodeGroup *group, Node *output); protected: void AddDefaultNode(Node* n) diff --git a/app/node/group/group.cpp b/app/node/group/group.cpp index fbe785bd9..f8e44790f 100644 --- a/app/node/group/group.cpp +++ b/app/node/group/group.cpp @@ -27,8 +27,6 @@ namespace olive { NodeGroup::NodeGroup() : output_passthrough_(nullptr) { - graph_ = new NodeGraph(); - graph_->setParent(this); } QString NodeGroup::Name() const @@ -57,26 +55,28 @@ QString NodeGroup::Description() const void NodeGroup::Retranslate() { - foreach (Node *n, graph_->nodes()) { + foreach (Node *n, nodes_) { n->Retranslate(); } } void NodeGroup::AddNode(Node *node) { - node->setParent(graph_); + nodes_.append(node); + + emit NodeAddedToGroup(node); } -void NodeGroup::RemoveNode(Node *node, QObject *new_parent) +void NodeGroup::RemoveNode(Node *node) { - if (node->parent() == graph_) { - node->setParent(new_parent); + if (nodes_.removeOne(node)) { + emit NodeRemovedFromGroup(node); } } void NodeGroup::AddInputPassthrough(const NodeInput &input) { - Q_ASSERT(graph_->nodes().contains(input.node())); + Q_ASSERT(nodes_.contains(input.node())); for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) { if (it.value() == input) { @@ -91,6 +91,8 @@ void NodeGroup::AddInputPassthrough(const NodeInput &input) AddInput(id, input.GetDataType(), input.GetDefaultValue(), input.GetFlags()); input_passthroughs_.insert(id, input); + + emit InputPassthroughAdded(this, input); } void NodeGroup::RemoveInputPassthrough(const NodeInput &input) @@ -99,6 +101,7 @@ void NodeGroup::RemoveInputPassthrough(const NodeInput &input) if (it.value() == input) { RemoveInput(it.key()); input_passthroughs_.erase(it); + emit InputPassthroughRemoved(this, it.value()); break; } } @@ -106,9 +109,11 @@ void NodeGroup::RemoveInputPassthrough(const NodeInput &input) void NodeGroup::SetOutputPassthrough(Node *node) { - Q_ASSERT(graph_->nodes().contains(node)); + Q_ASSERT(!node || nodes_.contains(node)); output_passthrough_ = node; + + emit OutputPassthroughChanged(this, output_passthrough_); } QString NodeGroup::GetGroupInputIDFromInput(const NodeInput &input) @@ -135,15 +140,19 @@ bool NodeGroup::ContainsInputPassthrough(const NodeInput &input) const return false; } +QString NodeGroup::GetInputName(const QString &id) const +{ + return input_passthroughs_.value(id).name(); +} + void NodeAddToGroupCommand::redo() { - previous_parent_ = node_->parent(); group_->AddNode(node_); } void NodeAddToGroupCommand::undo() { - group_->RemoveNode(node_, previous_parent_); + group_->RemoveNode(node_); } void NodeGroupSetCustomNameCommand::redo() @@ -174,4 +183,25 @@ void NodeGroupAddInputPassthrough::undo() } } +void NodeRemoveFromGroupCommand::redo() +{ + group_->RemoveNode(node_); +} + +void NodeRemoveFromGroupCommand::undo() +{ + group_->AddNode(node_); +} + +void NodeGroupSetOutputPassthrough::redo() +{ + old_output_ = group_->GetOutputPassthrough(); + group_->SetOutputPassthrough(new_output_); +} + +void NodeGroupSetOutputPassthrough::undo() +{ + group_->SetOutputPassthrough(old_output_); +} + } diff --git a/app/node/group/group.h b/app/node/group/group.h index a0aa9cee3..31e07ab07 100644 --- a/app/node/group/group.h +++ b/app/node/group/group.h @@ -43,12 +43,27 @@ public: void AddNode(Node *node); - void RemoveNode(Node *node, QObject *new_parent = nullptr); + void RemoveNode(Node *node); + + bool ContainsNode(Node *node) const + { + return nodes_.contains(node); + } + + const QVector &GetNodes() const + { + return nodes_; + } void AddInputPassthrough(const NodeInput &input); void RemoveInputPassthrough(const NodeInput &input); + Node *GetOutputPassthrough() const + { + return output_passthrough_; + } + void SetOutputPassthrough(Node *node); const QString &GetCustomName() const @@ -78,8 +93,21 @@ public: bool ContainsInputPassthrough(const NodeInput &input) const; + virtual QString GetInputName(const QString& id) const override; + +signals: + void NodeAddedToGroup(Node *node); + + void NodeRemovedFromGroup(Node *node); + + void InputPassthroughAdded(NodeGroup *group, const NodeInput &input); + + void InputPassthroughRemoved(NodeGroup *group, const NodeInput &input); + + void OutputPassthroughChanged(NodeGroup *group, Node *output); + private: - NodeGraph *graph_; + QVector nodes_; QHash input_passthroughs_; @@ -112,7 +140,30 @@ private: NodeGroup *group_; - QObject *previous_parent_; +}; + +class NodeRemoveFromGroupCommand : public UndoCommand +{ +public: + NodeRemoveFromGroupCommand(Node *node, NodeGroup *group) : + node_(node), + group_(group) + {} + + virtual Project * GetRelevantProject() const override + { + return node_->project(); + } + +protected: + virtual void redo() override; + + virtual void undo() override; + +private: + Node *node_; + + NodeGroup *group_; }; @@ -171,6 +222,32 @@ private: }; +class NodeGroupSetOutputPassthrough : public UndoCommand +{ +public: + NodeGroupSetOutputPassthrough(NodeGroup *group, Node *output) : + group_(group), + new_output_(output) + {} + + virtual Project * GetRelevantProject() const override + { + return group_->project(); + } + +protected: + virtual void redo() override; + + virtual void undo() override; + +private: + NodeGroup *group_; + + Node *new_output_; + Node *old_output_; + +}; + } #endif // NODEGROUP_H diff --git a/app/node/node.cpp b/app/node/node.cpp index 1d0316e8b..4e850e821 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -1610,7 +1610,9 @@ void Node::CopyValuesOfElement(const Node *src, Node *dst, const QString &input, dst->SetSplitStandardValue(input, src->GetSplitStandardValue(input, src_element), dst_element); // Copy keyframes - dst->GetImmediate(input, dst_element)->delete_all_keyframes(); + if (NodeInputImmediate *immediate = dst->GetImmediate(input, dst_element)) { + immediate->delete_all_keyframes(); + } foreach (const NodeKeyframeTrack& track, src->GetImmediate(input, src_element)->keyframe_tracks()) { foreach (NodeKeyframe* key, track) { key->copy(dst_element, dst); @@ -2366,15 +2368,13 @@ Project *Node::ArrayResizeCommand::GetRelevantProject() const void NodeSetPositionCommand::redo() { - if (!(added_ = !context_->ContextContainsNode(node_))) { + added_ = !context_->ContextContainsNode(node_); + + if (!added_) { old_pos_ = context_->GetNodePositionDataInContext(node_); } - if (added_) { - context_->SetNodePositionInContext(node_, pos_); - } else { - move(context_, node_, pos_.position - old_pos_.position, move_deps_); - } + context_->SetNodePositionInContext(node_, pos_); } void NodeSetPositionCommand::undo() @@ -2382,23 +2382,7 @@ void NodeSetPositionCommand::undo() if (added_) { context_->RemoveNodeFromContext(node_); } else { - move(context_, node_, old_pos_.position - pos_.position, move_deps_); - } -} - -void NodeSetPositionCommand::move(Node *context, Node *node, const QPointF &diff, bool recursive) -{ - QPointF p = context->GetNodePositionInContext(node); - p += diff; - context->SetNodePositionInContext(node, p); - - if (recursive) { - for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - Node *output = it->second; - if (context->ContextContainsNode(output)) { - move(context, output, diff, recursive); - } - } + context_->SetNodePositionInContext(node_, old_pos_); } } @@ -2407,7 +2391,7 @@ void NodeRemovePositionFromContextCommand::redo() contained_ = context_->ContextContainsNode(node_); if (contained_) { - old_pos_ = context_->GetNodePositionInContext(node_); + old_pos_ = context_->GetNodePositionDataInContext(node_); context_->RemoveNodeFromContext(node_); } } @@ -2486,4 +2470,37 @@ void Node::ValueHint::Save(QXmlStreamWriter *writer) const writer->writeTextElement(QStringLiteral("tag"), tag_); } +void NodeSetPositionAndDependenciesRecursivelyCommand::prepare() +{ + move_recursively(node_, pos_.position - context_->GetNodePositionDataInContext(node_).position); +} + +void NodeSetPositionAndDependenciesRecursivelyCommand::redo() +{ + for (auto it=commands_.cbegin(); it!=commands_.cend(); it++) { + (*it)->redo_now(); + } +} + +void NodeSetPositionAndDependenciesRecursivelyCommand::undo() +{ + for (auto it=commands_.crbegin(); it!=commands_.crend(); it++) { + (*it)->undo_now(); + } +} + +void NodeSetPositionAndDependenciesRecursivelyCommand::move_recursively(Node *node, const QPointF &diff) +{ + Node::Position pos = context_->GetNodePositionDataInContext(node); + pos += diff; + commands_.append(new NodeSetPositionCommand(node_, context_, pos)); + + for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { + Node *output = it->second; + if (context_->ContextContainsNode(output)) { + move_recursively(output, diff); + } + } +} + } diff --git a/app/node/node.h b/app/node/node.h index 3a0fa5130..f38835ce9 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -320,7 +320,7 @@ public: static void DisconnectEdge(Node *output, const NodeInput& input); - QString GetInputName(const QString& id) const; + virtual QString GetInputName(const QString& id) const; void LoadInput(QXmlStreamReader* reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled); void SaveInput(QXmlStreamWriter* writer, const QString& id) const; @@ -1420,12 +1420,11 @@ using NodePtr = std::shared_ptr; class NodeSetPositionCommand : public UndoCommand { public: - NodeSetPositionCommand(Node* node, Node* context, const Node::Position& pos, bool move_dependencies_relatively = false) + NodeSetPositionCommand(Node* node, Node* context, const Node::Position& pos) { node_ = node; context_ = context; pos_ = pos; - move_deps_ = move_dependencies_relatively; } virtual Project* GetRelevantProject() const override @@ -1439,14 +1438,41 @@ protected: virtual void undo() override; private: - static void move(Node *context, Node *node, const QPointF &diff, bool recursive); - Node* node_; Node* context_; Node::Position pos_; Node::Position old_pos_; bool added_; - bool move_deps_; + +}; + +class NodeSetPositionAndDependenciesRecursivelyCommand : public UndoCommand{ +public: + NodeSetPositionAndDependenciesRecursivelyCommand(Node* node, Node* context, const Node::Position& pos) : + node_(node), + context_(context), + pos_(pos) + {} + + virtual Project* GetRelevantProject() const override + { + return node_->project(); + } + +protected: + virtual void prepare() override; + + virtual void redo() override; + + virtual void undo() override; + +private: + void move_recursively(Node *node, const QPointF &diff); + + Node* node_; + Node* context_; + Node::Position pos_; + QVector commands_; }; @@ -1474,7 +1500,7 @@ private: Node *context_; - QPointF old_pos_; + Node::Position old_pos_; bool contained_; diff --git a/app/node/param.cpp b/app/node/param.cpp index 2e579f613..d7676c615 100644 --- a/app/node/param.cpp +++ b/app/node/param.cpp @@ -150,6 +150,15 @@ QVariant NodeInput::GetSplitDefaultValueForTrack(int track) const } } +int NodeInput::GetArraySize() const +{ + if (IsValid() && element_ == -1) { + return node_->InputArraySize(input_); + } else { + return 0; + } +} + uint qHash(const NodeInput &i) { return qHash(i.node()) ^ qHash(i.input()) ^ qHash(i.element()); diff --git a/app/node/param.h b/app/node/param.h index 2e2276187..77f8e63f6 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -134,6 +134,16 @@ public: return element_; } + void set_node(Node *node) + { + node_ = node; + } + + void set_input(const QString &input) + { + input_ = input; + } + void set_element(int e) { element_ = e; @@ -172,6 +182,8 @@ public: QVariant GetSplitDefaultValueForTrack(int track) const; + int GetArraySize() const; + void Reset() { *this = NodeInput(); diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 9b3619e0e..2dab4cb29 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -49,10 +49,9 @@ NodeView::NodeView(QWidget *parent) : create_edge_(nullptr), create_edge_output_item_(nullptr), create_edge_input_item_(nullptr), - create_edge_dst_temp_expanded_(false), + create_edge_expand_item_(nullptr), paste_command_(nullptr), - scale_(1.0), - first_show_(true) + scale_(1.0) { setScene(&scene_); SetDefaultDragMode(RubberBandDrag); @@ -311,46 +310,55 @@ void NodeView::mousePressEvent(QMouseEvent *event) QGraphicsItem* item = itemAt(event->pos()); if (event->button() == Qt::LeftButton) { - // Determine if user clicked on a connector - NodeViewItemConnector *connector = dynamic_cast(item); + // Sane defaults + create_edge_output_item_ = nullptr; + create_edge_input_item_ = nullptr; + create_edge_already_exists_ = false; + create_edge_from_output_ = true; - // If the user clicked on a connector OR the user is holding Ctrl - if (connector || (event->modifiers() & Qt::ControlModifier)) { - // Get the relevant item, either the one attached to the connector or the item if Ctrl+Clicked - NodeViewItem *attached_item = connector ? static_cast(connector->parentItem()) : dynamic_cast(item); - - if (attached_item) { - if (connector && !connector->IsOutput() && (create_edge_ = attached_item->GetEdgeFromInputConnector(connector))) { - // Since inputs can only have one edge connected, we grab the existing edge, if one exists - create_edge_output_item_ = create_edge_->from_item(); - create_edge_already_exists_ = true; - create_edge_from_output_ = true; - } else { - // Create a new edge from this output - create_edge_ = new NodeViewEdge(); - create_edge_->SetCurved(scene_.GetEdgesAreCurved()); - create_edge_->SetFlowDirection(scene_.GetFlowDirection()); - - // Set source and declare that we created this edge - if ((create_edge_from_output_ = (!connector || connector->IsOutput()))) { - // Edge is being created from output - create_edge_output_item_ = attached_item; - } else { - // Edge is being created from input - create_edge_input_item_ = attached_item; - create_edge_input_ = attached_item->GetInputFromInputConnector(connector); - } - create_edge_already_exists_ = false; - - // Add edge to scene - scene_.addItem(create_edge_); - - // Position edge to mouse cursor - PositionNewEdge(event->pos()); - } - return; + if (event->modifiers() & Qt::ControlModifier) { + create_edge_output_item_ = dynamic_cast(item); + if (create_edge_output_item_ && !create_edge_output_item_->IsOutputItem()) { + create_edge_output_item_ = nullptr; } } + + if (!create_edge_output_item_) { + // Determine if user clicked on a connector + if (NodeViewItemConnector *connector = dynamic_cast(item)) { + NodeViewItem *attached = static_cast(connector->parentItem()); + + if (connector->IsOutput()) { + create_edge_output_item_ = attached; + } else { + create_edge_input_item_ = attached; + + if (!create_edge_input_item_->edges().isEmpty()) { + // Drag existing edge instead + create_edge_ = create_edge_input_item_->edges().first(); + create_edge_input_item_ = nullptr; + create_edge_output_item_ = create_edge_->from_item(); + create_edge_already_exists_ = true; + } else { + create_edge_from_output_ = false; + create_edge_input_ = create_edge_input_item_->GetInput(); + } + } + } + } + + if ((create_edge_output_item_ || create_edge_input_item_) && !create_edge_already_exists_) { + // Create a new edge from this output + create_edge_ = new NodeViewEdge(); + create_edge_->SetCurved(scene_.GetEdgesAreCurved()); + + // Add edge to scene + scene_.addItem(create_edge_); + + // Position edge to mouse cursor + PositionNewEdge(event->pos()); + return; + } } // Handle selections with the right mouse button @@ -490,12 +498,12 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) if (create_edge_output_item_ && create_edge_input_item_) { // Clear highlight if we set one - create_edge_input_item_->SetHighlightedIndex(-1); + create_edge_input_item_->SetHighlighted(false); // Collapse if we expanded it - if (create_edge_dst_temp_expanded_) { - create_edge_input_item_->SetExpanded(false); - create_edge_input_item_->setZValue(0); + if (create_edge_expand_item_) { + create_edge_expand_item_->SetExpanded(false); + create_edge_expand_item_->setZValue(0); } NodeInput &creating_input = create_edge_input_; @@ -504,17 +512,20 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) if (!reconnected_to_itself) { Node *creating_output = create_edge_output_item_->GetNode(); + if (NodeGroup *output_group = dynamic_cast(creating_output)) { + creating_output = output_group->GetOutputPassthrough(); + } + + if (NodeGroup *input_group = dynamic_cast(creating_input.node())) { + creating_input = input_group->GetInputPassthroughs().value(creating_input.input()); + } + if (creating_input.IsConnected()) { Node::OutputConnection existing_edge_to_remove = {creating_input.GetConnectedOutput(), creating_input}; command->add_child(new NodeEdgeRemoveCommand(existing_edge_to_remove.first, existing_edge_to_remove.second)); } command->add_child(new NodeEdgeAddCommand(creating_output, creating_input)); - - // If the output is not in the input's context, add it now - if (!create_edge_input_item_->GetContext()->ContextContainsNode(creating_output)) { - command->add_child(new NodeSetPositionCommand(creating_output, create_edge_input_item_->GetContext(), scene_.context_map().value(create_edge_input_item_->GetContext())->MapScenePosToNodePosInContext(create_edge_output_item_->scenePos()))); - } } creating_input.Reset(); @@ -686,9 +697,7 @@ void NodeView::ShowContextMenu(const QPoint &pos) // Label node action QAction* label_action = m.addAction(tr("Label")); - connect(label_action, &QAction::triggered, this, [this](){ - Core::instance()->LabelNodes(selected_nodes_); - }); + connect(label_action, &QAction::triggered, this, &NodeView::LabelSelectedNodes); // Grouping if (selected.size() == 1 && dynamic_cast(selected.first()->GetNode())) { @@ -702,13 +711,19 @@ void NodeView::ShowContextMenu(const QPoint &pos) // Color menu MenuShared::instance()->AddColorCodingMenu(&m); - ViewerOutput* viewer = dynamic_cast(selected.first()->GetNode()); - if (viewer) { + // Show in Viewer option for nodes based on Viewer + if (ViewerOutput* viewer = dynamic_cast(selected.first()->GetNode())) { m.addSeparator(); QAction* open_in_viewer_action = m.addAction(tr("Open in Viewer")); connect(open_in_viewer_action, &QAction::triggered, this, &NodeView::OpenSelectedNodeInViewer); } + m.addSeparator(); + + // Properties + QAction *properties_action = m.addAction(tr("P&roperties")); + connect(properties_action, &QAction::triggered, this, &NodeView::ShowNodeProperties); + } else { QAction* curved_action = m.addAction(tr("Smooth Edges")); @@ -988,6 +1003,13 @@ void NodeView::PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNode }*/ } +void NodeView::changeEvent(QEvent *e) +{ + // Add translation code + + super::changeEvent(e); +} + void NodeView::ZoomFromKeyboard(double multiplier) { QPoint cursor_pos = mapFromGlobal(QCursor::pos()); @@ -1029,10 +1051,11 @@ void NodeView::PositionNewEdge(const QPoint &pos) item_at_cursor = nullptr; } - // Filter out connecting to a node that connects to us + // Filter out connecting to a node that connects to us or an item of the same type if (item_at_cursor && ((create_edge_from_output_ && item_at_cursor->GetNode()->OutputsTo(source_item->GetNode(), true)) - || (!create_edge_from_output_ && item_at_cursor->GetNode()->InputsFrom(source_item->GetNode(), true)))) { + || (!create_edge_from_output_ && item_at_cursor->GetNode()->InputsFrom(source_item->GetNode(), true)) + || (create_edge_from_output_ == item_at_cursor->IsOutputItem()))) { item_at_cursor = nullptr; } @@ -1040,46 +1063,32 @@ void NodeView::PositionNewEdge(const QPoint &pos) if (item_at_cursor != opposing_item) { // If we had a destination active, disconnect from it since the item has changed if (opposing_item) { - opposing_item->SetHighlightedIndex(-1); + opposing_item->SetHighlighted(false); + opposing_item = nullptr; + } - if (create_edge_dst_temp_expanded_) { - // We expanded this item, so we can un-expand it - opposing_item->SetExpanded(false); - opposing_item->setZValue(0); + // Clear cached input + if (create_edge_from_output_) { + if (create_edge_input_.IsValid()) { + create_edge_input_.Reset(); } } - // Set destination + // If this is an input and we're opposing_item = item_at_cursor; - // If our destination is an item, ensure it's expanded if (opposing_item) { - if (create_edge_from_output_ && (create_edge_dst_temp_expanded_ = (!create_edge_input_item_->IsExpanded()))) { - create_edge_input_item_->SetExpanded(true, true); - create_edge_input_item_->setZValue(100); // Ensure item is in front + opposing_item->SetHighlighted(true); + if (!opposing_item->IsOutputItem()) { + create_edge_input_ = opposing_item->GetInput(); } } } - // If we have a destination, highlight the appropriate input - if (create_edge_from_output_) { - int highlight_index = -1; - if (create_edge_input_item_) { - highlight_index = create_edge_input_item_->GetIndexAt(scene_pt); - create_edge_input_item_->SetHighlightedIndex(highlight_index); - } - - if (highlight_index >= 0) { - create_edge_input_ = create_edge_input_item_->GetInputAtIndex(highlight_index); - } else { - create_edge_input_.Reset(); - } - } - QPointF output_point = create_edge_output_item_ ? create_edge_output_item_->GetOutputPoint() : scene_pt; - QPointF input_point = create_edge_input_.IsValid() ? create_edge_input_item_->GetInputPoint(create_edge_input_.input(), create_edge_input_.element()) : scene_pt; + QPointF input_point = create_edge_input_.IsValid() ? create_edge_input_item_->GetInputPoint() : scene_pt; - create_edge_->SetPoints(output_point, input_point, create_edge_input_item_ && create_edge_input_item_->IsExpanded()); + create_edge_->SetPoints(output_point, input_point); create_edge_->SetConnected(create_edge_output_item_ && create_edge_input_.IsValid()); } @@ -1110,36 +1119,14 @@ void NodeView::GroupNodes() // Add group to graph and context MultiUndoCommand *command = new MultiUndoCommand(); - command->add_child(new NodeAddCommand(context->parent(), group)); - command->add_child(new NodeSetPositionCommand(group, context, avg_pos)); - // Add nodes to group + Node *output_passthrough = nullptr; QVector nodes_to_group = selected_nodes_; DeselectAll(); foreach (Node *n, nodes_to_group) { - for (auto it=n->input_connections().cbegin(); it!=n->input_connections().cend(); it++) { - Node *output = it->second; - const NodeInput &input = it->first; - - if (!nodes_to_group.contains(output)) { - command->add_child(new NodeEdgeRemoveCommand(output, input)); - command->add_child(new NodeEdgeAddCommand(output, NodeInput(group, input.input(), input.element()))); - } - } - - for (auto it=n->output_connections().cbegin(); it!=n->output_connections().cend(); it++) { - Node *output = it->first; - const NodeInput &input = it->second; - - if (!nodes_to_group.contains(input.node())) { - command->add_child(new NodeEdgeRemoveCommand(output, input)); - command->add_child(new NodeEdgeAddCommand(group, input)); - } - } - command->add_child(new NodeRemovePositionFromContextCommand(n, context)); command->add_child(new NodeAddToGroupCommand(n, group)); - command->add_child(new NodeSetPositionCommand(n, group, scene_.context_map().value(context)->GetItemFromMap(n)->GetNodePosition())); + command->add_child(new NodeSetPositionCommand(n, group, context->GetNodePositionDataInContext(n))); for (auto it=n->inputs().cbegin(); it!=n->inputs().cend(); it++) { NodeInput input(n, *it, -1); @@ -1148,8 +1135,25 @@ void NodeView::GroupNodes() command->add_child(new NodeGroupAddInputPassthrough(group, input)); } } + + if (!output_passthrough) { + // Default to the first node we find that doesn't output to a node inside the group + foreach (Node *potential_in, nodes_to_group) { + if (potential_in != n && !n->OutputsTo(potential_in, false)) { + output_passthrough = n; + break; + } + } + } } + // Set output passthrough + command->add_child(new NodeGroupSetOutputPassthrough(group, output_passthrough)); + + // Add group to graph + command->add_child(new NodeAddCommand(context->parent(), group)); + command->add_child(new NodeSetPositionCommand(group, context, avg_pos)); + // Do command command->redo_now(); @@ -1166,7 +1170,55 @@ void NodeView::GroupNodes() void NodeView::UngroupNodes() { - //NodeGroup *group = static_cast(selected_nodes_.first()); + NodeViewItem *group_item = nullptr; + QVector items = scene_.GetSelectedItems(); + if (items.isEmpty()) { + return; + } + + NodeGroup *group; + foreach (NodeViewItem *i, items) { + if ((group = dynamic_cast(i->GetNode()))) { + group_item = i; + break; + } + } + + if (!group_item) { + return; + } + + MultiUndoCommand *command = new MultiUndoCommand(); + + Node *context = group_item->GetContext(); + + command->add_child(new NodeRemovePositionFromContextCommand(group, context)); + command->add_child(new NodeRemoveAndDisconnectCommand(group)); + + foreach (Node *n, group->GetNodes()) { + command->add_child(new NodeRemovePositionFromContextCommand(n, group)); + command->add_child(new NodeRemoveFromGroupCommand(n, group)); + command->add_child(new NodeSetPositionCommand(n, context, group->GetNodePositionDataInContext(n))); + } + + Core::instance()->undo_stack()->push(command); +} + +void NodeView::ShowNodeProperties() +{ + Node *first_node = selected_nodes_.first(); + + if (NodeGroup *group = dynamic_cast(first_node)) { + NodeGroupDialog ngd(group, this); + ngd.exec(); + } else { + LabelSelectedNodes(); + } +} + +void NodeView::LabelSelectedNodes() +{ + Core::instance()->LabelNodes(selected_nodes_); } void NodeView::PasteNodesInternal(const QVector &duplicate_nodes) diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 5f7c802bb..4b3a0417f 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -119,6 +119,8 @@ protected: virtual void CopyNodesToClipboardInternal(QXmlStreamWriter *writer, const QVector &nodes, void* userdata) override; virtual void PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data, void* userdata) override; + virtual void changeEvent(QEvent *e) override; + private: void AttachItemsToCursor(const QVector &items); @@ -162,8 +164,8 @@ private: NodeViewEdge* create_edge_; NodeViewItem* create_edge_output_item_; NodeViewItem* create_edge_input_item_; + NodeViewItem *create_edge_expand_item_; NodeInput create_edge_input_; - bool create_edge_dst_temp_expanded_; bool create_edge_already_exists_; bool create_edge_from_output_; @@ -181,8 +183,6 @@ private: double scale_; - bool first_show_; - static const double kMinimumScale; private slots: @@ -225,6 +225,10 @@ private slots: void UngroupNodes(); + void ShowNodeProperties(); + + void LabelSelectedNodes(); + }; } diff --git a/app/widget/nodeview/nodeviewcommon.h b/app/widget/nodeview/nodeviewcommon.h index d0541fba4..f8ffa4583 100644 --- a/app/widget/nodeview/nodeviewcommon.h +++ b/app/widget/nodeview/nodeviewcommon.h @@ -30,6 +30,7 @@ namespace olive { class NodeViewCommon { public: enum FlowDirection { + kInvalidDirection = -1, kTopToBottom, kBottomToTop, kLeftToRight, diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index c2c516c60..59c640634 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -53,29 +53,16 @@ void NodeViewContext::AddChild(Node *node) NodeViewItem *item = new NodeViewItem(node, context_, this); item->SetFlowDirection(flow_dir_); - connect(node, &Node::InputConnected, this, &NodeViewContext::ChildInputConnected); - connect(node, &Node::InputDisconnected, this, &NodeViewContext::ChildInputDisconnected); + AddNodeInternal(node, item); - item_map_.insert(node, item); - - if (node == context_) { - item->SetLabelAsOutput(true); - } - - for (auto it=node->output_connections().cbegin(); it!=node->output_connections().cend(); it++) { - if (!it->second.IsHidden()) { - if (NodeViewItem *other_item = item_map_.value(it->second.node())) { - AddEdgeInternal(node, it->second, item, other_item); - } + if (NodeGroup *group = dynamic_cast(node)) { + foreach (Node *n, group->GetNodes()) { + // Use this item as the representative for all of these nodes too + AddNodeInternal(n, item); } - } - for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - if (!it->first.IsHidden()) { - if (NodeViewItem *other_item = item_map_.value(it->second)) { - AddEdgeInternal(it->second, it->first, other_item, item); - } - } + connect(group, &NodeGroup::NodeAddedToGroup, this, &NodeViewContext::GroupAddedNode); + connect(group, &NodeGroup::NodeRemovedFromGroup, this, &NodeViewContext::GroupRemovedNode); } UpdateRect(); @@ -95,12 +82,28 @@ void NodeViewContext::RemoveChild(Node *node) // Delete edges first because the edge destructor will try to reference item (maybe that should // be changed...) - QVector edges_to_remove = item->edges(); + QVector edges_to_remove = item->GetAllEdgesRecursively(); foreach (NodeViewEdge *edge, edges_to_remove) { - ChildInputDisconnected(edge->output(), edge->input()); + if (node == item->GetNode() || edge->output() == node || edge->input().node() == node) { + ChildInputDisconnected(edge->output(), edge->input()); + } } - delete item; + // Check if this item is specifically for this node and the node is a group. If so, remove it for + // all other entries in the map. + if (item->GetNode() == node) { + if (dynamic_cast(item->GetNode())) { + for (auto it=item_map_.begin(); it!=item_map_.end(); ) { + if (it.value() == item) { + it = item_map_.erase(it); + } else { + it++; + } + } + } + + delete item; + } } void NodeViewContext::ChildInputConnected(Node *output, const NodeInput &input) @@ -108,7 +111,7 @@ void NodeViewContext::ChildInputConnected(Node *output, const NodeInput &input) // Add edge if (!input.IsHidden()) { if (NodeViewItem* output_item = item_map_.value(output)) { - AddEdgeInternal(output, input, output_item, item_map_.value(input.node())); + AddEdgeInternal(output, input, output_item, item_map_.value(input.node())->GetItemForInput(input)); } } } @@ -117,8 +120,9 @@ bool NodeViewContext::ChildInputDisconnected(Node *output, const NodeInput &inpu { // Remove edge for (int i=0; ioutput() == output && edges_.at(i)->input() == input) { - delete edges_.at(i); + NodeViewEdge *e = edges_.at(i); + if (e->output() == output && e->input() == input) { + delete e; edges_.removeAt(i); return true; } @@ -154,10 +158,6 @@ void NodeViewContext::SetFlowDirection(NodeViewCommon::FlowDirection dir) foreach (NodeViewItem *item, item_map_) { item->SetFlowDirection(dir); } - - foreach (NodeViewEdge *edge, edges_) { - edge->SetFlowDirection(dir); - } } void NodeViewContext::SetCurvedEdges(bool e) @@ -201,7 +201,9 @@ QVector NodeViewContext::GetSelectedItems() const for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) { if (it.value()->isSelected()) { - items.append(it.value()); + if (!items.contains(it.value())) { + items.append(it.value()); + } } } @@ -269,16 +271,62 @@ void NodeViewContext::mousePressEvent(QGraphicsSceneMouseEvent *event) super::mousePressEvent(event); } -NodeViewEdge* NodeViewContext::AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to) +void NodeViewContext::AddNodeInternal(Node *node, NodeViewItem *item) { + connect(node, &Node::InputConnected, this, &NodeViewContext::ChildInputConnected); + connect(node, &Node::InputDisconnected, this, &NodeViewContext::ChildInputDisconnected); + + item_map_.insert(node, item); + + if (node == context_) { + item->SetLabelAsOutput(true); + } + + for (auto it=node->output_connections().cbegin(); it!=node->output_connections().cend(); it++) { + if (!it->second.IsHidden()) { + if (NodeViewItem *other_item = item_map_.value(it->second.node())) { + AddEdgeInternal(node, it->second, item, other_item->GetItemForInput(it->second)); + } + } + } + + for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { + if (!it->first.IsHidden()) { + if (NodeViewItem *other_item = item_map_.value(it->second)) { + AddEdgeInternal(it->second, it->first, other_item, item->GetItemForInput(it->first)); + } + } + } +} + +void NodeViewContext::AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to) +{ + if (from == to) { + return; + } + NodeViewEdge* edge_ui = new NodeViewEdge(output, input, from, to, this); - edge_ui->SetFlowDirection(flow_dir_); + edge_ui->Adjust(); edge_ui->SetCurved(curved_edges_); edges_.append(edge_ui); +} - return edge_ui; +void NodeViewContext::GroupAddedNode(Node *node) +{ + NodeGroup *group = static_cast(sender()); + + AddNodeInternal(node, item_map_.value(group)); +} + +void NodeViewContext::GroupRemovedNode(Node *node) +{ + NodeGroup *group = static_cast(sender()); + + if (item_map_.value(node) == item_map_.value(group)) { + item_map_.remove(node); + } } } diff --git a/app/widget/nodeview/nodeviewcontext.h b/app/widget/nodeview/nodeviewcontext.h index 7cdc638a7..6d191d916 100644 --- a/app/widget/nodeview/nodeviewcontext.h +++ b/app/widget/nodeview/nodeviewcontext.h @@ -60,7 +60,9 @@ protected: virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override; private: - NodeViewEdge *AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to); + void AddNodeInternal(Node *node, NodeViewItem *item); + + void AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to); Node *context_; @@ -76,6 +78,11 @@ private: QVector edges_; +private slots: + void GroupAddedNode(Node *node); + + void GroupRemovedNode(Node *node); + }; } diff --git a/app/widget/nodeview/nodeviewedge.cpp b/app/widget/nodeview/nodeviewedge.cpp index 794c98c52..0cf408ea1 100644 --- a/app/widget/nodeview/nodeviewedge.cpp +++ b/app/widget/nodeview/nodeviewedge.cpp @@ -70,12 +70,40 @@ NodeViewEdge::~NodeViewEdge() } } +void NodeViewEdge::set_from_item(NodeViewItem *i) +{ + if (from_item_) { + from_item_->RemoveEdge(this); + } + + from_item_ = i; + + if (from_item_) { + from_item_->AddEdge(this); + } + + Adjust(); +} + +void NodeViewEdge::set_to_item(NodeViewItem *i) +{ + if (to_item_) { + to_item_->RemoveEdge(this); + } + + to_item_ = i; + + if (to_item_) { + to_item_->AddEdge(this); + } + + Adjust(); +} + void NodeViewEdge::Adjust() { // Draw a line between the two - SetPoints(from_item()->GetOutputPoint(), - to_item()->GetInputPoint(input_.input(), input_.element()), - to_item()->IsExpanded()); + SetPoints(from_item()->GetOutputPoint(), to_item()->GetInputPoint()); } void NodeViewEdge::SetConnected(bool c) @@ -92,24 +120,14 @@ void NodeViewEdge::SetHighlighted(bool e) update(); } -void NodeViewEdge::SetPoints(const QPointF &start, const QPointF &end, bool input_is_expanded) +void NodeViewEdge::SetPoints(const QPointF &start, const QPointF &end) { cached_start_ = start; cached_end_ = end; - cached_input_is_expanded_ = input_is_expanded; UpdateCurve(); } -void NodeViewEdge::SetFlowDirection(NodeViewCommon::FlowDirection dir) -{ - flow_dir_ = dir; - - if (from_item_ && to_item_) { - Adjust(); - } -} - void NodeViewEdge::SetCurved(bool e) { curved_ = e; @@ -146,7 +164,6 @@ void NodeViewEdge::Init() { connected_ = false; highlighted_ = false; - flow_dir_ = NodeViewCommon::kLeftToRight; curved_ = true; setFlag(QGraphicsItem::ItemIsSelectable); @@ -175,13 +192,26 @@ void NodeViewEdge::UpdateCurve() QPointF cp1, cp2; - if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal) { + NodeViewCommon::FlowDirection from_flow = from_item_ ? from_item_->GetFlowDirection() : NodeViewCommon::kInvalidDirection; + NodeViewCommon::FlowDirection to_flow = to_item_ ? to_item_->GetFlowDirection() : NodeViewCommon::kInvalidDirection; + + if (from_flow == NodeViewCommon::kInvalidDirection && to_flow == NodeViewCommon::kInvalidDirection) { + // This is a technically unsupported scenario, but to avoid issues, we'll use a fallback + from_flow = NodeViewCommon::kLeftToRight; + to_flow = NodeViewCommon::kLeftToRight; + } else if (from_flow == NodeViewCommon::kInvalidDirection) { + from_flow = to_flow; + } else if (to_flow == NodeViewCommon::kInvalidDirection) { + to_flow = from_flow; + } + + if (NodeViewCommon::GetFlowOrientation(from_flow) == Qt::Horizontal) { cp1 = QPointF(half_x, start.y()); } else { cp1 = QPointF(start.x(), half_y); } - if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal || cached_input_is_expanded_) { + if (NodeViewCommon::GetFlowOrientation(to_flow) == Qt::Horizontal) { cp2 = QPointF(half_x, end.y()); } else { cp2 = QPointF(end.x(), half_y); diff --git a/app/widget/nodeview/nodeviewedge.h b/app/widget/nodeview/nodeviewedge.h index 31a56664e..b149b3680 100644 --- a/app/widget/nodeview/nodeviewedge.h +++ b/app/widget/nodeview/nodeviewedge.h @@ -72,6 +72,10 @@ public: return to_item_; } + void set_from_item(NodeViewItem *i); + + void set_to_item(NodeViewItem *i); + void Adjust(); /** @@ -101,12 +105,7 @@ public: /** * @brief Set points to create curve from */ - void SetPoints(const QPointF& start, const QPointF& end, bool input_is_expanded); - - /** - * @brief Sets the direction nodes are flowing - */ - void SetFlowDirection(NodeViewCommon::FlowDirection dir); + void SetPoints(const QPointF& start, const QPointF& end); /** * @brief Set whether edges should be drawn as curved or as straight lines @@ -137,13 +136,10 @@ private: bool highlighted_; - NodeViewCommon::FlowDirection flow_dir_; - bool curved_; QPointF cached_start_; QPointF cached_end_; - bool cached_input_is_expanded_; }; diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 1a9b6c516..9357a8f0b 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -39,21 +39,17 @@ namespace olive { -NodeViewItem::NodeViewItem(Node* n, Node *context, QGraphicsItem *parent) : +NodeViewItem::NodeViewItem(Node *node, const QString &input, int element, Node *context, QGraphicsItem *parent) : QGraphicsRectItem(parent), - node_(n), + node_(node), + input_(input), + element_(element), context_(context), expanded_(false), - hide_titlebar_(false), - highlighted_index_(-1), - flow_dir_(NodeViewCommon::kLeftToRight), + highlighted_(false), + flow_dir_(NodeViewCommon::kInvalidDirection), label_as_output_(false) { - // Set flags for this widget - setFlag(QGraphicsItem::ItemIsMovable); - setFlag(QGraphicsItem::ItemIsSelectable); - setFlag(QGraphicsItem::ItemSendsGeometryChanges); - // // We use font metrics to set all the UI measurements for DPI-awareness // @@ -61,32 +57,41 @@ NodeViewItem::NodeViewItem(Node* n, Node *context, QGraphicsItem *parent) : // Set border width node_border_width_ = DefaultItemBorder(); - int widget_width = DefaultItemWidth(); - int widget_height = DefaultItemHeight(); - - title_bar_rect_ = QRectF(-widget_width/2, -widget_height/2, widget_width, widget_height); - setRect(title_bar_rect_); + // Set rect size to default + SetRectSize(); + // Create connector + input_connector_ = new NodeViewItemConnector(false, this); output_connector_ = new NodeViewItemConnector(true, this); - // Set up node - node_->Retranslate(); - - foreach (const QString& input, node_->inputs()) { - if (node_->IsInputConnectable(input) && !node_->IsInputHidden(input)) { - node_inputs_.append(input); - } - } - - UpdateInputConnectors(); - connect(node_, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); connect(node_, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); - if (context_) { - SetNodePosition(context_->GetNodePositionInContext(node_)); - SetExpanded(context_->IsNodeExpandedInContext(node_)); + if (IsOutputItem()) { + connect(node_, &Node::InputAdded, this, &NodeViewItem::RepopulateInputs); + connect(node_, &Node::InputRemoved, this, &NodeViewItem::RepopulateInputs); + RepopulateInputs(); + + // Set flags for this widget + setFlag(QGraphicsItem::ItemSendsGeometryChanges); + setFlag(QGraphicsItem::ItemIsMovable); + setFlag(QGraphicsItem::ItemIsSelectable); + + if (context_) { + SetNodePosition(context_->GetNodePositionInContext(node_)); + SetExpanded(context_->IsNodeExpandedInContext(node_)); + } + } else { + output_connector_->setVisible(false); } + + // This should be set during runtime, but just in case here's a default fallback + SetFlowDirection(NodeViewCommon::kLeftToRight); +} + +NodeViewItem::~NodeViewItem() +{ + Q_ASSERT(edges_.isEmpty()); } QPointF NodeViewItem::GetNodePosition() const @@ -101,6 +106,17 @@ void NodeViewItem::SetNodePosition(const QPointF &pos) UpdateNodePosition(); } +QVector NodeViewItem::GetAllEdgesRecursively() const +{ + QVector list = edges_; + + foreach (NodeViewItem *item, children_) { + list.append(item->GetAllEdgesRecursively()); + } + + return list; +} + int NodeViewItem::DefaultTextPadding() { return QFontMetrics(QFont()).height() / 4; @@ -139,6 +155,8 @@ QPointF NodeViewItem::NodeToScreenPoint(QPointF p, NodeViewCommon::FlowDirection // Swap X/Y and invert Y p = QPointF(p.y(), -p.x()); break; + case NodeViewCommon::kInvalidDirection: + break; } // Multiply by item sizes for this direction @@ -164,12 +182,14 @@ QPointF NodeViewItem::ScreenToNodePoint(QPointF p, NodeViewCommon::FlowDirection break; case NodeViewCommon::kTopToBottom: // Swap X/Y - p = QPointF(p.y(), p.x()); + p = QPointF(p.y(), p.x()); break; case NodeViewCommon::kBottomToTop: // Swap X/Y and invert Y p = QPointF(-p.y(), p.x()); break; + case NodeViewCommon::kInvalidDirection: + break; } return p; @@ -213,51 +233,79 @@ void NodeViewItem::RemoveEdge(NodeViewEdge *edge) edges_.removeOne(edge); } -int NodeViewItem::GetIndexAt(QPointF pt) const -{ - pt -= this->scenePos(); - - for (int i=0; iGetInputFlags(input_) & kInputFlagArray)) + || (expanded_ == e)) { return; } expanded_ = e; - hide_titlebar_ = hide_titlebar; if (context_) { context_->SetNodeExpandedInContext(node_, e); } - if (expanded_ && !node_inputs_.isEmpty()) { - // Create new rect - QRectF new_rect = title_bar_rect_; - - if (hide_titlebar_) { - new_rect.setHeight(new_rect.height() * node_inputs_.size()); - } else { - new_rect.setHeight(new_rect.height() * (node_inputs_.size() + 1)); - } - - setRect(new_rect); - } else { - setRect(title_bar_rect_); + if (IsOutputItem()) { + // We don't have to check has_connectable_inputs_ here because we did it at the top + input_connector_->setVisible(!expanded_); } - UpdateInputConnectorFlowDirections(); + if (expanded_) { + if (IsOutputItem()) { + // Create items for each input of the node + int i = 1; + foreach (const QString &input, node_->inputs()) { + if (IsInputValid(input)) { + NodeViewItem *item = new NodeViewItem(node_, input, -1, context_, this); + item->setPos(QPointF(0, i * item->rect().height())); + children_.append(item); + i++; + } + } - UpdateConnectorPositions(); + QVector edges = edges_; + for (auto it=edges.cbegin(); it!=edges.cend(); it++) { + if ((*it)->to_item() == this) { + (*it)->set_to_item(GetItemForInput((*it)->input())); + } + } + + SetRectSize(i); + } else { + // Create items for each element of the input array + int arr_sz = node_->InputArraySize(input_); + children_.resize(arr_sz); + for (int i=0; isetPos(pos() + QPointF(0, (i+1) * item->rect().height())); + children_[i] = item; + } + + QVector edges = edges_; + for (auto it=edges.cbegin(); it!=edges.cend(); it++) { + if ((*it)->to_item() == this) { + (*it)->set_to_item(GetItemForInput((*it)->input())); + } + } + } + } else { + foreach (NodeViewItem *child, children_) { + QVector child_edges = child->edges(); + foreach (NodeViewEdge *edge, child_edges) { + edge->set_to_item(this); + } + delete child; + } + children_.clear(); + + SetRectSize(1); + } + + if (flow_dir_ == NodeViewCommon::kTopToBottom) { + UpdateOutputConnectorPosition(); + } ReadjustAllEdges(); @@ -277,35 +325,16 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti // has been slightly modified QPalette app_pal = Core::instance()->main_window()->palette(); - // Draw background rect if expanded - if (IsExpanded()) { - painter->setPen(Qt::NoPen); - painter->setBrush(app_pal.color(QPalette::Window)); - - painter->drawRect(rect()); - - painter->setPen(app_pal.color(QPalette::Text)); - - for (int i=0;ifillRect(input_rect, highlight_col); - } - - painter->drawText(input_rect, Qt::AlignCenter, node_->GetInputName(node_inputs_.at(i))); - } - } - // Draw the titlebar - if (!hide_titlebar_ && node_) { + if (IsOutputItem()) { + QRectF single_unit_rect = rect(); + single_unit_rect.setHeight(DefaultItemHeight()); + // Output item drawing code painter->setPen(Qt::black); - painter->setBrush(node_->brush(title_bar_rect_.top(), title_bar_rect_.bottom())); + painter->setBrush(node_->brush(single_unit_rect.top(), single_unit_rect.bottom())); - painter->drawRect(title_bar_rect_); + painter->drawRect(single_unit_rect); painter->setPen(app_pal.color(QPalette::Text)); @@ -320,40 +349,54 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti int icon_size = painter->fontMetrics().height()/2; - bool draw_arrow = !node_inputs_.isEmpty(); - if (node_label.isEmpty()) { // Draw shortname only - DrawNodeTitle(painter, node_shortname, title_bar_rect_, Qt::AlignVCenter, icon_size, draw_arrow); + DrawNodeTitle(painter, node_shortname, single_unit_rect, Qt::AlignVCenter, icon_size, has_connectable_inputs_); } else { int text_pad = DefaultTextPadding()/2; - QRectF safe_label_bounds = title_bar_rect_.adjusted(text_pad, text_pad, -text_pad, -text_pad); + QRectF safe_label_bounds = single_unit_rect.adjusted(text_pad, text_pad, -text_pad, -text_pad); QFont f; qreal font_sz = f.pointSizeF(); f.setPointSizeF(font_sz * 0.8); painter->setFont(f); - DrawNodeTitle(painter, node_label, safe_label_bounds, Qt::AlignTop, icon_size, draw_arrow); + DrawNodeTitle(painter, node_label, safe_label_bounds, Qt::AlignTop, icon_size, has_connectable_inputs_); f.setPointSizeF(font_sz * 0.6); painter->setFont(f); DrawNodeTitle(painter, node_shortname, safe_label_bounds, Qt::AlignBottom, icon_size, false); } - } + // Draw final border + QPen border_pen; + border_pen.setWidth(node_border_width_); - // Draw final border - QPen border_pen; - border_pen.setWidth(node_border_width_); + if (option->state & QStyle::State_Selected) { + border_pen.setColor(app_pal.color(QPalette::Highlight)); + } else { + border_pen.setColor(Qt::black); + } - if (option->state & QStyle::State_Selected) { - border_pen.setColor(app_pal.color(QPalette::Highlight)); + painter->setPen(border_pen); + painter->setBrush(Qt::NoBrush); + + painter->drawRect(rect()); } else { - border_pen.setColor(Qt::black); + // Input item drawing code + painter->setPen(Qt::NoPen); + painter->setBrush(app_pal.color(QPalette::Window)); + + painter->drawRect(rect()); + + if (highlighted_) { + QColor highlight_col = app_pal.color(QPalette::Text); + highlight_col.setAlpha(64); + painter->setBrush(highlight_col); + painter->drawRect(rect()); + } + + painter->setPen(app_pal.color(QPalette::Text)); + + painter->drawText(rect(), Qt::AlignCenter, node_->GetInputName(input_)); } - - painter->setPen(border_pen); - painter->setBrush(Qt::NoBrush); - - painter->drawRect(rect()); } void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event) @@ -399,11 +442,16 @@ QVariant NodeViewItem::itemChange(QGraphicsItem::GraphicsItemChange change, cons void NodeViewItem::ReadjustAllEdges() { - UpdateInputConnectorFlowDirections(); - UpdateInputConnectorPositions(); foreach (NodeViewEdge* edge, edges_) { + if (NodeViewItem *to_item = edge->to_item()) { + static_cast(to_item->parentItem())->UpdateFlowDirectionOfInputItem(to_item); + } + edge->Adjust(); } + foreach (NodeViewItem *child, children_) { + child->ReadjustAllEdges(); + } } void NodeViewItem::UpdateContextRect() @@ -420,19 +468,19 @@ void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& painter->setRenderHint(QPainter::SmoothPixmapTransform); // Draw right or down arrow based on expanded state - int icon_padding = title_bar_rect_.height() / 2 - icon_size / 2; + int icon_padding = DefaultItemHeight() / 2 - icon_size / 2; int icon_full_size = icon_size + icon_padding * 2; if (draw_arrow) { const QIcon& expand_icon = IsExpanded() ? icon::TriDown : icon::TriRight; int icon_size_scaled = icon_size * painter->transform().m11(); - painter->drawPixmap(QRect(title_bar_rect_.x() + icon_padding, - title_bar_rect_.y() + icon_padding, + painter->drawPixmap(QRect(this->rect().x() + icon_padding, + this->rect().y() + icon_padding, icon_size, icon_size), expand_icon.pixmap(QSize(icon_size_scaled, icon_size_scaled))); } // Calculate how much space we have for text - int item_width = title_bar_rect_.width(); + int item_width = this->rect().width(); int max_text_width = item_width - DefaultTextPadding() * 2 - icon_full_size; int label_width = QtUtils::QFontMetricsWidth(fm, text); @@ -467,28 +515,6 @@ void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& text); } -NodeViewEdge *NodeViewItem::GetEdgeFromInputIndex(int index) -{ - foreach (NodeViewEdge *edge, edges_) { - if (edge->input().input() == node_inputs_.at(index)) { - return edge; - } - } - - return nullptr; -} - -void NodeViewItem::SetHighlightedIndex(int index) -{ - if (highlighted_index_ == index) { - return; - } - - highlighted_index_ = index; - - update(); -} - void NodeViewItem::SetLabelAsOutput(bool e) { label_as_output_ = e; @@ -496,58 +522,9 @@ void NodeViewItem::SetLabelAsOutput(bool e) update(); } -NodeInput NodeViewItem::GetInputFromInputConnector(NodeViewItemConnector *connector) +QPointF NodeViewItem::GetInputPoint() const { - for (int i=0; i= int(input_connectors_.size())) { - return pos(); - } - - return input_connectors_[index]->scenePos(); + return input_connector_->scenePos(); } QPointF NodeViewItem::GetOutputPoint() const @@ -576,13 +553,21 @@ QPointF NodeViewItem::GetOutputPoint() const void NodeViewItem::SetFlowDirection(NodeViewCommon::FlowDirection dir) { - flow_dir_ = dir; + if (flow_dir_ != dir) { + flow_dir_ = dir; - UpdateInputConnectorFlowDirections(); - output_connector_->SetFlowDirection(dir); + input_connector_->SetFlowDirection(dir); + output_connector_->SetFlowDirection(dir); - UpdateConnectorPositions(); - UpdateNodePosition(); + UpdateInputConnectorPosition(); + UpdateOutputConnectorPosition(); + + if (IsOutputItem()) { + UpdateNodePosition(); + } + + ReadjustAllEdges(); + } } void NodeViewItem::UpdateNodePosition() @@ -590,26 +575,47 @@ void NodeViewItem::UpdateNodePosition() setPos(NodeToScreenPoint(cached_node_pos_, flow_dir_)); } -void NodeViewItem::UpdateInputConnectors() +void NodeViewItem::UpdateInputConnectorPosition() { - int old_sz = input_connectors_.size(); + QRectF output_rect = input_connector_->boundingRect(); - input_connectors_.resize(node_inputs_.size()); - for (size_t i=old_sz; i(false, this); + NodeViewCommon::FlowDirection using_flow_dir = flow_dir_; + + if (IsExpanded() && !NodeViewCommon::IsFlowHorizontal(flow_dir_)) { + if (edges_.isEmpty() || edges_.first()->from_item()->x() < this->x()) { + using_flow_dir = NodeViewCommon::kLeftToRight; + } else { + using_flow_dir = NodeViewCommon::kRightToLeft; + } + } + + // Input connector flow directions change conditionally + switch (using_flow_dir) { + case NodeViewCommon::kLeftToRight: + input_connector_->setPos(rect().left() - output_rect.width(), 0); + break; + case NodeViewCommon::kRightToLeft: + input_connector_->setPos(rect().right() + output_rect.width(), 0); + break; + case NodeViewCommon::kTopToBottom: + input_connector_->setPos(rect().center().x(), rect().top() - output_rect.height()); + break; + case NodeViewCommon::kBottomToTop: + input_connector_->setPos(rect().center().x(), rect().bottom() + output_rect.height()); + break; + case NodeViewCommon::kInvalidDirection: + break; } } -void NodeViewItem::UpdateConnectorPositions() +void NodeViewItem::UpdateOutputConnectorPosition() { - UpdateInputConnectorPositions(); - switch (flow_dir_) { case NodeViewCommon::kLeftToRight: - output_connector_->setPos(rect().right(), title_bar_rect_.center().y()); + output_connector_->setPos(rect().right(), 0); break; case NodeViewCommon::kRightToLeft: - output_connector_->setPos(rect().left(), title_bar_rect_.center().y()); + output_connector_->setPos(rect().left(), 0); break; case NodeViewCommon::kTopToBottom: output_connector_->setPos(rect().center().x(), rect().bottom()); @@ -617,57 +623,94 @@ void NodeViewItem::UpdateConnectorPositions() case NodeViewCommon::kBottomToTop: output_connector_->setPos(rect().center().x(), rect().top()); break; + case NodeViewCommon::kInvalidDirection: + break; } } -void NodeViewItem::UpdateInputConnectorPositions() +bool NodeViewItem::IsInputValid(const QString &input) { - QRectF output_rect = output_connector_->boundingRect(); - - // Input connector flow directions change conditionally - for (size_t i=0; isetPos(rect().left() - output_rect.width(), GetInputRect(i).center().y()); - break; - case NodeViewCommon::kRightToLeft: - input_connectors_[i]->setPos(rect().right() + output_rect.width(), GetInputRect(i).center().y()); - break; - case NodeViewCommon::kTopToBottom: - input_connectors_[i]->setPos(rect().center().x(), rect().top() - output_rect.height()); - break; - case NodeViewCommon::kBottomToTop: - input_connectors_[i]->setPos(rect().center().x(), rect().bottom() + output_rect.height()); - break; - } - } + return node_->IsInputConnectable(input) && !node_->IsInputHidden(input); } -void NodeViewItem::UpdateInputConnectorFlowDirections() +void NodeViewItem::SetRectSize(int height_units) { - for (size_t i=0; iSetFlowDirection(GetFlowDirectionForInput(i)); - } + // Set rect + int widget_width = DefaultItemWidth(); + int widget_height = DefaultItemHeight(); + + setRect(QRectF(-widget_width/2, -widget_height/2, widget_width, widget_height * height_units)); } -NodeViewCommon::FlowDirection NodeViewItem::GetFlowDirectionForInput(int index) +void NodeViewItem::UpdateFlowDirectionOfInputItem(NodeViewItem *child) { - if (!expanded_ || NodeViewCommon::IsFlowHorizontal(flow_dir_)) { - return flow_dir_; - } else { - NodeViewEdge *edge = GetEdgeFromInputIndex(index); - - if (!edge || edge->from_item()->x() < this->x()) { - return NodeViewCommon::kLeftToRight; + if (!child->IsOutputItem()) { + if (NodeViewCommon::IsFlowVertical(flow_dir_)) { + if (!child->edges().isEmpty() && child->edges().first()->from_item()->scenePos().x() > child->scenePos().x()) { + child->SetFlowDirection(NodeViewCommon::kRightToLeft); + } else { + child->SetFlowDirection(NodeViewCommon::kLeftToRight); + } } else { - return NodeViewCommon::kRightToLeft; + child->SetFlowDirection(flow_dir_); } } } +void NodeViewItem::RepopulateInputs() +{ + has_connectable_inputs_ = false; + + foreach (const QString& input, node_->inputs()) { + if (IsInputValid(input)) { + has_connectable_inputs_ = true; + break; + } + } + + input_connector_->setVisible(has_connectable_inputs_); +} + void NodeViewItem::NodeAppearanceChanged() { update(); } +void NodeViewItem::SetHighlighted(bool e) +{ + highlighted_ = e; + update(); +} + +NodeViewItem *NodeViewItem::GetItemForInput(NodeInput input) +{ + if (NodeGroup *group = dynamic_cast(node_)) { + if (input.node() != group) { + // Translate input to group input + QString id = NodeGroup::GetGroupInputIDFromInput(input); + input.set_node(group); + input.set_input(id); + } + } + + if (IsExpanded()) { + if (input_.isEmpty()) { + // Look for the input in our children + foreach (NodeViewItem *i, children_) { + if (i->input_ == input.input()) { + return i; + } + } + } else { + // Look for element in our children + if (input.element() >= 0 && input.element() < children_.size()) { + return children_.at(input.element()); + } + } + } + + // Fallback to this object + return this; +} + } diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index 60d1f3874..4b2c7236f 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -32,6 +32,7 @@ namespace olive { +class NodeViewItem; class NodeViewEdge; /** @@ -45,11 +46,19 @@ class NodeViewItem : public QObject, public QGraphicsRectItem { Q_OBJECT public: - NodeViewItem(Node* n, Node *context, QGraphicsItem* parent = nullptr); + NodeViewItem(Node *node, const QString &input, int element, Node *context, QGraphicsItem* parent = nullptr); + NodeViewItem(Node *node, Node *context, QGraphicsItem* parent = nullptr) : + NodeViewItem(node, QString(), -1, context, parent) + { + } + + virtual ~NodeViewItem() override; QPointF GetNodePosition() const; void SetNodePosition(const QPointF& pos); + QVector GetAllEdgesRecursively() const; + /** * @brief Get currently attached node */ @@ -58,6 +67,11 @@ public: return node_; } + NodeInput GetInput() const + { + return NodeInput(node_, input_, element_); + } + Node *GetContext() const { return context_; @@ -82,11 +96,7 @@ public: void SetExpanded(bool e, bool hide_titlebar = false); void ToggleExpanded(); - /** - * @brief Returns GLOBAL point that edges should connect to for any NodeParam member of this object - */ - QPointF GetInputPoint(const QString& input, int element) const; - + QPointF GetInputPoint() const; QPointF GetOutputPoint() const; /** @@ -94,6 +104,11 @@ public: */ void SetFlowDirection(NodeViewCommon::FlowDirection dir); + NodeViewCommon::FlowDirection GetFlowDirection() const + { + return flow_dir_; + } + static int DefaultTextPadding(); static int DefaultItemHeight(); @@ -113,19 +128,20 @@ public: void AddEdge(NodeViewEdge* edge); void RemoveEdge(NodeViewEdge* edge); - int GetIndexAt(QPointF pt) const; - - NodeInput GetInputAtIndex(int index) const - { - return NodeInput(node_, node_inputs_.at(index)); - } - - void SetHighlightedIndex(int index); - void SetLabelAsOutput(bool e); - NodeInput GetInputFromInputConnector(NodeViewItemConnector *connector); - NodeViewEdge *GetEdgeFromInputConnector(NodeViewItemConnector *connector); + void SetHighlighted(bool e); + + NodeViewItem *GetItemForInput(NodeInput input); + + bool IsOutputItem() const + { + return input_.isEmpty(); + } + + void ReadjustAllEdges(); + + void UpdateFlowDirectionOfInputItem(NodeViewItem *child); protected: virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; @@ -138,49 +154,35 @@ protected: virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; private: - void ReadjustAllEdges(); - void UpdateContextRect(); void DrawNodeTitle(QPainter *painter, QString text, const QRectF &rect, Qt::Alignment vertical_align, int icon_size, bool draw_arrow); - NodeViewEdge *GetEdgeFromInputIndex(int index); - - /** - * @brief Returns local rect of a NodeInput in array node_inputs_[index] - */ - QRectF GetInputRect(int index) const; - /** * @brief Internal update function when logical position changes */ void UpdateNodePosition(); - void UpdateInputConnectors(); + void UpdateInputConnectorPosition(); + void UpdateOutputConnectorPosition(); - void UpdateConnectorPositions(); + bool IsInputValid(const QString &input); - void UpdateInputConnectorPositions(); - void UpdateInputConnectorFlowDirections(); - - NodeViewCommon::FlowDirection GetFlowDirectionForInput(int index); + void SetRectSize(int height_units = 1); /** * @brief Reference to attached Node */ - Node* node_; + Node *node_; + QString input_; + int element_; Node *context_; /** * @brief Cached list of node inputs */ - QVector node_inputs_; - - /** - * @brief Rectangle of the Node's title bar (equal to rect() when collapsed) - */ - QRectF title_bar_rect_; + QVector children_; /// Sizing variables to use when drawing int node_border_width_; @@ -190,9 +192,7 @@ private: */ bool expanded_; - bool hide_titlebar_; - - int highlighted_index_; + bool highlighted_; NodeViewCommon::FlowDirection flow_dir_; @@ -200,14 +200,18 @@ private: QPointF cached_node_pos_; - std::vector > input_connectors_; + NodeViewItemConnector *input_connector_; NodeViewItemConnector *output_connector_; + bool has_connectable_inputs_; + bool label_as_output_; private slots: void NodeAppearanceChanged(); + void RepopulateInputs(); + }; } diff --git a/app/widget/nodeview/nodeviewitemconnector.cpp b/app/widget/nodeview/nodeviewitemconnector.cpp index 2ab919d77..032b9903f 100644 --- a/app/widget/nodeview/nodeviewitemconnector.cpp +++ b/app/widget/nodeview/nodeviewitemconnector.cpp @@ -74,6 +74,8 @@ void NodeViewItemConnector::SetFlowDirection(NodeViewCommon::FlowDirection dir) p[1] = QPointF(-triangle_sz_half, 0); p[2] = QPointF(0, triangle_sz_half); break; + case NodeViewCommon::kInvalidDirection: + break; } setPolygon(p); diff --git a/app/widget/timelinewidget/undo/timelineundogeneral.cpp b/app/widget/timelinewidget/undo/timelineundogeneral.cpp index 9c8bbae14..a64d1e342 100644 --- a/app/widget/timelinewidget/undo/timelineundogeneral.cpp +++ b/app/widget/timelinewidget/undo/timelineundogeneral.cpp @@ -153,7 +153,7 @@ void TimelineAddTrackCommand::redo() if (create_pos_command) { position_command_->add_child(new NodeSetPositionCommand(track_, sequence, sequence->GetNodePositionInContext(sequence) + QPointF(-1, -position_factor))); position_command_->add_child(new NodeSetPositionCommand(merge_, sequence, sequence->GetNodePositionInContext(sequence))); - position_command_->add_child(new NodeSetPositionCommand(merge_, sequence, sequence->GetNodePositionInContext(sequence) + QPointF(-1, position_factor * timeline_->GetTrackCount()), true)); + position_command_->add_child(new NodeSetPositionAndDependenciesRecursivelyCommand(merge_, sequence, sequence->GetNodePositionInContext(sequence) + QPointF(-1, position_factor * timeline_->GetTrackCount()))); } } else if (direct_.IsValid() && !direct_.IsConnected()) { // If no merge, we have a direct connection, and nothing else is connected, connect this diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index db245f005..ddc0e6c0c 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -750,7 +750,7 @@ void MainWindow::SetDefaultLayout() node_panel_->show(); tabifyDockWidget(param_panel_, node_panel_); - footage_viewer_panel_->raise(); + param_panel_->raise(); curve_panel_->hide(); curve_panel_->setFloating(true); From 2473bccec3d8c18ebadfb87ab528a1a3cf5c506f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 1 Dec 2021 12:14:26 -0800 Subject: [PATCH 13/34] implemented arrays and reimplemented expanding --- app/widget/nodeview/nodeview.cpp | 84 ++++++++- app/widget/nodeview/nodeview.h | 10 +- app/widget/nodeview/nodeviewitem.cpp | 262 +++++++++++++++++---------- app/widget/nodeview/nodeviewitem.h | 14 +- 4 files changed, 266 insertions(+), 104 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 2dab4cb29..5f87c40b4 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -49,7 +49,6 @@ NodeView::NodeView(QWidget *parent) : create_edge_(nullptr), create_edge_output_item_(nullptr), create_edge_input_item_(nullptr), - create_edge_expand_item_(nullptr), paste_command_(nullptr), scale_(1.0) { @@ -500,11 +499,11 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) // Clear highlight if we set one create_edge_input_item_->SetHighlighted(false); - // Collapse if we expanded it - if (create_edge_expand_item_) { - create_edge_expand_item_->SetExpanded(false); - create_edge_expand_item_->setZValue(0); + // Collapse any items we expanded + for (auto it=create_edge_expanded_items_.crbegin(); it!=create_edge_expanded_items_.crend(); it++) { + CollapseItem(*it); } + create_edge_expanded_items_.clear(); NodeInput &creating_input = create_edge_input_; if (creating_input.IsValid()) { @@ -526,6 +525,13 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } command->add_child(new NodeEdgeAddCommand(creating_output, creating_input)); + + // If the output is not in the input's context, add it now. We check the item rather than + // the node itself, because sometimes a node may not be in the context but another node + // representing it will be (e.g. groups) + if (!scene_.context_map().value(create_edge_input_item_->GetContext())->GetItemFromMap(creating_output)) { + command->add_child(new NodeSetPositionCommand(creating_output, create_edge_input_item_->GetContext(), scene_.context_map().value(create_edge_input_item_->GetContext())->MapScenePosToNodePosInContext(create_edge_output_item_->scenePos()))); + } } creating_input.Reset(); @@ -624,6 +630,18 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) super::mouseReleaseEvent(event); } +void NodeView::mouseDoubleClickEvent(QMouseEvent *event) +{ + super::mouseDoubleClickEvent(event); + + if (!(event->modifiers() & Qt::ControlModifier)) { + NodeViewItem *item_at_cursor = dynamic_cast(itemAt(event->pos())); + if (item_at_cursor) { + item_at_cursor->ToggleExpanded(); + } + } +} + void NodeView::resizeEvent(QResizeEvent *event) { super::resizeEvent(event); @@ -1022,6 +1040,13 @@ void NodeView::ZoomFromKeyboard(double multiplier) ZoomIntoCursorPosition(nullptr, multiplier, cursor_pos); } +void NodeView::ClearCreateEdgeInputIfNecessary() +{ + if (create_edge_from_output_ && create_edge_input_.IsValid()) { + create_edge_input_.Reset(); + } +} + QPointF NodeView::GetEstimatedPositionForContext(NodeViewItem *item, Node *context) const { return item->GetNodePosition() - context_offsets_.value(context); @@ -1051,6 +1076,37 @@ void NodeView::PositionNewEdge(const QPoint &pos) item_at_cursor = nullptr; } + // Collapse any items that the cursor is no longer inside + int i=create_edge_expanded_items_.size() - 1; + for ( ; i>=0; i--) { + NodeViewItem* nvi = create_edge_expanded_items_.at(i); + QPointF local_pt = nvi->mapFromScene(scene_pt); + + if (nvi->contains(local_pt) || (!nvi->IsOutputItem() && nvi->parentItem()->contains(nvi->parentItem()->mapFromScene(scene_pt)) && local_pt.y() > nvi->rect().bottom())) { + break; + } else { + // Collapsing an item will destroy its children, so if the cursor item happens to be a child + // of the item we're about to collapse, set it to null + if (item_at_cursor && item_at_cursor->parentItem() == nvi) { + item_at_cursor = nullptr; + } + + if (opposing_item && opposing_item->parentItem() == nvi) { + opposing_item = nullptr; + ClearCreateEdgeInputIfNecessary(); + } + + CollapseItem(nvi); + } + } + create_edge_expanded_items_.resize(i + 1); + + // Expand item if possible + if (item_at_cursor && item_at_cursor->CanBeExpanded() && !item_at_cursor->IsExpanded()) { + ExpandItem(item_at_cursor); + create_edge_expanded_items_.append(item_at_cursor); + } + // Filter out connecting to a node that connects to us or an item of the same type if (item_at_cursor && ((create_edge_from_output_ && item_at_cursor->GetNode()->OutputsTo(source_item->GetNode(), true)) @@ -1068,11 +1124,7 @@ void NodeView::PositionNewEdge(const QPoint &pos) } // Clear cached input - if (create_edge_from_output_) { - if (create_edge_input_.IsValid()) { - create_edge_input_.Reset(); - } - } + ClearCreateEdgeInputIfNecessary(); // If this is an input and we're opposing_item = item_at_cursor; @@ -1293,4 +1345,16 @@ bool NodeView::IsItemAttachedToCursor(NodeViewItem *item) const return false; } +void NodeView::ExpandItem(NodeViewItem *item) +{ + item->SetExpanded(true); + item->setZValue(100); +} + +void NodeView::CollapseItem(NodeViewItem *item) +{ + item->SetExpanded(false); + item->setZValue(0); +} + } diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 4b3a0417f..8e34bba99 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -107,6 +107,7 @@ protected: virtual void mousePressEvent(QMouseEvent *event) override; virtual void mouseMoveEvent(QMouseEvent *event) override; virtual void mouseReleaseEvent(QMouseEvent* event) override; + virtual void mouseDoubleClickEvent(QMouseEvent* event) override; virtual void resizeEvent(QResizeEvent *event) override; @@ -135,6 +136,8 @@ private: void ZoomFromKeyboard(double multiplier); + void ClearCreateEdgeInputIfNecessary(); + QPointF GetEstimatedPositionForContext(NodeViewItem *item, Node *context) const; Menu *CreateAddMenu(Menu *parent); @@ -149,6 +152,10 @@ private: bool IsItemAttachedToCursor(NodeViewItem *item) const; + void ExpandItem(NodeViewItem *item); + + void CollapseItem(NodeViewItem *item); + NodeViewMiniMap *minimap_; struct AttachedItem { @@ -164,11 +171,12 @@ private: NodeViewEdge* create_edge_; NodeViewItem* create_edge_output_item_; NodeViewItem* create_edge_input_item_; - NodeViewItem *create_edge_expand_item_; NodeInput create_edge_input_; bool create_edge_already_exists_; bool create_edge_from_output_; + QVector create_edge_expanded_items_; + NodeViewScene scene_; MultiUndoCommand* paste_command_; diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 9357a8f0b..d6bb565e9 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -48,6 +48,7 @@ NodeViewItem::NodeViewItem(Node *node, const QString &input, int element, Node * expanded_(false), highlighted_(false), flow_dir_(NodeViewCommon::kInvalidDirection), + arrow_click_(false), label_as_output_(false) { // @@ -235,9 +236,7 @@ void NodeViewItem::RemoveEdge(NodeViewEdge *edge) void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) { - if ((IsOutputItem() && !has_connectable_inputs_) - || (!IsOutputItem() && !(node_->GetInputFlags(input_) & kInputFlagArray)) - || (expanded_ == e)) { + if (!CanBeExpanded() || (expanded_ == e)) { return; } @@ -253,13 +252,14 @@ void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) } if (expanded_) { + node_->Retranslate(); + if (IsOutputItem()) { // Create items for each input of the node int i = 1; foreach (const QString &input, node_->inputs()) { if (IsInputValid(input)) { NodeViewItem *item = new NodeViewItem(node_, input, -1, context_, this); - item->setPos(QPointF(0, i * item->rect().height())); children_.append(item); i++; } @@ -271,15 +271,12 @@ void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) (*it)->set_to_item(GetItemForInput((*it)->input())); } } - - SetRectSize(i); } else { // Create items for each element of the input array int arr_sz = node_->InputArraySize(input_); children_.resize(arr_sz); for (int i=0; isetPos(pos() + QPointF(0, (i+1) * item->rect().height())); children_[i] = item; } @@ -299,10 +296,10 @@ void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) delete child; } children_.clear(); - - SetRectSize(1); } + UpdateChildrenPositions(); + if (flow_dir_ == NodeViewCommon::kTopToBottom) { UpdateOutputConnectorPosition(); } @@ -325,47 +322,81 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti // has been slightly modified QPalette app_pal = Core::instance()->main_window()->palette(); - // Draw the titlebar - if (IsOutputItem()) { - QRectF single_unit_rect = rect(); - single_unit_rect.setHeight(DefaultItemHeight()); + // We only draw a single unit's worth + QRectF single_unit_rect = rect(); + single_unit_rect.setHeight(DefaultItemHeight()); - // Output item drawing code + if (IsOutputItem()) { + // Set output item colors painter->setPen(Qt::black); painter->setBrush(node_->brush(single_unit_rect.top(), single_unit_rect.bottom())); + } else { + // Set input item colors + painter->setPen(Qt::NoPen); + painter->setBrush(element_ == -1 ? app_pal.color(QPalette::Window) : app_pal.color(QPalette::Base)); + } - painter->drawRect(single_unit_rect); + painter->drawRect(single_unit_rect); - painter->setPen(app_pal.color(QPalette::Text)); + // Draw highlight if applicable + if (highlighted_) { + QColor highlight_col = app_pal.color(QPalette::Text); + highlight_col.setAlpha(64); + painter->setBrush(highlight_col); + painter->drawRect(rect()); + } - QString node_label, node_shortname; + // Determine what text to draw and whether to draw an arrow + QString node_label, node_name; + if (IsOutputItem()) { if (label_as_output_) { - node_shortname = QCoreApplication::translate("NodeViewItem", "Output"); + node_name = QCoreApplication::translate("NodeViewItem", "Output"); } else { node_label = node_->GetLabel(); - node_shortname = node_->ShortName(); + node_name = node_->ShortName(); } - - int icon_size = painter->fontMetrics().height()/2; - - if (node_label.isEmpty()) { - // Draw shortname only - DrawNodeTitle(painter, node_shortname, single_unit_rect, Qt::AlignVCenter, icon_size, has_connectable_inputs_); + } else { + if (element_ == -1) { + node_name = node_->GetInputName(input_); } else { - int text_pad = DefaultTextPadding()/2; - QRectF safe_label_bounds = single_unit_rect.adjusted(text_pad, text_pad, -text_pad, -text_pad); - QFont f; - qreal font_sz = f.pointSizeF(); - f.setPointSizeF(font_sz * 0.8); - painter->setFont(f); - DrawNodeTitle(painter, node_label, safe_label_bounds, Qt::AlignTop, icon_size, has_connectable_inputs_); - f.setPointSizeF(font_sz * 0.6); - painter->setFont(f); - DrawNodeTitle(painter, node_shortname, safe_label_bounds, Qt::AlignBottom, icon_size, false); + node_name = QString::number(element_); } + } - // Draw final border + // Draw arrow if necessary + int arrow_size = CanBeExpanded() ? DrawExpandArrow(painter) : 0; + + if (IsOutputItem()) { + // Determine the text color (automatically calculate from node background color) + painter->setPen(ColorCoding::GetUISelectorColor(node_->color())); + } else { + // Just use text item + painter->setPen(app_pal.text().color()); + } + + if (node_label.isEmpty()) { + // Draw name only + DrawNodeTitle(painter, node_name, single_unit_rect, Qt::AlignVCenter, arrow_size); + } else { + int text_pad = DefaultTextPadding()/2; + QRectF safe_label_bounds = single_unit_rect.adjusted(text_pad, text_pad, -text_pad, -text_pad); + QFont f; + qreal font_sz = f.pointSizeF(); + + // Draw label as larger/upper text + f.setPointSizeF(font_sz * 0.8); + painter->setFont(f); + DrawNodeTitle(painter, node_label, safe_label_bounds, Qt::AlignTop, arrow_size); + + // Draw node name as smaller/lower text + f.setPointSizeF(font_sz * 0.6); + painter->setFont(f); + DrawNodeTitle(painter, node_name, safe_label_bounds, Qt::AlignBottom, arrow_size); + } + + // Draw final border (output only) + if (IsOutputItem()) { QPen border_pen; border_pen.setWidth(node_border_width_); @@ -379,28 +410,17 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti painter->setBrush(Qt::NoBrush); painter->drawRect(rect()); - } else { - // Input item drawing code - painter->setPen(Qt::NoPen); - painter->setBrush(app_pal.color(QPalette::Window)); - - painter->drawRect(rect()); - - if (highlighted_) { - QColor highlight_col = app_pal.color(QPalette::Text); - highlight_col.setAlpha(64); - painter->setBrush(highlight_col); - painter->drawRect(rect()); - } - - painter->setPen(app_pal.color(QPalette::Text)); - - painter->drawText(rect(), Qt::AlignCenter, node_->GetInputName(input_)); } } void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { + if (last_arrow_rect_.contains(event->pos().toPoint())) { + arrow_click_ = true; + ToggleExpanded(); + return; + } + event->setModifiers(FlipControlAndShiftModifiers(event->modifiers())); QGraphicsRectItem::mousePressEvent(event); @@ -408,6 +428,10 @@ void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event) void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { + if (arrow_click_) { + return; + } + event->setModifiers(FlipControlAndShiftModifiers(event->modifiers())); QGraphicsRectItem::mouseMoveEvent(event); @@ -415,20 +439,16 @@ void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) void NodeViewItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { + if (arrow_click_) { + arrow_click_ = false; + return; + } + event->setModifiers(FlipControlAndShiftModifiers(event->modifiers())); QGraphicsRectItem::mouseReleaseEvent(event); } -void NodeViewItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) -{ - QGraphicsRectItem::mouseDoubleClickEvent(event); - - if (!(event->modifiers() & Qt::ControlModifier)) { - SetExpanded(!IsExpanded()); - } -} - QVariant NodeViewItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) { if (change == ItemPositionHasChanged && node_) { @@ -456,29 +476,22 @@ void NodeViewItem::ReadjustAllEdges() void NodeViewItem::UpdateContextRect() { - if (NodeViewContext *ctx = dynamic_cast(parentItem())) { - ctx->UpdateRect(); + QGraphicsItem *item = parentItem(); + + while (item) { + if (NodeViewContext *ctx = dynamic_cast(item)) { + ctx->UpdateRect(); + break; + } + + item = item->parentItem(); } } -void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& rect, Qt::Alignment vertical_align, int icon_size, bool draw_arrow) +void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& rect, Qt::Alignment vertical_align, int icon_full_size) { QFontMetrics fm = painter->fontMetrics(); - painter->setRenderHint(QPainter::SmoothPixmapTransform); - - // Draw right or down arrow based on expanded state - int icon_padding = DefaultItemHeight() / 2 - icon_size / 2; - int icon_full_size = icon_size + icon_padding * 2; - if (draw_arrow) { - const QIcon& expand_icon = IsExpanded() ? icon::TriDown : icon::TriRight; - int icon_size_scaled = icon_size * painter->transform().m11(); - painter->drawPixmap(QRect(this->rect().x() + icon_padding, - this->rect().y() + icon_padding, - icon_size, - icon_size), expand_icon.pixmap(QSize(icon_size_scaled, icon_size_scaled))); - } - // Calculate how much space we have for text int item_width = this->rect().width(); int max_text_width = item_width - DefaultTextPadding() * 2 - icon_full_size; @@ -497,9 +510,6 @@ void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& text = concatenated; } - // Determine the text color (automatically calculate from node background color) - painter->setPen(ColorCoding::GetUISelectorColor(node_->color())); - // Determine X position (favors horizontal centering unless it'll overrun the arrow) QRectF text_rect = rect; Qt::Alignment text_align = Qt::AlignHCenter | vertical_align; @@ -515,6 +525,28 @@ void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& text); } +int NodeViewItem::DrawExpandArrow(QPainter *painter) +{ + // Draw right or down arrow based on expanded state + int icon_size = painter->fontMetrics().height()/2; + int icon_padding = DefaultItemHeight() / 2 - icon_size / 2; + int icon_full_size = icon_size + icon_padding * 2; + + painter->setRenderHint(QPainter::SmoothPixmapTransform); + + const QIcon& expand_icon = IsExpanded() ? icon::TriDown : icon::TriRight; + int icon_size_scaled = icon_size * painter->transform().m11(); + + last_arrow_rect_ = QRect(this->rect().x() + icon_padding, + this->rect().y() + icon_padding, + icon_size, + icon_size); + + painter->drawPixmap(last_arrow_rect_, expand_icon.pixmap(QSize(icon_size_scaled, icon_size_scaled))); + + return icon_full_size; +} + void NodeViewItem::SetLabelAsOutput(bool e) { label_as_output_ = e; @@ -642,6 +674,44 @@ void NodeViewItem::SetRectSize(int height_units) setRect(QRectF(-widget_width/2, -widget_height/2, widget_width, widget_height * height_units)); } +bool NodeViewItem::CanBeExpanded() const +{ + if (IsOutputItem()) { + return has_connectable_inputs_; + } else { + return node_->GetInputFlags(input_) & kInputFlagArray && element_ == -1 && !node_->IsInputConnected(input_); + } +} + +void NodeViewItem::UpdateChildrenPositions() +{ + int y = 1; + int h = DefaultItemHeight(); + + foreach (NodeViewItem *c, children_) { + c->setPos(QPointF(0, y * h)); + + y += c->GetLogicalHeightWithChildren(); + } + + SetRectSize(y); + + if (NodeViewItem *p = dynamic_cast(parentItem())) { + p->UpdateChildrenPositions(); + } +} + +int NodeViewItem::GetLogicalHeightWithChildren() const +{ + int h = 1; + + foreach (NodeViewItem *c, children_) { + h += c->GetLogicalHeightWithChildren(); + } + + return h; +} + void NodeViewItem::UpdateFlowDirectionOfInputItem(NodeViewItem *child) { if (!child->IsOutputItem()) { @@ -659,16 +729,26 @@ void NodeViewItem::UpdateFlowDirectionOfInputItem(NodeViewItem *child) void NodeViewItem::RepopulateInputs() { - has_connectable_inputs_ = false; + if (IsOutputItem()) { + has_connectable_inputs_ = false; - foreach (const QString& input, node_->inputs()) { - if (IsInputValid(input)) { - has_connectable_inputs_ = true; - break; + foreach (const QString& input, node_->inputs()) { + if (IsInputValid(input)) { + has_connectable_inputs_ = true; + break; + } + } + + input_connector_->setVisible(has_connectable_inputs_); + + if (IsExpanded()) { + // Create or remove inputs when necessary + } + } else { + if (IsExpanded() && element_ == -1) { + // Create or remove array elements when necessary } } - - input_connector_->setVisible(has_connectable_inputs_); } void NodeViewItem::NodeAppearanceChanged() @@ -698,13 +778,13 @@ NodeViewItem *NodeViewItem::GetItemForInput(NodeInput input) // Look for the input in our children foreach (NodeViewItem *i, children_) { if (i->input_ == input.input()) { - return i; + return i->GetItemForInput(input); } } } else { // Look for element in our children if (input.element() >= 0 && input.element() < children_.size()) { - return children_.at(input.element()); + return children_.at(input.element())->GetItemForInput(input); } } } diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index 4b2c7236f..bbd6ec395 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -143,20 +143,23 @@ public: void UpdateFlowDirectionOfInputItem(NodeViewItem *child); + bool CanBeExpanded() const; + protected: virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override; virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; - virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; private: void UpdateContextRect(); - void DrawNodeTitle(QPainter *painter, QString text, const QRectF &rect, Qt::Alignment vertical_align, int icon_size, bool draw_arrow); + void DrawNodeTitle(QPainter *painter, QString text, const QRectF &rect, Qt::Alignment vertical_align, int icon_full_size); + + int DrawExpandArrow(QPainter *painter); /** * @brief Internal update function when logical position changes @@ -170,6 +173,10 @@ private: void SetRectSize(int height_units = 1); + void UpdateChildrenPositions(); + + int GetLogicalHeightWithChildren() const; + /** * @brief Reference to attached Node */ @@ -200,6 +207,9 @@ private: QPointF cached_node_pos_; + QRect last_arrow_rect_; + bool arrow_click_; + NodeViewItemConnector *input_connector_; NodeViewItemConnector *output_connector_; From c04b689215f8c6d9afa78b306d9c78f968f8cacf Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 2 Dec 2021 08:56:36 -0800 Subject: [PATCH 14/34] paramview: preliminary group support --- .../nodeparamview/nodeparamviewitem.cpp | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 3347551ef..e8deef896 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -240,12 +240,21 @@ NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBe int insert_row = 0; // Create widgets all root level components - foreach (const QString& input, node->inputs()) { - CreateWidgets(root_layout, node, input, -1, insert_row); + foreach (QString input, node->inputs()) { + Node *n; + if (NodeGroup *g = dynamic_cast(node)) { + const NodeInput &ni = g->GetInputPassthroughs().value(input); + n = ni.node(); + input = ni.input(); + } else { + n = node; + } + + CreateWidgets(root_layout, n, input, -1, insert_row); insert_row++; - if (node->InputIsArray(input)) { + if (n->InputIsArray(input)) { // Insert here QWidget* array_widget = new QWidget(); @@ -265,7 +274,7 @@ NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBe array_widget->setVisible(false); - array_ui_.insert({node, input}, {array_widget, arr_sz, append_btn}); + array_ui_.insert({n, input}, {array_widget, arr_sz, append_btn}); insert_row++; } @@ -432,6 +441,12 @@ int NodeParamViewItemBody::GetElementY(NodeInput c) const c.set_element(-1); } + if (NodeGroup *g = dynamic_cast(c.node())) { + const NodeInput &passthrough = g->GetInputPassthroughs().value(c.input()); + c.set_node(passthrough.node()); + c.set_input(passthrough.input()); + } + // Find its row in the parameters QLabel* lbl = input_ui_map_.value(c).main_label; From f4230a18054e499a86a2b0d645507a24732c9a80 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 2 Dec 2021 08:56:48 -0800 Subject: [PATCH 15/34] nodeview: accept ctrl dragging inputs too --- app/widget/nodeview/nodeview.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 5f87c40b4..da10d899b 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -316,13 +316,20 @@ void NodeView::mousePressEvent(QMouseEvent *event) create_edge_from_output_ = true; if (event->modifiers() & Qt::ControlModifier) { - create_edge_output_item_ = dynamic_cast(item); - if (create_edge_output_item_ && !create_edge_output_item_->IsOutputItem()) { - create_edge_output_item_ = nullptr; + NodeViewItem *mouse_item = dynamic_cast(item); + + if (mouse_item) { + if (mouse_item->IsOutputItem()) { + create_edge_output_item_ = mouse_item; + } else { + create_edge_input_item_ = mouse_item; + create_edge_input_ = mouse_item->GetInput(); + create_edge_from_output_ = false; + } } } - if (!create_edge_output_item_) { + if (!create_edge_output_item_ && !create_edge_input_item_) { // Determine if user clicked on a connector if (NodeViewItemConnector *connector = dynamic_cast(item)) { NodeViewItem *attached = static_cast(connector->parentItem()); From e98ac256c66676091afe98fc5ce2dc61bc116b49 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 3 Dec 2021 12:03:33 -0800 Subject: [PATCH 16/34] reorganized some lines to prevent a crash --- app/widget/nodeview/nodeview.cpp | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index da10d899b..955228cbe 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -310,8 +310,6 @@ void NodeView::mousePressEvent(QMouseEvent *event) if (event->button() == Qt::LeftButton) { // Sane defaults - create_edge_output_item_ = nullptr; - create_edge_input_item_ = nullptr; create_edge_already_exists_ = false; create_edge_from_output_ = true; @@ -502,16 +500,15 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) create_edge_ = nullptr; - if (create_edge_output_item_ && create_edge_input_item_) { - // Clear highlight if we set one + // Clear highlight if we set one + if (create_edge_output_item_) { + create_edge_output_item_->SetHighlighted(false); + } + if (create_edge_input_item_) { create_edge_input_item_->SetHighlighted(false); + } - // Collapse any items we expanded - for (auto it=create_edge_expanded_items_.crbegin(); it!=create_edge_expanded_items_.crend(); it++) { - CollapseItem(*it); - } - create_edge_expanded_items_.clear(); - + if (create_edge_output_item_ && create_edge_input_item_) { NodeInput &creating_input = create_edge_input_; if (creating_input.IsValid()) { // Make connection @@ -543,11 +540,17 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) creating_input.Reset(); } - - create_edge_output_item_ = nullptr; - create_edge_input_item_ = nullptr; } + create_edge_output_item_ = nullptr; + create_edge_input_item_ = nullptr; + + // Collapse any items we expanded + for (auto it=create_edge_expanded_items_.crbegin(); it!=create_edge_expanded_items_.crend(); it++) { + CollapseItem(*it); + } + create_edge_expanded_items_.clear(); + Core::instance()->undo_stack()->pushIfHasChildren(command); } @@ -1109,7 +1112,8 @@ void NodeView::PositionNewEdge(const QPoint &pos) create_edge_expanded_items_.resize(i + 1); // Expand item if possible - if (item_at_cursor && item_at_cursor->CanBeExpanded() && !item_at_cursor->IsExpanded()) { + if (item_at_cursor && item_at_cursor->CanBeExpanded() && !item_at_cursor->IsExpanded() + && (create_edge_from_output_ || !item_at_cursor->IsOutputItem())) { ExpandItem(item_at_cursor); create_edge_expanded_items_.append(item_at_cursor); } From 090fbb09d06a6252b8b4f7c35290cc5d186c2004 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 8 Dec 2021 14:08:12 -0800 Subject: [PATCH 17/34] contexts in param view --- app/dialog/nodegroup/nodegroupdialog.cpp | 4 +- app/node/output/viewer/viewer.cpp | 4 +- app/panel/param/param.cpp | 23 +- app/panel/param/param.h | 2 + app/ui/icons/icons.cpp | 2 + app/ui/icons/icons.h | 1 + app/widget/nodeparamview/CMakeLists.txt | 24 +- app/widget/nodeparamview/nodeparamview.cpp | 227 ++++++++++++++---- app/widget/nodeparamview/nodeparamview.h | 23 +- .../nodeparamview/nodeparamviewcontext.cpp | 92 +++++++ .../nodeparamview/nodeparamviewcontext.h | 90 +++++++ .../nodeparamview/nodeparamviewdockarea.cpp | 14 ++ .../nodeparamview/nodeparamviewdockarea.h | 2 + .../nodeparamview/nodeparamviewitem.cpp | 224 ++++------------- app/widget/nodeparamview/nodeparamviewitem.h | 98 ++------ .../nodeparamview/nodeparamviewitembase.cpp | 114 +++++++++ .../nodeparamview/nodeparamviewitembase.h | 93 +++++++ .../nodeparamviewitemtitlebar.cpp | 90 +++++++ .../nodeparamview/nodeparamviewitemtitlebar.h | 89 +++++++ app/widget/nodeview/nodeview.cpp | 4 +- app/widget/nodeview/nodeviewitem.cpp | 23 +- app/widget/nodeview/nodeviewitem.h | 2 + app/window/mainwindow/mainwindow.cpp | 1 + 23 files changed, 887 insertions(+), 359 deletions(-) create mode 100644 app/widget/nodeparamview/nodeparamviewcontext.cpp create mode 100644 app/widget/nodeparamview/nodeparamviewcontext.h create mode 100644 app/widget/nodeparamview/nodeparamviewitembase.cpp create mode 100644 app/widget/nodeparamview/nodeparamviewitembase.h create mode 100644 app/widget/nodeparamview/nodeparamviewitemtitlebar.cpp create mode 100644 app/widget/nodeparamview/nodeparamviewitemtitlebar.h diff --git a/app/dialog/nodegroup/nodegroupdialog.cpp b/app/dialog/nodegroup/nodegroupdialog.cpp index 9fd1fa4af..951c45339 100644 --- a/app/dialog/nodegroup/nodegroupdialog.cpp +++ b/app/dialog/nodegroup/nodegroupdialog.cpp @@ -66,9 +66,7 @@ NodeGroupDialog::NodeGroupDialog(NodeGroup *group, QWidget *parent) : param_view->SetInputChecked(it.value(), true); } - connect(node_view, &NodeView::NodesSelected, param_view, &NodeParamView::SelectNodes); - connect(node_view, &NodeView::NodesDeselected, param_view, &NodeParamView::DeselectNodes); - node_view->SelectAll(); + param_view->SetContexts({group}); row++; diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 751cdbb62..87707e2c1 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -47,10 +47,10 @@ ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_stream video_cache_enabled_(true), audio_cache_enabled_(true) { - AddInput(kVideoParamsInput, NodeValue::kVideoParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray)); + AddInput(kVideoParamsInput, NodeValue::kVideoParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); SetInputProperty(kVideoParamsInput, QStringLiteral("mask"), QVariant::fromValue(kVideoParamEditMask)); - AddInput(kAudioParamsInput, NodeValue::kAudioParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray)); + AddInput(kAudioParamsInput, NodeValue::kAudioParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); if (create_buffer_inputs) { AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); diff --git a/app/panel/param/param.cpp b/app/panel/param/param.cpp index c90875d00..dc8f5ea45 100644 --- a/app/panel/param/param.cpp +++ b/app/panel/param/param.cpp @@ -38,16 +38,12 @@ ParamPanel::ParamPanel(QWidget* parent) : void ParamPanel::SelectNodes(const QVector &nodes) { - static_cast(GetTimeBasedWidget())->SelectNodes(nodes); - - Retranslate(); + //static_cast(GetTimeBasedWidget())->SelectNodes(nodes); } void ParamPanel::DeselectNodes(const QVector &nodes) { - static_cast(GetTimeBasedWidget())->DeselectNodes(nodes); - - Retranslate(); + //static_cast(GetTimeBasedWidget())->DeselectNodes(nodes); } void ParamPanel::DeleteSelected() @@ -65,19 +61,14 @@ void ParamPanel::DeselectAll() static_cast(GetTimeBasedWidget())->DeselectAll(); } +void ParamPanel::SetContexts(const QVector &contexts) +{ + static_cast(GetTimeBasedWidget())->SetContexts(contexts); +} + void ParamPanel::Retranslate() { SetTitle(tr("Parameter Editor")); - - NodeParamView* view = static_cast(GetTimeBasedWidget()); - - if (view->GetItemMap().isEmpty()) { - SetSubtitle(tr("(none)")); - } else if (view->GetItemMap().size() == 1) { - SetSubtitle(view->GetItemMap().firstKey()->Name()); - } else { - SetSubtitle(tr("(multiple)")); - } } } diff --git a/app/panel/param/param.h b/app/panel/param/param.h index a20c3310a..c6af767f9 100644 --- a/app/panel/param/param.h +++ b/app/panel/param/param.h @@ -43,6 +43,8 @@ public slots: virtual void DeselectAll() override; + void SetContexts(const QVector &contexts); + signals: void RequestSelectNode(const QVector& target); diff --git a/app/ui/icons/icons.cpp b/app/ui/icons/icons.cpp index 322237b65..20ecdaeb2 100644 --- a/app/ui/icons/icons.cpp +++ b/app/ui/icons/icons.cpp @@ -76,6 +76,7 @@ QIcon icon::Clock; QIcon icon::Diamond; QIcon icon::Plus; QIcon icon::Minus; +QIcon icon::AddEffect; void icon::LoadAll(const QString& theme) { @@ -129,6 +130,7 @@ void icon::LoadAll(const QString& theme) Diamond = Create(theme, "diamond"); Plus = Create(theme, "plus"); Minus = Create(theme, "minus"); + AddEffect = Create(theme, "add-effect"); } QIcon icon::Create(const QString& theme, const QString &name) diff --git a/app/ui/icons/icons.h b/app/ui/icons/icons.h index c8c1788a0..2bca32bb5 100644 --- a/app/ui/icons/icons.h +++ b/app/ui/icons/icons.h @@ -86,6 +86,7 @@ extern QIcon Clock; extern QIcon Diamond; extern QIcon Plus; extern QIcon Minus; +extern QIcon AddEffect; /** * @brief Create an icon object loaded from file diff --git a/app/widget/nodeparamview/CMakeLists.txt b/app/widget/nodeparamview/CMakeLists.txt index 5d0f37f40..067ffad1f 100644 --- a/app/widget/nodeparamview/CMakeLists.txt +++ b/app/widget/nodeparamview/CMakeLists.txt @@ -16,23 +16,29 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} - widget/nodeparamview/nodeparamview.h widget/nodeparamview/nodeparamview.cpp - widget/nodeparamview/nodeparamviewarraywidget.h + widget/nodeparamview/nodeparamview.h widget/nodeparamview/nodeparamviewarraywidget.cpp - widget/nodeparamview/nodeparamviewconnectedlabel.h + widget/nodeparamview/nodeparamviewarraywidget.h widget/nodeparamview/nodeparamviewconnectedlabel.cpp - widget/nodeparamview/nodeparamviewdockarea.h + widget/nodeparamview/nodeparamviewconnectedlabel.h + widget/nodeparamview/nodeparamviewcontext.cpp + widget/nodeparamview/nodeparamviewcontext.h widget/nodeparamview/nodeparamviewdockarea.cpp - widget/nodeparamview/nodeparamviewitem.h + widget/nodeparamview/nodeparamviewdockarea.h widget/nodeparamview/nodeparamviewitem.cpp - widget/nodeparamview/nodeparamviewkeyframecontrol.h + widget/nodeparamview/nodeparamviewitem.h + widget/nodeparamview/nodeparamviewitembase.cpp + widget/nodeparamview/nodeparamviewitembase.h + widget/nodeparamview/nodeparamviewitemtitlebar.cpp + widget/nodeparamview/nodeparamviewitemtitlebar.h widget/nodeparamview/nodeparamviewkeyframecontrol.cpp - widget/nodeparamview/nodeparamviewtextedit.h + widget/nodeparamview/nodeparamviewkeyframecontrol.h widget/nodeparamview/nodeparamviewtextedit.cpp - widget/nodeparamview/nodeparamviewundo.h + widget/nodeparamview/nodeparamviewtextedit.h widget/nodeparamview/nodeparamviewundo.cpp - widget/nodeparamview/nodeparamviewwidgetbridge.h + widget/nodeparamview/nodeparamviewundo.h widget/nodeparamview/nodeparamviewwidgetbridge.cpp + widget/nodeparamview/nodeparamviewwidgetbridge.h PARENT_SCOPE ) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 52b4407fc..0a6f743e5 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -25,6 +25,7 @@ #include #include +#include "common/functiontimer.h" #include "common/timecodefunctions.h" #include "node/output/viewer/viewer.h" @@ -61,12 +62,6 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : param_widget_area_ = new NodeParamViewDockArea(); - // Disable dock widgets from tabbing and disable glitchy animations - param_widget_area_->setDockOptions(static_cast(0)); - - // HACK: Hide the main window separators (unfortunately the cursors still appear) - param_widget_area_->setStyleSheet(QStringLiteral("QMainWindow::separator {background: rgba(0, 0, 0, 0)}")); - QVBoxLayout* param_widget_container_layout = new QVBoxLayout(param_widget_container_); QMargins param_widget_margin = param_widget_container_layout->contentsMargins(); param_widget_margin.setTop(ruler()->height()); @@ -76,9 +71,29 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : param_widget_container_layout->addStretch(INT_MAX); + // Create contexts for three different types + context_items_.resize(Track::kCount + 1); + for (int i=0; isetVisible(false); + static_cast(c->titleBarWidget())->SetAddEffectButtonVisible(i == Track::kVideo || i == Track::kAudio); + static_cast(c->titleBarWidget())->SetText(Footage::GetStreamTypeName(static_cast(i))); + context_items_[i] = c; + param_widget_area_->AddItem(c); + } + // Disable collapsing param view (but collapsing keyframe view is permitted) splitter->setCollapsible(0, false); + // Create global vertical scrollbar on the right + vertical_scrollbar_ = new QScrollBar(); + vertical_scrollbar_->setMaximum(0); + layout->addWidget(vertical_scrollbar_); + + // Connect scrollbars together + connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); + connect(vertical_scrollbar_, &QScrollBar::valueChanged, param_scroll_area_->verticalScrollBar(), &QScrollBar::setValue); + if (create_keyframe_view) { // Set up keyframe view QWidget* keyframe_area = new QWidget(); @@ -108,20 +123,7 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : // Set both widgets to 50/50 splitter->setSizes({INT_MAX, INT_MAX}); - } else { - keyframe_view_ = nullptr; - } - // Create global vertical scrollbar on the right - vertical_scrollbar_ = new QScrollBar(); - vertical_scrollbar_->setMaximum(0); - layout->addWidget(vertical_scrollbar_); - - // Connect scrollbars together - connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); - connect(vertical_scrollbar_, &QScrollBar::valueChanged, param_scroll_area_->verticalScrollBar(), &QScrollBar::setValue); - - if (keyframe_view_) { connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, param_scroll_area_->verticalScrollBar(), &QScrollBar::setValue); connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::valueChanged, keyframe_view_->verticalScrollBar(), &QScrollBar::setValue); @@ -132,6 +134,8 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : keyframe_view_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); connect(keyframe_view_->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll); + } else { + keyframe_view_ = nullptr; } // Set a default scale - FIXME: Hardcoded @@ -144,8 +148,14 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : &NodeParamView::FocusChanged); } -void NodeParamView::SelectNodes(const QVector &nodes) +NodeParamView::~NodeParamView() { + qDeleteAll(context_items_); +} + +/*void NodeParamView::SelectNodes(const QVector &nodes) +{ + return; int original_node_count = items_.size(); foreach (Node* n, nodes) { @@ -158,7 +168,7 @@ void NodeParamView::SelectNodes(const QVector &nodes) active_nodes_.append(n); // Create node UI - AddNode(n); + AddNode(n, param_widget_area_); } if (items_.size() > original_node_count ) { @@ -173,6 +183,7 @@ void NodeParamView::SelectNodes(const QVector &nodes) void NodeParamView::DeselectNodes(const QVector &nodes) { + return; // Remove item from map and delete the widget int original_node_count = items_.size(); @@ -199,13 +210,59 @@ void NodeParamView::DeselectNodes(const QVector &nodes) SignalNodeOrder(); } -} +}*/ void NodeParamView::SetInputChecked(const NodeInput &input, bool e) { input_checked_.insert(input, e); - if (NodeParamViewItem *item = items_.value(input.node())) { - item->SetInputChecked(input, e); + foreach (NodeParamViewContext *ctx, context_items_) { + ctx->SetInputChecked(input, e); + } +} + +void NodeParamView::SetContexts(const QVector &contexts) +{ + TIME_THIS_FUNCTION; + + foreach (NodeParamViewContext *ctx, context_items_) { + ctx->Clear(); + ctx->setVisible(false); + } + + if (focused_node_) { + focused_node_ = nullptr; + emit FocusedNodeChanged(nullptr); + } + + foreach (Node *ctx, contexts) { + Track::Type ctx_type = Track::kCount; + + if (ClipBlock *clip = dynamic_cast(ctx)) { + if (clip->track()) { + if (clip->track()->type() != Track::kNone) { + ctx_type = clip->track()->type(); + } + } + } else if (Track *track = dynamic_cast(ctx)) { + if (track->type() != Track::kNone) { + ctx_type = track->type(); + } + } + + NodeParamViewContext *item = context_items_.at(ctx_type); + + item->AddContext(ctx); + item->setVisible(true); + + for (auto it=ctx->GetContextPositions().cbegin(); it!=ctx->GetContextPositions().cend(); it++) { + if (!dynamic_cast(it.key()) && !dynamic_cast(it.key())) { + AddNode(it.key(), item); + } + } + } + + foreach (NodeParamViewContext *ctx, context_items_) { + SortItemsInContext(ctx); } } @@ -235,8 +292,8 @@ void NodeParamView::TimebaseChangedEvent(const rational &timebase) keyframe_view_->SetTimebase(timebase); } - foreach (NodeParamViewItem* item, items_) { - item->SetTimebase(timebase); + foreach (NodeParamViewContext* ctx, context_items_) { + ctx->SetTimebase(timebase); } UpdateItemTime(GetTime()); @@ -260,7 +317,7 @@ void NodeParamView::ConnectedNodeChangeEvent(ViewerOutput *n) keyframe_view_->SetTimeTarget(n); } - foreach (NodeParamViewItem* item, items_) { + foreach (NodeParamViewContext* item, context_items_) { item->SetTimeTarget(n); } @@ -281,7 +338,7 @@ void NodeParamView::DeleteSelected() void NodeParamView::UpdateItemTime(const rational &time) { - foreach (NodeParamViewItem* item, items_) { + foreach (NodeParamViewContext* item, context_items_) { item->SetTime(time); } } @@ -293,6 +350,7 @@ void NodeParamView::QueueKeyframePositionUpdate() void NodeParamView::SignalNodeOrder() { + /* // Sort by item Y (apparently there's no way in Qt to get the order of dock widgets) QVector nodes; QVector item_ys; @@ -318,15 +376,12 @@ void NodeParamView::SignalNodeOrder() } emit NodeOrderChanged(nodes); + */ } -void NodeParamView::AddNode(Node *n) +void NodeParamView::AddNode(Node *n, NodeParamViewContext *context) { - NodeParamViewItem* item = new NodeParamViewItem(n, create_checkboxes_, param_widget_area_); - - item->setAllowedAreas(Qt::LeftDockWidgetArea); - item->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable); - item->SetExpanded(node_expanded_state_.value(n, true)); + NodeParamViewItem* item = new NodeParamViewItem(n, create_checkboxes_, context); if (keyframe_view_) { connect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); @@ -357,14 +412,13 @@ void NodeParamView::AddNode(Node *n) // Set the timebase item->SetTimebase(timebase()); - items_.insert(n, item); - param_widget_area_->addDockWidget(Qt::LeftDockWidgetArea, item); + context->AddNode(item); if (!focused_node_ && n->HasGizmos()) { // We'll focus this node now item->SetHighlighted(true); - focused_node_ = n; - emit FocusedNodeChanged(focused_node_); + focused_node_ = item; + emit FocusedNodeChanged(n); } if (keyframe_view_) { @@ -372,9 +426,21 @@ void NodeParamView::AddNode(Node *n) } } +/*void NodeParamView::AddNode(Node *node, Node *context, NodeParamViewContext *ctx_item) +{ + int dist = GetDistanceBetweenNodes(context, node); + + if (dist == -1) { + dist = 0; + } + + ctx_item->GetDockArea()->insert +}*/ + void NodeParamView::RemoveNode(Node *n) { - if (keyframe_view_) { + qDebug() << "STUB!"; + /*if (keyframe_view_) { keyframe_view_->RemoveKeyframesOfNode(n); disconnect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); @@ -395,16 +461,64 @@ void NodeParamView::RemoveNode(Node *n) } emit FocusedNodeChanged(focused_node_); + }*/ +} + +int GetDistanceBetweenNodes(Node *start, Node *end) +{ + if (start == end) { + return 0; + } + + for (auto it=start->input_connections().cbegin(); it!=start->input_connections().cend(); it++) { + int this_node_dist = GetDistanceBetweenNodes(it->second, end); + if (this_node_dist != -1) { + return 1 + this_node_dist; + } + } + + return -1; +} + +void NodeParamView::SortItemsInContext(NodeParamViewContext *context_item) +{ + QVector > distances; + + for (auto it=context_item->GetItems().cbegin(); it!=context_item->GetItems().cend(); it++) { + int distance = 0; + foreach (Node *ctx, context_item->GetContexts()) { + distance = qMax(distance, GetDistanceBetweenNodes(ctx, it.key())); + } + + bool inserted = false; + QPair dist(it.value(), distance); + + for (int i=0; iGetDockArea()->AddItem(info.first); } } void NodeParamView::UpdateGlobalScrollBar() { - int height_offscreen = param_widget_container_->height() - ruler()->height() + scrollbar()->height(); + int height_offscreen = param_widget_container_->height() + scrollbar()->height(); if (keyframe_view_) { keyframe_view_->SetMaxScroll(height_offscreen); } + vertical_scrollbar_->setRange(0, height_offscreen - param_scroll_area_->height()); } @@ -430,28 +544,36 @@ void NodeParamView::FocusChanged(QWidget* old, QWidget* now) Q_UNUSED(old) QObject* parent = now; - NodeParamViewItem* item; while (parent) { - item = dynamic_cast(parent); + if (NodeParamViewItem* item = dynamic_cast(parent)) { + if (item != focused_node_) { + // Found a NodeParamViewItem that isn't already focused, see if it belongs to us + bool ours = false; - if (item) { - if (item->parent() == param_widget_area_) { - // Found it! - if (item->GetNode() != focused_node_) { + do { + parent = parent->parent(); + + if (parent == this) { + ours = true; + break; + } + } while (parent); + + if (ours) { + // This item is ours, if (focused_node_) { // De-focus current node - items_.value(focused_node_)->SetHighlighted(false); + focused_node_->SetHighlighted(false); } - focused_node_ = item->GetNode(); + focused_node_ = item; item->SetHighlighted(true); - emit FocusedNodeChanged(focused_node_); + emit FocusedNodeChanged(item->GetNode()); } } - break; } @@ -469,7 +591,8 @@ void NodeParamView::KeyframeViewDragged(int x, int y) void NodeParamView::UpdateElementY() { - if (keyframe_view_) { + qDebug() << "STUB"; + /*if (keyframe_view_) { for (auto it=items_.cbegin(); it!=items_.cend(); it++) { foreach (const QString& input, it.key()->inputs()) { int arr_sz = it.key()->InputArraySize(input); @@ -482,7 +605,7 @@ void NodeParamView::UpdateElementY() } } } - } + }*/ } } diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 677c4dce5..c1c8fb3b5 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -25,6 +25,7 @@ #include #include "node/node.h" +#include "nodeparamviewcontext.h" #include "nodeparamviewdockarea.h" #include "nodeparamviewitem.h" #include "widget/keyframeview/keyframeview.h" @@ -64,8 +65,7 @@ public: { } - void SelectNodes(const QVector &nodes); - void DeselectNodes(const QVector& nodes); + virtual ~NodeParamView() override; void SetCreateCheckBoxes(NodeParamViewCheckBoxBehavior e) { @@ -77,11 +77,6 @@ public: return input_checked_.value(input); } - const QMap& GetItemMap() const - { - return items_; - } - Node* GetTimeTarget() const; void DeleteSelected(); @@ -99,6 +94,8 @@ public: public slots: void SetInputChecked(const NodeInput &input, bool e); + void SetContexts(const QVector &contexts); + signals: void RequestSelectNode(const QVector& target); @@ -122,13 +119,17 @@ private: void SignalNodeOrder(); - void AddNode(Node* n); + void AddNode(Node* n, NodeParamViewContext *context); + + //void AddNode(Node *node, Node *context, NodeParamViewContext *ctx_item); void RemoveNode(Node* n); + void SortItemsInContext(NodeParamViewContext *context); + KeyframeView* keyframe_view_; - QMap items_; + QVector context_items_; QScrollBar* vertical_scrollbar_; @@ -144,9 +145,7 @@ private: QVector active_nodes_; - QMap node_expanded_state_; - - Node* focused_node_; + NodeParamViewItem* focused_node_; NodeParamViewCheckBoxBehavior create_checkboxes_; diff --git a/app/widget/nodeparamview/nodeparamviewcontext.cpp b/app/widget/nodeparamview/nodeparamviewcontext.cpp new file mode 100644 index 000000000..c8b409f0b --- /dev/null +++ b/app/widget/nodeparamview/nodeparamviewcontext.cpp @@ -0,0 +1,92 @@ +/*** + + 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 "nodeparamviewcontext.h" + +#include "node/block/clip/clip.h" + +namespace olive { + +#define super NodeParamViewItemBase + +NodeParamViewContext::NodeParamViewContext(QWidget *parent) : + super(parent) +{ + QWidget *body = new QWidget(); + QHBoxLayout *body_layout = new QHBoxLayout(body); + SetBody(body); + + dock_area_ = new NodeParamViewDockArea(); + body_layout->addWidget(dock_area_); + + setBackgroundRole(QPalette::Base); + + Retranslate(); +} + +void NodeParamViewContext::AddNode(NodeParamViewItem *item) +{ + items_.insert(item->GetNode(), item); + dock_area_->AddItem(item); +} + +void NodeParamViewContext::RemoveNode(Node *node) +{ +} + +void NodeParamViewContext::Clear() +{ + qDeleteAll(items_); + items_.clear(); +} + +void NodeParamViewContext::SetInputChecked(const NodeInput &input, bool e) +{ + if (NodeParamViewItem *item = items_.value(input.node())) { + item->SetInputChecked(input, e); + } +} + +void NodeParamViewContext::SetTimebase(const rational &timebase) +{ + foreach (NodeParamViewItem* item, items_) { + item->SetTimebase(timebase); + } +} + +void NodeParamViewContext::SetTimeTarget(Node *n) +{ + foreach (NodeParamViewItem* item, items_) { + item->SetTimeTarget(n); + } +} + +void NodeParamViewContext::SetTime(const rational &time) +{ + foreach (NodeParamViewItem* item, items_) { + item->SetTime(time); + } +} + +void NodeParamViewContext::Retranslate() +{ +} + +} diff --git a/app/widget/nodeparamview/nodeparamviewcontext.h b/app/widget/nodeparamview/nodeparamviewcontext.h new file mode 100644 index 000000000..2b59920c6 --- /dev/null +++ b/app/widget/nodeparamview/nodeparamviewcontext.h @@ -0,0 +1,90 @@ +/*** + + 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 NODEPARAMVIEWCONTEXT_H +#define NODEPARAMVIEWCONTEXT_H + +#include "nodeparamviewdockarea.h" +#include "nodeparamviewitembase.h" +#include "nodeparamviewitem.h" + +namespace olive { + +class NodeParamViewContext : public NodeParamViewItemBase +{ + Q_OBJECT +public: + NodeParamViewContext(QWidget *parent = nullptr); + + NodeParamViewDockArea *GetDockArea() const + { + return dock_area_; + } + + const QVector &GetContexts() const + { + return contexts_; + } + + const QMap &GetItems() const + { + return items_; + } + + void AddNode(NodeParamViewItem *item); + + void RemoveNode(Node *node); + + void Clear(); + + void SetInputChecked(const NodeInput &input, bool e); + + void SetTimebase(const rational &timebase); + + void SetTimeTarget(Node *n); + + void SetTime(const rational &time); + +public slots: + void AddContext(Node *node) + { + contexts_.append(node); + } + + void RemoveContext(Node *node) + { + contexts_.removeOne(node); + } + +protected slots: + virtual void Retranslate() override; + +private: + NodeParamViewDockArea *dock_area_; + + QVector contexts_; + + QMap items_; + +}; + +} + +#endif // NODEPARAMVIEWCONTEXT_H diff --git a/app/widget/nodeparamview/nodeparamviewdockarea.cpp b/app/widget/nodeparamview/nodeparamviewdockarea.cpp index a875bbf24..7f6c32e0d 100644 --- a/app/widget/nodeparamview/nodeparamviewdockarea.cpp +++ b/app/widget/nodeparamview/nodeparamviewdockarea.cpp @@ -20,11 +20,18 @@ #include "nodeparamviewdockarea.h" +#include + namespace olive { NodeParamViewDockArea::NodeParamViewDockArea(QWidget *parent) : QMainWindow(parent) { + // Disable dock widgets from tabbing and disable glitchy animations + setDockOptions(static_cast(0)); + + // HACK: Hide the main window separators (unfortunately the cursors still appear) + setStyleSheet(QStringLiteral("QMainWindow::separator {background: rgba(0, 0, 0, 0)}")); } QMenu *NodeParamViewDockArea::createPopupMenu() @@ -32,4 +39,11 @@ QMenu *NodeParamViewDockArea::createPopupMenu() return nullptr; } +void NodeParamViewDockArea::AddItem(QDockWidget *item) +{ + item->setAllowedAreas(Qt::LeftDockWidgetArea); + item->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable); + addDockWidget(Qt::LeftDockWidgetArea, item); +} + } diff --git a/app/widget/nodeparamview/nodeparamviewdockarea.h b/app/widget/nodeparamview/nodeparamviewdockarea.h index c313236cd..099eb087c 100644 --- a/app/widget/nodeparamview/nodeparamviewdockarea.h +++ b/app/widget/nodeparamview/nodeparamviewdockarea.h @@ -35,6 +35,8 @@ public: virtual QMenu *createPopupMenu() override; + void AddItem(QDockWidget *item); + }; } diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index e8deef896..50e275a0c 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -22,8 +22,6 @@ #include #include -#include -#include #include "common/qtutils.h" #include "core.h" @@ -43,18 +41,13 @@ const int NodeParamViewItemBody::kArrayCollapseBtnColumn = 1; const int NodeParamViewItemBody::kLabelColumn = 2; const int NodeParamViewItemBody::kWidgetStartColumn = 3; -#define super QDockWidget +#define super NodeParamViewItemBase NodeParamViewItem::NodeParamViewItem(Node *node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget *parent) : super(parent), - node_(node), - highlighted_(false) + node_(node) { - // Create title bar widget - title_bar_ = new NodeParamViewItemTitleBar(this); - - // Add title bar to widget - this->setTitleBarWidget(title_bar_); + node_->Retranslate(); // Create and add contents widget body_ = new NodeParamViewItemBody(node_, create_checkboxes); @@ -62,109 +55,31 @@ NodeParamViewItem::NodeParamViewItem(Node *node, NodeParamViewCheckBoxBehavior c connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime); connect(body_, &NodeParamViewItemBody::ArrayExpandedChanged, this, &NodeParamViewItem::ArrayExpandedChanged); connect(body_, &NodeParamViewItemBody::InputCheckedChanged, this, &NodeParamViewItem::InputCheckedChanged); - connect(title_bar_, &NodeParamViewItemTitleBar::ExpandedStateChanged, this, &NodeParamViewItem::SetExpanded); - connect(title_bar_, &NodeParamViewItemTitleBar::PinToggled, this, &NodeParamViewItem::PinToggled); - - this->setWidget(body_); - - // Use dummy QWidget to retain width when not expanded (QDockWidget seems to ignore the titlebar - // size hints and will shrink as small as possible if the body is hidden) - hidden_body_ = new QWidget(this); + SetBody(body_); connect(node_, &Node::LabelChanged, this, &NodeParamViewItem::Retranslate); - setBackgroundRole(QPalette::Base); - setAutoFillBackground(true); - - setFocusPolicy(Qt::ClickFocus); + setBackgroundRole(QPalette::Window); Retranslate(); } -void NodeParamViewItem::SetTimeTarget(Node *target) -{ - body_->SetTimeTarget(target); -} - -void NodeParamViewItem::SetTime(const rational &time) -{ - time_ = time; - - body_->SetTime(time_); -} - -void NodeParamViewItem::SetTimebase(const rational& timebase) -{ - body_->SetTimebase(timebase); -} - -Node *NodeParamViewItem::GetNode() const -{ - return node_; -} - -void NodeParamViewItem::changeEvent(QEvent *e) -{ - if (e->type() == QEvent::LanguageChange) { - Retranslate(); - } - - super::changeEvent(e); -} - -void NodeParamViewItem::paintEvent(QPaintEvent *event) -{ - super::paintEvent(event); - - // Draw border if focused - if (highlighted_) { - QPainter p(this); - p.setBrush(Qt::NoBrush); - p.setPen(palette().highlight().color()); - p.drawRect(rect().adjusted(0, 0, -1, -1)); - } -} - -void NodeParamViewItem::moveEvent(QMoveEvent *event) -{ - super::moveEvent(event); - - emit Moved(); -} - void NodeParamViewItem::Retranslate() { node_->Retranslate(); - if (node_->GetLabel().isEmpty()) { - title_bar_->SetText(node_->Name()); - } else { - title_bar_->SetText(tr("%1 (%2)").arg(node_->GetLabel(), node_->Name())); - } + title_bar()->SetText(GetTitleBarTextFromNode(node_)); body_->Retranslate(); } -void NodeParamViewItem::SetExpanded(bool e) -{ - setWidget(e ? body_ : hidden_body_); - title_bar_->SetExpanded(e); - - emit ExpandedChanged(e); -} - -bool NodeParamViewItem::IsExpanded() const -{ - return body_->isVisible(); -} - int NodeParamViewItem::GetElementY(const NodeInput &c) const { if (IsExpanded()) { return body_->GetElementY(c); } else { // Not expanded, put keyframes at the titlebar Y - return mapToGlobal(title_bar_->rect().center()).y(); + return mapToGlobal(title_bar()->rect().center()).y(); } } @@ -173,63 +88,6 @@ void NodeParamViewItem::SetInputChecked(const NodeInput &input, bool e) body_->SetInputChecked(input, e); } -void NodeParamViewItem::ToggleExpanded() -{ - SetExpanded(!IsExpanded()); -} - -NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) : - QWidget(parent), - draw_border_(true) -{ - QHBoxLayout* layout = new QHBoxLayout(this); - - collapse_btn_ = new CollapseButton(); - connect(collapse_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::ExpandedStateChanged); - layout->addWidget(collapse_btn_); - - lbl_ = new QLabel(); - layout->addWidget(lbl_); - - // Place next buttons on the far side - layout->addStretch(); - - QPushButton* pin_btn = new QPushButton(QStringLiteral("P")); - pin_btn->setCheckable(true); - pin_btn->setFixedSize(pin_btn->sizeHint().height(), pin_btn->sizeHint().height()); - layout->addWidget(pin_btn); - connect(pin_btn, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::PinToggled); -} - -void NodeParamViewItemTitleBar::SetExpanded(bool e) -{ - draw_border_ = e; - collapse_btn_->setChecked(e); - - update(); -} - -void NodeParamViewItemTitleBar::paintEvent(QPaintEvent *event) -{ - QWidget::paintEvent(event); - - if (draw_border_) { - QPainter p(this); - - // Draw bottom border using text color - int bottom = height() - 1; - p.setPen(palette().text().color()); - p.drawLine(0, bottom, width(), bottom); - } -} - -void NodeParamViewItemTitleBar::mouseDoubleClickEvent(QMouseEvent *event) -{ - QWidget::mouseDoubleClickEvent(event); - - collapse_btn_->click(); -} - NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget *parent) : QWidget(parent), node_(node), @@ -241,42 +99,42 @@ NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBe // Create widgets all root level components foreach (QString input, node->inputs()) { - Node *n; - if (NodeGroup *g = dynamic_cast(node)) { + Node *n = node; + while (NodeGroup *g = dynamic_cast(n)) { const NodeInput &ni = g->GetInputPassthroughs().value(input); n = ni.node(); input = ni.input(); - } else { - n = node; } - CreateWidgets(root_layout, n, input, -1, insert_row); - - insert_row++; - - if (n->InputIsArray(input)) { - // Insert here - QWidget* array_widget = new QWidget(); - - QGridLayout* array_layout = new QGridLayout(array_widget); - array_layout->setContentsMargins(QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral(" ")), 0, 0, 0); - - root_layout->addWidget(array_widget, insert_row, 1, 1, 10); - - // Start with zero elements for efficiency. We will make the widgets for them if the user - // requests the array UI to be expanded - int arr_sz = 0; - - // Add one last add button for appending to the array - NodeParamViewArrayButton* append_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kAdd); - connect(append_btn, &NodeParamViewArrayButton::clicked, this, &NodeParamViewItemBody::ArrayAppendClicked); - array_layout->addWidget(append_btn, arr_sz, kArrayInsertColumn); - - array_widget->setVisible(false); - - array_ui_.insert({n, input}, {array_widget, arr_sz, append_btn}); + if (!(n->GetInputFlags(input) & kInputFlagHidden)) { + CreateWidgets(root_layout, n, input, -1, insert_row); insert_row++; + + if (n->InputIsArray(input)) { + // Insert here + QWidget* array_widget = new QWidget(); + + QGridLayout* array_layout = new QGridLayout(array_widget); + array_layout->setContentsMargins(QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral(" ")), 0, 0, 0); + + root_layout->addWidget(array_widget, insert_row, 1, 1, 10); + + // Start with zero elements for efficiency. We will make the widgets for them if the user + // requests the array UI to be expanded + int arr_sz = 0; + + // Add one last add button for appending to the array + NodeParamViewArrayButton* append_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kAdd); + connect(append_btn, &NodeParamViewArrayButton::clicked, this, &NodeParamViewItemBody::ArrayAppendClicked); + array_layout->addWidget(append_btn, arr_sz, kArrayInsertColumn); + + array_widget->setVisible(false); + + array_ui_.insert({n, input}, {array_widget, arr_sz, append_btn}); + + insert_row++; + } } } @@ -441,7 +299,7 @@ int NodeParamViewItemBody::GetElementY(NodeInput c) const c.set_element(-1); } - if (NodeGroup *g = dynamic_cast(c.node())) { + while (NodeGroup *g = dynamic_cast(c.node())) { const NodeInput &passthrough = g->GetInputPassthroughs().value(c.input()); c.set_node(passthrough.node()); c.set_input(passthrough.input()); @@ -503,7 +361,13 @@ void NodeParamViewItemBody::PlaceWidgetsFromBridge(QGridLayout* layout, NodePara void NodeParamViewItemBody::InputArraySizeChangedInternal(Node *node, const QString &input, int size) { - ArrayUI& array_ui = array_ui_[{node, input}]; + NodeInputPair nip = {node, input}; + + if (!array_ui_.contains(nip)) { + return; + } + + ArrayUI& array_ui = array_ui_[nip]; if (size != array_ui.count) { QGridLayout* grid = static_cast(array_ui.widget->layout()); diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 55a0a3174..445bcace2 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -22,7 +22,6 @@ #define NODEPARAMVIEWITEM_H #include -#include #include #include #include @@ -33,6 +32,7 @@ #include "nodeparamviewarraywidget.h" #include "nodeparamviewconnectedlabel.h" #include "nodeparamviewkeyframecontrol.h" +#include "nodeparamviewitembase.h" #include "nodeparamviewwidgetbridge.h" #include "widget/clickablelabel/clickablelabel.h" #include "widget/collapsebutton/collapsebutton.h" @@ -45,40 +45,6 @@ enum NodeParamViewCheckBoxBehavior { kCheckBoxesOnNonConnected }; -class NodeParamViewItemTitleBar : public QWidget -{ - Q_OBJECT -public: - NodeParamViewItemTitleBar(QWidget* parent = nullptr); - - void SetExpanded(bool e); - - void SetText(const QString& s) - { - lbl_->setText(s); - lbl_->setToolTip(s); - lbl_->setMinimumWidth(1); - } - -signals: - void ExpandedStateChanged(bool e); - - void PinToggled(bool e); - -protected: - virtual void paintEvent(QPaintEvent *event) override; - - virtual void mouseDoubleClickEvent(QMouseEvent *event) override; - -private: - bool draw_border_; - - QLabel* lbl_; - - CollapseButton* collapse_btn_; - -}; - class NodeParamViewItemBody : public QWidget { Q_OBJECT public: @@ -189,77 +155,57 @@ private slots: }; -class NodeParamViewItem : public QDockWidget +class NodeParamViewItem : public NodeParamViewItemBase { Q_OBJECT public: NodeParamViewItem(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget* parent = nullptr); - void SetTimeTarget(Node* target); - - void SetTime(const rational& time); - - // Set the timebase of the NodeParamViewItemBody - void SetTimebase(const rational& timebase); - - Node* GetNode() const; - - bool IsExpanded() const; - - void SetHighlighted(bool e) + void SetTimeTarget(Node* target) { - highlighted_ = e; + body_->SetTimeTarget(target); + } - update(); + void SetTime(const rational& time) + { + time_ = time; + + body_->SetTime(time_); + } + + void SetTimebase(const rational& timebase) + { + body_->SetTimebase(timebase); + } + + Node* GetNode() const + { + return node_; } int GetElementY(const NodeInput& c) const; void SetInputChecked(const NodeInput &input, bool e); -public slots: - void SetExpanded(bool e); - - void ToggleExpanded(); - signals: void RequestSetTime(const rational& time); void RequestSelectNode(const QVector& node); - void PinToggled(bool e); - - void ExpandedChanged(bool e); - void ArrayExpandedChanged(bool e); - void Moved(); - void InputCheckedChanged(const NodeInput &input, bool e); -protected: - virtual void changeEvent(QEvent *e) override; - - virtual void paintEvent(QPaintEvent *event) override; - - virtual void moveEvent(QMoveEvent *event) override; +protected slots: + virtual void Retranslate() override; private: - NodeParamViewItemTitleBar* title_bar_; - NodeParamViewItemBody* body_; - QWidget *hidden_body_; - Node* node_; rational time_; - bool highlighted_; - -private slots: - void Retranslate(); - }; } diff --git a/app/widget/nodeparamview/nodeparamviewitembase.cpp b/app/widget/nodeparamview/nodeparamviewitembase.cpp new file mode 100644 index 000000000..709514326 --- /dev/null +++ b/app/widget/nodeparamview/nodeparamviewitembase.cpp @@ -0,0 +1,114 @@ +/*** + + 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 "nodeparamviewitembase.h" + +#include +#include + +namespace olive { + +#define super QDockWidget + +NodeParamViewItemBase::NodeParamViewItemBase(QWidget *parent) : + super(parent), + highlighted_(false) +{ + // Create title bar widget + title_bar_ = new NodeParamViewItemTitleBar(this); + + // Add title bar to widget + this->setTitleBarWidget(title_bar_); + + // Connect title bar to this + connect(title_bar_, &NodeParamViewItemTitleBar::ExpandedStateChanged, this, &NodeParamViewItemBase::SetExpanded); + connect(title_bar_, &NodeParamViewItemTitleBar::PinToggled, this, &NodeParamViewItemBase::PinToggled); + + // Use dummy QWidget to retain width when not expanded (QDockWidget seems to ignore the titlebar + // size hints and will shrink as small as possible if the body is hidden) + hidden_body_ = new QWidget(this); + + setAutoFillBackground(true); + + setFocusPolicy(Qt::ClickFocus); +} + +bool NodeParamViewItemBase::IsExpanded() const +{ + return body_->isVisible(); +} + +QString NodeParamViewItemBase::GetTitleBarTextFromNode(Node *n) +{ + if (n->GetLabel().isEmpty()) { + return n->Name(); + } else { + return tr("%1 (%2)").arg(n->GetLabel(), n->Name()); + } +} + +void NodeParamViewItemBase::SetBody(QWidget *body) +{ + body_ = body; + body_->setParent(this); + + if (title_bar_->IsExpanded()) { + setWidget(body_); + } +} + +void NodeParamViewItemBase::paintEvent(QPaintEvent *event) +{ + super::paintEvent(event); + + // Draw border if focused + if (highlighted_) { + QPainter p(this); + p.setBrush(Qt::NoBrush); + p.setPen(palette().highlight().color()); + p.drawRect(rect().adjusted(0, 0, -1, -1)); + } +} + +void NodeParamViewItemBase::SetExpanded(bool e) +{ + setWidget(e ? body_ : hidden_body_); + title_bar_->SetExpanded(e); + + emit ExpandedChanged(e); +} + +void NodeParamViewItemBase::changeEvent(QEvent *e) +{ + if (e->type() == QEvent::LanguageChange) { + Retranslate(); + } + + super::changeEvent(e); +} + +void NodeParamViewItemBase::moveEvent(QMoveEvent *event) +{ + super::moveEvent(event); + + emit Moved(); +} + +} diff --git a/app/widget/nodeparamview/nodeparamviewitembase.h b/app/widget/nodeparamview/nodeparamviewitembase.h new file mode 100644 index 000000000..d5f570656 --- /dev/null +++ b/app/widget/nodeparamview/nodeparamviewitembase.h @@ -0,0 +1,93 @@ +/*** + + 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 NODEPARAMVIEWITEMBASE_H +#define NODEPARAMVIEWITEMBASE_H + +#include + +#include "nodeparamviewitemtitlebar.h" +#include "node/node.h" + +namespace olive { + +class NodeParamViewItemBase : public QDockWidget +{ + Q_OBJECT +public: + NodeParamViewItemBase(QWidget* parent = nullptr); + + void SetHighlighted(bool e) + { + highlighted_ = e; + + update(); + } + + bool IsExpanded() const; + + static QString GetTitleBarTextFromNode(Node *n); + +public slots: + void SetExpanded(bool e); + + void ToggleExpanded() + { + SetExpanded(!IsExpanded()); + } + +signals: + void PinToggled(bool e); + + void ExpandedChanged(bool e); + + void Moved(); + +protected: + void SetBody(QWidget *body); + + virtual void paintEvent(QPaintEvent *event) override; + + NodeParamViewItemTitleBar* title_bar() const + { + return title_bar_; + } + + virtual void changeEvent(QEvent *e) override; + + virtual void moveEvent(QMoveEvent *event) override; + +protected slots: + virtual void Retranslate(){} + +private: + NodeParamViewItemTitleBar* title_bar_; + + QWidget *body_; + + QWidget *hidden_body_; + + bool highlighted_; + +}; + +} + +#endif // NODEPARAMVIEWITEMBASE_H diff --git a/app/widget/nodeparamview/nodeparamviewitemtitlebar.cpp b/app/widget/nodeparamview/nodeparamviewitemtitlebar.cpp new file mode 100644 index 000000000..6d61bd1ba --- /dev/null +++ b/app/widget/nodeparamview/nodeparamviewitemtitlebar.cpp @@ -0,0 +1,90 @@ +/*** + + 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 "nodeparamviewitemtitlebar.h" + +#include +#include + +#include "ui/icons/icons.h" + +namespace olive { + +NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) : + QWidget(parent), + draw_border_(true) +{ + QHBoxLayout* layout = new QHBoxLayout(this); + + collapse_btn_ = new CollapseButton(); + connect(collapse_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::ExpandedStateChanged); + layout->addWidget(collapse_btn_); + + lbl_ = new QLabel(); + layout->addWidget(lbl_); + + // Place next buttons on the far side + layout->addStretch(); + + add_fx_btn_ = new QPushButton(); + add_fx_btn_->setIcon(icon::AddEffect); + add_fx_btn_->setFixedSize(add_fx_btn_->sizeHint().height(), add_fx_btn_->sizeHint().height()); + add_fx_btn_->setVisible(false); + layout->addWidget(add_fx_btn_); + connect(add_fx_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::AddEffectButtonClicked); + + pin_btn_ = new QPushButton(QStringLiteral("P")); + pin_btn_->setCheckable(true); + pin_btn_->setFixedSize(pin_btn_->sizeHint().height(), pin_btn_->sizeHint().height()); + pin_btn_->setVisible(false); + layout->addWidget(pin_btn_); + connect(pin_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::PinToggled); +} + +void NodeParamViewItemTitleBar::SetExpanded(bool e) +{ + draw_border_ = e; + collapse_btn_->setChecked(e); + + update(); +} + +void NodeParamViewItemTitleBar::paintEvent(QPaintEvent *event) +{ + QWidget::paintEvent(event); + + if (draw_border_) { + QPainter p(this); + + // Draw bottom border using text color + int bottom = height() - 1; + p.setPen(palette().text().color()); + p.drawLine(0, bottom, width(), bottom); + } +} + +void NodeParamViewItemTitleBar::mouseDoubleClickEvent(QMouseEvent *event) +{ + QWidget::mouseDoubleClickEvent(event); + + collapse_btn_->click(); +} + +} diff --git a/app/widget/nodeparamview/nodeparamviewitemtitlebar.h b/app/widget/nodeparamview/nodeparamviewitemtitlebar.h new file mode 100644 index 000000000..7ddaac1aa --- /dev/null +++ b/app/widget/nodeparamview/nodeparamviewitemtitlebar.h @@ -0,0 +1,89 @@ +/*** + + 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 NODEPARAMVIEWITEMTITLEBAR_H +#define NODEPARAMVIEWITEMTITLEBAR_H + +#include +#include + +#include "widget/collapsebutton/collapsebutton.h" + +namespace olive { + +class NodeParamViewItemTitleBar : public QWidget +{ + Q_OBJECT +public: + NodeParamViewItemTitleBar(QWidget* parent = nullptr); + + bool IsExpanded() const + { + return collapse_btn_->isChecked(); + } + +public slots: + void SetExpanded(bool e); + + void SetText(const QString& s) + { + lbl_->setText(s); + lbl_->setToolTip(s); + lbl_->setMinimumWidth(1); + } + + void SetPinButtonVisible(bool e) + { + pin_btn_->setVisible(e); + } + + void SetAddEffectButtonVisible(bool e) + { + add_fx_btn_->setVisible(e); + } + +signals: + void ExpandedStateChanged(bool e); + + void PinToggled(bool e); + + void AddEffectButtonClicked(); + +protected: + virtual void paintEvent(QPaintEvent *event) override; + + virtual void mouseDoubleClickEvent(QMouseEvent *event) override; + +private: + bool draw_border_; + + QLabel* lbl_; + + CollapseButton* collapse_btn_; + + QPushButton *pin_btn_; + + QPushButton *add_fx_btn_; + +}; + +} + +#endif // NODEPARAMVIEWITEMTITLEBAR_H diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 955228cbe..3c9a855ff 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -515,11 +515,11 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) if (!reconnected_to_itself) { Node *creating_output = create_edge_output_item_->GetNode(); - if (NodeGroup *output_group = dynamic_cast(creating_output)) { + while (NodeGroup *output_group = dynamic_cast(creating_output)) { creating_output = output_group->GetOutputPassthrough(); } - if (NodeGroup *input_group = dynamic_cast(creating_input.node())) { + while (NodeGroup *input_group = dynamic_cast(creating_input.node())) { creating_input = input_group->GetInputPassthroughs().value(creating_input.input()); } diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index d6bb565e9..04849fbb2 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -84,6 +84,9 @@ NodeViewItem::NodeViewItem(Node *node, const QString &input, int element, Node * } } else { output_connector_->setVisible(false); + + connect(node_, &Node::InputArraySizeChanged, this, &NodeViewItem::InputArraySizeChanged); + connect(node_, &Node::InputArraySizeChanged, this, &NodeViewItem::InputArraySizeChanged); } // This should be set during runtime, but just in case here's a default fallback @@ -740,14 +743,20 @@ void NodeViewItem::RepopulateInputs() } input_connector_->setVisible(has_connectable_inputs_); + } - if (IsExpanded()) { - // Create or remove inputs when necessary - } - } else { - if (IsExpanded() && element_ == -1) { - // Create or remove array elements when necessary - } + if (IsExpanded() && (IsOutputItem() || element_ == -1)) { + // Create or remove inputs when necessary + // NOTE: This is not the most efficient thing in the world, but it does work + SetExpanded(false); + SetExpanded(true); + } +} + +void NodeViewItem::InputArraySizeChanged(const QString &input) +{ + if (input == input_) { + RepopulateInputs(); } } diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index bbd6ec395..898c49a41 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -222,6 +222,8 @@ private slots: void RepopulateInputs(); + void InputArraySizeChanged(const QString &input); + }; } diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index ddc0e6c0c..c26b9bdf9 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -720,6 +720,7 @@ void MainWindow::UpdateNodePanelContextFromTimelinePanel(TimelinePanel *panel) } node_panel_->SetContexts(context); + param_panel_->SetContexts(context); } void MainWindow::FocusedPanelChanged(PanelWidget *panel) From 7d82d56f31371ea096a898f73d2b9e43a4d833cc Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 13 Dec 2021 14:12:30 -0800 Subject: [PATCH 18/34] reworked much of keyframeview for new architecture --- CMakeLists.txt | 3 + app/panel/param/param.cpp | 1 - app/panel/param/param.h | 2 - app/widget/curvewidget/curveview.cpp | 154 +++--- app/widget/curvewidget/curveview.h | 18 +- app/widget/curvewidget/curvewidget.cpp | 43 +- app/widget/curvewidget/curvewidget.h | 6 - app/widget/keyframeview/CMakeLists.txt | 10 +- app/widget/keyframeview/keyframeview.cpp | 19 - app/widget/keyframeview/keyframeview.h | 8 +- app/widget/keyframeview/keyframeviewbase.cpp | 441 +++++++++--------- app/widget/keyframeview/keyframeviewbase.h | 75 +-- .../keyframeviewinputconnection.cpp | 83 ++++ .../keyframeviewinputconnection.h | 88 ++++ app/widget/keyframeview/keyframeviewitem.cpp | 129 ----- app/widget/keyframeview/keyframeviewitem.h | 68 --- app/widget/nodeparamview/nodeparamview.cpp | 132 ++---- app/widget/nodeparamview/nodeparamview.h | 8 - app/widget/nodeparamview/nodeparamviewitem.h | 13 + app/widget/timebased/CMakeLists.txt | 2 + .../timebasedviewselectionmanager.cpp | 26 ++ .../timebased/timebasedviewselectionmanager.h | 256 ++++++++++ app/widget/timebased/timescaledobject.cpp | 12 + app/widget/timebased/timescaledobject.h | 2 + app/window/mainwindow/mainwindow.cpp | 7 +- 25 files changed, 852 insertions(+), 754 deletions(-) create mode 100644 app/widget/keyframeview/keyframeviewinputconnection.cpp create mode 100644 app/widget/keyframeview/keyframeviewinputconnection.h delete mode 100644 app/widget/keyframeview/keyframeviewitem.cpp delete mode 100644 app/widget/keyframeview/keyframeviewitem.h create mode 100644 app/widget/timebased/timebasedviewselectionmanager.cpp create mode 100644 app/widget/timebased/timebasedviewselectionmanager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e8c28db2a..2b3ad5a73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,9 @@ else() endif() set(OLIVE_DEFINITIONS -DQT_DEPRECATED_WARNINGS) +if (WIN32) + list(APPEND OLIVE_DEFINITIONS -DUNICODE -D_UNICODE) +endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") diff --git a/app/panel/param/param.cpp b/app/panel/param/param.cpp index dc8f5ea45..bf44d5db3 100644 --- a/app/panel/param/param.cpp +++ b/app/panel/param/param.cpp @@ -29,7 +29,6 @@ ParamPanel::ParamPanel(QWidget* parent) : { NodeParamView* view = new NodeParamView(); connect(view, &NodeParamView::RequestSelectNode, this, &ParamPanel::RequestSelectNode); - connect(view, &NodeParamView::NodeOrderChanged, this, &ParamPanel::NodeOrderChanged); connect(view, &NodeParamView::FocusedNodeChanged, this, &ParamPanel::FocusedNodeChanged); SetTimeBasedWidget(view); diff --git a/app/panel/param/param.h b/app/panel/param/param.h index c6af767f9..e298ccf3e 100644 --- a/app/panel/param/param.h +++ b/app/panel/param/param.h @@ -48,8 +48,6 @@ public slots: signals: void RequestSelectNode(const QVector& target); - void NodeOrderChanged(const QVector& nodes); - void FocusedNodeChanged(Node* n); protected: diff --git a/app/widget/curvewidget/curveview.cpp b/app/widget/curvewidget/curveview.cpp index 80108a0d5..e83909702 100644 --- a/app/widget/curvewidget/curveview.cpp +++ b/app/widget/curvewidget/curveview.cpp @@ -47,22 +47,6 @@ CurveView::CurveView(QWidget *parent) : connect(scene(), &QGraphicsScene::selectionChanged, this, &CurveView::SelectionChanged); } -CurveView::~CurveView() -{ - // Quick way to avoid segfault when QGraphicsScene::selectionChanged is emitted after other members have been destroyed - Clear(); -} - -void CurveView::Clear() -{ - KeyframeViewBase::Clear(); - - foreach (QGraphicsLineItem* line, lines_) { - delete line; - } - lines_.clear(); -} - void CurveView::ConnectInput(const NodeKeyframeTrackReference& ref) { if (connected_inputs_.contains(ref)) { @@ -71,7 +55,9 @@ void CurveView::ConnectInput(const NodeKeyframeTrackReference& ref) } // Add keyframes from track - AddKeyframesOfTrack(ref); + KeyframeViewInputConnection *track_con = AddKeyframesOfTrack(ref); + track_con->SetBrush(keyframe_colors_.value(ref)); + track_connections_.insert(ref, track_con); // Append to the list connected_inputs_.append(ref); @@ -85,7 +71,7 @@ void CurveView::DisconnectInput(const NodeKeyframeTrackReference& ref) } // Remove keyframes belonging to this element and track - RemoveKeyframesOfTrack(ref); + RemoveKeyframesOfTrack(track_connections_.take(ref)); // Remove from the list connected_inputs_.removeOne(ref); @@ -95,24 +81,16 @@ void CurveView::SelectKeyframesOfInput(const NodeKeyframeTrackReference& ref) { DeselectAll(); - for (auto it=item_map().cbegin(); it!=item_map().cend(); it++) { - if (it.key()->key_track_ref() == ref) { - it.value()->setSelected(true); + foreach (KeyframeViewInputConnection *con, track_connections_) { + foreach (NodeKeyframe *key, con->GetKeyframes()) { + SelectKeyframe(key); } } } void CurveView::ZoomToFitInput(const NodeKeyframeTrackReference& ref) { - QList keys; - - for (auto it=item_map().cbegin(); it!=item_map().cend(); it++) { - if (it.key()->key_track_ref() == ref) { - keys.append(it.key()); - } - } - - ZoomToFitInternal(keys); + ZoomToFitInternal(track_connections_.value(ref)->GetKeyframes()); } void CurveView::SetKeyframeTrackColor(const NodeKeyframeTrackReference &ref, const QColor &color) @@ -121,11 +99,7 @@ void CurveView::SetKeyframeTrackColor(const NodeKeyframeTrackReference &ref, con keyframe_colors_.insert(ref, color); // Update all keyframes - for (auto it=item_map().cbegin(); it!=item_map().cend(); it++) { - if (it.key()->key_track_ref() == ref) { - it.value()->SetOverrideBrush(color); - } - } + track_connections_.value(ref)->SetBrush(color); } void CurveView::drawBackground(QPainter *painter, const QRectF &rect) @@ -196,7 +170,7 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect) QPainterPath path; // Draw straight line leading to first keyframe - QPointF first_key_pos = item_map().value(track.first())->pos(); + QPointF first_key_pos = GetKeyframePosition(track.first()); path.moveTo(QPointF(scene_bottom_left.x(), first_key_pos.y())); path.lineTo(first_key_pos); @@ -205,24 +179,24 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect) NodeKeyframe* before = track.at(i-1); NodeKeyframe* after = track.at(i); - KeyframeViewItem* before_item = item_map().value(before); - KeyframeViewItem* after_item = item_map().value(after); + QPointF before_pos = GetKeyframePosition(before); + QPointF after_pos = GetKeyframePosition(after); if (before->type() == NodeKeyframe::kHold) { // Draw a hold keyframe (basically a right angle) - path.lineTo(after_item->pos().x(), before_item->pos().y()); - path.lineTo(after_item->pos().x(), after_item->pos().y()); + path.lineTo(after_pos.x(), before_pos.y()); + path.lineTo(after_pos.x(), after_pos.y()); } else if (before->type() == NodeKeyframe::kBezier && after->type() == NodeKeyframe::kBezier) { // Draw a cubic bezier // Cubic beziers have two control points, so we can just use both - QPointF before_control_point = before_item->pos() + ScalePoint(before->valid_bezier_control_out()); - QPointF after_control_point = after_item->pos() + ScalePoint(after->valid_bezier_control_in()); + QPointF before_control_point = before_pos + ScalePoint(before->valid_bezier_control_out()); + QPointF after_control_point = after_pos + ScalePoint(after->valid_bezier_control_in()); - path.cubicTo(before_control_point, after_control_point, after_item->pos()); + path.cubicTo(before_control_point, after_control_point, after_pos); } else if (before->type() == NodeKeyframe::kBezier || after->type() == NodeKeyframe::kBezier) { // Draw a quadratic bezier @@ -232,10 +206,10 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect) QPointF control_point; if (before->type() == NodeKeyframe::kBezier) { - key_anchor = before_item->pos(); + key_anchor = before_pos; control_point = before->valid_bezier_control_out(); } else { - key_anchor = after_item->pos(); + key_anchor = after_pos; control_point = after->valid_bezier_control_in(); } @@ -243,18 +217,18 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect) control_point = key_anchor + ScalePoint(control_point); // Create the path from both keyframes - path.quadTo(control_point, after_item->pos()); + path.quadTo(control_point, after_pos); } else { // Linear to linear - path.lineTo(after_item->pos()); + path.lineTo(after_pos); } } // Draw straight line leading from end keyframe - QPointF last_key_pos = item_map().value(track.last())->pos(); + QPointF last_key_pos = GetKeyframePosition(track.last()); path.lineTo(QPointF(scene_top_right.x(), last_key_pos.y())); painter->drawPath(path); @@ -263,7 +237,7 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect) } // Draw bezier control point lines - if (!bezier_control_points_.isEmpty()) { + /*if (!bezier_control_points_.isEmpty()) { painter->setPen(QPen(palette().text().color(), 1)); QVector bezier_lines; @@ -274,13 +248,7 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect) bezier_lines.append(QLineF(par->pos(), par->pos() + item->pos())); } painter->drawLines(bezier_lines); - } -} - -void CurveView::KeyframeAboutToBeRemoved(NodeKeyframe *key) -{ - disconnect(key, &NodeKeyframe::ValueChanged, this, &CurveView::KeyframeValueChanged); - disconnect(key, &NodeKeyframe::TypeChanged, this, &CurveView::KeyframeTypeChanged); + }*/ } void CurveView::ScaleChangedEvent(const double& scale) @@ -296,13 +264,11 @@ void CurveView::VerticalScaleChangedEvent(double scale) { Q_UNUSED(scale) - for (auto iterator=item_map().begin();iterator!=item_map().end();iterator++) { - SetItemYFromKeyframeValue(iterator.value()->key(), iterator.value()); - } - foreach (BezierControlPointItem* item, bezier_control_points_) { item->SetYScale(scale); } + + viewport()->update(); } void CurveView::ContextMenuEvent(Menu &m) @@ -324,7 +290,7 @@ void CurveView::SceneRectUpdateEvent(QRectF &r) r.setBottom(r.bottom() + this->height()); } -void CurveView::ZoomToFitInternal(const QList &keys) +void CurveView::ZoomToFitInternal(const QVector &keys) { if (keys.isEmpty()) { // Prevent scaling to DBL_MIN/DBL_MAX @@ -373,53 +339,58 @@ qreal CurveView::GetItemYFromKeyframeValue(double value) return -value * GetYScale(); } -void CurveView::SetItemYFromKeyframeValue(NodeKeyframe *key, KeyframeViewItem *item) -{ - item->SetOverrideY(GetItemYFromKeyframeValue(key)); -} - QPointF CurveView::ScalePoint(const QPointF &point) { // Flips Y coordinate because curves are drawn bottom to top return QPointF(point.x() * GetScale(), - point.y() * GetYScale()); } -void CurveView::CreateBezierControlPoints(KeyframeViewItem* item) +void CurveView::CreateBezierControlPoints(NodeKeyframe* item) { - BezierControlPointItem* bezier_in_pt = new BezierControlPointItem(item->key(), NodeKeyframe::kInHandle, item); + qDebug() << "STUB!"; + /*BezierControlPointItem* bezier_in_pt = new BezierControlPointItem(item, NodeKeyframe::kInHandle, item); bezier_in_pt->SetXScale(GetScale()); bezier_in_pt->SetYScale(GetYScale()); bezier_control_points_.append(bezier_in_pt); connect(bezier_in_pt, &QObject::destroyed, this, &CurveView::BezierControlPointDestroyed, Qt::DirectConnection); - BezierControlPointItem* bezier_out_pt = new BezierControlPointItem(item->key(), NodeKeyframe::kOutHandle, item); + BezierControlPointItem* bezier_out_pt = new BezierControlPointItem(item, NodeKeyframe::kOutHandle, item); bezier_out_pt->SetXScale(GetScale()); bezier_out_pt->SetYScale(GetYScale()); bezier_control_points_.append(bezier_out_pt); - connect(bezier_out_pt, &QObject::destroyed, this, &CurveView::BezierControlPointDestroyed, Qt::DirectConnection); + connect(bezier_out_pt, &QObject::destroyed, this, &CurveView::BezierControlPointDestroyed, Qt::DirectConnection);*/ +} + +QPointF CurveView::GetKeyframePosition(NodeKeyframe *key) +{ + return QPointF(GetKeyframeSceneX(key), GetItemYFromKeyframeValue(key)); } void CurveView::KeyframeValueChanged() { - NodeKeyframe* key = static_cast(sender()); + qDebug() << "STUB!"; + /*NodeKeyframe* key = static_cast(sender()); KeyframeViewItem* item = item_map().value(key); - SetItemYFromKeyframeValue(key, item); + SetItemYFromKeyframeValue(key, item);*/ } void CurveView::KeyframeTypeChanged() { - NodeKeyframe* key = static_cast(sender()); + qDebug() << "STUB!"; + /*NodeKeyframe* key = static_cast(sender()); KeyframeViewItem* item = item_map().value(key); if (item->isSelected()) { item->setSelected(false); item->setSelected(true); - } + }*/ } void CurveView::SelectionChanged() { + qDebug() << "STUB!"; + /* // Clear current bezier handles while (!bezier_control_points_.isEmpty()) { delete bezier_control_points_.first(); @@ -434,6 +405,7 @@ void CurveView::SelectionChanged() CreateBezierControlPoints(this_item); } } + */ } void CurveView::BezierControlPointDestroyed() @@ -444,20 +416,22 @@ void CurveView::BezierControlPointDestroyed() void CurveView::ZoomToFit() { - ZoomToFitInternal(item_map().keys()); + QVector keys; + + foreach (KeyframeViewInputConnection *con, track_connections_) { + foreach (NodeKeyframe *k, con->GetKeyframes()) { + if (!keys.contains(k)) { + keys.append(k); + } + } + } + + ZoomToFitInternal(keys); } void CurveView::ZoomToFitSelected() { - QList selected_keys; - - for (auto it=item_map().cbegin(); it!=item_map().cend(); it++) { - if (it.value()->isSelected()) { - selected_keys.append(it.key()); - } - } - - ZoomToFitInternal(selected_keys); + ZoomToFitInternal(GetSelectedKeyframes()); } void CurveView::ResetZoom() @@ -466,16 +440,4 @@ void CurveView::ResetZoom() SetYScale(1.0); } -KeyframeViewItem* CurveView::AddKeyframe(NodeKeyframe* key) -{ - KeyframeViewItem* item = super::AddKeyframe(key); - SetItemYFromKeyframeValue(key, item); - item->SetOverrideBrush(keyframe_colors_.value(key->key_track_ref())); - - connect(key, &NodeKeyframe::ValueChanged, this, &CurveView::KeyframeValueChanged); - connect(key, &NodeKeyframe::TypeChanged, this, &CurveView::KeyframeTypeChanged); - - return item; -} - } diff --git a/app/widget/curvewidget/curveview.h b/app/widget/curvewidget/curveview.h index 6a4e155c7..e5b056830 100644 --- a/app/widget/curvewidget/curveview.h +++ b/app/widget/curvewidget/curveview.h @@ -24,7 +24,6 @@ #include "beziercontrolpointitem.h" #include "node/keyframe.h" #include "widget/keyframeview/keyframeview.h" -#include "widget/keyframeview/keyframeviewitem.h" namespace olive { @@ -34,10 +33,6 @@ class CurveView : public KeyframeViewBase public: CurveView(QWidget* parent = nullptr); - virtual ~CurveView() override; - - virtual void Clear() override; - void ConnectInput(const NodeKeyframeTrackReference &ref); void DisconnectInput(const NodeKeyframeTrackReference &ref); @@ -49,8 +44,6 @@ public: void SetKeyframeTrackColor(const NodeKeyframeTrackReference& ref, const QColor& color); public slots: - virtual KeyframeViewItem* AddKeyframe(NodeKeyframe* key) override; - void ZoomToFit(); void ZoomToFitSelected(); @@ -60,8 +53,6 @@ public slots: protected: virtual void drawBackground(QPainter* painter, const QRectF& rect) override; - virtual void KeyframeAboutToBeRemoved(NodeKeyframe *key) override; - virtual void ScaleChangedEvent(const double &scale) override; virtual void VerticalScaleChangedEvent(double scale) override; @@ -71,20 +62,21 @@ protected: virtual void SceneRectUpdateEvent(QRectF &r) override; private: - void ZoomToFitInternal(const QList &keys); + void ZoomToFitInternal(const QVector &keys); qreal GetItemYFromKeyframeValue(NodeKeyframe* key); qreal GetItemYFromKeyframeValue(double value); - void SetItemYFromKeyframeValue(NodeKeyframe* key, KeyframeViewItem* item); - QPointF ScalePoint(const QPointF& point); void AdjustLines(); - void CreateBezierControlPoints(KeyframeViewItem *item); + void CreateBezierControlPoints(NodeKeyframe *item); + + QPointF GetKeyframePosition(NodeKeyframe *key); QHash keyframe_colors_; + QHash track_connections_; int text_padding_; diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index 4de5787ef..c77a553aa 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -113,12 +113,6 @@ CurveWidget::CurveWidget(QWidget *parent) : SetScale(120.0); } -CurveWidget::~CurveWidget() -{ - // Quick way to avoid segfault when QGraphicsScene::selectionChanged is emitted after other members have been destroyed - view_->Clear(); -} - const double &CurveWidget::GetVerticalScale() { return view_->GetYScale(); @@ -223,15 +217,6 @@ void CurveWidget::ConnectNode(Node *node, bool connect) ConnectInput(node, input, connect); } } - - // Connect add/remove signals - if (connect) { - QObject::connect(node, &Node::KeyframeAdded, this, &CurveWidget::AddKeyframe); - QObject::connect(node, &Node::KeyframeRemoved, this, &CurveWidget::RemoveKeyframe); - } else { - QObject::disconnect(node, &Node::KeyframeAdded, this, &CurveWidget::AddKeyframe); - QObject::disconnect(node, &Node::KeyframeRemoved, this, &CurveWidget::RemoveKeyframe); - } } void CurveWidget::ConnectInput(Node *node, const QString &input, bool connect) @@ -287,20 +272,20 @@ void CurveWidget::ConnectInput(Node *node, const QString &input, bool connect) void CurveWidget::SelectionChanged() { - QList selected = view_->scene()->selectedItems(); + const QVector &selected = view_->GetSelectedKeyframes(); SetKeyframeButtonChecked(false); SetKeyframeButtonEnabled(!selected.isEmpty()); if (!selected.isEmpty()) { bool all_same_type = true; - NodeKeyframe::Type type = static_cast(selected.first())->key()->type(); + NodeKeyframe::Type type = selected.first()->type(); for (int i=1;i(selected.at(i-1)); - KeyframeViewItem* this_item = static_cast(selected.at(i)); + NodeKeyframe* prev_item = selected.at(i-1); + NodeKeyframe* this_item = selected.at(i); - if (prev_item->key()->type() != this_item->key()->type()) { + if (prev_item->type() != this_item->type()) { all_same_type = false; break; } @@ -323,7 +308,7 @@ void CurveWidget::KeyframeTypeButtonTriggered(bool checked) } // Get selected items and do nothing if there are none - QList selected = view_->scene()->selectedItems(); + const QVector &selected = view_->GetSelectedKeyframes(); if (selected.isEmpty()) { return; } @@ -345,10 +330,8 @@ void CurveWidget::KeyframeTypeButtonTriggered(bool checked) MultiUndoCommand* command = new MultiUndoCommand(); - foreach (QGraphicsItem* item, selected) { - KeyframeViewItem* key_item = static_cast(item); - - command->add_child(new KeyframeSetTypeCommand(key_item->key(), new_type)); + foreach (NodeKeyframe* item, selected) { + command->add_child(new KeyframeSetTypeCommand(item, new_type)); } Core::instance()->undo_stack()->push(command); @@ -368,16 +351,6 @@ void CurveWidget::InputEnabledChanged(const NodeKeyframeTrackReference& ref, boo } } -void CurveWidget::AddKeyframe(NodeKeyframe *key) -{ - view_->AddKeyframe(key); -} - -void CurveWidget::RemoveKeyframe(NodeKeyframe *key) -{ - view_->RemoveKeyframe(key); -} - void CurveWidget::InputSelectionChanged(const NodeKeyframeTrackReference& ref) { key_control_->SetInput(ref.input()); diff --git a/app/widget/curvewidget/curvewidget.h b/app/widget/curvewidget/curvewidget.h index 7b9e01021..db14f2955 100644 --- a/app/widget/curvewidget/curvewidget.h +++ b/app/widget/curvewidget/curvewidget.h @@ -40,8 +40,6 @@ class CurveWidget : public TimeBasedWidget, public TimeTargetObject public: CurveWidget(QWidget* parent = nullptr); - virtual ~CurveWidget() override; - const double& GetVerticalScale(); void SetVerticalScale(const double& vscale); @@ -107,10 +105,6 @@ private slots: void InputEnabledChanged(const NodeKeyframeTrackReference &ref, bool e); - void AddKeyframe(NodeKeyframe* key); - - void RemoveKeyframe(NodeKeyframe* key); - void InputSelectionChanged(const NodeKeyframeTrackReference& ref); void InputDoubleClicked(const NodeKeyframeTrackReference& ref); diff --git a/app/widget/keyframeview/CMakeLists.txt b/app/widget/keyframeview/CMakeLists.txt index c4d5ef43e..1ab460182 100644 --- a/app/widget/keyframeview/CMakeLists.txt +++ b/app/widget/keyframeview/CMakeLists.txt @@ -16,13 +16,13 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} - widget/keyframeview/keyframeview.h widget/keyframeview/keyframeview.cpp - widget/keyframeview/keyframeviewbase.h + widget/keyframeview/keyframeview.h widget/keyframeview/keyframeviewbase.cpp - widget/keyframeview/keyframeviewitem.h - widget/keyframeview/keyframeviewitem.cpp - widget/keyframeview/keyframeviewundo.h + widget/keyframeview/keyframeviewbase.h + widget/keyframeview/keyframeviewinputconnection.cpp + widget/keyframeview/keyframeviewinputconnection.h widget/keyframeview/keyframeviewundo.cpp + widget/keyframeview/keyframeviewundo.h PARENT_SCOPE ) diff --git a/app/widget/keyframeview/keyframeview.cpp b/app/widget/keyframeview/keyframeview.cpp index bba5f29f9..f8cf5413f 100644 --- a/app/widget/keyframeview/keyframeview.cpp +++ b/app/widget/keyframeview/keyframeview.cpp @@ -31,29 +31,10 @@ KeyframeView::KeyframeView(QWidget *parent) : setAlignment(Qt::AlignLeft | Qt::AlignTop); } -void KeyframeView::SetElementY(const NodeInput &c, int y) -{ - qreal scene_y = mapToScene(mapFromGlobal(QPoint(0, y))).y(); - - element_y_.insert(c, scene_y); - - for (auto it=item_map().cbegin(); it!=item_map().cend(); it++) { - if (it.key()->key_track_ref().input() == c) { - it.value()->SetOverrideY(scene_y); - } - } -} void KeyframeView::SceneRectUpdateEvent(QRectF &rect) { rect.setY(0); rect.setHeight(max_scroll_); } -KeyframeViewItem* KeyframeView::AddKeyframe(NodeKeyframe* key) -{ - KeyframeViewItem* item = super::AddKeyframe(key); - item->SetOverrideY(element_y_.value(key->key_track_ref().input())); - return item; -} - } diff --git a/app/widget/keyframeview/keyframeview.h b/app/widget/keyframeview/keyframeview.h index 9b7f76d89..56874232a 100644 --- a/app/widget/keyframeview/keyframeview.h +++ b/app/widget/keyframeview/keyframeview.h @@ -34,19 +34,13 @@ public: void SetMaxScroll(int i) { max_scroll_ = i; + UpdateSceneRect(); } - void SetElementY(const NodeInput& c, int y); - protected: virtual void SceneRectUpdateEvent(QRectF& rect) override; -public slots: - virtual KeyframeViewItem* AddKeyframe(NodeKeyframe* key) override; - private: - QHash element_y_; - int max_scroll_; }; diff --git a/app/widget/keyframeview/keyframeviewbase.cpp b/app/widget/keyframeview/keyframeviewbase.cpp index 5d0384708..c3be1e273 100644 --- a/app/widget/keyframeview/keyframeviewbase.cpp +++ b/app/widget/keyframeview/keyframeviewbase.cpp @@ -24,6 +24,7 @@ #include #include +#include "common/qtutils.h" #include "dialog/keyframeproperties/keyframeproperties.h" #include "keyframeviewundo.h" #include "node/node.h" @@ -33,201 +34,144 @@ namespace olive { +#define super TimeBasedView + KeyframeViewBase::KeyframeViewBase(QWidget *parent) : - TimeBasedView(parent), + super(parent), dragging_bezier_point_(nullptr), currently_autoselecting_(false), - dragging_(false) + dragging_(false), + selection_manager_(this) { SetDefaultDragMode(RubberBandDrag); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &KeyframeViewBase::customContextMenuRequested, this, &KeyframeViewBase::ShowContextMenu); - connect(scene(), &QGraphicsScene::selectionChanged, this, &KeyframeViewBase::AutoSelectKeyTimeNeighbors); -} - -void KeyframeViewBase::Clear() -{ - QMap::iterator iterator; - - for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) { - delete iterator.value(); - } - - item_map_.clear(); } void KeyframeViewBase::DeleteSelected() { MultiUndoCommand* command = new MultiUndoCommand(); - QMap::const_iterator i; - - for (i=item_map_.constBegin(); i!=item_map_.constEnd(); i++) { - if (i.value()->isSelected()) { - command->add_child(new NodeParamRemoveKeyframeCommand(i.key())); - } + foreach (NodeKeyframe *key, GetSelectedKeyframes()) { + command->add_child(new NodeParamRemoveKeyframeCommand(key)); } Core::instance()->undo_stack()->pushIfHasChildren(command); } -void KeyframeViewBase::AddKeyframesOfNode(Node *n) +KeyframeViewBase::NodeConnections KeyframeViewBase::AddKeyframesOfNode(Node *n) { + NodeConnections map; + foreach (const QString& i, n->inputs()) { - AddKeyframesOfInput(n, i); + map.insert(i, AddKeyframesOfInput(n, i)); } + + return map; } -void KeyframeViewBase::AddKeyframesOfInput(Node* n, const QString& input) +KeyframeViewBase::InputConnections KeyframeViewBase::AddKeyframesOfInput(Node* n, const QString& input) { - if (!n->IsInputKeyframable(input)) { - return; + InputConnections vec; + + if (n->IsInputKeyframable(input)) { + int arr_sz = n->InputArraySize(input); + vec.resize(arr_sz + 1); + for (int i=-1; iInputArraySize(input); - for (int i=-1; i& tracks = input.node()->GetKeyframeTracks(input); + ElementConnections vec(tracks.size()); for (int i=0; i& tracks = ref.input().node()->GetKeyframeTracks(ref.input()); - const NodeKeyframeTrack& t = tracks.at(ref.track()); - - foreach (NodeKeyframe* key, t) { - AddKeyframe(key); - } + KeyframeViewInputConnection *track = new KeyframeViewInputConnection(ref, this); + connect(track, &KeyframeViewInputConnection::RequireUpdate, this, &KeyframeViewBase::Redraw); + tracks_.append(track); + Redraw(); + return track; } -void KeyframeViewBase::RemoveKeyframesOfNode(Node *n) +void KeyframeViewBase::RemoveKeyframesOfTrack(KeyframeViewInputConnection *connection) { - foreach (const QString& i, n->inputs()) { - RemoveKeyframesOfInput(n, i); - } -} - -void KeyframeViewBase::RemoveKeyframesOfInput(Node* n, const QString& input) -{ - if (!n->IsInputKeyframable(input)) { - return; - } - - int arr_sz = n->InputArraySize(input); - for (int i=-1; i& tracks = input.node()->GetKeyframeTracks(input); - - for (int i=0; i& tracks = ref.input().node()->GetKeyframeTracks(ref.input()); - const NodeKeyframeTrack& t = tracks.at(ref.track()); - - foreach (NodeKeyframe* key, t) { - RemoveKeyframe(key); + if (tracks_.removeOne(connection)) { + delete connection; + Redraw(); } } void KeyframeViewBase::SelectAll() { - for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) { - it.value()->setSelected(true); + foreach (KeyframeViewInputConnection *track, tracks_) { + foreach (NodeKeyframe *key, track->GetKeyframes()) { + SelectKeyframe(key); + } } } void KeyframeViewBase::DeselectAll() { - for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) { - it.value()->setSelected(false); - } + selection_manager_.ClearSelection(); + + Redraw(); } -void KeyframeViewBase::RemoveKeyframe(NodeKeyframe* key) +void KeyframeViewBase::Clear() { - KeyframeAboutToBeRemoved(key); - - delete item_map_.take(key); -} - -KeyframeViewItem *KeyframeViewBase::AddKeyframe(NodeKeyframe* key) -{ - KeyframeViewItem* item = item_map_.value(key); - - if (!item) { - item = new KeyframeViewItem(key); - item->SetTimeTarget(GetTimeTarget()); - item->SetScale(GetScale()); - item_map_.insert(key, item); - scene()->addItem(item); + if (!tracks_.isEmpty()) { + qDeleteAll(tracks_); + tracks_.clear(); + Redraw(); } - - return item; } void KeyframeViewBase::mousePressEvent(QMouseEvent *event) { - QGraphicsItem* item_under_cursor = itemAt(event->pos()); + NodeKeyframe *key_under_cursor = selection_manager_.MousePress(event); + if (key_under_cursor) { + AutoSelectKeyTimeNeighbors(); + } - if (HandPress(event) || (!item_under_cursor && PlayheadPress(event))) { + BezierControlPointItem *bezier_under_cursor = dynamic_cast(itemAt(event->pos())); + + Redraw(); + + if (HandPress(event) || (!bezier_under_cursor && !key_under_cursor && PlayheadPress(event))) { return; } - active_tool_ = Core::instance()->tool(); - if (event->button() == Qt::LeftButton) { - QGraphicsView::mousePressEvent(event); + if (key_under_cursor || bezier_under_cursor) { + dragging_ = true; + drag_start_ = mapToScene(event->pos()); - if (active_tool_ == Tool::kPointer) { - if (item_under_cursor) { + // Determine what type of item is under the cursor + dragging_bezier_point_ = bezier_under_cursor; - dragging_ = true; - drag_start_ = mapToScene(event->pos()); + if (dragging_bezier_point_) { - // Determine what type of item is under the cursor - dragging_bezier_point_ = dynamic_cast(item_under_cursor); + dragging_bezier_point_start_ = dragging_bezier_point_->GetCorrespondingKeyframeHandle(); + dragging_bezier_point_opposing_start_ = dragging_bezier_point_->key()->bezier_control(NodeKeyframe::get_opposing_bezier_type(dragging_bezier_point_->mode())); - if (dragging_bezier_point_) { + } else { - dragging_bezier_point_start_ = dragging_bezier_point_->GetCorrespondingKeyframeHandle(); - dragging_bezier_point_opposing_start_ = dragging_bezier_point_->key()->bezier_control(NodeKeyframe::get_opposing_bezier_type(dragging_bezier_point_->mode())); + selection_manager_.DragStart(key_under_cursor, event); - } else { - - QList selected_items = scene()->selectedItems(); - - selected_keys_.resize(selected_items.size()); - - initial_drag_item_ = static_cast(item_under_cursor); - - for (int i=0;i(selected_items.at(i)); - - selected_keys_.replace(i, {key, - key->x(), - GetAdjustedTime(key->key()->parent(), GetTimeTarget(), key->key()->time(), false), - key->key()->value().toDouble()}); - } - } } } } @@ -240,11 +184,10 @@ void KeyframeViewBase::mouseMoveEvent(QMouseEvent *event) } if (event->buttons() & Qt::LeftButton) { - QGraphicsView::mouseMoveEvent(event); - if (dragging_) { // Calculate cursor difference and scale it - QPointF mouse_diff_scaled = GetScaledCursorPos(mapToScene(event->pos()) - drag_start_); + QPointF scene_pos = mapToScene(event->pos()); + QPointF mouse_diff_scaled = GetScaledCursorPos(scene_pos - drag_start_); if (event->modifiers() & Qt::ShiftModifier) { // If holding shift, only move one axis @@ -284,15 +227,19 @@ void KeyframeViewBase::mouseMoveEvent(QMouseEvent *event) QPointF bezier_pos = dragging_bezier_point_->pos() + dragging_bezier_point_->parentItem()->pos(); emit Dragged(qRound(bezier_pos.x()), qRound(bezier_pos.y())); - } else if (!selected_keys_.isEmpty()) { + } else if (selection_manager_.IsDragging()) { + QString tip; + + /* // Validate movement - ensure no keyframe goes above its max point or below its min point FloatSlider::DisplayType display_type = FloatSlider::kNormal; if (IsYAxisEnabled()) { - foreach (const KeyframeItemAndTime& keypair, selected_keys_) { - Node* node = keypair.key->key()->parent(); - const QString& input = keypair.key->key()->input(); + foreach (const KeyframeItemAndTime& keypair, dragging_keyframes_) { + NodeKeyframe *key = keypair.key; + Node* node = key->parent(); + const QString& input = key->input(); double new_val = keypair.value - mouse_diff_scaled.y(); double limited = new_val; @@ -309,48 +256,37 @@ void KeyframeViewBase::mouseMoveEvent(QMouseEvent *event) } } - Node* initial_drag_input = initial_drag_item_->key()->parent(); - const QString& initial_drag_input_id = initial_drag_item_->key()->input(); + Node* initial_drag_input = initial_drag_item_->parent(); + const QString& initial_drag_input_id = initial_drag_item_->input(); if (initial_drag_input->HasInputProperty(initial_drag_input_id, QStringLiteral("view"))) { display_type = static_cast(initial_drag_input->GetInputProperty(initial_drag_input_id, QStringLiteral("view")).toInt()); } } - foreach (const KeyframeItemAndTime& keypair, selected_keys_) { - rational node_time = GetAdjustedTime(GetTimeTarget(), - keypair.key->key()->parent(), - CalculateNewTimeFromScreen(keypair.time, mouse_diff_scaled.x()), - true); - - keypair.key->key()->set_time(node_time); - + foreach (const KeyframeItemAndTime& keypair, dragging_keyframes_) { if (IsYAxisEnabled()) { - keypair.key->key()->set_value(keypair.value - mouse_diff_scaled.y()); + key->set_value(keypair.value - mouse_diff_scaled.y()); } } - // Show information about this keyframe - QString tip = Timecode::time_to_timecode(initial_drag_item_->key()->time(), timebase(), - Core::instance()->GetTimecodeDisplay(), false); + if (IsYAxisEnabled()) { bool ok; - double num_value = initial_drag_item_->key()->value().toDouble(&ok); + double num_value = initial_drag_item_->value().toDouble(&ok); if (ok) { - tip.append('\n'); + tip = QStringLiteral("%1\n"); tip.append(FloatSlider::ValueToString(num_value, display_type, 2, true)); } - - // Force viewport to update since Qt might try to optimize it out if the keyframe is - // offscreen - viewport()->update(); } + */ - QToolTip::hideText(); - QToolTip::showText(QCursor::pos(), tip); + selection_manager_.DragMove(event, tip); - emit Dragged(qRound(initial_drag_item_->x()), qRound(initial_drag_item_->y())); + Redraw(); + + emit Dragged(scene_pos.x(), scene_pos.y()); } } @@ -364,8 +300,6 @@ void KeyframeViewBase::mouseReleaseEvent(QMouseEvent *event) } if (event->button() == Qt::LeftButton) { - QGraphicsView::mouseReleaseEvent(event); - if (dragging_) { if (dragging_bezier_point_) { MultiUndoCommand* command = new MultiUndoCommand(); @@ -388,30 +322,19 @@ void KeyframeViewBase::mouseReleaseEvent(QMouseEvent *event) dragging_bezier_point_ = nullptr; Core::instance()->undo_stack()->push(command); - } else if (!selected_keys_.isEmpty()) { + } else if (selection_manager_.IsDragging()) { MultiUndoCommand* command = new MultiUndoCommand(); - foreach (const KeyframeItemAndTime& keypair, selected_keys_) { - NodeKeyframe* item = keypair.key->key(); - - // Commit movement - command->add_child(new NodeParamSetKeyframeTimeCommand(item, - item->time(), - keypair.time)); - - // Commit value if we're setting a value - if (IsYAxisEnabled()) { - command->add_child(new NodeParamSetKeyframeValueCommand(item, - item->value(), - keypair.value)); - } - } + selection_manager_.DragStop(command); + /*if (IsYAxisEnabled()) { + command->add_child(new NodeParamSetKeyframeValueCommand(item, + item->value(), + keypair.value)); + }*/ Core::instance()->undo_stack()->push(command); } - selected_keys_.clear(); - dragging_ = false; QToolTip::hideText(); @@ -419,31 +342,77 @@ void KeyframeViewBase::mouseReleaseEvent(QMouseEvent *event) } } +void KeyframeViewBase::drawForeground(QPainter *painter, const QRectF &rect) +{ + int key_sz = QtUtils::QFontMetricsWidth(fontMetrics(), "Oi"); + int key_rad = key_sz/2; + + selection_manager_.ClearDrawnObjects(); + + painter->setRenderHint(QPainter::Antialiasing); + + painter->setPen(Qt::black); + + foreach (KeyframeViewInputConnection *track, tracks_) { + foreach (NodeKeyframe *key, track->GetKeyframes()) { + QRectF key_rect(-key_rad, -key_rad, key_sz, key_sz); + key_rect.translate(GetKeyframeSceneX(key), mapFromGlobal(QPoint(0, track->GetKeyframeY())).y()); + + if (!rect.intersects(key_rect)) { + continue; + } + + if (IsKeyframeSelected(key)) { + painter->setBrush(palette().highlight()); + } else { + painter->setBrush(track->GetBrush()); + } + + selection_manager_.DeclareDrawnObject(key, key_rect); + + switch (key->type()) { + case NodeKeyframe::kLinear: + { + QPointF points[] = { + QPointF(key_rect.center().x(), key_rect.top()), + QPointF(key_rect.right(), key_rect.center().y()), + QPointF(key_rect.center().x(), key_rect.bottom()), + QPointF(key_rect.left(), key_rect.center().y()) + }; + + painter->drawPolygon(points, 4); + break; + } + case NodeKeyframe::kBezier: + painter->drawEllipse(key_rect); + break; + case NodeKeyframe::kHold: + painter->drawRect(key_rect); + break; + } + } + } + + super::drawForeground(painter, rect); +} + void KeyframeViewBase::ScaleChangedEvent(const double &scale) { - TimeBasedView::ScaleChangedEvent(scale); + super::ScaleChangedEvent(scale); - for (auto iterator=item_map_.begin();iterator!=item_map_.end();iterator++) { - iterator.value()->SetScale(scale); - } -} - -const QMap &KeyframeViewBase::item_map() const -{ - return item_map_; -} - -void KeyframeViewBase::KeyframeAboutToBeRemoved(NodeKeyframe *) -{ + Redraw(); } void KeyframeViewBase::TimeTargetChangedEvent(Node *target) { - QMap::const_iterator i; + Redraw(); +} - for (i=item_map_.begin();i!=item_map_.end();i++) { - i.value()->SetTimeTarget(target); - } +void KeyframeViewBase::TimebaseChangedEvent(const rational &timebase) +{ + super::TimebaseChangedEvent(timebase); + + selection_manager_.SetTimebase(timebase); } void KeyframeViewBase::ContextMenuEvent(Menu& m) @@ -451,6 +420,30 @@ void KeyframeViewBase::ContextMenuEvent(Menu& m) Q_UNUSED(m) } +void KeyframeViewBase::SelectKeyframe(NodeKeyframe *key) +{ + if (selection_manager_.Select(key)) { + Redraw(); + } +} + +void KeyframeViewBase::DeselectKeyframe(NodeKeyframe *key) +{ + if (selection_manager_.Deselect(key)) { + Redraw(); + } +} + +rational KeyframeViewBase::GetAdjustedKeyframeTime(NodeKeyframe *key) +{ + return GetAdjustedTime(key->parent(), GetTimeTarget(), key->time(), false); +} + +double KeyframeViewBase::GetKeyframeSceneX(NodeKeyframe *key) +{ + return TimeToScene(GetAdjustedKeyframeTime(key)); +} + rational KeyframeViewBase::CalculateNewTimeFromScreen(const rational &old_time, double cursor_diff) { return rational::fromDouble(old_time.toDouble() + cursor_diff); @@ -492,16 +485,15 @@ void KeyframeViewBase::ShowContextMenu() QAction* bezier_key_action = nullptr; QAction* hold_key_action = nullptr; - QList items = scene()->selectedItems(); - if (!items.isEmpty()) { + if (!GetSelectedKeyframes().isEmpty()) { bool all_keys_are_same_type = true; - NodeKeyframe::Type type = static_cast(items.first())->key()->type(); + NodeKeyframe::Type type = GetSelectedKeyframes().first()->type(); - for (int i=1;i(items.at(i)); - KeyframeViewItem* prev_item = static_cast(items.at(i-1)); + for (int i=1;ikey()->type() != prev_item->key()->type()) { + if (key_item->type() != prev_item->type()) { all_keys_are_same_type = false; break; } @@ -536,7 +528,7 @@ void KeyframeViewBase::ShowContextMenu() ContextMenuEvent(m); - if (!items.isEmpty()) { + if (!GetSelectedKeyframes().isEmpty()) { m.addSeparator(); QAction* properties_action = m.addAction(tr("P&roperties")); @@ -546,7 +538,7 @@ void KeyframeViewBase::ShowContextMenu() QAction* selected = m.exec(QCursor::pos()); // Process keyframe type changes - if (!items.isEmpty()) { + if (selected) { if (selected == linear_key_action || selected == bezier_key_action || selected == hold_key_action) { @@ -561,26 +553,18 @@ void KeyframeViewBase::ShowContextMenu() } MultiUndoCommand* command = new MultiUndoCommand(); - foreach (QGraphicsItem* item, items) { - command->add_child(new KeyframeSetTypeCommand(static_cast(item)->key(), - new_type)); + foreach (NodeKeyframe* item, GetSelectedKeyframes()) { + command->add_child(new KeyframeSetTypeCommand(item, new_type)); } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); } } } void KeyframeViewBase::ShowKeyframePropertiesDialog() { - QList items = scene()->selectedItems(); - QVector keys; - - foreach (QGraphicsItem* item, items) { - keys.append(static_cast(item)->key()); - } - - if (!keys.isEmpty()) { - KeyframePropertiesDialog kd(keys, timebase(), this); + if (!GetSelectedKeyframes().isEmpty()) { + KeyframePropertiesDialog kd(GetSelectedKeyframes(), timebase(), this); kd.exec(); } } @@ -594,25 +578,15 @@ void KeyframeViewBase::AutoSelectKeyTimeNeighbors() // Prevents infinite loop currently_autoselecting_ = true; - QList selected_items = scene()->selectedItems(); + QVector copy = GetSelectedKeyframes(); + foreach (NodeKeyframe *key, copy) { + rational key_time = key->time(); - foreach (QGraphicsItem* g, selected_items) { - KeyframeViewItem* key_item = static_cast(g); - - rational key_time = key_item->key()->time(); - - QVector keys = key_item->key()->parent()->GetKeyframesAtTime(key_item->key()->input(), key_time, key_item->key()->element()); + QVector keys = key->parent()->GetKeyframesAtTime(key->input(), key_time, key->element()); foreach (NodeKeyframe* k, keys) { - if (k == key_item->key()) { - continue; - } - - // Ensure this key is not already selected - KeyframeViewItem* item = item_map_.value(k); - - if (item) { - item->setSelected(true); + if (k != key) { + SelectKeyframe(k); } } } @@ -620,4 +594,9 @@ void KeyframeViewBase::AutoSelectKeyTimeNeighbors() currently_autoselecting_ = false; } +void KeyframeViewBase::Redraw() +{ + viewport()->update(); +} + } diff --git a/app/widget/keyframeview/keyframeviewbase.h b/app/widget/keyframeview/keyframeviewbase.h index 55b6ecf17..2ce0673f7 100644 --- a/app/widget/keyframeview/keyframeviewbase.h +++ b/app/widget/keyframeview/keyframeviewbase.h @@ -21,11 +21,12 @@ #ifndef KEYFRAMEVIEWBASE_H #define KEYFRAMEVIEWBASE_H -#include "keyframeviewitem.h" +#include "keyframeviewinputconnection.h" #include "node/keyframe.h" #include "widget/curvewidget/beziercontrolpointitem.h" #include "widget/menu/menu.h" #include "widget/timebased/timebasedview.h" +#include "widget/timebased/timebasedviewselectionmanager.h" #include "widget/timetarget/timetarget.h" namespace olive { @@ -36,51 +37,49 @@ class KeyframeViewBase : public TimeBasedView, public TimeTargetObject public: KeyframeViewBase(QWidget* parent = nullptr); - virtual void Clear(); - void DeleteSelected(); - void AddKeyframesOfNode(Node* n); + using ElementConnections = QVector; + using InputConnections = QVector; + using NodeConnections = QMap; - void AddKeyframesOfInput(Node *n, const QString &input); + NodeConnections AddKeyframesOfNode(Node* n); - void AddKeyframesOfElement(const NodeInput &input); + InputConnections AddKeyframesOfInput(Node *n, const QString &input); - void AddKeyframesOfTrack(const NodeKeyframeTrackReference &ref); + ElementConnections AddKeyframesOfElement(const NodeInput &input); - void RemoveKeyframesOfNode(Node* n); + KeyframeViewInputConnection *AddKeyframesOfTrack(const NodeKeyframeTrackReference &ref); - void RemoveKeyframesOfInput(Node *n, const QString &input); - - void RemoveKeyframesOfElement(const NodeInput &input); - - void RemoveKeyframesOfTrack(const NodeKeyframeTrackReference &ref); + void RemoveKeyframesOfTrack(KeyframeViewInputConnection *connection); void SelectAll(); void DeselectAll(); + void Clear(); + + const QVector &GetSelectedKeyframes() const + { + return selection_manager_.GetSelectedObjects(); + } + signals: void Dragged(int current_x, int current_y); -public slots: - virtual KeyframeViewItem* AddKeyframe(NodeKeyframe* key); - - void RemoveKeyframe(NodeKeyframe* key); - protected: virtual void mousePressEvent(QMouseEvent *event) override; virtual void mouseMoveEvent(QMouseEvent *event) override; virtual void mouseReleaseEvent(QMouseEvent *event) override; + virtual void drawForeground(QPainter *painter, const QRectF &rect) override; + virtual void ScaleChangedEvent(const double& scale) override; - const QMap& item_map() const; - - virtual void KeyframeAboutToBeRemoved(NodeKeyframe* key); - virtual void TimeTargetChangedEvent(Node*) override; + virtual void TimebaseChangedEvent(const rational &timebase) override; + virtual void ContextMenuEvent(Menu &m); bool IsDragging() const @@ -88,6 +87,19 @@ protected: return dragging_; } + void SelectKeyframe(NodeKeyframe *key); + + void DeselectKeyframe(NodeKeyframe *key); + + bool IsKeyframeSelected(NodeKeyframe *key) const + { + return selection_manager_.IsSelected(key); + } + + rational GetAdjustedKeyframeTime(NodeKeyframe *key); + + double GetKeyframeSceneX(NodeKeyframe *key); + private: rational CalculateNewTimeFromScreen(const rational& old_time, double cursor_diff); @@ -97,31 +109,20 @@ private: QPointF GetScaledCursorPos(const QPointF &cursor_pos); - struct KeyframeItemAndTime { - KeyframeViewItem* key; - qreal item_x; - rational time; - double value; - }; - - QMap item_map_; - - Tool::Item active_tool_; - QPointF drag_start_; BezierControlPointItem* dragging_bezier_point_; QPointF dragging_bezier_point_start_; QPointF dragging_bezier_point_opposing_start_; - KeyframeViewItem* initial_drag_item_; - - QVector selected_keys_; + QVector tracks_; bool currently_autoselecting_; bool dragging_; + TimeBasedViewSelectionManager selection_manager_; + private slots: void ShowContextMenu(); @@ -129,6 +130,8 @@ private slots: void AutoSelectKeyTimeNeighbors(); + void Redraw(); + }; } diff --git a/app/widget/keyframeview/keyframeviewinputconnection.cpp b/app/widget/keyframeview/keyframeviewinputconnection.cpp new file mode 100644 index 000000000..a8e9a35e5 --- /dev/null +++ b/app/widget/keyframeview/keyframeviewinputconnection.cpp @@ -0,0 +1,83 @@ +/*** + + 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 "keyframeviewinputconnection.h" + +#include "keyframeview.h" + +namespace olive { + +KeyframeViewInputConnection::KeyframeViewInputConnection(const NodeKeyframeTrackReference &input, KeyframeViewBase *parent) : + QObject(parent), + keyframe_view_(parent), + input_(input), + y_(0), + y_behavior_(kSingleRow), + brush_(Qt::white) +{ + Node *n = input.input().node(); + + connect(n, &Node::KeyframeAdded, this, &KeyframeViewInputConnection::AddKeyframe); + connect(n, &Node::KeyframeRemoved, this, &KeyframeViewInputConnection::RemoveKeyframe); + connect(n, &Node::KeyframeTimeChanged, this, &KeyframeViewInputConnection::RequireUpdate); +} + +void KeyframeViewInputConnection::SetKeyframeY(int y) +{ + if (y_ != y) { + y_ = y; + + emit RequireUpdate(); + } +} + +void KeyframeViewInputConnection::SetYBehavior(YBehavior e) +{ + if (y_behavior_ != e) { + y_behavior_ = e; + + emit RequireUpdate(); + } +} + +void KeyframeViewInputConnection::SetBrush(const QBrush &brush) +{ + if (brush_ != brush) { + brush_ = brush; + + emit RequireUpdate(); + } +} + +void KeyframeViewInputConnection::AddKeyframe(NodeKeyframe *key) +{ + if (key->key_track_ref() == input_) { + emit RequireUpdate(); + } +} + +void KeyframeViewInputConnection::RemoveKeyframe(NodeKeyframe *key) +{ + if (key->key_track_ref() == input_) { + emit RequireUpdate(); + } +} + +} diff --git a/app/widget/keyframeview/keyframeviewinputconnection.h b/app/widget/keyframeview/keyframeviewinputconnection.h new file mode 100644 index 000000000..b8f951d22 --- /dev/null +++ b/app/widget/keyframeview/keyframeviewinputconnection.h @@ -0,0 +1,88 @@ +/*** + + 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 KEYFRAMEVIEWINPUTCONNECTION_H +#define KEYFRAMEVIEWINPUTCONNECTION_H + +#include + +#include "node/node.h" +#include "node/param.h" + +namespace olive { + +class KeyframeViewBase; + +class KeyframeViewInputConnection : public QObject +{ + Q_OBJECT +public: + KeyframeViewInputConnection(const NodeKeyframeTrackReference &input, KeyframeViewBase *parent); + + const int &GetKeyframeY() const + { + return y_; + } + + void SetKeyframeY(int y); + + enum YBehavior { + kSingleRow, + kValueIsHeight + }; + + void SetYBehavior(YBehavior e); + + const QVector GetKeyframes() const + { + return input_.input().node()->GetKeyframeTracks(input_.input()).at(input_.track()); + } + + const QBrush &GetBrush() const + { + return brush_; + } + + void SetBrush(const QBrush &brush); + +signals: + void RequireUpdate(); + +private: + KeyframeViewBase *keyframe_view_; + + NodeKeyframeTrackReference input_; + + int y_; + + YBehavior y_behavior_; + + QBrush brush_; + +private slots: + void AddKeyframe(NodeKeyframe *key); + + void RemoveKeyframe(NodeKeyframe *key); + +}; + +} + +#endif // KEYFRAMEVIEWINPUTCONNECTION_H diff --git a/app/widget/keyframeview/keyframeviewitem.cpp b/app/widget/keyframeview/keyframeviewitem.cpp deleted file mode 100644 index 45becc01b..000000000 --- a/app/widget/keyframeview/keyframeviewitem.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/*** - - 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 "keyframeviewitem.h" - -#include -#include -#include -#include - -#include "common/qtutils.h" - -namespace olive { - -KeyframeViewItem::KeyframeViewItem(NodeKeyframe* key, QGraphicsItem *parent) : - QGraphicsRectItem(parent), - key_(key), - scale_(1.0), - vert_center_(0), - use_custom_brush_(false) -{ - setFlag(QGraphicsItem::ItemIsSelectable); - - connect(key, &NodeKeyframe::TimeChanged, this, &KeyframeViewItem::UpdatePos); - connect(key, &NodeKeyframe::TypeChanged, this, &KeyframeViewItem::Redraw); - - int keyframe_size = QtUtils::QFontMetricsWidth(qApp->fontMetrics(), "Oi"); - int half_sz = keyframe_size/2; - setRect(-half_sz, -half_sz, keyframe_size, keyframe_size); - - UpdatePos(); - - // Set default brush -} - -void KeyframeViewItem::SetOverrideY(qreal vertical_center) -{ - vert_center_ = vertical_center; - UpdatePos(); -} - -void KeyframeViewItem::SetScale(double scale) -{ - scale_ = scale; - UpdatePos(); -} - -void KeyframeViewItem::SetOverrideBrush(const QBrush &b) -{ - use_custom_brush_ = true; - setBrush(b); -} - -NodeKeyframe* KeyframeViewItem::key() const -{ - return key_; -} - -void KeyframeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) -{ - painter->setRenderHint(QPainter::Antialiasing); - - painter->setPen(Qt::black); - - if (option->state & QStyle::State_Selected) { - painter->setBrush(widget->palette().highlight()); - } else if (use_custom_brush_) { - painter->setBrush(brush()); - } else { - painter->setBrush(widget->palette().text()); - } - - switch (key_->type()) { - case NodeKeyframe::kLinear: - { - QPointF points[] = { - QPointF(rect().center().x(), rect().top()), - QPointF(rect().right(), rect().center().y()), - QPointF(rect().center().x(), rect().bottom()), - QPointF(rect().left(), rect().center().y()) - }; - - painter->drawPolygon(points, 4); - break; - } - case NodeKeyframe::kBezier: - painter->drawEllipse(rect()); - break; - case NodeKeyframe::kHold: - painter->drawRect(rect()); - break; - } -} - -void KeyframeViewItem::TimeTargetChangedEvent(Node *) -{ - UpdatePos(); -} - -void KeyframeViewItem::UpdatePos() -{ - rational adjusted = GetAdjustedTime(key_->parent(), GetTimeTarget(), key_->time(), false); - - setPos(adjusted.toDouble() * scale_, vert_center_); -} - -void KeyframeViewItem::Redraw() -{ - QGraphicsItem::update(); -} - -} diff --git a/app/widget/keyframeview/keyframeviewitem.h b/app/widget/keyframeview/keyframeviewitem.h deleted file mode 100644 index 3f3725dc6..000000000 --- a/app/widget/keyframeview/keyframeviewitem.h +++ /dev/null @@ -1,68 +0,0 @@ -/*** - - 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 KEYFRAMEVIEWITEM_H -#define KEYFRAMEVIEWITEM_H - -#include - -#include "node/keyframe.h" -#include "widget/timetarget/timetarget.h" - -namespace olive { - -class KeyframeViewItem : public QObject, public QGraphicsRectItem, public TimeTargetObject -{ - Q_OBJECT -public: - KeyframeViewItem(NodeKeyframe* key, QGraphicsItem *parent = nullptr); - - void SetOverrideY(qreal vertical_center); - - void SetScale(double scale); - - void SetOverrideBrush(const QBrush& b); - - NodeKeyframe* key() const; - -protected: - virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; - - virtual void TimeTargetChangedEvent(Node* ) override; - -private: - NodeKeyframe* key_; - - double scale_; - - qreal vert_center_; - - bool use_custom_brush_; - -private slots: - void UpdatePos(); - - void Redraw(); - -}; - -} - -#endif // KEYFRAMEVIEWITEM_H diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 0a6f743e5..b2e219478 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -229,6 +229,10 @@ void NodeParamView::SetContexts(const QVector &contexts) ctx->setVisible(false); } + if (keyframe_view_) { + keyframe_view_->Clear(); + } + if (focused_node_) { focused_node_ = nullptr; emit FocusedNodeChanged(nullptr); @@ -264,6 +268,10 @@ void NodeParamView::SetContexts(const QVector &contexts) foreach (NodeParamViewContext *ctx, context_items_) { SortItemsInContext(ctx); } + + if (keyframe_view_) { + QueueKeyframePositionUpdate(); + } } void NodeParamView::resizeEvent(QResizeEvent *event) @@ -348,54 +356,13 @@ void NodeParamView::QueueKeyframePositionUpdate() QMetaObject::invokeMethod(this, &NodeParamView::UpdateElementY, Qt::QueuedConnection); } -void NodeParamView::SignalNodeOrder() -{ - /* - // Sort by item Y (apparently there's no way in Qt to get the order of dock widgets) - QVector nodes; - QVector item_ys; - - for (auto it=items_.cbegin(); it!=items_.cend(); it++) { - int item_y = it.value()->pos().y(); - - bool inserted = false; - - for (int i=0; i item_y) { - item_ys.insert(i, item_y); - nodes.insert(i, it.key()); - inserted = true; - break; - } - } - - if (!inserted) { - item_ys.append(item_y); - nodes.append(it.key()); - } - } - - emit NodeOrderChanged(nodes); - */ -} - void NodeParamView::AddNode(Node *n, NodeParamViewContext *context) { NodeParamViewItem* item = new NodeParamViewItem(n, create_checkboxes_, context); - if (keyframe_view_) { - connect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); - connect(n, &Node::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe); - } - connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::SetTimeAndSignal); connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::RequestSelectNode); - connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::QueueKeyframePositionUpdate); - connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::SignalNodeOrder); connect(item, &NodeParamViewItem::PinToggled, this, &NodeParamView::PinNode); - connect(item, &NodeParamViewItem::ArrayExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate); - connect(item, &NodeParamViewItem::ExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate); - connect(item, &NodeParamViewItem::Moved, this, &NodeParamView::QueueKeyframePositionUpdate); connect(item, &NodeParamViewItem::InputCheckedChanged, this, &NodeParamView::SetInputChecked); if (create_checkboxes_) { @@ -422,48 +389,15 @@ void NodeParamView::AddNode(Node *n, NodeParamViewContext *context) } if (keyframe_view_) { - keyframe_view_->AddKeyframesOfNode(n); + connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::QueueKeyframePositionUpdate); + connect(item, &NodeParamViewItem::ArrayExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate); + connect(item, &NodeParamViewItem::ExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate); + connect(item, &NodeParamViewItem::Moved, this, &NodeParamView::QueueKeyframePositionUpdate); + + item->SetKeyframeConnections(keyframe_view_->AddKeyframesOfNode(n)); } } -/*void NodeParamView::AddNode(Node *node, Node *context, NodeParamViewContext *ctx_item) -{ - int dist = GetDistanceBetweenNodes(context, node); - - if (dist == -1) { - dist = 0; - } - - ctx_item->GetDockArea()->insert -}*/ - -void NodeParamView::RemoveNode(Node *n) -{ - qDebug() << "STUB!"; - /*if (keyframe_view_) { - keyframe_view_->RemoveKeyframesOfNode(n); - - disconnect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); - disconnect(n, &Node::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe); - } - - delete items_.take(n); - - if (focused_node_ == n) { - // Try to find new node with gizmos to focus - focused_node_ = nullptr; - for (auto it=items_.cbegin(); it!=items_.cend(); it++) { - if (it.key()->HasGizmos()) { - focused_node_ = it.key(); - it.value()->SetHighlighted(true); - break; - } - } - - emit FocusedNodeChanged(focused_node_); - }*/ -} - int GetDistanceBetweenNodes(Node *start, Node *end) { if (start == end) { @@ -516,7 +450,7 @@ void NodeParamView::UpdateGlobalScrollBar() int height_offscreen = param_widget_container_->height() + scrollbar()->height(); if (keyframe_view_) { - keyframe_view_->SetMaxScroll(height_offscreen); + keyframe_view_->SetMaxScroll(height_offscreen + 2000); } vertical_scrollbar_->setRange(0, height_offscreen - param_scroll_area_->height()); @@ -533,8 +467,7 @@ void NodeParamView::PinNode(bool pin) pinned_nodes_.removeOne(node); if (!active_nodes_.contains(node)) { - RemoveNode(node); - SignalNodeOrder(); + //RemoveNode(node); } } } @@ -591,21 +524,34 @@ void NodeParamView::KeyframeViewDragged(int x, int y) void NodeParamView::UpdateElementY() { - qDebug() << "STUB"; - /*if (keyframe_view_) { - for (auto it=items_.cbegin(); it!=items_.cend(); it++) { - foreach (const QString& input, it.key()->inputs()) { - int arr_sz = it.key()->InputArraySize(input); + foreach (NodeParamViewContext *ctx, context_items_) { + for (auto it=ctx->GetItems().cbegin(); it!=ctx->GetItems().cend(); it++) { + const KeyframeViewBase::NodeConnections &connections = it.value()->GetKeyframeConnections(); - for (int i=-1; iinputs()) { + if (!(it.key()->GetInputFlags(input) & kInputFlagHidden)) { + int arr_sz = it.key()->InputArraySize(input); - int y = it.value()->GetElementY(ic); - keyframe_view_->SetElementY(ic, y); + for (int i=-1; iGetElementY(ic); + + const KeyframeViewBase::InputConnections &input_con = connections.value(input); + int use_index = i + 1; + if (use_index < input_con.size()) { + const KeyframeViewBase::ElementConnections &ele_con = input_con.at(ic.element()+1); + foreach (KeyframeViewInputConnection *track, ele_con) { + track->SetKeyframeY(y); + } + } + } + } } } } - }*/ + } } } diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index c1c8fb3b5..b7eb50edd 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -99,8 +99,6 @@ public slots: signals: void RequestSelectNode(const QVector& target); - void NodeOrderChanged(const QVector& nodes); - void FocusedNodeChanged(Node* n); protected: @@ -117,14 +115,8 @@ private: void QueueKeyframePositionUpdate(); - void SignalNodeOrder(); - void AddNode(Node* n, NodeParamViewContext *context); - //void AddNode(Node *node, Node *context, NodeParamViewContext *ctx_item); - - void RemoveNode(Node* n); - void SortItemsInContext(NodeParamViewContext *context); KeyframeView* keyframe_view_; diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 445bcace2..1e0aae242 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -36,6 +36,7 @@ #include "nodeparamviewwidgetbridge.h" #include "widget/clickablelabel/clickablelabel.h" #include "widget/collapsebutton/collapsebutton.h" +#include "widget/keyframeview/keyframeviewbase.h" namespace olive { @@ -187,6 +188,16 @@ public: void SetInputChecked(const NodeInput &input, bool e); + const KeyframeViewBase::NodeConnections &GetKeyframeConnections() const + { + return keyframe_connections_; + } + + void SetKeyframeConnections(const KeyframeViewBase::NodeConnections &c) + { + keyframe_connections_ = c; + } + signals: void RequestSetTime(const rational& time); @@ -206,6 +217,8 @@ private: rational time_; + KeyframeViewBase::NodeConnections keyframe_connections_; + }; } diff --git a/app/widget/timebased/CMakeLists.txt b/app/widget/timebased/CMakeLists.txt index e2a35686f..ed200fc08 100644 --- a/app/widget/timebased/CMakeLists.txt +++ b/app/widget/timebased/CMakeLists.txt @@ -18,6 +18,8 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} widget/timebased/timebasedview.cpp widget/timebased/timebasedview.h + widget/timebased/timebasedviewselectionmanager.cpp + widget/timebased/timebasedviewselectionmanager.h widget/timebased/timebasedwidget.cpp widget/timebased/timebasedwidget.h widget/timebased/timescaledobject.cpp diff --git a/app/widget/timebased/timebasedviewselectionmanager.cpp b/app/widget/timebased/timebasedviewselectionmanager.cpp new file mode 100644 index 000000000..dd44a172e --- /dev/null +++ b/app/widget/timebased/timebasedviewselectionmanager.cpp @@ -0,0 +1,26 @@ +/*** + + 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 "timebasedviewselectionmanager.h" + +namespace olive { + + +} diff --git a/app/widget/timebased/timebasedviewselectionmanager.h b/app/widget/timebased/timebasedviewselectionmanager.h new file mode 100644 index 000000000..d1f53d7d6 --- /dev/null +++ b/app/widget/timebased/timebasedviewselectionmanager.h @@ -0,0 +1,256 @@ +/*** + + 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 TIMEBASEDVIEWSELECTIONMANAGER_H +#define TIMEBASEDVIEWSELECTIONMANAGER_H + +#include +#include +#include + +#include "common/rational.h" +#include "common/timecodefunctions.h" +#include "timebasedview.h" + +namespace olive { + +template +class TimeBasedViewSelectionManager +{ +public: + TimeBasedViewSelectionManager(TimeBasedView *view) : + view_(view) + {} + + void ClearDrawnObjects() + { + drawn_objects_.clear(); + } + + void DeclareDrawnObject(T *object, const QRectF &pos) + { + drawn_objects_.append({object, pos}); + } + + bool Select(T *key) + { + if (!IsSelected(key)) { + selected_.append(key); + return true; + } + + return false; + } + + bool Deselect(T *key) + { + return selected_.removeOne(key); + } + + void ClearSelection() + { + selected_.clear(); + } + + bool IsSelected(T *key) const + { + return selected_.contains(key); + } + + const QVector &GetSelectedObjects() const + { + return selected_; + } + + void SetTimebase(const rational &tb) + { + timebase_ = tb; + } + + T *MousePress(QMouseEvent *event) + { + T *key_under_cursor = nullptr; + + if (event->button() == Qt::LeftButton) { + // See if there's a keyframe in this position + QPointF scene_pos = view_->mapToScene(event->pos()); + foreach (const DrawnObject &kp, drawn_objects_) { + if (kp.second.contains(scene_pos)) { + key_under_cursor = kp.first; + break; + } + } + + bool holding_shift = event->modifiers() & Qt::ShiftModifier; + + if (IsSelected(key_under_cursor)) { + if (holding_shift) { + // If selected and holding shift, de-select this item but do nothing else + Deselect(key_under_cursor); + } + } else { + if (!holding_shift) { + // If not already selecting and not holding shift, clear the current selection + ClearSelection(); + } + + // Add item to selection, either nothing if shift wasn't held, or the existing selection + Select(key_under_cursor); + } + } + + return key_under_cursor; + } + + bool IsDragging() const + { + return !dragging_.isEmpty(); + } + + void DragStart(T *initial_item, QMouseEvent *event) + { + initial_drag_item_ = initial_item; + + dragging_.clear(); + + dragging_.resize(selected_.size()); + for (int i=0; itime()}; + } + + drag_mouse_start_ = view_->mapToScene(event->pos()); + } + + void DragMove(QMouseEvent *event, const QString &tip_format) + { + QPointF diff = view_->mapToScene(event->pos()) - drag_mouse_start_; + + for (int i=0; iSceneToTimeNoGrid(diff.x()); + T *sel = selected_.at(i); + + // Magic number: use interval of 1ms to avoid collisions + rational adj(1, 1000); + if (old_time < proposed_time) { + adj = -adj; + } + while (true) { + NodeKeyframe *key_at_time = sel->parent()->GetKeyframeAtTimeOnTrack(sel->input(), proposed_time, sel->track(), sel->element()); + if (!key_at_time || key_at_time == sel) { + break; + } + + proposed_time += adj; + } + + sel->set_time(proposed_time); + } + + // Show information about this keyframe + QString tip = Timecode::time_to_timecode(initial_drag_item_->time(), timebase_, + Core::instance()->GetTimecodeDisplay(), false); + + if (!tip_format.isEmpty()) { + tip = tip_format.arg(tip); + } + + QToolTip::hideText(); + QToolTip::showText(QCursor::pos(), tip); + } + + void DragStop(MultiUndoCommand *command) + { + QToolTip::hideText(); + + for (int i=0; iadd_child(new SetTimeCommand(selected_.at(i), selected_.at(i)->time(), dragging_.at(i).time)); + } + } + +private: + class SetTimeCommand : public UndoCommand + { + public: + SetTimeCommand(T* key, const rational& time) + { + key_ = key; + new_time_ = time; + old_time_ = key_->time(); + } + + SetTimeCommand(T* key, const rational& new_time, const rational& old_time) + { + key_ = key; + new_time_ = new_time; + old_time_ = old_time; + } + + virtual Project* GetRelevantProject() const override + { + return key_->parent()->project(); + } + + protected: + virtual void redo() override + { + key_->set_time(new_time_); + } + + virtual void undo() override + { + key_->set_time(old_time_); + } + + private: + T* key_; + + rational old_time_; + rational new_time_; + + }; + + TimeBasedView *view_; + + using DrawnObject = QPair; + QVector drawn_objects_; + + QVector selected_; + + struct DragObject + { + rational time; + }; + + QVector dragging_; + + T *initial_drag_item_; + + QPointF drag_mouse_start_; + + rational timebase_; + +}; + +} + +#endif // TIMEBASEDVIEWSELECTIONMANAGER_H diff --git a/app/widget/timebased/timescaledobject.cpp b/app/widget/timebased/timescaledobject.cpp index 85b154a48..5ea4176aa 100644 --- a/app/widget/timebased/timescaledobject.cpp +++ b/app/widget/timebased/timescaledobject.cpp @@ -72,6 +72,13 @@ rational TimeScaledObject::SceneToTime(const double &x, const double &x_scale, c return rational(rounded_x_mvmt * timebase.numerator(), timebase.denominator()); } +rational TimeScaledObject::SceneToTimeNoGrid(const double &x, const double &x_scale) +{ + double unscaled_time = x / x_scale; + + return rational::fromDouble(unscaled_time); +} + double TimeScaledObject::TimeToScene(const rational &time) const { return time.toDouble() * scale_; @@ -82,6 +89,11 @@ rational TimeScaledObject::SceneToTime(const double &x, bool round) const return SceneToTime(x, scale_, timebase_, round); } +rational TimeScaledObject::SceneToTimeNoGrid(const double &x) const +{ + return SceneToTimeNoGrid(x, scale_); +} + void TimeScaledObject::SetMaximumScale(const double &max) { max_scale_ = max; diff --git a/app/widget/timebased/timescaledobject.h b/app/widget/timebased/timescaledobject.h index 8c858c8fc..acdc44acb 100644 --- a/app/widget/timebased/timescaledobject.h +++ b/app/widget/timebased/timescaledobject.h @@ -42,6 +42,7 @@ public: const double& timebase_dbl() const; static rational SceneToTime(const double &x, const double& x_scale, const rational& timebase, bool round = false); + static rational SceneToTimeNoGrid(const double &x, const double& x_scale); const double& GetScale() const; const double &GetMaximumScale() const { return max_scale_; } @@ -54,6 +55,7 @@ public: double TimeToScene(const rational& time) const; rational SceneToTime(const double &x, bool round = false) const; + rational SceneToTimeNoGrid(const double &x) const; protected: virtual void TimebaseChangedEvent(const rational&){} diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index c26b9bdf9..2cbf094d0 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -47,7 +47,7 @@ MainWindow::MainWindow(QWidget *parent) : #ifdef Q_OS_WINDOWS // Set up taskbar button progress bar (used for some modal tasks like exporting) - taskbar_btn_id_ = RegisterWindowMessage("TaskbarButtonCreated"); + taskbar_btn_id_ = RegisterWindowMessage(TEXT("TaskbarButtonCreated")); taskbar_interface_ = nullptr; #endif @@ -106,9 +106,6 @@ MainWindow::MainWindow(QWidget *parent) : connect(curve_panel_, &ParamPanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime); connect(curve_panel_, &ParamPanel::TimeChanged, param_panel_, &NodeTablePanel::SetTime); - // Connect node order signals - connect(param_panel_, &ParamPanel::NodeOrderChanged, curve_panel_, &CurvePanel::SetNodes); - connect(PanelManager::instance(), &PanelManager::FocusedPanelChanged, this, &MainWindow::FocusedPanelChanged); sequence_viewer_panel_->ConnectTimeBasedPanel(param_panel_); @@ -116,7 +113,7 @@ MainWindow::MainWindow(QWidget *parent) : UpdateTitle(); - QMetaObject::invokeMethod(this, "SetDefaultLayout", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &MainWindow::SetDefaultLayout, Qt::QueuedConnection); } MainWindow::~MainWindow() From 1644b9e4c15465f42fb37ddd3c453f5d56be3ac0 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 15 Dec 2021 13:45:51 -0800 Subject: [PATCH 19/34] remove dodgy node properties dialog --- app/dialog/CMakeLists.txt | 2 +- .../CMakeLists.txt | 8 +- .../footageproperties/footageproperties.cpp | 251 ++++++++++++++++ .../footageproperties/footageproperties.h | 120 ++++++++ .../streamproperties/CMakeLists.txt | 26 ++ .../audiostreamproperties.cpp | 37 +++ .../streamproperties/audiostreamproperties.h} | 29 +- .../streamproperties/streamproperties.cpp | 30 ++ .../streamproperties/streamproperties.h | 44 +++ .../videostreamproperties.cpp | 271 ++++++++++++++++++ .../streamproperties/videostreamproperties.h | 146 ++++++++++ .../nodeproperties/nodepropertiesdialog.cpp | 74 ----- .../projectexplorer/projectexplorer.cpp | 6 +- app/widget/timelinewidget/timelinewidget.cpp | 13 +- 14 files changed, 946 insertions(+), 111 deletions(-) rename app/dialog/{nodeproperties => footageproperties}/CMakeLists.txt (81%) create mode 100644 app/dialog/footageproperties/footageproperties.cpp create mode 100644 app/dialog/footageproperties/footageproperties.h create mode 100644 app/dialog/footageproperties/streamproperties/CMakeLists.txt create mode 100644 app/dialog/footageproperties/streamproperties/audiostreamproperties.cpp rename app/dialog/{nodeproperties/nodepropertiesdialog.h => footageproperties/streamproperties/audiostreamproperties.h} (54%) create mode 100644 app/dialog/footageproperties/streamproperties/streamproperties.cpp create mode 100644 app/dialog/footageproperties/streamproperties/streamproperties.h create mode 100644 app/dialog/footageproperties/streamproperties/videostreamproperties.cpp create mode 100644 app/dialog/footageproperties/streamproperties/videostreamproperties.h delete mode 100644 app/dialog/nodeproperties/nodepropertiesdialog.cpp diff --git a/app/dialog/CMakeLists.txt b/app/dialog/CMakeLists.txt index 01bbfd5ec..afe445704 100644 --- a/app/dialog/CMakeLists.txt +++ b/app/dialog/CMakeLists.txt @@ -21,10 +21,10 @@ add_subdirectory(color) add_subdirectory(configbase) add_subdirectory(diskcache) add_subdirectory(export) +add_subdirectory(footageproperties) add_subdirectory(footagerelink) add_subdirectory(keyframeproperties) add_subdirectory(nodegroup) -add_subdirectory(nodeproperties) add_subdirectory(preferences) add_subdirectory(progress) add_subdirectory(rendercancel) diff --git a/app/dialog/nodeproperties/CMakeLists.txt b/app/dialog/footageproperties/CMakeLists.txt similarity index 81% rename from app/dialog/nodeproperties/CMakeLists.txt rename to app/dialog/footageproperties/CMakeLists.txt index f1c44b5af..25287c233 100644 --- a/app/dialog/nodeproperties/CMakeLists.txt +++ b/app/dialog/footageproperties/CMakeLists.txt @@ -1,5 +1,5 @@ # Olive - Non-Linear Video Editor -# Copyright (C) 2021 Olive Team +# Copyright (C) 2020 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 @@ -14,9 +14,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +add_subdirectory(streamproperties) + set(OLIVE_SOURCES ${OLIVE_SOURCES} - dialog/nodeproperties/nodepropertiesdialog.cpp - dialog/nodeproperties/nodepropertiesdialog.h + dialog/footageproperties/footageproperties.cpp + dialog/footageproperties/footageproperties.h PARENT_SCOPE ) diff --git a/app/dialog/footageproperties/footageproperties.cpp b/app/dialog/footageproperties/footageproperties.cpp new file mode 100644 index 000000000..7cb56c63a --- /dev/null +++ b/app/dialog/footageproperties/footageproperties.cpp @@ -0,0 +1,251 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2020 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 "footageproperties.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "streamproperties/audiostreamproperties.h" +#include "streamproperties/videostreamproperties.h" +#include "widget/nodeview/nodeviewundo.h" + +namespace olive { + +FootagePropertiesDialog::FootagePropertiesDialog(QWidget *parent, Footage *footage) : + QDialog(parent), + footage_(footage) +{ + QGridLayout* layout = new QGridLayout(this); + + setWindowTitle(tr("\"%1\" Properties").arg(footage_->GetLabelOrName())); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + int row = 0; + + layout->addWidget(new QLabel(tr("Name:")), row, 0); + + footage_name_field_ = new QLineEdit(footage_->GetLabel()); + layout->addWidget(footage_name_field_, row, 1); + row++; + + layout->addWidget(new QLabel(tr("Tracks:")), row, 0, 1, 2); + row++; + + track_list = new QListWidget(); + layout->addWidget(track_list, row, 0, 1, 2); + + row++; + + stacked_widget_ = new QStackedWidget(); + layout->addWidget(stacked_widget_, row, 0, 1, 2); + + int first_usable_stream = -1; + + for (int i=0; iGetTotalStreamCount(); i++) { + Track::Reference reference = footage_->GetReferenceFromRealIndex(i); + + QString description; + bool is_enabled = false; + + switch (reference.type()) { + case Track::kVideo: + { + stacked_widget_->addWidget(new VideoStreamProperties(footage_, reference.index())); + + VideoParams vp = footage_->GetVideoParams(reference.index()); + is_enabled = vp.enabled(); + description = tr("%1x%2 %3 FPS").arg(QString::number(vp.width()), QString::number(vp.height()), QString::number(vp.frame_rate().toDouble())); + break; + } + case Track::kAudio: + { + stacked_widget_->addWidget(new AudioStreamProperties(footage_, reference.index())); + + AudioParams ap = footage_->GetAudioParams(reference.index()); + is_enabled = ap.enabled(); + description = tr("%1 Hz %2 channels").arg(QString::number(ap.sample_rate()), QString::number(ap.channel_count())); + break; + } + default: + stacked_widget_->addWidget(new StreamProperties()); + description = tr("Unknown"); + break; + } + + QListWidgetItem* item = new QListWidgetItem(description, track_list); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(is_enabled ? Qt::Checked : Qt::Unchecked); + track_list->addItem(item); + + if (first_usable_stream == -1 + && (reference.type() == Track::kVideo + || reference.type() == Track::kAudio)) { + first_usable_stream = i; + } + } + + row++; + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + buttons->setCenterButtons(true); + layout->addWidget(buttons, row, 0, 1, 2); + + connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); + + connect(track_list, &QListWidget::currentRowChanged, stacked_widget_, &QStackedWidget::setCurrentIndex); + + // Auto-select first item that actually has properties + if (first_usable_stream >= 0) { + track_list->setCurrentRow(first_usable_stream); + } + track_list->setFocus(); +} + +void FootagePropertiesDialog::accept() +{ + // Perform sanity check on all pages + for (int i=0;icount();i++) { + if (!static_cast(stacked_widget_->widget(i))->SanityCheck()) { + // Switch to the failed panel in question + stacked_widget_->setCurrentIndex(i); + + // Do nothing (it's up to the property panel itself to throw the error message) + return; + } + } + + MultiUndoCommand* command = new MultiUndoCommand(); + + if (footage_->GetLabel() != footage_name_field_->text()) { + NodeRenameCommand *nrc = new NodeRenameCommand(); + nrc->AddNode(footage_, footage_name_field_->text()); + command->add_child(nrc); + } + + for (int i=0; iGetTotalStreamCount(); i++) { + Track::Reference reference = footage_->GetReferenceFromRealIndex(i); + bool new_stream_enabled = (track_list->item(i)->checkState() == Qt::Checked); + bool old_stream_enabled = new_stream_enabled; + + switch (reference.type()) { + case Track::kVideo: + old_stream_enabled = footage_->GetVideoParams(reference.index()).enabled(); + break; + case Track::kAudio: + old_stream_enabled = footage_->GetAudioParams(reference.index()).enabled(); + break; + case Track::kSubtitle: + case Track::kNone: + case Track::kCount: + break; + } + + if (old_stream_enabled != new_stream_enabled) { + command->add_child(new StreamEnableChangeCommand(footage_, + reference.type(), + reference.index(), + new_stream_enabled)); + } + } + + for (int i=0;icount();i++) { + static_cast(stacked_widget_->widget(i))->Accept(command); + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); + + QDialog::accept(); +} + +FootagePropertiesDialog::StreamEnableChangeCommand::StreamEnableChangeCommand(Footage *footage, Track::Type type, int index_in_type, bool enabled) : + footage_(footage), + type_(type), + index_(index_in_type), + new_enabled_(enabled) +{ +} + +Project *FootagePropertiesDialog::StreamEnableChangeCommand::GetRelevantProject() const +{ + return footage_->project(); +} + +void FootagePropertiesDialog::StreamEnableChangeCommand::redo() +{ + switch (type_) { + case Track::kVideo: + { + VideoParams vp = footage_->GetVideoParams(index_); + old_enabled_ = vp.enabled(); + vp.set_enabled(new_enabled_); + footage_->SetVideoParams(vp, index_); + break; + } + case Track::kAudio: + { + AudioParams ap = footage_->GetAudioParams(index_); + old_enabled_ = ap.enabled(); + ap.set_enabled(new_enabled_); + footage_->SetAudioParams(ap, index_); + break; + } + case Track::kSubtitle: + case Track::kNone: + case Track::kCount: + break; + } +} + +void FootagePropertiesDialog::StreamEnableChangeCommand::undo() +{ + switch (type_) { + case Track::kVideo: + { + VideoParams vp = footage_->GetVideoParams(index_); + vp.set_enabled(old_enabled_); + footage_->SetVideoParams(vp, index_); + break; + } + case Track::kAudio: + { + AudioParams ap = footage_->GetAudioParams(index_); + ap.set_enabled(old_enabled_); + footage_->SetAudioParams(ap, index_); + break; + } + case Track::kSubtitle: + case Track::kNone: + case Track::kCount: + break; + } +} + +} diff --git a/app/dialog/footageproperties/footageproperties.h b/app/dialog/footageproperties/footageproperties.h new file mode 100644 index 000000000..7fd58a14e --- /dev/null +++ b/app/dialog/footageproperties/footageproperties.h @@ -0,0 +1,120 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2020 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 MEDIAPROPERTIESDIALOG_H +#define MEDIAPROPERTIESDIALOG_H + +#include +#include +#include +#include +#include +#include +#include + +#include "node/project/footage/footage.h" +#include "undo/undocommand.h" + +namespace olive { + +/** + * @brief The MediaPropertiesDialog class + * + * A dialog for setting properties on Media. This can be loaded from any part of the application provided it's given + * a valid Media object. + */ +class FootagePropertiesDialog : public QDialog { + Q_OBJECT +public: + /** + * @brief MediaPropertiesDialog Constructor + * + * @param parent + * + * QWidget parent. Usually MainWindow or Project panel. + * + * @param i + * + * Media object to set properties for. + */ + FootagePropertiesDialog(QWidget *parent, Footage* footage); +private: + class StreamEnableChangeCommand : public UndoCommand { + public: + StreamEnableChangeCommand(Footage *footage, + Track::Type type, + int index_in_type, + bool enabled); + + virtual Project* GetRelevantProject() const override; + + virtual void redo() override; + virtual void undo() override; + + private: + Footage *footage_; + Track::Type type_; + int index_; + + bool old_enabled_; + bool new_enabled_; + }; + + /** + * @brief Stack of widgets that changes based on whether the stream is a video or audio stream + */ + QStackedWidget* stacked_widget_; + + /** + * @brief ComboBox for interlacing setting + */ + QComboBox* interlacing_box; + + /** + * @brief Media name text field + */ + QLineEdit* footage_name_field_; + + /** + * @brief Internal pointer to Media object (set in constructor) + */ + Footage* footage_; + + /** + * @brief A list widget for listing the tracks in Media + */ + QListWidget* track_list; + + /** + * @brief Frame rate to conform to + */ + QDoubleSpinBox* conform_fr; + +private slots: + /** + * @brief Overridden accept function for saving the properties back to the Media class + */ + void accept(); + +}; + +} + +#endif // MEDIAPROPERTIESDIALOG_H diff --git a/app/dialog/footageproperties/streamproperties/CMakeLists.txt b/app/dialog/footageproperties/streamproperties/CMakeLists.txt new file mode 100644 index 000000000..3228e9520 --- /dev/null +++ b/app/dialog/footageproperties/streamproperties/CMakeLists.txt @@ -0,0 +1,26 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2020 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 . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + dialog/footageproperties/streamproperties/streamproperties.h + dialog/footageproperties/streamproperties/streamproperties.cpp + dialog/footageproperties/streamproperties/audiostreamproperties.h + dialog/footageproperties/streamproperties/audiostreamproperties.cpp + dialog/footageproperties/streamproperties/videostreamproperties.h + dialog/footageproperties/streamproperties/videostreamproperties.cpp + PARENT_SCOPE +) diff --git a/app/dialog/footageproperties/streamproperties/audiostreamproperties.cpp b/app/dialog/footageproperties/streamproperties/audiostreamproperties.cpp new file mode 100644 index 000000000..0359e7577 --- /dev/null +++ b/app/dialog/footageproperties/streamproperties/audiostreamproperties.cpp @@ -0,0 +1,37 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2020 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 "audiostreamproperties.h" + +namespace olive { + +AudioStreamProperties::AudioStreamProperties(Footage *footage, int audio_index) : + footage_(footage), + audio_index_(audio_index) +{ +} + +void AudioStreamProperties::Accept(MultiUndoCommand*) +{ + Q_UNUSED(footage_) + Q_UNUSED(audio_index_) +} + +} diff --git a/app/dialog/nodeproperties/nodepropertiesdialog.h b/app/dialog/footageproperties/streamproperties/audiostreamproperties.h similarity index 54% rename from app/dialog/nodeproperties/nodepropertiesdialog.h rename to app/dialog/footageproperties/streamproperties/audiostreamproperties.h index 5375a1313..058ff2bfc 100644 --- a/app/dialog/nodeproperties/nodepropertiesdialog.h +++ b/app/dialog/footageproperties/streamproperties/audiostreamproperties.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2020 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 @@ -18,35 +18,28 @@ ***/ -#ifndef NODEPROPERTIESDIALOG_H -#define NODEPROPERTIESDIALOG_H +#ifndef AUDIOSTREAMPROPERTIES_H +#define AUDIOSTREAMPROPERTIES_H -#include - -#include "widget/nodeparamview/nodeparamviewitem.h" +#include "node/project/footage/footage.h" +#include "streamproperties.h" namespace olive { -class NodePropertiesDialog : public QDialog +class AudioStreamProperties : public StreamProperties { - Q_OBJECT public: - NodePropertiesDialog(Node *node, const rational &timebase, QWidget *parent = nullptr); - NodePropertiesDialog(const QVector &node, const rational &timebase, QWidget *parent = nullptr) : - NodePropertiesDialog(node.first(), timebase, parent) - { - } + AudioStreamProperties(Footage *footage, int audio_index); -public slots: - virtual void accept() override; + virtual void Accept(MultiUndoCommand* parent) override; private: - Node *node_; + Footage *footage_; - QLineEdit *label_edit_; + int audio_index_; }; } -#endif // NODEPROPERTIESDIALOG_H +#endif // AUDIOSTREAMPROPERTIES_H diff --git a/app/dialog/footageproperties/streamproperties/streamproperties.cpp b/app/dialog/footageproperties/streamproperties/streamproperties.cpp new file mode 100644 index 000000000..96f3bbd5a --- /dev/null +++ b/app/dialog/footageproperties/streamproperties/streamproperties.cpp @@ -0,0 +1,30 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2020 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 "streamproperties.h" + +namespace olive { + +StreamProperties::StreamProperties(QWidget *parent) : + QWidget(parent) +{ +} + +} diff --git a/app/dialog/footageproperties/streamproperties/streamproperties.h b/app/dialog/footageproperties/streamproperties/streamproperties.h new file mode 100644 index 000000000..c8457216b --- /dev/null +++ b/app/dialog/footageproperties/streamproperties/streamproperties.h @@ -0,0 +1,44 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2020 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 STREAMPROPERTIES_H +#define STREAMPROPERTIES_H + +#include + +#include "common/define.h" +#include "undo/undocommand.h" + +namespace olive { + +class StreamProperties : public QWidget +{ +public: + StreamProperties(QWidget* parent = nullptr); + + virtual void Accept(MultiUndoCommand*){} + + virtual bool SanityCheck(){return true;} + +}; + +} + +#endif // STREAMPROPERTIES_H diff --git a/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp b/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp new file mode 100644 index 000000000..6fcbdd914 --- /dev/null +++ b/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp @@ -0,0 +1,271 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2020 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 "videostreamproperties.h" + +#include +#include +#include +#include +#include + +#include "common/ocioutils.h" +#include "core.h" +#include "undo/undostack.h" + +namespace olive { + +VideoStreamProperties::VideoStreamProperties(Footage *footage, int video_index) : + footage_(footage), + video_index_(video_index), + video_premultiply_alpha_(nullptr) +{ + QGridLayout* video_layout = new QGridLayout(this); + video_layout->setMargin(0); + + int row = 0; + + video_layout->addWidget(new QLabel(tr("Pixel Aspect:")), row, 0); + + VideoParams vp = footage_->GetVideoParams(video_index_); + + pixel_aspect_combo_ = new PixelAspectRatioComboBox(); + pixel_aspect_combo_->SetPixelAspectRatio(vp.pixel_aspect_ratio()); + video_layout->addWidget(pixel_aspect_combo_, row, 1); + + row++; + + video_layout->addWidget(new QLabel(tr("Interlacing:")), row, 0); + + video_interlace_combo_ = new InterlacedComboBox(); + video_interlace_combo_->SetInterlaceMode(vp.interlacing()); + + video_layout->addWidget(video_interlace_combo_, row, 1); + + row++; + + video_layout->addWidget(new QLabel(tr("Color Space:")), row, 0); + + video_color_space_ = new QComboBox(); + OCIO::ConstConfigRcPtr config = footage_->project()->color_manager()->GetConfig(); + int number_of_colorspaces = config->getNumColorSpaces(); + + video_color_space_->addItem(tr("Default (%1)").arg(footage_->project()->color_manager()->GetDefaultInputColorSpace())); + + for (int i=0;igetColorSpaceNameByIndex(i); + + video_color_space_->addItem(colorspace); + } + + video_color_space_->setCurrentText(vp.colorspace()); + + video_layout->addWidget(video_color_space_, row, 1); + + if (vp.channel_count() == VideoParams::kRGBAChannelCount) { + row++; + + video_premultiply_alpha_ = new QCheckBox(tr("Premultiplied Alpha")); + video_premultiply_alpha_->setChecked(vp.premultiplied_alpha()); + video_layout->addWidget(video_premultiply_alpha_, row, 0, 1, 2); + } + + row++; + + if (vp.video_type() == VideoParams::kVideoTypeImageSequence) { + QGroupBox* imgseq_group = new QGroupBox(tr("Image Sequence")); + QGridLayout* imgseq_layout = new QGridLayout(imgseq_group); + + int imgseq_row = 0; + + imgseq_layout->addWidget(new QLabel(tr("Start Index:")), imgseq_row, 0); + + imgseq_start_time_ = new IntegerSlider(); + imgseq_start_time_->SetMinimum(0); + imgseq_start_time_->SetValue(vp.start_time()); + imgseq_layout->addWidget(imgseq_start_time_, imgseq_row, 1); + + imgseq_row++; + + imgseq_layout->addWidget(new QLabel(tr("End Index:")), imgseq_row, 0); + + imgseq_end_time_ = new IntegerSlider(); + imgseq_end_time_->SetMinimum(0); + imgseq_end_time_->SetValue(vp.start_time() + vp.duration() - 1); + imgseq_layout->addWidget(imgseq_end_time_, imgseq_row, 1); + + imgseq_row++; + + imgseq_layout->addWidget(new QLabel(tr("Frame Rate:")), imgseq_row, 0); + + imgseq_frame_rate_ = new FrameRateComboBox(); + imgseq_frame_rate_->SetFrameRate(vp.frame_rate()); + imgseq_layout->addWidget(imgseq_frame_rate_, imgseq_row, 1); + + video_layout->addWidget(imgseq_group, row, 0, 1, 2); + } +} + +void VideoStreamProperties::Accept(MultiUndoCommand *parent) +{ + QString set_colorspace; + + if (video_color_space_->currentIndex() > 0) { + set_colorspace = video_color_space_->currentText(); + } + + VideoParams vp = footage_->GetVideoParams(video_index_); + + if ((video_premultiply_alpha_ && video_premultiply_alpha_->isChecked() != vp.premultiplied_alpha()) + || set_colorspace != vp.colorspace() + || static_cast(video_interlace_combo_->currentIndex()) != vp.interlacing() + || pixel_aspect_combo_->GetPixelAspectRatio() != vp.pixel_aspect_ratio()) { + + parent->add_child(new VideoStreamChangeCommand(footage_, + video_index_, + video_premultiply_alpha_ ? video_premultiply_alpha_->isChecked() : vp.premultiplied_alpha(), + set_colorspace, + static_cast(video_interlace_combo_->currentIndex()), + pixel_aspect_combo_->GetPixelAspectRatio())); + } + + if (vp.video_type() == VideoParams::kVideoTypeImageSequence) { + int64_t new_dur = imgseq_end_time_->GetValue() - imgseq_start_time_->GetValue() + 1; + + if (vp.start_time() != imgseq_start_time_->GetValue() + || vp.duration() != new_dur + || vp.frame_rate() != imgseq_frame_rate_->GetFrameRate()) { + parent->add_child(new ImageSequenceChangeCommand(footage_, + video_index_, + imgseq_start_time_->GetValue(), + new_dur, + imgseq_frame_rate_->GetFrameRate())); + } + } +} + +bool VideoStreamProperties::SanityCheck() +{ + if (footage_->GetVideoParams(video_index_).video_type() == VideoParams::kVideoTypeImageSequence) { + if (imgseq_start_time_->GetValue() >= imgseq_end_time_->GetValue()) { + QMessageBox::critical(this, + tr("Invalid Configuration"), + tr("Image sequence end index must be a value higher than the start index."), + QMessageBox::Ok); + return false; + } + } + + return true; +} + +VideoStreamProperties::VideoStreamChangeCommand::VideoStreamChangeCommand(Footage *footage, + int video_index, + bool premultiplied, + QString colorspace, + VideoParams::Interlacing interlacing, + const rational &pixel_ar) : + footage_(footage), + video_index_(video_index), + new_premultiplied_(premultiplied), + new_colorspace_(colorspace), + new_interlacing_(interlacing), + new_pixel_ar_(pixel_ar) +{ +} + +Project *VideoStreamProperties::VideoStreamChangeCommand::GetRelevantProject() const +{ + return footage_->project(); +} + +void VideoStreamProperties::VideoStreamChangeCommand::redo() +{ + VideoParams vp = footage_->GetVideoParams(video_index_); + + old_premultiplied_ = vp.premultiplied_alpha(); + old_colorspace_ = vp.colorspace(); + old_interlacing_ = vp.interlacing(); + old_pixel_ar_ = vp.pixel_aspect_ratio(); + + vp.set_premultiplied_alpha(new_premultiplied_); + vp.set_colorspace(new_colorspace_); + vp.set_interlacing(new_interlacing_); + vp.set_pixel_aspect_ratio(new_pixel_ar_); + + footage_->SetVideoParams(vp, video_index_); +} + +void VideoStreamProperties::VideoStreamChangeCommand::undo() +{ + VideoParams vp = footage_->GetVideoParams(video_index_); + + vp.set_premultiplied_alpha(old_premultiplied_); + vp.set_colorspace(old_colorspace_); + vp.set_interlacing(old_interlacing_); + vp.set_pixel_aspect_ratio(old_pixel_ar_); + + footage_->SetVideoParams(vp, video_index_); +} + +VideoStreamProperties::ImageSequenceChangeCommand::ImageSequenceChangeCommand(Footage *footage, int video_index, int64_t start_index, int64_t duration, const rational &frame_rate) : + footage_(footage), + video_index_(video_index), + new_start_index_(start_index), + new_duration_(duration), + new_frame_rate_(frame_rate) +{ +} + +Project *VideoStreamProperties::ImageSequenceChangeCommand::GetRelevantProject() const +{ + return footage_->project(); +} + +void VideoStreamProperties::ImageSequenceChangeCommand::redo() +{ + VideoParams vp = footage_->GetVideoParams(video_index_); + + old_start_index_ = vp.start_time(); + vp.set_start_time(new_start_index_); + + old_duration_ = vp.duration(); + vp.set_duration(new_duration_); + + old_frame_rate_ = vp.frame_rate(); + vp.set_frame_rate(new_frame_rate_); + vp.set_time_base(new_frame_rate_.flipped()); + + footage_->SetVideoParams(vp, video_index_); +} + +void VideoStreamProperties::ImageSequenceChangeCommand::undo() +{ + VideoParams vp = footage_->GetVideoParams(video_index_); + + vp.set_start_time(old_start_index_); + vp.set_duration(old_duration_); + vp.set_frame_rate(old_frame_rate_); + vp.set_time_base(old_frame_rate_.flipped()); + + footage_->SetVideoParams(vp, video_index_); +} + +} diff --git a/app/dialog/footageproperties/streamproperties/videostreamproperties.h b/app/dialog/footageproperties/streamproperties/videostreamproperties.h new file mode 100644 index 000000000..3d858b2c1 --- /dev/null +++ b/app/dialog/footageproperties/streamproperties/videostreamproperties.h @@ -0,0 +1,146 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2020 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 VIDEOSTREAMPROPERTIES_H +#define VIDEOSTREAMPROPERTIES_H + +#include +#include + +#include "node/project/footage/footage.h" +#include "streamproperties.h" +#include "widget/slider/integerslider.h" +#include "widget/standardcombos/standardcombos.h" + +namespace olive { + +class VideoStreamProperties : public StreamProperties +{ + Q_OBJECT +public: + VideoStreamProperties(Footage *footage, int video_index); + + virtual void Accept(MultiUndoCommand *parent) override; + + virtual bool SanityCheck() override; + +private: + Footage *footage_; + + int video_index_; + + /** + * @brief Setting for associated/premultiplied alpha + */ + QCheckBox* video_premultiply_alpha_; + + /** + * @brief Setting for this media's color space + */ + QComboBox* video_color_space_; + + /** + * @brief Setting for video interlacing + */ + InterlacedComboBox* video_interlace_combo_; + + /** + * @brief Sets the start index for image sequences + */ + IntegerSlider* imgseq_start_time_; + + /** + * @brief Sets the end index for image sequences + */ + IntegerSlider* imgseq_end_time_; + + /** + * @brief Sets the frame rate for image sequences + */ + FrameRateComboBox* imgseq_frame_rate_; + + /** + * @brief Sets the pixel aspect ratio of the stream + */ + PixelAspectRatioComboBox* pixel_aspect_combo_; + + class VideoStreamChangeCommand : public UndoCommand { + public: + VideoStreamChangeCommand(Footage *footage, + int video_index, + bool premultiplied, + QString colorspace, + VideoParams::Interlacing interlacing, + const rational& pixel_ar); + + virtual Project* GetRelevantProject() const override; + + virtual void redo() override; + virtual void undo() override; + + private: + Footage *footage_; + int video_index_; + + bool new_premultiplied_; + QString new_colorspace_; + VideoParams::Interlacing new_interlacing_; + rational new_pixel_ar_; + + bool old_premultiplied_; + QString old_colorspace_; + VideoParams::Interlacing old_interlacing_; + rational old_pixel_ar_; + + }; + + class ImageSequenceChangeCommand : public UndoCommand { + public: + ImageSequenceChangeCommand(Footage *footage, + int video_index, + int64_t start_index, + int64_t duration, + const rational& frame_rate); + + virtual Project* GetRelevantProject() const override; + + virtual void redo() override; + virtual void undo() override; + + private: + Footage *footage_; + int video_index_; + + int64_t new_start_index_; + int64_t old_start_index_; + + int64_t new_duration_; + int64_t old_duration_; + + rational new_frame_rate_; + rational old_frame_rate_; + + }; + +}; + +} + +#endif // VIDEOSTREAMPROPERTIES_H diff --git a/app/dialog/nodeproperties/nodepropertiesdialog.cpp b/app/dialog/nodeproperties/nodepropertiesdialog.cpp deleted file mode 100644 index fca1935ba..000000000 --- a/app/dialog/nodeproperties/nodepropertiesdialog.cpp +++ /dev/null @@ -1,74 +0,0 @@ -/*** - - 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 "nodepropertiesdialog.h" - -#include -#include - -#include "core.h" -#include "widget/nodeview/nodeviewundo.h" - -namespace olive { - -NodePropertiesDialog::NodePropertiesDialog(Node *node, const rational &timebase, QWidget *parent) : - QDialog(parent), - node_(node) -{ - setWindowTitle(tr("Node Properties")); - - QVBoxLayout *layout = new QVBoxLayout(this); - - QHBoxLayout *label_layout = new QHBoxLayout(); - label_layout->setMargin(0); - layout->addLayout(label_layout); - - label_layout->addWidget(new QLabel(tr("Name:"))); - - label_edit_ = new QLineEdit(); - label_edit_->setText(node->GetLabel()); - label_layout->addWidget(label_edit_); - - NodeParamViewItem *item = new NodeParamViewItem(node, kNoCheckBoxes); - item->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - item->SetTimebase(timebase); - item->setTitleBarWidget(new QWidget()); - layout->addWidget(item); - - layout->addStretch(); - - QDialogButtonBox *btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(btns, &QDialogButtonBox::accepted, this, &NodePropertiesDialog::accept); - connect(btns, &QDialogButtonBox::rejected, this, &NodePropertiesDialog::reject); - layout->addWidget(btns); -} - -void NodePropertiesDialog::accept() -{ - if (label_edit_->text() != node_->GetLabel()) { - NodeRenameCommand* rename_command = new NodeRenameCommand(); - rename_command->AddNode(node_, label_edit_->text()); - Core::instance()->undo_stack()->push(rename_command); - } - - QDialog::accept(); -} - -} diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 9326cd357..4482ac8d7 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -30,7 +30,7 @@ #include "common/define.h" #include "core.h" -#include "dialog/nodeproperties/nodepropertiesdialog.h" +#include "dialog/footageproperties/footageproperties.h" #include "dialog/sequence/sequence.h" #include "projectexplorerundo.h" #include "task/precache/precachetask.h" @@ -446,8 +446,8 @@ void ProjectExplorer::ShowItemPropertiesDialog() // FIXME: Support for multiple items if (dynamic_cast(sel)) { - NodePropertiesDialog npd(sel, static_cast(sel)->GetVideoParams().time_base(), this); - npd.exec(); + FootagePropertiesDialog fpd(this, static_cast(sel)); + fpd.exec(); } else if (dynamic_cast(sel)) { diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 3416f6e05..a16bdbc31 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -28,7 +28,6 @@ #include "core.h" #include "common/range.h" #include "common/timecodefunctions.h" -#include "dialog/nodeproperties/nodepropertiesdialog.h" #include "dialog/sequence/sequence.h" #include "dialog/speedduration/speeddurationdialog.h" #include "node/block/transition/transition.h" @@ -1023,17 +1022,7 @@ void TimelineWidget::ShowContextMenu() menu.addSeparator(); QAction* properties_action = menu.addAction(tr("Properties")); - connect(properties_action, &QAction::triggered, this, [this](){ - QVector block_items = GetSelectedBlocks(); - QVector nodes; - - foreach (Block* i, block_items) { - nodes.append(i); - } - - NodePropertiesDialog npd(nodes, timebase(), this); - npd.exec(); - }); + connect(properties_action, &QAction::triggered, this, &TimelineWidget::ShowSpeedDurationDialogForSelectedClips); } if (selected.isEmpty()) { From bf196e790b5a28d50ad37b6317da45b34ad7bbf9 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 15 Dec 2021 19:45:04 -0800 Subject: [PATCH 20/34] refined keyframeview and curveview --- app/common/bezier.cpp | 4 + .../sequence/sequencedialogparametertab.cpp | 47 +- .../sequence/sequencedialogparametertab.h | 22 +- app/node/node.cpp | 12 +- app/node/node.h | 6 +- app/node/output/viewer/viewer.cpp | 4 - app/node/output/viewer/viewer.h | 2 - app/node/project/footage/footage.cpp | 34 - app/panel/curve/curve.h | 12 + app/widget/CMakeLists.txt | 1 - app/widget/curvewidget/CMakeLists.txt | 6 +- .../curvewidget/beziercontrolpointitem.cpp | 106 --- .../curvewidget/beziercontrolpointitem.h | 68 -- app/widget/curvewidget/curveview.cpp | 330 +++++++--- app/widget/curvewidget/curveview.h | 54 +- app/widget/curvewidget/curvewidget.cpp | 12 +- app/widget/keyframeview/CMakeLists.txt | 2 - app/widget/keyframeview/keyframeview.cpp | 429 ++++++++++++- app/widget/keyframeview/keyframeview.h | 110 +++- app/widget/keyframeview/keyframeviewbase.cpp | 602 ------------------ app/widget/keyframeview/keyframeviewbase.h | 139 ---- .../keyframeviewinputconnection.cpp | 13 +- .../keyframeviewinputconnection.h | 8 +- app/widget/nodeparamview/nodeparamview.cpp | 10 +- app/widget/nodeparamview/nodeparamviewitem.h | 8 +- .../nodeparamviewwidgetbridge.cpp | 47 +- app/widget/nodetreeview/nodetreeview.cpp | 2 + app/widget/timebased/timebasedview.h | 4 + .../timebased/timebasedviewselectionmanager.h | 100 ++- app/widget/videoparamedit/CMakeLists.txt | 22 - app/widget/videoparamedit/videoparamedit.cpp | 391 ------------ app/widget/videoparamedit/videoparamedit.h | 182 ------ app/window/mainwindow/mainwindow.cpp | 1 + 33 files changed, 1013 insertions(+), 1777 deletions(-) delete mode 100644 app/widget/curvewidget/beziercontrolpointitem.cpp delete mode 100644 app/widget/curvewidget/beziercontrolpointitem.h delete mode 100644 app/widget/keyframeview/keyframeviewbase.cpp delete mode 100644 app/widget/keyframeview/keyframeviewbase.h delete mode 100644 app/widget/videoparamedit/CMakeLists.txt delete mode 100644 app/widget/videoparamedit/videoparamedit.cpp delete mode 100644 app/widget/videoparamedit/videoparamedit.h diff --git a/app/common/bezier.cpp b/app/common/bezier.cpp index 10fd88a2a..a1d900d57 100644 --- a/app/common/bezier.cpp +++ b/app/common/bezier.cpp @@ -58,6 +58,10 @@ double Bezier::CalculateTFromX(bool cubic, double x, double a, double b, double double top = 1.0; while (true) { + if (bottom == top) { + return bottom; + } + double mid = (bottom + top) * 0.5; double test = cubic ? CubicTtoY(a, b, c, d, mid) : QuadraticTtoY(a, b, c, mid); diff --git a/app/dialog/sequence/sequencedialogparametertab.cpp b/app/dialog/sequence/sequencedialogparametertab.cpp index 9d77d8bc7..00f89e002 100644 --- a/app/dialog/sequence/sequencedialogparametertab.cpp +++ b/app/dialog/sequence/sequencedialogparametertab.cpp @@ -19,11 +19,28 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg // Set up video section QGroupBox* video_group = new QGroupBox(); video_group->setTitle(tr("Video")); - QHBoxLayout* video_layout = new QHBoxLayout(video_group); - video_section_ = new VideoParamEdit(); - video_section_->SetParameterMask(Sequence::kVideoParamEditMask); - connect(video_section_, &VideoParamEdit::Changed, this, &SequenceDialogParameterTab::UpdatePreviewResolutionLabel); - video_layout->addWidget(video_section_); + QGridLayout *video_layout = new QGridLayout(video_group); + video_layout->addWidget(new QLabel(tr("Width:")), row, 0); + width_slider_ = new IntegerSlider(); + width_slider_->SetMinimum(0); + video_layout->addWidget(width_slider_, row, 1); + row++; + video_layout->addWidget(new QLabel(tr("Height:")), row, 0); + height_slider_ = new IntegerSlider(); + height_slider_->SetMinimum(0); + video_layout->addWidget(height_slider_, row, 1); + row++; + video_layout->addWidget(new QLabel(tr("Frame Rate:")), row, 0); + framerate_combo_ = new FrameRateComboBox(); + video_layout->addWidget(framerate_combo_, row, 1); + row++; + video_layout->addWidget(new QLabel(tr("Pixel Aspect Ratio:")), row, 0); + pixelaspect_combo_ = new PixelAspectRatioComboBox(); + video_layout->addWidget(pixelaspect_combo_, row, 1); + row++; + video_layout->addWidget(new QLabel(tr("Interlacing:")), row, 0); + interlacing_combo_ = new InterlacedComboBox(); + video_layout->addWidget(interlacing_combo_, row, 1); layout->addWidget(video_group); row = 0; @@ -65,7 +82,11 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg // Set values based on input sequence VideoParams vp = sequence->GetVideoParams(); AudioParams ap = sequence->GetAudioParams(); - video_section_->SetVideoParams(vp); + width_slider_->SetValue(vp.width()); + height_slider_->SetValue(vp.height()); + framerate_combo_->SetFrameRate(vp.time_base().flipped()); + pixelaspect_combo_->SetPixelAspectRatio(vp.pixel_aspect_ratio()); + interlacing_combo_->SetInterlaceMode(vp.interlacing()); preview_resolution_field_->SetDivider(vp.divider()); preview_format_field_->SetPixelFormat(vp.format()); preview_autocache_field_->setChecked(sequence->GetVideoAutoCacheEnabled()); @@ -86,11 +107,11 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg void SequenceDialogParameterTab::PresetChanged(const SequencePreset &preset) { - video_section_->SetWidth(preset.width()); - video_section_->SetHeight(preset.height()); - video_section_->SetFrameRate(preset.frame_rate()); - video_section_->SetPixelAspectRatio(preset.pixel_aspect()); - video_section_->SetInterlaceMode(preset.interlacing()); + width_slider_->SetValue(preset.width()); + height_slider_->SetValue(preset.height()); + framerate_combo_->SetFrameRate(preset.frame_rate()); + pixelaspect_combo_->SetPixelAspectRatio(preset.pixel_aspect()); + interlacing_combo_->SetInterlaceMode(preset.interlacing()); audio_sample_rate_field_->SetSampleRate(preset.sample_rate()); audio_channels_field_->SetChannelLayout(preset.channel_layout()); preview_resolution_field_->SetDivider(preset.preview_divider()); @@ -115,8 +136,8 @@ void SequenceDialogParameterTab::SavePresetClicked() void SequenceDialogParameterTab::UpdatePreviewResolutionLabel() { - VideoParams test_param(video_section_->GetWidth(), - video_section_->GetHeight(), + VideoParams test_param(GetSelectedVideoWidth(), + GetSelectedVideoHeight(), VideoParams::kFormatInvalid, VideoParams::kInternalChannelCount, rational(1), diff --git a/app/dialog/sequence/sequencedialogparametertab.h b/app/dialog/sequence/sequencedialogparametertab.h index 6d4d3bf08..e11561b2f 100644 --- a/app/dialog/sequence/sequencedialogparametertab.h +++ b/app/dialog/sequence/sequencedialogparametertab.h @@ -1,6 +1,7 @@ #ifndef SEQUENCEDIALOGPARAMETERTAB_H #define SEQUENCEDIALOGPARAMETERTAB_H +#include #include #include #include @@ -9,7 +10,6 @@ #include "sequencepreset.h" #include "widget/slider/integerslider.h" #include "widget/standardcombos/standardcombos.h" -#include "widget/videoparamedit/videoparamedit.h" namespace olive { @@ -21,27 +21,27 @@ public: int GetSelectedVideoWidth() const { - return video_section_->GetWidth(); + return width_slider_->GetValue(); } int GetSelectedVideoHeight() const { - return video_section_->GetHeight(); + return height_slider_->GetValue(); } rational GetSelectedVideoFrameRate() const { - return video_section_->GetFrameRate(); + return framerate_combo_->GetFrameRate(); } rational GetSelectedVideoPixelAspect() const { - return video_section_->GetPixelAspectRatio(); + return pixelaspect_combo_->GetPixelAspectRatio(); } VideoParams::Interlacing GetSelectedVideoInterlacingMode() const { - return video_section_->GetInterlaceMode(); + return interlacing_combo_->GetInterlaceMode(); } int GetSelectedAudioSampleRate() const @@ -76,7 +76,15 @@ signals: void SaveParametersAsPreset(const SequencePreset& preset); private: - VideoParamEdit* video_section_; + IntegerSlider *width_slider_; + + IntegerSlider *height_slider_; + + FrameRateComboBox *framerate_combo_; + + PixelAspectRatioComboBox *pixelaspect_combo_; + + InterlacedComboBox *interlacing_combo_; SampleRateComboBox* audio_sample_rate_field_; diff --git a/app/node/node.cpp b/app/node/node.cpp index 4e850e821..ad406a8bb 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -2246,7 +2246,6 @@ void Node::childEvent(QChildEvent *event) GetImmediate(key->input(), key->element())->insert_keyframe(key); connect(key, &NodeKeyframe::TimeChanged, this, &Node::InvalidateFromKeyframeTimeChange); - connect(key, &NodeKeyframe::TimeChanged, this, &Node::KeyframeTimeChanged); connect(key, &NodeKeyframe::ValueChanged, this, &Node::InvalidateFromKeyframeValueChange); connect(key, &NodeKeyframe::TypeChanged, this, &Node::InvalidateFromKeyframeTypeChanged); connect(key, &NodeKeyframe::BezierControlInChanged, this, &Node::InvalidateFromKeyframeBezierInChange); @@ -2258,15 +2257,14 @@ void Node::childEvent(QChildEvent *event) TimeRange time_affected = GetRangeAffectedByKeyframe(key); disconnect(key, &NodeKeyframe::TimeChanged, this, &Node::InvalidateFromKeyframeTimeChange); - disconnect(key, &NodeKeyframe::TimeChanged, this, &Node::KeyframeTimeChanged); disconnect(key, &NodeKeyframe::ValueChanged, this, &Node::InvalidateFromKeyframeValueChange); disconnect(key, &NodeKeyframe::TypeChanged, this, &Node::InvalidateFromKeyframeTypeChanged); disconnect(key, &NodeKeyframe::BezierControlInChanged, this, &Node::InvalidateFromKeyframeBezierInChange); disconnect(key, &NodeKeyframe::BezierControlOutChanged, this, &Node::InvalidateFromKeyframeBezierOutChange); - GetImmediate(key->input(), key->element())->remove_keyframe(key); - emit KeyframeRemoved(key); + + GetImmediate(key->input(), key->element())->remove_keyframe(key); ParameterValueChanged(i, time_affected); } } @@ -2329,12 +2327,16 @@ void Node::InvalidateFromKeyframeTimeChange() foreach (const TimeRange& r, invalidate_range) { ParameterValueChanged(key->key_track_ref().input(), r); } + + emit KeyframeTimeChanged(key); } void Node::InvalidateFromKeyframeValueChange() { NodeKeyframe* key = static_cast(sender()); ParameterValueChanged(key->key_track_ref().input(), GetRangeAffectedByKeyframe(key)); + + emit KeyframeValueChanged(key); } void Node::InvalidateFromKeyframeTypeChanged() @@ -2349,6 +2351,8 @@ void Node::InvalidateFromKeyframeTypeChanged() // Invalidate entire range ParameterValueChanged(key->key_track_ref().input(), GetRangeAroundIndex(key->input(), track.indexOf(key), key->track(), key->element())); + + emit KeyframeTypeChanged(key); } Project *Node::ArrayInsertCommand::GetRelevantProject() const diff --git a/app/node/node.h b/app/node/node.h index f38835ce9..92d06e63f 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -1066,7 +1066,11 @@ signals: void KeyframeRemoved(NodeKeyframe* key); - void KeyframeTimeChanged(); + void KeyframeTimeChanged(NodeKeyframe* key); + + void KeyframeTypeChanged(NodeKeyframe* key); + + void KeyframeValueChanged(NodeKeyframe* key); void KeyframeEnableChanged(const NodeInput& input, bool enabled); diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 87707e2c1..8e257f6e9 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -23,7 +23,6 @@ #include "config/config.h" #include "core.h" #include "node/traverser.h" -#include "widget/videoparamedit/videoparamedit.h" namespace olive { @@ -34,8 +33,6 @@ const QString ViewerOutput::kSamplesInput = QStringLiteral("samples_in"); const QString ViewerOutput::kVideoAutoCacheInput = QStringLiteral("video_autocache_in"); const QString ViewerOutput::kAudioAutoCacheInput = QStringLiteral("audio_autocache_in"); -const uint64_t ViewerOutput::kVideoParamEditMask = VideoParamEdit::kWidthHeight | VideoParamEdit::kInterlacing | VideoParamEdit::kFrameRate | VideoParamEdit::kPixelAspect; - #define super Node ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_streams) : @@ -48,7 +45,6 @@ ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_stream audio_cache_enabled_(true) { AddInput(kVideoParamsInput, NodeValue::kVideoParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); - SetInputProperty(kVideoParamsInput, QStringLiteral("mask"), QVariant::fromValue(kVideoParamEditMask)); AddInput(kAudioParamsInput, NodeValue::kAudioParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 5c516000c..2be79bf2f 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -200,8 +200,6 @@ public: static const QString kVideoAutoCacheInput; static const QString kAudioAutoCacheInput; - static const uint64_t kVideoParamEditMask; - signals: void FrameRateChanged(const rational&); diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 4ab67ebb8..a9aa54705 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -33,7 +33,6 @@ #include "core.h" #include "render/job/footagejob.h" #include "ui/icons/icons.h" -#include "widget/videoparamedit/videoparamedit.h" namespace olive { @@ -144,39 +143,6 @@ void Footage::InputValueChangedEvent(const QString &input, int element) AddStream(Track::kVideo, QVariant::fromValue(vp)); } - if (!footage_info.GetVideoStreams().isEmpty()) { - // FIXME: This will break on multiple video streams. Currently we don't have - // infrastructure for different properties per element. We'll see if this becomes - // a problem. - VideoParams vp = footage_info.GetVideoStreams().first(); - - uint64_t video_param_mask = 0; - - video_param_mask |= VideoParamEdit::kEnabled; - video_param_mask |= VideoParamEdit::kColorspace; - video_param_mask |= VideoParamEdit::kPixelAspect; - video_param_mask |= VideoParamEdit::kInterlacing; - video_param_mask |= VideoParamEdit::kFrameRateIsArbitrary; - - if (vp.channel_count() == VideoParams::kRGBAChannelCount) { - // Add premultiplied setting if this footage has an alpha channel - video_param_mask |= VideoParamEdit::kPremultipliedAlpha; - } - - if (vp.video_type() == VideoParams::kVideoTypeVideo) { - // This is video, ensure that the frame rate does not overwrite the timebase - video_param_mask |= VideoParamEdit::kFrameRateIsNotTimebase; - } else { - // This is not a video, so it's either a still image or an image sequence - video_param_mask |= VideoParamEdit::kIsImageSequence; - video_param_mask |= VideoParamEdit::kStartTime; - video_param_mask |= VideoParamEdit::kEndTime; - video_param_mask |= VideoParamEdit::kFrameRate; - } - - SetInputProperty(kVideoParamsInput, QStringLiteral("mask"), QVariant::fromValue(video_param_mask)); - } - for (int i=0; i nodes; + + if (node) { + nodes.append(node); + } + + SetNodes(nodes); + } + void SetNodes(const QVector &nodes); virtual void IncreaseTrackHeight() override; diff --git a/app/widget/CMakeLists.txt b/app/widget/CMakeLists.txt index 38fefccbd..915214d9e 100644 --- a/app/widget/CMakeLists.txt +++ b/app/widget/CMakeLists.txt @@ -51,7 +51,6 @@ add_subdirectory(timelinewidget) add_subdirectory(timeruler) add_subdirectory(timetarget) add_subdirectory(toolbar) -add_subdirectory(videoparamedit) add_subdirectory(viewer) set(OLIVE_SOURCES diff --git a/app/widget/curvewidget/CMakeLists.txt b/app/widget/curvewidget/CMakeLists.txt index 1ceeadf85..baafedb04 100644 --- a/app/widget/curvewidget/CMakeLists.txt +++ b/app/widget/curvewidget/CMakeLists.txt @@ -16,11 +16,9 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} - widget/curvewidget/beziercontrolpointitem.h - widget/curvewidget/beziercontrolpointitem.cpp - widget/curvewidget/curveview.h widget/curvewidget/curveview.cpp - widget/curvewidget/curvewidget.h + widget/curvewidget/curveview.h widget/curvewidget/curvewidget.cpp + widget/curvewidget/curvewidget.h PARENT_SCOPE ) diff --git a/app/widget/curvewidget/beziercontrolpointitem.cpp b/app/widget/curvewidget/beziercontrolpointitem.cpp deleted file mode 100644 index 26f062803..000000000 --- a/app/widget/curvewidget/beziercontrolpointitem.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/*** - - 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 "beziercontrolpointitem.h" - -#include -#include -#include -#include - -#include "common/qtutils.h" - -namespace olive { - -BezierControlPointItem::BezierControlPointItem(NodeKeyframe* key, NodeKeyframe::BezierType mode, QGraphicsItem *parent) : - QGraphicsRectItem(parent), - key_(key), - mode_(mode), - x_scale_(1.0), - y_scale_(1.0) -{ - setFlag(QGraphicsItem::ItemIsMovable); - - connect(key, &NodeKeyframe::TimeChanged, this, &BezierControlPointItem::UpdatePos); - - if (mode_ == NodeKeyframe::kInHandle) { - connect(key, &NodeKeyframe::BezierControlInChanged, this, &BezierControlPointItem::UpdatePos); - } else { - connect(key, &NodeKeyframe::BezierControlOutChanged, this, &BezierControlPointItem::UpdatePos); - } - - - int control_point_size = QtUtils::QFontMetricsWidth(qApp->fontMetrics(), "o"); - int half_sz = control_point_size / 2; - setRect(-half_sz, -half_sz, control_point_size, control_point_size); -} - -void BezierControlPointItem::SetXScale(double scale) -{ - x_scale_ = scale; - UpdatePos(); -} - -void BezierControlPointItem::SetYScale(double scale) -{ - y_scale_ = scale; - UpdatePos(); -} - -NodeKeyframe* BezierControlPointItem::key() const -{ - return key_; -} - -const NodeKeyframe::BezierType &BezierControlPointItem::mode() const -{ - return mode_; -} - -QPointF BezierControlPointItem::GetCorrespondingKeyframeHandle() const -{ - return key_->bezier_control(mode_); -} - -void BezierControlPointItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) -{ - if (option->state & QStyle::State_Selected) { - painter->setPen(widget->palette().highlight().color()); - } else { - painter->setPen(widget->palette().text().color()); - } - - painter->drawEllipse(rect()); -} - -void BezierControlPointItem::UpdatePos() -{ - QPointF handle_offset = GetCorrespondingKeyframeHandle(); - - // Scale handle offset - handle_offset.setX(handle_offset.x() * x_scale_); - - // Flip the Y coordinate because bezier curves are drawn bottom to top - handle_offset.setY(-handle_offset.y() * y_scale_); - - setPos(handle_offset - rect().center()); -} - -} diff --git a/app/widget/curvewidget/beziercontrolpointitem.h b/app/widget/curvewidget/beziercontrolpointitem.h deleted file mode 100644 index 26d87f8dc..000000000 --- a/app/widget/curvewidget/beziercontrolpointitem.h +++ /dev/null @@ -1,68 +0,0 @@ -/*** - - 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 BEZIERCONTROLPOINTITEM_H -#define BEZIERCONTROLPOINTITEM_H - -#include - -#include "node/keyframe.h" - -namespace olive { - -class BezierControlPointItem : public QObject, public QGraphicsRectItem -{ -public: - BezierControlPointItem(NodeKeyframe* key, NodeKeyframe::BezierType mode, QGraphicsItem* parent = nullptr); - - void SetXScale(double scale); - - void SetYScale(double scale); - - NodeKeyframe* key() const; - - const NodeKeyframe::BezierType& mode() const; - - QPointF GetCorrespondingKeyframeHandle() const; - - void SetCorrespondingKeyframeHandle(const QPointF& handle); - - void SetOpposingKeyframeHandle(const QPointF& handle); - -protected: - virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; - -private: - NodeKeyframe* key_; - - NodeKeyframe::BezierType mode_; - - double x_scale_; - - double y_scale_; - -private slots: - void UpdatePos(); - -}; - -} - -#endif // BEZIERCONTROLPOINTITEM_H diff --git a/app/widget/curvewidget/curveview.cpp b/app/widget/curvewidget/curveview.cpp index e83909702..8fee9d725 100644 --- a/app/widget/curvewidget/curveview.cpp +++ b/app/widget/curvewidget/curveview.cpp @@ -20,31 +20,33 @@ #include "curveview.h" +#include #include #include +#include #include #include -#include #include "common/qtutils.h" +#include "widget/keyframeview/keyframeviewundo.h" +#include "widget/nodeparamview/nodeparamviewundo.h" +#include "widget/slider/floatslider.h" namespace olive { -#define super KeyframeViewBase +#define super KeyframeView CurveView::CurveView(QWidget *parent) : - KeyframeViewBase(parent) + KeyframeView(parent), + dragging_bezier_pt_(nullptr) { setAlignment(Qt::AlignLeft | Qt::AlignVCenter); - setDragMode(RubberBandDrag); - setViewportUpdateMode(FullViewportUpdate); SetYAxisEnabled(true); + SetAutoSelectSiblings(false); text_padding_ = QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral("i")); minimum_grid_space_ = QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral("00000")); - - connect(scene(), &QGraphicsScene::selectionChanged, this, &CurveView::SelectionChanged); } void CurveView::ConnectInput(const NodeKeyframeTrackReference& ref) @@ -90,7 +92,9 @@ void CurveView::SelectKeyframesOfInput(const NodeKeyframeTrackReference& ref) void CurveView::ZoomToFitInput(const NodeKeyframeTrackReference& ref) { - ZoomToFitInternal(track_connections_.value(ref)->GetKeyframes()); + if (KeyframeViewInputConnection *con = track_connections_.value(ref)) { + ZoomToFitInternal(con->GetKeyframes()); + } } void CurveView::SetKeyframeTrackColor(const NodeKeyframeTrackReference &ref, const QColor &color) @@ -98,8 +102,10 @@ void CurveView::SetKeyframeTrackColor(const NodeKeyframeTrackReference &ref, con // Insert color into hashmap keyframe_colors_.insert(ref, color); - // Update all keyframes - track_connections_.value(ref)->SetBrush(color); + if (KeyframeViewInputConnection *con = track_connections_.value(ref)) { + // Update all keyframes + con->SetBrush(color); + } } void CurveView::drawBackground(QPainter *painter, const QRectF &rect) @@ -235,40 +241,13 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect) } } } - - // Draw bezier control point lines - /*if (!bezier_control_points_.isEmpty()) { - painter->setPen(QPen(palette().text().color(), 1)); - - QVector bezier_lines; - foreach (BezierControlPointItem* item, bezier_control_points_) { - // All BezierControlPointItems should be children of a KeyframeViewItem - KeyframeViewItem* par = static_cast(item->parentItem()); - - bezier_lines.append(QLineF(par->pos(), par->pos() + item->pos())); - } - painter->drawLines(bezier_lines); - }*/ } -void CurveView::ScaleChangedEvent(const double& scale) +void CurveView::drawForeground(QPainter *painter, const QRectF &rect) { - KeyframeViewBase::ScaleChangedEvent(scale); + bezier_pts_.clear(); - foreach (BezierControlPointItem* item, bezier_control_points_) { - item->SetXScale(scale); - } -} - -void CurveView::VerticalScaleChangedEvent(double scale) -{ - Q_UNUSED(scale) - - foreach (BezierControlPointItem* item, bezier_control_points_) { - item->SetYScale(scale); - } - - viewport()->update(); + super::drawForeground(painter, rect); } void CurveView::ContextMenuEvent(Menu &m) @@ -290,6 +269,225 @@ void CurveView::SceneRectUpdateEvent(QRectF &r) r.setBottom(r.bottom() + this->height()); } +qreal CurveView::GetKeyframeSceneY(KeyframeViewInputConnection *track, NodeKeyframe *key) +{ + return GetItemYFromKeyframeValue(key); +} + +void CurveView::DrawKeyframe(QPainter *painter, NodeKeyframe *key, KeyframeViewInputConnection *track, const QRectF &key_rect) +{ + if (IsKeyframeSelected(key) && key->type() == NodeKeyframe::kBezier) { + // Draw bezier control points if keyframe is selected + int control_point_size = QtUtils::QFontMetricsWidth(fontMetrics(), "o"); + int half_sz = control_point_size / 2; + QRectF control_point_rect(-half_sz, -half_sz, control_point_size, control_point_size); + + painter->setPen(palette().text().color()); + painter->setBrush(Qt::NoBrush); + + QRectF cp_in = control_point_rect.translated(key_rect.center() + ScalePoint(key->bezier_control_in())); + QRectF cp_out = control_point_rect.translated(key_rect.center() + ScalePoint(key->bezier_control_out())); + + painter->drawLine(key_rect.center(), cp_in.center()); + painter->drawLine(key_rect.center(), cp_out.center()); + + painter->drawEllipse(cp_in); + painter->drawEllipse(cp_out); + + bezier_pts_.append({cp_in, key, NodeKeyframe::kInHandle}); + bezier_pts_.append({cp_out, key, NodeKeyframe::kOutHandle}); + } + + super::DrawKeyframe(painter, key, track, key_rect); +} + +bool CurveView::FirstChanceMousePress(QMouseEvent *event) +{ + dragging_bezier_pt_ = nullptr; + QPointF scene_pt = mapToScene(event->pos()); + foreach (const BezierPoint &b, bezier_pts_) { + if (b.rect.contains(scene_pt)) { + dragging_bezier_pt_ = &b; + break; + } + } + + if (dragging_bezier_pt_) { + NodeKeyframe *key = dragging_bezier_pt_->keyframe; + dragging_bezier_point_start_ = (dragging_bezier_pt_->type == NodeKeyframe::kInHandle) ? key->bezier_control_in() : key->bezier_control_out(); + dragging_bezier_point_opposing_start_ = (dragging_bezier_pt_->type == NodeKeyframe::kInHandle) ? key->bezier_control_out() : key->bezier_control_in(); + + drag_start_ = mapToScene(event->pos()); + return true; + } else { + return false; + } +} + +void CurveView::FirstChanceMouseMove(QMouseEvent *event) +{ + // Calculate cursor difference and scale it + QPointF scene_pos = mapToScene(event->pos()); + QPointF mouse_diff_scaled = GetScaledCursorPos(scene_pos - drag_start_); + + if (event->modifiers() & Qt::ShiftModifier) { + // If holding shift, only move one axis + mouse_diff_scaled.setY(0); + } + + // Flip the mouse Y because bezier control points are drawn bottom to top, not top to bottom + mouse_diff_scaled.setY(-mouse_diff_scaled.y()); + + QPointF new_bezier_pos = GenerateBezierControlPosition(dragging_bezier_pt_->type, + dragging_bezier_point_start_, + mouse_diff_scaled); + + // If the user is NOT holding control, we set the other handle to the exact negative of this handle + QPointF new_opposing_pos; + NodeKeyframe::BezierType opposing_type = NodeKeyframe::get_opposing_bezier_type(dragging_bezier_pt_->type); + + + if (!(event->modifiers() & Qt::ControlModifier)) { + new_opposing_pos = GenerateBezierControlPosition(opposing_type, + dragging_bezier_point_opposing_start_, + -mouse_diff_scaled); + } else { + new_opposing_pos = dragging_bezier_point_opposing_start_; + } + + dragging_bezier_pt_->keyframe->set_bezier_control(dragging_bezier_pt_->type, + new_bezier_pos); + + dragging_bezier_pt_->keyframe->set_bezier_control(opposing_type, + new_opposing_pos); + + Redraw(); +} + +void CurveView::FirstChanceMouseRelease(QMouseEvent *event) +{ + MultiUndoCommand* command = new MultiUndoCommand(); + + // Create undo command with the current bezier point and the old one + command->add_child(new KeyframeSetBezierControlPoint(dragging_bezier_pt_->keyframe, + dragging_bezier_pt_->type, + dragging_bezier_pt_->keyframe->bezier_control(dragging_bezier_pt_->type), + dragging_bezier_point_start_)); + + if (!(event->modifiers() & Qt::ControlModifier)) { + auto opposing_type = NodeKeyframe::get_opposing_bezier_type(dragging_bezier_pt_->type); + + command->add_child(new KeyframeSetBezierControlPoint(dragging_bezier_pt_->keyframe, + opposing_type, + dragging_bezier_pt_->keyframe->bezier_control(opposing_type), + dragging_bezier_point_opposing_start_)); + } + + dragging_bezier_pt_ = nullptr; + + Core::instance()->undo_stack()->push(command); +} + +void CurveView::KeyframeDragStart(QMouseEvent *event) +{ + drag_keyframe_values_.resize(GetSelectedKeyframes().size()); + for (int i=0; ivalue(); + } + + drag_start_ = mapToScene(event->pos()); +} + +void CurveView::KeyframeDragMove(QMouseEvent *event, QString &tip) +{ + if (event->modifiers() & Qt::ShiftModifier) { + // Lock to X axis only + return; + } + + // Calculate cursor difference + double scaled_diff = (mapToScene(event->pos()).y() - drag_start_.y()) / GetYScale(); + + // Validate movement - ensure no keyframe goes above its max point or below its min point + for (int i=0; iparent(); + double original_val = drag_keyframe_values_.at(i).toDouble(); + const QString& input = key->input(); + double new_val = original_val - scaled_diff; + double limited = new_val; + + if (node->HasInputProperty(input, QStringLiteral("min"))) { + limited = qMax(limited, node->GetInputProperty(input, QStringLiteral("min")).toDouble()); + } + + if (node->HasInputProperty(input, QStringLiteral("max"))) { + limited = qMin(limited, node->GetInputProperty(input, QStringLiteral("max")).toDouble()); + } + + if (limited != new_val) { + scaled_diff = original_val - limited; + } + } + + // Set values + for (int i=0; iset_value(drag_keyframe_values_.at(i).toDouble() - scaled_diff); + } + + NodeKeyframe *tip_item = GetSelectedKeyframes().first(); + FloatSlider::DisplayType display_type = FloatSlider::kNormal; + Node* initial_drag_input = tip_item->parent(); + const QString& initial_drag_input_id = tip_item->input(); + if (initial_drag_input->HasInputProperty(initial_drag_input_id, QStringLiteral("view"))) { + display_type = static_cast(initial_drag_input->GetInputProperty(initial_drag_input_id, QStringLiteral("view")).toInt()); + } + + bool ok; + double num_value = tip_item->value().toDouble(&ok); + + if (ok) { + tip = QStringLiteral("%1\n"); + tip.append(FloatSlider::ValueToString(num_value, display_type, 2, true)); + } +} + +void CurveView::KeyframeDragRelease(QMouseEvent *event, MultiUndoCommand *command) +{ + for (int i=0; iadd_child(new NodeParamSetKeyframeValueCommand(k, k->value(), drag_keyframe_values_.at(i))); + } +} + +QPointF CurveView::GenerateBezierControlPosition(const NodeKeyframe::BezierType mode, const QPointF &start_point, const QPointF &scaled_cursor_diff) +{ + QPointF new_bezier_pos = start_point; + + new_bezier_pos += scaled_cursor_diff; + + // LIMIT bezier handles from overlapping each other + if (mode == NodeKeyframe::kInHandle) { + if (new_bezier_pos.x() > 0) { + new_bezier_pos.setX(0); + } + } else { + if (new_bezier_pos.x() < 0) { + new_bezier_pos.setX(0); + } + } + + return new_bezier_pos; +} + +QPointF CurveView::GetScaledCursorPos(const QPointF &cursor_pos) +{ + return QPointF(cursor_pos.x() / GetScale(), + cursor_pos.y() / GetYScale()); +} + void CurveView::ZoomToFitInternal(const QVector &keys) { if (keys.isEmpty()) { @@ -345,36 +543,11 @@ QPointF CurveView::ScalePoint(const QPointF &point) return QPointF(point.x() * GetScale(), - point.y() * GetYScale()); } -void CurveView::CreateBezierControlPoints(NodeKeyframe* item) -{ - qDebug() << "STUB!"; - /*BezierControlPointItem* bezier_in_pt = new BezierControlPointItem(item, NodeKeyframe::kInHandle, item); - bezier_in_pt->SetXScale(GetScale()); - bezier_in_pt->SetYScale(GetYScale()); - bezier_control_points_.append(bezier_in_pt); - connect(bezier_in_pt, &QObject::destroyed, this, &CurveView::BezierControlPointDestroyed, Qt::DirectConnection); - - BezierControlPointItem* bezier_out_pt = new BezierControlPointItem(item, NodeKeyframe::kOutHandle, item); - bezier_out_pt->SetXScale(GetScale()); - bezier_out_pt->SetYScale(GetYScale()); - bezier_control_points_.append(bezier_out_pt); - connect(bezier_out_pt, &QObject::destroyed, this, &CurveView::BezierControlPointDestroyed, Qt::DirectConnection);*/ -} - QPointF CurveView::GetKeyframePosition(NodeKeyframe *key) { return QPointF(GetKeyframeSceneX(key), GetItemYFromKeyframeValue(key)); } -void CurveView::KeyframeValueChanged() -{ - qDebug() << "STUB!"; - /*NodeKeyframe* key = static_cast(sender()); - KeyframeViewItem* item = item_map().value(key); - - SetItemYFromKeyframeValue(key, item);*/ -} - void CurveView::KeyframeTypeChanged() { qDebug() << "STUB!"; @@ -387,33 +560,6 @@ void CurveView::KeyframeTypeChanged() }*/ } -void CurveView::SelectionChanged() -{ - qDebug() << "STUB!"; - /* - // Clear current bezier handles - while (!bezier_control_points_.isEmpty()) { - delete bezier_control_points_.first(); - } - - QList selected = scene()->selectedItems(); - - foreach (QGraphicsItem* item, selected) { - KeyframeViewItem* this_item = static_cast(item); - - if (this_item->key()->type() == NodeKeyframe::kBezier) { - CreateBezierControlPoints(this_item); - } - } - */ -} - -void CurveView::BezierControlPointDestroyed() -{ - BezierControlPointItem* item = static_cast(sender()); - bezier_control_points_.removeOne(item); -} - void CurveView::ZoomToFit() { QVector keys; diff --git a/app/widget/curvewidget/curveview.h b/app/widget/curvewidget/curveview.h index e5b056830..3858368e8 100644 --- a/app/widget/curvewidget/curveview.h +++ b/app/widget/curvewidget/curveview.h @@ -21,13 +21,12 @@ #ifndef CURVEVIEW_H #define CURVEVIEW_H -#include "beziercontrolpointitem.h" #include "node/keyframe.h" #include "widget/keyframeview/keyframeview.h" namespace olive { -class CurveView : public KeyframeViewBase +class CurveView : public KeyframeView { Q_OBJECT public: @@ -52,15 +51,24 @@ public slots: protected: virtual void drawBackground(QPainter* painter, const QRectF& rect) override; - - virtual void ScaleChangedEvent(const double &scale) override; - - virtual void VerticalScaleChangedEvent(double scale) override; + virtual void drawForeground(QPainter *painter, const QRectF &rect) override; virtual void ContextMenuEvent(Menu &m) override; virtual void SceneRectUpdateEvent(QRectF &r) override; + virtual qreal GetKeyframeSceneY(KeyframeViewInputConnection *track, NodeKeyframe *key) override; + + virtual void DrawKeyframe(QPainter *painter, NodeKeyframe *key, KeyframeViewInputConnection *track, const QRectF &key_rect) override; + + virtual bool FirstChanceMousePress(QMouseEvent *event) override; + virtual void FirstChanceMouseMove(QMouseEvent *event) override; + virtual void FirstChanceMouseRelease(QMouseEvent *event) override; + + virtual void KeyframeDragStart(QMouseEvent *event) override; + virtual void KeyframeDragMove(QMouseEvent *event, QString &tip) override; + virtual void KeyframeDragRelease(QMouseEvent *event, MultiUndoCommand *command) override; + private: void ZoomToFitInternal(const QVector &keys); @@ -71,10 +79,14 @@ private: void AdjustLines(); - void CreateBezierControlPoints(NodeKeyframe *item); - QPointF GetKeyframePosition(NodeKeyframe *key); + static QPointF GenerateBezierControlPosition(const NodeKeyframe::BezierType mode, + const QPointF& start_point, + const QPointF& scaled_cursor_diff); + + QPointF GetScaledCursorPos(const QPointF &cursor_pos); + QHash keyframe_colors_; QHash track_connections_; @@ -82,21 +94,27 @@ private: int minimum_grid_space_; - QVector lines_; - - QVector bezier_control_points_; - QVector connected_inputs_; + struct BezierPoint + { + QRectF rect; + NodeKeyframe *keyframe; + NodeKeyframe::BezierType type; + }; + + QVector bezier_pts_; + const BezierPoint *dragging_bezier_pt_; + + QPointF dragging_bezier_point_start_; + QPointF dragging_bezier_point_opposing_start_; + QPointF drag_start_; + + QVector drag_keyframe_values_; + private slots: - void KeyframeValueChanged(); - void KeyframeTypeChanged(); - void SelectionChanged(); - - void BezierControlPointDestroyed(); - }; } diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index c77a553aa..6dac944dd 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -34,8 +34,10 @@ namespace olive { +#define super TimeBasedWidget + CurveWidget::CurveWidget(QWidget *parent) : - TimeBasedWidget(parent) + super(parent) { QHBoxLayout* outer_layout = new QHBoxLayout(this); @@ -152,7 +154,7 @@ void CurveWidget::SetNodes(const QVector &nodes) void CurveWidget::TimeChangedEvent(const rational &time) { - TimeBasedWidget::TimeChangedEvent(time); + super::TimeChangedEvent(time); view_->SetTime(time); UpdateBridgeTime(time); @@ -160,14 +162,14 @@ void CurveWidget::TimeChangedEvent(const rational &time) void CurveWidget::TimebaseChangedEvent(const rational &timebase) { - TimeBasedWidget::TimebaseChangedEvent(timebase); + super::TimebaseChangedEvent(timebase); view_->SetTimebase(timebase); } void CurveWidget::ScaleChangedEvent(const double &scale) { - TimeBasedWidget::ScaleChangedEvent(scale); + super::ScaleChangedEvent(scale); view_->SetScale(scale); } @@ -238,7 +240,7 @@ void CurveWidget::ConnectInput(Node *node, const QString &input, bool connect) NodeKeyframeTrackReference ref(NodeInput(node, input, i), j); if (!keyframe_colors_.contains(ref)) { - QColor c = QColor::fromHsv(std::rand()%360, std::rand()%255, 255); + QColor c = QColor::fromHsl(std::rand()%360, 255, 160); keyframe_colors_.insert(ref, c); tree_view_->SetKeyframeTrackColor(ref, c); diff --git a/app/widget/keyframeview/CMakeLists.txt b/app/widget/keyframeview/CMakeLists.txt index 1ab460182..d61022fbe 100644 --- a/app/widget/keyframeview/CMakeLists.txt +++ b/app/widget/keyframeview/CMakeLists.txt @@ -18,8 +18,6 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} widget/keyframeview/keyframeview.cpp widget/keyframeview/keyframeview.h - widget/keyframeview/keyframeviewbase.cpp - widget/keyframeview/keyframeviewbase.h widget/keyframeview/keyframeviewinputconnection.cpp widget/keyframeview/keyframeviewinputconnection.h widget/keyframeview/keyframeviewundo.cpp diff --git a/app/widget/keyframeview/keyframeview.cpp b/app/widget/keyframeview/keyframeview.cpp index f8cf5413f..cec7aca99 100644 --- a/app/widget/keyframeview/keyframeview.cpp +++ b/app/widget/keyframeview/keyframeview.cpp @@ -20,15 +20,334 @@ #include "keyframeview.h" +#include +#include +#include + +#include "common/qtutils.h" +#include "dialog/keyframeproperties/keyframeproperties.h" +#include "keyframeviewundo.h" +#include "node/node.h" +#include "widget/menu/menu.h" +#include "widget/menu/menushared.h" +#include "widget/nodeparamview/nodeparamviewundo.h" + namespace olive { -#define super KeyframeViewBase +#define super TimeBasedView KeyframeView::KeyframeView(QWidget *parent) : - KeyframeViewBase(parent), - max_scroll_(0) + super(parent), + selection_manager_(this), + autoselect_siblings_(true), + max_scroll_(0), + first_chance_mouse_event_(false) { setAlignment(Qt::AlignLeft | Qt::AlignTop); + SetDefaultDragMode(RubberBandDrag); + setContextMenuPolicy(Qt::CustomContextMenu); + + connect(this, &KeyframeView::customContextMenuRequested, this, &KeyframeView::ShowContextMenu); +} + +void KeyframeView::DeleteSelected() +{ + MultiUndoCommand* command = new MultiUndoCommand(); + + foreach (NodeKeyframe *key, GetSelectedKeyframes()) { + command->add_child(new NodeParamRemoveKeyframeCommand(key)); + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); +} + +KeyframeView::NodeConnections KeyframeView::AddKeyframesOfNode(Node *n) +{ + NodeConnections map; + + foreach (const QString& i, n->inputs()) { + map.insert(i, AddKeyframesOfInput(n, i)); + } + + return map; +} + +KeyframeView::InputConnections KeyframeView::AddKeyframesOfInput(Node* n, const QString& input) +{ + InputConnections vec; + + if (n->IsInputKeyframable(input)) { + int arr_sz = n->InputArraySize(input); + vec.resize(arr_sz + 1); + for (int i=-1; i& tracks = input.node()->GetKeyframeTracks(input); + ElementConnections vec(tracks.size()); + + for (int i=0; iGetKeyframes()) { + selection_manager_.Deselect(key); + } + delete connection; + Redraw(); + } +} + +void KeyframeView::SelectAll() +{ + foreach (KeyframeViewInputConnection *track, tracks_) { + foreach (NodeKeyframe *key, track->GetKeyframes()) { + SelectKeyframe(key); + } + } +} + +void KeyframeView::DeselectAll() +{ + selection_manager_.ClearSelection(); + + Redraw(); +} + +void KeyframeView::Clear() +{ + if (!tracks_.isEmpty()) { + qDeleteAll(tracks_); + tracks_.clear(); + Redraw(); + } + + selection_manager_.ClearSelection(); +} + +void KeyframeView::SelectionManagerSelectEvent(void *obj) +{ + if (autoselect_siblings_) { + NodeKeyframe *key = static_cast(obj); + QVector keys = key->parent()->GetKeyframesAtTime(key->input(), key->time(), key->element()); + foreach (NodeKeyframe* k, keys) { + if (k != key) { + SelectKeyframe(k); + } + } + } +} + +void KeyframeView::SelectionManagerDeselectEvent(void *obj) +{ + if (autoselect_siblings_) { + NodeKeyframe *key = static_cast(obj); + QVector keys = key->parent()->GetKeyframesAtTime(key->input(), key->time(), key->element()); + foreach (NodeKeyframe* k, keys) { + if (k != key) { + DeselectKeyframe(k); + } + } + } +} + +void KeyframeView::mousePressEvent(QMouseEvent *event) +{ + NodeKeyframe *key_under_cursor = selection_manager_.GetObjectAtPoint(event->pos()); + + if (HandPress(event) || (!key_under_cursor && PlayheadPress(event))) { + return; + } + + // Do mouse press things + if (FirstChanceMousePress(event)) { + first_chance_mouse_event_ = true; + } else if (NodeKeyframe *initial_key = selection_manager_.MousePress(event)) { + selection_manager_.DragStart(initial_key, event); + KeyframeDragStart(event); + } else { + selection_manager_.RubberBandStart(event); + } + + // Update view + Redraw(); +} + +void KeyframeView::mouseMoveEvent(QMouseEvent *event) +{ + if (HandMove(event) || PlayheadMove(event)) { + return; + } + + if (first_chance_mouse_event_) { + FirstChanceMouseMove(event); + } else if (selection_manager_.IsDragging()) { + QString tip; + KeyframeDragMove(event, tip); + selection_manager_.DragMove(event, tip); + } else if (selection_manager_.IsRubberBanding()) { + selection_manager_.RubberBandMove(event); + Redraw(); + } + + if (event->buttons()) { + // Signal cursor pos in case we should scroll to catch up to it + QPointF scene_pos = mapToScene(event->pos()); + emit Dragged(scene_pos.x(), scene_pos.y()); + } +} + +void KeyframeView::mouseReleaseEvent(QMouseEvent *event) +{ + if (HandRelease(event) || PlayheadRelease(event)) { + return; + } + + if (first_chance_mouse_event_) { + FirstChanceMouseRelease(event); + first_chance_mouse_event_ = false; + } else if (selection_manager_.IsDragging()) { + MultiUndoCommand* command = new MultiUndoCommand(); + selection_manager_.DragStop(command); + KeyframeDragRelease(event, command); + Core::instance()->undo_stack()->push(command); + } else if (selection_manager_.IsRubberBanding()) { + selection_manager_.RubberBandStop(); + Redraw(); + } +} + +void KeyframeView::drawForeground(QPainter *painter, const QRectF &rect) +{ + int key_sz = QtUtils::QFontMetricsWidth(fontMetrics(), "Oi"); + int key_rad = key_sz/2; + + selection_manager_.ClearDrawnObjects(); + + painter->setRenderHint(QPainter::Antialiasing); + + foreach (KeyframeViewInputConnection *track, tracks_) { + foreach (NodeKeyframe *key, track->GetKeyframes()) { + QRectF key_rect(-key_rad, -key_rad, key_sz, key_sz); + key_rect.translate(GetKeyframeSceneX(key), GetKeyframeSceneY(track, key)); + + if (!rect.intersects(key_rect)) { + continue; + } + + DrawKeyframe(painter, key, track, key_rect); + } + } + + super::drawForeground(painter, rect); +} + +void KeyframeView::DrawKeyframe(QPainter *painter, NodeKeyframe *key, KeyframeViewInputConnection *track, const QRectF &key_rect) +{ + painter->setPen(Qt::black); + + if (IsKeyframeSelected(key)) { + painter->setBrush(palette().highlight()); + } else { + painter->setBrush(track->GetBrush()); + } + + selection_manager_.DeclareDrawnObject(key, key_rect); + + switch (key->type()) { + case NodeKeyframe::kLinear: + { + QPointF points[] = { + QPointF(key_rect.center().x(), key_rect.top()), + QPointF(key_rect.right(), key_rect.center().y()), + QPointF(key_rect.center().x(), key_rect.bottom()), + QPointF(key_rect.left(), key_rect.center().y()) + }; + + painter->drawPolygon(points, 4); + break; + } + case NodeKeyframe::kBezier: + painter->drawEllipse(key_rect); + break; + case NodeKeyframe::kHold: + painter->drawRect(key_rect); + break; + } +} + +void KeyframeView::ScaleChangedEvent(const double &scale) +{ + super::ScaleChangedEvent(scale); + + Redraw(); +} + +void KeyframeView::TimeTargetChangedEvent(Node *target) +{ + Redraw(); +} + +void KeyframeView::TimebaseChangedEvent(const rational &timebase) +{ + super::TimebaseChangedEvent(timebase); + + selection_manager_.SetTimebase(timebase); +} + +void KeyframeView::ContextMenuEvent(Menu& m) +{ + Q_UNUSED(m) +} + +void KeyframeView::SelectKeyframe(NodeKeyframe *key) +{ + if (selection_manager_.Select(key)) { + Redraw(); + } +} + +void KeyframeView::DeselectKeyframe(NodeKeyframe *key) +{ + if (selection_manager_.Deselect(key)) { + Redraw(); + } +} + +rational KeyframeView::GetAdjustedKeyframeTime(NodeKeyframe *key) +{ + return GetAdjustedTime(key->parent(), GetTimeTarget(), key->time(), false); +} + +double KeyframeView::GetKeyframeSceneX(NodeKeyframe *key) +{ + return TimeToScene(GetAdjustedKeyframeTime(key)); +} + +qreal KeyframeView::GetKeyframeSceneY(KeyframeViewInputConnection *track, NodeKeyframe *key) +{ + return mapFromGlobal(QPoint(0, track->GetKeyframeY())).y(); } void KeyframeView::SceneRectUpdateEvent(QRectF &rect) @@ -37,4 +356,108 @@ void KeyframeView::SceneRectUpdateEvent(QRectF &rect) rect.setHeight(max_scroll_); } +rational KeyframeView::CalculateNewTimeFromScreen(const rational &old_time, double cursor_diff) +{ + return rational::fromDouble(old_time.toDouble() + cursor_diff); +} + +void KeyframeView::ShowContextMenu() +{ + Menu m; + + MenuShared::instance()->AddItemsForEditMenu(&m, false); + + QAction* linear_key_action = nullptr; + QAction* bezier_key_action = nullptr; + QAction* hold_key_action = nullptr; + + if (!GetSelectedKeyframes().isEmpty()) { + bool all_keys_are_same_type = true; + NodeKeyframe::Type type = GetSelectedKeyframes().first()->type(); + + for (int i=1;itype() != prev_item->type()) { + all_keys_are_same_type = false; + break; + } + } + + m.addSeparator(); + + linear_key_action = m.addAction(tr("Linear")); + bezier_key_action = m.addAction(tr("Bezier")); + hold_key_action = m.addAction(tr("Hold")); + + if (all_keys_are_same_type) { + switch (type) { + case NodeKeyframe::kLinear: + linear_key_action->setChecked(true); + break; + case NodeKeyframe::kBezier: + bezier_key_action->setChecked(true); + break; + case NodeKeyframe::kHold: + hold_key_action->setChecked(true); + break; + } + } + } + + m.addSeparator(); + + AddSetScrollZoomsByDefaultActionToMenu(&m); + + m.addSeparator(); + + ContextMenuEvent(m); + + if (!GetSelectedKeyframes().isEmpty()) { + m.addSeparator(); + + QAction* properties_action = m.addAction(tr("P&roperties")); + connect(properties_action, &QAction::triggered, this, &KeyframeView::ShowKeyframePropertiesDialog); + } + + QAction* selected = m.exec(QCursor::pos()); + + // Process keyframe type changes + if (selected) { + if (selected == linear_key_action + || selected == bezier_key_action + || selected == hold_key_action) { + NodeKeyframe::Type new_type; + + if (selected == hold_key_action) { + new_type = NodeKeyframe::kHold; + } else if (selected == bezier_key_action) { + new_type = NodeKeyframe::kBezier; + } else { + new_type = NodeKeyframe::kLinear; + } + + MultiUndoCommand* command = new MultiUndoCommand(); + foreach (NodeKeyframe* item, GetSelectedKeyframes()) { + command->add_child(new KeyframeSetTypeCommand(item, new_type)); + } + Core::instance()->undo_stack()->push(command); + } + } +} + +void KeyframeView::ShowKeyframePropertiesDialog() +{ + if (!GetSelectedKeyframes().isEmpty()) { + KeyframePropertiesDialog kd(GetSelectedKeyframes(), timebase(), this); + kd.exec(); + } +} + +void KeyframeView::Redraw() +{ + viewport()->update(); +} + } diff --git a/app/widget/keyframeview/keyframeview.h b/app/widget/keyframeview/keyframeview.h index 56874232a..a0c1aa99a 100644 --- a/app/widget/keyframeview/keyframeview.h +++ b/app/widget/keyframeview/keyframeview.h @@ -18,33 +18,133 @@ ***/ -#ifndef KEYFRAMEVIEW_H -#define KEYFRAMEVIEW_H +#ifndef KEYFRAMEVIEWBASE_H +#define KEYFRAMEVIEWBASE_H -#include "keyframeviewbase.h" +#include "keyframeviewinputconnection.h" +#include "node/keyframe.h" +#include "widget/menu/menu.h" +#include "widget/timebased/timebasedview.h" +#include "widget/timebased/timebasedviewselectionmanager.h" +#include "widget/timetarget/timetarget.h" namespace olive { -class KeyframeView : public KeyframeViewBase +class KeyframeView : public TimeBasedView, public TimeTargetObject { Q_OBJECT public: KeyframeView(QWidget* parent = nullptr); + void DeleteSelected(); + + using ElementConnections = QVector; + using InputConnections = QVector; + using NodeConnections = QMap; + + NodeConnections AddKeyframesOfNode(Node* n); + + InputConnections AddKeyframesOfInput(Node *n, const QString &input); + + ElementConnections AddKeyframesOfElement(const NodeInput &input); + + KeyframeViewInputConnection *AddKeyframesOfTrack(const NodeKeyframeTrackReference &ref); + + void RemoveKeyframesOfTrack(KeyframeViewInputConnection *connection); + + void SelectAll(); + + void DeselectAll(); + + void Clear(); + + const QVector &GetSelectedKeyframes() const + { + return selection_manager_.GetSelectedObjects(); + } + + virtual void SelectionManagerSelectEvent(void *obj) override; + virtual void SelectionManagerDeselectEvent(void *obj) override; + void SetMaxScroll(int i) { max_scroll_ = i; UpdateSceneRect(); } +signals: + void Dragged(int current_x, int current_y); + protected: + virtual void mousePressEvent(QMouseEvent *event) override; + virtual void mouseMoveEvent(QMouseEvent *event) override; + virtual void mouseReleaseEvent(QMouseEvent *event) override; + + virtual void drawForeground(QPainter *painter, const QRectF &rect) override; + + virtual void DrawKeyframe(QPainter *painter, NodeKeyframe *key, KeyframeViewInputConnection *track, const QRectF &key_rect); + + virtual void ScaleChangedEvent(const double& scale) override; + + virtual void TimeTargetChangedEvent(Node*) override; + + virtual void TimebaseChangedEvent(const rational &timebase) override; + + virtual void ContextMenuEvent(Menu &m); + + virtual bool FirstChanceMousePress(QMouseEvent *event){return false;} + virtual void FirstChanceMouseMove(QMouseEvent *event){} + virtual void FirstChanceMouseRelease(QMouseEvent *event){} + + virtual void KeyframeDragStart(QMouseEvent *event){} + virtual void KeyframeDragMove(QMouseEvent *event, QString &tip){} + virtual void KeyframeDragRelease(QMouseEvent *event, MultiUndoCommand *command){} + + void SelectKeyframe(NodeKeyframe *key); + + void DeselectKeyframe(NodeKeyframe *key); + + bool IsKeyframeSelected(NodeKeyframe *key) const + { + return selection_manager_.IsSelected(key); + } + + rational GetAdjustedKeyframeTime(NodeKeyframe *key); + + double GetKeyframeSceneX(NodeKeyframe *key); + + virtual qreal GetKeyframeSceneY(KeyframeViewInputConnection *track, NodeKeyframe *key); + + void SetAutoSelectSiblings(bool e) + { + autoselect_siblings_ = e; + } + virtual void SceneRectUpdateEvent(QRectF& rect) override; +protected slots: + void Redraw(); + private: + rational CalculateNewTimeFromScreen(const rational& old_time, double cursor_diff); + + QVector tracks_; + + TimeBasedViewSelectionManager selection_manager_; + + bool autoselect_siblings_; + int max_scroll_; + bool first_chance_mouse_event_; + +private slots: + void ShowContextMenu(); + + void ShowKeyframePropertiesDialog(); + }; } -#endif // KEYFRAMEVIEW_H +#endif // KEYFRAMEVIEWBASE_H diff --git a/app/widget/keyframeview/keyframeviewbase.cpp b/app/widget/keyframeview/keyframeviewbase.cpp deleted file mode 100644 index c3be1e273..000000000 --- a/app/widget/keyframeview/keyframeviewbase.cpp +++ /dev/null @@ -1,602 +0,0 @@ -/*** - - 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 "keyframeviewbase.h" - -#include -#include -#include - -#include "common/qtutils.h" -#include "dialog/keyframeproperties/keyframeproperties.h" -#include "keyframeviewundo.h" -#include "node/node.h" -#include "widget/menu/menu.h" -#include "widget/menu/menushared.h" -#include "widget/nodeparamview/nodeparamviewundo.h" - -namespace olive { - -#define super TimeBasedView - -KeyframeViewBase::KeyframeViewBase(QWidget *parent) : - super(parent), - dragging_bezier_point_(nullptr), - currently_autoselecting_(false), - dragging_(false), - selection_manager_(this) -{ - SetDefaultDragMode(RubberBandDrag); - setContextMenuPolicy(Qt::CustomContextMenu); - - connect(this, &KeyframeViewBase::customContextMenuRequested, this, &KeyframeViewBase::ShowContextMenu); -} - -void KeyframeViewBase::DeleteSelected() -{ - MultiUndoCommand* command = new MultiUndoCommand(); - - foreach (NodeKeyframe *key, GetSelectedKeyframes()) { - command->add_child(new NodeParamRemoveKeyframeCommand(key)); - } - - Core::instance()->undo_stack()->pushIfHasChildren(command); -} - -KeyframeViewBase::NodeConnections KeyframeViewBase::AddKeyframesOfNode(Node *n) -{ - NodeConnections map; - - foreach (const QString& i, n->inputs()) { - map.insert(i, AddKeyframesOfInput(n, i)); - } - - return map; -} - -KeyframeViewBase::InputConnections KeyframeViewBase::AddKeyframesOfInput(Node* n, const QString& input) -{ - InputConnections vec; - - if (n->IsInputKeyframable(input)) { - int arr_sz = n->InputArraySize(input); - vec.resize(arr_sz + 1); - for (int i=-1; i& tracks = input.node()->GetKeyframeTracks(input); - ElementConnections vec(tracks.size()); - - for (int i=0; iGetKeyframes()) { - SelectKeyframe(key); - } - } -} - -void KeyframeViewBase::DeselectAll() -{ - selection_manager_.ClearSelection(); - - Redraw(); -} - -void KeyframeViewBase::Clear() -{ - if (!tracks_.isEmpty()) { - qDeleteAll(tracks_); - tracks_.clear(); - Redraw(); - } -} - -void KeyframeViewBase::mousePressEvent(QMouseEvent *event) -{ - NodeKeyframe *key_under_cursor = selection_manager_.MousePress(event); - if (key_under_cursor) { - AutoSelectKeyTimeNeighbors(); - } - - BezierControlPointItem *bezier_under_cursor = dynamic_cast(itemAt(event->pos())); - - Redraw(); - - if (HandPress(event) || (!bezier_under_cursor && !key_under_cursor && PlayheadPress(event))) { - return; - } - - if (event->button() == Qt::LeftButton) { - if (key_under_cursor || bezier_under_cursor) { - dragging_ = true; - drag_start_ = mapToScene(event->pos()); - - // Determine what type of item is under the cursor - dragging_bezier_point_ = bezier_under_cursor; - - if (dragging_bezier_point_) { - - dragging_bezier_point_start_ = dragging_bezier_point_->GetCorrespondingKeyframeHandle(); - dragging_bezier_point_opposing_start_ = dragging_bezier_point_->key()->bezier_control(NodeKeyframe::get_opposing_bezier_type(dragging_bezier_point_->mode())); - - } else { - - selection_manager_.DragStart(key_under_cursor, event); - - } - } - } -} - -void KeyframeViewBase::mouseMoveEvent(QMouseEvent *event) -{ - if (HandMove(event) || PlayheadMove(event)) { - return; - } - - if (event->buttons() & Qt::LeftButton) { - if (dragging_) { - // Calculate cursor difference and scale it - QPointF scene_pos = mapToScene(event->pos()); - QPointF mouse_diff_scaled = GetScaledCursorPos(scene_pos - drag_start_); - - if (event->modifiers() & Qt::ShiftModifier) { - // If holding shift, only move one axis - mouse_diff_scaled.setY(0); - } - - if (dragging_bezier_point_) { - - // Flip the mouse Y because bezier control points are drawn bottom to top, not top to bottom - mouse_diff_scaled.setY(-mouse_diff_scaled.y()); - - QPointF new_bezier_pos = GenerateBezierControlPosition(dragging_bezier_point_->mode(), - dragging_bezier_point_start_, - mouse_diff_scaled); - - // If the user is NOT holding control, we set the other handle to the exact negative of this handle - QPointF new_opposing_pos; - NodeKeyframe::BezierType opposing_type = NodeKeyframe::get_opposing_bezier_type(dragging_bezier_point_->mode()); - - - if (!(event->modifiers() & Qt::ControlModifier)) { - new_opposing_pos = GenerateBezierControlPosition(opposing_type, - dragging_bezier_point_opposing_start_, - -mouse_diff_scaled); - } else { - new_opposing_pos = dragging_bezier_point_opposing_start_; - } - - dragging_bezier_point_->key()->set_bezier_control(dragging_bezier_point_->mode(), - new_bezier_pos); - - dragging_bezier_point_->key()->set_bezier_control(opposing_type, - new_opposing_pos); - - // Bezier control points are parented to keyframe items making their positions relative - // to those items. We need to map them to the scene coordinates for this to work properly. - QPointF bezier_pos = dragging_bezier_point_->pos() + dragging_bezier_point_->parentItem()->pos(); - emit Dragged(qRound(bezier_pos.x()), qRound(bezier_pos.y())); - - } else if (selection_manager_.IsDragging()) { - - QString tip; - - /* - // Validate movement - ensure no keyframe goes above its max point or below its min point - FloatSlider::DisplayType display_type = FloatSlider::kNormal; - - if (IsYAxisEnabled()) { - foreach (const KeyframeItemAndTime& keypair, dragging_keyframes_) { - NodeKeyframe *key = keypair.key; - Node* node = key->parent(); - const QString& input = key->input(); - double new_val = keypair.value - mouse_diff_scaled.y(); - double limited = new_val; - - if (node->HasInputProperty(input, QStringLiteral("min"))) { - limited = qMax(limited, node->GetInputProperty(input, QStringLiteral("min")).toDouble()); - } - - if (node->HasInputProperty(input, QStringLiteral("max"))) { - limited = qMin(limited, node->GetInputProperty(input, QStringLiteral("max")).toDouble()); - } - - if (limited != new_val) { - mouse_diff_scaled.setY(keypair.value - limited); - } - } - - Node* initial_drag_input = initial_drag_item_->parent(); - const QString& initial_drag_input_id = initial_drag_item_->input(); - if (initial_drag_input->HasInputProperty(initial_drag_input_id, QStringLiteral("view"))) { - display_type = static_cast(initial_drag_input->GetInputProperty(initial_drag_input_id, QStringLiteral("view")).toInt()); - } - } - - foreach (const KeyframeItemAndTime& keypair, dragging_keyframes_) { - if (IsYAxisEnabled()) { - key->set_value(keypair.value - mouse_diff_scaled.y()); - } - } - - - - if (IsYAxisEnabled()) { - bool ok; - double num_value = initial_drag_item_->value().toDouble(&ok); - - if (ok) { - tip = QStringLiteral("%1\n"); - tip.append(FloatSlider::ValueToString(num_value, display_type, 2, true)); - } - } - */ - - selection_manager_.DragMove(event, tip); - - Redraw(); - - emit Dragged(scene_pos.x(), scene_pos.y()); - - } - } - } -} - -void KeyframeViewBase::mouseReleaseEvent(QMouseEvent *event) -{ - if (HandRelease(event) || PlayheadRelease(event)) { - return; - } - - if (event->button() == Qt::LeftButton) { - if (dragging_) { - if (dragging_bezier_point_) { - MultiUndoCommand* command = new MultiUndoCommand(); - - // Create undo command with the current bezier point and the old one - command->add_child(new KeyframeSetBezierControlPoint(dragging_bezier_point_->key(), - dragging_bezier_point_->mode(), - dragging_bezier_point_->key()->bezier_control(dragging_bezier_point_->mode()), - dragging_bezier_point_start_)); - - if (!(event->modifiers() & Qt::ControlModifier)) { - auto opposing_type = NodeKeyframe::get_opposing_bezier_type(dragging_bezier_point_->mode()); - - command->add_child(new KeyframeSetBezierControlPoint(dragging_bezier_point_->key(), - opposing_type, - dragging_bezier_point_->key()->bezier_control(opposing_type), - dragging_bezier_point_opposing_start_)); - } - - dragging_bezier_point_ = nullptr; - - Core::instance()->undo_stack()->push(command); - } else if (selection_manager_.IsDragging()) { - MultiUndoCommand* command = new MultiUndoCommand(); - - selection_manager_.DragStop(command); - /*if (IsYAxisEnabled()) { - command->add_child(new NodeParamSetKeyframeValueCommand(item, - item->value(), - keypair.value)); - }*/ - - Core::instance()->undo_stack()->push(command); - } - - dragging_ = false; - - QToolTip::hideText(); - } - } -} - -void KeyframeViewBase::drawForeground(QPainter *painter, const QRectF &rect) -{ - int key_sz = QtUtils::QFontMetricsWidth(fontMetrics(), "Oi"); - int key_rad = key_sz/2; - - selection_manager_.ClearDrawnObjects(); - - painter->setRenderHint(QPainter::Antialiasing); - - painter->setPen(Qt::black); - - foreach (KeyframeViewInputConnection *track, tracks_) { - foreach (NodeKeyframe *key, track->GetKeyframes()) { - QRectF key_rect(-key_rad, -key_rad, key_sz, key_sz); - key_rect.translate(GetKeyframeSceneX(key), mapFromGlobal(QPoint(0, track->GetKeyframeY())).y()); - - if (!rect.intersects(key_rect)) { - continue; - } - - if (IsKeyframeSelected(key)) { - painter->setBrush(palette().highlight()); - } else { - painter->setBrush(track->GetBrush()); - } - - selection_manager_.DeclareDrawnObject(key, key_rect); - - switch (key->type()) { - case NodeKeyframe::kLinear: - { - QPointF points[] = { - QPointF(key_rect.center().x(), key_rect.top()), - QPointF(key_rect.right(), key_rect.center().y()), - QPointF(key_rect.center().x(), key_rect.bottom()), - QPointF(key_rect.left(), key_rect.center().y()) - }; - - painter->drawPolygon(points, 4); - break; - } - case NodeKeyframe::kBezier: - painter->drawEllipse(key_rect); - break; - case NodeKeyframe::kHold: - painter->drawRect(key_rect); - break; - } - } - } - - super::drawForeground(painter, rect); -} - -void KeyframeViewBase::ScaleChangedEvent(const double &scale) -{ - super::ScaleChangedEvent(scale); - - Redraw(); -} - -void KeyframeViewBase::TimeTargetChangedEvent(Node *target) -{ - Redraw(); -} - -void KeyframeViewBase::TimebaseChangedEvent(const rational &timebase) -{ - super::TimebaseChangedEvent(timebase); - - selection_manager_.SetTimebase(timebase); -} - -void KeyframeViewBase::ContextMenuEvent(Menu& m) -{ - Q_UNUSED(m) -} - -void KeyframeViewBase::SelectKeyframe(NodeKeyframe *key) -{ - if (selection_manager_.Select(key)) { - Redraw(); - } -} - -void KeyframeViewBase::DeselectKeyframe(NodeKeyframe *key) -{ - if (selection_manager_.Deselect(key)) { - Redraw(); - } -} - -rational KeyframeViewBase::GetAdjustedKeyframeTime(NodeKeyframe *key) -{ - return GetAdjustedTime(key->parent(), GetTimeTarget(), key->time(), false); -} - -double KeyframeViewBase::GetKeyframeSceneX(NodeKeyframe *key) -{ - return TimeToScene(GetAdjustedKeyframeTime(key)); -} - -rational KeyframeViewBase::CalculateNewTimeFromScreen(const rational &old_time, double cursor_diff) -{ - return rational::fromDouble(old_time.toDouble() + cursor_diff); -} - -QPointF KeyframeViewBase::GenerateBezierControlPosition(const NodeKeyframe::BezierType mode, const QPointF &start_point, const QPointF &scaled_cursor_diff) -{ - QPointF new_bezier_pos = start_point; - - new_bezier_pos += scaled_cursor_diff; - - // LIMIT bezier handles from overlapping each other - if (mode == NodeKeyframe::kInHandle) { - if (new_bezier_pos.x() > 0) { - new_bezier_pos.setX(0); - } - } else { - if (new_bezier_pos.x() < 0) { - new_bezier_pos.setX(0); - } - } - - return new_bezier_pos; -} - -QPointF KeyframeViewBase::GetScaledCursorPos(const QPointF &cursor_pos) -{ - return QPointF(cursor_pos.x() / GetScale(), - cursor_pos.y() / GetYScale()); -} - -void KeyframeViewBase::ShowContextMenu() -{ - Menu m; - - MenuShared::instance()->AddItemsForEditMenu(&m, false); - - QAction* linear_key_action = nullptr; - QAction* bezier_key_action = nullptr; - QAction* hold_key_action = nullptr; - - if (!GetSelectedKeyframes().isEmpty()) { - bool all_keys_are_same_type = true; - NodeKeyframe::Type type = GetSelectedKeyframes().first()->type(); - - for (int i=1;itype() != prev_item->type()) { - all_keys_are_same_type = false; - break; - } - } - - m.addSeparator(); - - linear_key_action = m.addAction(tr("Linear")); - bezier_key_action = m.addAction(tr("Bezier")); - hold_key_action = m.addAction(tr("Hold")); - - if (all_keys_are_same_type) { - switch (type) { - case NodeKeyframe::kLinear: - linear_key_action->setChecked(true); - break; - case NodeKeyframe::kBezier: - bezier_key_action->setChecked(true); - break; - case NodeKeyframe::kHold: - hold_key_action->setChecked(true); - break; - } - } - } - - m.addSeparator(); - - AddSetScrollZoomsByDefaultActionToMenu(&m); - - m.addSeparator(); - - ContextMenuEvent(m); - - if (!GetSelectedKeyframes().isEmpty()) { - m.addSeparator(); - - QAction* properties_action = m.addAction(tr("P&roperties")); - connect(properties_action, &QAction::triggered, this, &KeyframeViewBase::ShowKeyframePropertiesDialog); - } - - QAction* selected = m.exec(QCursor::pos()); - - // Process keyframe type changes - if (selected) { - if (selected == linear_key_action - || selected == bezier_key_action - || selected == hold_key_action) { - NodeKeyframe::Type new_type; - - if (selected == hold_key_action) { - new_type = NodeKeyframe::kHold; - } else if (selected == bezier_key_action) { - new_type = NodeKeyframe::kBezier; - } else { - new_type = NodeKeyframe::kLinear; - } - - MultiUndoCommand* command = new MultiUndoCommand(); - foreach (NodeKeyframe* item, GetSelectedKeyframes()) { - command->add_child(new KeyframeSetTypeCommand(item, new_type)); - } - Core::instance()->undo_stack()->push(command); - } - } -} - -void KeyframeViewBase::ShowKeyframePropertiesDialog() -{ - if (!GetSelectedKeyframes().isEmpty()) { - KeyframePropertiesDialog kd(GetSelectedKeyframes(), timebase(), this); - kd.exec(); - } -} - -void KeyframeViewBase::AutoSelectKeyTimeNeighbors() -{ - if (currently_autoselecting_ || IsYAxisEnabled()) { - return; - } - - // Prevents infinite loop - currently_autoselecting_ = true; - - QVector copy = GetSelectedKeyframes(); - foreach (NodeKeyframe *key, copy) { - rational key_time = key->time(); - - QVector keys = key->parent()->GetKeyframesAtTime(key->input(), key_time, key->element()); - - foreach (NodeKeyframe* k, keys) { - if (k != key) { - SelectKeyframe(k); - } - } - } - - currently_autoselecting_ = false; -} - -void KeyframeViewBase::Redraw() -{ - viewport()->update(); -} - -} diff --git a/app/widget/keyframeview/keyframeviewbase.h b/app/widget/keyframeview/keyframeviewbase.h deleted file mode 100644 index 2ce0673f7..000000000 --- a/app/widget/keyframeview/keyframeviewbase.h +++ /dev/null @@ -1,139 +0,0 @@ -/*** - - 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 KEYFRAMEVIEWBASE_H -#define KEYFRAMEVIEWBASE_H - -#include "keyframeviewinputconnection.h" -#include "node/keyframe.h" -#include "widget/curvewidget/beziercontrolpointitem.h" -#include "widget/menu/menu.h" -#include "widget/timebased/timebasedview.h" -#include "widget/timebased/timebasedviewselectionmanager.h" -#include "widget/timetarget/timetarget.h" - -namespace olive { - -class KeyframeViewBase : public TimeBasedView, public TimeTargetObject -{ - Q_OBJECT -public: - KeyframeViewBase(QWidget* parent = nullptr); - - void DeleteSelected(); - - using ElementConnections = QVector; - using InputConnections = QVector; - using NodeConnections = QMap; - - NodeConnections AddKeyframesOfNode(Node* n); - - InputConnections AddKeyframesOfInput(Node *n, const QString &input); - - ElementConnections AddKeyframesOfElement(const NodeInput &input); - - KeyframeViewInputConnection *AddKeyframesOfTrack(const NodeKeyframeTrackReference &ref); - - void RemoveKeyframesOfTrack(KeyframeViewInputConnection *connection); - - void SelectAll(); - - void DeselectAll(); - - void Clear(); - - const QVector &GetSelectedKeyframes() const - { - return selection_manager_.GetSelectedObjects(); - } - -signals: - void Dragged(int current_x, int current_y); - -protected: - virtual void mousePressEvent(QMouseEvent *event) override; - virtual void mouseMoveEvent(QMouseEvent *event) override; - virtual void mouseReleaseEvent(QMouseEvent *event) override; - - virtual void drawForeground(QPainter *painter, const QRectF &rect) override; - - virtual void ScaleChangedEvent(const double& scale) override; - - virtual void TimeTargetChangedEvent(Node*) override; - - virtual void TimebaseChangedEvent(const rational &timebase) override; - - virtual void ContextMenuEvent(Menu &m); - - bool IsDragging() const - { - return dragging_; - } - - void SelectKeyframe(NodeKeyframe *key); - - void DeselectKeyframe(NodeKeyframe *key); - - bool IsKeyframeSelected(NodeKeyframe *key) const - { - return selection_manager_.IsSelected(key); - } - - rational GetAdjustedKeyframeTime(NodeKeyframe *key); - - double GetKeyframeSceneX(NodeKeyframe *key); - -private: - rational CalculateNewTimeFromScreen(const rational& old_time, double cursor_diff); - - static QPointF GenerateBezierControlPosition(const NodeKeyframe::BezierType mode, - const QPointF& start_point, - const QPointF& scaled_cursor_diff); - - QPointF GetScaledCursorPos(const QPointF &cursor_pos); - - QPointF drag_start_; - - BezierControlPointItem* dragging_bezier_point_; - QPointF dragging_bezier_point_start_; - QPointF dragging_bezier_point_opposing_start_; - - QVector tracks_; - - bool currently_autoselecting_; - - bool dragging_; - - TimeBasedViewSelectionManager selection_manager_; - -private slots: - void ShowContextMenu(); - - void ShowKeyframePropertiesDialog(); - - void AutoSelectKeyTimeNeighbors(); - - void Redraw(); - -}; - -} - -#endif // KEYFRAMEVIEWBASE_H diff --git a/app/widget/keyframeview/keyframeviewinputconnection.cpp b/app/widget/keyframeview/keyframeviewinputconnection.cpp index a8e9a35e5..b7b3c2dca 100644 --- a/app/widget/keyframeview/keyframeviewinputconnection.cpp +++ b/app/widget/keyframeview/keyframeviewinputconnection.cpp @@ -24,7 +24,7 @@ namespace olive { -KeyframeViewInputConnection::KeyframeViewInputConnection(const NodeKeyframeTrackReference &input, KeyframeViewBase *parent) : +KeyframeViewInputConnection::KeyframeViewInputConnection(const NodeKeyframeTrackReference &input, KeyframeView *parent) : QObject(parent), keyframe_view_(parent), input_(input), @@ -36,7 +36,9 @@ KeyframeViewInputConnection::KeyframeViewInputConnection(const NodeKeyframeTrack connect(n, &Node::KeyframeAdded, this, &KeyframeViewInputConnection::AddKeyframe); connect(n, &Node::KeyframeRemoved, this, &KeyframeViewInputConnection::RemoveKeyframe); - connect(n, &Node::KeyframeTimeChanged, this, &KeyframeViewInputConnection::RequireUpdate); + connect(n, &Node::KeyframeTimeChanged, this, &KeyframeViewInputConnection::KeyframeChanged); + connect(n, &Node::KeyframeTypeChanged, this, &KeyframeViewInputConnection::KeyframeChanged); + connect(n, &Node::KeyframeValueChanged, this, &KeyframeViewInputConnection::KeyframeChanged); } void KeyframeViewInputConnection::SetKeyframeY(int y) @@ -80,4 +82,11 @@ void KeyframeViewInputConnection::RemoveKeyframe(NodeKeyframe *key) } } +void KeyframeViewInputConnection::KeyframeChanged(NodeKeyframe *key) +{ + if (key->key_track_ref() == input_) { + emit RequireUpdate(); + } +} + } diff --git a/app/widget/keyframeview/keyframeviewinputconnection.h b/app/widget/keyframeview/keyframeviewinputconnection.h index b8f951d22..f10f37d2b 100644 --- a/app/widget/keyframeview/keyframeviewinputconnection.h +++ b/app/widget/keyframeview/keyframeviewinputconnection.h @@ -28,13 +28,13 @@ namespace olive { -class KeyframeViewBase; +class KeyframeView; class KeyframeViewInputConnection : public QObject { Q_OBJECT public: - KeyframeViewInputConnection(const NodeKeyframeTrackReference &input, KeyframeViewBase *parent); + KeyframeViewInputConnection(const NodeKeyframeTrackReference &input, KeyframeView *parent); const int &GetKeyframeY() const { @@ -66,7 +66,7 @@ signals: void RequireUpdate(); private: - KeyframeViewBase *keyframe_view_; + KeyframeView *keyframe_view_; NodeKeyframeTrackReference input_; @@ -81,6 +81,8 @@ private slots: void RemoveKeyframe(NodeKeyframe *key); + void KeyframeChanged(NodeKeyframe *key); + }; } diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index b2e219478..e21ead255 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -373,11 +373,9 @@ void NodeParamView::AddNode(Node *n, NodeParamViewContext *context) } } - // Set time target item->SetTimeTarget(GetTimeTarget()); - - // Set the timebase item->SetTimebase(timebase()); + item->SetTime(GetTime()); context->AddNode(item); @@ -526,7 +524,7 @@ void NodeParamView::UpdateElementY() { foreach (NodeParamViewContext *ctx, context_items_) { for (auto it=ctx->GetItems().cbegin(); it!=ctx->GetItems().cend(); it++) { - const KeyframeViewBase::NodeConnections &connections = it.value()->GetKeyframeConnections(); + const KeyframeView::NodeConnections &connections = it.value()->GetKeyframeConnections(); if (!connections.isEmpty()) { foreach (const QString& input, it.key()->inputs()) { @@ -538,10 +536,10 @@ void NodeParamView::UpdateElementY() int y = it.value()->GetElementY(ic); - const KeyframeViewBase::InputConnections &input_con = connections.value(input); + const KeyframeView::InputConnections &input_con = connections.value(input); int use_index = i + 1; if (use_index < input_con.size()) { - const KeyframeViewBase::ElementConnections &ele_con = input_con.at(ic.element()+1); + const KeyframeView::ElementConnections &ele_con = input_con.at(ic.element()+1); foreach (KeyframeViewInputConnection *track, ele_con) { track->SetKeyframeY(y); } diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 1e0aae242..9e45f55f7 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -36,7 +36,7 @@ #include "nodeparamviewwidgetbridge.h" #include "widget/clickablelabel/clickablelabel.h" #include "widget/collapsebutton/collapsebutton.h" -#include "widget/keyframeview/keyframeviewbase.h" +#include "widget/keyframeview/keyframeview.h" namespace olive { @@ -188,12 +188,12 @@ public: void SetInputChecked(const NodeInput &input, bool e); - const KeyframeViewBase::NodeConnections &GetKeyframeConnections() const + const KeyframeView::NodeConnections &GetKeyframeConnections() const { return keyframe_connections_; } - void SetKeyframeConnections(const KeyframeViewBase::NodeConnections &c) + void SetKeyframeConnections(const KeyframeView::NodeConnections &c) { keyframe_connections_ = c; } @@ -217,7 +217,7 @@ private: rational time_; - KeyframeViewBase::NodeConnections keyframe_connections_; + KeyframeView::NodeConnections keyframe_connections_; }; diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index e54350ef4..dda919eee 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -38,7 +38,6 @@ #include "widget/slider/floatslider.h" #include "widget/slider/integerslider.h" #include "widget/slider/rationalslider.h" -#include "widget/videoparamedit/videoparamedit.h" namespace olive { @@ -89,6 +88,8 @@ void NodeParamViewWidgetBridge::CreateWidgets() case NodeValue::kShaderJob: case NodeValue::kSampleJob: case NodeValue::kGenerateJob: + case NodeValue::kVideoParams: + case NodeValue::kAudioParams: break; case NodeValue::kInt: { @@ -156,19 +157,6 @@ void NodeParamViewWidgetBridge::CreateWidgets() connect(font_combobox, &QFontComboBox::currentFontChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); break; } - case NodeValue::kVideoParams: - { - VideoParamEdit* edit = new VideoParamEdit(); - edit->SetColorManager(input_.node()->project()->color_manager()); - widgets_.append(edit); - connect(edit, &VideoParamEdit::Changed, this, &NodeParamViewWidgetBridge::WidgetCallback); - break; - } - case NodeValue::kAudioParams: - { - // FIXME: Create audio param widget - break; - } } // Check all properties @@ -278,6 +266,8 @@ void NodeParamViewWidgetBridge::WidgetCallback() case NodeValue::kShaderJob: case NodeValue::kSampleJob: case NodeValue::kGenerateJob: + case NodeValue::kVideoParams: + case NodeValue::kAudioParams: break; case NodeValue::kInt: { @@ -388,15 +378,6 @@ void NodeParamViewWidgetBridge::WidgetCallback() SetInputValue(index, 0); break; } - case NodeValue::kVideoParams: - { - VideoParamEdit* edit = static_cast(sender()); - SetInputValue(QVariant::fromValue(edit->GetVideoParams()), 0); - break; - } - case NodeValue::kAudioParams: - // FIXME: No audio param widget yet - break; } } @@ -434,6 +415,8 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues() case NodeValue::kShaderJob: case NodeValue::kSampleJob: case NodeValue::kGenerateJob: + case NodeValue::kVideoParams: + case NodeValue::kAudioParams: break; case NodeValue::kInt: { @@ -533,15 +516,6 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues() cb->blockSignals(false); break; } - case NodeValue::kVideoParams: - { - VideoParamEdit* edit = static_cast(widgets_.first()); - edit->SetVideoParams(input_.GetValueAtTime(node_time).value()); - break; - } - case NodeValue::kAudioParams: - // FIXME: No audio param widget - break; } } @@ -796,15 +770,6 @@ void NodeParamViewWidgetBridge::PropertyChanged(const QString& input, const QStr ff->SetDirectoryMode(value.toBool()); } } - - // Parameters for video param objects - if (data_type == NodeValue::kVideoParams) { - VideoParamEdit* edit = static_cast(widgets_.first()); - - if (key == QStringLiteral("mask")) { - edit->SetParameterMask(value.toULongLong()); - } - } } void NodeParamViewWidgetBridge::InputDataTypeChanged(const QString &input, NodeValue::Type type) diff --git a/app/widget/nodetreeview/nodetreeview.cpp b/app/widget/nodetreeview/nodetreeview.cpp index a1ee354a3..a2f8ec9df 100644 --- a/app/widget/nodetreeview/nodetreeview.cpp +++ b/app/widget/nodetreeview/nodetreeview.cpp @@ -114,6 +114,8 @@ void NodeTreeView::SetNodes(const QVector &nodes) } else { delete node_item; } + + node_item->setExpanded(true); } } diff --git a/app/widget/timebased/timebasedview.h b/app/widget/timebased/timebasedview.h index 247948b61..f6f46d285 100644 --- a/app/widget/timebased/timebasedview.h +++ b/app/widget/timebased/timebasedview.h @@ -53,6 +53,10 @@ public: return dragging_playhead_; } + // To be called only by selection managers + virtual void SelectionManagerSelectEvent(void *obj){} + virtual void SelectionManagerDeselectEvent(void *obj){} + public slots: void SetTime(const rational &time); diff --git a/app/widget/timebased/timebasedviewselectionmanager.h b/app/widget/timebased/timebasedviewselectionmanager.h index d1f53d7d6..19d61e203 100644 --- a/app/widget/timebased/timebasedviewselectionmanager.h +++ b/app/widget/timebased/timebasedviewselectionmanager.h @@ -23,6 +23,7 @@ #include #include +#include #include #include "common/rational.h" @@ -36,7 +37,8 @@ class TimeBasedViewSelectionManager { public: TimeBasedViewSelectionManager(TimeBasedView *view) : - view_(view) + view_(view), + rubberband_(nullptr) {} void ClearDrawnObjects() @@ -51,6 +53,8 @@ public: bool Select(T *key) { + Q_ASSERT(key); + if (!IsSelected(key)) { selected_.append(key); return true; @@ -61,6 +65,8 @@ public: bool Deselect(T *key) { + Q_ASSERT(key); + return selected_.removeOne(key); } @@ -84,35 +90,48 @@ public: timebase_ = tb; } + T *GetObjectAtPoint(const QPointF &scene_pt) + { + foreach (const DrawnObject &kp, drawn_objects_) { + if (kp.second.contains(scene_pt)) { + return kp.first; + } + } + + return nullptr; + } + + T *GetObjectAtPoint(const QPoint &pt) + { + return GetObjectAtPoint(view_->mapToScene(pt)); + } + T *MousePress(QMouseEvent *event) { T *key_under_cursor = nullptr; - if (event->button() == Qt::LeftButton) { + if (event->button() == Qt::LeftButton || event->button() == Qt::RightButton) { // See if there's a keyframe in this position - QPointF scene_pos = view_->mapToScene(event->pos()); - foreach (const DrawnObject &kp, drawn_objects_) { - if (kp.second.contains(scene_pos)) { - key_under_cursor = kp.first; - break; - } - } + key_under_cursor = GetObjectAtPoint(event->pos()); bool holding_shift = event->modifiers() & Qt::ShiftModifier; - if (IsSelected(key_under_cursor)) { - if (holding_shift) { - // If selected and holding shift, de-select this item but do nothing else - Deselect(key_under_cursor); - } - } else { + if (!key_under_cursor || !IsSelected(key_under_cursor)) { if (!holding_shift) { // If not already selecting and not holding shift, clear the current selection ClearSelection(); } // Add item to selection, either nothing if shift wasn't held, or the existing selection - Select(key_under_cursor); + if (key_under_cursor) { + Select(key_under_cursor); + view_->SelectionManagerSelectEvent(key_under_cursor); + } + } else if (holding_shift) { + // If selected and holding shift, de-select this item but do nothing else + Deselect(key_under_cursor); + view_->SelectionManagerDeselectEvent(key_under_cursor); + key_under_cursor = nullptr; } } @@ -185,6 +204,51 @@ public: for (int i=0; iadd_child(new SetTimeCommand(selected_.at(i), selected_.at(i)->time(), dragging_.at(i).time)); } + + dragging_.clear(); + } + + void RubberBandStart(QMouseEvent *event) + { + if (event->button() == Qt::LeftButton || event->button() == Qt::RightButton) { + rubberband_start_ = event->pos(); + + rubberband_ = new QRubberBand(QRubberBand::Rectangle, view_); + rubberband_->setGeometry(QRect(rubberband_start_.x(), rubberband_start_.y(), 0, 0)); + rubberband_->show(); + + rubberband_preselected_ = selected_; + } + } + + void RubberBandMove(QMouseEvent *event) + { + if (IsRubberBanding()) { + QRect band_rect = QRect(rubberband_start_, event->pos()).normalized(); + rubberband_->setGeometry(band_rect); + + QRectF scene_rect = view_->mapToScene(band_rect).boundingRect(); + + selected_ = rubberband_preselected_; + foreach (const DrawnObject &kp, drawn_objects_) { + if (scene_rect.intersects(kp.second)) { + Select(kp.first); + } + } + } + } + + void RubberBandStop() + { + if (IsRubberBanding()) { + delete rubberband_; + rubberband_ = nullptr; + } + } + + bool IsRubberBanding() const + { + return rubberband_; } private: @@ -249,6 +313,10 @@ private: rational timebase_; + QRubberBand *rubberband_; + QPoint rubberband_start_; + QVector rubberband_preselected_; + }; } diff --git a/app/widget/videoparamedit/CMakeLists.txt b/app/widget/videoparamedit/CMakeLists.txt deleted file mode 100644 index bd799b34d..000000000 --- a/app/widget/videoparamedit/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -# 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 . - -set(OLIVE_SOURCES - ${OLIVE_SOURCES} - widget/videoparamedit/videoparamedit.cpp - widget/videoparamedit/videoparamedit.h - PARENT_SCOPE -) diff --git a/app/widget/videoparamedit/videoparamedit.cpp b/app/widget/videoparamedit/videoparamedit.cpp deleted file mode 100644 index 13ddfc825..000000000 --- a/app/widget/videoparamedit/videoparamedit.cpp +++ /dev/null @@ -1,391 +0,0 @@ -/*** - - 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 "videoparamedit.h" - -#include - -namespace olive { - -VideoParamEdit::VideoParamEdit(QWidget* parent) : - QWidget(parent), - color_manager_(nullptr), - mask_(0) -{ - QGridLayout* layout = new QGridLayout(this); - - layout->setMargin(0); - - int row = 0; - - // Enabled - enabled_lbl_ = new QLabel(tr("Enabled:")); - layout->addWidget(enabled_lbl_, row, 0); - enabled_box_ = new QCheckBox(); - connect(enabled_box_, &QCheckBox::clicked, this, &VideoParamEdit::Changed); - layout->addWidget(enabled_box_, row, 1); - - row++; - - // Width - width_lbl_ = new QLabel(tr("Width:")); - layout->addWidget(width_lbl_, row, 0); - - width_slider_ = new IntegerSlider(); - width_slider_->SetMinimum(1); - width_slider_->SetMaximum(32768); - connect(width_slider_, &IntegerSlider::ValueChanged, this, &VideoParamEdit::Changed); - layout->addWidget(width_slider_, row, 1); - - row++; - - // Height - height_lbl_ = new QLabel(tr("Height:")); - layout->addWidget(height_lbl_, row, 0); - - height_slider_ = new IntegerSlider(); - height_slider_->SetMinimum(1); - height_slider_->SetMaximum(32768); - connect(height_slider_, &IntegerSlider::ValueChanged, this, &VideoParamEdit::Changed); - layout->addWidget(height_slider_, row, 1); - - row++; - - // Depth - depth_lbl_ = new QLabel(tr("Depth:")); - layout->addWidget(depth_lbl_, row, 0); - - depth_slider_ = new IntegerSlider(); - depth_slider_->SetMinimum(1); - depth_slider_->SetMaximum(32768); - connect(depth_slider_, &IntegerSlider::ValueChanged, this, &VideoParamEdit::Changed); - layout->addWidget(depth_slider_, row, 1); - - row++; - - // Pixel Format - format_lbl_ = new QLabel(tr("Format:")); - layout->addWidget(format_lbl_, row, 0); - format_combobox_ = new PixelFormatComboBox(true); - connect(format_combobox_, static_cast(&PixelFormatComboBox::currentIndexChanged), this, &VideoParamEdit::Changed); - layout->addWidget(format_combobox_, row, 1); - - row++; - - // Frame Rate - frame_rate_lbl_ = new QLabel(tr("Frame Rate:")); - layout->addWidget(frame_rate_lbl_, row, 0); - - frame_rate_combobox_ = new FrameRateComboBox(); - connect(frame_rate_combobox_, &FrameRateComboBox::FrameRateChanged, this, &VideoParamEdit::Changed); - layout->addWidget(frame_rate_combobox_, row, 1); - - frame_rate_slider_ = new RationalSlider(); - frame_rate_slider_->SetMinimum(0); - frame_rate_slider_->SetDecimalPlaces(3); - frame_rate_slider_->SetAutoTrimDecimalPlaces(true); - frame_rate_slider_->SetTimebase(rational(1, 1000)); // Drag interval - frame_rate_slider_->DisableDisplayType(RationalSlider::kTime); - connect(frame_rate_slider_, &RationalSlider::ValueChanged, this, &VideoParamEdit::Changed); - layout->addWidget(frame_rate_slider_, row, 1); - - row++; - - // Pixel Aspect Ratio - pixel_aspect_lbl_ = new QLabel(tr("Pixel Aspect Ratio:")); - layout->addWidget(pixel_aspect_lbl_, row, 0); - - pixel_aspect_combobox_ = new PixelAspectRatioComboBox(); - connect(pixel_aspect_combobox_, static_cast(&PixelAspectRatioComboBox::currentIndexChanged), this, &VideoParamEdit::Changed); - layout->addWidget(pixel_aspect_combobox_, row, 1); - - row++; - - // Interlacing - interlaced_lbl_ = new QLabel(tr("Interlacing:")); - layout->addWidget(interlaced_lbl_, row, 0); - - interlaced_combobox_ = new InterlacedComboBox(); - connect(interlaced_combobox_, static_cast(&InterlacedComboBox::currentIndexChanged), this, &VideoParamEdit::Changed); - layout->addWidget(interlaced_combobox_, row, 1); - - row++; - - // Channel Count - channel_count_lbl_ = new QLabel(tr("Channel Count:")); - layout->addWidget(channel_count_lbl_, row, 0); - - channel_count_combobox_ = new QComboBox(); - channel_count_combobox_->addItem(tr("RGB"), VideoParams::kRGBChannelCount); - channel_count_combobox_->addItem(tr("RGBA"), VideoParams::kRGBAChannelCount); - connect(channel_count_combobox_, static_cast(&QComboBox::currentIndexChanged), this, &VideoParamEdit::Changed); - layout->addWidget(channel_count_combobox_, row, 1); - - row++; - - // Divider - divider_lbl_ = new QLabel(tr("Divider:")); - layout->addWidget(divider_lbl_, row, 0); - - divider_combobox_ = new VideoDividerComboBox(); - connect(divider_combobox_, static_cast(&VideoDividerComboBox::currentIndexChanged), this, &VideoParamEdit::Changed); - layout->addWidget(divider_combobox_, row, 1); - - row++; - - // Stream Index - stream_index_lbl_ = new QLabel(tr("Stream Index:")); - layout->addWidget(stream_index_lbl_, row, 0); - stream_index_slider_ = new IntegerSlider(); - stream_index_slider_->SetMinimum(0); - connect(stream_index_slider_, &IntegerSlider::ValueChanged, this, &VideoParamEdit::Changed); - layout->addWidget(stream_index_slider_, row, 1); - - row++; - - // Video type - video_type_lbl_ = new QLabel(tr("Video Type:")); - layout->addWidget(video_type_lbl_, row, 0); - video_type_combobox_ = new QComboBox(); - video_type_combobox_->addItem(tr("Video"), VideoParams::kVideoTypeVideo); - video_type_combobox_->addItem(tr("Still"), VideoParams::kVideoTypeStill); - video_type_combobox_->addItem(tr("Image Sequence"), VideoParams::kVideoTypeImageSequence); - connect(video_type_combobox_, static_cast(&QComboBox::currentIndexChanged), this, &VideoParamEdit::Changed); - layout->addWidget(video_type_combobox_, row, 1); - - row++; - - // Start time (for image sequences) - start_time_lbl_ = new QLabel(tr("Start Time")); - layout->addWidget(start_time_lbl_, row, 0); - - start_time_slider_ = new IntegerSlider(); - start_time_slider_->SetMinimum(0); - connect(start_time_slider_, &IntegerSlider::ValueChanged, this, &VideoParamEdit::Changed); - layout->addWidget(start_time_slider_, row, 1); - - row++; - - // End time (for image sequences) - end_time_lbl_ = new QLabel(tr("End Time")); - layout->addWidget(end_time_lbl_, row, 0); - - end_time_slider_ = new IntegerSlider(); - end_time_slider_->SetMinimum(0); - connect(end_time_slider_, &IntegerSlider::ValueChanged, this, &VideoParamEdit::Changed); - layout->addWidget(end_time_slider_, row, 1); - - row++; - - // Premultiplied alpha - premultiplied_alpha_lbl_ = new QLabel(tr("Premultiplied Alpha")); - layout->addWidget(premultiplied_alpha_lbl_, row, 0); - - premultiplied_alpha_box_ = new QCheckBox(); - connect(premultiplied_alpha_box_, &QCheckBox::clicked, this, &VideoParamEdit::Changed); - layout->addWidget(premultiplied_alpha_box_, row, 1); - - row++; - - // Colorspace - colorspace_lbl_ = new QLabel(tr("Colorspace")); - layout->addWidget(colorspace_lbl_, row, 0); - - colorspace_combobox_ = new QComboBox(); - connect(colorspace_combobox_, static_cast(&QComboBox::currentIndexChanged), this, &VideoParamEdit::Changed); - layout->addWidget(colorspace_combobox_, row, 1); -} - -void VideoParamEdit::SetParameterMask(uint64_t mask) -{ - mask_ = mask; - - width_lbl_->setVisible(mask & kWidthHeight); - width_slider_->setVisible(mask & kWidthHeight); - height_lbl_->setVisible(mask & kWidthHeight); - height_slider_->setVisible(mask & kWidthHeight); - - depth_lbl_->setVisible(mask & kDepth); - depth_slider_->setVisible(mask & kDepth); - - frame_rate_lbl_->setVisible(mask & kFrameRate); - frame_rate_combobox_->setVisible((mask & kFrameRate) && !(mask & kFrameRateIsArbitrary)); - frame_rate_slider_->setVisible((mask & kFrameRate) && (mask & kFrameRateIsArbitrary)); - - pixel_aspect_lbl_->setVisible(mask & kPixelAspect); - pixel_aspect_combobox_->setVisible(mask & kPixelAspect); - - interlaced_lbl_->setVisible(mask & kInterlacing); - interlaced_combobox_->setVisible(mask & kInterlacing); - - enabled_lbl_->setVisible(mask & kEnabled); - enabled_box_->setVisible(mask & kEnabled); - - format_lbl_->setVisible(mask & kFormat); - format_combobox_->setVisible(mask & kFormat); - - channel_count_lbl_->setVisible(mask & kChannelCount); - channel_count_combobox_->setVisible(mask & kChannelCount); - - divider_lbl_->setVisible(mask & kDivider); - divider_combobox_->setVisible(mask & kDivider); - - stream_index_lbl_->setVisible(mask & kStreamIndex); - stream_index_slider_->setVisible(mask & kStreamIndex); - - video_type_lbl_->setVisible(mask & kIsImageSequence); - video_type_combobox_->setVisible(mask & kIsImageSequence); - - start_time_lbl_->setVisible(mask & kStartTime); - start_time_slider_->setVisible(mask & kStartTime); - - end_time_lbl_->setVisible(mask & kEndTime); - end_time_slider_->setVisible(mask & kEndTime); - - premultiplied_alpha_lbl_->setVisible(mask & kPremultipliedAlpha); - premultiplied_alpha_box_->setVisible(mask & kPremultipliedAlpha); - - colorspace_lbl_->setVisible(mask & kColorspace); - colorspace_combobox_->setVisible(mask & kColorspace); -} - -VideoParams VideoParamEdit::GetVideoParams() const -{ - VideoParams p; - - p.set_enabled(enabled_box_->isChecked()); - p.set_width(width_slider_->GetValue()); - p.set_height(height_slider_->GetValue()); - p.set_depth(depth_slider_->GetValue()); - - { - rational using_frame_rate; - - if (mask_ & kFrameRateIsArbitrary) { - using_frame_rate = frame_rate_slider_->GetValue(); - } else { - using_frame_rate = frame_rate_combobox_->GetFrameRate(); - } - - p.set_frame_rate(using_frame_rate); - if (mask_ & kFrameRateIsNotTimebase) { - // Frame rate editor will only edit the frame rate - p.set_time_base(timebase_temp_); - } else { - p.set_time_base(using_frame_rate.flipped()); - } - } - - p.set_pixel_aspect_ratio(pixel_aspect_combobox_->GetPixelAspectRatio()); - p.set_interlacing(interlaced_combobox_->GetInterlaceMode()); - p.set_format(format_combobox_->GetPixelFormat()); - p.set_channel_count(channel_count_combobox_->currentData().toInt()); - p.set_divider(divider_combobox_->GetDivider()); - p.set_stream_index(stream_index_slider_->GetValue()); - p.set_video_type(static_cast(video_type_combobox_->currentData().toInt())); - p.set_start_time(start_time_slider_->GetValue()); - p.set_duration(end_time_slider_->GetValue() - start_time_slider_->GetValue() + 1); - p.set_premultiplied_alpha(premultiplied_alpha_box_->isChecked()); - p.set_colorspace(colorspace_combobox_->currentData().toString()); - - return p; -} - -void VideoParamEdit::SetVideoParams(const VideoParams &p) -{ - blockSignals(true); - - enabled_box_->setChecked(p.enabled()); - width_slider_->SetValue(p.width()); - height_slider_->SetValue(p.height()); - depth_slider_->SetValue(p.depth()); - - frame_rate_combobox_->SetFrameRate(p.frame_rate()); - frame_rate_slider_->SetValue(p.frame_rate()); - timebase_temp_ = p.time_base(); - - pixel_aspect_combobox_->SetPixelAspectRatio(p.pixel_aspect_ratio()); - interlaced_combobox_->SetInterlaceMode(p.interlacing()); - format_combobox_->SetPixelFormat(p.format()); - SetChannelCount(p.channel_count()); - divider_combobox_->SetDivider(p.divider()); - stream_index_slider_->SetValue(p.stream_index()); - SetVideoTypeComboBox(p.video_type()); - start_time_slider_->SetValue(p.start_time()); - end_time_slider_->SetValue(p.start_time() + p.duration() - 1); - premultiplied_alpha_box_->setChecked(p.premultiplied_alpha()); - - if (color_manager_) { - // Assume colorspace box has been populated correctly - for (int i=0; icount(); i++) { - if (colorspace_combobox_->itemData(i).toString() == p.colorspace()) { - colorspace_combobox_->setCurrentIndex(i); - break; - } - } - } else { - // Box is empty, fill with single option so that it gets preserved in GetVideoParams() - colorspace_combobox_->clear(); - colorspace_combobox_->addItem(p.colorspace(), p.colorspace()); - } - - blockSignals(false); -} - -void VideoParamEdit::SetColorManager(ColorManager *cm) -{ - color_manager_ = cm; - - // Re-populate colorspace combobox - colorspace_combobox_->clear(); - - if (color_manager_) { - // Add default colorspace - colorspace_combobox_->addItem(tr("Default (%1)").arg(color_manager_->GetDefaultInputColorSpace()), QString()); - - // Add remaining - QStringList spaces = color_manager_->ListAvailableColorspaces(); - foreach (const QString& s, spaces) { - colorspace_combobox_->addItem(s, s); - } - } -} - -void VideoParamEdit::SetChannelCount(int count) -{ - for (int i=0; icount(); i++) { - if (channel_count_combobox_->itemData(i).toInt() == count) { - channel_count_combobox_->setCurrentIndex(i); - break; - } - } -} - -void VideoParamEdit::SetVideoTypeComboBox(VideoParams::Type type) -{ - for (int i=0; icount(); i++) { - if (video_type_combobox_->itemData(i).toInt() == type) { - video_type_combobox_->setCurrentIndex(i); - break; - } - } -} - -} diff --git a/app/widget/videoparamedit/videoparamedit.h b/app/widget/videoparamedit/videoparamedit.h deleted file mode 100644 index 0aa372869..000000000 --- a/app/widget/videoparamedit/videoparamedit.h +++ /dev/null @@ -1,182 +0,0 @@ -/*** - - 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 VIDEOPARAMEDIT_H -#define VIDEOPARAMEDIT_H - -#include -#include -#include - -#include "node/color/colormanager/colormanager.h" -#include "render/videoparams.h" -#include "widget/slider/integerslider.h" -#include "widget/slider/rationalslider.h" -#include "widget/standardcombos/frameratecombobox.h" -#include "widget/standardcombos/interlacedcombobox.h" -#include "widget/standardcombos/pixelaspectratiocombobox.h" -#include "widget/standardcombos/pixelformatcombobox.h" -#include "widget/standardcombos/videodividercombobox.h" - -namespace olive { - -class VideoParamEdit : public QWidget -{ - Q_OBJECT -public: - VideoParamEdit(QWidget* parent = nullptr); - - enum ParamMask { - kNone = 0x0, - kEnabled = 0x1, - kWidthHeight = 0x2, - kDepth = 0x4, - kFrameRate = 0x8, - kFormat = 0x10, - kChannelCount = 0x20, - kPixelAspect = 0x40, - kInterlacing = 0x80, - kDivider = 0x100, - kStreamIndex = 0x200, - kIsImageSequence = 0x400, - kStartTime = 0x800, - kEndTime = 0x1000, - kPremultipliedAlpha = 0x2000, - kColorspace = 0x4000, - kFrameRateIsNotTimebase = 0x8000, - kFrameRateIsArbitrary = 0x10000 - }; - - void SetParameterMask(uint64_t mask); - - VideoParams GetVideoParams() const; - void SetVideoParams(const VideoParams& p); - - /** - * @brief Set pointer to ColorManager - * - * Call this before calling SetVideoParams because it'll populate the colorspace list so it - * can correctly be chosen from in the UI. - */ - void SetColorManager(ColorManager* cm); - - int GetWidth() const - { - return width_slider_->GetValue(); - } - - void SetWidth(int w) - { - width_slider_->SetValue(w); - } - - int GetHeight() const - { - return height_slider_->GetValue(); - } - - void SetHeight(int h) - { - height_slider_->SetValue(h); - } - - rational GetFrameRate() const - { - return frame_rate_combobox_->GetFrameRate(); - } - - void SetFrameRate(const rational& r) - { - frame_rate_combobox_->SetFrameRate(r); - } - - rational GetPixelAspectRatio() const - { - return pixel_aspect_combobox_->GetPixelAspectRatio(); - } - - void SetPixelAspectRatio(const rational& r) - { - pixel_aspect_combobox_->SetPixelAspectRatio(r); - } - - VideoParams::Interlacing GetInterlaceMode() const - { - return interlaced_combobox_->GetInterlaceMode(); - } - - void SetInterlaceMode(VideoParams::Interlacing i) - { - interlaced_combobox_->SetInterlaceMode(i); - } - -signals: - void Changed(); - -private: - void SetChannelCount(int count); - - void SetVideoTypeComboBox(VideoParams::Type type); - - QLabel* enabled_lbl_; - QCheckBox* enabled_box_; - QLabel* width_lbl_; - IntegerSlider* width_slider_; - QLabel* height_lbl_; - IntegerSlider* height_slider_; - QLabel* depth_lbl_; - IntegerSlider* depth_slider_; - QLabel* frame_rate_lbl_; - FrameRateComboBox* frame_rate_combobox_; - RationalSlider* frame_rate_slider_; - QLabel* pixel_aspect_lbl_; - PixelAspectRatioComboBox* pixel_aspect_combobox_; - QLabel* interlaced_lbl_; - InterlacedComboBox* interlaced_combobox_; - QLabel* format_lbl_; - PixelFormatComboBox* format_combobox_; - QLabel* channel_count_lbl_; - QComboBox* channel_count_combobox_; - QLabel* divider_lbl_; - VideoDividerComboBox* divider_combobox_; - QLabel* stream_index_lbl_; - IntegerSlider* stream_index_slider_; - QLabel* video_type_lbl_; - QComboBox* video_type_combobox_; - QLabel* start_time_lbl_; - IntegerSlider* start_time_slider_; - QLabel* end_time_lbl_; - IntegerSlider* end_time_slider_; - QLabel* premultiplied_alpha_lbl_; - QCheckBox* premultiplied_alpha_box_; - QLabel* colorspace_lbl_; - QComboBox* colorspace_combobox_; - - ColorManager* color_manager_; - - rational timebase_temp_; - - uint64_t mask_; - -}; - -} - -#endif // VIDEOPARAMEDIT_H diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 2cbf094d0..bceb6a935 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -97,6 +97,7 @@ MainWindow::MainWindow(QWidget *parent) : node_panel_->Select(target, true); }); connect(param_panel_, &ParamPanel::FocusedNodeChanged, sequence_viewer_panel_, &ViewerPanel::SetGizmos); + connect(param_panel_, &ParamPanel::FocusedNodeChanged, curve_panel_, &CurvePanel::SetNode); // Connect time signals together connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, param_panel_, &ParamPanel::SetTime); From 619de01db4ef62247e1c052220bb6269b23c7536 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 17 Dec 2021 17:07:59 -0800 Subject: [PATCH 21/34] refined curve view --- app/widget/curvewidget/curveview.cpp | 218 +++++++++++------- app/widget/curvewidget/curveview.h | 14 +- app/widget/curvewidget/curvewidget.cpp | 79 +++---- app/widget/curvewidget/curvewidget.h | 8 +- app/widget/keyframeview/keyframeview.cpp | 10 + app/widget/keyframeview/keyframeview.h | 2 + .../keyframeviewinputconnection.cpp | 8 + .../keyframeviewinputconnection.h | 4 + app/widget/nodeparamview/nodeparamview.cpp | 7 +- .../nodeparamviewwidgetbridge.cpp | 42 +--- app/widget/nodetreeview/nodetreeview.cpp | 43 ++-- app/widget/nodetreeview/nodetreeview.h | 9 + app/widget/slider/floatslider.cpp | 88 ++++--- app/widget/slider/floatslider.h | 4 + app/widget/timebased/timebasedview.h | 11 +- .../timebased/timebasedviewselectionmanager.h | 15 +- 16 files changed, 309 insertions(+), 253 deletions(-) diff --git a/app/widget/curvewidget/curveview.cpp b/app/widget/curvewidget/curveview.cpp index 8fee9d725..e756d7376 100644 --- a/app/widget/curvewidget/curveview.cpp +++ b/app/widget/curvewidget/curveview.cpp @@ -30,7 +30,6 @@ #include "common/qtutils.h" #include "widget/keyframeview/keyframeviewundo.h" #include "widget/nodeparamview/nodeparamviewundo.h" -#include "widget/slider/floatslider.h" namespace olive { @@ -61,6 +60,9 @@ void CurveView::ConnectInput(const NodeKeyframeTrackReference& ref) track_con->SetBrush(keyframe_colors_.value(ref)); track_connections_.insert(ref, track_con); + // Signal to CurveWidget to update its bezier/linear/hold buttons if a key type changes + connect(track_con, &KeyframeViewInputConnection::TypeChanged, this, &CurveView::SelectionChanged); + // Append to the list connected_inputs_.append(ref); } @@ -90,13 +92,6 @@ void CurveView::SelectKeyframesOfInput(const NodeKeyframeTrackReference& ref) } } -void CurveView::ZoomToFitInput(const NodeKeyframeTrackReference& ref) -{ - if (KeyframeViewInputConnection *con = track_connections_.value(ref)) { - ZoomToFitInternal(con->GetKeyframes()); - } -} - void CurveView::SetKeyframeTrackColor(const NodeKeyframeTrackReference &ref, const QColor &color) { // Insert color into hashmap @@ -265,8 +260,28 @@ void CurveView::ContextMenuEvent(Menu &m) void CurveView::SceneRectUpdateEvent(QRectF &r) { - r.setTop(r.top() - this->height()); - r.setBottom(r.bottom() + this->height()); + double min_val, max_val; + bool got_val = false; + + foreach (KeyframeViewInputConnection *con, track_connections_) { + foreach (NodeKeyframe *key, con->GetKeyframes()) { + qreal key_y = GetItemYFromKeyframeValue(key); + + if (got_val) { + min_val = qMin(key_y, min_val); + max_val = qMax(key_y, max_val); + } else { + min_val = key_y; + max_val = key_y; + got_val = true; + } + } + } + + if (got_val) { + r.setTop(min_val - this->height()); + r.setBottom(max_val + this->height()); + } } qreal CurveView::GetKeyframeSceneY(KeyframeViewInputConnection *track, NodeKeyframe *key) @@ -392,7 +407,8 @@ void CurveView::KeyframeDragStart(QMouseEvent *event) { drag_keyframe_values_.resize(GetSelectedKeyframes().size()); for (int i=0; ivalue(); + NodeKeyframe *key = GetSelectedKeyframes().at(i); + drag_keyframe_values_[i] = key->value(); } drag_start_ = mapToScene(event->pos()); @@ -401,7 +417,11 @@ void CurveView::KeyframeDragStart(QMouseEvent *event) void CurveView::KeyframeDragMove(QMouseEvent *event, QString &tip) { if (event->modifiers() & Qt::ShiftModifier) { - // Lock to X axis only + // Lock to X axis only and set original values on all keys + for (int i=0; iset_value(drag_keyframe_values_.at(i)); + } return; } @@ -412,10 +432,11 @@ void CurveView::KeyframeDragMove(QMouseEvent *event, QString &tip) for (int i=0; iparent(); - double original_val = drag_keyframe_values_.at(i).toDouble(); + double original_val = FloatSlider::TransformValueToDisplay(drag_keyframe_values_.at(i).toDouble(), display); const QString& input = key->input(); - double new_val = original_val - scaled_diff; + double new_val = FloatSlider::TransformDisplayToValue(original_val - scaled_diff, display); double limited = new_val; if (node->HasInputProperty(input, QStringLiteral("min"))) { @@ -434,23 +455,18 @@ void CurveView::KeyframeDragMove(QMouseEvent *event, QString &tip) // Set values for (int i=0; iset_value(drag_keyframe_values_.at(i).toDouble() - scaled_diff); + FloatSlider::DisplayType display = GetFloatDisplayTypeFromKeyframe(key); + key->set_value(FloatSlider::TransformDisplayToValue(FloatSlider::TransformValueToDisplay(drag_keyframe_values_.at(i).toDouble(), display) - scaled_diff, display)); } NodeKeyframe *tip_item = GetSelectedKeyframes().first(); - FloatSlider::DisplayType display_type = FloatSlider::kNormal; - Node* initial_drag_input = tip_item->parent(); - const QString& initial_drag_input_id = tip_item->input(); - if (initial_drag_input->HasInputProperty(initial_drag_input_id, QStringLiteral("view"))) { - display_type = static_cast(initial_drag_input->GetInputProperty(initial_drag_input_id, QStringLiteral("view")).toInt()); - } bool ok; double num_value = tip_item->value().toDouble(&ok); if (ok) { tip = QStringLiteral("%1\n"); - tip.append(FloatSlider::ValueToString(num_value, display_type, 2, true)); + tip.append(FloatSlider::ValueToString(num_value + GetOffsetFromKeyframe(tip_item), GetFloatDisplayTypeFromKeyframe(tip_item), 2, true)); } } @@ -458,7 +474,9 @@ void CurveView::KeyframeDragRelease(QMouseEvent *event, MultiUndoCommand *comman { for (int i=0; iadd_child(new NodeParamSetKeyframeValueCommand(k, k->value(), drag_keyframe_values_.at(i))); + if (!qFuzzyCompare(k->value().toDouble(), drag_keyframe_values_.at(i).toDouble())) { + command->add_child(new NodeParamSetKeyframeValueCommand(k, k->value(), drag_keyframe_values_.at(i))); + } } } @@ -488,53 +506,88 @@ QPointF CurveView::GetScaledCursorPos(const QPointF &cursor_pos) cursor_pos.y() / GetYScale()); } -void CurveView::ZoomToFitInternal(const QVector &keys) +void CurveView::ZoomToFitInternal(bool selected_only) { - if (keys.isEmpty()) { - // Prevent scaling to DBL_MIN/DBL_MAX - return; + bool got_val = false; + + rational min_time, max_time; + double min_val, max_val; + + foreach (KeyframeViewInputConnection *con, track_connections_) { + foreach (NodeKeyframe *key, con->GetKeyframes()) { + if (!selected_only || IsKeyframeSelected(key)) { + rational transformed_time = GetAdjustedTime(key->parent(), + GetTimeTarget(), + key->time(), + false); + + qreal key_y = GetUnscaledItemYFromKeyframeValue(key); + + if (got_val) { + min_time = qMin(transformed_time, min_time); + max_time = qMax(transformed_time, max_time); + + min_val = qMin(key_y, min_val); + max_val = qMax(key_y, max_val); + } else { + min_time = transformed_time; + max_time = transformed_time; + + min_val = key_y; + max_val = key_y; + + got_val = true; + } + } + } } - rational min_time = RATIONAL_MAX; - rational max_time = RATIONAL_MIN; + // Prevent scaling if no keyframes were found + if (got_val) { + QRectF desired(QPointF(min_time.toDouble(), min_val), QPointF(max_time.toDouble(), max_val)); - double min_val = DBL_MAX; - double max_val = DBL_MIN; + const double scale_divider = 0.5; + double scale_half_divider = scale_divider*0.5; - foreach (NodeKeyframe* key, keys) { - rational transformed_time = GetAdjustedTime(key->parent(), - GetTimeTarget(), - key->time(), - false); + double new_x_scale = viewport()->width() / desired.width() * scale_divider; + double new_y_scale; - min_time = qMin(transformed_time, min_time); - max_time = qMax(transformed_time, max_time); + if (qFuzzyIsNull(desired.height())) { + // Catch divide by zero + new_y_scale = 1.0; + scale_half_divider = 0.5; + } else { + // Use height as normal + new_y_scale = viewport()->height() / desired.height() * scale_divider; + } - min_val = qMin(key->value().toDouble(), min_val); - max_val = qMax(key->value().toDouble(), max_val); + emit ScaleChanged(new_x_scale); + SetYScale(new_y_scale); + + UpdateSceneRect(); + + int sb_x = desired.left() * new_x_scale - viewport()->width() * scale_half_divider; + QMetaObject::invokeMethod(horizontalScrollBar(), "setValue", Qt::QueuedConnection, Q_ARG(int, sb_x)); + + int sb_y = desired.top() * new_y_scale - viewport()->height() * scale_half_divider; + QMetaObject::invokeMethod(verticalScrollBar(), "setValue", Qt::QueuedConnection, Q_ARG(int, sb_y)); } - - double time_range = max_time.toDouble() - min_time.toDouble(); - double new_x_scale = CalculateScaleFromDimensions(this->width(), time_range); - double new_y_scale = CalculateScaleFromDimensions(this->height(), max_val - min_val); - - emit ScaleChanged(new_x_scale); - SetYScale(new_y_scale); - - QMetaObject::invokeMethod(horizontalScrollBar(), "setValue", Qt::QueuedConnection, - Q_ARG(int, TimeToScene(min_time) - CalculatePaddingFromDimensionScale(this->width()))); - QMetaObject::invokeMethod(verticalScrollBar(), "setValue", Qt::QueuedConnection, - Q_ARG(int, GetItemYFromKeyframeValue(max_val) - CalculatePaddingFromDimensionScale(this->height()))); } qreal CurveView::GetItemYFromKeyframeValue(NodeKeyframe *key) { - return GetItemYFromKeyframeValue(key->value().toDouble()); + return GetUnscaledItemYFromKeyframeValue(key) * GetYScale(); } -qreal CurveView::GetItemYFromKeyframeValue(double value) +qreal CurveView::GetUnscaledItemYFromKeyframeValue(NodeKeyframe *key) { - return -value * GetYScale(); + double val = key->value().toDouble(); + + val = FloatSlider::TransformValueToDisplay(val, GetFloatDisplayTypeFromKeyframe(key)); + + val += GetOffsetFromKeyframe(key); + + return -val; } QPointF CurveView::ScalePoint(const QPointF &point) @@ -543,41 +596,48 @@ QPointF CurveView::ScalePoint(const QPointF &point) return QPointF(point.x() * GetScale(), - point.y() * GetYScale()); } +FloatSlider::DisplayType CurveView::GetFloatDisplayTypeFromKeyframe(NodeKeyframe *key) +{ + Node* node = key->parent(); + const QString& input = key->input(); + if (node->HasInputProperty(input, QStringLiteral("view"))) { + // Try to get view from input (which will be normal if unset) + return static_cast(node->GetInputProperty(input, QStringLiteral("view")).toInt()); + } + + // Fallback to normal + return FloatSlider::kNormal; +} + +double CurveView::GetOffsetFromKeyframe(NodeKeyframe *key) +{ + Node *node = key->parent(); + const QString &input = key->input(); + if (node->HasInputProperty(input, QStringLiteral("offset"))) { + QVariant v = node->GetInputProperty(input, QStringLiteral("offset")); + + // NOTE: Implement getting correct offset for the track based on the data type + QVector track_vals = NodeValue::split_normal_value_into_track_values(node->GetInputDataType(input), v); + + return track_vals.at(key->track()).toDouble(); + } + + return 0; +} + QPointF CurveView::GetKeyframePosition(NodeKeyframe *key) { return QPointF(GetKeyframeSceneX(key), GetItemYFromKeyframeValue(key)); } -void CurveView::KeyframeTypeChanged() -{ - qDebug() << "STUB!"; - /*NodeKeyframe* key = static_cast(sender()); - KeyframeViewItem* item = item_map().value(key); - - if (item->isSelected()) { - item->setSelected(false); - item->setSelected(true); - }*/ -} - void CurveView::ZoomToFit() { - QVector keys; - - foreach (KeyframeViewInputConnection *con, track_connections_) { - foreach (NodeKeyframe *k, con->GetKeyframes()) { - if (!keys.contains(k)) { - keys.append(k); - } - } - } - - ZoomToFitInternal(keys); + ZoomToFitInternal(false); } void CurveView::ZoomToFitSelected() { - ZoomToFitInternal(GetSelectedKeyframes()); + ZoomToFitInternal(true); } void CurveView::ResetZoom() diff --git a/app/widget/curvewidget/curveview.h b/app/widget/curvewidget/curveview.h index 3858368e8..1eb8355f1 100644 --- a/app/widget/curvewidget/curveview.h +++ b/app/widget/curvewidget/curveview.h @@ -23,6 +23,7 @@ #include "node/keyframe.h" #include "widget/keyframeview/keyframeview.h" +#include "widget/slider/floatslider.h" namespace olive { @@ -38,8 +39,6 @@ public: void SelectKeyframesOfInput(const NodeKeyframeTrackReference &ref); - void ZoomToFitInput(const NodeKeyframeTrackReference &ref); - void SetKeyframeTrackColor(const NodeKeyframeTrackReference& ref, const QColor& color); public slots: @@ -70,13 +69,17 @@ protected: virtual void KeyframeDragRelease(QMouseEvent *event, MultiUndoCommand *command) override; private: - void ZoomToFitInternal(const QVector &keys); + void ZoomToFitInternal(bool selected_only); qreal GetItemYFromKeyframeValue(NodeKeyframe* key); - qreal GetItemYFromKeyframeValue(double value); + qreal GetUnscaledItemYFromKeyframeValue(NodeKeyframe* key); QPointF ScalePoint(const QPointF& point); + static FloatSlider::DisplayType GetFloatDisplayTypeFromKeyframe(NodeKeyframe *key); + + static double GetOffsetFromKeyframe(NodeKeyframe *key); + void AdjustLines(); QPointF GetKeyframePosition(NodeKeyframe *key); @@ -112,9 +115,6 @@ private: QVector drag_keyframe_values_; -private slots: - void KeyframeTypeChanged(); - }; } diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index 6dac944dd..e32f75d67 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -47,10 +47,7 @@ CurveWidget::CurveWidget(QWidget *parent) : tree_view_ = new NodeTreeView(); tree_view_->SetOnlyShowKeyframable(true); tree_view_->SetShowKeyframeTracksAsRows(true); - connect(tree_view_, &NodeTreeView::NodeEnableChanged, this, &CurveWidget::NodeEnabledChanged); - connect(tree_view_, &NodeTreeView::InputEnableChanged, this, &CurveWidget::InputEnabledChanged); connect(tree_view_, &NodeTreeView::InputSelectionChanged, this, &CurveWidget::InputSelectionChanged); - connect(tree_view_, &NodeTreeView::InputDoubleClicked, this, &CurveWidget::InputDoubleClicked); splitter->addWidget(tree_view_); QWidget* workarea = new QWidget(); @@ -101,7 +98,7 @@ CurveWidget::CurveWidget(QWidget *parent) : // Connect ruler and view together connect(view_, &CurveView::TimeChanged, this, &CurveWidget::SetTimeAndSignal); - connect(view_->scene(), &QGraphicsScene::selectionChanged, this, &CurveWidget::SelectionChanged); + connect(view_, &CurveView::SelectionChanged, this, &CurveWidget::SelectionChanged); connect(view_, &CurveView::ScaleChanged, this, &CurveWidget::SetScale); connect(view_, &CurveView::Dragged, this, &CurveWidget::KeyframeViewDragged); @@ -176,6 +173,8 @@ void CurveWidget::ScaleChangedEvent(const double &scale) void CurveWidget::TimeTargetChangedEvent(Node *target) { + TimeTargetObject::TimeTargetChangedEvent(target); + key_control_->SetTimeTarget(target); view_->SetTimeTarget(target); @@ -183,6 +182,8 @@ void CurveWidget::TimeTargetChangedEvent(Node *target) void CurveWidget::ConnectedNodeChangeEvent(ViewerOutput *n) { + super::ConnectedNodeChangeEvent(n); + SetTimeTarget(n); } @@ -215,7 +216,7 @@ void CurveWidget::UpdateBridgeTime(const rational &time) void CurveWidget::ConnectNode(Node *node, bool connect) { foreach (const QString& input, node->inputs()) { - if (node->IsInputKeyframable(input)) { + if (node->IsInputKeyframable(input) && !node->IsInputHidden(input)) { ConnectInput(node, input, connect); } } @@ -228,9 +229,6 @@ void CurveWidget::ConnectInput(Node *node, const QString &input, bool connect) return; } - int track_count = NodeValue::get_number_of_keyframe_tracks(node->GetInputDataType(input)); - bool multiple_tracks = track_count > 1; - int arr_sz = node->InputArraySize(input); for (int i=-1; iSetKeyframeTrackColor(ref, c); } } - - if (tree_view_->IsInputEnabled(NodeKeyframeTrackReference(NodeInput(node, input, i), multiple_tracks ? -1 : 0))) { - if (multiple_tracks) { - for (int j=0; jIsInputEnabled(ref)) { - if (connect) { - view_->ConnectInput(ref); - } else { - view_->DisconnectInput(ref); - } - } - } - } else { - NodeKeyframeTrackReference ref(NodeInput(node, input, i), 0); - if (connect) { - view_->ConnectInput(ref); - } else { - view_->DisconnectInput(ref); - } - } - } } } @@ -339,32 +315,35 @@ void CurveWidget::KeyframeTypeButtonTriggered(bool checked) Core::instance()->undo_stack()->push(command); } -void CurveWidget::NodeEnabledChanged(Node* n, bool e) -{ - ConnectNode(n, e); -} - -void CurveWidget::InputEnabledChanged(const NodeKeyframeTrackReference& ref, bool e) -{ - if (e) { - view_->ConnectInput(ref); - } else { - view_->DisconnectInput(ref); - } -} - void CurveWidget::InputSelectionChanged(const NodeKeyframeTrackReference& ref) { key_control_->SetInput(ref.input()); - if (ref.IsValid()) { - view_->SelectKeyframesOfInput(ref); + foreach (const NodeKeyframeTrackReference &c, selected_tracks_) { + view_->DisconnectInput(c); } -} -void CurveWidget::InputDoubleClicked(const NodeKeyframeTrackReference& ref) -{ - view_->ZoomToFitInput(ref); + selected_tracks_.clear(); + + if (ref.IsValid()) { + view_->ConnectInput(ref); + selected_tracks_.append(ref); + } else if (ref.input().IsValid()) { + int track_count = NodeValue::get_number_of_keyframe_tracks(ref.input().GetDataType()); + for (int i=0; iConnectInput(track_ref); + selected_tracks_.append(track_ref); + } + } else if (Node *node = ref.input().node()) { + foreach (const QString &input, node->inputs()) { + if (!node->IsInputKeyframable(input) || node->IsInputHidden(input)) { + + } + } + } + + view_->ZoomToFit(); } void CurveWidget::KeyframeViewDragged(int x, int y) diff --git a/app/widget/curvewidget/curvewidget.h b/app/widget/curvewidget/curvewidget.h index db14f2955..c27057fc8 100644 --- a/app/widget/curvewidget/curvewidget.h +++ b/app/widget/curvewidget/curvewidget.h @@ -96,19 +96,15 @@ private: QVector nodes_; + QVector selected_tracks_; + private slots: void SelectionChanged(); void KeyframeTypeButtonTriggered(bool checked); - void NodeEnabledChanged(Node* n, bool e); - - void InputEnabledChanged(const NodeKeyframeTrackReference &ref, bool e); - void InputSelectionChanged(const NodeKeyframeTrackReference& ref); - void InputDoubleClicked(const NodeKeyframeTrackReference& ref); - void KeyframeViewDragged(int x, int y); void CatchUpYScrollToPoint(int point); diff --git a/app/widget/keyframeview/keyframeview.cpp b/app/widget/keyframeview/keyframeview.cpp index cec7aca99..0e751265a 100644 --- a/app/widget/keyframeview/keyframeview.cpp +++ b/app/widget/keyframeview/keyframeview.cpp @@ -116,6 +116,7 @@ void KeyframeView::RemoveKeyframesOfTrack(KeyframeViewInputConnection *connectio } delete connection; Redraw(); + emit SelectionChanged(); } } @@ -157,6 +158,8 @@ void KeyframeView::SelectionManagerSelectEvent(void *obj) } } } + + emit SelectionChanged(); } void KeyframeView::SelectionManagerDeselectEvent(void *obj) @@ -170,6 +173,8 @@ void KeyframeView::SelectionManagerDeselectEvent(void *obj) } } } + + emit SelectionChanged(); } void KeyframeView::mousePressEvent(QMouseEvent *event) @@ -235,6 +240,7 @@ void KeyframeView::mouseReleaseEvent(QMouseEvent *event) } else if (selection_manager_.IsRubberBanding()) { selection_manager_.RubberBandStop(); Redraw(); + emit SelectionChanged(); } } @@ -325,6 +331,8 @@ void KeyframeView::SelectKeyframe(NodeKeyframe *key) { if (selection_manager_.Select(key)) { Redraw(); + + emit SelectionChanged(); } } @@ -332,6 +340,8 @@ void KeyframeView::DeselectKeyframe(NodeKeyframe *key) { if (selection_manager_.Deselect(key)) { Redraw(); + + emit SelectionChanged(); } } diff --git a/app/widget/keyframeview/keyframeview.h b/app/widget/keyframeview/keyframeview.h index a0c1aa99a..09565f1b2 100644 --- a/app/widget/keyframeview/keyframeview.h +++ b/app/widget/keyframeview/keyframeview.h @@ -75,6 +75,8 @@ public: signals: void Dragged(int current_x, int current_y); + void SelectionChanged(); + protected: virtual void mousePressEvent(QMouseEvent *event) override; virtual void mouseMoveEvent(QMouseEvent *event) override; diff --git a/app/widget/keyframeview/keyframeviewinputconnection.cpp b/app/widget/keyframeview/keyframeviewinputconnection.cpp index b7b3c2dca..c0d2c3f31 100644 --- a/app/widget/keyframeview/keyframeviewinputconnection.cpp +++ b/app/widget/keyframeview/keyframeviewinputconnection.cpp @@ -38,6 +38,7 @@ KeyframeViewInputConnection::KeyframeViewInputConnection(const NodeKeyframeTrack connect(n, &Node::KeyframeRemoved, this, &KeyframeViewInputConnection::RemoveKeyframe); connect(n, &Node::KeyframeTimeChanged, this, &KeyframeViewInputConnection::KeyframeChanged); connect(n, &Node::KeyframeTypeChanged, this, &KeyframeViewInputConnection::KeyframeChanged); + connect(n, &Node::KeyframeTypeChanged, this, &KeyframeViewInputConnection::KeyframeTypeChanged); connect(n, &Node::KeyframeValueChanged, this, &KeyframeViewInputConnection::KeyframeChanged); } @@ -89,4 +90,11 @@ void KeyframeViewInputConnection::KeyframeChanged(NodeKeyframe *key) } } +void KeyframeViewInputConnection::KeyframeTypeChanged(NodeKeyframe *key) +{ + if (key->key_track_ref() == input_) { + emit TypeChanged(); + } +} + } diff --git a/app/widget/keyframeview/keyframeviewinputconnection.h b/app/widget/keyframeview/keyframeviewinputconnection.h index f10f37d2b..b6b0c1da5 100644 --- a/app/widget/keyframeview/keyframeviewinputconnection.h +++ b/app/widget/keyframeview/keyframeviewinputconnection.h @@ -65,6 +65,8 @@ public: signals: void RequireUpdate(); + void TypeChanged(); + private: KeyframeView *keyframe_view_; @@ -83,6 +85,8 @@ private slots: void KeyframeChanged(NodeKeyframe *key); + void KeyframeTypeChanged(NodeKeyframe *key); + }; } diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index e21ead255..697067343 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -417,11 +417,15 @@ void NodeParamView::SortItemsInContext(NodeParamViewContext *context_item) QVector > distances; for (auto it=context_item->GetItems().cbegin(); it!=context_item->GetItems().cend(); it++) { - int distance = 0; + int distance = -1; foreach (Node *ctx, context_item->GetContexts()) { distance = qMax(distance, GetDistanceBetweenNodes(ctx, it.key())); } + if (distance == -1) { + distance = INT_MAX; + } + bool inserted = false; QPair dist(it.value(), distance); @@ -439,6 +443,7 @@ void NodeParamView::SortItemsInContext(NodeParamViewContext *context_item) } foreach (auto info, distances) { + qDebug() << "Inserting" << info.first->GetNode() << "with distance" << info.second; context_item->GetDockArea()->AddItem(info.first); } } diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index dda919eee..854d4a15f 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -648,42 +648,12 @@ void NodeParamViewWidgetBridge::PropertyChanged(const QString& input, const QStr break; } } else if (key == QStringLiteral("offset")) { - switch (data_type) { - case NodeValue::kInt: - static_cast(widgets_.first())->SetOffset(value); - break; - case NodeValue::kFloat: - static_cast(widgets_.first())->SetOffset(value); - break; - case NodeValue::kRational: - static_cast(widgets_.first())->SetOffset(value); - break; - case NodeValue::kVec2: - { - QVector2D offs = value.value(); - static_cast(widgets_.at(0))->SetOffset(offs.x()); - static_cast(widgets_.at(1))->SetOffset(offs.y()); - break; - } - case NodeValue::kVec3: - { - QVector3D offs = value.value(); - static_cast(widgets_.at(0))->SetOffset(offs.x()); - static_cast(widgets_.at(1))->SetOffset(offs.y()); - static_cast(widgets_.at(2))->SetOffset(offs.z()); - break; - } - case NodeValue::kVec4: - { - QVector4D offs = value.value(); - static_cast(widgets_.at(0))->SetOffset(offs.x()); - static_cast(widgets_.at(1))->SetOffset(offs.y()); - static_cast(widgets_.at(2))->SetOffset(offs.z()); - static_cast(widgets_.at(3))->SetOffset(offs.w()); - break; - } - default: - break; + int tracks = NodeValue::get_number_of_keyframe_tracks(data_type); + + QVector offsets = NodeValue::split_normal_value_into_track_values(data_type, value); + + for (int i=0; i(widgets_.at(i))->SetOffset(offsets.at(i)); } UpdateWidgetValues(); diff --git a/app/widget/nodetreeview/nodetreeview.cpp b/app/widget/nodetreeview/nodetreeview.cpp index a2f8ec9df..fd0afc2d9 100644 --- a/app/widget/nodetreeview/nodetreeview.cpp +++ b/app/widget/nodetreeview/nodetreeview.cpp @@ -27,7 +27,8 @@ namespace olive { NodeTreeView::NodeTreeView(QWidget *parent) : QTreeWidget(parent), only_show_keyframable_(false), - show_keyframe_tracks_as_rows_(false) + show_keyframe_tracks_as_rows_(false), + checkboxes_enabled_(false) { connect(this, &NodeTreeView::itemChanged, this, &NodeTreeView::ItemCheckStateChanged); connect(this, &NodeTreeView::itemSelectionChanged, this, &NodeTreeView::SelectionChanged); @@ -67,12 +68,14 @@ void NodeTreeView::SetNodes(const QVector &nodes) foreach (Node* n, nodes_) { QTreeWidgetItem* node_item = new QTreeWidgetItem(); node_item->setText(0, n->Name()); - node_item->setCheckState(0, disabled_nodes_.contains(n) ? Qt::Unchecked : Qt::Checked); + if (checkboxes_enabled_) { + node_item->setCheckState(0, disabled_nodes_.contains(n) ? Qt::Unchecked : Qt::Checked); + } node_item->setData(0, kItemType, kItemTypeNode); node_item->setData(0, kItemNodePointer, Node::PtrToValue(n)); foreach (const QString& input, n->inputs()) { - if (only_show_keyframable_ && !n->IsInputKeyframable(input)) { + if (n->IsInputHidden(input) || (only_show_keyframable_ && !n->IsInputKeyframable(input))) { continue; } @@ -105,7 +108,6 @@ void NodeTreeView::SetNodes(const QVector &nodes) CreateItemsForTracks(element_item, input_ref, key_tracks.size()); } } - } // Add at the end to prevent unnecessary signalling while we're setting these objects up @@ -114,9 +116,9 @@ void NodeTreeView::SetNodes(const QVector &nodes) } else { delete node_item; } - - node_item->setExpanded(true); } + + expandAll(); } void NodeTreeView::changeEvent(QEvent *e) @@ -155,6 +157,8 @@ NodeKeyframeTrackReference NodeTreeView::GetSelectedInput() if (item->data(0, kItemType).toInt() == kItemTypeInput) { selected_ref = item->data(0, kItemInputReference).value(); + } else { + selected_ref = NodeKeyframeTrackReference(NodeInput(Node::ValueToPtr(item->data(0, kItemNodePointer)), QString())); } } @@ -166,21 +170,27 @@ QTreeWidgetItem* NodeTreeView::CreateItem(QTreeWidgetItem *parent, const NodeKey QTreeWidgetItem* input_item = new QTreeWidgetItem(parent); QString item_name; - if (ref.track() == -1 || NodeValue::get_number_of_keyframe_tracks(ref.input().GetDataType()) == 1) { - item_name = ref.input().name(); + if (ref.track() == -1 + || NodeValue::get_number_of_keyframe_tracks(ref.input().GetDataType()) == 1 + || (ref.input().IsArray() && ref.input().element() == -1)) { + if (ref.input().element() == -1) { + item_name = ref.input().name(); + } else { + item_name = QString::number(ref.input().element()); + } } else { switch (ref.track()) { case 0: - item_name = tr("X"); + item_name = UseRGBAOverXYZW(ref) ? tr("R") : tr("X"); break; case 1: - item_name = tr("Y"); + item_name = UseRGBAOverXYZW(ref) ? tr("G") : tr("Y"); break; case 2: - item_name = tr("Z"); + item_name = UseRGBAOverXYZW(ref) ? tr("B") : tr("Z"); break; case 3: - item_name = tr("W"); + item_name = UseRGBAOverXYZW(ref) ? tr("A") : tr("W"); break; default: item_name = QString::number(ref.track()); @@ -188,7 +198,9 @@ QTreeWidgetItem* NodeTreeView::CreateItem(QTreeWidgetItem *parent, const NodeKey } input_item->setText(0, item_name); - input_item->setCheckState(0, disabled_inputs_.contains(ref) ? Qt::Unchecked : Qt::Checked); + if (checkboxes_enabled_) { + input_item->setCheckState(0, disabled_inputs_.contains(ref) ? Qt::Unchecked : Qt::Checked); + } input_item->setData(0, kItemType, kItemTypeInput); input_item->setData(0, kItemInputReference, QVariant::fromValue(ref)); @@ -208,6 +220,11 @@ void NodeTreeView::CreateItemsForTracks(QTreeWidgetItem *parent, const NodeInput } } +bool NodeTreeView::UseRGBAOverXYZW(const NodeKeyframeTrackReference &ref) +{ + return ref.input().GetDataType() == NodeValue::kColor; +} + void NodeTreeView::ItemCheckStateChanged(QTreeWidgetItem *item, int column) { Q_UNUSED(column) diff --git a/app/widget/nodetreeview/nodetreeview.h b/app/widget/nodetreeview/nodetreeview.h index 3528b2822..067a4c82a 100644 --- a/app/widget/nodetreeview/nodetreeview.h +++ b/app/widget/nodetreeview/nodetreeview.h @@ -37,6 +37,11 @@ public: bool IsInputEnabled(const NodeKeyframeTrackReference& ref) const; + void SetCheckBoxesEnabled(bool e) + { + checkboxes_enabled_ = e; + } + void SetKeyframeTrackColor(const NodeKeyframeTrackReference& ref, const QColor& color); void SetOnlyShowKeyframable(bool e) @@ -75,6 +80,8 @@ private: void CreateItemsForTracks(QTreeWidgetItem* parent, const NodeInput& input, int track_count); + static bool UseRGBAOverXYZW(const NodeKeyframeTrackReference &ref); + enum ItemType { kItemTypeNode, kItemTypeInput @@ -98,6 +105,8 @@ private: QHash keyframe_colors_; + bool checkboxes_enabled_; + private slots: void ItemCheckStateChanged(QTreeWidgetItem* item, int column); diff --git a/app/widget/slider/floatslider.cpp b/app/widget/slider/floatslider.cpp index 39c00f686..7510c36ef 100644 --- a/app/widget/slider/floatslider.cpp +++ b/app/widget/slider/floatslider.cpp @@ -78,29 +78,46 @@ void FloatSlider::SetDisplayType(const FloatSlider::DisplayType &type) } } -QString FloatSlider::ValueToString(double val, FloatSlider::DisplayType display, int decimal_places, bool autotrim_decimal_places) +double FloatSlider::TransformValueToDisplay(double val, DisplayType display) { switch (display) { case kNormal: - // Do nothing, skip to the return string at the end break; case kDecibel: - // Convert to decibels and return dB formatted string - - // Return negative infinity for zero volume - if (qIsNull(val)) { - return tr("\xE2\x88\x9E"); - } - val = Decibel::fromLinear(val); break; case kPercentage: - // Multiply value by 100 for user-friendly percentage val *= 100.0; break; } - return FloatToString(val, decimal_places, autotrim_decimal_places); + return val; +} + +double FloatSlider::TransformDisplayToValue(double val, DisplayType display) +{ + switch (display) { + case kNormal: + break; + case kDecibel: + val = Decibel::toLinear(val); + break; + case kPercentage: + val *= 0.01; + break; + } + + return val; +} + +QString FloatSlider::ValueToString(double val, FloatSlider::DisplayType display, int decimal_places, bool autotrim_decimal_places) +{ + // Return negative infinity for zero volume + if (display == kDecibel && qIsNull(val)) { + return tr("\xE2\x88\x9E"); + } + + return FloatToString(TransformValueToDisplay(val, display), decimal_places, autotrim_decimal_places); } QString FloatSlider::ValueToString(const QVariant &v) const @@ -110,46 +127,21 @@ QString FloatSlider::ValueToString(const QVariant &v) const QVariant FloatSlider::StringToValue(const QString &s, bool *ok) const { - switch (display_type_) { - case kNormal: - // Do nothing, skip to the return string at the end - break; - case kDecibel: - { - bool valid; + bool valid; + double val = s.toDouble(&valid); - // See if we can get a decimal number out of this - qreal decibels = s.toDouble(&valid); - - if (ok) *ok = valid; - - if (valid) { - // Convert from decibel scale to linear decimal - return Decibel::toLinear(decibels); - } - - break; - } - case kPercentage: - { - bool valid; - - // Try to get double value - double val = s.toDouble(&valid); - - if (ok) *ok = valid; - - // If we could get it, convert back to a 0.0 - 1.0 value and return - if (valid) { - return val * 0.01; - } - - break; - } + // If we were given an `ok` pointer, set it to `valid` + if (ok) { + *ok = valid; } - // Just try to convert the string to a double - return s.toDouble(ok) - GetOffset().toDouble(); + // If valid, transform it from display + if (valid) { + val = TransformDisplayToValue(val, display_type_); + } + + // Return un-offset value + return val - GetOffset().toDouble(); } QVariant FloatSlider::AdjustDragDistanceInternal(const QVariant &start, const double &drag) const diff --git a/app/widget/slider/floatslider.h b/app/widget/slider/floatslider.h index 4000e41bd..fe392a983 100644 --- a/app/widget/slider/floatslider.h +++ b/app/widget/slider/floatslider.h @@ -49,6 +49,10 @@ public: void SetDisplayType(const DisplayType& type); + static double TransformValueToDisplay(double val, DisplayType display); + + static double TransformDisplayToValue(double val, DisplayType display); + static QString ValueToString(double val, DisplayType display, int decimal_places, bool autotrim_decimal_places); protected: diff --git a/app/widget/timebased/timebasedview.h b/app/widget/timebased/timebasedview.h index f6f46d285..a51728afb 100644 --- a/app/widget/timebased/timebasedview.h +++ b/app/widget/timebased/timebasedview.h @@ -62,6 +62,11 @@ public slots: void SetEndTime(const rational& length); + /** + * @brief Slot called whenever the view resizes or the scene contents change to enforce minimum scene sizes + */ + void UpdateSceneRect(); + signals: void TimeChanged(const rational& time); @@ -101,12 +106,6 @@ protected: y_axis_enabled_ = e; } -protected slots: - /** - * @brief Slot called whenever the view resizes or the scene contents change to enforce minimum scene sizes - */ - void UpdateSceneRect(); - private: qreal GetPlayheadX(); diff --git a/app/widget/timebased/timebasedviewselectionmanager.h b/app/widget/timebased/timebasedviewselectionmanager.h index 19d61e203..ae5e32230 100644 --- a/app/widget/timebased/timebasedviewselectionmanager.h +++ b/app/widget/timebased/timebasedviewselectionmanager.h @@ -92,7 +92,9 @@ public: T *GetObjectAtPoint(const QPointF &scene_pt) { - foreach (const DrawnObject &kp, drawn_objects_) { + // Iterate in reverse order because the objects drawn later will appear on top to the user + for (auto it=drawn_objects_.crbegin(); it!=drawn_objects_.crend(); it++) { + const DrawnObject &kp = *it; if (kp.second.contains(scene_pt)) { return kp.first; } @@ -147,13 +149,11 @@ public: { initial_drag_item_ = initial_item; - dragging_.clear(); - dragging_.resize(selected_.size()); for (int i=0; itime()}; + dragging_[i] = {obj->time(), view_->TimeToScene(obj->time())}; } drag_mouse_start_ = view_->mapToScene(event->pos()); @@ -164,15 +164,15 @@ public: QPointF diff = view_->mapToScene(event->pos()) - drag_mouse_start_; for (int i=0; iSceneToTimeNoGrid(diff.x()); + rational proposed_time = view_->SceneToTimeNoGrid(dragging_.at(i).x + diff.x()); T *sel = selected_.at(i); // Magic number: use interval of 1ms to avoid collisions rational adj(1, 1000); - if (old_time < proposed_time) { + if (dragging_.at(i).time < proposed_time) { adj = -adj; } + while (true) { NodeKeyframe *key_at_time = sel->parent()->GetKeyframeAtTimeOnTrack(sel->input(), proposed_time, sel->track(), sel->element()); if (!key_at_time || key_at_time == sel) { @@ -303,6 +303,7 @@ private: struct DragObject { rational time; + double x; }; QVector dragging_; From 408b3e6da6787550f23e5bb3104e9603e7eea230 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 18 Dec 2021 11:43:08 -0800 Subject: [PATCH 22/34] remove debug line --- app/widget/nodeparamview/nodeparamview.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 697067343..f4dee8b7c 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -443,7 +443,6 @@ void NodeParamView::SortItemsInContext(NodeParamViewContext *context_item) } foreach (auto info, distances) { - qDebug() << "Inserting" << info.first->GetNode() << "with distance" << info.second; context_item->GetDockArea()->AddItem(info.first); } } From 8ba5107e73d80df014fd8ac54fd39a7f77b668fc Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 18 Dec 2021 11:43:20 -0800 Subject: [PATCH 23/34] fix curveview selections --- app/widget/curvewidget/curvewidget.cpp | 97 +++++++++++++------------- app/widget/curvewidget/curvewidget.h | 4 +- 2 files changed, 50 insertions(+), 51 deletions(-) diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index e32f75d67..4d56896ae 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -131,22 +131,33 @@ void CurveWidget::SetNodes(const QVector &nodes) { tree_view_->SetNodes(nodes); - // Detect removed nodes - foreach (Node* n, nodes_) { - if (!nodes.contains(n)) { - ConnectNode(n, false); - } - } - - // Detect added nodes - foreach (Node* n, nodes) { - if (tree_view_->IsNodeEnabled(n) && !nodes_.contains(n)) { - ConnectNode(n, true); - } - } - // Save new node list nodes_ = nodes; + + // Generate colors + foreach (Node *node, nodes_) { + foreach (const QString& input, node->inputs()) { + if (node->IsInputKeyframable(input) && !node->IsInputHidden(input)) { + int arr_sz = node->InputArraySize(input); + for (int i=-1; i& tracks = node->GetKeyframeTracks(input, i); + + for (int j=0; jSetKeyframeTrackColor(ref, c); + view_->SetKeyframeTrackColor(ref, c); + } + } + } + } + } + } } void CurveWidget::TimeChangedEvent(const rational &time) @@ -213,38 +224,28 @@ void CurveWidget::UpdateBridgeTime(const rational &time) key_control_->SetTime(time); } -void CurveWidget::ConnectNode(Node *node, bool connect) +void CurveWidget::ConnectInput(Node *node, const QString &input, int element) { - foreach (const QString& input, node->inputs()) { - if (node->IsInputKeyframable(input) && !node->IsInputHidden(input)) { - ConnectInput(node, input, connect); + if (element == -1 && node->InputIsArray(input)) { + // This is the root element, connect all elements (if applicable) + int arr_sz = node->InputArraySize(input); + for (int i=-1; iIsInputKeyframable(input)) { - qWarning() << "Tried to connect input that isn't keyframable"; - return; - } - - int arr_sz = node->InputArraySize(input); - for (int i=-1; i& tracks = node->GetKeyframeTracks(input, i); - - for (int j=0; jSetKeyframeTrackColor(ref, c); - view_->SetKeyframeTrackColor(ref, c); - } - } + NodeInput input_ref(node, input, element); + int track_count = NodeValue::get_number_of_keyframe_tracks(input_ref.GetDataType()); + for (int i=0; iConnectInput(track_ref); + selected_tracks_.append(track_ref); } } @@ -325,20 +326,18 @@ void CurveWidget::InputSelectionChanged(const NodeKeyframeTrackReference& ref) selected_tracks_.clear(); - if (ref.IsValid()) { + if (ref.IsValid() && !ref.input().IsArray()) { + // This reference is a track, connect it only view_->ConnectInput(ref); selected_tracks_.append(ref); } else if (ref.input().IsValid()) { - int track_count = NodeValue::get_number_of_keyframe_tracks(ref.input().GetDataType()); - for (int i=0; iConnectInput(track_ref); - selected_tracks_.append(track_ref); - } + // This reference is a input, connect all tracks + ConnectInput(ref.input().node(), ref.input().input(), ref.input().element()); } else if (Node *node = ref.input().node()) { + // This is a node, add all inputs foreach (const QString &input, node->inputs()) { - if (!node->IsInputKeyframable(input) || node->IsInputHidden(input)) { - + if (node->IsInputKeyframable(input) && !node->IsInputHidden(input)) { + ConnectInput(node, input, -1); } } } diff --git a/app/widget/curvewidget/curvewidget.h b/app/widget/curvewidget/curvewidget.h index c27057fc8..ba6d1304b 100644 --- a/app/widget/curvewidget/curvewidget.h +++ b/app/widget/curvewidget/curvewidget.h @@ -76,9 +76,9 @@ private: void UpdateBridgeTime(const rational &time); - void ConnectNode(Node* node, bool connect); + void ConnectInput(Node *node, const QString &input, int element); - void ConnectInput(Node* node, const QString& input, bool connect); + void ConnectInputInternal(Node *node, const QString &input, int element); QHash keyframe_colors_; From 671fafb4964bd87aea5adfc79298a0afd3e36543 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 18 Dec 2021 14:03:44 -0800 Subject: [PATCH 24/34] refined some nodeview behavior --- app/widget/nodeview/nodeview.cpp | 17 ++++++++++++++--- app/widget/nodeview/nodeviewitem.h | 5 +++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 3c9a855ff..c6a3c9746 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -312,6 +312,7 @@ void NodeView::mousePressEvent(QMouseEvent *event) // Sane defaults create_edge_already_exists_ = false; create_edge_from_output_ = true; + create_edge_input_.Reset(); if (event->modifiers() & Qt::ControlModifier) { NodeViewItem *mouse_item = dynamic_cast(item); @@ -324,6 +325,9 @@ void NodeView::mousePressEvent(QMouseEvent *event) create_edge_input_ = mouse_item->GetInput(); create_edge_from_output_ = false; } + + // Highlight start item for better user experience + mouse_item->SetHighlighted(true); } } @@ -1082,7 +1086,7 @@ void NodeView::PositionNewEdge(const QPoint &pos) NodeViewItem *&opposing_item = create_edge_from_output_ ? create_edge_input_item_ : create_edge_output_item_; // Filter out connecting to self - if (item_at_cursor == source_item) { + if (item_at_cursor && item_at_cursor->GetNode() == source_item->GetNode()) { item_at_cursor = nullptr; } @@ -1112,8 +1116,10 @@ void NodeView::PositionNewEdge(const QPoint &pos) create_edge_expanded_items_.resize(i + 1); // Expand item if possible - if (item_at_cursor && item_at_cursor->CanBeExpanded() && !item_at_cursor->IsExpanded() - && (create_edge_from_output_ || !item_at_cursor->IsOutputItem())) { + if (item_at_cursor + && item_at_cursor->CanBeExpanded() + && !item_at_cursor->IsExpanded() + && create_edge_from_output_) { ExpandItem(item_at_cursor); create_edge_expanded_items_.append(item_at_cursor); } @@ -1126,6 +1132,11 @@ void NodeView::PositionNewEdge(const QPoint &pos) item_at_cursor = nullptr; } + // Filter out "output node" of the context, we assume users won't want to fetch the output of this + if (item_at_cursor && !create_edge_from_output_ && item_at_cursor->IsLabelledAsOutputOfContext()) { + item_at_cursor = nullptr; + } + // If the item has changed if (item_at_cursor != opposing_item) { // If we had a destination active, disconnect from it since the item has changed diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index 898c49a41..e236de3ea 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -128,6 +128,11 @@ public: void AddEdge(NodeViewEdge* edge); void RemoveEdge(NodeViewEdge* edge); + bool IsLabelledAsOutputOfContext() const + { + return label_as_output_; + } + void SetLabelAsOutput(bool e); void SetHighlighted(bool e); From 21165cb2ff8d83122e642720eae934cab37f4c1c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 19 Dec 2021 13:19:31 -0800 Subject: [PATCH 25/34] implemented node flags --- app/node/block/block.cpp | 2 ++ app/node/math/merge/merge.cpp | 2 ++ app/node/node.cpp | 3 ++- app/node/node.h | 17 +++++++++++++++++ app/node/output/viewer/viewer.cpp | 2 ++ app/widget/nodeparamview/nodeparamview.cpp | 14 +++++++++++--- 6 files changed, 36 insertions(+), 4 deletions(-) diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index 34f87b2e1..a726d6b0f 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -47,6 +47,8 @@ Block::Block() : IgnoreHashingFrom(kLengthInput); AddInput(kEnabledInput, NodeValue::kBoolean, true, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + + SetFlags(kDontShowInParamView); } QVector Block::Category() const diff --git a/app/node/math/merge/merge.cpp b/app/node/math/merge/merge.cpp index f5bd9ed0d..5b1582c84 100644 --- a/app/node/math/merge/merge.cpp +++ b/app/node/math/merge/merge.cpp @@ -32,6 +32,8 @@ MergeNode::MergeNode() AddInput(kBaseIn, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); AddInput(kBlendIn, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + + SetFlags(kDontShowInParamView); } Node *MergeNode::copy() const diff --git a/app/node/node.cpp b/app/node/node.cpp index ad406a8bb..e76ece9e5 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -47,7 +47,8 @@ Node::Node() : override_color_(-1), folder_(nullptr), operation_stack_(0), - cache_result_(false) + cache_result_(false), + flags_(kNone) { uuid_ = QUuid::createUuid(); } diff --git a/app/node/node.h b/app/node/node.h index 92d06e63f..299e9b5ae 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -94,6 +94,11 @@ public: kCategoryCount }; + enum Flag { + kNone = 0, + kDontShowInParamView = 0x1 + }; + Node(); virtual ~Node() override; @@ -116,6 +121,11 @@ public: const QUuid &GetUUID() const {return uuid_;} void SetUUID(const QUuid &uuid) {uuid_ = uuid;} + const uint64_t &GetFlags() const + { + return flags_; + } + /** * @brief Clear current node variables and replace them with */ @@ -1036,6 +1046,11 @@ protected: tooltip_ = s; } + void SetFlags(const uint64_t &f) + { + flags_ = f; + } + signals: /** * @brief Signal emitted when SetLabel() is called @@ -1315,6 +1330,8 @@ private: QUuid uuid_; + uint64_t flags_; + private slots: /** * @brief Slot when a keyframe's time changes to keep the keyframes correctly sorted by time diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 8e257f6e9..82e5acdc6 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -66,6 +66,8 @@ ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_stream AddStream(Track::kAudio, QVariant()); set_default_parameters(); } + + SetFlags(kDontShowInParamView); } Node *ViewerOutput::copy() const diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index f4dee8b7c..268bf4af0 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -76,8 +76,16 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : for (int i=0; isetVisible(false); - static_cast(c->titleBarWidget())->SetAddEffectButtonVisible(i == Track::kVideo || i == Track::kAudio); - static_cast(c->titleBarWidget())->SetText(Footage::GetStreamTypeName(static_cast(i))); + + NodeParamViewItemTitleBar *title_bar = static_cast(c->titleBarWidget()); + + if (i == Track::kVideo || i == Track::kAudio) { + title_bar->SetAddEffectButtonVisible(true); + title_bar->SetText(tr("%1 Nodes").arg(Footage::GetStreamTypeName(static_cast(i)))); + } else { + title_bar->SetText(tr("Other")); + } + context_items_[i] = c; param_widget_area_->AddItem(c); } @@ -259,7 +267,7 @@ void NodeParamView::SetContexts(const QVector &contexts) item->setVisible(true); for (auto it=ctx->GetContextPositions().cbegin(); it!=ctx->GetContextPositions().cend(); it++) { - if (!dynamic_cast(it.key()) && !dynamic_cast(it.key())) { + if (!(it.key()->GetFlags() & Node::kDontShowInParamView)) { AddNode(it.key(), item); } } From a8ac7edd519a9568e13922651c366c34484492fb Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 25 Dec 2021 11:11:45 -0800 Subject: [PATCH 26/34] improved nodeparamview scroll behavior --- app/widget/nodeparamview/nodeparamview.cpp | 13 ++++-------- app/widget/nodeparamview/nodeparamview.h | 24 +--------------------- 2 files changed, 5 insertions(+), 32 deletions(-) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 268bf4af0..b04f41011 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -56,8 +56,7 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : splitter->addWidget(param_scroll_area_); // Param widget - param_widget_container_ = new NodeParamViewParamContainer(); - connect(param_widget_container_, &NodeParamViewParamContainer::Resized, this, &NodeParamView::UpdateGlobalScrollBar); + param_widget_container_ = new QWidget(); param_scroll_area_->setWidget(param_widget_container_); param_widget_area_ = new NodeParamViewDockArea(); @@ -100,6 +99,8 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : // Connect scrollbars together connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); + connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::rangeChanged, vertical_scrollbar_, &QScrollBar::setRange); + connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::rangeChanged, this, &NodeParamView::UpdateGlobalScrollBar); connect(vertical_scrollbar_, &QScrollBar::valueChanged, param_scroll_area_->verticalScrollBar(), &QScrollBar::setValue); if (create_keyframe_view) { @@ -287,8 +288,6 @@ void NodeParamView::resizeEvent(QResizeEvent *event) super::resizeEvent(event); vertical_scrollbar_->setPageStep(vertical_scrollbar_->height()); - - UpdateGlobalScrollBar(); } void NodeParamView::ScaleChangedEvent(const double &scale) @@ -457,13 +456,9 @@ void NodeParamView::SortItemsInContext(NodeParamViewContext *context_item) void NodeParamView::UpdateGlobalScrollBar() { - int height_offscreen = param_widget_container_->height() + scrollbar()->height(); - if (keyframe_view_) { - keyframe_view_->SetMaxScroll(height_offscreen + 2000); + keyframe_view_->SetMaxScroll(param_widget_container_->height() - ruler()->height()); } - - vertical_scrollbar_->setRange(0, height_offscreen - param_scroll_area_->height()); } void NodeParamView::PinNode(bool pin) diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index b7eb50edd..d9f7985de 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -33,28 +33,6 @@ namespace olive { -class NodeParamViewParamContainer : public QWidget -{ - Q_OBJECT -public: - NodeParamViewParamContainer(QWidget* parent = nullptr) : - QWidget(parent) - { - } - -protected: - virtual void resizeEvent(QResizeEvent *event) override - { - QWidget::resizeEvent(event); - - emit Resized(event->size().height()); - } - -signals: - void Resized(int new_height); - -}; - class NodeParamView : public TimeBasedWidget { Q_OBJECT @@ -129,7 +107,7 @@ private: QScrollArea* param_scroll_area_; - NodeParamViewParamContainer* param_widget_container_; + QWidget* param_widget_container_; NodeParamViewDockArea* param_widget_area_; From 5223483e8e1c8f513114605cda70bf6a286d3216 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 27 Dec 2021 15:16:49 -0800 Subject: [PATCH 27/34] work towards group editor --- app/dialog/nodegroup/nodegroupdialog.cpp | 270 +++++++++++++++++++-- app/dialog/nodegroup/nodegroupdialog.h | 23 +- app/node/graph.cpp | 4 - app/node/group/group.cpp | 42 +--- app/node/group/group.h | 70 ------ app/panel/node/node.cpp | 32 +-- app/panel/node/node.h | 41 ++-- app/panel/param/param.cpp | 4 +- app/panel/param/param.h | 5 + app/render/previewautocacher.cpp | 5 + app/undo/undocommand.cpp | 38 ++- app/undo/undocommand.h | 6 +- app/widget/nodeparamview/nodeparamview.cpp | 18 +- app/widget/nodeparamview/nodeparamview.h | 10 + app/widget/nodeview/CMakeLists.txt | 2 + app/widget/nodeview/nodeview.cpp | 38 ++- app/widget/nodeview/nodeview.h | 8 + app/widget/nodeview/nodeviewcontext.cpp | 13 +- app/widget/nodeview/nodeviewscene.cpp | 11 - app/widget/nodeview/nodeviewscene.h | 3 +- app/widget/nodeview/nodewidget.cpp | 51 ++++ app/widget/nodeview/nodewidget.h | 57 +++++ app/window/mainwindow/mainwindow.cpp | 16 -- 23 files changed, 500 insertions(+), 267 deletions(-) create mode 100644 app/widget/nodeview/nodewidget.cpp create mode 100644 app/widget/nodeview/nodewidget.h diff --git a/app/dialog/nodegroup/nodegroupdialog.cpp b/app/dialog/nodegroup/nodegroupdialog.cpp index 951c45339..90742f176 100644 --- a/app/dialog/nodegroup/nodegroupdialog.cpp +++ b/app/dialog/nodegroup/nodegroupdialog.cpp @@ -23,10 +23,12 @@ #include #include #include +#include #include -#include "widget/nodeparamview/nodeparamview.h" -#include "widget/nodeview/nodeview.h" +#include "panel/node/node.h" +#include "panel/panelmanager.h" +#include "panel/param/param.h" namespace olive { @@ -35,7 +37,7 @@ namespace olive { NodeGroupDialog::NodeGroupDialog(NodeGroup *group, QWidget *parent) : super(parent), group_(group), - parent_undo_(nullptr) + prepend_undo_(nullptr) { QGridLayout *layout = new QGridLayout(this); @@ -44,29 +46,26 @@ NodeGroupDialog::NodeGroupDialog(NodeGroup *group, QWidget *parent) : layout->addWidget(new QLabel(tr("Name:")), row, 0); name_edit_ = new QLineEdit(); + name_edit_->setText(group->GetLabel()); layout->addWidget(name_edit_, row, 1); row++; - QSplitter *splitter = new QSplitter(Qt::Horizontal); + QMainWindow *splitter = new QMainWindow(); + QWidget *cw = new QWidget(); + cw->setFixedSize(0, 0); + splitter->setCentralWidget(cw); layout->addWidget(splitter, row, 0, 1, 2); - NodeParamView *param_view = new NodeParamView(false); - param_view->SetCreateCheckBoxes(kCheckBoxesOnNonConnected); - splitter->addWidget(param_view); + ParamPanel *param_view = PanelManager::instance()->CreatePanel(splitter); + param_view->GetParamView()->SetIgnoreNodeFlags(true); + param_view->GetParamView()->SetCreateCheckBoxes(kCheckBoxesOnNonConnected); + splitter->addDockWidget(Qt::LeftDockWidgetArea, param_view); - NodeView *node_view = new NodeView(); - node_view->SetContexts({group}); - QMetaObject::invokeMethod(node_view, &NodeView::CenterOnItemsBoundingRect, Qt::QueuedConnection); - splitter->addWidget(node_view); - - splitter->setSizes({splitter->width() / 4, splitter->width() / 4 * 3}); - - for (auto it=group->GetInputPassthroughs().cbegin(); it!=group->GetInputPassthroughs().cend(); it++) { - param_view->SetInputChecked(it.value(), true); - } - - param_view->SetContexts({group}); + NodePanel *node_view = PanelManager::instance()->CreatePanel(splitter); + node_view->GetNodeWidget()->view()->OverrideUndoStack(&undo_stack_); + QMetaObject::invokeMethod(node_view->GetNodeWidget()->view(), &NodeView::CenterOnItemsBoundingRect, Qt::QueuedConnection); + splitter->addDockWidget(Qt::RightDockWidgetArea, node_view); row++; @@ -76,24 +75,245 @@ NodeGroupDialog::NodeGroupDialog(NodeGroup *group, QWidget *parent) : connect(btns, &QDialogButtonBox::rejected, this, &NodeGroupDialog::reject); layout->addWidget(btns, row, 0, 1, 2); - setWindowTitle(tr("Group Editor")); + setWindowTitle(tr("Group Editor - %1").arg(group->GetLabelOrName())); + + // Create a project + copied_project_ = new Project(); + copied_project_->setParent(this); + + // Copy project nodes + // NOTE: I hate this. What might be better is to fold the "ColorManager" and "ProjectSettings" + // nodes into "Project" inputs and make "Project" a node too. + for (int i=0; inodes().size(); i++) { + Node *ours = copied_project_->nodes().at(i); + Node *theirs = group_->project()->nodes().at(i); + + copy_subnodes_.insert(theirs, ours); + Node::CopyInputs(ours, theirs, false); + } + + // Copy group and nodes + copy_group_ = static_cast(group_->copy()); + copy_group_->setParent(copied_project_); + + // Copy subnodes with positions + const Node::PositionMap &map = group_->GetContextPositions(); + for (auto it=map.cbegin(); it!=map.cend(); it++) { + Node *copy = it.key()->copy(); + copy->SetUUID(it.key()->GetUUID()); + copy->setParent(copied_project_); + + copy_group_->SetNodePositionInContext(copy, it.value()); + copy_subnodes_.insert(it.key(), copy); + } + + for (auto it=map.cbegin(); it!=map.cend(); it++) { + Node::CopyInputs(it.key(), copy_subnodes_.value(it.key()), false); + } + + // Copy edges + for (auto it=copy_subnodes_.cbegin(); it!=copy_subnodes_.cend(); it++) { + Node *src = it.key(); + Node *cpy = it.value(); + + for (auto jt=src->input_connections().cbegin(); jt!=src->input_connections().cend(); jt++) { + if (Node *cpy_output = copy_subnodes_.value(jt->second)) { + Node::ConnectEdge(cpy_output, NodeInput(cpy, jt->first.input(), jt->first.element())); + copied_edges_.append({cpy_output, NodeInput(cpy, jt->first.input(), jt->first.element())}); + } + } + } + + node_view->SetContexts({copy_group_}); + + for (auto it=group_->GetInputPassthroughs().cbegin(); it!=group_->GetInputPassthroughs().cend(); it++) { + const NodeInput &src_input = it.value(); + NodeInput copy_input(copy_subnodes_.value(src_input.node()), src_input.input(), src_input.element()); + param_view->GetParamView()->SetInputChecked(copy_input, true); + } + + param_view->SetContexts({copy_group_}); } void NodeGroupDialog::accept() { + // First, validate if a connection, deleted node, or disabled passthrough is going to disconnect + // something elsewhere in the group. Ask the user to confirm if so. + + // Detect removed nodes + QVector nodes_to_delete = GetNodesToDelete(); + + // Detect new connections + QVector edges_to_connect = GetNewConnections(); + + // Warn user if operation will disconnect a node outside the group + if (OperationWillAffectOutsideGroup(nodes_to_delete, edges_to_connect)) { + if (QMessageBox::question(this, QString(), tr("This operation will disconnect nodes outside of this group. Do you wish to continue?"), QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) { + return; + } + } + + NodeViewDeleteCommand *delete_command = new NodeViewDeleteCommand(); + + // Remove deleted nodes + foreach (Node *n, nodes_to_delete) { + delete_command->AddNode(n, group_); + } + + // Disconnect old edges + foreach (const Node::OutputConnection &edge, copied_edges_) { + bool found = false; + const NodeInput ©_input = edge.second; + + for (auto it=edge.first->output_connections().cbegin(); it!=edge.first->output_connections().cend(); it++) { + if (it->second == copy_input) { + found = true; + break; + } + } + + if (!found) { + // Edge has been disconnected + delete_command->AddEdge(copy_subnodes_.key(edge.first), NodeInput(copy_subnodes_.key(copy_input.node()), copy_input.input(), copy_input.element())); + } + } + + delete_command->redo_now(); + + // Add new nodes + MultiUndoCommand *add_command = new MultiUndoCommand(); + for (auto it=copy_group_->GetContextPositions().cbegin(); it!=copy_group_->GetContextPositions().cend(); it++) { + Node *n = it.key(); + Node *original = copy_subnodes_.key(n); + if (!original) { + // If it's a new node that isn't even in the project yet, add it + original = n->copy(); + Node::CopyInputs(n, original, false); + add_command->add_child(new NodeAddCommand(group_->parent(), original)); + copy_subnodes_.insert(original, n); + } + + // Update position in context + add_command->add_child(new NodeSetPositionCommand(original, group_, it.value())); + } + add_command->redo_now(); + + // Connect new edges + MultiUndoCommand *connect_command = new MultiUndoCommand(); + edges_to_connect = GetNewConnections(); // Update list with new nodes made above if necessary + foreach (const Node::OutputConnection &edge, edges_to_connect) { + connect_command->add_child(new NodeEdgeAddCommand(edge.first, edge.second)); + } + connect_command->redo_now(); + MultiUndoCommand *command = new MultiUndoCommand(); + if (prepend_undo_) { + command->add_child(prepend_undo_); + } + + command->add_child(delete_command); + command->add_child(add_command); + command->add_child(connect_command); + + // Update group name if (name_edit_->text() != group_->GetCustomName()) { command->add_child(new NodeGroupSetCustomNameCommand(group_, name_edit_->text())); } - if (parent_undo_) { - parent_undo_->add_child(command); - } else { - Core::instance()->undo_stack()->push(command); - } + Core::instance()->undo_stack()->push(command); super::accept(); } +void NodeGroupDialog::reject() +{ + if (prepend_undo_) { + prepend_undo_->undo_now(); + delete prepend_undo_; + } + + super::reject(); +} + +QVector NodeGroupDialog::GetNodesToDelete() +{ + QVector nodes_to_delete; + + for (auto it=group_->GetContextPositions().cbegin(); it!=group_->GetContextPositions().cend(); it++) { + Node *original = it.key(); + Node *copy = copy_subnodes_.value(original); + + if (!copy_group_->ContextContainsNode(copy)) { + nodes_to_delete.append(original); + } + } + + return nodes_to_delete; +} + +QVector NodeGroupDialog::GetNewConnections() +{ + QVector edges_to_connect; + + for (auto it=copy_group_->GetContextPositions().cbegin(); it!=copy_group_->GetContextPositions().cend(); it++) { + const Node::InputConnections &ic = it.key()->input_connections(); + + for (auto jt=ic.cbegin(); jt!=ic.cend(); jt++) { + // All connections inside this dialog will be valid and inside the group + const NodeInput &copied_input = jt->first; + Node *copied_output = jt->second; + + NodeInput original_input(copy_subnodes_.key(copied_input.node()), copied_input.input(), copied_input.element()); + Node *original_output = copy_subnodes_.key(copied_output); + + bool found = false; + + if (original_output) { + for (auto kt=original_output->output_connections().cbegin(); kt!=original_output->output_connections().cend(); kt++) { + if (kt->second == original_input) { + found = true; + break; + } + } + } + + if (!found) { + edges_to_connect.append({original_output, original_input}); + } + } + } + + return edges_to_connect; +} + +bool NodeGroupDialog::OperationWillAffectOutsideGroup(const QVector &deleted, const QVector &connections) +{ + // Check if a node to be deleted inputs from a node outside the group + foreach (Node *n, deleted) { + for (auto jt=n->input_connections().cbegin(); jt!=n->input_connections().cend(); jt++) { + if (!group_->ContextContainsNode(jt->second)) { + return true; + } + } + + for (auto jt=n->output_connections().cbegin(); jt!=n->output_connections().cend(); jt++) { + if (!group_->ContextContainsNode(jt->second.node())) { + return true; + } + } + } + + // Check if a new connection will overwrite a connection from outside the group + foreach (const Node::OutputConnection &edge, connections) { + if (Node *current_conn = edge.second.GetConnectedOutput()) { + if (!group_->ContextContainsNode(current_conn)) { + return true; + } + } + } + + return false; +} + } diff --git a/app/dialog/nodegroup/nodegroupdialog.h b/app/dialog/nodegroup/nodegroupdialog.h index 741f0ac55..d18b7fd10 100644 --- a/app/dialog/nodegroup/nodegroupdialog.h +++ b/app/dialog/nodegroup/nodegroupdialog.h @@ -23,8 +23,10 @@ #include #include +#include #include "node/group/group.h" +#include "undo/undostack.h" namespace olive { @@ -34,22 +36,37 @@ class NodeGroupDialog : public QDialog public: explicit NodeGroupDialog(NodeGroup *group, QWidget *parent = nullptr); - void SetParentUndoCommand(MultiUndoCommand *c) + void PrependUndoCommand(MultiUndoCommand *c) { - parent_undo_ = c; + prepend_undo_ = c; } public slots: virtual void accept() override; + virtual void reject() override; + signals: private: + QVector GetNodesToDelete(); + + QVector GetNewConnections(); + + bool OperationWillAffectOutsideGroup(const QVector &deleted, const QVector &connections); + NodeGroup *group_; QLineEdit *name_edit_; - MultiUndoCommand *parent_undo_; + MultiUndoCommand *prepend_undo_; + + Project *copied_project_; + NodeGroup *copy_group_; + QMap copy_subnodes_; + QVector copied_edges_; + + UndoStack undo_stack_; }; diff --git a/app/node/graph.cpp b/app/node/graph.cpp index 024cebf98..f9a68c500 100644 --- a/app/node/graph.cpp +++ b/app/node/graph.cpp @@ -105,10 +105,6 @@ void NodeGraph::childEvent(QChildEvent *event) // Remove from any contexts foreach (Node *context, node_children_) { context->RemoveNodeFromContext(node); - - if (NodeGroup *group = dynamic_cast(context)) { - group->RemoveNode(node); - } } } } diff --git a/app/node/group/group.cpp b/app/node/group/group.cpp index f8e44790f..be92d9be8 100644 --- a/app/node/group/group.cpp +++ b/app/node/group/group.cpp @@ -55,28 +55,14 @@ QString NodeGroup::Description() const void NodeGroup::Retranslate() { - foreach (Node *n, nodes_) { - n->Retranslate(); - } -} - -void NodeGroup::AddNode(Node *node) -{ - nodes_.append(node); - - emit NodeAddedToGroup(node); -} - -void NodeGroup::RemoveNode(Node *node) -{ - if (nodes_.removeOne(node)) { - emit NodeRemovedFromGroup(node); + for (auto it=GetContextPositions().cbegin(); it!=GetContextPositions().cend(); it++) { + it.key()->Retranslate(); } } void NodeGroup::AddInputPassthrough(const NodeInput &input) { - Q_ASSERT(nodes_.contains(input.node())); + Q_ASSERT(ContextContainsNode(input.node())); for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) { if (it.value() == input) { @@ -109,7 +95,7 @@ void NodeGroup::RemoveInputPassthrough(const NodeInput &input) void NodeGroup::SetOutputPassthrough(Node *node) { - Q_ASSERT(!node || nodes_.contains(node)); + Q_ASSERT(!node || ContextContainsNode(node)); output_passthrough_ = node; @@ -145,16 +131,6 @@ QString NodeGroup::GetInputName(const QString &id) const return input_passthroughs_.value(id).name(); } -void NodeAddToGroupCommand::redo() -{ - group_->AddNode(node_); -} - -void NodeAddToGroupCommand::undo() -{ - group_->RemoveNode(node_); -} - void NodeGroupSetCustomNameCommand::redo() { old_name_ = group_->GetCustomName(); @@ -183,16 +159,6 @@ void NodeGroupAddInputPassthrough::undo() } } -void NodeRemoveFromGroupCommand::redo() -{ - group_->RemoveNode(node_); -} - -void NodeRemoveFromGroupCommand::undo() -{ - group_->AddNode(node_); -} - void NodeGroupSetOutputPassthrough::redo() { old_output_ = group_->GetOutputPassthrough(); diff --git a/app/node/group/group.h b/app/node/group/group.h index 31e07ab07..d023a7416 100644 --- a/app/node/group/group.h +++ b/app/node/group/group.h @@ -41,20 +41,6 @@ public: virtual void Retranslate() override; - void AddNode(Node *node); - - void RemoveNode(Node *node); - - bool ContainsNode(Node *node) const - { - return nodes_.contains(node); - } - - const QVector &GetNodes() const - { - return nodes_; - } - void AddInputPassthrough(const NodeInput &input); void RemoveInputPassthrough(const NodeInput &input); @@ -96,10 +82,6 @@ public: virtual QString GetInputName(const QString& id) const override; signals: - void NodeAddedToGroup(Node *node); - - void NodeRemovedFromGroup(Node *node); - void InputPassthroughAdded(NodeGroup *group, const NodeInput &input); void InputPassthroughRemoved(NodeGroup *group, const NodeInput &input); @@ -107,8 +89,6 @@ signals: void OutputPassthroughChanged(NodeGroup *group, Node *output); private: - QVector nodes_; - QHash input_passthroughs_; Node *output_passthrough_; @@ -117,56 +97,6 @@ private: }; -class NodeAddToGroupCommand : public UndoCommand -{ -public: - NodeAddToGroupCommand(Node *node, NodeGroup *group) : - node_(node), - group_(group) - {} - - virtual Project * GetRelevantProject() const override - { - return node_->project(); - } - -protected: - virtual void redo() override; - - virtual void undo() override; - -private: - Node *node_; - - NodeGroup *group_; - -}; - -class NodeRemoveFromGroupCommand : public UndoCommand -{ -public: - NodeRemoveFromGroupCommand(Node *node, NodeGroup *group) : - node_(node), - group_(group) - {} - - virtual Project * GetRelevantProject() const override - { - return node_->project(); - } - -protected: - virtual void redo() override; - - virtual void undo() override; - -private: - Node *node_; - - NodeGroup *group_; - -}; - class NodeGroupSetCustomNameCommand : public UndoCommand { public: diff --git a/app/panel/node/node.cpp b/app/panel/node/node.cpp index 3f0e3a4a2..6e4b842e6 100644 --- a/app/panel/node/node.cpp +++ b/app/panel/node/node.cpp @@ -20,40 +20,20 @@ #include "node.h" -#include - namespace olive { NodePanel::NodePanel(QWidget *parent) : PanelWidget(QStringLiteral("NodePanel"), parent) { - QWidget *outer_widget = new QWidget(this); + node_widget_ = new NodeWidget(); + connect(this, &NodePanel::visibilityChanged, node_widget_->view(), &NodeView::CenterOnItemsBoundingRect); - QVBoxLayout *outer_layout = new QVBoxLayout(outer_widget); - outer_layout->setMargin(0); - - toolbar_ = new NodeViewToolBar(); - outer_layout->addWidget(toolbar_); - - // Create NodeView widget - node_view_ = new NodeView(this); - outer_layout->addWidget(node_view_); - connect(this, &NodePanel::visibilityChanged, node_view_, &NodeView::CenterOnItemsBoundingRect); - - // Connect toolbar to NodeView - connect(toolbar_, &NodeViewToolBar::MiniMapEnabledToggled, node_view_, &NodeView::SetMiniMapEnabled); - connect(toolbar_, &NodeViewToolBar::AddNodeClicked, node_view_, &NodeView::ShowAddMenu); - - // Set defaults - toolbar_->SetMiniMapEnabled(true); - node_view_->SetMiniMapEnabled(true); - - // Connect node view signals to this panel - connect(node_view_, &NodeView::NodesSelected, this, &NodePanel::NodesSelected); - connect(node_view_, &NodeView::NodesDeselected, this, &NodePanel::NodesDeselected); + // Connect node view signals to this panel - MAY REMOVE + connect(node_widget_->view(), &NodeView::NodesSelected, this, &NodePanel::NodesSelected); + connect(node_widget_->view(), &NodeView::NodesDeselected, this, &NodePanel::NodesDeselected); // Set it as the main widget of this panel - SetWidgetWithPadding(outer_widget); + SetWidgetWithPadding(node_widget_); // Set strings Retranslate(); diff --git a/app/panel/node/node.h b/app/panel/node/node.h index 20eccc79a..083445029 100644 --- a/app/panel/node/node.h +++ b/app/panel/node/node.h @@ -21,8 +21,7 @@ #ifndef NODEPANEL_H #define NODEPANEL_H -#include "widget/nodeview/nodeview.h" -#include "widget/nodeview/nodeviewtoolbar.h" +#include "widget/nodeview/nodewidget.h" #include "widget/panel/panel.h" namespace olive { @@ -36,76 +35,80 @@ class NodePanel : public PanelWidget public: NodePanel(QWidget* parent); + NodeWidget *GetNodeWidget() const + { + return node_widget_; + } + void SetContexts(const QVector &nodes) { - node_view_->SetContexts(nodes); - toolbar_->setEnabled(!nodes.isEmpty()); + node_widget_->SetContexts(nodes); } void CloseContextsBelongingToProject(Project *project) { - node_view_->CloseContextsBelongingToProject(project); + node_widget_->view()->CloseContextsBelongingToProject(project); } const QVector &GetCurrentContexts() const { - return node_view_->GetCurrentContexts(); + return node_widget_->view()->GetCurrentContexts(); } virtual void SelectAll() override { - node_view_->SelectAll(); + node_widget_->view()->SelectAll(); } virtual void DeselectAll() override { - node_view_->DeselectAll(); + node_widget_->view()->DeselectAll(); } virtual void DeleteSelected() override { - node_view_->DeleteSelected(); + node_widget_->view()->DeleteSelected(); } virtual void CutSelected() override { - node_view_->CopySelected(true); + node_widget_->view()->CopySelected(true); } virtual void CopySelected() override { - node_view_->CopySelected(false); + node_widget_->view()->CopySelected(false); } virtual void Paste() override { - node_view_->Paste(); + node_widget_->view()->Paste(); } virtual void Duplicate() override { - node_view_->Duplicate(); + node_widget_->view()->Duplicate(); } virtual void SetColorLabel(int index) override { - node_view_->SetColorLabel(index); + node_widget_->view()->SetColorLabel(index); } virtual void ZoomIn() override { - node_view_->ZoomIn(); + node_widget_->view()->ZoomIn(); } virtual void ZoomOut() override { - node_view_->ZoomOut(); + node_widget_->view()->ZoomOut(); } public slots: void Select(const QVector& nodes, bool center_view_on_item) { - node_view_->Select(nodes, center_view_on_item); + node_widget_->view()->Select(nodes, center_view_on_item); } signals: @@ -119,9 +122,7 @@ private: SetTitle(tr("Node Editor")); } - NodeView* node_view_; - - NodeViewToolBar *toolbar_; + NodeWidget *node_widget_; }; diff --git a/app/panel/param/param.cpp b/app/panel/param/param.cpp index bf44d5db3..90c2c1983 100644 --- a/app/panel/param/param.cpp +++ b/app/panel/param/param.cpp @@ -37,12 +37,12 @@ ParamPanel::ParamPanel(QWidget* parent) : void ParamPanel::SelectNodes(const QVector &nodes) { - //static_cast(GetTimeBasedWidget())->SelectNodes(nodes); + static_cast(GetTimeBasedWidget())->SelectNodes(nodes); } void ParamPanel::DeselectNodes(const QVector &nodes) { - //static_cast(GetTimeBasedWidget())->DeselectNodes(nodes); + static_cast(GetTimeBasedWidget())->DeselectNodes(nodes); } void ParamPanel::DeleteSelected() diff --git a/app/panel/param/param.h b/app/panel/param/param.h index e298ccf3e..4389bc15f 100644 --- a/app/panel/param/param.h +++ b/app/panel/param/param.h @@ -33,6 +33,11 @@ class ParamPanel : public TimeBasedPanel public: ParamPanel(QWidget* parent); + NodeParamView *GetParamView() const + { + return static_cast(GetTimeBasedWidget()); + } + public slots: void SelectNodes(const QVector& nodes); void DeselectNodes(const QVector& nodes); diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 75b78c3c5..887c8b578 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -367,6 +367,11 @@ void PreviewAutoCacher::ProcessUpdateQueue() void PreviewAutoCacher::AddNode(Node *node) { + if (dynamic_cast(node)) { + // Group nodes are just dummy nodes, no need to copy them + return; + } + // Copy node Node* copy = node->copy(); diff --git a/app/undo/undocommand.cpp b/app/undo/undocommand.cpp index 075348c15..f182f86f4 100644 --- a/app/undo/undocommand.cpp +++ b/app/undo/undocommand.cpp @@ -24,34 +24,24 @@ namespace olive { -MultiUndoCommand::MultiUndoCommand() : - done_(false) -{ -} - void MultiUndoCommand::redo() { - if (!done_) { - for (auto it=children_.cbegin(); it!=children_.cend(); it++) { - (*it)->redo_and_set_modified(); - } - done_ = true; + for (auto it=children_.cbegin(); it!=children_.cend(); it++) { + (*it)->redo_and_set_modified(); } } void MultiUndoCommand::undo() { - if (done_) { - for (auto it=children_.crbegin(); it!=children_.crend(); it++) { - (*it)->undo_and_set_modified(); - } - done_ = false; + for (auto it=children_.crbegin(); it!=children_.crend(); it++) { + (*it)->undo_and_set_modified(); } } UndoCommand::UndoCommand() { prepared_ = false; + done_ = false; } void UndoCommand::redo_and_set_modified() @@ -76,17 +66,23 @@ void UndoCommand::undo_and_set_modified() void UndoCommand::redo_now() { - if (!prepared_) { - prepare(); - prepared_ = true; - } + if (!done_) { + if (!prepared_) { + prepare(); + prepared_ = true; + } - redo(); + redo(); + done_ = true; + } } void UndoCommand::undo_now() { - undo(); + if (done_) { + undo(); + done_ = false; + } } } diff --git a/app/undo/undocommand.h b/app/undo/undocommand.h index 94d4d3e8f..bd6032da2 100644 --- a/app/undo/undocommand.h +++ b/app/undo/undocommand.h @@ -75,12 +75,14 @@ private: bool prepared_; + bool done_; + }; class MultiUndoCommand : public UndoCommand { public: - MultiUndoCommand(); + MultiUndoCommand() = default; virtual Project* GetRelevantProject() const override { @@ -109,8 +111,6 @@ protected: private: std::vector children_; - bool done_; - }; } diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index b04f41011..86732d37c 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -38,7 +38,8 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : last_scroll_val_(0), focused_node_(nullptr), create_checkboxes_(kNoCheckBoxes), - time_target_(nullptr) + time_target_(nullptr), + ignore_flags_(false) { // Create horizontal layout to place scroll area in (and keyframe editing eventually) QHBoxLayout* layout = new QHBoxLayout(this); @@ -268,7 +269,7 @@ void NodeParamView::SetContexts(const QVector &contexts) item->setVisible(true); for (auto it=ctx->GetContextPositions().cbegin(); it!=ctx->GetContextPositions().cend(); it++) { - if (!(it.key()->GetFlags() & Node::kDontShowInParamView)) { + if (!(it.key()->GetFlags() & Node::kDontShowInParamView) || ignore_flags_) { AddNode(it.key(), item); } } @@ -351,6 +352,16 @@ void NodeParamView::DeleteSelected() } } +void NodeParamView::SelectNodes(const QVector &nodes) +{ + // Do nothing, this is a placeholder if we ever need this to do anything in the future +} + +void NodeParamView::DeselectNodes(const QVector &nodes) +{ + // Do nothing, this is a placeholder if we ever need this to do anything in the future +} + void NodeParamView::UpdateItemTime(const rational &time) { foreach (NodeParamViewContext* item, context_items_) { @@ -543,6 +554,9 @@ void NodeParamView::UpdateElementY() int y = it.value()->GetElementY(ic); + // For some reason Qt's mapToGlobal doesn't seem to handle this, so we offset here + y += vertical_scrollbar_->value(); + const KeyframeView::InputConnections &input_con = connections.value(input); int use_index = i + 1; if (use_index < input_con.size()) { diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index d9f7985de..29110deca 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -69,6 +69,14 @@ public: keyframe_view_->DeselectAll(); } + void SetIgnoreNodeFlags(bool e) + { + ignore_flags_ = e; + } + + void SelectNodes(const QVector &nodes); + void DeselectNodes(const QVector &nodes); + public slots: void SetInputChecked(const NodeInput &input, bool e); @@ -123,6 +131,8 @@ private: QHash input_checked_; + bool ignore_flags_; + private slots: void UpdateGlobalScrollBar(); diff --git a/app/widget/nodeview/CMakeLists.txt b/app/widget/nodeview/CMakeLists.txt index 12512695f..cf3a2375e 100644 --- a/app/widget/nodeview/CMakeLists.txt +++ b/app/widget/nodeview/CMakeLists.txt @@ -35,5 +35,7 @@ set(OLIVE_SOURCES widget/nodeview/nodeviewtoolbar.h widget/nodeview/nodeviewundo.cpp widget/nodeview/nodeviewundo.h + widget/nodeview/nodewidget.cpp + widget/nodeview/nodewidget.h PARENT_SCOPE ) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index c6a3c9746..d0e85fc37 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -26,7 +26,6 @@ #include #include -#include "core.h" #include "dialog/nodegroup/nodegroupdialog.h" #include "nodeviewundo.h" #include "node/audio/volume/volume.h" @@ -50,6 +49,7 @@ NodeView::NodeView(QWidget *parent) : create_edge_output_item_(nullptr), create_edge_input_item_(nullptr), paste_command_(nullptr), + undo_stack_(Core::instance()->undo_stack()), scale_(1.0) { setScene(&scene_); @@ -127,7 +127,13 @@ void NodeView::ClearGraph() void NodeView::DeleteSelected() { - scene_.DeleteSelected(); + NodeViewDeleteCommand* command = new NodeViewDeleteCommand(); + + foreach (NodeViewContext *ctx, scene_.context_map()) { + ctx->DeleteSelected(command); + } + + undo_stack_->push(command); } void NodeView::SelectAll() @@ -221,7 +227,7 @@ void NodeView::SetColorLabel(int index) command->add_child(new NodeOverrideColorCommand(node, index)); } - Core::instance()->undo_stack()->push(command); + undo_stack_->push(command); } void NodeView::ZoomIn() @@ -276,7 +282,7 @@ void NodeView::keyPressEvent(QKeyEvent *event) } } } - Core::instance()->undo_stack()->pushIfHasChildren(pos_command); + undo_stack_->pushIfHasChildren(pos_command); break; } case Qt::Key_Escape: @@ -555,7 +561,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } create_edge_expanded_items_.clear(); - Core::instance()->undo_stack()->pushIfHasChildren(command); + undo_stack_->pushIfHasChildren(command); } MultiUndoCommand* command = new MultiUndoCommand(); @@ -639,7 +645,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } dragging_items_.clear(); - Core::instance()->undo_stack()->pushIfHasChildren(command); + undo_stack_->pushIfHasChildren(command); super::mouseReleaseEvent(event); } @@ -1199,7 +1205,6 @@ void NodeView::GroupNodes() DeselectAll(); foreach (Node *n, nodes_to_group) { command->add_child(new NodeRemovePositionFromContextCommand(n, context)); - command->add_child(new NodeAddToGroupCommand(n, group)); command->add_child(new NodeSetPositionCommand(n, group, context->GetNodePositionDataInContext(n))); for (auto it=n->inputs().cbegin(); it!=n->inputs().cend(); it++) { @@ -1232,14 +1237,8 @@ void NodeView::GroupNodes() command->redo_now(); NodeGroupDialog ngd(group, this); - if (ngd.exec() == QDialog::Accepted) { - // Push to stack so it can be undone (MultiUndoCommand will ignore the request to redo again) - Core::instance()->undo_stack()->push(command); - } else { - // Undo command and delete - command->undo_now(); - delete command; - } + ngd.PrependUndoCommand(command); + ngd.exec(); } void NodeView::UngroupNodes() @@ -1269,13 +1268,12 @@ void NodeView::UngroupNodes() command->add_child(new NodeRemovePositionFromContextCommand(group, context)); command->add_child(new NodeRemoveAndDisconnectCommand(group)); - foreach (Node *n, group->GetNodes()) { - command->add_child(new NodeRemovePositionFromContextCommand(n, group)); - command->add_child(new NodeRemoveFromGroupCommand(n, group)); - command->add_child(new NodeSetPositionCommand(n, context, group->GetNodePositionDataInContext(n))); + for (auto it=group->GetContextPositions().cbegin(); it!=group->GetContextPositions().cend(); it++) { + command->add_child(new NodeRemovePositionFromContextCommand(it.key(), group)); + command->add_child(new NodeSetPositionCommand(it.key(), context, group->GetNodePositionDataInContext(it.key()))); } - Core::instance()->undo_stack()->push(command); + undo_stack_->push(command); } void NodeView::ShowNodeProperties() diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 8e34bba99..02118ba45 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -24,6 +24,7 @@ #include #include +#include "core.h" #include "node/graph.h" #include "node/nodecopypaste.h" #include "nodeviewedge.h" @@ -76,6 +77,11 @@ public: void ZoomOut(); + void OverrideUndoStack(UndoStack *stack) + { + undo_stack_ = stack; + } + const QVector &GetCurrentContexts() const { return contexts_; @@ -189,6 +195,8 @@ private: QMap dragging_items_; + UndoStack *undo_stack_; + double scale_; static const double kMinimumScale; diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index 59c640634..8ded8fdc3 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -56,13 +56,13 @@ void NodeViewContext::AddChild(Node *node) AddNodeInternal(node, item); if (NodeGroup *group = dynamic_cast(node)) { - foreach (Node *n, group->GetNodes()) { + for (auto it=group->GetContextPositions().cbegin(); it!=group->GetContextPositions().cend(); it++) { // Use this item as the representative for all of these nodes too - AddNodeInternal(n, item); + AddNodeInternal(it.key(), item); } - connect(group, &NodeGroup::NodeAddedToGroup, this, &NodeViewContext::GroupAddedNode); - connect(group, &NodeGroup::NodeRemovedFromGroup, this, &NodeViewContext::GroupRemovedNode); + connect(group, &NodeGroup::NodeAddedToContext, this, &NodeViewContext::GroupAddedNode); + connect(group, &NodeGroup::NodeRemovedFromContext, this, &NodeViewContext::GroupRemovedNode); } UpdateRect(); @@ -78,6 +78,11 @@ void NodeViewContext::RemoveChild(Node *node) disconnect(node, &Node::InputConnected, this, &NodeViewContext::ChildInputConnected); disconnect(node, &Node::InputDisconnected, this, &NodeViewContext::ChildInputDisconnected); + if (NodeGroup *group = dynamic_cast(node)) { + disconnect(group, &NodeGroup::NodeAddedToContext, this, &NodeViewContext::GroupAddedNode); + disconnect(group, &NodeGroup::NodeRemovedFromContext, this, &NodeViewContext::GroupRemovedNode); + } + NodeViewItem *item = item_map_.take(node); // Delete edges first because the edge destructor will try to reference item (maybe that should diff --git a/app/widget/nodeview/nodeviewscene.cpp b/app/widget/nodeview/nodeviewscene.cpp index 37a226710..dc76509cc 100644 --- a/app/widget/nodeview/nodeviewscene.cpp +++ b/app/widget/nodeview/nodeviewscene.cpp @@ -58,17 +58,6 @@ void NodeViewScene::DeselectAll() } } -void NodeViewScene::DeleteSelected() -{ - NodeViewDeleteCommand* command = new NodeViewDeleteCommand(); - - foreach (NodeViewContext *ctx, context_map_) { - ctx->DeleteSelected(command); - } - - Core::instance()->undo_stack()->push(command); -} - QVector NodeViewScene::GetSelectedItems() const { QVector items; diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index 467109923..64a149bd3 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -28,6 +28,7 @@ #include "nodeviewcontext.h" #include "nodeviewedge.h" #include "nodeviewitem.h" +#include "undo/undostack.h" namespace olive { @@ -40,8 +41,6 @@ public: void SelectAll(); void DeselectAll(); - void DeleteSelected(); - QVector GetSelectedItems() const; const QHash &context_map() const diff --git a/app/widget/nodeview/nodewidget.cpp b/app/widget/nodeview/nodewidget.cpp new file mode 100644 index 000000000..4fdb2dd06 --- /dev/null +++ b/app/widget/nodeview/nodewidget.cpp @@ -0,0 +1,51 @@ +/*** + + 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 "nodewidget.h" + +#include + +namespace olive { + +NodeWidget::NodeWidget(QWidget *parent) : + QWidget(parent) +{ + QVBoxLayout *outer_layout = new QVBoxLayout(this); + outer_layout->setMargin(0); + + toolbar_ = new NodeViewToolBar(); + outer_layout->addWidget(toolbar_); + + // Create NodeView widget + node_view_ = new NodeView(this); + outer_layout->addWidget(node_view_); + + // Connect toolbar to NodeView + connect(toolbar_, &NodeViewToolBar::MiniMapEnabledToggled, node_view_, &NodeView::SetMiniMapEnabled); + connect(toolbar_, &NodeViewToolBar::AddNodeClicked, node_view_, &NodeView::ShowAddMenu); + + // Set defaults + toolbar_->SetMiniMapEnabled(true); + node_view_->SetMiniMapEnabled(true); + + setSizePolicy(node_view_->sizePolicy()); +} + +} diff --git a/app/widget/nodeview/nodewidget.h b/app/widget/nodeview/nodewidget.h new file mode 100644 index 000000000..47762f499 --- /dev/null +++ b/app/widget/nodeview/nodewidget.h @@ -0,0 +1,57 @@ +/*** + + 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 NODEWIDGET_H +#define NODEWIDGET_H + +#include + +#include "nodeview.h" +#include "nodeviewtoolbar.h" + +namespace olive { + +class NodeWidget : public QWidget +{ + Q_OBJECT +public: + NodeWidget(QWidget *parent = nullptr); + + NodeView *view() const + { + return node_view_; + } + + void SetContexts(const QVector &nodes) + { + node_view_->SetContexts(nodes); + toolbar_->setEnabled(!nodes.isEmpty()); + } + +private: + NodeView *node_view_; + + NodeViewToolBar *toolbar_; + +}; + +} + +#endif // NODEWIDGET_H diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index bceb6a935..61eb7ef92 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -220,11 +220,6 @@ void MainWindow::FolderOpen(Project* p, Folder *i, bool floating) { ProjectPanel* panel = PanelManager::instance()->CreatePanel(this); - // Set custom name to distinguish it from regular ProjectPanels - panel->setObjectName(QStringLiteral("FolderPanel")); - - SetUniquePanelID(panel, folder_panels_); - panel->set_project(p); panel->set_root(i); @@ -823,8 +818,6 @@ T *MainWindow::AppendPanelInternal(QList& list) { T* panel = PanelManager::instance()->CreatePanel(this); - SetUniquePanelID(panel, list); - if (!list.isEmpty()) { tabifyDockWidget(list.last(), panel); } @@ -841,20 +834,11 @@ T *MainWindow::AppendPanelInternal(QList& list) return panel; } -template -void MainWindow::SetUniquePanelID(T *panel, const QList &list) -{ - // Set unique object name so it can be identified by QMainWindow's save and restore state functions - panel->setObjectName(panel->objectName().append(QString::number(list.size()))); -} - template T *MainWindow::AppendFloatingPanelInternal(QList &list) { T* panel = PanelManager::instance()->CreatePanel(this); - SetUniquePanelID(panel, list); - panel->setFloating(true); panel->show(); From f8ac3f4e40c19ab4de17cd73c413fc225537ebaf Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 27 Dec 2021 20:30:00 -0800 Subject: [PATCH 28/34] refactor node gizmo release --- app/node/distort/crop/cropdistortnode.cpp | 4 +- app/node/distort/crop/cropdistortnode.h | 2 +- .../transform/transformdistortnode.cpp | 4 +- .../distort/transform/transformdistortnode.h | 2 +- app/node/generator/shape/shapenodebase.cpp | 4 +- app/node/generator/shape/shapenodebase.h | 2 +- app/node/node.cpp | 2 +- app/node/node.h | 2 +- app/panel/panelmanager.cpp | 41 +++++++++++--- app/panel/panelmanager.h | 56 ++++--------------- app/widget/viewer/viewerdisplay.cpp | 4 +- app/window/mainwindow/mainwindow.cpp | 26 ++++----- 12 files changed, 68 insertions(+), 81 deletions(-) diff --git a/app/node/distort/crop/cropdistortnode.cpp b/app/node/distort/crop/cropdistortnode.cpp index 5018a8f5a..009e05310 100644 --- a/app/node/distort/crop/cropdistortnode.cpp +++ b/app/node/distort/crop/cropdistortnode.cpp @@ -233,13 +233,11 @@ void CropDistortNode::GizmoMove(const QPointF &p, const rational &time, const Qt } } -void CropDistortNode::GizmoRelease() +void CropDistortNode::GizmoRelease(MultiUndoCommand *command) { - MultiUndoCommand *command = new MultiUndoCommand(); for (NodeInputDragger& i : gizmo_dragger_) { i.End(command); } - Core::instance()->undo_stack()->push(command); gizmo_dragger_.clear(); gizmo_start_.clear(); diff --git a/app/node/distort/crop/cropdistortnode.h b/app/node/distort/crop/cropdistortnode.h index a20998a7d..24f2c0b50 100644 --- a/app/node/distort/crop/cropdistortnode.h +++ b/app/node/distort/crop/cropdistortnode.h @@ -76,7 +76,7 @@ public: virtual bool GizmoPress(const NodeValueRow& row, const NodeGlobals &globals, const QPointF &p) override; virtual void GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) override; - virtual void GizmoRelease() override; + virtual void GizmoRelease(MultiUndoCommand *command) override; static const QString kTextureInput; static const QString kLeftInput; diff --git a/app/node/distort/transform/transformdistortnode.cpp b/app/node/distort/transform/transformdistortnode.cpp index df6266a7a..483b9f001 100644 --- a/app/node/distort/transform/transformdistortnode.cpp +++ b/app/node/distort/transform/transformdistortnode.cpp @@ -301,13 +301,11 @@ void TransformDistortNode::GizmoMove(const QPointF &p, const rational &time, con } } -void TransformDistortNode::GizmoRelease() +void TransformDistortNode::GizmoRelease(MultiUndoCommand *command) { - MultiUndoCommand *command = new MultiUndoCommand(); for (NodeInputDragger& i : gizmo_dragger_) { i.End(command); } - Core::instance()->undo_stack()->push(command); gizmo_dragger_.clear(); gizmo_start_.clear(); diff --git a/app/node/distort/transform/transformdistortnode.h b/app/node/distort/transform/transformdistortnode.h index 17bf541a0..6bfe5e2f3 100644 --- a/app/node/distort/transform/transformdistortnode.h +++ b/app/node/distort/transform/transformdistortnode.h @@ -78,7 +78,7 @@ public: virtual bool GizmoPress(const NodeValueRow &row, const NodeGlobals &globals, const QPointF &p) override; virtual void GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) override; - virtual void GizmoRelease() override; + virtual void GizmoRelease(MultiUndoCommand *command) override; enum AutoScaleType { kAutoScaleNone, diff --git a/app/node/generator/shape/shapenodebase.cpp b/app/node/generator/shape/shapenodebase.cpp index 8211bb180..d5c03b0ac 100644 --- a/app/node/generator/shape/shapenodebase.cpp +++ b/app/node/generator/shape/shapenodebase.cpp @@ -297,13 +297,11 @@ void ShapeNodeBase::GizmoMove(const QPointF &p, const rational &time, const Qt:: } } -void ShapeNodeBase::GizmoRelease() +void ShapeNodeBase::GizmoRelease(MultiUndoCommand *command) { - MultiUndoCommand *command = new MultiUndoCommand(); for (NodeInputDragger& i : gizmo_dragger_) { i.End(command); } - Core::instance()->undo_stack()->push(command); gizmo_dragger_.clear(); } diff --git a/app/node/generator/shape/shapenodebase.h b/app/node/generator/shape/shapenodebase.h index 819d4cb94..7337d5b6c 100644 --- a/app/node/generator/shape/shapenodebase.h +++ b/app/node/generator/shape/shapenodebase.h @@ -49,7 +49,7 @@ public: virtual bool GizmoPress(const NodeValueRow& row, const NodeGlobals &globals, const QPointF &p) override; virtual void GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) override; - virtual void GizmoRelease() override; + virtual void GizmoRelease(MultiUndoCommand *command) override; private: static QVector2D GenerateGizmoAnchor(const QVector2D &pos, const QVector2D &size, int drag, QVector2D *pt); diff --git a/app/node/node.cpp b/app/node/node.cpp index e76ece9e5..90ee197f9 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -1504,7 +1504,7 @@ void Node::GizmoMove(const QPointF &, const rational&, const Qt::KeyboardModifie { } -void Node::GizmoRelease() +void Node::GizmoRelease(MultiUndoCommand *) { } diff --git a/app/node/node.h b/app/node/node.h index 299e9b5ae..45516fbb8 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -855,7 +855,7 @@ public: virtual bool GizmoPress(const NodeValueRow& row, const NodeGlobals &globals, const QPointF& p); virtual void GizmoMove(const QPointF& p, const rational &time, const Qt::KeyboardModifiers &modifiers); - virtual void GizmoRelease(); + virtual void GizmoRelease(MultiUndoCommand *command); const QString& GetLabel() const; void SetLabel(const QString& s); diff --git a/app/panel/panelmanager.cpp b/app/panel/panelmanager.cpp index 779c2be35..e3959bfbf 100644 --- a/app/panel/panelmanager.cpp +++ b/app/panel/panelmanager.cpp @@ -95,6 +95,40 @@ PanelManager *PanelManager::instance() return instance_; } +void PanelManager::RegisterPanel(PanelWidget *panel) +{ + // Add panel to the bottom of the focus history + focus_history_.append(panel); + + panel->SetMovementLocked(locked_); + + // Get panel parent (it's assumed it has one) + QWidget *parent = panel->parentWidget(); + + // Sane default for panel size + panel->resize(parent->size() / 3); + + // We're about to center the panel relative to the parent (usually the main window), but for some + // reason this requires the panel to be shown first. + panel->show(); + + // Center the panel relative to the parent + QPoint parent_center = panel->mapFromGlobal(parent->mapToGlobal(parent->rect().center())); + QPoint panel_center = panel->rect().center(); + panel->move(parent_center - panel_center); + + if (focus_history_.size() == 1) { + // This is the first panel, focus it + panel->SetBorderVisible(true); + emit FocusedPanelChanged(panel); + } +} + +void PanelManager::UnregisterPanel(PanelWidget *panel) +{ + focus_history_.removeOne(panel); +} + void PanelManager::FocusChanged(QWidget *old, QWidget *now) { Q_UNUSED(old) @@ -151,11 +185,4 @@ void PanelManager::SetPanelsLocked(bool locked) locked_ = locked; } -void PanelManager::PanelDestroyed() -{ - PanelWidget* panel = static_cast(sender()); - - focus_history_.removeOne(panel); -} - } diff --git a/app/panel/panelmanager.h b/app/panel/panelmanager.h index 50dfefce6..a71a312a3 100644 --- a/app/panel/panelmanager.h +++ b/app/panel/panelmanager.h @@ -84,12 +84,6 @@ public: */ T* MostRecentlyFocused(); - template - /** - * @brief Create a panel - */ - T* CreatePanel(QWidget* parent); - /** * @brief Get whether panels are currently prevented from moving */ @@ -118,6 +112,16 @@ public: */ QList GetPanelsOfType(); + /** + * @brief Panel should call this upon construction so it can be kept track of + */ + void RegisterPanel(PanelWidget *panel); + + /** + * @brief Panel should call this upon destruction so no invalid pointers will be kept for it + */ + void UnregisterPanel(PanelWidget *panel); + public slots: /** * @brief Connect this to a QApplication's SIGNAL(focusChanged()) @@ -153,48 +157,8 @@ private: */ static PanelManager* instance_; -private slots: - /** - * @brief Processing if a panel gets deleted - */ - void PanelDestroyed(); - }; -template -T *PanelManager::CreatePanel(QWidget *parent) -{ - T* panel = new T(parent); - - // Add panel to the bottom of the focus history - focus_history_.append(panel); - - panel->SetMovementLocked(locked_); - - // Sane default for panel size - panel->resize(parent->size() / 3); - - // We're about to center the panel relative to the parent (usually the main window), but for some - // reason this requires the panel to be shown first. - panel->show(); - - // Center the panel relative to the parent - QPoint parent_center = panel->mapFromGlobal(parent->mapToGlobal(parent->rect().center())); - QPoint panel_center = panel->rect().center(); - panel->move(parent_center - panel_center); - - // Connect destroy signal so we can remove it from focus history - connect(panel, &PanelWidget::destroyed, this, &PanelManager::PanelDestroyed, Qt::DirectConnection); - - if (focus_history_.size() == 1) { - // This is the first panel, focus it - panel->SetBorderVisible(true); - emit FocusedPanelChanged(panel); - } - - return panel; -} - template T* PanelManager::MostRecentlyFocused() { diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 3e21bf120..4c9b21654 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -271,7 +271,9 @@ void ViewerDisplayWidget::mouseReleaseEvent(QMouseEvent *event) } else if (gizmo_click_) { // Handle gizmo - gizmos_->GizmoRelease(); + MultiUndoCommand *command = new MultiUndoCommand(); + gizmos_->GizmoRelease(command); + undo_stack()->pushIfHasChildren(command); gizmo_click_ = false; } else { diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 61eb7ef92..6b4fd9883 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -78,17 +78,17 @@ MainWindow::MainWindow(QWidget *parent) : setStatusBar(status_bar); // Create standard panels - node_panel_ = PanelManager::instance()->CreatePanel(this); - footage_viewer_panel_ = PanelManager::instance()->CreatePanel(this); - param_panel_ = PanelManager::instance()->CreatePanel(this); - curve_panel_ = PanelManager::instance()->CreatePanel(this); - sequence_viewer_panel_ = PanelManager::instance()->CreatePanel(this); - pixel_sampler_panel_ = PanelManager::instance()->CreatePanel(this); + node_panel_ = new NodePanel(undo_stack_, this); + footage_viewer_panel_ = new FootageViewerPanel(undo_stack_, this); + param_panel_ = new ParamPanel(undo_stack_, this); + curve_panel_ = new CurvePanel(undo_stack_, this); + sequence_viewer_panel_ = new SequenceViewerPanel(undo_stack_, this); + pixel_sampler_panel_ = new PixelSamplerPanel(undo_stack_, this); AppendProjectPanel(); - tool_panel_ = PanelManager::instance()->CreatePanel(this); - task_man_panel_ = PanelManager::instance()->CreatePanel(this); + tool_panel_ = new ToolPanel(undo_stack_, this); + task_man_panel_ = new TaskManagerPanel(undo_stack_, this); AppendTimelinePanel(); - audio_monitor_panel_ = PanelManager::instance()->CreatePanel(this); + audio_monitor_panel_ = new AudioMonitorPanel(undo_stack_, this); // Make node-related connections connect(node_panel_, &NodePanel::NodesSelected, param_panel_, &ParamPanel::SelectNodes); @@ -218,7 +218,7 @@ bool MainWindow::IsSequenceOpen(Sequence *sequence) const void MainWindow::FolderOpen(Project* p, Folder *i, bool floating) { - ProjectPanel* panel = PanelManager::instance()->CreatePanel(this); + ProjectPanel* panel = new ProjectPanel(undo_stack_, this); panel->set_project(p); panel->set_root(i); @@ -256,7 +256,7 @@ void MainWindow::OpenNodeInViewer(ViewerOutput *node) viewer_panels_.value(node)->raise(); } else { // Create a viewer for this node - ViewerPanel* viewer = PanelManager::instance()->CreatePanel(this); + ViewerPanel* viewer = new ViewerPanel(undo_stack_, this); viewer->SetSignalInsteadOfClose(true); viewer->setFloating(true); @@ -816,7 +816,7 @@ void MainWindow::showEvent(QShowEvent *e) template T *MainWindow::AppendPanelInternal(QList& list) { - T* panel = PanelManager::instance()->CreatePanel(this); + T* panel = new T(undo_stack_, this); if (!list.isEmpty()) { tabifyDockWidget(list.last(), panel); @@ -837,7 +837,7 @@ T *MainWindow::AppendPanelInternal(QList& list) template T *MainWindow::AppendFloatingPanelInternal(QList &list) { - T* panel = PanelManager::instance()->CreatePanel(this); + T* panel = new T(undo_stack_, this); panel->setFloating(true); panel->show(); From 65a1c75933a809af28074a7b0845560724df2a31 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 27 Dec 2021 20:56:04 -0800 Subject: [PATCH 29/34] clean up and remove nodegroupdialog --- app/core.cpp | 14 +- app/core.h | 2 +- app/dialog/CMakeLists.txt | 1 - app/dialog/nodegroup/CMakeLists.txt | 22 -- app/dialog/nodegroup/nodegroupdialog.cpp | 319 ------------------- app/dialog/nodegroup/nodegroupdialog.h | 75 ----- app/panel/pixelsampler/pixelsamplerpanel.cpp | 2 +- app/panel/viewer/viewer.cpp | 25 +- app/panel/viewer/viewer.h | 8 +- app/widget/nodeview/nodeview.cpp | 23 +- app/widget/nodeview/nodeview.h | 7 - app/widget/panel/panel.cpp | 9 + app/widget/panel/panel.h | 2 + app/widget/viewer/viewerdisplay.cpp | 2 +- app/window/mainwindow/mainwindow.cpp | 26 +- 15 files changed, 58 insertions(+), 479 deletions(-) delete mode 100644 app/dialog/nodegroup/CMakeLists.txt delete mode 100644 app/dialog/nodegroup/nodegroupdialog.cpp delete mode 100644 app/dialog/nodegroup/nodegroupdialog.h diff --git a/app/core.cpp b/app/core.cpp index 1db15b4fa..241d57206 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -1358,10 +1358,10 @@ void Core::SetPreferenceForRenderMode(RenderMode::Mode mode, const QString &pref Config::Current()[GetRenderModePreferencePrefix(mode, preference)] = value; } -void Core::LabelNodes(const QVector &nodes) +bool Core::LabelNodes(const QVector &nodes, MultiUndoCommand *parent) { if (nodes.isEmpty()) { - return; + return false; } bool ok; @@ -1390,8 +1390,16 @@ void Core::LabelNodes(const QVector &nodes) rename_command->AddNode(n, s); } - undo_stack_.push(rename_command); + if (parent) { + parent->add_child(rename_command); + } else { + undo_stack_.push(rename_command); + } + + return true; } + + return false; } Sequence *Core::CreateNewSequenceForProject(Project* project) const diff --git a/app/core.h b/app/core.h index 3ac9b1e70..003e9e3f3 100644 --- a/app/core.h +++ b/app/core.h @@ -253,7 +253,7 @@ public: /** * @brief Show a dialog to the user to rename a set of nodes */ - void LabelNodes(const QVector &nodes); + bool LabelNodes(const QVector &nodes, MultiUndoCommand *parent = nullptr); /** * @brief Create a new sequence named appropriately for the active project diff --git a/app/dialog/CMakeLists.txt b/app/dialog/CMakeLists.txt index afe445704..64c718d2a 100644 --- a/app/dialog/CMakeLists.txt +++ b/app/dialog/CMakeLists.txt @@ -24,7 +24,6 @@ add_subdirectory(export) add_subdirectory(footageproperties) add_subdirectory(footagerelink) add_subdirectory(keyframeproperties) -add_subdirectory(nodegroup) add_subdirectory(preferences) add_subdirectory(progress) add_subdirectory(rendercancel) diff --git a/app/dialog/nodegroup/CMakeLists.txt b/app/dialog/nodegroup/CMakeLists.txt deleted file mode 100644 index 0f48edfc9..000000000 --- a/app/dialog/nodegroup/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -# 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 . - -set(OLIVE_SOURCES - ${OLIVE_SOURCES} - dialog/nodegroup/nodegroupdialog.cpp - dialog/nodegroup/nodegroupdialog.h - PARENT_SCOPE -) diff --git a/app/dialog/nodegroup/nodegroupdialog.cpp b/app/dialog/nodegroup/nodegroupdialog.cpp deleted file mode 100644 index 90742f176..000000000 --- a/app/dialog/nodegroup/nodegroupdialog.cpp +++ /dev/null @@ -1,319 +0,0 @@ -/*** - - 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 "nodegroupdialog.h" - -#include -#include -#include -#include -#include - -#include "panel/node/node.h" -#include "panel/panelmanager.h" -#include "panel/param/param.h" - -namespace olive { - -#define super QDialog - -NodeGroupDialog::NodeGroupDialog(NodeGroup *group, QWidget *parent) : - super(parent), - group_(group), - prepend_undo_(nullptr) -{ - QGridLayout *layout = new QGridLayout(this); - - int row = 0; - - layout->addWidget(new QLabel(tr("Name:")), row, 0); - - name_edit_ = new QLineEdit(); - name_edit_->setText(group->GetLabel()); - layout->addWidget(name_edit_, row, 1); - - row++; - - QMainWindow *splitter = new QMainWindow(); - QWidget *cw = new QWidget(); - cw->setFixedSize(0, 0); - splitter->setCentralWidget(cw); - layout->addWidget(splitter, row, 0, 1, 2); - - ParamPanel *param_view = PanelManager::instance()->CreatePanel(splitter); - param_view->GetParamView()->SetIgnoreNodeFlags(true); - param_view->GetParamView()->SetCreateCheckBoxes(kCheckBoxesOnNonConnected); - splitter->addDockWidget(Qt::LeftDockWidgetArea, param_view); - - NodePanel *node_view = PanelManager::instance()->CreatePanel(splitter); - node_view->GetNodeWidget()->view()->OverrideUndoStack(&undo_stack_); - QMetaObject::invokeMethod(node_view->GetNodeWidget()->view(), &NodeView::CenterOnItemsBoundingRect, Qt::QueuedConnection); - splitter->addDockWidget(Qt::RightDockWidgetArea, node_view); - - row++; - - QDialogButtonBox *btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - btns->setCenterButtons(true); - connect(btns, &QDialogButtonBox::accepted, this, &NodeGroupDialog::accept); - connect(btns, &QDialogButtonBox::rejected, this, &NodeGroupDialog::reject); - layout->addWidget(btns, row, 0, 1, 2); - - setWindowTitle(tr("Group Editor - %1").arg(group->GetLabelOrName())); - - // Create a project - copied_project_ = new Project(); - copied_project_->setParent(this); - - // Copy project nodes - // NOTE: I hate this. What might be better is to fold the "ColorManager" and "ProjectSettings" - // nodes into "Project" inputs and make "Project" a node too. - for (int i=0; inodes().size(); i++) { - Node *ours = copied_project_->nodes().at(i); - Node *theirs = group_->project()->nodes().at(i); - - copy_subnodes_.insert(theirs, ours); - Node::CopyInputs(ours, theirs, false); - } - - // Copy group and nodes - copy_group_ = static_cast(group_->copy()); - copy_group_->setParent(copied_project_); - - // Copy subnodes with positions - const Node::PositionMap &map = group_->GetContextPositions(); - for (auto it=map.cbegin(); it!=map.cend(); it++) { - Node *copy = it.key()->copy(); - copy->SetUUID(it.key()->GetUUID()); - copy->setParent(copied_project_); - - copy_group_->SetNodePositionInContext(copy, it.value()); - copy_subnodes_.insert(it.key(), copy); - } - - for (auto it=map.cbegin(); it!=map.cend(); it++) { - Node::CopyInputs(it.key(), copy_subnodes_.value(it.key()), false); - } - - // Copy edges - for (auto it=copy_subnodes_.cbegin(); it!=copy_subnodes_.cend(); it++) { - Node *src = it.key(); - Node *cpy = it.value(); - - for (auto jt=src->input_connections().cbegin(); jt!=src->input_connections().cend(); jt++) { - if (Node *cpy_output = copy_subnodes_.value(jt->second)) { - Node::ConnectEdge(cpy_output, NodeInput(cpy, jt->first.input(), jt->first.element())); - copied_edges_.append({cpy_output, NodeInput(cpy, jt->first.input(), jt->first.element())}); - } - } - } - - node_view->SetContexts({copy_group_}); - - for (auto it=group_->GetInputPassthroughs().cbegin(); it!=group_->GetInputPassthroughs().cend(); it++) { - const NodeInput &src_input = it.value(); - NodeInput copy_input(copy_subnodes_.value(src_input.node()), src_input.input(), src_input.element()); - param_view->GetParamView()->SetInputChecked(copy_input, true); - } - - param_view->SetContexts({copy_group_}); -} - -void NodeGroupDialog::accept() -{ - // First, validate if a connection, deleted node, or disabled passthrough is going to disconnect - // something elsewhere in the group. Ask the user to confirm if so. - - // Detect removed nodes - QVector nodes_to_delete = GetNodesToDelete(); - - // Detect new connections - QVector edges_to_connect = GetNewConnections(); - - // Warn user if operation will disconnect a node outside the group - if (OperationWillAffectOutsideGroup(nodes_to_delete, edges_to_connect)) { - if (QMessageBox::question(this, QString(), tr("This operation will disconnect nodes outside of this group. Do you wish to continue?"), QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) { - return; - } - } - - NodeViewDeleteCommand *delete_command = new NodeViewDeleteCommand(); - - // Remove deleted nodes - foreach (Node *n, nodes_to_delete) { - delete_command->AddNode(n, group_); - } - - // Disconnect old edges - foreach (const Node::OutputConnection &edge, copied_edges_) { - bool found = false; - const NodeInput ©_input = edge.second; - - for (auto it=edge.first->output_connections().cbegin(); it!=edge.first->output_connections().cend(); it++) { - if (it->second == copy_input) { - found = true; - break; - } - } - - if (!found) { - // Edge has been disconnected - delete_command->AddEdge(copy_subnodes_.key(edge.first), NodeInput(copy_subnodes_.key(copy_input.node()), copy_input.input(), copy_input.element())); - } - } - - delete_command->redo_now(); - - // Add new nodes - MultiUndoCommand *add_command = new MultiUndoCommand(); - for (auto it=copy_group_->GetContextPositions().cbegin(); it!=copy_group_->GetContextPositions().cend(); it++) { - Node *n = it.key(); - Node *original = copy_subnodes_.key(n); - if (!original) { - // If it's a new node that isn't even in the project yet, add it - original = n->copy(); - Node::CopyInputs(n, original, false); - add_command->add_child(new NodeAddCommand(group_->parent(), original)); - copy_subnodes_.insert(original, n); - } - - // Update position in context - add_command->add_child(new NodeSetPositionCommand(original, group_, it.value())); - } - add_command->redo_now(); - - // Connect new edges - MultiUndoCommand *connect_command = new MultiUndoCommand(); - edges_to_connect = GetNewConnections(); // Update list with new nodes made above if necessary - foreach (const Node::OutputConnection &edge, edges_to_connect) { - connect_command->add_child(new NodeEdgeAddCommand(edge.first, edge.second)); - } - connect_command->redo_now(); - - MultiUndoCommand *command = new MultiUndoCommand(); - - if (prepend_undo_) { - command->add_child(prepend_undo_); - } - - command->add_child(delete_command); - command->add_child(add_command); - command->add_child(connect_command); - - // Update group name - if (name_edit_->text() != group_->GetCustomName()) { - command->add_child(new NodeGroupSetCustomNameCommand(group_, name_edit_->text())); - } - - Core::instance()->undo_stack()->push(command); - - super::accept(); -} - -void NodeGroupDialog::reject() -{ - if (prepend_undo_) { - prepend_undo_->undo_now(); - delete prepend_undo_; - } - - super::reject(); -} - -QVector NodeGroupDialog::GetNodesToDelete() -{ - QVector nodes_to_delete; - - for (auto it=group_->GetContextPositions().cbegin(); it!=group_->GetContextPositions().cend(); it++) { - Node *original = it.key(); - Node *copy = copy_subnodes_.value(original); - - if (!copy_group_->ContextContainsNode(copy)) { - nodes_to_delete.append(original); - } - } - - return nodes_to_delete; -} - -QVector NodeGroupDialog::GetNewConnections() -{ - QVector edges_to_connect; - - for (auto it=copy_group_->GetContextPositions().cbegin(); it!=copy_group_->GetContextPositions().cend(); it++) { - const Node::InputConnections &ic = it.key()->input_connections(); - - for (auto jt=ic.cbegin(); jt!=ic.cend(); jt++) { - // All connections inside this dialog will be valid and inside the group - const NodeInput &copied_input = jt->first; - Node *copied_output = jt->second; - - NodeInput original_input(copy_subnodes_.key(copied_input.node()), copied_input.input(), copied_input.element()); - Node *original_output = copy_subnodes_.key(copied_output); - - bool found = false; - - if (original_output) { - for (auto kt=original_output->output_connections().cbegin(); kt!=original_output->output_connections().cend(); kt++) { - if (kt->second == original_input) { - found = true; - break; - } - } - } - - if (!found) { - edges_to_connect.append({original_output, original_input}); - } - } - } - - return edges_to_connect; -} - -bool NodeGroupDialog::OperationWillAffectOutsideGroup(const QVector &deleted, const QVector &connections) -{ - // Check if a node to be deleted inputs from a node outside the group - foreach (Node *n, deleted) { - for (auto jt=n->input_connections().cbegin(); jt!=n->input_connections().cend(); jt++) { - if (!group_->ContextContainsNode(jt->second)) { - return true; - } - } - - for (auto jt=n->output_connections().cbegin(); jt!=n->output_connections().cend(); jt++) { - if (!group_->ContextContainsNode(jt->second.node())) { - return true; - } - } - } - - // Check if a new connection will overwrite a connection from outside the group - foreach (const Node::OutputConnection &edge, connections) { - if (Node *current_conn = edge.second.GetConnectedOutput()) { - if (!group_->ContextContainsNode(current_conn)) { - return true; - } - } - } - - return false; -} - -} diff --git a/app/dialog/nodegroup/nodegroupdialog.h b/app/dialog/nodegroup/nodegroupdialog.h deleted file mode 100644 index d18b7fd10..000000000 --- a/app/dialog/nodegroup/nodegroupdialog.h +++ /dev/null @@ -1,75 +0,0 @@ -/*** - - 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 NODEGROUPDIALOG_H -#define NODEGROUPDIALOG_H - -#include -#include -#include - -#include "node/group/group.h" -#include "undo/undostack.h" - -namespace olive { - -class NodeGroupDialog : public QDialog -{ - Q_OBJECT -public: - explicit NodeGroupDialog(NodeGroup *group, QWidget *parent = nullptr); - - void PrependUndoCommand(MultiUndoCommand *c) - { - prepend_undo_ = c; - } - -public slots: - virtual void accept() override; - - virtual void reject() override; - -signals: - -private: - QVector GetNodesToDelete(); - - QVector GetNewConnections(); - - bool OperationWillAffectOutsideGroup(const QVector &deleted, const QVector &connections); - - NodeGroup *group_; - - QLineEdit *name_edit_; - - MultiUndoCommand *prepend_undo_; - - Project *copied_project_; - NodeGroup *copy_group_; - QMap copy_subnodes_; - QVector copied_edges_; - - UndoStack undo_stack_; - -}; - -} - -#endif // NODEGROUPDIALOG_H diff --git a/app/panel/pixelsampler/pixelsamplerpanel.cpp b/app/panel/pixelsampler/pixelsamplerpanel.cpp index 2f17abb9e..dce9fd698 100644 --- a/app/panel/pixelsampler/pixelsamplerpanel.cpp +++ b/app/panel/pixelsampler/pixelsamplerpanel.cpp @@ -25,7 +25,7 @@ namespace olive { PixelSamplerPanel::PixelSamplerPanel(QWidget *parent) : - PanelWidget(QStringLiteral("ProjectPanel"), parent) + PanelWidget(QStringLiteral("PixelSamplerPanel"), parent) { sampler_widget_ = new ManagedPixelSamplerWidget(); SetWidgetWithPadding(sampler_widget_); diff --git a/app/panel/viewer/viewer.cpp b/app/panel/viewer/viewer.cpp index d23981aab..3d2bf8561 100644 --- a/app/panel/viewer/viewer.cpp +++ b/app/panel/viewer/viewer.cpp @@ -24,24 +24,6 @@ namespace olive { ViewerPanel::ViewerPanel(const QString &object_name, QWidget *parent) : ViewerPanelBase(object_name, parent) -{ - Init(); -} - -ViewerPanel::ViewerPanel(QWidget *parent) : - ViewerPanelBase(QStringLiteral("ViewerPanel"), parent) -{ - Init(); -} - -void ViewerPanel::Retranslate() -{ - ViewerPanelBase::Retranslate(); - - SetTitle(tr("Viewer")); -} - -void ViewerPanel::Init() { // Set ViewerWidget as the central widget ViewerWidget* vw = new ViewerWidget(); @@ -52,4 +34,11 @@ void ViewerPanel::Init() Retranslate(); } +void ViewerPanel::Retranslate() +{ + ViewerPanelBase::Retranslate(); + + SetTitle(tr("Viewer")); +} + } diff --git a/app/panel/viewer/viewer.h b/app/panel/viewer/viewer.h index a0f1d99bd..3b1cc4eb5 100644 --- a/app/panel/viewer/viewer.h +++ b/app/panel/viewer/viewer.h @@ -34,14 +34,14 @@ class ViewerPanel : public ViewerPanelBase { Q_OBJECT public: ViewerPanel(const QString& object_name, QWidget* parent); - ViewerPanel(QWidget* parent); + ViewerPanel(QWidget *parent) : + ViewerPanel(QStringLiteral("ViewerPanel"), parent) + { + } protected: virtual void Retranslate() override; -private: - void Init(); - }; } diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index d0e85fc37..c87b8ccd5 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -26,7 +26,6 @@ #include #include -#include "dialog/nodegroup/nodegroupdialog.h" #include "nodeviewundo.h" #include "node/audio/volume/volume.h" #include "node/distort/transform/transformdistortnode.h" @@ -49,7 +48,6 @@ NodeView::NodeView(QWidget *parent) : create_edge_output_item_(nullptr), create_edge_input_item_(nullptr), paste_command_(nullptr), - undo_stack_(Core::instance()->undo_stack()), scale_(1.0) { setScene(&scene_); @@ -133,7 +131,7 @@ void NodeView::DeleteSelected() ctx->DeleteSelected(command); } - undo_stack_->push(command); + Core::instance()->undo_stack()->push(command); } void NodeView::SelectAll() @@ -227,7 +225,7 @@ void NodeView::SetColorLabel(int index) command->add_child(new NodeOverrideColorCommand(node, index)); } - undo_stack_->push(command); + Core::instance()->undo_stack()->push(command); } void NodeView::ZoomIn() @@ -282,7 +280,7 @@ void NodeView::keyPressEvent(QKeyEvent *event) } } } - undo_stack_->pushIfHasChildren(pos_command); + Core::instance()->undo_stack()->pushIfHasChildren(pos_command); break; } case Qt::Key_Escape: @@ -561,7 +559,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } create_edge_expanded_items_.clear(); - undo_stack_->pushIfHasChildren(command); + Core::instance()->undo_stack()->pushIfHasChildren(command); } MultiUndoCommand* command = new MultiUndoCommand(); @@ -645,7 +643,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } dragging_items_.clear(); - undo_stack_->pushIfHasChildren(command); + Core::instance()->undo_stack()->pushIfHasChildren(command); super::mouseReleaseEvent(event); } @@ -1234,11 +1232,9 @@ void NodeView::GroupNodes() command->add_child(new NodeSetPositionCommand(group, context, avg_pos)); // Do command - command->redo_now(); + Core::instance()->LabelNodes({group}, command); - NodeGroupDialog ngd(group, this); - ngd.PrependUndoCommand(command); - ngd.exec(); + Core::instance()->undo_stack()->push(command); } void NodeView::UngroupNodes() @@ -1273,7 +1269,7 @@ void NodeView::UngroupNodes() command->add_child(new NodeSetPositionCommand(it.key(), context, group->GetNodePositionDataInContext(it.key()))); } - undo_stack_->push(command); + Core::instance()->undo_stack()->push(command); } void NodeView::ShowNodeProperties() @@ -1281,8 +1277,7 @@ void NodeView::ShowNodeProperties() Node *first_node = selected_nodes_.first(); if (NodeGroup *group = dynamic_cast(first_node)) { - NodeGroupDialog ngd(group, this); - ngd.exec(); + qDebug() << "STUB!"; } else { LabelSelectedNodes(); } diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 02118ba45..23e03964a 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -77,11 +77,6 @@ public: void ZoomOut(); - void OverrideUndoStack(UndoStack *stack) - { - undo_stack_ = stack; - } - const QVector &GetCurrentContexts() const { return contexts_; @@ -195,8 +190,6 @@ private: QMap dragging_items_; - UndoStack *undo_stack_; - double scale_; static const double kMinimumScale; diff --git a/app/widget/panel/panel.cpp b/app/widget/panel/panel.cpp index 3009a3fb3..4ce4cd405 100644 --- a/app/widget/panel/panel.cpp +++ b/app/widget/panel/panel.cpp @@ -28,6 +28,8 @@ #include #include +#include "panel/panelmanager.h" + namespace olive { PanelWidget::PanelWidget(const QString &object_name, QWidget *parent) : @@ -39,6 +41,13 @@ PanelWidget::PanelWidget(const QString &object_name, QWidget *parent) : setFocusPolicy(Qt::ClickFocus); connect(this, &PanelWidget::visibilityChanged, this, &PanelWidget::PanelVisibilityChanged); + + PanelManager::instance()->RegisterPanel(this); +} + +PanelWidget::~PanelWidget() +{ + PanelManager::instance()->UnregisterPanel(this); } void PanelWidget::SetMovementLocked(bool locked) diff --git a/app/widget/panel/panel.h b/app/widget/panel/panel.h index 87690bf0d..130e3eaf8 100644 --- a/app/widget/panel/panel.h +++ b/app/widget/panel/panel.h @@ -45,6 +45,8 @@ public: */ PanelWidget(const QString& object_name, QWidget* parent); + virtual ~PanelWidget() override; + /** * @brief Set whether panel movement is locked or not */ diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 4c9b21654..721903b2c 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -273,7 +273,7 @@ void ViewerDisplayWidget::mouseReleaseEvent(QMouseEvent *event) // Handle gizmo MultiUndoCommand *command = new MultiUndoCommand(); gizmos_->GizmoRelease(command); - undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->pushIfHasChildren(command); gizmo_click_ = false; } else { diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 6b4fd9883..1447895fe 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -78,17 +78,17 @@ MainWindow::MainWindow(QWidget *parent) : setStatusBar(status_bar); // Create standard panels - node_panel_ = new NodePanel(undo_stack_, this); - footage_viewer_panel_ = new FootageViewerPanel(undo_stack_, this); - param_panel_ = new ParamPanel(undo_stack_, this); - curve_panel_ = new CurvePanel(undo_stack_, this); - sequence_viewer_panel_ = new SequenceViewerPanel(undo_stack_, this); - pixel_sampler_panel_ = new PixelSamplerPanel(undo_stack_, this); + node_panel_ = new NodePanel(this); + footage_viewer_panel_ = new FootageViewerPanel(this); + param_panel_ = new ParamPanel(this); + curve_panel_ = new CurvePanel(this); + sequence_viewer_panel_ = new SequenceViewerPanel(this); + pixel_sampler_panel_ = new PixelSamplerPanel(this); AppendProjectPanel(); - tool_panel_ = new ToolPanel(undo_stack_, this); - task_man_panel_ = new TaskManagerPanel(undo_stack_, this); + tool_panel_ = new ToolPanel(this); + task_man_panel_ = new TaskManagerPanel(this); AppendTimelinePanel(); - audio_monitor_panel_ = new AudioMonitorPanel(undo_stack_, this); + audio_monitor_panel_ = new AudioMonitorPanel(this); // Make node-related connections connect(node_panel_, &NodePanel::NodesSelected, param_panel_, &ParamPanel::SelectNodes); @@ -218,7 +218,7 @@ bool MainWindow::IsSequenceOpen(Sequence *sequence) const void MainWindow::FolderOpen(Project* p, Folder *i, bool floating) { - ProjectPanel* panel = new ProjectPanel(undo_stack_, this); + ProjectPanel* panel = new ProjectPanel(this); panel->set_project(p); panel->set_root(i); @@ -256,7 +256,7 @@ void MainWindow::OpenNodeInViewer(ViewerOutput *node) viewer_panels_.value(node)->raise(); } else { // Create a viewer for this node - ViewerPanel* viewer = new ViewerPanel(undo_stack_, this); + ViewerPanel* viewer = new ViewerPanel(this); viewer->SetSignalInsteadOfClose(true); viewer->setFloating(true); @@ -816,7 +816,7 @@ void MainWindow::showEvent(QShowEvent *e) template T *MainWindow::AppendPanelInternal(QList& list) { - T* panel = new T(undo_stack_, this); + T* panel = new T(this); if (!list.isEmpty()) { tabifyDockWidget(list.last(), panel); @@ -837,7 +837,7 @@ T *MainWindow::AppendPanelInternal(QList& list) template T *MainWindow::AppendFloatingPanelInternal(QList &list) { - T* panel = new T(undo_stack_, this); + T* panel = new T(this); panel->setFloating(true); panel->show(); From 74d9c43ff5f301dbe8082046ae892710c857b755 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 28 Dec 2021 21:10:57 -0800 Subject: [PATCH 30/34] keyframeview: optimized keyframe drawing --- app/node/keyframe.h | 1 + app/widget/keyframeview/keyframeview.cpp | 82 ++++++++++++++++++++++-- 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/app/node/keyframe.h b/app/node/keyframe.h index c9462cefe..a142c5015 100644 --- a/app/node/keyframe.h +++ b/app/node/keyframe.h @@ -43,6 +43,7 @@ public: * @brief Methods of interpolation to use with this keyframe */ enum Type { + kInvalid = -1, kLinear, kHold, kBezier diff --git a/app/widget/keyframeview/keyframeview.cpp b/app/widget/keyframeview/keyframeview.cpp index 0e751265a..57d8f8c5d 100644 --- a/app/widget/keyframeview/keyframeview.cpp +++ b/app/widget/keyframeview/keyframeview.cpp @@ -244,6 +244,27 @@ void KeyframeView::mouseReleaseEvent(QMouseEvent *event) } } +int BinarySearchFirstKeyframeAfterOrAt(const QVector &keys, const rational &time) +{ + int low = 0; + int high = keys.size()-1; + + while (low <= high) { + int mid = low + (high-low)/2; + NodeKeyframe *test_key = keys.at(mid); + + if (test_key->time() == time || (test_key->time() > time && (mid == 0 || keys.at(mid-1)->time() < time))) { + return mid; + } else if (test_key->time() < time) { + low = mid + 1; + } else { + high = mid - 1; + } + } + + return -1; +} + void KeyframeView::drawForeground(QPainter *painter, const QRectF &rect) { int key_sz = QtUtils::QFontMetricsWidth(fontMetrics(), "Oi"); @@ -254,15 +275,64 @@ void KeyframeView::drawForeground(QPainter *painter, const QRectF &rect) painter->setRenderHint(QPainter::Antialiasing); foreach (KeyframeViewInputConnection *track, tracks_) { - foreach (NodeKeyframe *key, track->GetKeyframes()) { - QRectF key_rect(-key_rad, -key_rad, key_sz, key_sz); - key_rect.translate(GetKeyframeSceneX(key), GetKeyframeSceneY(track, key)); + const QVector &keys = track->GetKeyframes(); - if (!rect.intersects(key_rect)) { + if (keys.isEmpty()) { + continue; + } + + if (!IsYAxisEnabled()) { + // Filter out if the keyframes are offscreen Y + qreal y = GetKeyframeSceneY(track, keys.first()); + if (y + key_rad < rect.top() || y - key_rad >= rect.bottom()) { continue; } + } + + // Find first keyframe to show with binary search + rational left_time = SceneToTime(rect.left() - key_sz); + int using_index = BinarySearchFirstKeyframeAfterOrAt(keys, left_time); + + rational next_key = RATIONAL_MIN; + NodeKeyframe::Type last_type = NodeKeyframe::kInvalid; + for (int i=using_index; itime() < next_key && key->type() == last_type) { + // This key will be drawn at exactly the same location as the last one and therefore + // doesn't need to be drawn. See if the next one will be drawn. + i++; + if (i == keys.size()) { + break; + } + + key = keys.at(i); + + if (key->time() < next_key) { + // Next key still won't be drawn, so we'll switch to a binary search + i = BinarySearchFirstKeyframeAfterOrAt(keys, next_key); + + if (i == -1) { + break; + } + + key = keys.at(i); + } + } + + QRectF key_rect(-key_rad, -key_rad, key_sz, key_sz); + qreal key_x = GetKeyframeSceneX(key); + key_rect.translate(key_x, GetKeyframeSceneY(track, key)); + + if (key_rect.left() >= rect.right()) { + // Break after last keyframe + break; + } DrawKeyframe(painter, key, track, key_rect); + + next_key = SceneToTime(key_x + 1); + last_type = key->type(); } } @@ -282,6 +352,8 @@ void KeyframeView::DrawKeyframe(QPainter *painter, NodeKeyframe *key, KeyframeVi selection_manager_.DeclareDrawnObject(key, key_rect); switch (key->type()) { + case NodeKeyframe::kInvalid: + break; case NodeKeyframe::kLinear: { QPointF points[] = { @@ -403,6 +475,8 @@ void KeyframeView::ShowContextMenu() if (all_keys_are_same_type) { switch (type) { + case NodeKeyframe::kInvalid: + break; case NodeKeyframe::kLinear: linear_key_action->setChecked(true); break; From a4934f1d47f33032ed346d47acfb06346f41da6d Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 28 Dec 2021 21:52:28 -0800 Subject: [PATCH 31/34] implemented saving/loading for groups --- app/common/xmlutils.cpp | 65 +++++++++++++++++++--------- app/common/xmlutils.h | 16 +++++-- app/core.cpp | 2 +- app/node/factory.cpp | 11 +++++ app/node/factory.h | 3 ++ app/node/group/group.cpp | 62 +++++++++++++++++++++----- app/node/group/group.h | 52 +++------------------- app/node/nodecopypaste.cpp | 7 +-- app/node/project/project.cpp | 5 +-- app/panel/node/node.cpp | 1 + app/panel/node/node.h | 8 ++++ app/panel/param/param.h | 10 +++++ app/window/mainwindow/mainwindow.cpp | 20 ++++++++- app/window/mainwindow/mainwindow.h | 2 + 14 files changed, 170 insertions(+), 94 deletions(-) diff --git a/app/common/xmlutils.cpp b/app/common/xmlutils.cpp index f778b6440..97ec92057 100644 --- a/app/common/xmlutils.cpp +++ b/app/common/xmlutils.cpp @@ -24,15 +24,30 @@ #include "node/factory.h" #include "widget/nodeparamview/nodeparamviewundo.h" #include "widget/nodeview/nodeviewundo.h" +//#include "widget/timelinewidget/undo/timelineundogeneral.h" namespace olive { -void XMLConnectNodes(const XMLNodeData &xml_node_data, uint version, MultiUndoCommand *command) +bool XMLReadNextStartElement(QXmlStreamReader *reader) { - foreach (const XMLNodeData::SerializedConnection& con, xml_node_data.desired_connections) { - Node *out = xml_node_data.node_ptrs.value(con.output_node); + QXmlStreamReader::TokenType token; - if (out) { + while ((token = reader->readNext()) != QXmlStreamReader::Invalid + && token != QXmlStreamReader::EndDocument) { + if (reader->isEndElement()) { + return false; + } else if (reader->isStartElement()) { + return true; + } + } + + return false; +} + +void XMLNodeData::PostConnect(uint version, MultiUndoCommand *command) const +{ + foreach (const XMLNodeData::SerializedConnection& con, desired_connections) { + if (Node *out = node_ptrs.value(con.output_node)) { // Use output param as hint tag since we grandfathered those in Node::ValueHint hint(con.output_param); @@ -53,28 +68,36 @@ void XMLConnectNodes(const XMLNodeData &xml_node_data, uint version, MultiUndoCo } } } -} -bool XMLReadNextStartElement(QXmlStreamReader *reader) -{ - QXmlStreamReader::TokenType token; - - while ((token = reader->readNext()) != QXmlStreamReader::Invalid - && token != QXmlStreamReader::EndDocument) { - if (reader->isEndElement()) { - return false; - } else if (reader->isStartElement()) { - return true; + foreach (const XMLNodeData::BlockLink& l, block_links) { + Node *a = l.block; + Node *b = node_ptrs.value(l.link); + if (command) { + command->add_child(new NodeLinkCommand(a, b, true)); + } else { + Node::Link(a, b); } } - return false; -} + foreach (const XMLNodeData::GroupLink &l, group_input_links) { + if (Node *input_node = node_ptrs.value(l.input_node)) { + NodeInput resolved(input_node, l.input_id, l.input_element); + if (command) { + command->add_child(new NodeGroupAddInputPassthrough(l.group, resolved)); + } else { + l.group->AddInputPassthrough(resolved); + } + } + } -void XMLLinkBlocks(const XMLNodeData &xml_node_data) -{ - foreach (const XMLNodeData::BlockLink& l, xml_node_data.block_links) { - Block::Link(l.block, static_cast(xml_node_data.node_ptrs.value(l.link))); + for (auto it=group_output_links.cbegin(); it!=group_output_links.cend(); it++) { + if (Node *output_node = node_ptrs.value(it.value())) { + if (command) { + command->add_child(new NodeGroupSetOutputPassthrough(it.key(), output_node)); + } else { + it.key()->SetOutputPassthrough(output_node); + } + } } } diff --git a/app/common/xmlutils.h b/app/common/xmlutils.h index e8b88d6a3..d0488ccd0 100644 --- a/app/common/xmlutils.h +++ b/app/common/xmlutils.h @@ -31,6 +31,7 @@ namespace olive { class Block; class Node; class NodeInput; +class NodeGroup; #define XMLAttributeLoop(reader, item) \ foreach (const QXmlStreamAttribute& item, reader->attributes()) @@ -49,14 +50,23 @@ struct XMLNodeData { quintptr link; }; + struct GroupLink { + NodeGroup *group; + quintptr input_node; + QString input_id; + int input_element; + }; + QHash node_ptrs; QList desired_connections; QList block_links; + QVector group_input_links; + QHash group_output_links; + + void PostConnect(uint version, MultiUndoCommand *command = nullptr) const; }; -void XMLConnectNodes(const XMLNodeData& xml_node_data, uint version, MultiUndoCommand *command = nullptr); - /** * @brief Workaround for QXmlStreamReader::readNextStartElement not detecting the end of a document * @@ -68,8 +78,6 @@ void XMLConnectNodes(const XMLNodeData& xml_node_data, uint version, MultiUndoCo */ bool XMLReadNextStartElement(QXmlStreamReader* reader); -void XMLLinkBlocks(const XMLNodeData& xml_node_data); - } #endif // XMLREADLOOP_H diff --git a/app/core.cpp b/app/core.cpp index 241d57206..d0301a329 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -75,7 +75,7 @@ namespace olive { Core* Core::instance_ = nullptr; -const uint Core::kProjectVersion = 210907; +const uint Core::kProjectVersion = 211228; Core::Core(const CoreParams& params) : main_window_(nullptr), diff --git a/app/node/factory.cpp b/app/node/factory.cpp index f4368d898..447ba0dbd 100644 --- a/app/node/factory.cpp +++ b/app/node/factory.cpp @@ -54,6 +54,7 @@ namespace olive { QList NodeFactory::library_; +QVector NodeFactory::hidden_; void NodeFactory::Initialize() { @@ -65,6 +66,9 @@ void NodeFactory::Initialize() library_.append(created_node); } + + hidden_.append(kTextGeneratorLegacy); + hidden_.append(kGroupNode); } void NodeFactory::Destroy() @@ -86,6 +90,11 @@ Menu *NodeFactory::CreateMenu(QWidget* parent, bool create_none_item, Node::Cate continue; } + if (hidden_.contains(i)) { + // Skip this node + continue; + } + // Make sure nodes are up-to-date with the current translation n->Retranslate(); @@ -241,6 +250,8 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id) return new SubtitleBlock(); case kShapeGenerator: return new ShapeNode(); + case kGroupNode: + return new NodeGroup(); case kInternalNodeCount: break; diff --git a/app/node/factory.h b/app/node/factory.h index 1d8cd72f2..10101e648 100644 --- a/app/node/factory.h +++ b/app/node/factory.h @@ -61,6 +61,7 @@ public: kTimeRemapNode, kSubtitleBlock, kShapeGenerator, + kGroupNode, // Count value kInternalNodeCount @@ -87,6 +88,8 @@ public: private: static QList library_; + static QVector hidden_; + }; } diff --git a/app/node/group/group.cpp b/app/node/group/group.cpp index be92d9be8..e64b551d8 100644 --- a/app/node/group/group.cpp +++ b/app/node/group/group.cpp @@ -24,6 +24,8 @@ namespace olive { +#define super Node + NodeGroup::NodeGroup() : output_passthrough_(nullptr) { @@ -31,11 +33,7 @@ NodeGroup::NodeGroup() : QString NodeGroup::Name() const { - if (custom_name_.isEmpty()) { - return tr("Group"); - } else { - return custom_name_; - } + return tr("Group"); } QString NodeGroup::id() const @@ -131,15 +129,59 @@ QString NodeGroup::GetInputName(const QString &id) const return input_passthroughs_.value(id).name(); } -void NodeGroupSetCustomNameCommand::redo() +bool NodeGroup::LoadCustom(QXmlStreamReader *reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt *cancelled) { - old_name_ = group_->GetCustomName(); - group_->SetCustomName(new_name_); + if (reader->name() == QStringLiteral("inputpassthroughs")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("inputpassthrough")) { + XMLNodeData::GroupLink link; + + link.group = this; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + link.input_node = reader->readElementText().toULongLong(); + } else if (reader->name() == QStringLiteral("input")) { + link.input_id = reader->readElementText(); + } else if (reader->name() == QStringLiteral("element")) { + link.input_element = reader->readElementText().toInt(); + } else { + reader->skipCurrentElement(); + } + } + + xml_node_data.group_input_links.append(link); + } else { + reader->skipCurrentElement(); + } + } + + return true; + } else if (reader->name() == QStringLiteral("outputpassthrough")) { + xml_node_data.group_output_links.insert(this, reader->readElementText().toULongLong()); + return true; + } else { + return super::LoadCustom(reader, xml_node_data, version, cancelled); + } } -void NodeGroupSetCustomNameCommand::undo() +void NodeGroup::SaveCustom(QXmlStreamWriter *writer) const { - group_->SetCustomName(old_name_); + super::SaveCustom(writer); + + writer->writeStartElement(QStringLiteral("inputpassthroughs")); + + foreach (const NodeInput &ip, input_passthroughs_) { + writer->writeStartElement(QStringLiteral("inputpassthrough")); + writer->writeTextElement(QStringLiteral("node"), QString::number(reinterpret_cast(ip.node()))); + writer->writeTextElement(QStringLiteral("input"), ip.input()); + writer->writeTextElement(QStringLiteral("element"), QString::number(ip.element())); + writer->writeEndElement(); // input + } + + writer->writeEndElement(); // inputpassthroughs + + writer->writeTextElement(QStringLiteral("outputpassthrough"), QString::number(reinterpret_cast(output_passthrough_))); } void NodeGroupAddInputPassthrough::redo() diff --git a/app/node/group/group.h b/app/node/group/group.h index d023a7416..979a52bc7 100644 --- a/app/node/group/group.h +++ b/app/node/group/group.h @@ -52,24 +52,6 @@ public: void SetOutputPassthrough(Node *node); - const QString &GetCustomName() const - { - return custom_name_; - } - - void SetCustomName(const QString &name) - { - custom_name_ = name; - - // NOTE: Not technically the right signal, but should achieve the right goal - emit LabelChanged(custom_name_); - } - - void ClearCustomName() - { - custom_name_.clear(); - } - static QString GetGroupInputIDFromInput(const NodeInput &input); const QHash &GetInputPassthroughs() const @@ -88,40 +70,16 @@ signals: void OutputPassthroughChanged(NodeGroup *group, Node *output); +protected: + virtual bool LoadCustom(QXmlStreamReader* reader, XMLNodeData& xml_node_data, uint version, const QAtomicInt* cancelled) override; + + virtual void SaveCustom(QXmlStreamWriter* writer) const override; + private: QHash input_passthroughs_; Node *output_passthrough_; - QString custom_name_; - -}; - -class NodeGroupSetCustomNameCommand : public UndoCommand -{ -public: - NodeGroupSetCustomNameCommand(NodeGroup *group, const QString &name) : - group_(group), - new_name_(name) - {} - - virtual Project * GetRelevantProject() const override - { - return group_->project(); - } - -protected: - virtual void redo() override; - - virtual void undo() override; - -private: - NodeGroup *group_; - - QString old_name_; - - QString new_name_; - }; class NodeGroupAddInputPassthrough : public UndoCommand diff --git a/app/node/nodecopypaste.cpp b/app/node/nodecopypaste.cpp index e9c0a4d0e..2a54212af 100644 --- a/app/node/nodecopypaste.cpp +++ b/app/node/nodecopypaste.cpp @@ -205,12 +205,7 @@ QVector NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph, } // Make connections - if (!xml_node_data.desired_connections.isEmpty()) { - XMLConnectNodes(xml_node_data, data_version, command); - } - - // Link blocks - XMLLinkBlocks(xml_node_data); + xml_node_data.PostConnect(data_version, command); // Process contexts for (auto it=pasted_contexts.cbegin(); it!=pasted_contexts.cend(); it++) { diff --git a/app/node/project/project.cpp b/app/node/project/project.cpp index ba118ba50..0ca246374 100644 --- a/app/node/project/project.cpp +++ b/app/node/project/project.cpp @@ -193,10 +193,7 @@ void Project::Load(QXmlStreamReader *reader, MainWindowLayoutInfo* layout, uint } // Make connections - XMLConnectNodes(xml_node_data, version); - - // Link blocks - XMLLinkBlocks(xml_node_data); + xml_node_data.PostConnect(version); } void Project::Save(QXmlStreamWriter *writer) const diff --git a/app/panel/node/node.cpp b/app/panel/node/node.cpp index 6e4b842e6..d6abc3a1d 100644 --- a/app/panel/node/node.cpp +++ b/app/panel/node/node.cpp @@ -31,6 +31,7 @@ NodePanel::NodePanel(QWidget *parent) : // Connect node view signals to this panel - MAY REMOVE connect(node_widget_->view(), &NodeView::NodesSelected, this, &NodePanel::NodesSelected); connect(node_widget_->view(), &NodeView::NodesDeselected, this, &NodePanel::NodesDeselected); + connect(node_widget_->view(), &NodeView::NodeGroupOpenRequested, this, &NodePanel::NodeGroupOpenRequested); // Set it as the main widget of this panel SetWidgetWithPadding(node_widget_); diff --git a/app/panel/node/node.h b/app/panel/node/node.h index 083445029..f1dae048f 100644 --- a/app/panel/node/node.h +++ b/app/panel/node/node.h @@ -40,6 +40,11 @@ public: return node_widget_; } + const QVector &GetContexts() const + { + return node_widget_->view()->GetContexts(); + } + void SetContexts(const QVector &nodes) { node_widget_->SetContexts(nodes); @@ -109,6 +114,7 @@ public slots: void Select(const QVector& nodes, bool center_view_on_item) { node_widget_->view()->Select(nodes, center_view_on_item); + this->raise(); } signals: @@ -116,6 +122,8 @@ signals: void NodesDeselected(const QVector& nodes); + void NodeGroupOpenRequested(NodeGroup *group); + private: virtual void Retranslate() override { diff --git a/app/panel/param/param.h b/app/panel/param/param.h index 4389bc15f..5d7928f04 100644 --- a/app/panel/param/param.h +++ b/app/panel/param/param.h @@ -38,6 +38,16 @@ public: return static_cast(GetTimeBasedWidget()); } + void SetCreateCheckBoxes(NodeParamViewCheckBoxBehavior e) + { + GetParamView()->SetCreateCheckBoxes(e); + } + + void SetIgnoreNodeFlags(bool e) + { + GetParamView()->SetIgnoreNodeFlags(e); + } + public slots: void SelectNodes(const QVector& nodes); void DeselectNodes(const QVector& nodes); diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 1447895fe..d17ff3a0f 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -93,6 +93,7 @@ MainWindow::MainWindow(QWidget *parent) : // Make node-related connections connect(node_panel_, &NodePanel::NodesSelected, param_panel_, &ParamPanel::SelectNodes); connect(node_panel_, &NodePanel::NodesDeselected, param_panel_, &ParamPanel::DeselectNodes); + connect(node_panel_, &NodePanel::NodeGroupOpenRequested, this, &MainWindow::NodeGroupRequested); connect(param_panel_, &ParamPanel::RequestSelectNode, this, [this](const QVector& target){ node_panel_->Select(target, true); }); @@ -451,6 +452,17 @@ void MainWindow::StatusBarDoubleClicked() task_man_panel_->raise(); } +void MainWindow::NodeGroupRequested(NodeGroup *group) +{ + NodePanel *panel = new NodePanel(this); + panel->setFloating(true); + panel->setVisible(true); + panel->SetContexts({group}); + panel->SetSignalInsteadOfClose(true); + addDockWidget(Qt::LeftDockWidgetArea, panel); + connect(panel, &NodePanel::CloseRequested, panel, &NodePanel::deleteLater); +} + void MainWindow::TimelinePanelSelectionChanged(const QVector &blocks) { TimelinePanel *panel = static_cast(sender()); @@ -723,7 +735,13 @@ void MainWindow::FocusedPanelChanged(PanelWidget *panel) UpdateAudioMonitorParams(tbp->GetConnectedViewer()); } - if (TimelinePanel* timeline = dynamic_cast(panel)) { + if (NodePanel *node_panel = dynamic_cast(panel)) { + // Set param view contexts to these + bool is_default_node_panel = node_panel == node_panel_; + param_panel_->SetIgnoreNodeFlags(!is_default_node_panel); + param_panel_->SetCreateCheckBoxes(is_default_node_panel ? kNoCheckBoxes : kCheckBoxesOnNonConnected); + param_panel_->SetContexts(node_panel->GetContexts()); + } else if (TimelinePanel* timeline = dynamic_cast(panel)) { // Signal timeline focus TimelineFocused(timeline->GetConnectedViewer()); diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index 719652200..63dc4445f 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -187,6 +187,8 @@ private slots: void StatusBarDoubleClicked(); + void NodeGroupRequested(NodeGroup *group); + #ifdef Q_OS_LINUX void ShowNouveauWarning(); #endif From 8daa83b682c29f94602335e50296d46ee11e0643 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 29 Dec 2021 18:44:14 -0800 Subject: [PATCH 32/34] work towards finishing nodeview copy/paste refactor --- app/common/xmlutils.cpp | 13 ++- app/node/nodecopypaste.cpp | 12 ++- app/widget/nodeview/nodeview.cpp | 115 ++++++++++++++---------- app/widget/nodeview/nodeview.h | 11 +++ app/widget/nodeview/nodeviewcontext.cpp | 2 + app/widget/nodeview/nodeviewitem.cpp | 9 +- app/widget/nodeview/nodeviewitem.h | 1 + 7 files changed, 103 insertions(+), 60 deletions(-) diff --git a/app/common/xmlutils.cpp b/app/common/xmlutils.cpp index 97ec92057..1b7820e26 100644 --- a/app/common/xmlutils.cpp +++ b/app/common/xmlutils.cpp @@ -53,16 +53,15 @@ void XMLNodeData::PostConnect(uint version, MultiUndoCommand *command) const if (command) { command->add_child(new NodeEdgeAddCommand(out, con.input)); - - if (version < 210907) { - /// Deprecated: backwards compatibility only - command->add_child(new NodeSetValueHintCommand(con.input, hint)); - } } else { Node::ConnectEdge(out, con.input); + } - if (version < 210907) { - /// Deprecated: backwards compatibility only + if (version < 210907) { + /// Deprecated: backwards compatibility only + if (command) { + command->add_child(new NodeSetValueHintCommand(con.input, hint)); + } else { con.input.node()->SetValueHintForInput(con.input.input(), hint, con.input.element()); } } diff --git a/app/node/nodecopypaste.cpp b/app/node/nodecopypaste.cpp index 2a54212af..7ae4ff17a 100644 --- a/app/node/nodecopypaste.cpp +++ b/app/node/nodecopypaste.cpp @@ -201,7 +201,11 @@ QVector NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph, // Add all nodes to graph foreach (Node* n, pasted_nodes) { - command->add_child(new NodeAddCommand(graph, n)); + if (command) { + command->add_child(new NodeAddCommand(graph, n)); + } else { + n->setParent(graph); + } } // Make connections @@ -215,7 +219,11 @@ QVector NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph, for (auto jt=map.cbegin(); jt!=map.cend(); jt++) { Node *subnode = xml_node_data.node_ptrs.value(jt.key()); if (subnode) { - command->add_child(new NodeSetPositionCommand(subnode, context, jt.value())); + if (command) { + command->add_child(new NodeSetPositionCommand(subnode, context, jt.value())); + } else { + context->SetNodePositionInContext(subnode, jt.value()); + } } } } diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index c87b8ccd5..53621364b 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -181,13 +181,10 @@ void NodeView::Select(const QVector &nodes, bool center_view_on_item) context->Select(nodes); } - /* // Center on something - Node *first_item = nodes.isEmpty() ? nullptr : nodes.first(); - if (center_view_on_item && first_item) { - centerOn(first_item); + if (center_view_on_item && !nodes.isEmpty()) { + QMetaObject::invokeMethod(this, "CenterOnNode", Qt::QueuedConnection, OLIVE_NS_ARG(Node*, nodes.first())); } - */ ConnectSelectionChangedSignal(); @@ -849,6 +846,16 @@ void NodeView::CenterOnItemsBoundingRect() centerOn(scene_.itemsBoundingRect().center()); } +void NodeView::CenterOnNode(Node *n) +{ + foreach (NodeViewContext *ctx, scene_.context_map()) { + if (NodeViewItem* item = ctx->GetItemFromMap(n)) { + centerOn(item); + break; + } + } +} + void NodeView::RepositionMiniMap() { if (minimap_->isVisible()) { @@ -980,34 +987,32 @@ bool NodeView::eventFilter(QObject *object, QEvent *event) void NodeView::CopyNodesToClipboardInternal(QXmlStreamWriter *writer, const QVector &nodes, void *userdata) { - qDebug() << "STUB!"; - /*writer->writeStartElement(QStringLiteral("pos")); + writer->writeStartElement(QStringLiteral("pos")); for (Node *n : nodes) { - NodeViewItem *item = scene_.item_map().value(n); - QPointF pos = item->GetNodePosition(); + Node::Position pos = GetAssumedPositionForSelectedNode(n); writer->writeStartElement(QStringLiteral("node")); writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(n))); - writer->writeTextElement(QStringLiteral("x"), QString::number(pos.x())); - writer->writeTextElement(QStringLiteral("y"), QString::number(pos.y())); + writer->writeTextElement(QStringLiteral("x"), QString::number(pos.position.x())); + writer->writeTextElement(QStringLiteral("y"), QString::number(pos.position.y())); + writer->writeTextElement(QStringLiteral("expanded"), QString::number(pos.expanded)); writer->writeEndElement(); // node } - writer->writeEndElement(); // pos*/ + writer->writeEndElement(); // pos } void NodeView::PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data, void *userdata) { - qDebug() << "STUB!"; - /*NodeGraph::PositionMap *map = static_cast(userdata); + Node::PositionMap *map = static_cast(userdata); while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("pos")) { while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("node")) { Node *n = nullptr; - QPointF pos; + Node::Position pos; XMLAttributeLoop(reader, attr) { if (attr.name() == QStringLiteral("ptr")) { @@ -1018,9 +1023,11 @@ void NodeView::PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNode while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("x")) { - pos.setX(reader->readElementText().toDouble()); + pos.position.setX(reader->readElementText().toDouble()); } else if (reader->name() == QStringLiteral("y")) { - pos.setY(reader->readElementText().toDouble()); + pos.position.setY(reader->readElementText().toDouble()); + } else if (reader->name() == QStringLiteral("expanded")) { + pos.expanded = reader->readElementText().toInt(); } else { reader->skipCurrentElement(); } @@ -1036,7 +1043,7 @@ void NodeView::PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNode } else { reader->skipCurrentElement(); } - }*/ + } } void NodeView::changeEvent(QEvent *e) @@ -1070,6 +1077,21 @@ QPointF NodeView::GetEstimatedPositionForContext(NodeViewItem *item, Node *conte return item->GetNodePosition() - context_offsets_.value(context); } +Node::Position NodeView::GetAssumedPositionForSelectedNode(Node *node) +{ + // Try to find corresponding selected item + foreach (NodeViewContext *ctx, scene_.context_map()) { + NodeViewItem *item = ctx->GetItemFromMap(node); + if (item && item->isSelected()) { + // Good enough + return Node::Position(item->GetNodePosition(), item->IsExpanded()); + } + } + + // Fallback + return Node::Position(); +} + Menu *NodeView::CreateAddMenu(Menu *parent) { Menu* add_menu = NodeFactory::CreateMenu(parent); @@ -1277,7 +1299,7 @@ void NodeView::ShowNodeProperties() Node *first_node = selected_nodes_.first(); if (NodeGroup *group = dynamic_cast(first_node)) { - qDebug() << "STUB!"; + emit NodeGroupOpenRequested(group); } else { LabelSelectedNodes(); } @@ -1290,51 +1312,46 @@ void NodeView::LabelSelectedNodes() void NodeView::PasteNodesInternal(const QVector &duplicate_nodes) { - /* // If no graph, do nothing if (contexts_.isEmpty()) { return; } - paste_command_ = new MultiUndoCommand(); - // If duplicating nodes, duplicate, otherwise paste QVector new_nodes; - NodeGraph::PositionMap map; + Node::PositionMap map; if (duplicate_nodes.isEmpty()) { - new_nodes = PasteNodesFromClipboard(graph_, paste_command_, &map); - - for (auto it=new_nodes.cbegin(); it!=new_nodes.cend(); it++) { - for (Node *context : qAsConst(contexts_)) { - paste_command_->add_child(new NodeSetPositionCommand(*it, context, map.value(*it), false)); - } - } + new_nodes = PasteNodesFromClipboard(nullptr, nullptr, &map); } else { - new_nodes = Node::CopyDependencyGraph(duplicate_nodes, paste_command_); + new_nodes.resize(selected_nodes_.size()); - for (int i=0; iGetNodePosition(); - paste_command_->add_child(new NodeSetPositionCommand(copy, context, p, false)); - } + for (int i=0; icopy(); + Node::CopyInputs(og, copy, false); + map.insert(copy, GetAssumedPositionForSelectedNode(og)); + new_nodes[i] = copy; } + + Node::CopyDependencyGraph(selected_nodes_, new_nodes, nullptr); } // If no nodes were retrieved, do nothing - if (new_nodes.isEmpty()) { - delete paste_command_; - paste_command_ = nullptr; - return; + if (!new_nodes.isEmpty()) { + QVector items(new_nodes.size()); + + for (int i=0; iSetFlowDirection(scene_.GetFlowDirection()); + new_item->SetNodePosition(map.value(node)); + scene_.addItem(new_item); + items[i] = new_item; + } + + // Attach nodes to cursor + AttachItemsToCursor(items); } - - // Attach nodes to cursor - paste_command_->add_child(new NodeViewAttachNodesToCursor(this, new_nodes)); - - paste_command_->redo_now(); - */ } void NodeView::AddContext(Node *n) diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 23e03964a..c3cc73f71 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -52,6 +52,11 @@ public: void SetContexts(const QVector &nodes); + const QVector &GetContexts() const + { + return contexts_; + } + void CloseContextsBelongingToProject(Project *project); void ClearGraph(); @@ -97,11 +102,15 @@ public slots: void CenterOnItemsBoundingRect(); + void CenterOnNode(olive::Node *n); + signals: void NodesSelected(const QVector& nodes); void NodesDeselected(const QVector& nodes); + void NodeGroupOpenRequested(NodeGroup *group); + protected: virtual void keyPressEvent(QKeyEvent *event) override; @@ -141,6 +150,8 @@ private: QPointF GetEstimatedPositionForContext(NodeViewItem *item, Node *context) const; + Node::Position GetAssumedPositionForSelectedNode(Node *node); + Menu *CreateAddMenu(Menu *parent); void PositionNewEdge(const QPoint &pos); diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index 8ded8fdc3..054624a11 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -189,6 +189,8 @@ void NodeViewContext::DeleteSelected(NodeViewDeleteCommand *command) command->AddNode(node->GetNode(), context_); } } + + UpdateRect(); } void NodeViewContext::Select(const QVector &nodes) diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 04849fbb2..7389d2cd8 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -79,8 +79,7 @@ NodeViewItem::NodeViewItem(Node *node, const QString &input, int element, Node * setFlag(QGraphicsItem::ItemIsSelectable); if (context_) { - SetNodePosition(context_->GetNodePositionInContext(node_)); - SetExpanded(context_->IsNodeExpandedInContext(node_)); + SetNodePosition(context_->GetNodePositionDataInContext(node_)); } } else { output_connector_->setVisible(false); @@ -110,6 +109,12 @@ void NodeViewItem::SetNodePosition(const QPointF &pos) UpdateNodePosition(); } +void NodeViewItem::SetNodePosition(const Node::Position &pos) +{ + SetNodePosition(pos.position); + SetExpanded(pos.expanded); +} + QVector NodeViewItem::GetAllEdgesRecursively() const { QVector list = edges_; diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index e236de3ea..6ec3a8ca1 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -56,6 +56,7 @@ public: QPointF GetNodePosition() const; void SetNodePosition(const QPointF& pos); + void SetNodePosition(const Node::Position& pos); QVector GetAllEdgesRecursively() const; From a7be16fec95381b566ca1c93377b92c92a05c85b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 30 Dec 2021 19:35:52 -0800 Subject: [PATCH 33/34] finished refactoring copy/paste code --- app/node/nodecopypaste.cpp | 21 ++- app/node/nodecopypaste.h | 2 +- app/panel/param/param.h | 5 + app/widget/nodeparamview/nodeparamview.cpp | 1 + app/widget/nodeparamview/nodeparamview.h | 7 + .../nodeparamview/nodeparamviewitembase.cpp | 2 +- app/widget/nodeview/nodeview.cpp | 151 +++++++++++------- app/widget/nodeview/nodeview.h | 11 +- app/widget/nodeview/nodeviewitem.cpp | 5 + app/widget/nodeview/nodeviewitem.h | 1 + app/window/mainwindow/mainwindow.cpp | 12 +- 11 files changed, 143 insertions(+), 75 deletions(-) diff --git a/app/node/nodecopypaste.cpp b/app/node/nodecopypaste.cpp index 7ae4ff17a..190146472 100644 --- a/app/node/nodecopypaste.cpp +++ b/app/node/nodecopypaste.cpp @@ -29,7 +29,7 @@ namespace olive { -void NodeCopyPasteService::CopyNodesToClipboard(const QVector &nodes, void *userdata) +void NodeCopyPasteService::CopyNodesToClipboard(QVector nodes, void *userdata) { QString copy_str; @@ -42,11 +42,22 @@ void NodeCopyPasteService::CopyNodesToClipboard(const QVector &nodes, vo writer.writeTextElement(QStringLiteral("version"), QString::number(Core::kProjectVersion)); writer.writeStartElement(QStringLiteral("nodes")); - foreach (Node* n, nodes) { + for (int i=0; iid()); n->Save(&writer); writer.writeEndElement(); // node + + // If this is a group, add the child nodes too + if (NodeGroup *g = dynamic_cast(n)) { + for (auto it=g->GetContextPositions().cbegin(); it!=g->GetContextPositions().cend(); it++) { + if (!nodes.contains(it.key())) { + nodes.append(it.key()); + } + } + } } writer.writeEndElement(); // nodes @@ -208,9 +219,6 @@ QVector NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph, } } - // Make connections - xml_node_data.PostConnect(data_version, command); - // Process contexts for (auto it=pasted_contexts.cbegin(); it!=pasted_contexts.cend(); it++) { Node *context = xml_node_data.node_ptrs.value(it.key()); @@ -229,6 +237,9 @@ QVector NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph, } } + // Make connections + xml_node_data.PostConnect(data_version, command); + return pasted_nodes; } diff --git a/app/node/nodecopypaste.h b/app/node/nodecopypaste.h index cc29d281a..41a25302e 100644 --- a/app/node/nodecopypaste.h +++ b/app/node/nodecopypaste.h @@ -35,7 +35,7 @@ public: NodeCopyPasteService() = default; protected: - void CopyNodesToClipboard(const QVector &nodes, void* userdata = nullptr); + void CopyNodesToClipboard(QVector nodes, void* userdata = nullptr); QVector PasteNodesFromClipboard(NodeGraph *graph, MultiUndoCommand *command, void* userdata = nullptr); diff --git a/app/panel/param/param.h b/app/panel/param/param.h index 5d7928f04..95ddc9b9a 100644 --- a/app/panel/param/param.h +++ b/app/panel/param/param.h @@ -38,6 +38,11 @@ public: return static_cast(GetTimeBasedWidget()); } + const QVector &GetContexts() const + { + return GetParamView()->GetContexts(); + } + void SetCreateCheckBoxes(NodeParamViewCheckBoxBehavior e) { GetParamView()->SetCreateCheckBoxes(e); diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 86732d37c..68f04e7fd 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -238,6 +238,7 @@ void NodeParamView::SetContexts(const QVector &contexts) ctx->Clear(); ctx->setVisible(false); } + contexts_ = contexts; if (keyframe_view_) { keyframe_view_->Clear(); diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 29110deca..b36e31cc0 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -77,6 +77,11 @@ public: void SelectNodes(const QVector &nodes); void DeselectNodes(const QVector &nodes); + const QVector &GetContexts() const + { + return contexts_; + } + public slots: void SetInputChecked(const NodeInput &input, bool e); @@ -133,6 +138,8 @@ private: bool ignore_flags_; + QVector contexts_; + private slots: void UpdateGlobalScrollBar(); diff --git a/app/widget/nodeparamview/nodeparamviewitembase.cpp b/app/widget/nodeparamview/nodeparamviewitembase.cpp index 709514326..5116d872a 100644 --- a/app/widget/nodeparamview/nodeparamviewitembase.cpp +++ b/app/widget/nodeparamview/nodeparamviewitembase.cpp @@ -52,7 +52,7 @@ NodeParamViewItemBase::NodeParamViewItemBase(QWidget *parent) : bool NodeParamViewItemBase::IsExpanded() const { - return body_->isVisible(); + return title_bar_->IsExpanded(); } QString NodeParamViewItemBase::GetTitleBarTextFromNode(Node *n) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 53621364b..481a88b71 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -47,7 +47,6 @@ NodeView::NodeView(QWidget *parent) : create_edge_(nullptr), create_edge_output_item_(nullptr), create_edge_input_item_(nullptr), - paste_command_(nullptr), scale_(1.0) { setScene(&scene_); @@ -283,14 +282,6 @@ void NodeView::keyPressEvent(QKeyEvent *event) case Qt::Key_Escape: if (!attached_items_.isEmpty()) { DetachItemsFromCursor(); - - // We undo the last action which SHOULD be adding the node - if (paste_command_) { - paste_command_->undo_now(); - delete paste_command_; - paste_command_ = nullptr; - } - break; } @@ -573,23 +564,18 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } if (context) { - if (paste_command_) { - // We've already "done" this command, but MultiUndoCommand prevents "redoing" twice, so we - // add it to this command (which may have extra commands added too) so that it all gets undone - // in the same action - command->add_child(paste_command_); - paste_command_ = nullptr; - } - { MultiUndoCommand *add_command = new MultiUndoCommand(); foreach (const AttachedItem &ai, attached_items_) { // Add node to the same graph that the context is in - add_command->add_child(new NodeAddCommand(context->parent(), ai.item->GetNode())); + add_command->add_child(new NodeAddCommand(context->parent(), ai.node)); // Add node to the context - add_command->add_child(new NodeSetPositionCommand(ai.item->GetNode(), context, scene_.context_map().value(context)->MapScenePosToNodePosInContext(ai.item->pos()))); + if (ai.item) { + qDebug() << "Placing an item!"; + add_command->add_child(new NodeSetPositionCommand(ai.node, context, scene_.context_map().value(context)->MapScenePosToNodePosInContext(ai.item->pos()))); + } } if (add_command->child_count()) { @@ -604,9 +590,16 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) // Dropped attached item onto an edge, connect it between them MultiUndoCommand *drop_edge_command = new MultiUndoCommand(); if (attached_items_.size() == 1) { - Node* dropping_node = attached_items_.first().item->GetNode(); + Node* dropping_node = nullptr; - if (drop_edge_) { + foreach (const AttachedItem &ai, attached_items_) { + if (ai.item) { + dropping_node = ai.node; + break; + } + } + + if (dropping_node && drop_edge_) { // Remove old edge drop_edge_command->add_child(new NodeEdgeRemoveCommand(drop_edge_->output(), drop_edge_->input())); @@ -625,7 +618,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } } - DetachItemsFromCursor(); + DetachItemsFromCursor(false); } else { QToolTip::showText(QCursor::pos(), tr("Nodes must be placed inside a context.")); } @@ -809,7 +802,8 @@ void NodeView::CreateNodeSlot(QAction *action) NodeViewItem *new_item = new NodeViewItem(new_node, nullptr); new_item->SetFlowDirection(scene_.GetFlowDirection()); scene_.addItem(new_item); - AttachItemsToCursor({new_item}); + + SetAttachedItems({{new_item, new_node, QPointF(0, 0)}}); } } @@ -899,23 +893,14 @@ void NodeView::NodeRemovedFromGraph() contexts_.removeOne(context); } -void NodeView::AttachItemsToCursor(const QVector& items) -{ - DetachItemsFromCursor(); - - if (!items.isEmpty()) { - for (NodeViewItem* i : items) { - attached_items_.append({i, i->pos() - items.first()->pos()}); - } - - MoveAttachedNodesToCursor(mapFromGlobal(QCursor::pos())); - } -} - -void NodeView::DetachItemsFromCursor() +void NodeView::DetachItemsFromCursor(bool delete_nodes_too) { foreach (const AttachedItem &ai, attached_items_) { delete ai.item; + + if (delete_nodes_too) { + delete ai.node; + } } attached_items_.clear(); @@ -931,7 +916,9 @@ void NodeView::MoveAttachedNodesToCursor(const QPoint& p) QPointF item_pos = mapToScene(p); for (const AttachedItem& i : qAsConst(attached_items_)) { - i.item->setPos(item_pos + i.original_pos); + if (i.item) { + i.item->setPos(item_pos + i.original_pos); + } } } @@ -990,14 +977,18 @@ void NodeView::CopyNodesToClipboardInternal(QXmlStreamWriter *writer, const QVec writer->writeStartElement(QStringLiteral("pos")); for (Node *n : nodes) { - Node::Position pos = GetAssumedPositionForSelectedNode(n); + NodeViewItem *item = GetAssumedItemForSelectedNode(n); - writer->writeStartElement(QStringLiteral("node")); - writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(n))); - writer->writeTextElement(QStringLiteral("x"), QString::number(pos.position.x())); - writer->writeTextElement(QStringLiteral("y"), QString::number(pos.position.y())); - writer->writeTextElement(QStringLiteral("expanded"), QString::number(pos.expanded)); - writer->writeEndElement(); // node + if (item) { + Node::Position pos = item->GetNodePositionData(); + + writer->writeStartElement(QStringLiteral("node")); + writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(n))); + writer->writeTextElement(QStringLiteral("x"), QString::number(pos.position.x())); + writer->writeTextElement(QStringLiteral("y"), QString::number(pos.position.y())); + writer->writeTextElement(QStringLiteral("expanded"), QString::number(pos.expanded)); + writer->writeEndElement(); // node + } } writer->writeEndElement(); // pos @@ -1077,19 +1068,27 @@ QPointF NodeView::GetEstimatedPositionForContext(NodeViewItem *item, Node *conte return item->GetNodePosition() - context_offsets_.value(context); } -Node::Position NodeView::GetAssumedPositionForSelectedNode(Node *node) +NodeViewItem *NodeView::GetAssumedItemForSelectedNode(Node *node) { // Try to find corresponding selected item foreach (NodeViewContext *ctx, scene_.context_map()) { NodeViewItem *item = ctx->GetItemFromMap(node); - if (item && item->isSelected()) { + if (item && item->GetNode() == node && item->isSelected()) { // Good enough - return Node::Position(item->GetNodePosition(), item->IsExpanded()); + return item; } } - // Fallback - return Node::Position(); + return nullptr; +} + +Node::Position NodeView::GetAssumedPositionForSelectedNode(Node *node) +{ + if (NodeViewItem *item = GetAssumedItemForSelectedNode(node)) { + return item->GetNodePositionData(); + } else { + return Node::Position(); + } } Menu *NodeView::CreateAddMenu(Menu *parent) @@ -1338,19 +1337,44 @@ void NodeView::PasteNodesInternal(const QVector &duplicate_nodes) // If no nodes were retrieved, do nothing if (!new_nodes.isEmpty()) { - QVector items(new_nodes.size()); + QVector new_attached; + + NodeViewItem *first_item = nullptr; for (int i=0; iSetFlowDirection(scene_.GetFlowDirection()); - new_item->SetNodePosition(map.value(node)); - scene_.addItem(new_item); - items[i] = new_item; + + // Determine if item had a position, if not don't create an item for it + NodeViewItem *new_item; + + if (map.contains(node)) { + new_item = new NodeViewItem(node, nullptr); + new_item->SetFlowDirection(scene_.GetFlowDirection()); + new_item->SetNodePosition(map.value(node)); + scene_.addItem(new_item); + + if (!first_item) { + first_item = new_item; + } + } else { + new_item = nullptr; + } + + new_attached.append({new_item, node, QPointF(0, 0)}); } - // Attach nodes to cursor - AttachItemsToCursor(items); + // Correct positions + if (first_item) { + for (int i=0; ipos() - ai.item->pos(); + } + } + } + + SetAttachedItems(new_attached); } } @@ -1389,4 +1413,15 @@ void NodeView::CollapseItem(NodeViewItem *item) item->setZValue(0); } +void NodeView::SetAttachedItems(const QVector &items) +{ + // Detach anything currently attached + DetachItemsFromCursor(); + + attached_items_ = items; + + // Move to cursor + MoveAttachedNodesToCursor(mapFromGlobal(QCursor::pos())); +} + } diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index c3cc73f71..77a188adc 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -133,9 +133,7 @@ protected: virtual void changeEvent(QEvent *e) override; private: - void AttachItemsToCursor(const QVector &items); - - void DetachItemsFromCursor(); + void DetachItemsFromCursor(bool delete_nodes_too = true); void SetFlowDirection(NodeViewCommon::FlowDirection dir); @@ -150,6 +148,7 @@ private: QPointF GetEstimatedPositionForContext(NodeViewItem *item, Node *context) const; + NodeViewItem *GetAssumedItemForSelectedNode(Node *node); Node::Position GetAssumedPositionForSelectedNode(Node *node); Menu *CreateAddMenu(Menu *parent); @@ -172,10 +171,12 @@ private: struct AttachedItem { NodeViewItem* item; + Node *node; QPointF original_pos; }; - QList attached_items_; + void SetAttachedItems(const QVector &items); + QVector attached_items_; NodeViewEdge* drop_edge_; NodeInput drop_input_; @@ -191,8 +192,6 @@ private: NodeViewScene scene_; - MultiUndoCommand* paste_command_; - QVector selected_nodes_; QVector contexts_; diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 7389d2cd8..01c1c6eb6 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -97,6 +97,11 @@ NodeViewItem::~NodeViewItem() Q_ASSERT(edges_.isEmpty()); } +Node::Position NodeViewItem::GetNodePositionData() const +{ + return Node::Position(GetNodePosition(), IsExpanded()); +} + QPointF NodeViewItem::GetNodePosition() const { return ScreenToNodePoint(pos(), flow_dir_); diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index 6ec3a8ca1..d91733dcc 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -54,6 +54,7 @@ public: virtual ~NodeViewItem() override; + Node::Position GetNodePositionData() const; QPointF GetNodePosition() const; void SetNodePosition(const QPointF& pos); void SetNodePosition(const Node::Position& pos); diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index d17ff3a0f..dfb4e34bb 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -737,10 +737,14 @@ void MainWindow::FocusedPanelChanged(PanelWidget *panel) if (NodePanel *node_panel = dynamic_cast(panel)) { // Set param view contexts to these - bool is_default_node_panel = node_panel == node_panel_; - param_panel_->SetIgnoreNodeFlags(!is_default_node_panel); - param_panel_->SetCreateCheckBoxes(is_default_node_panel ? kNoCheckBoxes : kCheckBoxesOnNonConnected); - param_panel_->SetContexts(node_panel->GetContexts()); + const QVector &new_ctxs = node_panel->GetContexts(); + + if (new_ctxs != param_panel_->GetContexts()) { + bool is_default_node_panel = node_panel == node_panel_; + param_panel_->SetIgnoreNodeFlags(!is_default_node_panel); + param_panel_->SetCreateCheckBoxes(is_default_node_panel ? kNoCheckBoxes : kCheckBoxesOnNonConnected); + param_panel_->SetContexts(node_panel->GetContexts()); + } } else if (TimelinePanel* timeline = dynamic_cast(panel)) { // Signal timeline focus TimelineFocused(timeline->GetConnectedViewer()); From 3d0964fb490a88c24002891c8306371c51528ffb Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 2 Jan 2022 11:14:14 -0800 Subject: [PATCH 34/34] add stub message --- app/widget/nodeparamview/nodeparamviewcontext.cpp | 9 +++++++++ app/widget/nodeparamview/nodeparamviewcontext.h | 3 +++ 2 files changed, 12 insertions(+) diff --git a/app/widget/nodeparamview/nodeparamviewcontext.cpp b/app/widget/nodeparamview/nodeparamviewcontext.cpp index c8b409f0b..b55081014 100644 --- a/app/widget/nodeparamview/nodeparamviewcontext.cpp +++ b/app/widget/nodeparamview/nodeparamviewcontext.cpp @@ -20,6 +20,8 @@ #include "nodeparamviewcontext.h" +#include + #include "node/block/clip/clip.h" namespace olive { @@ -39,6 +41,8 @@ NodeParamViewContext::NodeParamViewContext(QWidget *parent) : setBackgroundRole(QPalette::Base); Retranslate(); + + connect(title_bar(), &NodeParamViewItemTitleBar::AddEffectButtonClicked, this, &NodeParamViewContext::AddEffectButtonClicked); } void NodeParamViewContext::AddNode(NodeParamViewItem *item) @@ -89,4 +93,9 @@ void NodeParamViewContext::Retranslate() { } +void NodeParamViewContext::AddEffectButtonClicked() +{ + QMessageBox::information(this, tr("STUB"), tr("This feature is coming soon. Thanks for testing development builds of Olive :)")); +} + } diff --git a/app/widget/nodeparamview/nodeparamviewcontext.h b/app/widget/nodeparamview/nodeparamviewcontext.h index 2b59920c6..a8590271e 100644 --- a/app/widget/nodeparamview/nodeparamviewcontext.h +++ b/app/widget/nodeparamview/nodeparamviewcontext.h @@ -83,6 +83,9 @@ private: QMap items_; +private slots: + void AddEffectButtonClicked(); + }; }