began new infrastructure

Yep, this is another one of my "famous" sweeping rewrites. Expect things to break.

Goals for this are:
- Greatly simplify node connections (particularly with arrays) so the code requires less maintenance/is more stable
- Redesign node structure to address issues where UI would stall for lengthy periods of time
- Less reliance on shared ptrs/greater reliance on QObject system for inheritance/memory management
- General code cleanup and improvements
This commit is contained in:
itsmattkc
2021-01-05 11:20:38 +11:00
parent 19eabf2830
commit 181235f6ce
152 changed files with 3932 additions and 8166 deletions
+2 -11
View File
@@ -16,18 +16,9 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/nodeview/nodeview.h
widget/nodeview/nodeview.cpp
widget/nodeview/nodeviewcommon.h
widget/nodeview/nodeviewedge.h
widget/nodeview/nodeviewedge.cpp
widget/nodeview/nodeviewitem.h
widget/nodeview/nodeviewitem.cpp
widget/nodeview/nodeviewitemwidgetproxy.h
widget/nodeview/nodeviewitemwidgetproxy.cpp
widget/nodeview/nodeviewscene.h
widget/nodeview/nodeviewscene.cpp
widget/nodeview/nodeviewundo.h
widget/nodeview/nodeview.h
widget/nodeview/nodeviewundo.cpp
widget/nodeview/nodeviewundo.h
PARENT_SCOPE
)
+3 -876
View File
@@ -20,887 +20,14 @@
#include "nodeview.h"
#include <QInputDialog>
#include <QMouseEvent>
#include "core.h"
#include "nodeviewundo.h"
#include "node/factory.h"
#include "widget/menu/menushared.h"
#define super HandMovableView
namespace olive {
NodeView::NodeView(QWidget *parent) :
NodeView::NodeView(QWidget* parent) :
HandMovableView(parent),
graph_(nullptr),
drop_edge_(nullptr),
filter_mode_(kFilterShowSelectedBlocks),
scale_(1.0)
graph_(nullptr)
{
setScene(&scene_);
SetDefaultDragMode(RubberBandDrag);
setContextMenuPolicy(Qt::CustomContextMenu);
setMouseTracking(true);
setRenderHint(QPainter::Antialiasing);
setViewportUpdateMode(FullViewportUpdate);
connect(&scene_, &QGraphicsScene::changed, this, &NodeView::ItemsChanged);
connect(this, &NodeView::customContextMenuRequested, this, &NodeView::ShowContextMenu);
ConnectSelectionChangedSignal();
SetFlowDirection(NodeViewCommon::kTopToBottom);
// Set massive scene rect and hide the scrollbars to create an "infinite space" effect
scene_.setSceneRect(-1000000, -1000000, 2000000, 2000000);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
select_blocks_internal_timer_.setSingleShot(true);
select_blocks_internal_timer_.setInterval(100);
connect(&select_blocks_internal_timer_, &QTimer::timeout, this, &NodeView::SelectBlocksInternal);
}
NodeView::~NodeView()
{
// Unset the current graph
SetGraph(nullptr);
}
void NodeView::SetGraph(NodeGraph *graph)
{
if (graph_ == graph) {
return;
}
if (graph_ != nullptr) {
disconnect(graph_, &NodeGraph::NodeAdded, &scene_, &NodeViewScene::AddNode);
disconnect(graph_, &NodeGraph::NodeRemoved, &scene_, &NodeViewScene::RemoveNode);
disconnect(graph_, &NodeGraph::NodeRemoved, this, &NodeView::GraphNodeRemoved);
disconnect(graph_, &NodeGraph::EdgeAdded, this, &NodeView::GraphEdgeAdded);
disconnect(graph_, &NodeGraph::EdgeRemoved, this, &NodeView::GraphEdgeRemoved);
}
// Clear the scene of all UI objects
scene_.clear();
// Set reference to the graph
graph_ = graph;
scene_.SetGraph(graph_);
// If the graph is valid, add UI objects for each of its Nodes
if (graph_ != nullptr) {
connect(graph_, &NodeGraph::NodeAdded, &scene_, &NodeViewScene::AddNode);
connect(graph_, &NodeGraph::NodeRemoved, &scene_, &NodeViewScene::RemoveNode);
connect(graph_, &NodeGraph::NodeRemoved, this, &NodeView::GraphNodeRemoved);
connect(graph_, &NodeGraph::EdgeAdded, this, &NodeView::GraphEdgeAdded);
connect(graph_, &NodeGraph::EdgeRemoved, this, &NodeView::GraphEdgeRemoved);
foreach (Node* n, graph_->nodes()) {
scene_.AddNode(n);
}
ValidateFilter();
}
}
void NodeView::DeleteSelected()
{
if (!graph_) {
return;
}
QUndoCommand* command = new QUndoCommand();
{
QVector<NodeEdge*> selected_edges = scene_.GetSelectedEdges();
foreach (NodeEdge* edge, selected_edges) {
new NodeEdgeRemoveCommand(edge->output(), edge->input(), command);
}
}
{
QVector<Node*> selected_nodes = scene_.GetSelectedNodes();
// Ensure no nodes are "undeletable"
for (int i=0;i<selected_nodes.size();i++) {
if (!selected_nodes.at(i)->CanBeDeleted()) {
selected_nodes.removeAt(i);
i--;
}
}
if (!selected_nodes.isEmpty()) {
new NodeRemoveCommand(graph_, selected_nodes, command);
}
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
}
void NodeView::SelectAll()
{
// Optimization: rather than respond to every single item being selected, ignore the signal and
// then handle them all at the end.
DisconnectSelectionChangedSignal();
scene_.SelectAll();
ConnectSelectionChangedSignal();
SceneSelectionChangedSlot();
}
void NodeView::DeselectAll()
{
// Optimization: rather than respond to every single item being selected, ignore the signal and
// then handle them all at the end.
DisconnectSelectionChangedSignal();
scene_.DeselectAll();
ConnectSelectionChangedSignal();
SceneSelectionChangedSlot();
}
void NodeView::Select(const QVector<Node *> &nodes)
{
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();
scene_.DeselectAll();
foreach (Node* n, nodes) {
NodeViewItem* item = scene_.NodeToUIObject(n);
item->setSelected(true);
}
ConnectSelectionChangedSignal();
SceneSelectionChangedSlot();
}
void NodeView::SelectWithDependencies(QVector<Node *> nodes)
{
if (!graph_) {
return;
}
int original_length = nodes.size();
for (int i=0;i<original_length;i++) {
nodes.append(nodes.at(i)->GetDependencies());
}
Select(nodes);
}
void NodeView::SelectBlocks(const QVector<Block *> &blocks)
{
if (!graph_) {
return;
}
selected_blocks_.append(blocks);
QueueSelectBlocksInternal();
}
void NodeView::DeselectBlocks(const QVector<Block *> &blocks)
{
if (!graph_) {
return;
}
// Remove temporary associations
foreach (Block* b, selected_blocks_) {
if (!blocks.contains(b)) {
temporary_association_map_.remove(b);
}
}
// Remove blocks from selected array
foreach (Block* b, blocks) {
selected_blocks_.removeOne(b);
}
QueueSelectBlocksInternal();
}
void NodeView::CopySelected(bool cut)
{
if (!graph_) {
return;
}
QVector<Node*> selected = scene_.GetSelectedNodes();
if (selected.isEmpty()) {
return;
}
CopyNodesToClipboard(selected);
if (cut) {
DeleteSelected();
}
}
void NodeView::Paste()
{
if (!graph_) {
return;
}
QUndoCommand* command = new QUndoCommand();
QVector<Node*> pasted_nodes = PasteNodesFromClipboard(static_cast<Sequence*>(graph_), command);
Core::instance()->undo_stack()->pushIfHasChildren(command);
if (!pasted_nodes.isEmpty()) {
foreach (Node* n, pasted_nodes) {
AssociateNodeWithSelectedBlocks(n);
}
AttachNodesToCursor(pasted_nodes);
}
}
void NodeView::Duplicate()
{
if (!graph_) {
return;
}
QVector<Node*> selected = scene_.GetSelectedNodes();
if (selected.isEmpty()) {
return;
}
QUndoCommand* command = new QUndoCommand();
QVector<Node*> duplicated_nodes = Node::CopyDependencyGraph(selected, command);
Core::instance()->undo_stack()->pushIfHasChildren(command);
AttachNodesToCursor(duplicated_nodes);
}
void NodeView::ItemsChanged()
{
QHash<NodeEdge *, NodeViewEdge *>::const_iterator i;
for (i=scene_.edge_map().constBegin(); i!=scene_.edge_map().constEnd(); i++) {
i.value()->Adjust();
}
}
void NodeView::keyPressEvent(QKeyEvent *event)
{
super::keyPressEvent(event);
if (event->key() == Qt::Key_Escape && !attached_items_.isEmpty()) {
DetachItemsFromCursor();
// We undo the last action which SHOULD be adding the node
// FIXME: Possible danger of this not being the case?
Core::instance()->undo_stack()->undo();
}
}
void NodeView::mousePressEvent(QMouseEvent *event)
{
if (HandPress(event)) return;
if (event->button() == Qt::RightButton) {
// Qt doesn't do this by default for some reason
if (!(event->modifiers() & Qt::ShiftModifier)) {
scene_.clearSelection();
}
// If there's an item here, select it
QGraphicsItem* item = itemAt(event->pos());
if (item) {
item->setSelected(true);
}
}
super::mousePressEvent(event);
}
void NodeView::mouseMoveEvent(QMouseEvent *event)
{
if (HandMove(event)) return;
super::mouseMoveEvent(event);
if (!attached_items_.isEmpty()) {
MoveAttachedNodesToCursor(event->pos());
// See if the user clicked on an edge (only when dropping single nodes)
if (attached_items_.size() == 1) {
Node* attached_node = attached_items_.first().item->GetNode();
QRect edge_detect_rect(event->pos(), event->pos());
int edge_detect_radius = fontMetrics().height();
edge_detect_rect.adjust(-edge_detect_radius, -edge_detect_radius, edge_detect_radius, edge_detect_radius);
QList<QGraphicsItem*> items = this->items(edge_detect_rect);
NodeViewEdge* new_drop_edge = nullptr;
// See if there is an edge here
foreach (QGraphicsItem* item, items) {
new_drop_edge = dynamic_cast<NodeViewEdge*>(item);
if (new_drop_edge) {
drop_input_ = nullptr;
foreach (NodeParam* param, attached_node->parameters()) {
if (param->type() == NodeParam::kInput) {
NodeInput* input = static_cast<NodeInput*>(param);
if (input->is_connectable()) {
if (input->data_type() & new_drop_edge->edge()->input()->data_type()) {
drop_input_ = input;
break;
} else if (!drop_input_) {
drop_input_ = input;
}
}
}
}
if (drop_input_) {
break;
} else {
new_drop_edge = nullptr;
}
}
}
if (drop_edge_ != new_drop_edge) {
if (drop_edge_) {
drop_edge_->SetHighlighted(false);
}
drop_edge_ = new_drop_edge;
if (drop_edge_) {
drop_edge_->SetHighlighted(true);
}
}
}
}
}
void NodeView::mouseReleaseEvent(QMouseEvent *event)
{
if (HandRelease(event)) return;
if (!attached_items_.isEmpty()) {
if (attached_items_.size() == 1) {
Node* dropping_node = attached_items_.first().item->GetNode();
if (drop_edge_) {
NodeEdgePtr old_edge = drop_edge_->edge();
// We have everything we need to place the node in between
QUndoCommand* command = new QUndoCommand();
// Remove old edge
new NodeEdgeRemoveCommand(old_edge, command);
// Place new edges
new NodeEdgeAddCommand(old_edge->output(), drop_input_, command);
new NodeEdgeAddCommand(dropping_node->output(), old_edge->input(), command);
Core::instance()->undo_stack()->push(command);
}
drop_edge_ = nullptr;
}
DetachItemsFromCursor();
}
super::mouseReleaseEvent(event);
}
void NodeView::wheelEvent(QWheelEvent *event)
{
if (event->modifiers() & Qt::ControlModifier) {
// FIXME: Hardcoded divider (0.001)
qreal multiplier = 1.0 + (static_cast<qreal>(event->angleDelta().x() + event->angleDelta().y()) * 0.001);
double test_scale = scale_ * multiplier;
if (test_scale > 0.1) {
scale(multiplier, multiplier);
scale_ = test_scale;
}
} else {
QWidget::wheelEvent(event);
}
}
void NodeView::SceneSelectionChangedSlot()
{
QVector<Node*> current_selection = scene_.GetSelectedNodes();
QVector<Node*> selected;
QVector<Node*> 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 {
foreach (Node* n, current_selection) {
if (!selected_nodes_.contains(n)) {
selected.append(n);
}
}
}
// Determine which nodes are newly deselected
if (current_selection.isEmpty()) {
// All nodes that were selected have been deselected
deselected = selected_nodes_;
} else {
foreach (Node* n, selected_nodes_) {
if (!current_selection.contains(n)) {
deselected.append(n);
}
}
}
selected_nodes_ = current_selection;
if (!selected.isEmpty()) {
emit NodesSelected(selected);
}
if (!deselected.isEmpty()) {
emit NodesDeselected(deselected);
}
}
void NodeView::ShowContextMenu(const QPoint &pos)
{
if (!graph_) {
return;
}
Menu m;
MenuShared::instance()->AddItemsForEditMenu(&m, false);
m.addSeparator();
QVector<NodeViewItem*> selected = scene_.GetSelectedItems();
if (itemAt(pos) && !selected.isEmpty()) {
// Label node action
QAction* label_action = m.addAction(tr("Label"));
connect(label_action, &QAction::triggered, this, [this](){
Core::instance()->LabelNodes(scene_.GetSelectedNodes());
});
m.addSeparator();
// Auto-position action
QAction* autopos = m.addAction(tr("Auto-Position"));
connect(autopos, &QAction::triggered, this, &NodeView::AutoPositionDescendents);
} else {
QAction* curved_action = m.addAction(tr("Smooth Edges"));
curved_action->setCheckable(true);
curved_action->setChecked(scene_.GetEdgesAreCurved());
connect(curved_action, &QAction::triggered, &scene_, &NodeViewScene::SetEdgesAreCurved);
m.addSeparator();
Menu* filter_menu = new Menu(tr("Filter"), &m);
m.addMenu(filter_menu);
filter_menu->AddActionWithData(tr("Show All"),
kFilterShowAll,
filter_mode_);
filter_menu->AddActionWithData(tr("Show Selected Blocks Only"),
kFilterShowSelectedBlocks,
filter_mode_);
connect(filter_menu, &Menu::triggered, this, &NodeView::ContextMenuFilterChanged);
Menu* direction_menu = new Menu(tr("Direction"), &m);
m.addMenu(direction_menu);
direction_menu->AddActionWithData(tr("Top to Bottom"),
NodeViewCommon::kTopToBottom,
scene_.GetFlowDirection());
direction_menu->AddActionWithData(tr("Bottom to Top"),
NodeViewCommon::kBottomToTop,
scene_.GetFlowDirection());
direction_menu->AddActionWithData(tr("Left to Right"),
NodeViewCommon::kLeftToRight,
scene_.GetFlowDirection());
direction_menu->AddActionWithData(tr("Right to Left"),
NodeViewCommon::kRightToLeft,
scene_.GetFlowDirection());
connect(direction_menu, &Menu::triggered, this, &NodeView::ContextMenuSetDirection);
m.addSeparator();
Menu* add_menu = NodeFactory::CreateMenu(&m);
add_menu->setTitle(tr("Add"));
connect(add_menu, &Menu::triggered, this, &NodeView::CreateNodeSlot);
m.addMenu(add_menu);
}
m.exec(mapToGlobal(pos));
}
void NodeView::CreateNodeSlot(QAction *action)
{
Node* new_node = NodeFactory::CreateFromMenuAction(action);
if (new_node) {
// Associate this new node with these blocks (allows it to be visible with them even while it
// isn't connected to anything)
AssociateNodeWithSelectedBlocks(new_node);
Core::instance()->undo_stack()->push(new NodeAddCommand(graph_, new_node));
NodeViewItem* item = scene_.NodeToUIObject(new_node);
AttachItemsToCursor({item});
}
}
void NodeView::ContextMenuSetDirection(QAction *action)
{
SetFlowDirection(static_cast<NodeViewCommon::FlowDirection>(action->data().toInt()));
}
void NodeView::AutoPositionDescendents()
{
QVector<Node*> selected = scene_.GetSelectedNodes();
foreach (Node* n, selected) {
scene_.ReorganizeFrom(n);
}
}
void NodeView::ContextMenuFilterChanged(QAction *action)
{
FilterMode filter = static_cast<FilterMode>(action->data().toInt());
if (filter_mode_ != filter) {
filter_mode_ = filter;
if (filter == kFilterShowAll) {
// Un-hide all blocks
foreach (NodeViewItem* item, scene_.item_map()) {
item->setVisible(true);
}
foreach (NodeViewEdge* edge, scene_.edge_map()) {
edge->setVisible(true);
}
}
ValidateFilter();
}
}
void NodeView::AttachNodesToCursor(const QVector<Node *> &nodes)
{
QVector<NodeViewItem*> items(nodes.size());
for (int i=0; i<nodes.size(); i++) {
items[i] = scene_.NodeToUIObject(nodes.at(i));
}
AttachItemsToCursor(items);
}
void NodeView::AttachItemsToCursor(const QVector<NodeViewItem*>& items)
{
DetachItemsFromCursor();
if (!items.isEmpty()) {
foreach (NodeViewItem* i, items) {
attached_items_.append({i, i->pos() - items.first()->pos()});
}
setMouseTracking(true);
MoveAttachedNodesToCursor(mapFromGlobal(QCursor::pos()));
}
}
void NodeView::DetachItemsFromCursor()
{
attached_items_.clear();
setMouseTracking(false);
}
void NodeView::SetFlowDirection(NodeViewCommon::FlowDirection dir)
{
scene_.SetFlowDirection(dir);
}
void NodeView::MoveAttachedNodesToCursor(const QPoint& p)
{
QPointF item_pos = mapToScene(p);
foreach (const AttachedItem& i, attached_items_) {
i.item->setPos(item_pos + i.original_pos);
}
}
void NodeView::ConnectSelectionChangedSignal()
{
connect(&scene_, &QGraphicsScene::selectionChanged, this, &NodeView::SceneSelectionChangedSlot);
}
void NodeView::DisconnectSelectionChangedSignal()
{
disconnect(&scene_, &QGraphicsScene::selectionChanged, this, &NodeView::SceneSelectionChangedSlot);
}
void NodeView::UpdateBlockFilter()
{
// Hide all nodes
foreach (NodeViewItem* item, scene_.item_map()) {
item->setVisible(false);
}
bool first = true;
QPointF last_bottom_right;
QVector<Node*> currently_visible;
foreach (Block* b, selected_blocks_) {
// Auto-position this node's dependencies
scene_.ReorganizeFrom(b);
// Start calculating the bounding rect of this node's deps
QPointF node_pos = b->GetPosition();
QRectF anchor(node_pos, node_pos);
QVector<Node*> deps = b->GetDependencies();
foreach (Node* d, deps) {
QPointF dep_pos = d->GetPosition();
anchor.setLeft(qMin(anchor.left(), dep_pos.x()));
anchor.setRight(qMax(anchor.right(), dep_pos.x()));
anchor.setTop(qMin(anchor.top(), dep_pos.y()));
anchor.setBottom(qMax(anchor.bottom(), dep_pos.y()));
NodeViewItem* item = scene_.NodeToUIObject(d);
if (item) {
item->setVisible(true);
}
}
// Shift the bounding rect in relation to the other bounding rects
if (first) {
first = false;
} else {
QPointF desired_anchor_pos = last_bottom_right + QPointF(0, 2);
QPointF necessary_movement = anchor.topRight() - desired_anchor_pos;
// Calculate the bottom right from which we'll anchor the next rect
last_bottom_right = anchor.bottomRight() - necessary_movement;
b->SetPosition(b->GetPosition() - necessary_movement);
foreach (Node* d, deps) {
d->SetPosition(d->GetPosition() - necessary_movement);
}
}
// Now that we're done calculating the bounding rect, add this block...
deps.append(b);
// ...then add its associations
deps.append(temporary_association_map_[b]);
for (auto i=association_map_.begin(); i!=association_map_.end(); i++) {
if (i.value().contains(b)) {
deps.append(i.key());
}
}
// And make sure all nodes are shown
foreach (Node* n, deps) {
NodeViewItem* item = scene_.NodeToUIObject(n);
if (item) {
item->setVisible(true);
}
}
// And lastly, add them all to our currently visible list
currently_visible.append(deps);
}
// Show only edges between those dependencies
foreach (NodeViewEdge* edge, scene_.edge_map()) {
edge->setVisible((currently_visible.contains(edge->edge()->input_node())
&& currently_visible.contains(edge->edge()->output_node())));
}
}
void NodeView::AssociateNodeWithSelectedBlocks(Node *n)
{
association_map_.insert(n, selected_blocks_);
connect(n, &Node::destroyed, this, &NodeView::AssociatedNodeDestroyed, Qt::DirectConnection);
}
void NodeView::DisassociateNode(Node *n, bool remove_from_map)
{
if (remove_from_map) {
association_map_.remove(n);
}
disconnect(n, &Node::destroyed, this, &NodeView::AssociatedNodeDestroyed);
}
void NodeView::QueueSelectBlocksInternal()
{
select_blocks_internal_timer_.stop();
select_blocks_internal_timer_.start();
}
void NodeView::SelectBlocksInternal()
{
// Block scene signals while our selection is changing a lot
DisconnectSelectionChangedSignal();
if (filter_mode_ == kFilterShowSelectedBlocks) {
UpdateBlockFilter();
}
QVector<Node*> nodes;
nodes.reserve(selected_blocks_.size());
foreach (Block* b, selected_blocks_) {
nodes.append(b);
nodes.append(b->GetDependencies());
}
SelectWithDependencies(nodes);
// Stop blocking signals and send a change signal now that all of our processing is done
ConnectSelectionChangedSignal();
SceneSelectionChangedSlot();
if (!selected_blocks_.isEmpty()) {
NodeViewItem* item = scene_.NodeToUIObject(selected_blocks_.first());
if (item) {
centerOn(item);
}
}
}
void NodeView::ValidateFilter()
{
// Force auto-positioning
switch (filter_mode_) {
case kFilterShowAll:
// NOTE: Assumes Sequence
scene_.ReorganizeFrom(static_cast<Sequence*>(graph_)->viewer_output());
break;
case kFilterShowSelectedBlocks:
UpdateBlockFilter();
break;
}
}
void NodeView::AssociatedNodeDestroyed()
{
DisassociateNode(static_cast<Node*>(sender()), true);
}
void NodeView::GraphNodeRemoved(Node *node)
{
if (selected_blocks_.contains(static_cast<Block*>(node))) {
DeselectBlocks({static_cast<Block*>(node)});
}
}
void NodeView::GraphEdgeAdded(NodeEdgePtr edge)
{
Node* input_node = edge->input()->parentNode();
if (input_node->OutputsTo(static_cast<Sequence*>(graph_)->viewer_output(), true)) {
auto i = association_map_.begin();
while (i != association_map_.end()) {
if (input_node->InputsFrom(i.key(), true)) {
i = association_map_.erase(i);
} else {
i++;
}
}
}
scene_.AddEdge(edge);
ValidateFilter();
}
void NodeView::GraphEdgeRemoved(NodeEdgePtr edge)
{
scene_.RemoveEdge(edge);
Node* output_node = edge->output_node();
// Check if this disconnected node still connects to a selected block, in which case do nothing
foreach (Block* b, selected_blocks_) {
if (output_node->OutputsTo(b, true)) {
return;
}
}
QVector<Node*> disconnected_nodes;
disconnected_nodes.append(output_node);
disconnected_nodes.append(output_node->GetDependencies());
if (output_node->OutputsTo(static_cast<Sequence*>(graph_)->viewer_output(), true)) {
// Check if this disconnected node still has a path to the viewer somewhere else
foreach (Block* b, selected_blocks_) {
QVector<Node*>& temp_assocs = temporary_association_map_[b];
temp_assocs.append(disconnected_nodes);
}
} else {
// Otherwise, we must associate these nodes
foreach (Node* n, disconnected_nodes) {
AssociateNodeWithSelectedBlocks(n);
}
}
}
}
+11 -136
View File
@@ -22,172 +22,47 @@
#define NODEVIEW_H
#include <QGraphicsView>
#include <QTimer>
#include "node/graph.h"
#include "node/nodecopypaste.h"
#include "nodeviewscene.h"
#include "widget/handmovableview/handmovableview.h"
namespace olive {
/**
* @brief A widget for viewing and editing node graphs
*
* This widget takes a NodeGraph object and constructs a QGraphicsScene representing its data, viewing and allowing
* the user to make modifications to it.
*/
class NodeView : public HandMovableView, public NodeCopyPasteService
{
Q_OBJECT
public:
NodeView(QWidget* parent);
virtual ~NodeView() override;
/**
* @brief Sets the graph to view
*/
void SetGraph(NodeGraph* graph);
void SetGraph(NodeGraph* graph){}
/**
* @brief Delete selected nodes from graph (user-friendly/undoable)
*/
void DeleteSelected();
void DeleteSelected(){}
void SelectAll();
void DeselectAll();
void SelectAll(){}
void DeselectAll(){}
void Select(const QVector<Node*>& nodes);
void SelectWithDependencies(QVector<Node *> nodes);
void Select(const QVector<Node*>& nodes){}
void SelectWithDependencies(QVector<Node *> nodes){}
void CopySelected(bool cut);
void Paste();
void CopySelected(bool cut){}
void Paste(){}
void Duplicate();
void Duplicate(){}
void SelectBlocks(const QVector<Block*>& blocks);
void SelectBlocks(const QVector<Block*>& blocks){}
void DeselectBlocks(const QVector<Block*>& blocks);
signals:
void NodesSelected(const QVector<Node*>& nodes);
void NodesDeselected(const QVector<Node*>& nodes);
protected:
virtual void keyPressEvent(QKeyEvent *event) override;
virtual void mousePressEvent(QMouseEvent *event) override;
virtual void mouseMoveEvent(QMouseEvent *event) override;
virtual void mouseReleaseEvent(QMouseEvent* event) override;
virtual void wheelEvent(QWheelEvent* event) override;
void DeselectBlocks(const QVector<Block*>& blocks){}
private:
void AttachNodesToCursor(const QVector<Node *> &nodes);
void AttachItemsToCursor(const QVector<NodeViewItem *> &items);
void DetachItemsFromCursor();
void SetFlowDirection(NodeViewCommon::FlowDirection dir);
void MoveAttachedNodesToCursor(const QPoint &p);
void ConnectSelectionChangedSignal();
void DisconnectSelectionChangedSignal();
void UpdateBlockFilter();
void AssociateNodeWithSelectedBlocks(Node* n);
void DisassociateNode(Node* n, bool remove_from_map);
void QueueSelectBlocksInternal();
QTimer select_blocks_internal_timer_;
NodeGraph* graph_;
struct AttachedItem {
NodeViewItem* item;
QPointF original_pos;
};
QList<AttachedItem> attached_items_;
NodeViewEdge* drop_edge_;
NodeInput* drop_input_;
NodeViewScene scene_;
QVector<Node*> selected_nodes_;
QVector<Block*> selected_blocks_;
QHash<Node*, QVector<Block*> > association_map_;
QHash<Block*, QVector<Node*> > temporary_association_map_;
enum FilterMode {
kFilterShowAll,
kFilterShowSelectedBlocks
};
FilterMode filter_mode_;
double scale_;
private slots:
void ValidateFilter();
void AssociatedNodeDestroyed();
void GraphNodeRemoved(Node* node);
void GraphEdgeAdded(NodeEdgePtr edge);
void GraphEdgeRemoved(NodeEdgePtr edge);
/**
* @brief Internal function triggered when any change is signalled from the QGraphicsScene
*
* Current primary function is to inform all NodeViewEdges to re-adjust in case any Nodes have moved
*/
void ItemsChanged();
/**
* @brief Receiver for when the scene's selected items change
*/
void SceneSelectionChangedSlot();
/**
* @brief Receiver for when the user right clicks (or otherwise requests a context menu)
*/
void ShowContextMenu(const QPoint &pos);
/**
* @brief Receiver for when the user requests a new node from the add menu
*/
void CreateNodeSlot(QAction* action);
/**
* @brief Receiver for setting the direction from the context menu
*/
void ContextMenuSetDirection(QAction* action);
/**
* @brief Receiver for auto-position descendents menu action
*/
void AutoPositionDescendents();
/**
* @brief Receiver for the user changing the filter
*/
void ContextMenuFilterChanged(QAction* action);
void SelectBlocksInternal();
};
}
-58
View File
@@ -1,58 +0,0 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#ifndef NODEVIEWCOMMON_H
#define NODEVIEWCOMMON_H
#include <QtGlobal>
#include "common/define.h"
namespace olive {
class NodeViewCommon {
public:
enum FlowDirection {
kTopToBottom,
kBottomToTop,
kLeftToRight,
kRightToLeft
};
static Qt::Orientation GetFlowOrientation(FlowDirection dir) {
if (dir == kTopToBottom || dir == kBottomToTop) {
return Qt::Vertical;
} else {
return Qt::Horizontal;
}
}
static bool DirectionsAreOpposing(FlowDirection a, FlowDirection b) {
return ((a == NodeViewCommon::kLeftToRight && b == NodeViewCommon::kRightToLeft)
|| (a == NodeViewCommon::kRightToLeft && b == NodeViewCommon::kLeftToRight)
|| (a == NodeViewCommon::kTopToBottom && b == NodeViewCommon::kBottomToTop)
|| (a == NodeViewCommon::kBottomToTop && b == NodeViewCommon::kTopToBottom));
}
};
}
#endif // NODEVIEWCOMMON_H
-223
View File
@@ -1,223 +0,0 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#include "nodeviewedge.h"
#include <QApplication>
#include <QDebug>
#include <QGraphicsSceneMouseEvent>
#include <QStyleOptionGraphicsItem>
#include "common/bezier.h"
#include "common/lerp.h"
#include "nodeview.h"
#include "nodeviewscene.h"
namespace olive {
NodeViewEdge::NodeViewEdge(QGraphicsItem *parent) :
QGraphicsPathItem(parent),
edge_(nullptr),
connected_(false),
highlighted_(false),
flow_dir_(NodeViewCommon::kLeftToRight),
curved_(true)
{
setFlag(QGraphicsItem::ItemIsSelectable);
// Ensures this UI object is drawn behind other objects
setZValue(-1);
// 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::SetEdge(NodeEdgePtr edge)
{
SetConnected(true);
// Set the new edge pointer
edge_ = edge;
// Re-adjust the line positioning for this new edge
Adjust();
}
NodeEdgePtr NodeViewEdge::edge()
{
return edge_;
}
void NodeViewEdge::Adjust()
{
if (!edge_ || !scene()) {
return;
}
// Get the UI objects of both nodes that this edge connects
NodeViewItem* output = static_cast<NodeViewScene*>(scene())->NodeToUIObject(edge_->output()->parentNode());
NodeViewItem* input = static_cast<NodeViewScene*>(scene())->NodeToUIObject(edge_->input()->parentNode());
if (!output || !input) {
return;
}
// Draw a line between the two
SetPoints(output->GetParamPoint(edge_->output(), output->pos()),
input->GetParamPoint(edge_->input(), output->pos()),
input->IsExpanded());
}
void NodeViewEdge::SetConnected(bool c)
{
connected_ = c;
update();
}
void NodeViewEdge::SetHighlighted(bool e)
{
highlighted_ = e;
update();
}
void NodeViewEdge::SetPoints(const QPointF &start, const QPointF &end, bool input_is_expanded)
{
QPainterPath path;
path.moveTo(start);
double angle = qAtan2(end.y() - start.y(), end.x() - start.x());
if (curved_) {
double half_x = lerp(start.x(), end.x(), 0.5);
double half_y = lerp(start.y(), end.y(), 0.5);
QPointF cp1, cp2;
if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal) {
cp1 = QPointF(half_x, start.y());
} else {
cp1 = QPointF(start.x(), half_y);
}
if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal || input_is_expanded) {
cp2 = QPointF(half_x, end.y());
} else {
cp2 = QPointF(end.x(), half_y);
}
path.cubicTo(cp1, cp2, end);
if (!qFuzzyCompare(start.x(), end.x())) {
double continue_x = end.x() - qCos(angle)*arrow_size_;
double x1, x2, x3, x4, y1, y2, y3, y4;
if (start.x() < end.x()) {
x1 = start.x();
x2 = cp1.x();
x3 = cp2.x();
x4 = end.x();
y1 = start.y();
y2 = cp1.y();
y3 = cp2.y();
y4 = end.y();
} else {
x1 = end.x();
x2 = cp2.x();
x3 = cp1.x();
x4 = start.x();
y1 = end.y();
y2 = cp2.y();
y3 = cp1.y();
y4 = start.y();
}
double t = Bezier::CubicXtoT(continue_x, x1, x2, x3, x4);
double y = Bezier::CubicTtoY(y1, y2, y3, y4, t);
angle = qAtan2(end.y() - y, end.x() - continue_x);
}
} else {
path.lineTo(end);
}
setPath(path);
const double arrow_angle = 150.0 * 3.141592 / 180.0;
QVector<QPointF> 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);
}
void NodeViewEdge::SetFlowDirection(NodeViewCommon::FlowDirection dir)
{
flow_dir_ = dir;
Adjust();
}
void NodeViewEdge::SetCurved(bool e)
{
curved_ = e;
update();
}
void NodeViewEdge::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *)
{
QPalette::ColorGroup group;
QPalette::ColorRole role;
if (connected_) {
group = QPalette::Active;
} else {
group = QPalette::Disabled;
}
if (highlighted_ != bool(option->state & QStyle::State_Selected)) {
role = QPalette::Highlight;
} else {
role = QPalette::Text;
}
// Draw main path
QColor edge_color = qApp->palette().color(group, role);
painter->setPen(QPen(edge_color, edge_width_));
painter->setBrush(Qt::NoBrush);
painter->drawPath(path());
// Draw arrow
painter->setPen(Qt::NoPen);
painter->setBrush(edge_color);
painter->drawPolygon(arrow_);
}
}
-121
View File
@@ -1,121 +0,0 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#ifndef NODEEDGEITEM_H
#define NODEEDGEITEM_H
#include <QGraphicsPathItem>
#include <QPalette>
#include "node/edge.h"
#include "nodeviewcommon.h"
namespace olive {
/**
* @brief A graphical representation of a NodeEdge to be used in NodeView
*
* A fairly simple line widget use to visualize a connection between two node parameters (a NodeEdge).
*/
class NodeViewEdge : public QGraphicsPathItem
{
public:
NodeViewEdge(QGraphicsItem* parent = nullptr);
/**
* @brief Set the edge that this item corresponds to
*
* This can be changed at any time (but under most circumstances won't be). Calling this will automatically call
* Adjust() to move this item into the correct position.
*/
void SetEdge(NodeEdgePtr edge);
NodeEdgePtr edge();
/**
* @brief Moves/updates this line to visually connect between the two corresponding NodeViewItems
*
* Using the attached edge (see SetEdge()), this function retrieves the NodeViewItems representing the two nodes
* that this edge connects. It uses their positions to determine where the line should visually connect and sets
* it accordingly.
*
* This should be set any time the NodeEdge changes (see SetEdge()), and any time the nodes move in the NodeGraph
* (see NodeView::ItemsChanged()). This will keep the nodes visually connected at all times.
*/
void Adjust();
/**
* @brief Set the connected state of this line
*
* When the edge is not connected, it visually depicts this by coloring the line grey. When an edge is connected or
* a potential connection is valid, the line is colored white. This function sets whether the line should be grey
* (false) or white (true).
*
* Using SetEdge() automatically sets this to true. Under most circumstances this should be left alone, and only
* be set when an edge is being created/dragged.
*/
void SetConnected(bool c);
/**
* @brief Set highlighted state
*
* Changes color of edge.
*/
void SetHighlighted(bool e);
/**
* @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);
/**
* @brief Set whether edges should be drawn as curved or as straight lines
*/
void SetCurved(bool e);
protected:
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
private:
NodeEdgePtr edge_;
int edge_width_;
bool connected_;
bool highlighted_;
NodeViewCommon::FlowDirection flow_dir_;
bool curved_;
QPolygonF arrow_;
int arrow_size_;
};
}
#endif // NODEEDGEITEM_H
-702
View File
@@ -1,702 +0,0 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#include "nodeviewitem.h"
#include <QDebug>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <QStyleOptionGraphicsItem>
#include "common/flipmodifiers.h"
#include "common/qtutils.h"
#include "config/config.h"
#include "core.h"
#include "nodeview.h"
#include "nodeviewscene.h"
#include "nodeviewundo.h"
#include "ui/icons/icons.h"
#include "window/mainwindow/mainwindow.h"
namespace olive {
NodeViewItem::NodeViewItem(QGraphicsItem *parent) :
QGraphicsRectItem(parent),
node_(nullptr),
dragging_edge_(nullptr),
cached_drop_item_(nullptr),
cached_drop_item_expanded_(false),
expanded_(false),
hide_titlebar_(false),
standard_click_(false),
highlighted_index_(-1),
node_edge_change_command_(nullptr),
flow_dir_(NodeViewCommon::kLeftToRight)
{
// 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
//
// 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_);
}
QPointF NodeViewItem::GetNodePosition() const
{
QPointF node_pos;
qreal adjusted_x = pos().x() / DefaultItemHorizontalPadding();
qreal adjusted_y = pos().y() / DefaultItemVerticalPadding();
switch (flow_dir_) {
case NodeViewCommon::kLeftToRight:
node_pos.setX(adjusted_x);
node_pos.setY(adjusted_y);
break;
case NodeViewCommon::kRightToLeft:
node_pos.setX(-adjusted_x);
node_pos.setY(adjusted_y);
break;
case NodeViewCommon::kTopToBottom:
node_pos.setX(adjusted_y);
node_pos.setY(adjusted_x);
break;
case NodeViewCommon::kBottomToTop:
node_pos.setX(-adjusted_y);
node_pos.setY(adjusted_x);
break;
}
return node_pos;
}
void NodeViewItem::SetNodePosition(const QPointF &pos)
{
switch (flow_dir_) {
case NodeViewCommon::kLeftToRight:
setPos(pos.x() * DefaultItemHorizontalPadding(),
pos.y() * DefaultItemVerticalPadding());
break;
case NodeViewCommon::kRightToLeft:
setPos(-pos.x() * DefaultItemHorizontalPadding(),
pos.y() * DefaultItemVerticalPadding());
break;
case NodeViewCommon::kTopToBottom:
setPos(pos.y() * DefaultItemHorizontalPadding(),
pos.x() * DefaultItemVerticalPadding());
break;
case NodeViewCommon::kBottomToTop:
setPos(pos.y() * DefaultItemHorizontalPadding(),
-pos.x() * DefaultItemVerticalPadding());
break;
}
}
int NodeViewItem::DefaultTextPadding()
{
return QFontMetrics(QFont()).height() / 4;
}
int NodeViewItem::DefaultItemHeight()
{
return QFontMetrics(QFont()).height() + DefaultTextPadding() * 2;
}
int NodeViewItem::DefaultItemWidth()
{
return QtUtils::QFontMetricsWidth(QFontMetrics(QFont()), "HHHHHHHHHHHH");;
}
int NodeViewItem::DefaultItemBorder()
{
return QFontMetrics(QFont()).height() / 12;
}
qreal NodeViewItem::DefaultItemHorizontalPadding() const
{
if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal) {
return DefaultItemWidth() * 1.5;
} else {
return DefaultItemWidth() * 1.25;
}
}
qreal NodeViewItem::DefaultItemVerticalPadding() const
{
if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal) {
return DefaultItemHeight() * 1.5;
} else {
return DefaultItemHeight() * 2.0;
}
}
void NodeViewItem::SetNode(Node *n)
{
node_ = n;
node_inputs_.clear();
if (node_) {
node_->Retranslate();
foreach (NodeParam* p, node_->parameters()) {
if (p->type() == NodeParam::kInput) {
NodeInput* input = static_cast<NodeInput*>(p);
if (input->is_connectable()) {
node_inputs_.append(input);
}
}
}
SetNodePosition(node_->GetPosition());
}
update();
}
Node *NodeViewItem::GetNode() const
{
return node_;
}
bool NodeViewItem::IsExpanded() const
{
return expanded_;
}
void NodeViewItem::SetExpanded(bool e, bool hide_titlebar)
{
if (node_inputs_.isEmpty()
|| (expanded_ == e && hide_titlebar_ == hide_titlebar)) {
return;
}
expanded_ = e;
hide_titlebar_ = hide_titlebar;
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_);
}
update();
}
void NodeViewItem::ToggleExpanded()
{
SetExpanded(!IsExpanded());
}
void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *)
{
// HACK for getting the main QWidget palette color (the `widget`'s palette uses the NodeView color instead which we
// don't want here)
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;i<node_inputs_.size();i++) {
QRectF input_rect = GetInputRect(i);
if (highlighted_index_ == i) {
QColor highlight_col = app_pal.color(QPalette::Text);
highlight_col.setAlpha(64);
painter->fillRect(input_rect, highlight_col);
}
painter->drawText(input_rect, Qt::AlignCenter, node_inputs_.at(i)->name());
}
}
// Draw the titlebar
if (!hide_titlebar_ && node_) {
Color node_color = Config::Current()[QStringLiteral("NodeCatColor%1")
.arg(node_->Category().first())].value<Color>();
QLinearGradient grad;
grad.setStart(0, rect().top());
grad.setFinalStop(0, rect().bottom());
QColor node_color_as_qcolor = node_color.toQColor();
{
// Generate lighter color for gradient
qreal hue, sat, lightness;
node_color_as_qcolor.getHslF(&hue, &sat, &lightness);
lightness = qMin(1.0, lightness + 0.2);
grad.setColorAt(0.0, QColor::fromHslF(hue, sat, lightness));
}
grad.setColorAt(1.0, node_color_as_qcolor);
painter->setPen(Qt::black);
painter->setBrush(grad);
painter->drawRect(title_bar_rect_);
painter->setPen(app_pal.color(QPalette::Text));
QString node_label;
if (!node_->GetLabel().isEmpty()) {
// Use label directly if node has one
node_label = node_->GetLabel();
} else if (node_->IsTrack()) {
// If node is a track, use a special track name to help users identify better
// Exception for tracks
TrackOutput* track = static_cast<TrackOutput*>(node_);
node_label = TrackOutput::GetDefaultTrackName(track->track_type(), track->Index());
} else {
// Otherwise, just use the node's short name
node_label = node_->ShortName();
}
QFont f;
QFontMetrics fm(f);
// Draw right or down arrow based on expanded state
int icon_size = fm.height() / 2;
int icon_padding = title_bar_rect_.height() / 2 - icon_size / 2;
int icon_full_size = icon_size + icon_padding * 2;
const QIcon& expand_icon = IsExpanded() ? icon::TriDown : icon::TriRight;
expand_icon.paint(painter, QRect(title_bar_rect_.x() + icon_padding,
title_bar_rect_.y() + icon_padding,
icon_size,
icon_size));
// Calculate how much space we have for text
int item_width = title_bar_rect_.width();
int max_text_width = item_width - DefaultTextPadding() * 2 - icon_full_size;
int label_width = QtUtils::QFontMetricsWidth(fm, node_label);
// Concatenate text if necessary (adds a "..." to the end and removes characters until the
// string fits in the bounds)
if (label_width > max_text_width) {
QString concatenated;
do {
node_label.chop(1);
concatenated = QCoreApplication::translate("NodeViewItem", "%1...").arg(node_label);
} while ((label_width = QtUtils::QFontMetricsWidth(fm, concatenated)) > max_text_width);
node_label = concatenated;
}
// Determine the text color (automatically calculate from node background color)
if (node_color.GetRoughLuminance() > 0.5) {
painter->setPen(Qt::black);
} else {
painter->setPen(Qt::white);
}
// Determine X position (favors horizontal centering unless it'll overrun the arrow)
QRectF text_rect = title_bar_rect_;
Qt::Alignment text_align = Qt::AlignCenter;
int likely_x = item_width / 2 - label_width / 2;
if (likely_x < icon_full_size) {
text_rect.adjust(icon_full_size, 0, 0, 0);
text_align = Qt::AlignLeft | Qt::AlignVCenter;
}
// Draw the text in a rect (the rect is sized around text already in the constructor)
painter->drawText(text_rect,
text_align,
node_label);
}
// 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);
}
painter->setPen(border_pen);
painter->setBrush(Qt::NoBrush);
painter->drawRect(rect());
}
void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
// We override standard mouse behavior in some cases. In these cases, we don't want the standard "move" and "release"
// events to trigger if we haven't already triggered the "press" event. We use this variable to determine whether
// base class behavior is valid here.
standard_click_ = false;
// If CTRL is held, we start a new edge
if (node_ && event->modifiers() & Qt::ControlModifier) {
NodeParam* param = node_->output();
// Create draggable object
dragging_edge_ = new NodeViewEdge();
dragging_edge_->SetFlowDirection(flow_dir_);
// Set up a QUndoCommand to make this action undoable
node_edge_change_command_ = new QUndoCommand();
if (param->type() == NodeParam::kOutput || param->edges().isEmpty()) {
// For an output param (or an input param with no connections), we default to creating a new edge
drag_source_ = this;
drag_src_param_ = param;
// Set the starting position to the current param's connector
dragging_edge_start_ = GetParamPoint(param, QPointF());
} else if (param->type() == NodeParam::kInput) {
// For an input param, we default to moving an existing edge
// (here we use the last one, which will usually also be the first)
NodeEdgePtr edge = param->edges().last();
// The starting position will be the OPPOSING parameter's rectangle
// Get the opposing parameter
drag_src_param_ = edge->output();
// Get its Node UI object
drag_source_ = static_cast<NodeViewScene*>(scene())->NodeToUIObject(drag_src_param_->parentNode());
// Get the opposing parameter's rect center using the line's current coordinates
// (we use the current coordinates because a complex formula is used for the line's coords if
// the opposing node is collapsed, therefore it's easier to just retrieve it from line itself)
NodeViewEdge* existing_edge_ui = static_cast<NodeViewScene*>(scene())->EdgeToUIObject(edge);
QPainterPath existing_edge_line = existing_edge_ui->path();
QPointF edge_start = existing_edge_line.pointAtPercent(0);
QPointF edge_end = existing_edge_line.pointAtPercent(1);
if (existing_edge_ui->contains(edge_start)) {
dragging_edge_start_ = edge_start;
} else {
dragging_edge_start_ = edge_end;
}
// Remove old edge
NodeEdgeRemoveCommand* remove_command = new NodeEdgeRemoveCommand(edge->output(),
edge->input(),
node_edge_change_command_);
remove_command->redo();
}
// Add it to the scene
scene()->addItem(dragging_edge_);
// Trigger initial line setting
mouseMoveEvent(event);
return;
} else {
// We aren't using any override behaviors, switch back to standard click behavior
standard_click_ = true;
event->setModifiers(FlipControlAndShiftModifiers(event->modifiers()));
QGraphicsRectItem::mousePressEvent(event);
}
}
void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
// Check if an edge drag was initiated
if (dragging_edge_ != nullptr) {
QPointF end_point = event->scenePos();
drag_dest_param_ = nullptr;
if (!cached_drop_item_
|| !cached_drop_item_->contains(cached_drop_item_->mapFromScene(event->scenePos()))) {
// Cursor has left last entered item, we need to find a new one
if (cached_drop_item_) {
// If we expanded an item below but are no longer dragging over it, re-collapse it
if (cached_drop_item_expanded_) {
cached_drop_item_->SetExpanded(false);
}
cached_drop_item_->SetHighlightedIndex(-1);
cached_drop_item_->setZValue(0);
cached_drop_item_ = nullptr;
}
NodeViewItem* cursor_item = dynamic_cast<NodeViewItem*>(scene()->itemAt(event->scenePos(), sceneTransform()));
if (cursor_item
&& cursor_item != drag_source_
&& !cursor_item->node_inputs_.isEmpty()) {
cached_drop_item_ = cursor_item;
// If the item we're dragging over is collapsed, expand it
cached_drop_item_expanded_ = !cached_drop_item_->IsExpanded();
if (cached_drop_item_expanded_) {
cached_drop_item_->SetExpanded(true, true);
}
cached_drop_item_->setZValue(1);
} else {
cached_drop_item_ = nullptr;
}
}
if (cached_drop_item_) {
int highlight_their_index = -1;
// See if the mouse is currently inside a connector rect
for (int i=0;i<cached_drop_item_->node_inputs_.size();i++) {
// Get the parameter we're dragging into
NodeParam* comp_param = cached_drop_item_->node_inputs_.at(i);
// See if cursor is inside its UI
if (cached_drop_item_->GetInputRect(i).contains(cached_drop_item_->mapFromScene(event->scenePos()))) { // See if we're dragging inside the hitbox
// Attempt to prevent circular dependency - ensure the receiving node doesn't output to the outputting node
if (!cached_drop_item_->GetNode()->OutputsTo(node_, true)) {
drag_dest_param_ = comp_param;
highlight_their_index = i;
QPointF end_point_local = cached_drop_item_->GetInputPoint(i, pos());
end_point = cached_drop_item_->mapToScene(end_point_local);
}
break;
}
}
cached_drop_item_->SetHighlightedIndex(highlight_their_index);
}
dragging_edge_->SetConnected(drag_dest_param_);
dragging_edge_->SetPoints(dragging_edge_start_,
end_point,
cached_drop_item_ ? cached_drop_item_->IsExpanded() : false);
return;
}
if (standard_click_) {
event->setModifiers(FlipControlAndShiftModifiers(event->modifiers()));
QGraphicsRectItem::mouseMoveEvent(event);
}
}
void NodeViewItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
// Check if an edge drag was initiated
if (dragging_edge_ != nullptr) {
// Remove the drag object
scene()->removeItem(dragging_edge_);
// If we expanded an item in the drag, re-collapse it now
if (cached_drop_item_) {
if (cached_drop_item_expanded_) {
cached_drop_item_->SetExpanded(false);
}
cached_drop_item_->SetHighlightedIndex(-1);
cached_drop_item_->setZValue(0);
cached_drop_item_ = nullptr;
}
if (drag_dest_param_ != nullptr) {
// We dragged to somewhere, so we'll make a new connection
NodeEdgePtr new_edge;
NodeOutput* output;
NodeInput* input;
// Connecting will automatically add an edge UI object through the signal/slot system
if (drag_dest_param_->type() == NodeParam::kOutput) {
output = static_cast<NodeOutput*>(drag_dest_param_);
input = static_cast<NodeInput*>(drag_src_param_);
} else {
output = static_cast<NodeOutput*>(drag_src_param_);
input = static_cast<NodeInput*>(drag_dest_param_);
}
// Use a command to make Node connecting undoable
new NodeEdgeAddCommand(output,
input,
node_edge_change_command_);
}
dragging_edge_ = nullptr;
Core::instance()->undo_stack()->pushIfHasChildren(node_edge_change_command_);
node_edge_change_command_ = nullptr;
return;
}
if (standard_click_) {
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_) {
node_->blockSignals(true);
node_->SetPosition(GetNodePosition());
node_->blockSignals(false);
}
return QGraphicsItem::itemChange(change, value);
}
void NodeViewItem::SetHighlightedIndex(int index)
{
if (highlighted_index_ == index) {
return;
}
highlighted_index_ = index;
update();
}
QRectF NodeViewItem::GetInputRect(int index) const
{
QRectF r = title_bar_rect_;
if (!hide_titlebar_) {
index++;
}
if (IsExpanded()) {
r.translate(0, r.height() * index);
}
return r;
}
QPointF NodeViewItem::GetParamPoint(NodeParam *param, const QPointF& source_pos) const
{
if (param->type() == NodeParam::kOutput) {
switch (flow_dir_) {
case NodeViewCommon::kLeftToRight:
default:
return pos() + QPointF(rect().right(), rect().center().y());
case NodeViewCommon::kRightToLeft:
return pos() + QPointF(rect().left(), rect().center().y());
case NodeViewCommon::kTopToBottom:
return pos() + QPointF(rect().center().x(), rect().bottom());
case NodeViewCommon::kBottomToTop:
return pos() + QPointF(rect().center().x(), rect().top());
}
} else {
NodeInput* input = static_cast<NodeInput*>(param);
// Resolve NodeInputArray elements
while (input->parentNode() != input->parent()) {
input = static_cast<NodeInput*>(input->parent());
}
return pos() + GetInputPoint(node_inputs_.indexOf(input), source_pos);
}
}
void NodeViewItem::SetFlowDirection(NodeViewCommon::FlowDirection dir)
{
flow_dir_ = dir;
}
QPointF NodeViewItem::GetInputPoint(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());
}
}
}
}
-179
View File
@@ -1,179 +0,0 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#ifndef NODEVIEWITEM_H
#define NODEVIEWITEM_H
#include <QFontMetrics>
#include <QGraphicsRectItem>
#include <QLinearGradient>
#include <QUndoCommand>
#include <QWidget>
#include "node/node.h"
#include "nodeviewcommon.h"
#include "nodeviewedge.h"
#include "nodeviewitemwidgetproxy.h"
namespace olive {
/**
* @brief A visual widget representation of a Node object to be used in a NodeView
*
* This widget can be collapsed or expanded to show/hide the node's various parameters.
*
* To retrieve the NodeViewItem for a certain Node, use NodeView::NodeToUIObject().
*/
class NodeViewItem : public QGraphicsRectItem
{
public:
NodeViewItem(QGraphicsItem* parent = nullptr);
QPointF GetNodePosition() const;
void SetNodePosition(const QPointF& pos);
/**
* @brief Set the Node to correspond to this widget
*/
void SetNode(Node* n);
/**
* @brief Get currently attached node
*/
Node* GetNode() const;
/**
* @brief Get expanded state
*/
bool IsExpanded() const;
/**
* @brief Set expanded state
*/
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 GetParamPoint(NodeParam* param, const QPointF &source_pos) const;
/**
* @brief Sets the direction nodes are flowing
*/
void SetFlowDirection(NodeViewCommon::FlowDirection dir);
static int DefaultTextPadding();
static int DefaultItemHeight();
static int DefaultItemWidth();
static int DefaultItemBorder();
qreal DefaultItemHorizontalPadding() const;
qreal DefaultItemVerticalPadding() 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:
/**
* @brief Highlight an input (for instance when the user is dragging over it)
*/
void SetHighlightedIndex(int index);
/**
* @brief Returns local rect of a NodeInput in array node_inputs_[index]
*/
QRectF GetInputRect(int index) const;
/**
* @brief Returns local point that edges should connect to for a NodeInput in array node_inputs_[index]
*/
QPointF GetInputPoint(int index, const QPointF &source_pos) const;
/**
* @brief Reference to attached Node
*/
Node* node_;
/**
* @brief Cached list of node inputs
*/
QList<NodeInput*> node_inputs_;
/**
* @brief Rectangle of the Node's title bar (equal to rect() when collapsed)
*/
QRectF title_bar_rect_;
/// Edge dragging variables
NodeViewEdge* dragging_edge_;
QPointF dragging_edge_start_;
NodeParam* drag_src_param_;
NodeParam* drag_dest_param_;
NodeViewItem* drag_source_;
NodeViewItem* cached_drop_item_;
bool cached_drop_item_expanded_;
/// Sizing variables to use when drawing
int node_border_width_;
/**
* @brief Expanded state
*/
bool expanded_;
bool hide_titlebar_;
/**
* @brief Current click mode
*
* \see mousePressEvent()
*/
bool standard_click_;
int highlighted_index_;
/**
* @brief QUndoCommand for creating and deleting edges by dragging
*
* \see mousePressEvent()
* \see mouseReleaseEvent()
*/
QUndoCommand* node_edge_change_command_;
NodeViewCommon::FlowDirection flow_dir_;
};
}
#endif // NODEVIEWITEM_H
@@ -1,41 +0,0 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#include "nodeviewitemwidgetproxy.h"
QColor NodeViewItemWidget::TitleBarColor()
{
return title_bar_color_;
}
void NodeViewItemWidget::SetTitleBarColor(QColor color)
{
title_bar_color_ = color;
}
QColor NodeViewItemWidget::BorderColor()
{
return border_color_;
}
void NodeViewItemWidget::SetBorderColor(QColor color)
{
border_color_ = color;
}
@@ -1,49 +0,0 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#ifndef NODEVIEWITEMWIDGETPROXY_H
#define NODEVIEWITEMWIDGETPROXY_H
#include <QWidget>
/**
* @brief A proxy object to allow NodeViewItem access to CSS functions
*
* QGraphicsItems can't take Q_PROPERTYs for CSS stylesheet input, but QWidgets can. This is a hack to allow CSS
* properties to be set from CSS and then read by NodeViewItem.
*/
class NodeViewItemWidget : public QWidget {
Q_OBJECT
Q_PROPERTY(QColor titlebarColor READ TitleBarColor WRITE SetTitleBarColor DESIGNABLE true)
Q_PROPERTY(QColor borderColor READ BorderColor WRITE SetBorderColor DESIGNABLE true)
public:
NodeViewItemWidget() = default;
QColor TitleBarColor();
void SetTitleBarColor(QColor color);
QColor BorderColor();
void SetBorderColor(QColor color);
private:
QColor title_bar_color_;
QColor border_color_;
};
#endif // NODEVIEWITEMWIDGETPROXY_H
-313
View File
@@ -1,313 +0,0 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#include "nodeviewscene.h"
#include "common/functiontimer.h"
#include "nodeviewedge.h"
#include "nodeviewitem.h"
#include "project/item/sequence/sequence.h"
namespace olive {
NodeViewScene::NodeViewScene(QObject *parent) :
QGraphicsScene(parent),
graph_(nullptr),
direction_(NodeViewCommon::kLeftToRight),
curved_edges_(true)
{
}
void NodeViewScene::SetFlowDirection(NodeViewCommon::FlowDirection direction)
{
direction_ = direction;
{
// Iterate over node items setting direction
QHash<Node*, NodeViewItem*>::const_iterator i;
for (i=item_map_.constBegin(); i!=item_map_.constEnd(); i++) {
i.value()->SetFlowDirection(direction_);
// Update position too
i.value()->SetNodePosition(i.key()->GetPosition());
}
}
{
// Iterate over edge items setting direction
QHash<NodeEdge*, NodeViewEdge*>::const_iterator i;
for (i=edge_map_.constBegin(); i!=edge_map_.constEnd(); i++) {
i.value()->SetFlowDirection(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();
{
QHash<Node*, NodeViewItem*>::const_iterator i;
for (i=item_map_.begin();i!=item_map_.end();i++) {
delete i.value();
}
item_map_.clear();
}
{
QHash<NodeEdge*, NodeViewEdge*>::const_iterator i;
for (i=edge_map_.begin();i!=edge_map_.end();i++) {
delete i.value();
}
edge_map_.clear();
}
}
void NodeViewScene::SelectAll()
{
QList<QGraphicsItem *> all_items = this->items();
foreach (QGraphicsItem* i, all_items) {
i->setSelected(true);
}
}
void NodeViewScene::DeselectAll()
{
QList<QGraphicsItem *> selected_items = this->selectedItems();
foreach (QGraphicsItem* i, selected_items) {
i->setSelected(false);
}
}
NodeViewItem *NodeViewScene::NodeToUIObject(Node *n)
{
return item_map_.value(n);
}
NodeViewEdge *NodeViewScene::EdgeToUIObject(NodeEdgePtr n)
{
return edge_map_.value(n.get());
}
void NodeViewScene::SetGraph(NodeGraph *graph)
{
graph_ = graph;
}
QVector<Node *> NodeViewScene::GetSelectedNodes() const
{
QHash<Node*, NodeViewItem*>::const_iterator iterator;
QVector<Node *> selected;
for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) {
if (iterator.value()->isSelected()) {
selected.append(iterator.key());
}
}
return selected;
}
QVector<NodeViewItem *> NodeViewScene::GetSelectedItems() const
{
QHash<Node*, NodeViewItem*>::const_iterator iterator;
QVector<NodeViewItem *> selected;
for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) {
if (iterator.value()->isSelected()) {
selected.append(iterator.value());
}
}
return selected;
}
QVector<NodeEdge*> NodeViewScene::GetSelectedEdges() const
{
QVector<NodeEdge*> edges;
QHash<NodeEdge*, NodeViewEdge*>::const_iterator i;
for (i=edge_map_.constBegin(); i!=edge_map_.constEnd(); i++) {
if (i.value()->isSelected()) {
edges.append(i.key());
}
}
return edges;
}
const QHash<Node *, NodeViewItem *> &NodeViewScene::item_map() const
{
return item_map_;
}
const QHash<NodeEdge *, NodeViewEdge *> &NodeViewScene::edge_map() const
{
return edge_map_;
}
void NodeViewScene::AddNode(Node* node)
{
NodeViewItem* item = new NodeViewItem();
item->SetFlowDirection(direction_);
item->SetNode(node);
addItem(item);
item_map_.insert(node, item);
// Add a NodeViewEdge for each connection
foreach (NodeParam* param, node->parameters()) {
// We only bother working with outputs since eventually this will cover all inputs too
// (covering both would lead to duplicates since every edge connects to one input and one output)
if (param->type() == NodeParam::kOutput) {
const QVector<NodeEdgePtr>& edges = param->edges();
foreach(NodeEdgePtr edge, edges) {
AddEdge(edge);
}
}
}
connect(node, &Node::PositionChanged, this, &NodeViewScene::NodePositionChanged);
connect(node, &Node::LabelChanged, this, &NodeViewScene::NodeLabelChanged);
}
void NodeViewScene::RemoveNode(Node *node)
{
disconnect(node, &Node::LabelChanged, this, &NodeViewScene::NodeLabelChanged);
disconnect(node, &Node::PositionChanged, this, &NodeViewScene::NodePositionChanged);
delete item_map_.take(node);
}
void NodeViewScene::AddEdge(NodeEdgePtr edge)
{
NodeViewEdge* edge_ui = new NodeViewEdge();
edge_ui->SetEdge(edge);
edge_ui->SetFlowDirection(direction_);
edge_ui->SetCurved(curved_edges_);
addItem(edge_ui);
edge_map_.insert(edge.get(), edge_ui);
}
void NodeViewScene::RemoveEdge(NodeEdgePtr edge)
{
delete edge_map_.take(edge.get());
}
int NodeViewScene::DetermineWeight(Node *n)
{
QVector<Node*> inputs = n->GetImmediateDependencies();
int weight = 0;
foreach (Node* i, inputs) {
if (i->GetRoutesTo(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::ReorganizeFrom(Node* n)
{
QVector<Node*> immediates = n->GetImmediateDependencies();
if (immediates.isEmpty()) {
// Nothing to do
return;
}
QPointF parent_pos = n->GetPosition();
int weight_count = DetermineWeight(n);
qreal child_x = parent_pos.x() - 1.0;
qreal children_height = weight_count-1;
qreal children_y = parent_pos.y() - children_height * 0.5;
int weight_counter = 0;
foreach (Node* i, immediates) {
if (i->GetRoutesTo(n) == 1) {
int weight = DetermineWeight(i);
i->SetPosition(QPointF(child_x,
children_y + weight_counter + (weight - 1) * 0.5));
weight_counter += weight;
ReorganizeFrom(i);
}
}
}
bool NodeViewScene::GetEdgesAreCurved() const
{
return curved_edges_;
}
void NodeViewScene::SetEdgesAreCurved(bool curved)
{
if (curved_edges_ != curved) {
curved_edges_ = curved;
foreach (NodeViewEdge* e, edge_map_) {
e->SetCurved(curved_edges_);
}
}
}
void NodeViewScene::NodePositionChanged(const QPointF &pos)
{
item_map_.value(static_cast<Node*>(sender()))->SetNodePosition(pos);
}
void NodeViewScene::NodeLabelChanged()
{
item_map_.value(static_cast<Node*>(sender()))->update();
}
}
-151
View File
@@ -1,151 +0,0 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#ifndef NODEVIEWSCENE_H
#define NODEVIEWSCENE_H
#include <QGraphicsScene>
#include <QTimer>
#include "node/graph.h"
#include "nodeviewedge.h"
#include "nodeviewitem.h"
namespace olive {
class NodeViewScene : public QGraphicsScene
{
Q_OBJECT
public:
NodeViewScene(QObject *parent = nullptr);
void clear();
void SelectAll();
void DeselectAll();
/**
* @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);
/**
* @brief Retrieve the graphical widget corresponding to a specific NodeEdge
*
* Same as NodeToUIObject() but returns a NodeViewEdge corresponding to a NodeEdgePtr instead.
*/
NodeViewEdge* EdgeToUIObject(NodeEdgePtr n);
void SetGraph(NodeGraph* graph);
QVector<Node *> GetSelectedNodes() const;
QVector<NodeViewItem*> GetSelectedItems() const;
QVector<NodeEdge *> GetSelectedEdges() const;
const QHash<Node*, NodeViewItem*>& item_map() const;
const QHash<NodeEdge*, NodeViewEdge*>& edge_map() const;
Qt::Orientation GetFlowOrientation() const;
NodeViewCommon::FlowDirection GetFlowDirection() const;
void SetFlowDirection(NodeViewCommon::FlowDirection direction);
/**
* @brief Automatically reposition the nodes based on their connections
*/
void ReorganizeFrom(Node* n);
bool GetEdgesAreCurved() const;
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().
*/
void 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);
/**
* @brief Slot when an edge is added to a graph (SetGraph() connects this)
*
* This should NEVER be called directly, only connected to a NodeGraph. To add an edge (i.e. connect two node
* parameters together), use NodeParam::ConnectEdge().
*/
void AddEdge(NodeEdgePtr edge);
/**
* @brief Slot when an edge is removed from a graph (SetGraph() connects this)
*
* This should NEVER be called directly, only connected to a NodeGraph. To remove an edge (i.e. disconnect two node
* parameters), use NodeParam::DisconnectEdge().
*/
void RemoveEdge(NodeEdgePtr edge);
/**
* @brief Set whether edges in this scene should be curved or not
*/
void SetEdgesAreCurved(bool curved);
private:
static int DetermineWeight(Node* n);
QHash<Node*, NodeViewItem*> item_map_;
QHash<NodeEdge*, NodeViewEdge*> edge_map_;
NodeGraph* graph_;
NodeViewCommon::FlowDirection direction_;
bool curved_edges_;
private slots:
/**
* @brief Receiver for whenever a node position changes
*/
void NodePositionChanged(const QPointF& pos);
/**
* @brief Receiver for when a node's label has changed
*/
void NodeLabelChanged();
};
}
#endif // NODEVIEWSCENE_H
+18 -119
View File
@@ -24,87 +24,50 @@
namespace olive {
NodeEdgeAddCommand::NodeEdgeAddCommand(NodeOutput *output, NodeInput *input, QUndoCommand *parent) :
NodeEdgeAddCommand::NodeEdgeAddCommand(Node *output, NodeInput *input, int element, QUndoCommand *parent) :
UndoCommand(parent),
output_(output),
input_(input),
old_edge_(nullptr),
done_(false)
element_(element)
{
}
void NodeEdgeAddCommand::redo_internal()
{
if (done_) {
return;
}
old_edge_ = NodeParam::DisconnectForNewOutput(input_);
NodeParam::ConnectEdge(output_, input_);
done_ = true;
Node::ConnectEdge(output_, input_, element_);
}
void NodeEdgeAddCommand::undo_internal()
{
if (!done_) {
return;
}
NodeParam::DisconnectEdge(output_, input_);
if (old_edge_ != nullptr) {
NodeParam::ConnectEdge(old_edge_->output(), old_edge_->input());
}
done_ = false;
Node::DisconnectEdge(output_, input_, element_);
}
Project *NodeEdgeAddCommand::GetRelevantProject() const
{
return static_cast<Sequence*>(output_->parentNode()->parent())->project();
return output_->parent()->project();
}
NodeEdgeRemoveCommand::NodeEdgeRemoveCommand(NodeOutput *output, NodeInput *input, QUndoCommand *parent) :
NodeEdgeRemoveCommand::NodeEdgeRemoveCommand(Node *output, NodeInput *input, int element, QUndoCommand *parent) :
UndoCommand(parent),
output_(output),
input_(input),
done_(false)
{
}
NodeEdgeRemoveCommand::NodeEdgeRemoveCommand(NodeEdgePtr edge, QUndoCommand *parent) :
UndoCommand(parent),
output_(edge->output()),
input_(edge->input()),
done_(false)
element_(element)
{
}
void NodeEdgeRemoveCommand::redo_internal()
{
if (done_) {
return;
}
NodeParam::DisconnectEdge(output_, input_);
done_ = true;
Node::DisconnectEdge(output_, input_, element_);
}
void NodeEdgeRemoveCommand::undo_internal()
{
if (!done_) {
return;
}
NodeParam::ConnectEdge(output_, input_);
done_ = false;
Node::ConnectEdge(output_, input_, element_);
}
Project *NodeEdgeRemoveCommand::GetRelevantProject() const
{
return static_cast<Sequence*>(output_->parentNode()->parent())->project();
return output_->parent()->project();
}
NodeAddCommand::NodeAddCommand(NodeGraph *graph, Node *node, QUndoCommand *parent) :
@@ -118,75 +81,34 @@ NodeAddCommand::NodeAddCommand(NodeGraph *graph, Node *node, QUndoCommand *paren
void NodeAddCommand::redo_internal()
{
graph_->AddNode(node_);
node_->setParent(graph_);
}
void NodeAddCommand::undo_internal()
{
graph_->TakeNode(node_, &memory_manager_);
node_->setParent(&memory_manager_);
}
Project *NodeAddCommand::GetRelevantProject() const
{
return static_cast<Sequence*>(graph_)->project();
return graph_->project();
}
NodeRemoveCommand::NodeRemoveCommand(NodeGraph *graph, const QVector<Node *> &nodes, QUndoCommand *parent) :
NodeRemoveCommand::NodeRemoveCommand(Node *node, QUndoCommand *parent) :
UndoCommand(parent),
graph_(graph),
nodes_(nodes)
graph_(node->parent()),
node_(node)
{
}
void NodeRemoveCommand::redo_internal()
{
// Cache edges for undoing
foreach (Node* n, nodes_) {
foreach (NodeParam* param, n->parameters()) {
foreach (NodeEdgePtr edge, param->edges()) {
// Ensures the same edge isn't added twice (prevents double connecting when undoing)
if (!edges_.contains(edge)) {
edges_.append(edge);
}
}
}
}
// Take nodes from graph (TakeNode() will automatically disconnect edges)
foreach (Node* n, nodes_) {
// If the node is a block, unlink any linked blocks before removing
if (n->IsBlock()) {
Block *b = static_cast<Block *>(n);
if (b->HasLinks()) {
BlockUnlinkAllCommand *unlink_command = new BlockUnlinkAllCommand(b);
unlink_command->redo();
block_unlink_commands_.append(unlink_command);
}
}
graph_->TakeNode(n, &memory_manager_);
}
node_->setParent(&memory_manager_);
}
void NodeRemoveCommand::undo_internal()
{
// Re-add nodes to graph
foreach (Node* n, nodes_) {
graph_->AddNode(n);
}
// Relink any blocks that were unlinked
foreach(BlockUnlinkAllCommand* command, block_unlink_commands_) {
command->undo();
delete command;
}
// Re-connect edges
foreach (NodeEdgePtr edge, edges_) {
NodeParam::ConnectEdge(edge->output(), edge->input());
}
edges_.clear();
block_unlink_commands_.clear();
node_->setParent(graph_);
}
Project *NodeRemoveCommand::GetRelevantProject() const
@@ -194,21 +116,6 @@ Project *NodeRemoveCommand::GetRelevantProject() const
return static_cast<Sequence*>(graph_)->project();
}
NodeRemoveWithExclusiveDeps::NodeRemoveWithExclusiveDeps(NodeGraph *graph, Node *node, QUndoCommand *parent) :
UndoCommand(parent)
{
QVector<Node*> node_and_its_deps;
node_and_its_deps.append(node);
node_and_its_deps.append(node->GetExclusiveDependencies());
remove_command_ = new NodeRemoveCommand(graph, node_and_its_deps, this);
}
Project *NodeRemoveWithExclusiveDeps::GetRelevantProject() const
{
return remove_command_->GetRelevantProject();
}
NodeCopyInputsCommand::NodeCopyInputsCommand(Node *src, Node *dest, bool include_connections, QUndoCommand *parent) :
QUndoCommand(parent),
src_(src),
@@ -217,14 +124,6 @@ NodeCopyInputsCommand::NodeCopyInputsCommand(Node *src, Node *dest, bool include
{
}
NodeCopyInputsCommand::NodeCopyInputsCommand(Node *src, Node *dest, QUndoCommand *parent) :
QUndoCommand(parent),
src_(src),
dest_(dest),
include_connections_(true)
{
}
void NodeCopyInputsCommand::redo()
{
Node::CopyInputs(src_, dest_, include_connections_);
+9 -89
View File
@@ -25,7 +25,6 @@
#include "node/graph.h"
#include "node/node.h"
#include "nodeviewitem.h"
#include "undo/undocommand.h"
#include "widget/timelinewidget/undo/undo.h"
@@ -38,7 +37,7 @@ namespace olive {
*/
class NodeEdgeAddCommand : public UndoCommand {
public:
NodeEdgeAddCommand(NodeOutput* output, NodeInput* input, QUndoCommand* parent = nullptr);
NodeEdgeAddCommand(Node* output, NodeInput* input, int element, QUndoCommand* parent = nullptr);
virtual Project* GetRelevantProject() const override;
@@ -47,12 +46,10 @@ protected:
virtual void undo_internal() override;
private:
NodeOutput* output_;
Node* output_;
NodeInput* input_;
int element_;
NodeEdgePtr old_edge_;
bool done_;
};
/**
@@ -62,8 +59,7 @@ private:
*/
class NodeEdgeRemoveCommand : public UndoCommand {
public:
NodeEdgeRemoveCommand(NodeOutput* output, NodeInput* input, QUndoCommand* parent = nullptr);
NodeEdgeRemoveCommand(NodeEdgePtr edge, QUndoCommand* parent = nullptr);
NodeEdgeRemoveCommand(Node* output, NodeInput* input, int element, QUndoCommand* parent = nullptr);
virtual Project* GetRelevantProject() const override;
@@ -72,10 +68,10 @@ protected:
virtual void undo_internal() override;
private:
NodeOutput* output_;
Node* output_;
NodeInput* input_;
int element_;
bool done_;
};
class NodeAddCommand : public UndoCommand {
@@ -97,8 +93,7 @@ private:
class NodeRemoveCommand : public UndoCommand {
public:
NodeRemoveCommand(NodeGraph* graph,
const QVector<Node*>& nodes,
NodeRemoveCommand(Node* node,
QUndoCommand* parent = nullptr);
virtual Project* GetRelevantProject() const override;
@@ -111,32 +106,15 @@ private:
QObject memory_manager_;
NodeGraph* graph_;
QVector<Node*> nodes_;
QVector<NodeEdgePtr> edges_;
QVector<BlockUnlinkAllCommand*> block_unlink_commands_;
};
Node* node_;
class NodeRemoveWithExclusiveDeps : public UndoCommand {
public:
NodeRemoveWithExclusiveDeps(NodeGraph* graph,
Node* node,
QUndoCommand* parent = nullptr);
virtual Project* GetRelevantProject() const override;
private:
NodeRemoveCommand* remove_command_;
};
class NodeCopyInputsCommand : public QUndoCommand {
public:
NodeCopyInputsCommand(Node* src,
Node* dest,
bool include_connections = true,
QUndoCommand* parent = nullptr);
NodeCopyInputsCommand(Node* src,
Node* dest,
bool include_connections,
QUndoCommand* parent = nullptr);
protected:
@@ -151,64 +129,6 @@ private:
};
class NodeGraphBeginOperationCommand : public UndoCommand {
public:
NodeGraphBeginOperationCommand(NodeGraph* graph, QUndoCommand* parent = nullptr) :
UndoCommand(parent),
graph_(graph)
{
}
virtual Project* GetRelevantProject() const override
{
return static_cast<Sequence*>(graph_)->project();
}
protected:
virtual void redo_internal() override
{
graph_->BeginOperation();
}
virtual void undo_internal() override
{
graph_->EndOperation();
}
private:
NodeGraph* graph_;
};
class NodeGraphEndOperationCommand : public UndoCommand {
public:
NodeGraphEndOperationCommand(NodeGraph* graph, QUndoCommand* parent = nullptr) :
UndoCommand(parent),
graph_(graph)
{
}
virtual Project* GetRelevantProject() const override
{
return static_cast<Sequence*>(graph_)->project();
}
protected:
virtual void redo_internal() override
{
graph_->EndOperation();
}
virtual void undo_internal() override
{
graph_->BeginOperation();
}
private:
NodeGraph* graph_;
};
}
#endif // NODEVIEWUNDO_H