nodeview: support multiple flow directions
This commit is contained in:
@@ -391,7 +391,7 @@ void KeyframeViewBase::ShowContextMenu()
|
||||
{
|
||||
Menu m;
|
||||
|
||||
MenuShared::instance()->AddItemsForEditMenu(&m);
|
||||
MenuShared::instance()->AddItemsForEditMenu(&m, false);
|
||||
|
||||
QAction* linear_key_action = nullptr;
|
||||
QAction* bezier_key_action = nullptr;
|
||||
|
||||
@@ -50,6 +50,17 @@ Menu::Menu(const QString &s, QWidget *parent) :
|
||||
Init();
|
||||
}
|
||||
|
||||
QAction *Menu::AddActionWithData(const QString &text, const QVariant &data, const QVariant &compare)
|
||||
{
|
||||
QAction* a = addAction(text);
|
||||
|
||||
a->setData(data);
|
||||
a->setCheckable(true);
|
||||
a->setChecked(data == compare);
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
QAction* Menu::InsertAlphabetically(const QString &s)
|
||||
{
|
||||
QAction* action = new QAction(s, this);
|
||||
|
||||
@@ -131,6 +131,10 @@ public:
|
||||
return a;
|
||||
}
|
||||
|
||||
QAction* AddActionWithData(const QString& text,
|
||||
const QVariant& data,
|
||||
const QVariant& compare);
|
||||
|
||||
QAction *InsertAlphabetically(const QString& s);
|
||||
void InsertAlphabetically(QAction* entry);
|
||||
void InsertAlphabetically(Menu* menu);
|
||||
|
||||
@@ -79,7 +79,7 @@ void MenuShared::AddItemsForNewMenu(Menu *m)
|
||||
m->addAction(new_folder_item_);
|
||||
}
|
||||
|
||||
void MenuShared::AddItemsForEditMenu(Menu *m)
|
||||
void MenuShared::AddItemsForEditMenu(Menu *m, bool for_clips)
|
||||
{
|
||||
m->addAction(edit_cut_item_);
|
||||
m->addAction(edit_copy_item_);
|
||||
@@ -87,8 +87,11 @@ void MenuShared::AddItemsForEditMenu(Menu *m)
|
||||
m->addAction(edit_paste_insert_item_);
|
||||
m->addAction(edit_duplicate_item_);
|
||||
m->addAction(edit_delete_item_);
|
||||
m->addAction(edit_ripple_delete_item_);
|
||||
m->addAction(edit_split_item_);
|
||||
|
||||
if (for_clips) {
|
||||
m->addAction(edit_ripple_delete_item_);
|
||||
m->addAction(edit_split_item_);
|
||||
}
|
||||
}
|
||||
|
||||
void MenuShared::AddItemsForInOutMenu(Menu *m)
|
||||
|
||||
@@ -39,7 +39,7 @@ public:
|
||||
void Retranslate();
|
||||
|
||||
void AddItemsForNewMenu(Menu* m);
|
||||
void AddItemsForEditMenu(Menu* m);
|
||||
void AddItemsForEditMenu(Menu* m, bool for_clips);
|
||||
void AddItemsForInOutMenu(Menu* m);
|
||||
void AddItemsForClipEditMenu(Menu* m);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
widget/nodeview/nodeview.h
|
||||
widget/nodeview/nodeview.cpp
|
||||
widget/nodeview/nodeviewcommon.h
|
||||
widget/nodeview/nodeviewedge.h
|
||||
widget/nodeview/nodeviewedge.cpp
|
||||
widget/nodeview/nodeviewitem.h
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "core.h"
|
||||
#include "nodeviewundo.h"
|
||||
#include "node/factory.h"
|
||||
#include "widget/menu/menushared.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
@@ -37,13 +38,14 @@ NodeView::NodeView(QWidget *parent) :
|
||||
setScene(&scene_);
|
||||
setDragMode(RubberBandDrag);
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
setMouseTracking(true);
|
||||
setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
connect(&scene_, &QGraphicsScene::changed, this, &NodeView::ItemsChanged);
|
||||
connect(&scene_, &QGraphicsScene::selectionChanged, this, &NodeView::SceneSelectionChangedSlot);
|
||||
connect(this, &NodeView::customContextMenuRequested, this, &NodeView::ShowContextMenu);
|
||||
|
||||
setMouseTracking(true);
|
||||
setRenderHint(QPainter::Antialiasing);
|
||||
SetFlowDirection(NodeViewCommon::kTopToBottom);
|
||||
}
|
||||
|
||||
NodeView::~NodeView()
|
||||
@@ -321,10 +323,42 @@ void NodeView::ShowContextMenu(const QPoint &pos)
|
||||
|
||||
Menu m;
|
||||
|
||||
Menu* add_menu = NodeFactory::CreateMenu(&m);
|
||||
add_menu->setTitle(tr("Add"));
|
||||
connect(add_menu, &Menu::triggered, this, &NodeView::CreateNodeSlot);
|
||||
m.addMenu(add_menu);
|
||||
MenuShared::instance()->AddItemsForEditMenu(&m, false);
|
||||
|
||||
m.addSeparator();
|
||||
|
||||
if (itemAt(pos)) {
|
||||
QAction* autopos = m.addAction(tr("Auto-Position"));
|
||||
connect(autopos, &QAction::triggered, this, &NodeView::AutoPositionDescendents);
|
||||
} else {
|
||||
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));
|
||||
}
|
||||
@@ -341,6 +375,20 @@ void NodeView::CreateNodeSlot(QAction *action)
|
||||
}
|
||||
}
|
||||
|
||||
void NodeView::ContextMenuSetDirection(QAction *action)
|
||||
{
|
||||
SetFlowDirection(static_cast<NodeViewCommon::FlowDirection>(action->data().toInt()));
|
||||
}
|
||||
|
||||
void NodeView::AutoPositionDescendents()
|
||||
{
|
||||
QList<Node*> selected = scene_.GetSelectedNodes();
|
||||
|
||||
foreach (Node* n, selected) {
|
||||
scene_.ReorganizeFrom(n);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeView::PlaceNode(NodeViewItem *n, const QPointF &pos)
|
||||
{
|
||||
QRectF destination_rect = n->rect();
|
||||
@@ -487,4 +535,9 @@ void NodeView::DetachItemFromCursor()
|
||||
AttachItemToCursor(nullptr);
|
||||
}
|
||||
|
||||
void NodeView::SetFlowDirection(NodeViewCommon::FlowDirection dir)
|
||||
{
|
||||
scene_.SetFlowDirection(dir);
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -85,6 +85,8 @@ private:
|
||||
|
||||
void DetachItemFromCursor();
|
||||
|
||||
void SetFlowDirection(NodeViewCommon::FlowDirection dir);
|
||||
|
||||
NodeGraph* graph_;
|
||||
|
||||
NodeViewItem* attached_item_;
|
||||
@@ -117,6 +119,16 @@ private slots:
|
||||
*/
|
||||
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();
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 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"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // NODEVIEWCOMMON_H
|
||||
@@ -35,7 +35,8 @@ NodeViewEdge::NodeViewEdge(QGraphicsItem *parent) :
|
||||
QGraphicsPathItem(parent),
|
||||
edge_(nullptr),
|
||||
color_group_(QPalette::Active),
|
||||
color_role_(QPalette::Text)
|
||||
color_role_(QPalette::Text),
|
||||
flow_dir_(NodeViewCommon::kLeftToRight)
|
||||
{
|
||||
// Ensures this UI object is drawn behind other objects
|
||||
setZValue(-1);
|
||||
@@ -75,7 +76,8 @@ void NodeViewEdge::Adjust()
|
||||
}
|
||||
|
||||
// Draw a line between the two
|
||||
SetPoints(output->GetParamPoint(edge_->output()), input->GetParamPoint(edge_->input()));
|
||||
SetPoints(output->GetParamPoint(edge_->output()),
|
||||
input->GetParamPoint(edge_->input()));
|
||||
}
|
||||
|
||||
void NodeViewEdge::SetConnected(bool c)
|
||||
@@ -103,12 +105,26 @@ void NodeViewEdge::SetHighlighted(bool e)
|
||||
void NodeViewEdge::SetPoints(const QPointF &start, const QPointF &end)
|
||||
{
|
||||
QPainterPath path;
|
||||
double half_x = lerp(start.x(), end.x(), 0.5);
|
||||
path.moveTo(start);
|
||||
path.cubicTo(QPointF(half_x, start.y()), QPointF(half_x, end.y()), end);
|
||||
|
||||
if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal) {
|
||||
double half_x = lerp(start.x(), end.x(), 0.5);
|
||||
path.cubicTo(QPointF(half_x, start.y()), QPointF(half_x, end.y()), end);
|
||||
} else {
|
||||
double half_y = lerp(start.y(), end.y(), 0.5);
|
||||
path.cubicTo(QPointF(start.x(), half_y), QPointF(end.x(), half_y), end);
|
||||
}
|
||||
|
||||
setPath(path);
|
||||
}
|
||||
|
||||
void NodeViewEdge::SetFlowDirection(NodeViewCommon::FlowDirection dir)
|
||||
{
|
||||
flow_dir_ = dir;
|
||||
|
||||
Adjust();
|
||||
}
|
||||
|
||||
void NodeViewEdge::UpdatePen()
|
||||
{
|
||||
setPen(QPen(qApp->palette().color(color_group_, color_role_), edge_width_));
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <QPalette>
|
||||
|
||||
#include "node/edge.h"
|
||||
#include "nodeviewcommon.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
@@ -83,6 +84,11 @@ public:
|
||||
*/
|
||||
void SetPoints(const QPointF& start, const QPointF& end);
|
||||
|
||||
/**
|
||||
* @brief Sets the direction nodes are flowing
|
||||
*/
|
||||
void SetFlowDirection(NodeViewCommon::FlowDirection dir);
|
||||
|
||||
private:
|
||||
void UpdatePen();
|
||||
|
||||
@@ -94,6 +100,8 @@ private:
|
||||
|
||||
QPalette::ColorRole color_role_;
|
||||
|
||||
NodeViewCommon::FlowDirection flow_dir_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -46,7 +46,8 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) :
|
||||
expanded_(false),
|
||||
standard_click_(false),
|
||||
highlighted_index_(-1),
|
||||
node_edge_change_command_(nullptr)
|
||||
node_edge_change_command_(nullptr),
|
||||
flow_dir_(NodeViewCommon::kLeftToRight)
|
||||
{
|
||||
// Set flags for this widget
|
||||
setFlag(QGraphicsItem::ItemIsMovable);
|
||||
@@ -57,26 +58,36 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) :
|
||||
// We use font metrics to set all the UI measurements for DPI-awareness
|
||||
//
|
||||
|
||||
QFont default_font;
|
||||
QFontMetrics font_metrics(default_font);
|
||||
|
||||
// Set border width
|
||||
node_border_width_ = font_metrics.height() / 12;
|
||||
node_border_width_ = DefaultItemBorder();
|
||||
|
||||
// Set text and icon padding
|
||||
int node_text_padding = font_metrics.height() / 4;
|
||||
|
||||
// Not particularly great way of using text scaling to set the width (DPI-awareness, etc.)
|
||||
int widget_width = QFontMetricsWidth(font_metrics, "HHHHHHHHHHHHHH");
|
||||
|
||||
// Use the current default font height to size this widget
|
||||
// Set default "collapsed" size
|
||||
int widget_height = font_metrics.height() + node_text_padding * 2;
|
||||
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_);
|
||||
}
|
||||
|
||||
int NodeViewItem::DefaultTextPadding()
|
||||
{
|
||||
return QFontMetrics(QFont()).height() / 4;
|
||||
}
|
||||
|
||||
int NodeViewItem::DefaultItemHeight()
|
||||
{
|
||||
return QFontMetrics(QFont()).height() + DefaultTextPadding() * 2;
|
||||
}
|
||||
|
||||
int NodeViewItem::DefaultItemWidth()
|
||||
{
|
||||
return QFontMetricsWidth(QFontMetrics(QFont()), "HHHHHHHHHHHHHH");;
|
||||
}
|
||||
|
||||
int NodeViewItem::DefaultItemBorder()
|
||||
{
|
||||
return QFontMetrics(QFont()).height() / 12;
|
||||
}
|
||||
|
||||
void NodeViewItem::SetNode(Node *n)
|
||||
{
|
||||
node_ = n;
|
||||
@@ -203,6 +214,7 @@ void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
|
||||
// Create draggable object
|
||||
dragging_edge_ = new NodeViewEdge();
|
||||
dragging_edge_->SetFlowDirection(flow_dir_);
|
||||
|
||||
// Set up a QUndoCommand to make this action undoable
|
||||
node_edge_change_command_ = new QUndoCommand();
|
||||
@@ -330,7 +342,9 @@ void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
if (!cached_drop_item_->GetNode()->OutputsTo(node_)) {
|
||||
drag_dest_param_ = comp_param;
|
||||
highlight_their_index = i;
|
||||
end_point = cached_drop_item_->mapToScene(cached_drop_item_->GetInputPoint(i));
|
||||
|
||||
QPointF end_point_local = cached_drop_item_->GetInputPoint(i);
|
||||
end_point = cached_drop_item_->mapToScene(end_point_local);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -439,7 +453,19 @@ QRectF NodeViewItem::GetInputRect(int index) const
|
||||
QPointF NodeViewItem::GetParamPoint(NodeParam *param) const
|
||||
{
|
||||
if (param->type() == NodeParam::kOutput) {
|
||||
return pos() + QPointF(rect().right(), rect().center().y());
|
||||
|
||||
switch (flow_dir_) {
|
||||
case NodeViewCommon::kLeftToRight:
|
||||
default:
|
||||
return pos() + QPointF(rect().right(), rect().center().y());
|
||||
case NodeViewCommon::kRightToLeft:
|
||||
return pos() + QPointF(rect().left(), rect().center().y());
|
||||
case NodeViewCommon::kTopToBottom:
|
||||
return pos() + QPointF(rect().center().x(), rect().bottom());
|
||||
case NodeViewCommon::kBottomToTop:
|
||||
return pos() + QPointF(rect().center().x(), rect().top());
|
||||
}
|
||||
|
||||
} else {
|
||||
NodeInput* input = static_cast<NodeInput*>(param);
|
||||
|
||||
@@ -452,11 +478,26 @@ QPointF NodeViewItem::GetParamPoint(NodeParam *param) const
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewItem::SetFlowDirection(NodeViewCommon::FlowDirection dir)
|
||||
{
|
||||
flow_dir_ = dir;
|
||||
}
|
||||
|
||||
QPointF NodeViewItem::GetInputPoint(int index) const
|
||||
{
|
||||
QRectF input_rect = GetInputRect(index);
|
||||
|
||||
return QPointF(input_rect.left(), input_rect.center().y());
|
||||
switch (flow_dir_) {
|
||||
case NodeViewCommon::kLeftToRight:
|
||||
default:
|
||||
return QPointF(input_rect.left(), input_rect.center().y());
|
||||
case NodeViewCommon::kRightToLeft:
|
||||
return QPointF(input_rect.right(), input_rect.center().y());
|
||||
case NodeViewCommon::kTopToBottom:
|
||||
return QPointF(input_rect.center().x(), input_rect.top());
|
||||
case NodeViewCommon::kBottomToTop:
|
||||
return QPointF(input_rect.center().x(), input_rect.bottom());
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <QWidget>
|
||||
|
||||
#include "node/node.h"
|
||||
#include "nodeviewcommon.h"
|
||||
#include "nodeviewedge.h"
|
||||
#include "nodeviewitemwidgetproxy.h"
|
||||
|
||||
@@ -71,6 +72,19 @@ public:
|
||||
*/
|
||||
QPointF GetParamPoint(NodeParam* param) 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();
|
||||
|
||||
protected:
|
||||
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
|
||||
|
||||
@@ -153,6 +167,8 @@ private:
|
||||
*/
|
||||
QUndoCommand* node_edge_change_command_;
|
||||
|
||||
NodeViewCommon::FlowDirection flow_dir_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -20,14 +20,35 @@
|
||||
|
||||
#include "nodeviewscene.h"
|
||||
|
||||
#include "nodeviewedge.h"
|
||||
#include "nodeviewitem.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
NodeViewScene::NodeViewScene(QObject *parent) :
|
||||
QGraphicsScene(parent),
|
||||
graph_(nullptr)
|
||||
graph_(nullptr),
|
||||
direction_(NodeViewCommon::kLeftToRight)
|
||||
{
|
||||
connect(&reorganize_timer_, &QTimer::timeout, &reorganize_timer_, &QTimer::stop);
|
||||
connect(&reorganize_timer_, &QTimer::timeout, this, &NodeViewScene::Reorganize);
|
||||
}
|
||||
|
||||
void NodeViewScene::SetFlowDirection(NodeViewCommon::FlowDirection direction)
|
||||
{
|
||||
direction_ = direction;
|
||||
|
||||
{
|
||||
QHash<Node*, NodeViewItem*>::const_iterator i;
|
||||
for (i=item_map_.constBegin(); i!=item_map_.constEnd(); i++) {
|
||||
i.value()->SetFlowDirection(direction_);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
QHash<NodeEdge*, NodeViewEdge*>::const_iterator i;
|
||||
for (i=edge_map_.constBegin(); i!=edge_map_.constEnd(); i++) {
|
||||
i.value()->SetFlowDirection(direction_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewScene::clear()
|
||||
@@ -35,7 +56,7 @@ void NodeViewScene::clear()
|
||||
// Deselect everything (prevents signals that a selection has changed after deleting an object)
|
||||
DeselectAll();
|
||||
|
||||
// HACK: QGraphicsScene contains some sort of internal hashing of the selected items which doesn't update unless
|
||||
// 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.
|
||||
@@ -134,6 +155,7 @@ void NodeViewScene::AddNode(Node* node)
|
||||
NodeViewItem* item = new NodeViewItem();
|
||||
|
||||
item->SetNode(node);
|
||||
item->SetFlowDirection(direction_);
|
||||
|
||||
addItem(item);
|
||||
item_map_.insert(node, item);
|
||||
@@ -151,8 +173,6 @@ void NodeViewScene::AddNode(Node* node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QueueReorganize();
|
||||
}
|
||||
|
||||
void NodeViewScene::RemoveNode(Node *node)
|
||||
@@ -165,11 +185,10 @@ void NodeViewScene::AddEdge(NodeEdgePtr edge)
|
||||
NodeViewEdge* edge_ui = new NodeViewEdge();
|
||||
|
||||
edge_ui->SetEdge(edge);
|
||||
edge_ui->SetFlowDirection(direction_);
|
||||
|
||||
addItem(edge_ui);
|
||||
edge_map_.insert(edge.get(), edge_ui);
|
||||
|
||||
QueueReorganize();
|
||||
}
|
||||
|
||||
void NodeViewScene::RemoveEdge(NodeEdgePtr edge)
|
||||
@@ -177,145 +196,77 @@ void NodeViewScene::RemoveEdge(NodeEdgePtr edge)
|
||||
delete edge_map_.take(edge.get());
|
||||
}
|
||||
|
||||
void NodeViewScene::QueueReorganize()
|
||||
Qt::Orientation NodeViewScene::GetFlowOrientation() const
|
||||
{
|
||||
// Avoids the fairly complex Reorganize() function every single time a connection or node is added
|
||||
|
||||
reorganize_timer_.stop();
|
||||
reorganize_timer_.start(20);
|
||||
return NodeViewCommon::GetFlowOrientation(direction_);
|
||||
}
|
||||
|
||||
QList<Node *> NodeViewScene::GetNodeDirectDescendants(Node* n, const QList<Node*> connected_nodes, QList<Node*>& processed_nodes)
|
||||
NodeViewCommon::FlowDirection NodeViewScene::GetFlowDirection() const
|
||||
{
|
||||
QList<Node*> direct_descendants = connected_nodes;
|
||||
|
||||
processed_nodes.append(n);
|
||||
|
||||
// Remove any nodes that aren't necessarily attached directly
|
||||
for (int i=0;i<direct_descendants.size();i++) {
|
||||
Node* connected = direct_descendants.at(i);
|
||||
|
||||
for (int j=1;j<connected->output()->edges().size();j++) {
|
||||
Node* this_output_connection = connected->output()->edges().at(j)->input()->parentNode();
|
||||
if (!processed_nodes.contains(this_output_connection)) {
|
||||
direct_descendants.removeAt(i);
|
||||
i--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return direct_descendants;
|
||||
return direction_;
|
||||
}
|
||||
|
||||
int NodeViewScene::FindWeightsInternal(Node *node, QHash<Node *, int> &weights, QList<Node*>& weighted_nodes)
|
||||
void NodeViewScene::ReorganizeFrom(Node* n)
|
||||
{
|
||||
QList<Node*> connected_nodes = node->GetImmediateDependencies();
|
||||
QList<Node*> immediates = n->GetImmediateDependencies();
|
||||
|
||||
int weight = 0;
|
||||
|
||||
if (!connected_nodes.isEmpty()) {
|
||||
QList<Node*> direct_descendants = GetNodeDirectDescendants(node, connected_nodes, weighted_nodes);
|
||||
|
||||
foreach (Node* dep, direct_descendants) {
|
||||
weight += FindWeightsInternal(dep, weights, weighted_nodes);
|
||||
}
|
||||
}
|
||||
|
||||
weight = qMax(weight, 1);
|
||||
|
||||
weights.insert(node, weight);
|
||||
|
||||
return weight;
|
||||
}
|
||||
|
||||
void NodeViewScene::ReorganizeInternal(NodeViewItem* src_item, QHash<Node*, int>& weights, QList<Node*>& positioned_nodes)
|
||||
{
|
||||
if (!src_item) {
|
||||
if (immediates.isEmpty()) {
|
||||
// Nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
Node* n = src_item->GetNode();
|
||||
NodeViewItem* parent_item = NodeToUIObject(n);
|
||||
|
||||
QList<Node*> connected_nodes = n->GetImmediateDependencies();
|
||||
int item_sz, item_padding, layer_diff, total_top;
|
||||
|
||||
if (connected_nodes.isEmpty()) {
|
||||
return;
|
||||
if (GetFlowOrientation() == Qt::Vertical) {
|
||||
item_sz = NodeViewItem::DefaultItemWidth();
|
||||
item_padding = item_sz / 2;
|
||||
layer_diff = NodeViewItem::DefaultItemHeight() * 2;
|
||||
total_top = parent_item->x();
|
||||
} else {
|
||||
item_sz = NodeViewItem::DefaultItemHeight();
|
||||
item_padding = item_sz;
|
||||
layer_diff = NodeViewItem::DefaultItemWidth() * 3 / 2;
|
||||
total_top = parent_item->y();
|
||||
}
|
||||
|
||||
QList<Node*> direct_descendants = GetNodeDirectDescendants(n, connected_nodes, positioned_nodes);
|
||||
int item_sz_with_padding = item_sz + item_padding;
|
||||
|
||||
int descendant_weight = 0;
|
||||
foreach (Node* dep, direct_descendants) {
|
||||
descendant_weight += weights.value(dep);
|
||||
int total_sz = item_sz_with_padding * immediates.size() - item_padding;
|
||||
|
||||
total_top -= total_sz / 2;
|
||||
total_top += item_sz / 2;
|
||||
|
||||
int item_layer_pos;
|
||||
|
||||
switch (direction_) {
|
||||
case NodeViewCommon::kTopToBottom:
|
||||
item_layer_pos = parent_item->pos().y() - layer_diff;
|
||||
break;
|
||||
case NodeViewCommon::kLeftToRight:
|
||||
item_layer_pos = parent_item->pos().x() - layer_diff;
|
||||
break;
|
||||
case NodeViewCommon::kBottomToTop:
|
||||
item_layer_pos = parent_item->pos().y() + layer_diff;
|
||||
break;
|
||||
case NodeViewCommon::kRightToLeft:
|
||||
item_layer_pos = parent_item->pos().x() + layer_diff;
|
||||
break;
|
||||
}
|
||||
|
||||
qreal center_y = src_item->y();
|
||||
qreal total_height = descendant_weight * src_item->rect().height() + (direct_descendants.size()-1) * src_item->rect().height()/2;
|
||||
double item_top = center_y - (total_height/2) + src_item->rect().height()/2;
|
||||
for (int i=0;i<immediates.size();i++) {
|
||||
NodeViewItem* item = NodeToUIObject(immediates.at(i));
|
||||
|
||||
// Set each node's position
|
||||
int weight_index = 0;
|
||||
for (int i=0;i<direct_descendants.size();i++) {
|
||||
Node* connected = direct_descendants.at(i);
|
||||
|
||||
NodeViewItem* item = NodeToUIObject(connected);
|
||||
|
||||
if (!item) {
|
||||
continue;
|
||||
if (GetFlowOrientation() == Qt::Vertical) {
|
||||
item->setPos(total_top + item_sz_with_padding * i,
|
||||
item_layer_pos);
|
||||
} else {
|
||||
item->setPos(item_layer_pos,
|
||||
total_top + item_sz_with_padding * i);
|
||||
}
|
||||
|
||||
double item_y = item_top;
|
||||
|
||||
// Multiply the index by the item height (with 1.5 for padding)
|
||||
item_y += weight_index * src_item->rect().height() * 1.5;
|
||||
|
||||
QPointF item_pos(src_item->pos().x() - item->rect().width() * 3 / 2,
|
||||
item_y);
|
||||
|
||||
item->setPos(item_pos);
|
||||
|
||||
weight_index += weights.value(connected);
|
||||
}
|
||||
|
||||
// Recursively work on each node
|
||||
foreach (Node* connected, connected_nodes) {
|
||||
NodeViewItem* item = NodeToUIObject(connected);
|
||||
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ReorganizeInternal(item, weights, positioned_nodes);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewScene::Reorganize()
|
||||
{
|
||||
if (!graph_) {
|
||||
return;
|
||||
}
|
||||
|
||||
QList<Node*> end_nodes;
|
||||
|
||||
// Calculate the nodes that don't output to anything, they'll be our anchors
|
||||
foreach (Node* node, graph_->nodes()) {
|
||||
if (!node->HasConnectedOutputs()) {
|
||||
end_nodes.append(node);
|
||||
}
|
||||
}
|
||||
|
||||
QList<Node*> processed_nodes;
|
||||
|
||||
QHash<Node*, int> node_weights;
|
||||
foreach (Node* end_node, end_nodes) {
|
||||
FindWeightsInternal(end_node, node_weights, processed_nodes);
|
||||
}
|
||||
|
||||
processed_nodes.clear();
|
||||
|
||||
foreach (Node* end_node, end_nodes) {
|
||||
ReorganizeInternal(NodeToUIObject(end_node), node_weights, processed_nodes);
|
||||
ReorganizeFrom(immediates.at(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
#include <QTimer>
|
||||
|
||||
#include "node/graph.h"
|
||||
#include "widget/nodeview/nodeviewedge.h"
|
||||
#include "widget/nodeview/nodeviewitem.h"
|
||||
#include "nodeviewedge.h"
|
||||
#include "nodeviewitem.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
@@ -69,6 +69,16 @@ public:
|
||||
const QHash<Node*, NodeViewItem*>& item_map() const;
|
||||
const QHash<NodeEdge*, NodeViewEdge*>& edge_map() const;
|
||||
|
||||
Qt::Orientation GetFlowOrientation() const;
|
||||
|
||||
NodeViewCommon::FlowDirection GetFlowDirection() const;
|
||||
void SetFlowDirection(NodeViewCommon::FlowDirection direction);
|
||||
|
||||
/**
|
||||
* @brief Automatically reposition the nodes based on their connections
|
||||
*/
|
||||
void ReorganizeFrom(Node* n);
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Slot when a Node is added to a graph (SetGraph() connects this)
|
||||
@@ -103,27 +113,13 @@ public slots:
|
||||
void RemoveEdge(NodeEdgePtr edge);
|
||||
|
||||
private:
|
||||
void QueueReorganize();
|
||||
|
||||
QList<Node*> GetNodeDirectDescendants(Node* n, const QList<Node*> connected_nodes, QList<Node*>& processed_nodes);
|
||||
|
||||
int FindWeightsInternal(Node* node, QHash<Node*, int>& weights, QList<Node*>& weighted_nodes);
|
||||
|
||||
void ReorganizeInternal(NodeViewItem *src_item, QHash<Node*, int>& weights, QList<Node *> &positioned_nodes);
|
||||
|
||||
QHash<Node*, NodeViewItem*> item_map_;
|
||||
|
||||
QHash<NodeEdge*, NodeViewEdge*> edge_map_;
|
||||
|
||||
QTimer reorganize_timer_;
|
||||
|
||||
NodeGraph* graph_;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief Automatically reposition the nodes based on their connections
|
||||
*/
|
||||
void Reorganize();
|
||||
NodeViewCommon::FlowDirection direction_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ MainMenu::MainMenu(MainWindow *parent) :
|
||||
edit_menu_->addAction(edit_redo_item_);
|
||||
|
||||
edit_menu_->addSeparator();
|
||||
MenuShared::instance()->AddItemsForEditMenu(edit_menu_);
|
||||
MenuShared::instance()->AddItemsForEditMenu(edit_menu_, true);
|
||||
edit_menu_->addSeparator();
|
||||
edit_select_all_item_ = edit_menu_->AddItem("selectall", this, &MainMenu::SelectAllTriggered, "Ctrl+A");
|
||||
edit_deselect_all_item_ = edit_menu_->AddItem("deselectall", this, &MainMenu::DeselectAllTriggered, "Ctrl+Shift+A");
|
||||
|
||||
Reference in New Issue
Block a user