restored nodeview
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user