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:
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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_);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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_);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user