restored nodeview

This commit is contained in:
itsmattkc
2021-01-19 00:06:57 +11:00
parent 835b82205f
commit 8e7035bd40
10 changed files with 2307 additions and 14 deletions
+7
View File
@@ -18,6 +18,13 @@ set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/nodeview/nodeview.cpp
widget/nodeview/nodeview.h
widget/nodeview/nodeviewcommon.h
widget/nodeview/nodeviewedge.cpp
widget/nodeview/nodeviewedge.h
widget/nodeview/nodeviewitem.cpp
widget/nodeview/nodeviewitem.h
widget/nodeview/nodeviewscene.cpp
widget/nodeview/nodeviewscene.h
widget/nodeview/nodeviewundo.cpp
widget/nodeview/nodeviewundo.h
PARENT_SCOPE
+685 -3
View File
@@ -20,14 +20,696 @@
#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)
graph_(nullptr),
drop_edge_(nullptr),
create_edge_(nullptr),
create_edge_dst_(nullptr),
create_edge_dst_input_(nullptr),
create_edge_dst_temp_expanded_(false),
filter_mode_(kFilterShowSelectedBlocks),
scale_(1.0)
{
setScene(&scene_);
SetDefaultDragMode(RubberBandDrag);
setContextMenuPolicy(Qt::CustomContextMenu);
setMouseTracking(true);
setRenderHint(QPainter::Antialiasing);
setViewportUpdateMode(FullViewportUpdate);
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);
}
NodeView::~NodeView()
{
// Unset the current graph
SetGraph(nullptr);
}
void NodeView::SetGraph(NodeGraph *graph)
{
if (graph_ == graph) {
return;
}
if (graph_) {
disconnect(graph_, &NodeGraph::NodeAdded, &scene_, &NodeViewScene::AddNode);
disconnect(graph_, &NodeGraph::NodeRemoved, &scene_, &NodeViewScene::RemoveNode);
disconnect(graph_, &NodeGraph::InputConnected, &scene_, &NodeViewScene::AddEdge);
disconnect(graph_, &NodeGraph::InputDisconnected, &scene_, &NodeViewScene::RemoveEdge);
}
// 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_) {
connect(graph_, &NodeGraph::NodeAdded, &scene_, &NodeViewScene::AddNode);
connect(graph_, &NodeGraph::NodeRemoved, &scene_, &NodeViewScene::RemoveNode);
connect(graph_, &NodeGraph::InputConnected, &scene_, &NodeViewScene::AddEdge);
connect(graph_, &NodeGraph::InputDisconnected, &scene_, &NodeViewScene::RemoveEdge);
foreach (Node* n, graph_->nodes()) {
scene_.AddNode(n);
}
foreach (Node* n, graph_->nodes()) {
foreach (NodeInput* input, n->parameters()) {
for (auto it=input->edges().cbegin(); it!=input->edges().cend(); it++) {
scene_.AddEdge(it->second, input, it->first);
}
}
}
}
}
void NodeView::DeleteSelected()
{
if (!graph_) {
return;
}
QUndoCommand* command = new QUndoCommand();
{
QVector<NodeViewEdge *> selected_edges = scene_.GetSelectedEdges();
foreach (NodeViewEdge* edge, selected_edges) {
new NodeEdgeRemoveCommand(edge->output(), edge->input(), edge->element(), 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()) {
foreach (Node* node, selected_nodes) {
Node::RemoveNodeAndDisconnect(node, 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::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()) {
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::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);
}
}
if (event->modifiers() & Qt::ControlModifier) {
NodeViewItem* item = dynamic_cast<NodeViewItem*>(itemAt(event->pos()));
if (item) {
create_edge_ = new NodeViewEdge();
create_edge_src_ = item;
scene_.addItem(create_edge_);
return;
}
}
super::mousePressEvent(event);
}
void NodeView::mouseMoveEvent(QMouseEvent *event)
{
if (HandMove(event)) return;
if (create_edge_) {
// Determine scene coordinate
QPointF scene_pt = mapToScene(event->pos());
// Find if the cursor is currently inside an item
NodeViewItem* item_at_cursor = dynamic_cast<NodeViewItem*>(itemAt(event->pos()));
// If the item has changed
if (item_at_cursor != create_edge_dst_) {
// If we had a destination active, disconnect from it since the item has changed
if (create_edge_dst_) {
create_edge_dst_->SetHighlightedIndex(-1);
if (create_edge_dst_temp_expanded_) {
// We expanded this item, so we can un-expand it
create_edge_dst_->SetExpanded(false);
}
}
// Set destination
create_edge_dst_ = item_at_cursor;
// If our destination is an item, ensure it's expanded
if (create_edge_dst_) {
if ((create_edge_dst_temp_expanded_ = (!create_edge_dst_->IsExpanded()))) {
create_edge_dst_->SetExpanded(true, true);
}
}
}
// If we have a destination, highlight the appropriate input
int highlight_index = -1;
if (create_edge_dst_) {
highlight_index = create_edge_dst_->GetIndexAt(scene_pt);
create_edge_dst_->SetHighlightedIndex(highlight_index);
}
if (highlight_index >= 0) {
create_edge_dst_input_ = create_edge_dst_->GetInputAtIndex(highlight_index);
create_edge_->SetPoints(create_edge_src_->GetOutputPoint(),
create_edge_dst_->GetInputPoint(highlight_index, create_edge_src_->pos()),
true);
} else {
create_edge_dst_input_ = nullptr;
create_edge_->SetPoints(create_edge_src_->GetOutputPoint(),
scene_pt,
false);
}
// Set connected to whether we have a valid input destination
create_edge_->SetConnected(create_edge_dst_input_);
return;
}
super::mouseMoveEvent(event);
// See if there are any items attached
if (!attached_items_.isEmpty()) {
// Move those items to the cursor
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 (NodeInput* input, attached_node->parameters()) {
if (input->IsConnectable()) {
if (input->GetDataType() == new_drop_edge->input()->GetDataType()) {
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 (create_edge_) {
delete create_edge_;
create_edge_ = nullptr;
if (create_edge_dst_) {
// Clear highlight
create_edge_dst_->SetHighlightedIndex(-1);
// Collapse if we expanded it
if (create_edge_dst_temp_expanded_) {
create_edge_dst_->SetExpanded(false);
}
if (create_edge_dst_input_) {
// Make connection
Core::instance()->undo_stack()->push(new NodeEdgeAddCommand(create_edge_src_->GetNode(), create_edge_dst_input_, -1));
create_edge_dst_input_ = nullptr;
}
create_edge_dst_ = nullptr;
}
return;
}
if (!attached_items_.isEmpty()) {
if (attached_items_.size() == 1) {
Node* dropping_node = attached_items_.first().item->GetNode();
if (drop_edge_) {
// We have everything we need to place the node in between
QUndoCommand* command = new QUndoCommand();
// Remove old edge
new NodeEdgeRemoveCommand(drop_edge_->output(), drop_edge_->input(), drop_edge_->element(), command);
// Place new edges
new NodeEdgeAddCommand(drop_edge_->output(), drop_input_, -1, command);
new NodeEdgeAddCommand(dropping_node, drop_edge_->input(), drop_edge_->element(), 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) {
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)
{
Q_UNUSED(action)
}
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);
}
}
+107 -11
View File
@@ -22,47 +22,143 @@
#define NODEVIEW_H
#include <QGraphicsView>
#include <QTimer>
#include "node/graph.h"
#include "node/nodecopypaste.h"
#include "nodeviewedge.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){}
signals:
void NodesSelected(const QVector<Node*>& nodes);
void DeselectBlocks(const QVector<Block*>& blocks){}
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;
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();
NodeGraph* graph_;
struct AttachedItem {
NodeViewItem* item;
QPointF original_pos;
};
QList<AttachedItem> attached_items_;
NodeViewEdge* drop_edge_;
NodeInput* drop_input_;
NodeViewEdge* create_edge_;
NodeViewItem* create_edge_src_;
NodeViewItem* create_edge_dst_;
NodeInput* create_edge_dst_input_;
bool create_edge_dst_temp_expanded_;
NodeViewScene scene_;
QVector<Node*> selected_nodes_;
QVector<Block*> selected_blocks_;
enum FilterMode {
kFilterShowAll,
kFilterShowSelectedBlocks
};
FilterMode filter_mode_;
double scale_;
private slots:
/**
* @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);
};
}
+58
View File
@@ -0,0 +1,58 @@
/***
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
+215
View File
@@ -0,0 +1,215 @@
/***
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 "nodeviewitem.h"
#include "nodeviewscene.h"
namespace olive {
NodeViewEdge::NodeViewEdge(Node* output, NodeInput *input, int element,
NodeViewItem* from_item, NodeViewItem* to_item,
QGraphicsItem* parent) :
QGraphicsPathItem(parent),
output_(output),
input_(input),
element_(element),
from_item_(from_item),
to_item_(to_item)
{
Init();
SetConnected(true);
}
NodeViewEdge::NodeViewEdge(QGraphicsItem *parent) :
QGraphicsPathItem(parent)
{
Init();
}
void NodeViewEdge::Adjust()
{
// Draw a line between the two
SetPoints(from_item()->GetOutputPoint(),
to_item()->GetInputPoint(input_, from_item()->pos()),
to_item()->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_);
}
void NodeViewEdge::Init()
{
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;
}
}
+143
View File
@@ -0,0 +1,143 @@
/***
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 "nodeviewcommon.h"
#include "node/node.h"
namespace olive {
class NodeViewItem;
/**
* @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(Node* output, NodeInput *input, int element,
NodeViewItem* from_item, NodeViewItem* to_item,
QGraphicsItem* parent = nullptr);
NodeViewEdge(QGraphicsItem* parent = nullptr);
Node* output() const
{
return output_;
}
NodeInput* input() const
{
return input_;
}
int element() const
{
return element_;
}
NodeViewItem* from_item() const
{
return from_item_;
}
NodeViewItem* to_item() const
{
return to_item_;
}
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:
void Init();
Node* output_;
NodeInput* input_;
int element_;
NodeViewItem* from_item_;
NodeViewItem* to_item_;
int edge_width_;
bool connected_;
bool highlighted_;
NodeViewCommon::FlowDirection flow_dir_;
bool curved_;
QPolygonF arrow_;
int arrow_size_;
};
}
#endif // NODEEDGEITEM_H
+485
View File
@@ -0,0 +1,485 @@
/***
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/colorcoding.h"
#include "ui/icons/icons.h"
#include "window/mainwindow/mainwindow.h"
namespace olive {
NodeViewItem::NodeViewItem(QGraphicsItem *parent) :
QGraphicsRectItem(parent),
node_(nullptr),
expanded_(false),
hide_titlebar_(false),
highlighted_index_(-1),
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::AddEdge(NodeViewEdge *edge)
{
edges_.append(edge);
}
void NodeViewItem::RemoveEdge(NodeViewEdge *edge)
{
edges_.removeOne(edge);
}
int NodeViewItem::GetIndexAt(QPointF pt) const
{
pt -= pos();
for (int i=0; i<node_inputs_.size(); i++) {
if (GetInputRect(i).contains(pt)) {
return i;
}
}
return -1;
}
void NodeViewItem::SetNode(Node *n)
{
node_ = n;
node_inputs_.clear();
if (node_) {
node_->Retranslate();
foreach (NodeInput* input, node_->parameters()) {
if (input->IsConnectable()) {
node_inputs_.append(input);
}
}
SetNodePosition(node_->GetPosition());
}
update();
}
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();
ReadjustAllEdges();
}
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_) {
painter->setPen(Qt::black);
painter->setBrush(node_->brush(title_bar_rect_.top(), title_bar_rect_.bottom()));
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 {
Track* track = dynamic_cast<Track*>(node_);
if (track) {
// Exception for tracks
node_label = Track::GetDefaultTrackName(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)
painter->setPen(ColorCoding::GetUISelectorColor(node_->color()));
// 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)
{
event->setModifiers(FlipControlAndShiftModifiers(event->modifiers()));
QGraphicsRectItem::mousePressEvent(event);
}
void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
event->setModifiers(FlipControlAndShiftModifiers(event->modifiers()));
QGraphicsRectItem::mouseMoveEvent(event);
}
void NodeViewItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
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);
ReadjustAllEdges();
}
return QGraphicsItem::itemChange(change, value);
}
void NodeViewItem::ReadjustAllEdges()
{
foreach (NodeViewEdge* edge, edges_) {
edge->Adjust();
}
}
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::GetInputPoint(NodeInput *input, const QPointF& source_pos) const
{
return GetInputPoint(node_inputs_.indexOf(input), source_pos);
}
QPointF NodeViewItem::GetInputPoint(int input, const QPointF &source_pos) const
{
return pos() + GetInputPointInternal(input, source_pos);
}
QPointF NodeViewItem::GetOutputPoint() const
{
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());
}
}
void NodeViewItem::SetFlowDirection(NodeViewCommon::FlowDirection dir)
{
flow_dir_ = dir;
}
QPointF NodeViewItem::GetInputPointInternal(int index, const QPointF& source_pos) const
{
QRectF input_rect = GetInputRect(index);
Qt::Orientation flow_orientation = NodeViewCommon::GetFlowOrientation(flow_dir_);
if (flow_orientation == Qt::Horizontal || IsExpanded()) {
if (flow_dir_ == NodeViewCommon::kLeftToRight
|| (flow_orientation == Qt::Vertical && source_pos.x() < pos().x())) {
return QPointF(input_rect.left(), input_rect.center().y());
} else {
return QPointF(input_rect.right(), input_rect.center().y());
}
} else {
if (flow_dir_ == NodeViewCommon::kTopToBottom) {
return QPointF(input_rect.center().x(), input_rect.top());
} else {
return QPointF(input_rect.center().x(), input_rect.bottom());
}
}
}
}
+174
View File
@@ -0,0 +1,174 @@
/***
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"
namespace olive {
class NodeViewEdge;
/**
* @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
{
return node_;
}
/**
* @brief Get expanded state
*/
bool IsExpanded() const
{
return expanded_;
}
/**
* @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 GetInputPoint(NodeInput* input, const QPointF &source_pos) const;
QPointF GetInputPoint(int input, const QPointF &source_pos) const;
QPointF GetOutputPoint() 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;
void AddEdge(NodeViewEdge* edge);
void RemoveEdge(NodeViewEdge* edge);
int GetIndexAt(QPointF pt) const;
NodeInput* GetInputAtIndex(int index) const
{
return node_inputs_.at(index);
}
void SetHighlightedIndex(int index);
protected:
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
private:
void ReadjustAllEdges();
/**
* @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 GetInputPointInternal(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_;
/// Sizing variables to use when drawing
int node_border_width_;
/**
* @brief Expanded state
*/
bool expanded_;
bool hide_titlebar_;
int highlighted_index_;
NodeViewCommon::FlowDirection flow_dir_;
QVector<NodeViewEdge*> edges_;
};
}
#endif // NODEVIEWITEM_H
+295
View File
@@ -0,0 +1,295 @@
/***
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
foreach (NodeViewEdge* edge, edges_) {
edge->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();
qDeleteAll(item_map_);
item_map_.clear();
qDeleteAll(edges_);
edges_.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(Node* output, NodeInput* input, int element)
{
foreach (NodeViewEdge* edge, edges_) {
if (edge->output() == output && edge->input() == input && edge->element() == element) {
return edge;
}
}
return nullptr;
}
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<NodeViewEdge *> NodeViewScene::GetSelectedEdges() const
{
QVector<NodeViewEdge*> edges;
foreach (NodeViewEdge* e, edges_) {
if (e->isSelected()) {
edges.append(e);
}
}
return edges;
}
void NodeViewScene::AddNode(Node* node)
{
NodeViewItem* item = new NodeViewItem();
item->SetFlowDirection(direction_);
item->SetNode(node);
addItem(item);
item_map_.insert(node, item);
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(Node* output, NodeInput* input, int element)
{
AddEdgeInternal(output, input, element, NodeToUIObject(output), NodeToUIObject(input->parent()));
}
void NodeViewScene::RemoveEdge(Node* output, NodeInput* input, int element)
{
NodeViewEdge* edge = EdgeToUIObject(output, input, element);
edge->from_item()->RemoveEdge(edge);
edge->to_item()->RemoveEdge(edge);
edges_.removeOne(edge);
delete edge;
}
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);
}
void NodeViewScene::AddEdgeInternal(Node *output, NodeInput *input, int element, NodeViewItem *from, NodeViewItem *to)
{
NodeViewEdge* edge_ui = new NodeViewEdge(output, input, element, from, to);
edge_ui->SetFlowDirection(direction_);
edge_ui->SetCurved(curved_edges_);
from->AddEdge(edge_ui);
to->AddEdge(edge_ui);
addItem(edge_ui);
edges_.append(edge_ui);
}
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, edges_) {
e->SetCurved(curved_edges_);
}
}
}
void NodeViewScene::NodePositionChanged(const QPointF &pos)
{
// Update node's internal position
item_map_.value(static_cast<Node*>(sender()))->SetNodePosition(pos);
}
void NodeViewScene::NodeLabelChanged()
{
// Force item to update
item_map_.value(static_cast<Node*>(sender()))->update();
}
}
+138
View File
@@ -0,0 +1,138 @@
/***
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);
NodeViewEdge *EdgeToUIObject(Node* output, NodeInput *input, int element);
void SetGraph(NodeGraph* graph);
QVector<Node *> GetSelectedNodes() const;
QVector<NodeViewItem*> GetSelectedItems() const;
QVector<NodeViewEdge*> GetSelectedEdges() const;
const QHash<Node*, NodeViewItem*>& item_map() const
{
return item_map_;
}
const QVector<NodeViewEdge*>& edges() const
{
return edges_;
}
Qt::Orientation GetFlowOrientation() const;
NodeViewCommon::FlowDirection GetFlowDirection() const;
void SetFlowDirection(NodeViewCommon::FlowDirection direction);
bool GetEdgesAreCurved() const;
void ReorganizeFrom(Node* n);
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);
void AddEdge(Node* output, NodeInput* input, int element);
void RemoveEdge(Node* output, NodeInput* input, int element);
/**
* @brief Set whether edges in this scene should be curved or not
*/
void SetEdgesAreCurved(bool curved);
private:
static int DetermineWeight(Node* n);
void AddEdgeInternal(Node* output, NodeInput* input, int element, NodeViewItem* from, NodeViewItem* to);
QHash<Node*, NodeViewItem*> item_map_;
QVector<NodeViewEdge*> edges_;
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