build/ci: green builds and tests on all three platforms
Compiler hygiene (all platforms): - Silence warnings across the tree: missing override, -Wreorder ctor init, -Wshadow, -Wsign-compare, missing switch cases, unused functions/captures, Qt 6.11 deprecations (QMouseEvent/QDropEvent accessors, qAsConst, Q_FOREACH over non-shared containers, AA_UseHighDpiPixmaps) and the .bak/ backup tree removal. - Fix regressions from the cleanup: missing clip decls in capi/timeline.cpp, plugin.cpp rename fallout, panel setFocus ambiguity, QGraphicsItem::pos vs event->position(), duplicate k_push_button case, boolean test variable. Windows: - Qt portability: CommandLineParser is_set/add_option, QTimeZone systemTimeZone (QTimeZone::LocalTime is 6.11-only), k_progress_* enum. - Linking: stop adding oakengine to OLIVE_LIBRARIES (import lib plus oakengine-obj caused multiple definitions); add OAKENGINE_STATIC so internal consumers no longer reference __imp_* stubs. - oakengine.ver: export olive::Renderer typeinfo so liboakgl.so can be dlopened (Linux), DynamicRenderer no longer dlcloses backend libraries (crash in RenderManager's dtor calling into unmapped memory). - OTIO runtime: copy DLLs next to every binary on Windows instead of relying on PATH (0xc0000135 in gtest discovery). - Headless GL: the runner only has GDI OpenGL 1.1, killing every render worker. Deploy Mesa llvmpipe as opengl32sw.dll (Qt's software-GL channel) with QT_OPENGL=software, and let QT_OPENGL override the AA_UseDesktopOpenGL default. ExportTask fails fast after 8 consecutive undelivered frames instead of segfaulting or grinding forever; FFmpegEncoder::write_frame tolerates null frames. - Tests: GetTempPathA+PID temp dirs, GetLongPathNameA for 8.3 names, forward-slash normalization when comparing project filenames. Linux: - Install libshaderc-dev so oakvulkan compiles GLSL (Vulkan tests). - Accept UNORM floor-or-round (63/64) in the blit ping-pong test. - Skip MainWindow construction test on the offscreen QPA (cannot paint QOpenGLWidget). Also: oak_cli_transcode gets a 300s ctest timeout, worker logs GL context version and LoadGraph/render_frame stages, and docs/plans/eliminate-event-bridge-issues.md (English translation).
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,301 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 OAK_NODEVIEW_H
|
|
||||||
#define OAK_NODEVIEW_H
|
|
||||||
|
|
||||||
#include <QGraphicsView>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#include "core.h"
|
|
||||||
#include "node/group/group.h"
|
|
||||||
#include "nodeviewedge.h"
|
|
||||||
#include "nodeviewcontext.h"
|
|
||||||
#include "nodeviewminimap.h"
|
|
||||||
#include "nodeviewscene.h"
|
|
||||||
#include "widget/handmovableview/handmovableview.h"
|
|
||||||
#include "widget/menu/menu.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 {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
NodeView(QWidget *parent = nullptr);
|
|
||||||
|
|
||||||
virtual ~NodeView() override;
|
|
||||||
|
|
||||||
void set_contexts(const QVector<Node *> &nodes);
|
|
||||||
|
|
||||||
const QVector<Node *> &get_contexts() const
|
|
||||||
{
|
|
||||||
if (overlay_view_) {
|
|
||||||
return overlay_view_->get_contexts();
|
|
||||||
} else {
|
|
||||||
return contexts_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_group_overlay() const
|
|
||||||
{
|
|
||||||
return overlay_view_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void close_contexts_belonging_to_project(Project *project);
|
|
||||||
|
|
||||||
void clear_graph();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Delete selected nodes from graph (user-friendly/undoable)
|
|
||||||
*/
|
|
||||||
void delete_selected();
|
|
||||||
|
|
||||||
void select_all();
|
|
||||||
void deselect_all();
|
|
||||||
|
|
||||||
void select(const QVector<Node::ContextPair> &nodes,
|
|
||||||
bool center_view_on_item);
|
|
||||||
|
|
||||||
void copy_selected(bool cut);
|
|
||||||
void paste();
|
|
||||||
|
|
||||||
void duplicate();
|
|
||||||
|
|
||||||
void set_color_label(int index);
|
|
||||||
|
|
||||||
void zoom_in();
|
|
||||||
|
|
||||||
void zoom_out();
|
|
||||||
|
|
||||||
const QVector<Node *> &get_current_contexts() const
|
|
||||||
{
|
|
||||||
return contexts_;
|
|
||||||
}
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void set_mini_map_enabled(bool e)
|
|
||||||
{
|
|
||||||
minimap_->setVisible(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
void show_add_menu()
|
|
||||||
{
|
|
||||||
Menu *m = create_add_menu(nullptr);
|
|
||||||
m->exec(QCursor::pos());
|
|
||||||
delete m;
|
|
||||||
}
|
|
||||||
|
|
||||||
void center_on_items_bounding_rect();
|
|
||||||
|
|
||||||
void center_on_node(OakEngineNode *n);
|
|
||||||
|
|
||||||
void label_selected_nodes();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void nodes_selected(const QVector<Node *> &nodes);
|
|
||||||
|
|
||||||
void nodes_deselected(const QVector<Node *> &nodes);
|
|
||||||
|
|
||||||
void node_selection_changed(const QVector<Node *> &nodes);
|
|
||||||
void
|
|
||||||
node_selection_changed_with_contexts(const QVector<Node::ContextPair> &nodes);
|
|
||||||
|
|
||||||
void node_group_opened(NodeGroup *group);
|
|
||||||
void node_group_closed();
|
|
||||||
|
|
||||||
void esc_pressed();
|
|
||||||
|
|
||||||
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 mouseDoubleClickEvent(QMouseEvent *event) override;
|
|
||||||
|
|
||||||
virtual void dragEnterEvent(QDragEnterEvent *event) override;
|
|
||||||
virtual void dragMoveEvent(QDragMoveEvent *event) override;
|
|
||||||
virtual void dropEvent(QDropEvent *event) override;
|
|
||||||
virtual void dragLeaveEvent(QDragLeaveEvent *event) override;
|
|
||||||
|
|
||||||
virtual void resizeEvent(QResizeEvent *event) override;
|
|
||||||
|
|
||||||
virtual void zoom_into_cursor_position(QWheelEvent *event, double multiplier,
|
|
||||||
const QPointF &cursor_pos) override;
|
|
||||||
|
|
||||||
virtual bool event(QEvent *event) override;
|
|
||||||
|
|
||||||
virtual bool eventFilter(QObject *object, QEvent *event) override;
|
|
||||||
|
|
||||||
virtual void changeEvent(QEvent *e) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void detach_items_from_cursor(bool delete_nodes_too = true);
|
|
||||||
|
|
||||||
void set_flow_direction(NodeViewCommon::FlowDirection dir);
|
|
||||||
|
|
||||||
void move_attached_nodes_to_cursor(const QPoint &p);
|
|
||||||
void process_moving_attached_nodes(const QPoint &pos);
|
|
||||||
QVector<Node *> process_dropping_attached_nodes(MultiUndoCommand *command,
|
|
||||||
Node *select_context,
|
|
||||||
const QPoint &pos);
|
|
||||||
Node *get_context_at_mouse_pos(const QPoint &p);
|
|
||||||
|
|
||||||
void connect_selection_changed_signal();
|
|
||||||
void disconnect_selection_changed_signal();
|
|
||||||
|
|
||||||
void zoom_from_keyboard(double multiplier);
|
|
||||||
|
|
||||||
void clear_create_edge_input_if_necessary();
|
|
||||||
|
|
||||||
QPointF get_estimated_position_for_context(NodeViewItem *item,
|
|
||||||
Node *context) const;
|
|
||||||
|
|
||||||
NodeViewItem *get_assumed_item_for_selected_node(Node *node);
|
|
||||||
bool get_assumed_position_for_selected_node(Node *node, Node::Position *pos);
|
|
||||||
|
|
||||||
Menu *create_add_menu(Menu *parent);
|
|
||||||
|
|
||||||
void position_new_edge(const QPoint &pos);
|
|
||||||
|
|
||||||
void add_context(Node *n);
|
|
||||||
|
|
||||||
void remove_context(Node *n);
|
|
||||||
|
|
||||||
bool is_item_attached_to_cursor(NodeViewItem *item) const;
|
|
||||||
|
|
||||||
void expand_item(NodeViewItem *item);
|
|
||||||
|
|
||||||
void collapse_item(NodeViewItem *item);
|
|
||||||
|
|
||||||
void end_edge_drag(bool cancel = false);
|
|
||||||
|
|
||||||
void post_paste(const QVector<Node *> &new_nodes,
|
|
||||||
const Node::PositionMap &map);
|
|
||||||
|
|
||||||
void resize_overlay();
|
|
||||||
|
|
||||||
NodeViewMiniMap *minimap_;
|
|
||||||
|
|
||||||
NodeViewContext *get_context_item_from_node_item(NodeViewItem *item);
|
|
||||||
|
|
||||||
struct AttachedItem {
|
|
||||||
NodeViewItem *item;
|
|
||||||
Node *node;
|
|
||||||
QPointF original_pos;
|
|
||||||
};
|
|
||||||
|
|
||||||
void set_attached_items(const QVector<AttachedItem> &items);
|
|
||||||
QVector<AttachedItem> attached_items_;
|
|
||||||
|
|
||||||
NodeViewEdge *drop_edge_;
|
|
||||||
NodeInput drop_input_;
|
|
||||||
|
|
||||||
NodeViewEdge *create_edge_;
|
|
||||||
NodeViewItem *create_edge_output_item_;
|
|
||||||
NodeViewItem *create_edge_input_item_;
|
|
||||||
NodeInput create_edge_input_;
|
|
||||||
bool create_edge_already_exists_;
|
|
||||||
bool create_edge_from_output_;
|
|
||||||
|
|
||||||
QVector<NodeViewItem *> create_edge_expanded_items_;
|
|
||||||
|
|
||||||
NodeViewScene scene_;
|
|
||||||
|
|
||||||
QVector<Node *> selected_nodes_;
|
|
||||||
|
|
||||||
QVector<Node *> contexts_;
|
|
||||||
QVector<Node *> last_set_filter_nodes_;
|
|
||||||
QMap<Node *, QPointF> context_offsets_;
|
|
||||||
|
|
||||||
QMap<NodeViewItem *, QPointF> dragging_items_;
|
|
||||||
|
|
||||||
NodeView *overlay_view_;
|
|
||||||
|
|
||||||
double scale_;
|
|
||||||
|
|
||||||
bool dont_emit_selection_signals_;
|
|
||||||
|
|
||||||
QAction *show_in_param_editor_action_;
|
|
||||||
|
|
||||||
static const double k_minimum_scale;
|
|
||||||
|
|
||||||
static const int k_maximum_contexts;
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
/**
|
|
||||||
* @brief Receiver for when the scene's selected items change
|
|
||||||
*/
|
|
||||||
void update_selection_cache();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Receiver for when the user right clicks (or otherwise requests a context menu)
|
|
||||||
*/
|
|
||||||
void show_context_menu(const QPoint &pos);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Receiver for when the user requests a new node from the add menu
|
|
||||||
*/
|
|
||||||
void create_node_slot(QAction *action);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Receiver for setting the direction from the context menu
|
|
||||||
*/
|
|
||||||
void context_menu_set_direction(QAction *action);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Opens the selected node in a Viewer
|
|
||||||
*/
|
|
||||||
void open_selected_node_in_viewer();
|
|
||||||
|
|
||||||
void update_scene_bounding_rect();
|
|
||||||
|
|
||||||
void reposition_mini_map();
|
|
||||||
|
|
||||||
void update_viewport_on_mini_map();
|
|
||||||
|
|
||||||
void move_to_scene_point(const QPointF &pos);
|
|
||||||
|
|
||||||
void node_removed_from_graph();
|
|
||||||
|
|
||||||
void group_nodes();
|
|
||||||
|
|
||||||
void ungroup_nodes();
|
|
||||||
|
|
||||||
void show_node_properties();
|
|
||||||
|
|
||||||
void show_selected_node_in_param_editor();
|
|
||||||
|
|
||||||
void item_about_to_be_deleted(NodeViewItem *item);
|
|
||||||
|
|
||||||
void close_overlay();
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // OAK_NODEVIEW_H
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 OAK_NODEVIEWCOMMON_H
|
|
||||||
#define OAK_NODEVIEWCOMMON_H
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
|
|
||||||
#include "common/define.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
class NodeViewCommon {
|
|
||||||
public:
|
|
||||||
enum FlowDirection {
|
|
||||||
k_invalid_direction = -1,
|
|
||||||
k_top_to_bottom,
|
|
||||||
k_bottom_to_top,
|
|
||||||
k_left_to_right,
|
|
||||||
k_right_to_left
|
|
||||||
};
|
|
||||||
|
|
||||||
static Qt::Orientation get_flow_orientation(FlowDirection dir)
|
|
||||||
{
|
|
||||||
if (dir == k_top_to_bottom || dir == k_bottom_to_top) {
|
|
||||||
return Qt::Vertical;
|
|
||||||
} else {
|
|
||||||
return Qt::Horizontal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool is_flow_vertical(FlowDirection dir)
|
|
||||||
{
|
|
||||||
return dir == k_top_to_bottom || dir == k_bottom_to_top;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool is_flow_horizontal(FlowDirection dir)
|
|
||||||
{
|
|
||||||
return dir == k_left_to_right || dir == k_right_to_left;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool directions_are_opposing(FlowDirection a, FlowDirection b)
|
|
||||||
{
|
|
||||||
return ((a == NodeViewCommon::k_left_to_right &&
|
|
||||||
b == NodeViewCommon::k_right_to_left) ||
|
|
||||||
(a == NodeViewCommon::k_right_to_left &&
|
|
||||||
b == NodeViewCommon::k_left_to_right) ||
|
|
||||||
(a == NodeViewCommon::k_top_to_bottom &&
|
|
||||||
b == NodeViewCommon::k_bottom_to_top) ||
|
|
||||||
(a == NodeViewCommon::k_bottom_to_top &&
|
|
||||||
b == NodeViewCommon::k_top_to_bottom));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // OAK_NODEVIEWCOMMON_H
|
|
||||||
@@ -1,412 +0,0 @@
|
|||||||
/*
|
|
||||||
* Oak Video Editor - Non-Linear Video Editor
|
|
||||||
* Copyright (C) 2025 Olive CE 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 "nodeviewcontext.h"
|
|
||||||
|
|
||||||
#include <QBrush>
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QGraphicsScene>
|
|
||||||
#include <QGraphicsSceneMouseEvent>
|
|
||||||
#include <QPen>
|
|
||||||
#include <QStyleOptionGraphicsItem>
|
|
||||||
|
|
||||||
#include "core.h"
|
|
||||||
#include "node/block/block.h"
|
|
||||||
#include "node/group/group.h"
|
|
||||||
#include "node/output/track/track.h"
|
|
||||||
#include "node/project.h"
|
|
||||||
#include "node/project/sequence/sequence.h"
|
|
||||||
#include "nodeviewitem.h"
|
|
||||||
#include "ui/colorcoding.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
#define super QGraphicsRectItem
|
|
||||||
|
|
||||||
NodeViewContext::NodeViewContext(Node *context, QGraphicsItem *item)
|
|
||||||
: super(item)
|
|
||||||
, context_(context)
|
|
||||||
{
|
|
||||||
Block *block = dynamic_cast<Block *>(context_);
|
|
||||||
if (block && block->track() && block->track()->sequence()) {
|
|
||||||
Rational timebase = block->track()
|
|
||||||
->sequence()
|
|
||||||
->get_video_params()
|
|
||||||
.frame_rate_as_time_base();
|
|
||||||
lbl_ =
|
|
||||||
QCoreApplication::translate("NodeViewContext", "%1 [%2] :: %3 - %4")
|
|
||||||
.arg(block->get_label_and_name(),
|
|
||||||
Track::Reference::type_to_translated_string(
|
|
||||||
block->track()->type()),
|
|
||||||
QString::fromStdString(Timecode::time_to_timecode(
|
|
||||||
block->in(), timebase,
|
|
||||||
Core::instance()->get_timecode_display())),
|
|
||||||
QString::fromStdString(Timecode::time_to_timecode(
|
|
||||||
block->out(), timebase,
|
|
||||||
Core::instance()->get_timecode_display())));
|
|
||||||
} else {
|
|
||||||
lbl_ = context_->get_label_and_name();
|
|
||||||
}
|
|
||||||
|
|
||||||
const Node::PositionMap &map = context_->get_context_positions();
|
|
||||||
for (auto it = map.cbegin(); it != map.cend(); it++) {
|
|
||||||
add_child(it.key());
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(context_, &Node::node_added_to_context, this,
|
|
||||||
&NodeViewContext::add_child, Qt::DirectConnection);
|
|
||||||
connect(context_, &Node::node_position_in_context_changed, this,
|
|
||||||
&NodeViewContext::set_child_position, Qt::DirectConnection);
|
|
||||||
connect(context_, &Node::node_removed_from_context, this,
|
|
||||||
&NodeViewContext::remove_child, Qt::DirectConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
NodeViewContext::~NodeViewContext()
|
|
||||||
{
|
|
||||||
// Delete edges before items, because the edge constructor references the items
|
|
||||||
qDeleteAll(edges_);
|
|
||||||
edges_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewContext::add_child(Node *node)
|
|
||||||
{
|
|
||||||
if (!context_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NodeViewItem *item = new NodeViewItem(node, context_, this);
|
|
||||||
item->set_flow_direction(flow_dir_);
|
|
||||||
|
|
||||||
add_node_internal(node, item);
|
|
||||||
|
|
||||||
if (NodeGroup *group = dynamic_cast<NodeGroup *>(node)) {
|
|
||||||
for (auto it = group->get_context_positions().cbegin();
|
|
||||||
it != group->get_context_positions().cend(); it++) {
|
|
||||||
// Use this item as the representative for all of these nodes too
|
|
||||||
add_node_internal(it.key(), item);
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(group, &NodeGroup::node_added_to_context, this,
|
|
||||||
&NodeViewContext::group_added_node);
|
|
||||||
connect(group, &NodeGroup::node_removed_from_context, this,
|
|
||||||
&NodeViewContext::group_removed_node);
|
|
||||||
}
|
|
||||||
|
|
||||||
update_rect();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewContext::set_child_position(Node *node, const QPointF &pos)
|
|
||||||
{
|
|
||||||
item_map_.value(node)->set_node_position(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewContext::remove_child(Node *node)
|
|
||||||
{
|
|
||||||
disconnect(node, &Node::input_connected, this,
|
|
||||||
&NodeViewContext::child_input_connected);
|
|
||||||
disconnect(node, &Node::input_disconnected, this,
|
|
||||||
&NodeViewContext::child_input_disconnected);
|
|
||||||
|
|
||||||
if (NodeGroup *group = dynamic_cast<NodeGroup *>(node)) {
|
|
||||||
disconnect(group, &NodeGroup::node_added_to_context, this,
|
|
||||||
&NodeViewContext::group_added_node);
|
|
||||||
disconnect(group, &NodeGroup::node_removed_from_context, this,
|
|
||||||
&NodeViewContext::group_removed_node);
|
|
||||||
}
|
|
||||||
|
|
||||||
NodeViewItem *item = item_map_.take(node);
|
|
||||||
|
|
||||||
// Remove from scene before emitting signal so that any drag functions that might be happening
|
|
||||||
// now can be handled before the item is destroyed
|
|
||||||
scene()->removeItem(item);
|
|
||||||
|
|
||||||
emit item_about_to_be_deleted(item);
|
|
||||||
|
|
||||||
// Delete edges first because the edge destructor will try to reference item (maybe that should
|
|
||||||
// be changed...)
|
|
||||||
QVector<NodeViewEdge *> edges_to_remove = item->get_all_edges_recursively();
|
|
||||||
foreach (NodeViewEdge *edge, edges_to_remove) {
|
|
||||||
if (node == item->get_node() || edge->output() == node ||
|
|
||||||
edge->input().node() == node) {
|
|
||||||
child_input_disconnected(edge->output(), edge->input());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this item is specifically for this node and the node is a group. If so, remove it for
|
|
||||||
// all other entries in the map.
|
|
||||||
if (item->get_node() == node) {
|
|
||||||
if (dynamic_cast<NodeGroup *>(item->get_node())) {
|
|
||||||
for (auto it = item_map_.begin(); it != item_map_.end();) {
|
|
||||||
if (it.value() == item) {
|
|
||||||
it = item_map_.erase(it);
|
|
||||||
} else {
|
|
||||||
it++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delete item;
|
|
||||||
}
|
|
||||||
|
|
||||||
update_rect();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewContext::child_input_connected(Node *output, const NodeInput &input)
|
|
||||||
{
|
|
||||||
// Add edge
|
|
||||||
if (!input.is_hidden()) {
|
|
||||||
if (NodeViewItem *output_item = item_map_.value(output)) {
|
|
||||||
add_edge_internal(
|
|
||||||
output, input, output_item,
|
|
||||||
item_map_.value(input.node())->get_item_for_input(input));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NodeViewContext::child_input_disconnected(Node *output,
|
|
||||||
const NodeInput &input)
|
|
||||||
{
|
|
||||||
// Remove edge
|
|
||||||
for (int i = 0; i < edges_.size(); i++) {
|
|
||||||
NodeViewEdge *e = edges_.at(i);
|
|
||||||
if (e->output() == output && e->input() == input) {
|
|
||||||
delete e;
|
|
||||||
edges_.removeAt(i);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
qreal get_text_offset(const QFontMetricsF &fm)
|
|
||||||
{
|
|
||||||
return fm.height() / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewContext::update_rect()
|
|
||||||
{
|
|
||||||
QFont f;
|
|
||||||
QFontMetricsF fm(f);
|
|
||||||
qreal lbl_offset = get_text_offset(fm);
|
|
||||||
|
|
||||||
QRectF cbr = childrenBoundingRect();
|
|
||||||
QRectF rect = cbr;
|
|
||||||
int pad = NodeViewItem::default_item_height();
|
|
||||||
rect.adjust(-pad, -lbl_offset * 2 - fm.height() - pad, pad, pad);
|
|
||||||
setRect(rect);
|
|
||||||
|
|
||||||
last_titlebar_height_ = rect.y() + (cbr.y() - rect.y()) - pad;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewContext::set_flow_direction(NodeViewCommon::FlowDirection dir)
|
|
||||||
{
|
|
||||||
flow_dir_ = dir;
|
|
||||||
|
|
||||||
foreach (NodeViewItem *item, item_map_) {
|
|
||||||
item->set_flow_direction(dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewContext::set_curved_edges(bool e)
|
|
||||||
{
|
|
||||||
curved_edges_ = e;
|
|
||||||
|
|
||||||
foreach (NodeViewEdge *edge, edges_) {
|
|
||||||
edge->set_curved(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int NodeViewContext::delete_selected(NodeViewDeleteCommand *command)
|
|
||||||
{
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
// Delete any selected edges
|
|
||||||
foreach (NodeViewEdge *edge, edges_) {
|
|
||||||
if (edge->isSelected()) {
|
|
||||||
command->add_edge(edge->output(), edge->input());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete any selected nodes
|
|
||||||
foreach (NodeViewItem *node, item_map_) {
|
|
||||||
if (node->isSelected()) {
|
|
||||||
command->add_node(node->get_node(), context_);
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewContext::select(const QVector<Node *> &nodes)
|
|
||||||
{
|
|
||||||
foreach (Node *n, nodes) {
|
|
||||||
if (NodeViewItem *item = item_map_.value(n)) {
|
|
||||||
item->setSelected(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<NodeViewItem *> NodeViewContext::get_selected_items() const
|
|
||||||
{
|
|
||||||
QVector<NodeViewItem *> items;
|
|
||||||
|
|
||||||
for (auto it = item_map_.cbegin(); it != item_map_.cend(); it++) {
|
|
||||||
if (it.value()->isSelected()) {
|
|
||||||
if (!items.contains(it.value())) {
|
|
||||||
items.append(it.value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
QPointF NodeViewContext::map_scene_pos_to_node_pos_in_context(const QPointF &pos) const
|
|
||||||
{
|
|
||||||
for (auto it = item_map_.cbegin(); it != item_map_.cend(); it++) {
|
|
||||||
QPointF pos_inside_parent =
|
|
||||||
it.value()->mapToParent(it.value()->mapFromScene(pos));
|
|
||||||
return NodeViewItem::screen_to_node_point(pos_inside_parent, flow_dir_);
|
|
||||||
}
|
|
||||||
return QPointF(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewContext::paint(QPainter *painter,
|
|
||||||
const QStyleOptionGraphicsItem *option,
|
|
||||||
QWidget *widget)
|
|
||||||
{
|
|
||||||
// Set pen and brush
|
|
||||||
Color color = context_->color();
|
|
||||||
QColor c = QtUtils::to_q_color(color);
|
|
||||||
QPen pen(c, 2);
|
|
||||||
if (option->state & QStyle::State_Selected) {
|
|
||||||
pen.setStyle(Qt::DotLine);
|
|
||||||
}
|
|
||||||
painter->setPen(pen);
|
|
||||||
|
|
||||||
QColor bg = c;
|
|
||||||
bg.setAlpha(128);
|
|
||||||
painter->setBrush(bg);
|
|
||||||
|
|
||||||
// Draw semi-transparent rect for whole item
|
|
||||||
int rounded = painter->fontMetrics().height();
|
|
||||||
painter->drawRoundedRect(rect(), rounded, rounded);
|
|
||||||
|
|
||||||
// Draw solid background for titlebar
|
|
||||||
QRectF titlebar_rect = rect();
|
|
||||||
titlebar_rect.setHeight(last_titlebar_height_ - rect().top());
|
|
||||||
painter->setClipRect(titlebar_rect);
|
|
||||||
painter->setBrush(c);
|
|
||||||
painter->drawRoundedRect(rect(), rounded, rounded);
|
|
||||||
painter->setClipping(false);
|
|
||||||
|
|
||||||
// Draw titlebar text
|
|
||||||
painter->setPen(ColorCoding::get_ui_selector_color(color));
|
|
||||||
|
|
||||||
int offset = get_text_offset(painter->fontMetrics());
|
|
||||||
|
|
||||||
QRectF text_rect = rect();
|
|
||||||
text_rect.adjust(offset, offset, -offset, -offset);
|
|
||||||
painter->drawText(text_rect, lbl_);
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant NodeViewContext::itemChange(GraphicsItemChange change,
|
|
||||||
const QVariant &value)
|
|
||||||
{
|
|
||||||
return super::itemChange(change, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewContext::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
|
||||||
{
|
|
||||||
bool clicked_inside_titlebar = (event->pos().y() < last_titlebar_height_);
|
|
||||||
|
|
||||||
setFlag(ItemIsMovable, clicked_inside_titlebar);
|
|
||||||
setFlag(ItemIsSelectable, clicked_inside_titlebar);
|
|
||||||
|
|
||||||
super::mousePressEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewContext::add_node_internal(Node *node, NodeViewItem *item)
|
|
||||||
{
|
|
||||||
connect(node, &Node::input_connected, this,
|
|
||||||
&NodeViewContext::child_input_connected);
|
|
||||||
connect(node, &Node::input_disconnected, this,
|
|
||||||
&NodeViewContext::child_input_disconnected);
|
|
||||||
|
|
||||||
item_map_.insert(node, item);
|
|
||||||
|
|
||||||
if (node == context_) {
|
|
||||||
item->set_label_as_output(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto it = node->output_connections().cbegin();
|
|
||||||
it != node->output_connections().cend(); it++) {
|
|
||||||
if (!it->second.is_hidden()) {
|
|
||||||
if (NodeViewItem *other_item = item_map_.value(it->second.node())) {
|
|
||||||
add_edge_internal(node, it->second, item,
|
|
||||||
other_item->get_item_for_input(it->second));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto it = node->input_connections().cbegin();
|
|
||||||
it != node->input_connections().cend(); it++) {
|
|
||||||
if (!it->first.is_hidden()) {
|
|
||||||
if (NodeViewItem *other_item = item_map_.value(it->second)) {
|
|
||||||
add_edge_internal(it->second, it->first, other_item,
|
|
||||||
item->get_item_for_input(it->first));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewContext::add_edge_internal(Node *output, const NodeInput &input,
|
|
||||||
NodeViewItem *from, NodeViewItem *to)
|
|
||||||
{
|
|
||||||
if (from == to) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NodeViewEdge *edge_ui = new NodeViewEdge(output, input, from, to, this);
|
|
||||||
|
|
||||||
edge_ui->adjust();
|
|
||||||
edge_ui->set_curved(curved_edges_);
|
|
||||||
|
|
||||||
edges_.append(edge_ui);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewContext::group_added_node(Node *node)
|
|
||||||
{
|
|
||||||
NodeGroup *group = static_cast<NodeGroup *>(sender());
|
|
||||||
|
|
||||||
add_node_internal(node, item_map_.value(group));
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewContext::group_removed_node(Node *node)
|
|
||||||
{
|
|
||||||
NodeGroup *group = static_cast<NodeGroup *>(sender());
|
|
||||||
|
|
||||||
if (item_map_.value(node) == item_map_.value(group)) {
|
|
||||||
item_map_.remove(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
/*
|
|
||||||
* Oak Video Editor - Non-Linear Video Editor
|
|
||||||
* Copyright (C) 2025 Olive CE 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 OAK_NODEVIEWCONTEXT_H
|
|
||||||
#define OAK_NODEVIEWCONTEXT_H
|
|
||||||
|
|
||||||
#include <QGraphicsRectItem>
|
|
||||||
#include <QGraphicsTextItem>
|
|
||||||
|
|
||||||
#include "node/node.h"
|
|
||||||
#include "node/nodeundo.h"
|
|
||||||
#include "nodeviewcommon.h"
|
|
||||||
#include "nodeviewedge.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
class NodeViewContext : public QObject, public QGraphicsRectItem {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
NodeViewContext(Node *context, QGraphicsItem *item = nullptr);
|
|
||||||
|
|
||||||
virtual ~NodeViewContext() override;
|
|
||||||
|
|
||||||
Node *get_context() const
|
|
||||||
{
|
|
||||||
return context_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void update_rect();
|
|
||||||
|
|
||||||
void set_flow_direction(NodeViewCommon::FlowDirection dir);
|
|
||||||
|
|
||||||
void set_curved_edges(bool e);
|
|
||||||
|
|
||||||
int delete_selected(NodeViewDeleteCommand *command);
|
|
||||||
|
|
||||||
void select(const QVector<Node *> &nodes);
|
|
||||||
|
|
||||||
QVector<NodeViewItem *> get_selected_items() const;
|
|
||||||
|
|
||||||
QPointF map_scene_pos_to_node_pos_in_context(const QPointF &pos) const;
|
|
||||||
|
|
||||||
NodeViewItem *get_item_from_map(Node *node) const
|
|
||||||
{
|
|
||||||
return item_map_.value(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void paint(QPainter *painter,
|
|
||||||
const QStyleOptionGraphicsItem *option,
|
|
||||||
QWidget *widget = nullptr) override;
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void add_child(Node *node);
|
|
||||||
|
|
||||||
void set_child_position(Node *node, const QPointF &pos);
|
|
||||||
|
|
||||||
void remove_child(Node *node);
|
|
||||||
|
|
||||||
void child_input_connected(Node *output, const NodeInput &input);
|
|
||||||
|
|
||||||
bool child_input_disconnected(Node *output, const NodeInput &input);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void item_about_to_be_deleted(NodeViewItem *item);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change,
|
|
||||||
const QVariant &value) override;
|
|
||||||
|
|
||||||
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void add_node_internal(Node *node, NodeViewItem *item);
|
|
||||||
|
|
||||||
void add_edge_internal(Node *output, const NodeInput &input,
|
|
||||||
NodeViewItem *from, NodeViewItem *to);
|
|
||||||
|
|
||||||
Node *context_;
|
|
||||||
|
|
||||||
QString lbl_;
|
|
||||||
|
|
||||||
NodeViewCommon::FlowDirection flow_dir_;
|
|
||||||
|
|
||||||
bool curved_edges_;
|
|
||||||
|
|
||||||
int last_titlebar_height_;
|
|
||||||
|
|
||||||
QMap<Node *, NodeViewItem *> item_map_;
|
|
||||||
|
|
||||||
QVector<NodeViewEdge *> edges_;
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void group_added_node(Node *node);
|
|
||||||
|
|
||||||
void group_removed_node(Node *node);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // OAK_NODEVIEWCONTEXT_H
|
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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/lerp.h"
|
|
||||||
#include "nodeview.h"
|
|
||||||
#include "nodeviewitem.h"
|
|
||||||
#include "nodeviewscene.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
#define super QGraphicsPathItem
|
|
||||||
|
|
||||||
NodeViewEdge::NodeViewEdge(Node *output, const NodeInput &input,
|
|
||||||
NodeViewItem *from_item, NodeViewItem *to_item,
|
|
||||||
QGraphicsItem *parent)
|
|
||||||
: super(parent)
|
|
||||||
, output_(output)
|
|
||||||
, input_(input)
|
|
||||||
, from_item_(from_item)
|
|
||||||
, to_item_(to_item)
|
|
||||||
{
|
|
||||||
init();
|
|
||||||
set_connected(true);
|
|
||||||
|
|
||||||
from_item_->add_edge(this);
|
|
||||||
to_item_->add_edge(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
NodeViewEdge::NodeViewEdge(QGraphicsItem *parent)
|
|
||||||
: QGraphicsPathItem(parent)
|
|
||||||
, from_item_(nullptr)
|
|
||||||
, to_item_(nullptr)
|
|
||||||
{
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
NodeViewEdge::~NodeViewEdge()
|
|
||||||
{
|
|
||||||
if (from_item_) {
|
|
||||||
from_item_->remove_edge(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (to_item_) {
|
|
||||||
to_item_->remove_edge(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewEdge::set_from_item(NodeViewItem *i)
|
|
||||||
{
|
|
||||||
if (from_item_) {
|
|
||||||
from_item_->remove_edge(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
from_item_ = i;
|
|
||||||
|
|
||||||
if (from_item_) {
|
|
||||||
from_item_->add_edge(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
adjust();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewEdge::set_to_item(NodeViewItem *i)
|
|
||||||
{
|
|
||||||
if (to_item_) {
|
|
||||||
to_item_->remove_edge(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
to_item_ = i;
|
|
||||||
|
|
||||||
if (to_item_) {
|
|
||||||
to_item_->add_edge(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
adjust();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewEdge::adjust()
|
|
||||||
{
|
|
||||||
// Draw a line between the two
|
|
||||||
set_points(from_item()->get_output_point(), to_item()->get_input_point());
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewEdge::set_connected(bool c)
|
|
||||||
{
|
|
||||||
connected_ = c;
|
|
||||||
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewEdge::set_highlighted(bool e)
|
|
||||||
{
|
|
||||||
highlighted_ = e;
|
|
||||||
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewEdge::set_points(const QPointF &start, const QPointF &end)
|
|
||||||
{
|
|
||||||
cached_start_ = start;
|
|
||||||
cached_end_ = end;
|
|
||||||
|
|
||||||
update_curve();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewEdge::set_curved(bool e)
|
|
||||||
{
|
|
||||||
curved_ = e;
|
|
||||||
|
|
||||||
update_curve();
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewEdge::init()
|
|
||||||
{
|
|
||||||
connected_ = false;
|
|
||||||
highlighted_ = false;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewEdge::update_curve()
|
|
||||||
{
|
|
||||||
const QPointF &start = cached_start_;
|
|
||||||
const QPointF &end = cached_end_;
|
|
||||||
|
|
||||||
QPainterPath path;
|
|
||||||
path.moveTo(start);
|
|
||||||
|
|
||||||
double angle = std::atan2(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;
|
|
||||||
|
|
||||||
NodeViewCommon::FlowDirection from_flow =
|
|
||||||
from_item_ ? from_item_->get_flow_direction() :
|
|
||||||
NodeViewCommon::k_invalid_direction;
|
|
||||||
NodeViewCommon::FlowDirection to_flow =
|
|
||||||
to_item_ ? to_item_->get_flow_direction() :
|
|
||||||
NodeViewCommon::k_invalid_direction;
|
|
||||||
|
|
||||||
if (from_flow == NodeViewCommon::k_invalid_direction &&
|
|
||||||
to_flow == NodeViewCommon::k_invalid_direction) {
|
|
||||||
// This is a technically unsupported scenario, but to avoid issues, we'll use a fallback
|
|
||||||
from_flow = NodeViewCommon::k_left_to_right;
|
|
||||||
to_flow = NodeViewCommon::k_left_to_right;
|
|
||||||
} else if (from_flow == NodeViewCommon::k_invalid_direction) {
|
|
||||||
from_flow = to_flow;
|
|
||||||
} else if (to_flow == NodeViewCommon::k_invalid_direction) {
|
|
||||||
to_flow = from_flow;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NodeViewCommon::get_flow_orientation(from_flow) == Qt::Horizontal) {
|
|
||||||
cp1 = QPointF(half_x, start.y());
|
|
||||||
} else {
|
|
||||||
cp1 = QPointF(start.x(), half_y);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NodeViewCommon::get_flow_orientation(to_flow) == Qt::Horizontal) {
|
|
||||||
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() - std::cos(angle);
|
|
||||||
|
|
||||||
double x1 = start.x();
|
|
||||||
double x2 = cp1.x();
|
|
||||||
double x3 = cp2.x();
|
|
||||||
double x4 = end.x();
|
|
||||||
double y1 = start.y();
|
|
||||||
double y2 = cp1.y();
|
|
||||||
double y3 = cp2.y();
|
|
||||||
double y4 = end.y();
|
|
||||||
|
|
||||||
if (start.x() >= end.x()) {
|
|
||||||
std::swap(x1, x4);
|
|
||||||
std::swap(x2, x3);
|
|
||||||
std::swap(y1, y4);
|
|
||||||
std::swap(y2, y3);
|
|
||||||
}
|
|
||||||
|
|
||||||
double t = Bezier::cubic_xto_t(continue_x, x1, x2, x3, x4);
|
|
||||||
double y = Bezier::cubic_tto_y(y1, y2, y3, y4, t);
|
|
||||||
|
|
||||||
angle = std::atan2(end.y() - y, end.x() - continue_x);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
path.lineTo(end);
|
|
||||||
}
|
|
||||||
|
|
||||||
setPath(mapFromScene(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 OAK_NODEEDGEITEM_H
|
|
||||||
#define OAK_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, const NodeInput &input, NodeViewItem *from_item,
|
|
||||||
NodeViewItem *to_item, QGraphicsItem *parent = nullptr);
|
|
||||||
|
|
||||||
NodeViewEdge(QGraphicsItem *parent = nullptr);
|
|
||||||
|
|
||||||
virtual ~NodeViewEdge() override;
|
|
||||||
|
|
||||||
Node *output() const
|
|
||||||
{
|
|
||||||
return output_;
|
|
||||||
}
|
|
||||||
|
|
||||||
const 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 set_from_item(NodeViewItem *i);
|
|
||||||
|
|
||||||
void set_to_item(NodeViewItem *i);
|
|
||||||
|
|
||||||
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 set_connected(bool c);
|
|
||||||
|
|
||||||
bool is_connected() const
|
|
||||||
{
|
|
||||||
return connected_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set highlighted state
|
|
||||||
*
|
|
||||||
* Changes color of edge.
|
|
||||||
*/
|
|
||||||
void set_highlighted(bool e);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set points to create curve from
|
|
||||||
*/
|
|
||||||
void set_points(const QPointF &start, const QPointF &end);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set whether edges should be drawn as curved or as straight lines
|
|
||||||
*/
|
|
||||||
void set_curved(bool e);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void paint(QPainter *painter,
|
|
||||||
const QStyleOptionGraphicsItem *option,
|
|
||||||
QWidget *widget = nullptr) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void init();
|
|
||||||
|
|
||||||
void update_curve();
|
|
||||||
|
|
||||||
Node *output_;
|
|
||||||
|
|
||||||
NodeInput input_;
|
|
||||||
|
|
||||||
int element_;
|
|
||||||
|
|
||||||
NodeViewItem *from_item_;
|
|
||||||
|
|
||||||
NodeViewItem *to_item_;
|
|
||||||
|
|
||||||
int edge_width_;
|
|
||||||
|
|
||||||
bool connected_;
|
|
||||||
|
|
||||||
bool highlighted_;
|
|
||||||
|
|
||||||
bool curved_;
|
|
||||||
|
|
||||||
QPointF cached_start_;
|
|
||||||
QPointF cached_end_;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // OAK_NODEEDGEITEM_H
|
|
||||||
@@ -1,907 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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/qtutils.h"
|
|
||||||
#include "config/config.h"
|
|
||||||
#include "core.h"
|
|
||||||
#include "node/nodeundo.h"
|
|
||||||
#include "node/value.h"
|
|
||||||
#include "pluginSupport/oliveplugininstance.h"
|
|
||||||
#include "nodeview.h"
|
|
||||||
#include "nodeviewscene.h"
|
|
||||||
#include "ui/colorcoding.h"
|
|
||||||
#include "ui/icons/icons.h"
|
|
||||||
#include "window/mainwindow/mainwindow.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
NodeViewItem::NodeViewItem(Node *node, const QString &input, int element,
|
|
||||||
Node *context, QGraphicsItem *parent)
|
|
||||||
: QGraphicsRectItem(parent)
|
|
||||||
, node_(node)
|
|
||||||
, input_(input)
|
|
||||||
, element_(element)
|
|
||||||
, context_(context)
|
|
||||||
, expanded_(false)
|
|
||||||
, highlighted_(false)
|
|
||||||
, flow_dir_(NodeViewCommon::k_invalid_direction)
|
|
||||||
, arrow_click_(false)
|
|
||||||
, label_as_output_(false)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// We use font metrics to set all the UI measurements for DPI-awareness
|
|
||||||
//
|
|
||||||
|
|
||||||
// Set border width
|
|
||||||
node_border_width_ = default_item_border();
|
|
||||||
|
|
||||||
// Set rect size to default
|
|
||||||
set_rect_size();
|
|
||||||
|
|
||||||
// Create connector
|
|
||||||
input_connector_ = new NodeViewItemConnector(false, this);
|
|
||||||
output_connector_ = new NodeViewItemConnector(true, this);
|
|
||||||
|
|
||||||
connect(node_, &Node::label_changed, this,
|
|
||||||
&NodeViewItem::node_appearance_changed);
|
|
||||||
connect(node_, &Node::color_changed, this,
|
|
||||||
&NodeViewItem::node_appearance_changed);
|
|
||||||
connect(node_, &Node::message_count_changed, this,
|
|
||||||
&NodeViewItem::node_appearance_changed);
|
|
||||||
|
|
||||||
if (is_output_item()) {
|
|
||||||
connect(node_, &Node::input_added, this,
|
|
||||||
&NodeViewItem::repopulate_inputs);
|
|
||||||
connect(node_, &Node::input_removed, this,
|
|
||||||
&NodeViewItem::repopulate_inputs);
|
|
||||||
repopulate_inputs();
|
|
||||||
|
|
||||||
// Set flags for this widget
|
|
||||||
setFlag(QGraphicsItem::ItemSendsGeometryChanges);
|
|
||||||
setFlag(QGraphicsItem::ItemIsMovable);
|
|
||||||
setFlag(QGraphicsItem::ItemIsSelectable);
|
|
||||||
|
|
||||||
if (context_) {
|
|
||||||
set_node_position(context_->get_node_position_data_in_context(node_));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
output_connector_->setVisible(false);
|
|
||||||
|
|
||||||
connect(node_, &Node::input_array_size_changed, this,
|
|
||||||
&NodeViewItem::input_array_size_changed);
|
|
||||||
connect(node_, &Node::input_array_size_changed, this,
|
|
||||||
&NodeViewItem::input_array_size_changed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This should be set during runtime, but just in case here's a default fallback
|
|
||||||
set_flow_direction(NodeViewCommon::k_left_to_right);
|
|
||||||
}
|
|
||||||
|
|
||||||
NodeViewItem::~NodeViewItem()
|
|
||||||
{
|
|
||||||
Q_ASSERT(edges_.isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
Node::Position NodeViewItem::get_node_position_data() const
|
|
||||||
{
|
|
||||||
return Node::Position(get_node_position(), is_expanded());
|
|
||||||
}
|
|
||||||
|
|
||||||
QPointF NodeViewItem::get_node_position() const
|
|
||||||
{
|
|
||||||
return screen_to_node_point(pos(), flow_dir_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::set_node_position(const QPointF &pos)
|
|
||||||
{
|
|
||||||
cached_node_pos_ = pos;
|
|
||||||
|
|
||||||
update_node_position();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::set_node_position(const Node::Position &pos)
|
|
||||||
{
|
|
||||||
set_node_position(pos.position);
|
|
||||||
set_expanded(pos.expanded);
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<NodeViewEdge *> NodeViewItem::get_all_edges_recursively() const
|
|
||||||
{
|
|
||||||
QVector<NodeViewEdge *> list = edges_;
|
|
||||||
|
|
||||||
foreach (NodeViewItem *item, children_) {
|
|
||||||
list.append(item->get_all_edges_recursively());
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
int NodeViewItem::default_text_padding()
|
|
||||||
{
|
|
||||||
return QFontMetrics(QFont()).height() / 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
int NodeViewItem::default_item_height()
|
|
||||||
{
|
|
||||||
return QFontMetrics(QFont()).height() + default_text_padding() * 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
int NodeViewItem::default_item_width()
|
|
||||||
{
|
|
||||||
return QtUtils::q_font_metrics_width(QFontMetrics(QFont()),
|
|
||||||
"HHHHHHHHHHHHHHHH");
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
int NodeViewItem::default_item_border()
|
|
||||||
{
|
|
||||||
return QFontMetrics(QFont()).height() / 12;
|
|
||||||
}
|
|
||||||
|
|
||||||
QPointF NodeViewItem::node_to_screen_point(QPointF p,
|
|
||||||
NodeViewCommon::FlowDirection direction)
|
|
||||||
{
|
|
||||||
switch (direction) {
|
|
||||||
case NodeViewCommon::k_left_to_right:
|
|
||||||
// NodeGraphs are always left-to-right internally, no need to translate
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_right_to_left:
|
|
||||||
// Invert X value
|
|
||||||
p.setX(-p.x());
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_top_to_bottom:
|
|
||||||
// Swap X/Y
|
|
||||||
p = QPointF(p.y(), p.x());
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_bottom_to_top:
|
|
||||||
// Swap X/Y and invert Y
|
|
||||||
p = QPointF(p.y(), -p.x());
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_invalid_direction:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multiply by item sizes for this direction
|
|
||||||
p.setX(p.x() * default_item_horizontal_padding(direction));
|
|
||||||
p.setY(p.y() * default_item_vertical_padding(direction));
|
|
||||||
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
QPointF NodeViewItem::screen_to_node_point(QPointF p,
|
|
||||||
NodeViewCommon::FlowDirection direction)
|
|
||||||
{
|
|
||||||
// Divide by item sizes for this direction
|
|
||||||
p.setX(p.x() / default_item_horizontal_padding(direction));
|
|
||||||
p.setY(p.y() / default_item_vertical_padding(direction));
|
|
||||||
|
|
||||||
switch (direction) {
|
|
||||||
case NodeViewCommon::k_left_to_right:
|
|
||||||
// NodeGraphs are always left-to-right internally, no need to translate
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_right_to_left:
|
|
||||||
// Invert X value
|
|
||||||
p.setX(-p.x());
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_top_to_bottom:
|
|
||||||
// Swap X/Y
|
|
||||||
p = QPointF(p.y(), p.x());
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_bottom_to_top:
|
|
||||||
// Swap X/Y and invert Y
|
|
||||||
p = QPointF(-p.y(), p.x());
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_invalid_direction:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
qreal NodeViewItem::default_item_horizontal_padding(
|
|
||||||
NodeViewCommon::FlowDirection dir)
|
|
||||||
{
|
|
||||||
if (NodeViewCommon::get_flow_orientation(dir) == Qt::Horizontal) {
|
|
||||||
return default_item_width() * 1.5;
|
|
||||||
} else {
|
|
||||||
return default_item_width() * 1.25;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
qreal NodeViewItem::default_item_vertical_padding(NodeViewCommon::FlowDirection dir)
|
|
||||||
{
|
|
||||||
if (NodeViewCommon::get_flow_orientation(dir) == Qt::Horizontal) {
|
|
||||||
return default_item_height() * 1.5;
|
|
||||||
} else {
|
|
||||||
return default_item_height() * 2.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
qreal NodeViewItem::default_item_horizontal_padding() const
|
|
||||||
{
|
|
||||||
return default_item_horizontal_padding(flow_dir_);
|
|
||||||
}
|
|
||||||
|
|
||||||
qreal NodeViewItem::default_item_vertical_padding() const
|
|
||||||
{
|
|
||||||
return default_item_vertical_padding(flow_dir_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::add_edge(NodeViewEdge *edge)
|
|
||||||
{
|
|
||||||
edges_.append(edge);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::remove_edge(NodeViewEdge *edge)
|
|
||||||
{
|
|
||||||
edges_.removeOne(edge);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::set_expanded(bool e, bool hide_titlebar)
|
|
||||||
{
|
|
||||||
if (!can_be_expanded() || (expanded_ == e)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
expanded_ = e;
|
|
||||||
|
|
||||||
if (context_) {
|
|
||||||
context_->set_node_expanded_in_context(node_, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_output_item()) {
|
|
||||||
// We don't have to check has_connectable_inputs_ here because we did it at the top
|
|
||||||
input_connector_->setVisible(!expanded_);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expanded_) {
|
|
||||||
node_->retranslate();
|
|
||||||
|
|
||||||
if (is_output_item()) {
|
|
||||||
// Create items for each input of the node
|
|
||||||
foreach (const QString &input, node_->inputs()) {
|
|
||||||
if (is_input_valid(input)) {
|
|
||||||
NodeViewItem *item =
|
|
||||||
new NodeViewItem(node_, input, -1, context_, this);
|
|
||||||
children_.append(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<NodeViewEdge *> edges = edges_;
|
|
||||||
for (auto it = edges.cbegin(); it != edges.cend(); it++) {
|
|
||||||
if ((*it)->to_item() == this) {
|
|
||||||
(*it)->set_to_item(get_item_for_input((*it)->input()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Create items for each element of the input array
|
|
||||||
int arr_sz = node_->input_array_size(input_);
|
|
||||||
children_.resize(arr_sz);
|
|
||||||
for (int i = 0; i < arr_sz; i++) {
|
|
||||||
NodeViewItem *item =
|
|
||||||
new NodeViewItem(node_, input_, i, context_, this);
|
|
||||||
children_[i] = item;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<NodeViewEdge *> edges = edges_;
|
|
||||||
for (auto it = edges.cbegin(); it != edges.cend(); it++) {
|
|
||||||
if ((*it)->to_item() == this) {
|
|
||||||
(*it)->set_to_item(get_item_for_input((*it)->input()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
foreach (NodeViewItem *child, children_) {
|
|
||||||
QVector<NodeViewEdge *> child_edges = child->edges();
|
|
||||||
foreach (NodeViewEdge *edge, child_edges) {
|
|
||||||
edge->set_to_item(this);
|
|
||||||
}
|
|
||||||
delete child;
|
|
||||||
}
|
|
||||||
children_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
update_children_positions();
|
|
||||||
|
|
||||||
if (flow_dir_ == NodeViewCommon::k_top_to_bottom) {
|
|
||||||
update_output_connector_position();
|
|
||||||
}
|
|
||||||
|
|
||||||
readjust_all_edges();
|
|
||||||
|
|
||||||
update_context_rect();
|
|
||||||
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::toggle_expanded()
|
|
||||||
{
|
|
||||||
set_expanded(!is_expanded());
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::paint(QPainter *painter,
|
|
||||||
const QStyleOptionGraphicsItem *option, QWidget *)
|
|
||||||
{
|
|
||||||
// Use main window palette since the palette passed in `widget` is the NodeView palette which
|
|
||||||
// has been slightly modified
|
|
||||||
QPalette app_pal = Core::instance()->main_window()->palette();
|
|
||||||
|
|
||||||
// We only draw a single unit's worth
|
|
||||||
QRectF single_unit_rect = rect();
|
|
||||||
single_unit_rect.setHeight(default_item_height());
|
|
||||||
|
|
||||||
if (is_output_item()) {
|
|
||||||
// Set output item colors
|
|
||||||
painter->setPen(Qt::black);
|
|
||||||
painter->setBrush(
|
|
||||||
node_->brush(single_unit_rect.top(), single_unit_rect.bottom()));
|
|
||||||
} else {
|
|
||||||
// Set input item colors
|
|
||||||
painter->setPen(Qt::NoPen);
|
|
||||||
painter->setBrush(element_ == -1 ? app_pal.color(QPalette::Window) :
|
|
||||||
app_pal.color(QPalette::Base));
|
|
||||||
}
|
|
||||||
|
|
||||||
painter->drawRect(single_unit_rect);
|
|
||||||
|
|
||||||
// Draw highlight if applicable
|
|
||||||
if (highlighted_) {
|
|
||||||
QColor highlight_col = app_pal.color(QPalette::Text);
|
|
||||||
highlight_col.setAlpha(64);
|
|
||||||
painter->setBrush(highlight_col);
|
|
||||||
painter->drawRect(rect());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine what text to draw and whether to draw an arrow
|
|
||||||
QString node_label, node_name;
|
|
||||||
|
|
||||||
if (is_output_item()) {
|
|
||||||
if (label_as_output_) {
|
|
||||||
node_name = QCoreApplication::translate("NodeViewItem", "Output");
|
|
||||||
} else {
|
|
||||||
node_label = node_->get_label();
|
|
||||||
node_name = node_->short_name();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (element_ == -1) {
|
|
||||||
node_name = node_->get_input_name(input_);
|
|
||||||
} else {
|
|
||||||
node_name = QString::number(
|
|
||||||
element_ +
|
|
||||||
node_->get_input_property(input_, QStringLiteral("arraystart"))
|
|
||||||
.toInt());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw arrow if necessary
|
|
||||||
int arrow_size = can_be_expanded() ? draw_expand_arrow(painter) : 0;
|
|
||||||
|
|
||||||
if (is_output_item()) {
|
|
||||||
// Determine the text color (automatically calculate from node background color)
|
|
||||||
painter->setPen(ColorCoding::get_ui_selector_color(node_->color()));
|
|
||||||
} else {
|
|
||||||
// Just use text item
|
|
||||||
painter->setPen(app_pal.text().color());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node_label.isEmpty()) {
|
|
||||||
// Draw name only
|
|
||||||
draw_node_title(painter, node_name, single_unit_rect, Qt::AlignVCenter,
|
|
||||||
arrow_size);
|
|
||||||
} else {
|
|
||||||
int text_pad = default_text_padding() / 2;
|
|
||||||
QRectF safe_label_bounds =
|
|
||||||
single_unit_rect.adjusted(text_pad, text_pad, -text_pad, -text_pad);
|
|
||||||
QFont f;
|
|
||||||
qreal font_sz = f.pointSizeF();
|
|
||||||
|
|
||||||
// Draw label as larger/upper text
|
|
||||||
f.setPointSizeF(font_sz * 0.8);
|
|
||||||
painter->setFont(f);
|
|
||||||
draw_node_title(painter, node_label, safe_label_bounds, Qt::AlignTop,
|
|
||||||
arrow_size);
|
|
||||||
|
|
||||||
// Draw node name as smaller/lower text
|
|
||||||
f.setPointSizeF(font_sz * 0.6);
|
|
||||||
painter->setFont(f);
|
|
||||||
draw_node_title(painter, node_name, safe_label_bounds, Qt::AlignBottom,
|
|
||||||
arrow_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_output_item()) {
|
|
||||||
auto *instance = node_->getPluginInstance();
|
|
||||||
auto *olive_instance =
|
|
||||||
dynamic_cast<plugin::OlivePluginInstance *>(instance);
|
|
||||||
int message_count =
|
|
||||||
olive_instance ? olive_instance->persistent_message_count() : 0;
|
|
||||||
|
|
||||||
if (message_count > 0) {
|
|
||||||
QString badge_text = QString::number(message_count);
|
|
||||||
QFont badge_font = painter->font();
|
|
||||||
badge_font.setPointSizeF(badge_font.pointSizeF() * 0.7);
|
|
||||||
painter->setFont(badge_font);
|
|
||||||
|
|
||||||
QFontMetrics badge_metrics(badge_font);
|
|
||||||
int text_width = badge_metrics.horizontalAdvance(badge_text);
|
|
||||||
int text_height = badge_metrics.height();
|
|
||||||
int pad = text_height / 3;
|
|
||||||
int badge_width = qMax(text_width + pad * 2, text_height + pad);
|
|
||||||
int badge_height = text_height + pad;
|
|
||||||
|
|
||||||
QRectF badge_rect(single_unit_rect.right() - badge_width - 4,
|
|
||||||
single_unit_rect.top() + 4, badge_width,
|
|
||||||
badge_height);
|
|
||||||
|
|
||||||
painter->setPen(Qt::NoPen);
|
|
||||||
painter->setBrush(QColor(220, 50, 47));
|
|
||||||
painter->drawRoundedRect(badge_rect, badge_height / 2,
|
|
||||||
badge_height / 2);
|
|
||||||
|
|
||||||
painter->setPen(Qt::white);
|
|
||||||
painter->drawText(badge_rect, Qt::AlignCenter, badge_text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw final border (output only)
|
|
||||||
if (is_output_item()) {
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
if (last_arrow_rect_.contains(event->pos().toPoint())) {
|
|
||||||
arrow_click_ = true;
|
|
||||||
toggle_expanded();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
event->setModifiers(
|
|
||||||
QtUtils::flip_control_and_shift_modifiers(event->modifiers()));
|
|
||||||
|
|
||||||
QGraphicsRectItem::mousePressEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
|
||||||
{
|
|
||||||
if (arrow_click_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
event->setModifiers(
|
|
||||||
QtUtils::flip_control_and_shift_modifiers(event->modifiers()));
|
|
||||||
|
|
||||||
QGraphicsRectItem::mouseMoveEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
|
||||||
{
|
|
||||||
if (arrow_click_) {
|
|
||||||
arrow_click_ = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
event->setModifiers(
|
|
||||||
QtUtils::flip_control_and_shift_modifiers(event->modifiers()));
|
|
||||||
|
|
||||||
QGraphicsRectItem::mouseReleaseEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant NodeViewItem::itemChange(QGraphicsItem::GraphicsItemChange change,
|
|
||||||
const QVariant &value)
|
|
||||||
{
|
|
||||||
if (node_) {
|
|
||||||
if (change == ItemPositionHasChanged) {
|
|
||||||
readjust_all_edges();
|
|
||||||
|
|
||||||
update_context_rect();
|
|
||||||
} else if (change == ItemSelectedHasChanged) {
|
|
||||||
if (value.toBool()) {
|
|
||||||
qDebug() << "Selected node:" << node_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return QGraphicsItem::itemChange(change, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::readjust_all_edges()
|
|
||||||
{
|
|
||||||
foreach (NodeViewEdge *edge, edges_) {
|
|
||||||
if (NodeViewItem *to_item = edge->to_item()) {
|
|
||||||
static_cast<NodeViewItem *>(to_item->parentItem())
|
|
||||||
->update_flow_direction_of_input_item(to_item);
|
|
||||||
}
|
|
||||||
|
|
||||||
edge->adjust();
|
|
||||||
}
|
|
||||||
foreach (NodeViewItem *child, children_) {
|
|
||||||
child->readjust_all_edges();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::update_context_rect()
|
|
||||||
{
|
|
||||||
QGraphicsItem *item = parentItem();
|
|
||||||
|
|
||||||
while (item) {
|
|
||||||
if (NodeViewContext *ctx = dynamic_cast<NodeViewContext *>(item)) {
|
|
||||||
ctx->update_rect();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
item = item->parentItem();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::draw_node_title(QPainter *painter, QString text,
|
|
||||||
const QRectF &rect,
|
|
||||||
Qt::Alignment vertical_align,
|
|
||||||
int icon_full_size)
|
|
||||||
{
|
|
||||||
QFontMetrics fm = painter->fontMetrics();
|
|
||||||
|
|
||||||
// Calculate how much space we have for text
|
|
||||||
int item_width = this->rect().width();
|
|
||||||
int max_text_width = item_width - default_text_padding() * 2 - icon_full_size;
|
|
||||||
int label_width = QtUtils::q_font_metrics_width(fm, text);
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
text.chop(1);
|
|
||||||
concatenated =
|
|
||||||
QCoreApplication::translate("NodeViewItem", "%1...").arg(text);
|
|
||||||
} while ((label_width = QtUtils::q_font_metrics_width(fm, concatenated)) >
|
|
||||||
max_text_width);
|
|
||||||
|
|
||||||
text = concatenated;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine X position (favors horizontal centering unless it'll overrun the arrow)
|
|
||||||
QRectF text_rect = rect;
|
|
||||||
Qt::Alignment text_align = Qt::AlignHCenter | vertical_align;
|
|
||||||
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 | vertical_align;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the text in a rect (the rect is sized around text already in the constructor)
|
|
||||||
painter->drawText(text_rect, text_align, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
int NodeViewItem::draw_expand_arrow(QPainter *painter)
|
|
||||||
{
|
|
||||||
// Draw right or down arrow based on expanded state
|
|
||||||
int icon_size = painter->fontMetrics().height() / 2;
|
|
||||||
int icon_padding = default_item_height() / 2 - icon_size / 2;
|
|
||||||
int icon_full_size = icon_size + icon_padding * 2;
|
|
||||||
|
|
||||||
painter->setRenderHint(QPainter::SmoothPixmapTransform);
|
|
||||||
|
|
||||||
const QIcon &expand_icon = is_expanded() ? icon::tri_down : icon::tri_right;
|
|
||||||
int icon_size_scaled = icon_size * painter->transform().m11();
|
|
||||||
|
|
||||||
last_arrow_rect_ = QRect(this->rect().x() + icon_padding,
|
|
||||||
this->rect().y() + icon_padding, icon_size,
|
|
||||||
icon_size);
|
|
||||||
|
|
||||||
painter->drawPixmap(
|
|
||||||
last_arrow_rect_,
|
|
||||||
expand_icon.pixmap(QSize(icon_size_scaled, icon_size_scaled)));
|
|
||||||
|
|
||||||
return icon_full_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::set_label_as_output(bool e)
|
|
||||||
{
|
|
||||||
label_as_output_ = e;
|
|
||||||
output_connector_->setVisible(!e);
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
QPointF NodeViewItem::get_input_point() const
|
|
||||||
{
|
|
||||||
return input_connector_->scenePos();
|
|
||||||
}
|
|
||||||
|
|
||||||
QPointF NodeViewItem::get_output_point() const
|
|
||||||
{
|
|
||||||
QPointF p = output_connector_->scenePos();
|
|
||||||
QRectF r = output_connector_->polygon().boundingRect();
|
|
||||||
|
|
||||||
switch (flow_dir_) {
|
|
||||||
case NodeViewCommon::k_left_to_right:
|
|
||||||
default:
|
|
||||||
p.setX(p.x() + r.width());
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_right_to_left:
|
|
||||||
p.setX(p.x() - r.width());
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_top_to_bottom:
|
|
||||||
p.setY(p.y() + r.height());
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_bottom_to_top:
|
|
||||||
p.setY(p.y() - r.height());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::set_flow_direction(NodeViewCommon::FlowDirection dir)
|
|
||||||
{
|
|
||||||
if (flow_dir_ != dir) {
|
|
||||||
flow_dir_ = dir;
|
|
||||||
|
|
||||||
input_connector_->set_flow_direction(dir);
|
|
||||||
output_connector_->set_flow_direction(dir);
|
|
||||||
|
|
||||||
update_input_connector_position();
|
|
||||||
update_output_connector_position();
|
|
||||||
|
|
||||||
if (is_output_item()) {
|
|
||||||
update_node_position();
|
|
||||||
}
|
|
||||||
|
|
||||||
readjust_all_edges();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::update_node_position()
|
|
||||||
{
|
|
||||||
setPos(node_to_screen_point(cached_node_pos_, flow_dir_));
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::update_input_connector_position()
|
|
||||||
{
|
|
||||||
QRectF output_rect = input_connector_->polygon().boundingRect();
|
|
||||||
|
|
||||||
NodeViewCommon::FlowDirection using_flow_dir = flow_dir_;
|
|
||||||
|
|
||||||
if (is_expanded() && !NodeViewCommon::is_flow_horizontal(flow_dir_)) {
|
|
||||||
if (edges_.isEmpty() || edges_.first()->from_item()->x() < this->x()) {
|
|
||||||
using_flow_dir = NodeViewCommon::k_left_to_right;
|
|
||||||
} else {
|
|
||||||
using_flow_dir = NodeViewCommon::k_right_to_left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input connector flow directions change conditionally
|
|
||||||
switch (using_flow_dir) {
|
|
||||||
case NodeViewCommon::k_left_to_right:
|
|
||||||
input_connector_->setPos(rect().left() - output_rect.width(), 0);
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_right_to_left:
|
|
||||||
input_connector_->setPos(rect().right() + output_rect.width(), 0);
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_top_to_bottom:
|
|
||||||
input_connector_->setPos(rect().center().x(),
|
|
||||||
rect().top() - output_rect.height());
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_bottom_to_top:
|
|
||||||
input_connector_->setPos(rect().center().x(),
|
|
||||||
rect().bottom() + output_rect.height());
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_invalid_direction:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::update_output_connector_position()
|
|
||||||
{
|
|
||||||
switch (flow_dir_) {
|
|
||||||
case NodeViewCommon::k_left_to_right:
|
|
||||||
output_connector_->setPos(rect().right(), 0);
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_right_to_left:
|
|
||||||
output_connector_->setPos(rect().left(), 0);
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_top_to_bottom:
|
|
||||||
output_connector_->setPos(rect().center().x(), rect().bottom());
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_bottom_to_top:
|
|
||||||
output_connector_->setPos(rect().center().x(), rect().top());
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_invalid_direction:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NodeViewItem::is_input_valid(const QString &input)
|
|
||||||
{
|
|
||||||
if (!node_->is_input_connectable(input) || node_->is_input_hidden(input)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// For OFX plugin nodes, only show texture inputs in the node graph
|
|
||||||
// to avoid excessively tall nodes with dozens of scalar parameters.
|
|
||||||
// Scalar parameters are still visible in the parameter panel.
|
|
||||||
if (node_->getPluginInstance() != nullptr &&
|
|
||||||
node_->get_input_data_type(input) != NodeValue::k_texture) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::set_rect_size(int height_units)
|
|
||||||
{
|
|
||||||
// Set rect
|
|
||||||
int widget_width = default_item_width();
|
|
||||||
int widget_height = default_item_height();
|
|
||||||
|
|
||||||
setRect(QRectF(-widget_width / 2, -widget_height / 2, widget_width,
|
|
||||||
widget_height * height_units));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NodeViewItem::can_be_expanded() const
|
|
||||||
{
|
|
||||||
if (is_output_item()) {
|
|
||||||
return has_connectable_inputs_;
|
|
||||||
} else {
|
|
||||||
return node_->get_input_flags(input_) & k_input_flag_array &&
|
|
||||||
element_ == -1 && !node_->is_input_connected(input_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::update_children_positions()
|
|
||||||
{
|
|
||||||
int y = 1;
|
|
||||||
int h = default_item_height();
|
|
||||||
|
|
||||||
foreach (NodeViewItem *c, children_) {
|
|
||||||
c->setPos(QPointF(0, y * h));
|
|
||||||
|
|
||||||
y += c->get_logical_height_with_children();
|
|
||||||
}
|
|
||||||
|
|
||||||
set_rect_size(y);
|
|
||||||
|
|
||||||
if (NodeViewItem *p = dynamic_cast<NodeViewItem *>(parentItem())) {
|
|
||||||
p->update_children_positions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int NodeViewItem::get_logical_height_with_children() const
|
|
||||||
{
|
|
||||||
int h = 1;
|
|
||||||
|
|
||||||
foreach (NodeViewItem *c, children_) {
|
|
||||||
h += c->get_logical_height_with_children();
|
|
||||||
}
|
|
||||||
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::update_flow_direction_of_input_item(NodeViewItem *child)
|
|
||||||
{
|
|
||||||
if (!child->is_output_item()) {
|
|
||||||
if (NodeViewCommon::is_flow_vertical(flow_dir_)) {
|
|
||||||
if (!child->edges().isEmpty() &&
|
|
||||||
child->edges().first()->from_item()->scenePos().x() >
|
|
||||||
child->scenePos().x()) {
|
|
||||||
child->set_flow_direction(NodeViewCommon::k_right_to_left);
|
|
||||||
} else {
|
|
||||||
child->set_flow_direction(NodeViewCommon::k_left_to_right);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
child->set_flow_direction(flow_dir_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::repopulate_inputs()
|
|
||||||
{
|
|
||||||
if (is_output_item()) {
|
|
||||||
has_connectable_inputs_ = false;
|
|
||||||
|
|
||||||
foreach (const QString &input, node_->inputs()) {
|
|
||||||
if (is_input_valid(input)) {
|
|
||||||
has_connectable_inputs_ = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input_connector_->setVisible(has_connectable_inputs_);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_expanded() && (is_output_item() || element_ == -1)) {
|
|
||||||
// Create or remove inputs when necessary
|
|
||||||
// NOTE: This is not the most efficient thing in the world, but it does work
|
|
||||||
set_expanded(false);
|
|
||||||
set_expanded(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::input_array_size_changed(const QString &input)
|
|
||||||
{
|
|
||||||
if (input == input_) {
|
|
||||||
repopulate_inputs();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::node_appearance_changed()
|
|
||||||
{
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItem::set_highlighted(bool e)
|
|
||||||
{
|
|
||||||
highlighted_ = e;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
NodeViewItem *NodeViewItem::get_item_for_input(NodeInput input)
|
|
||||||
{
|
|
||||||
if (NodeGroup *group = dynamic_cast<NodeGroup *>(node_)) {
|
|
||||||
if (input.node() != group) {
|
|
||||||
// Translate input to group input
|
|
||||||
QString id = group->get_id_of_passthrough(input);
|
|
||||||
input.set_node(group);
|
|
||||||
input.set_input(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_expanded()) {
|
|
||||||
if (input_.isEmpty()) {
|
|
||||||
// Look for the input in our children
|
|
||||||
foreach (NodeViewItem *i, children_) {
|
|
||||||
if (i->input_ == input.input()) {
|
|
||||||
return i->get_item_for_input(input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Look for element in our children
|
|
||||||
if (input.element() >= 0 && input.element() < children_.size()) {
|
|
||||||
return children_.at(input.element())->get_item_for_input(input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to this object
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,246 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 OAK_NODEVIEWITEM_H
|
|
||||||
#define OAK_NODEVIEWITEM_H
|
|
||||||
|
|
||||||
#include <QFontMetrics>
|
|
||||||
#include <QGraphicsRectItem>
|
|
||||||
#include <QLinearGradient>
|
|
||||||
#include <QWidget>
|
|
||||||
|
|
||||||
#include "node/node.h"
|
|
||||||
#include "nodeviewcommon.h"
|
|
||||||
#include "nodeviewitemconnector.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
class NodeViewItem;
|
|
||||||
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 QObject, public QGraphicsRectItem {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
NodeViewItem(Node *node, const QString &input, int element, Node *context,
|
|
||||||
QGraphicsItem *parent = nullptr);
|
|
||||||
NodeViewItem(Node *node, Node *context, QGraphicsItem *parent = nullptr)
|
|
||||||
: NodeViewItem(node, QString(), -1, context, parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~NodeViewItem() override;
|
|
||||||
|
|
||||||
Node::Position get_node_position_data() const;
|
|
||||||
QPointF get_node_position() const;
|
|
||||||
void set_node_position(const QPointF &pos);
|
|
||||||
void set_node_position(const Node::Position &pos);
|
|
||||||
|
|
||||||
QVector<NodeViewEdge *> get_all_edges_recursively() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get currently attached node
|
|
||||||
*/
|
|
||||||
Node *get_node() const
|
|
||||||
{
|
|
||||||
return node_;
|
|
||||||
}
|
|
||||||
|
|
||||||
NodeInput get_input() const
|
|
||||||
{
|
|
||||||
return NodeInput(node_, input_, element_);
|
|
||||||
}
|
|
||||||
|
|
||||||
Node *get_context() const
|
|
||||||
{
|
|
||||||
return context_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get expanded state
|
|
||||||
*/
|
|
||||||
bool is_expanded() const
|
|
||||||
{
|
|
||||||
return expanded_;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QVector<NodeViewEdge *> &edges() const
|
|
||||||
{
|
|
||||||
return edges_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set expanded state
|
|
||||||
*/
|
|
||||||
void set_expanded(bool e, bool hide_titlebar = false);
|
|
||||||
void toggle_expanded();
|
|
||||||
|
|
||||||
QPointF get_input_point() const;
|
|
||||||
QPointF get_output_point() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Sets the direction nodes are flowing
|
|
||||||
*/
|
|
||||||
void set_flow_direction(NodeViewCommon::FlowDirection dir);
|
|
||||||
|
|
||||||
NodeViewCommon::FlowDirection get_flow_direction() const
|
|
||||||
{
|
|
||||||
return flow_dir_;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int default_text_padding();
|
|
||||||
|
|
||||||
static int default_item_height();
|
|
||||||
|
|
||||||
static int default_item_width();
|
|
||||||
|
|
||||||
static int default_item_border();
|
|
||||||
|
|
||||||
static QPointF node_to_screen_point(QPointF p,
|
|
||||||
NodeViewCommon::FlowDirection direction);
|
|
||||||
static QPointF screen_to_node_point(QPointF p,
|
|
||||||
NodeViewCommon::FlowDirection direction);
|
|
||||||
|
|
||||||
static qreal
|
|
||||||
default_item_horizontal_padding(NodeViewCommon::FlowDirection dir);
|
|
||||||
static qreal default_item_vertical_padding(NodeViewCommon::FlowDirection dir);
|
|
||||||
qreal default_item_horizontal_padding() const;
|
|
||||||
qreal default_item_vertical_padding() const;
|
|
||||||
|
|
||||||
void add_edge(NodeViewEdge *edge);
|
|
||||||
void remove_edge(NodeViewEdge *edge);
|
|
||||||
|
|
||||||
bool is_labelled_as_output_of_context() const
|
|
||||||
{
|
|
||||||
return label_as_output_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_label_as_output(bool e);
|
|
||||||
|
|
||||||
void set_highlighted(bool e);
|
|
||||||
|
|
||||||
NodeViewItem *get_item_for_input(NodeInput input);
|
|
||||||
|
|
||||||
bool is_output_item() const
|
|
||||||
{
|
|
||||||
return input_.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
void readjust_all_edges();
|
|
||||||
|
|
||||||
void update_flow_direction_of_input_item(NodeViewItem *child);
|
|
||||||
|
|
||||||
bool can_be_expanded() const;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void paint(QPainter *painter,
|
|
||||||
const QStyleOptionGraphicsItem *option,
|
|
||||||
QWidget *widget = nullptr) override;
|
|
||||||
|
|
||||||
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
|
|
||||||
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
|
|
||||||
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
|
|
||||||
|
|
||||||
virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change,
|
|
||||||
const QVariant &value) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void update_context_rect();
|
|
||||||
|
|
||||||
void draw_node_title(QPainter *painter, QString text, const QRectF &rect,
|
|
||||||
Qt::Alignment vertical_align, int icon_full_size);
|
|
||||||
|
|
||||||
int draw_expand_arrow(QPainter *painter);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Internal update function when logical position changes
|
|
||||||
*/
|
|
||||||
void update_node_position();
|
|
||||||
|
|
||||||
void update_input_connector_position();
|
|
||||||
void update_output_connector_position();
|
|
||||||
|
|
||||||
bool is_input_valid(const QString &input);
|
|
||||||
|
|
||||||
void set_rect_size(int height_units = 1);
|
|
||||||
|
|
||||||
void update_children_positions();
|
|
||||||
|
|
||||||
int get_logical_height_with_children() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Reference to attached Node
|
|
||||||
*/
|
|
||||||
Node *node_;
|
|
||||||
QString input_;
|
|
||||||
int element_;
|
|
||||||
|
|
||||||
Node *context_;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Cached list of node inputs
|
|
||||||
*/
|
|
||||||
QVector<NodeViewItem *> children_;
|
|
||||||
|
|
||||||
/// Sizing variables to use when drawing
|
|
||||||
int node_border_width_;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Expanded state
|
|
||||||
*/
|
|
||||||
bool expanded_;
|
|
||||||
|
|
||||||
bool highlighted_;
|
|
||||||
|
|
||||||
NodeViewCommon::FlowDirection flow_dir_;
|
|
||||||
|
|
||||||
QVector<NodeViewEdge *> edges_;
|
|
||||||
|
|
||||||
QPointF cached_node_pos_;
|
|
||||||
|
|
||||||
QRect last_arrow_rect_;
|
|
||||||
bool arrow_click_;
|
|
||||||
|
|
||||||
NodeViewItemConnector *input_connector_;
|
|
||||||
NodeViewItemConnector *output_connector_;
|
|
||||||
|
|
||||||
bool has_connectable_inputs_;
|
|
||||||
|
|
||||||
bool label_as_output_;
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void node_appearance_changed();
|
|
||||||
|
|
||||||
void repopulate_inputs();
|
|
||||||
|
|
||||||
void input_array_size_changed(const QString &input);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // OAK_NODEVIEWITEM_H
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 "nodeviewitemconnector.h"
|
|
||||||
|
|
||||||
#include <QApplication>
|
|
||||||
#include <QFontMetrics>
|
|
||||||
#include <QPalette>
|
|
||||||
#include <QPen>
|
|
||||||
|
|
||||||
#include "nodeviewitem.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
NodeViewItemConnector::NodeViewItemConnector(bool is_output,
|
|
||||||
QGraphicsItem *parent)
|
|
||||||
: QGraphicsPolygonItem(parent)
|
|
||||||
, output_(is_output)
|
|
||||||
{
|
|
||||||
QColor c = qApp->palette().text().color();
|
|
||||||
setPen(QPen(c, NodeViewItem::default_item_border()));
|
|
||||||
setBrush(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewItemConnector::set_flow_direction(NodeViewCommon::FlowDirection dir)
|
|
||||||
{
|
|
||||||
QFont f;
|
|
||||||
QFontMetricsF fm(f);
|
|
||||||
|
|
||||||
int triangle_sz = fm.height() / 2;
|
|
||||||
int triangle_sz_half = triangle_sz / 2;
|
|
||||||
|
|
||||||
QPolygonF p;
|
|
||||||
p.resize(3);
|
|
||||||
|
|
||||||
switch (dir) {
|
|
||||||
case NodeViewCommon::k_left_to_right:
|
|
||||||
// Triangle pointing right
|
|
||||||
p[0] = QPointF(0, -triangle_sz_half);
|
|
||||||
p[1] = QPointF(triangle_sz_half, 0);
|
|
||||||
p[2] = QPointF(0, triangle_sz_half);
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_top_to_bottom:
|
|
||||||
// Triangle pointing down
|
|
||||||
p[0] = QPointF(-triangle_sz_half, 0);
|
|
||||||
p[1] = QPointF(0, triangle_sz_half);
|
|
||||||
p[2] = QPointF(triangle_sz_half, 0);
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_bottom_to_top:
|
|
||||||
// Triangle pointing up
|
|
||||||
p[0] = QPointF(-triangle_sz_half, 0);
|
|
||||||
p[1] = QPointF(0, -triangle_sz_half);
|
|
||||||
p[2] = QPointF(triangle_sz_half, 0);
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_right_to_left:
|
|
||||||
// Triangle pointing left
|
|
||||||
p[0] = QPointF(0, -triangle_sz_half);
|
|
||||||
p[1] = QPointF(-triangle_sz_half, 0);
|
|
||||||
p[2] = QPointF(0, triangle_sz_half);
|
|
||||||
break;
|
|
||||||
case NodeViewCommon::k_invalid_direction:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
setPolygon(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
QPainterPath NodeViewItemConnector::shape() const
|
|
||||||
{
|
|
||||||
// Yes, we skip QGraphicsPolygonItem because it adds the polygon. QGraphicsItem adds the
|
|
||||||
// boundingRect which we modify below
|
|
||||||
return QGraphicsItem::shape(); // clazy:exclude=skipped-base-method
|
|
||||||
}
|
|
||||||
|
|
||||||
QRectF NodeViewItemConnector::boundingRect() const
|
|
||||||
{
|
|
||||||
QRectF b = this->polygon().boundingRect();
|
|
||||||
const int radius = QFontMetrics(QFont()).height() / 2;
|
|
||||||
b.adjust(-radius, -radius, radius, radius);
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 OAK_NODEVIEWITEMCONNECTOR_H
|
|
||||||
#define OAK_NODEVIEWITEMCONNECTOR_H
|
|
||||||
|
|
||||||
#include <QGraphicsPolygonItem>
|
|
||||||
|
|
||||||
#include "nodeviewcommon.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
class NodeViewItemConnector : public QGraphicsPolygonItem {
|
|
||||||
public:
|
|
||||||
NodeViewItemConnector(bool is_output, QGraphicsItem *parent = nullptr);
|
|
||||||
|
|
||||||
void set_flow_direction(NodeViewCommon::FlowDirection dir);
|
|
||||||
|
|
||||||
bool is_output() const
|
|
||||||
{
|
|
||||||
return output_;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual QPainterPath shape() const override;
|
|
||||||
virtual QRectF boundingRect() const override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool output_;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // OAK_NODEVIEWITEMCONNECTOR_H
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 "nodeviewminimap.h"
|
|
||||||
|
|
||||||
#include <QMouseEvent>
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
#define super QGraphicsView
|
|
||||||
|
|
||||||
NodeViewMiniMap::NodeViewMiniMap(NodeViewScene *scene, QWidget *parent)
|
|
||||||
: super(parent)
|
|
||||||
, resizing_(false)
|
|
||||||
{
|
|
||||||
connect(scene, &QGraphicsScene::sceneRectChanged, this,
|
|
||||||
&NodeViewMiniMap::scene_changed);
|
|
||||||
setScene(scene);
|
|
||||||
|
|
||||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
||||||
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
||||||
setViewportUpdateMode(FullViewportUpdate);
|
|
||||||
setFrameShape(QFrame::Panel);
|
|
||||||
setFrameShadow(QFrame::Plain);
|
|
||||||
setMouseTracking(true);
|
|
||||||
|
|
||||||
QMetaObject::invokeMethod(this, &NodeViewMiniMap::set_default_size,
|
|
||||||
Qt::QueuedConnection);
|
|
||||||
|
|
||||||
resize_triangle_sz_ = fontMetrics().height() / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewMiniMap::set_viewport_rect(const QPolygonF &rect)
|
|
||||||
{
|
|
||||||
viewport_rect_ = rect;
|
|
||||||
|
|
||||||
viewport()->update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewMiniMap::drawForeground(QPainter *painter, const QRectF &rect)
|
|
||||||
{
|
|
||||||
super::drawForeground(painter, rect);
|
|
||||||
|
|
||||||
QColor viewport_color = palette().text().color();
|
|
||||||
|
|
||||||
// Draw resize triangle
|
|
||||||
painter->save();
|
|
||||||
painter->resetTransform();
|
|
||||||
|
|
||||||
QPointF triangle[3] = { QPointF(0, 0), QPointF(resize_triangle_sz_, 0),
|
|
||||||
QPointF(0, resize_triangle_sz_) };
|
|
||||||
painter->setBrush(viewport_color);
|
|
||||||
painter->setPen(viewport_color);
|
|
||||||
painter->drawPolygon(triangle, 3);
|
|
||||||
|
|
||||||
painter->restore();
|
|
||||||
|
|
||||||
// Draw viewport rectangle
|
|
||||||
viewport_color.setAlphaF(0.25);
|
|
||||||
painter->setBrush(viewport_color);
|
|
||||||
|
|
||||||
painter->drawPolygon(viewport_rect_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewMiniMap::resizeEvent(QResizeEvent *event)
|
|
||||||
{
|
|
||||||
super::resizeEvent(event);
|
|
||||||
|
|
||||||
emit resized();
|
|
||||||
|
|
||||||
scene_changed(sceneRect());
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewMiniMap::mousePressEvent(QMouseEvent *event)
|
|
||||||
{
|
|
||||||
if (event->button() == Qt::LeftButton) {
|
|
||||||
if (mouse_inside_resize_triangle(event)) {
|
|
||||||
// Resizing!
|
|
||||||
resizing_ = true;
|
|
||||||
resize_anchor_ = QCursor::pos();
|
|
||||||
} else {
|
|
||||||
emit_move_signal(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewMiniMap::mouseMoveEvent(QMouseEvent *event)
|
|
||||||
{
|
|
||||||
if (event->buttons() & Qt::LeftButton) {
|
|
||||||
if (resizing_) {
|
|
||||||
QPointF movement = QCursor::pos() - resize_anchor_;
|
|
||||||
resize(QSize(width() - movement.x(), height() - movement.y()));
|
|
||||||
resize_anchor_ = QCursor::pos();
|
|
||||||
} else {
|
|
||||||
emit_move_signal(event);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (mouse_inside_resize_triangle(event)) {
|
|
||||||
setCursor(Qt::SizeFDiagCursor);
|
|
||||||
} else {
|
|
||||||
unsetCursor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewMiniMap::mouseReleaseEvent(QMouseEvent *event)
|
|
||||||
{
|
|
||||||
resizing_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewMiniMap::scene_changed(const QRectF &bounding)
|
|
||||||
{
|
|
||||||
double x_scale = double(this->width()) / bounding.width();
|
|
||||||
double y_scale = double(this->height()) / bounding.height();
|
|
||||||
|
|
||||||
double min_scale = qMin(x_scale, y_scale);
|
|
||||||
|
|
||||||
QTransform transform;
|
|
||||||
transform.scale(min_scale, min_scale);
|
|
||||||
|
|
||||||
setTransform(transform);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewMiniMap::set_default_size()
|
|
||||||
{
|
|
||||||
if (parentWidget()) {
|
|
||||||
resize(parentWidget()->width() / 4, parentWidget()->height() / 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NodeViewMiniMap::mouse_inside_resize_triangle(QMouseEvent *event)
|
|
||||||
{
|
|
||||||
return event->pos().x() <= resize_triangle_sz_ &&
|
|
||||||
event->pos().y() <= resize_triangle_sz_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewMiniMap::emit_move_signal(QMouseEvent *event)
|
|
||||||
{
|
|
||||||
emit move_to_scene_point(mapToScene(event->pos()));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 OAK_NODEVIEWMINIMAP_H
|
|
||||||
#define OAK_NODEVIEWMINIMAP_H
|
|
||||||
|
|
||||||
#include <QGraphicsView>
|
|
||||||
|
|
||||||
#include "nodeviewscene.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
class NodeViewMiniMap : public QGraphicsView {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
NodeViewMiniMap(NodeViewScene *scene, QWidget *parent = nullptr);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void set_viewport_rect(const QPolygonF &rect);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void resized();
|
|
||||||
|
|
||||||
void move_to_scene_point(const QPointF &pos);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void drawForeground(QPainter *painter, const QRectF &rect) override;
|
|
||||||
|
|
||||||
virtual void resizeEvent(QResizeEvent *event) override;
|
|
||||||
|
|
||||||
virtual void mousePressEvent(QMouseEvent *event) override;
|
|
||||||
virtual void mouseMoveEvent(QMouseEvent *event) override;
|
|
||||||
virtual void mouseReleaseEvent(QMouseEvent *event) override;
|
|
||||||
virtual void mouseDoubleClickEvent(QMouseEvent *event) override
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void scene_changed(const QRectF &bounding);
|
|
||||||
|
|
||||||
void set_default_size();
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool mouse_inside_resize_triangle(QMouseEvent *event);
|
|
||||||
|
|
||||||
void emit_move_signal(QMouseEvent *event);
|
|
||||||
|
|
||||||
int resize_triangle_sz_;
|
|
||||||
|
|
||||||
QPolygonF viewport_rect_;
|
|
||||||
|
|
||||||
bool resizing_;
|
|
||||||
|
|
||||||
QPoint resize_anchor_;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // OAK_NODEVIEWMINIMAP_H
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 "core.h"
|
|
||||||
#include "node/project/sequence/sequence.h"
|
|
||||||
#include "nodeviewedge.h"
|
|
||||||
#include "nodeviewitem.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
NodeViewScene::NodeViewScene(QObject *parent)
|
|
||||||
: QGraphicsScene(parent)
|
|
||||||
, direction_(NodeViewCommon::k_left_to_right)
|
|
||||||
, curved_edges_(true)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewScene::set_flow_direction(NodeViewCommon::FlowDirection direction)
|
|
||||||
{
|
|
||||||
direction_ = direction;
|
|
||||||
|
|
||||||
foreach (NodeViewContext *ctx, context_map_) {
|
|
||||||
ctx->set_flow_direction(direction_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewScene::select_all()
|
|
||||||
{
|
|
||||||
foreach (QGraphicsItem *i, items()) {
|
|
||||||
i->setSelected(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewScene::deselect_all()
|
|
||||||
{
|
|
||||||
foreach (QGraphicsItem *i, items()) {
|
|
||||||
i->setSelected(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<NodeViewItem *> NodeViewScene::get_selected_items() const
|
|
||||||
{
|
|
||||||
QVector<NodeViewItem *> items;
|
|
||||||
|
|
||||||
foreach (NodeViewContext *ctx, context_map_) {
|
|
||||||
items.append(ctx->get_selected_items());
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
NodeViewContext *NodeViewScene::add_context(Node *node)
|
|
||||||
{
|
|
||||||
NodeViewContext *context_item = context_map_.value(node);
|
|
||||||
|
|
||||||
if (!context_item) {
|
|
||||||
context_item = new NodeViewContext(node);
|
|
||||||
|
|
||||||
context_item->set_flow_direction(get_flow_direction());
|
|
||||||
context_item->set_curved_edges(get_edges_are_curved());
|
|
||||||
|
|
||||||
QPointF pos(0, 0);
|
|
||||||
QRectF item_rect = context_item->rect();
|
|
||||||
while (!items(item_rect).isEmpty()) {
|
|
||||||
pos.setY(pos.y() + item_rect.height());
|
|
||||||
item_rect = context_item->rect().translated(pos);
|
|
||||||
}
|
|
||||||
context_item->setPos(pos);
|
|
||||||
|
|
||||||
addItem(context_item);
|
|
||||||
|
|
||||||
context_map_.insert(node, context_item);
|
|
||||||
}
|
|
||||||
|
|
||||||
return context_item;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewScene::remove_context(Node *node)
|
|
||||||
{
|
|
||||||
delete context_map_.take(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
Qt::Orientation NodeViewScene::get_flow_orientation() const
|
|
||||||
{
|
|
||||||
return NodeViewCommon::get_flow_orientation(direction_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewScene::set_edges_are_curved(bool curved)
|
|
||||||
{
|
|
||||||
if (curved_edges_ != curved) {
|
|
||||||
curved_edges_ = curved;
|
|
||||||
|
|
||||||
foreach (NodeViewContext *ctx, context_map_) {
|
|
||||||
ctx->set_curved_edges(curved_edges_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 OAK_NODEVIEWSCENE_H
|
|
||||||
#define OAK_NODEVIEWSCENE_H
|
|
||||||
|
|
||||||
#include <QGraphicsScene>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#include "node/project.h"
|
|
||||||
#include "nodeviewcontext.h"
|
|
||||||
#include "nodeviewedge.h"
|
|
||||||
#include "nodeviewitem.h"
|
|
||||||
#include "undo/undostack.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
class NodeViewScene : public QGraphicsScene {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
NodeViewScene(QObject *parent = nullptr);
|
|
||||||
|
|
||||||
void select_all();
|
|
||||||
void deselect_all();
|
|
||||||
|
|
||||||
QVector<NodeViewItem *> get_selected_items() const;
|
|
||||||
|
|
||||||
const QHash<Node *, NodeViewContext *> &context_map() const
|
|
||||||
{
|
|
||||||
return context_map_;
|
|
||||||
}
|
|
||||||
|
|
||||||
Qt::Orientation get_flow_orientation() const;
|
|
||||||
|
|
||||||
NodeViewCommon::FlowDirection get_flow_direction() const
|
|
||||||
{
|
|
||||||
return direction_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_flow_direction(NodeViewCommon::FlowDirection direction);
|
|
||||||
|
|
||||||
bool get_edges_are_curved() const
|
|
||||||
{
|
|
||||||
return curved_edges_;
|
|
||||||
}
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
NodeViewContext *add_context(Node *node);
|
|
||||||
void remove_context(Node *node);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set whether edges in this scene should be curved or not
|
|
||||||
*/
|
|
||||||
void set_edges_are_curved(bool curved);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QHash<Node *, NodeViewContext *> context_map_;
|
|
||||||
|
|
||||||
Project *graph_;
|
|
||||||
|
|
||||||
NodeViewCommon::FlowDirection direction_;
|
|
||||||
|
|
||||||
bool curved_edges_;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // OAK_NODEVIEWSCENE_H
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
/*
|
|
||||||
* Oak Video Editor - Non-Linear Video Editor
|
|
||||||
* Copyright (C) 2025 Olive CE 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 "nodeviewtoolbar.h"
|
|
||||||
|
|
||||||
#include <QEvent>
|
|
||||||
#include <QHBoxLayout>
|
|
||||||
|
|
||||||
#include "ui/icons/icons.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
#define super QWidget
|
|
||||||
|
|
||||||
NodeViewToolBar::NodeViewToolBar(QWidget *parent)
|
|
||||||
: QWidget(parent)
|
|
||||||
{
|
|
||||||
QHBoxLayout *layout = new QHBoxLayout(this);
|
|
||||||
layout->setContentsMargins(0, 0, 0, 0);
|
|
||||||
|
|
||||||
add_node_btn_ = new QPushButton();
|
|
||||||
connect(add_node_btn_, &QPushButton::clicked, this,
|
|
||||||
&NodeViewToolBar::add_node_clicked);
|
|
||||||
layout->addWidget(add_node_btn_);
|
|
||||||
|
|
||||||
minimap_btn_ = new QPushButton();
|
|
||||||
minimap_btn_->setCheckable(true);
|
|
||||||
connect(minimap_btn_, &QPushButton::clicked, this,
|
|
||||||
&NodeViewToolBar::mini_map_enabled_toggled);
|
|
||||||
layout->addWidget(minimap_btn_);
|
|
||||||
|
|
||||||
layout->addStretch();
|
|
||||||
|
|
||||||
retranslate();
|
|
||||||
update_icons();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewToolBar::changeEvent(QEvent *e)
|
|
||||||
{
|
|
||||||
if (e->type() == QEvent::LanguageChange) {
|
|
||||||
retranslate();
|
|
||||||
} else if (e->type() == QEvent::StyleChange) {
|
|
||||||
update_icons();
|
|
||||||
}
|
|
||||||
super::changeEvent(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewToolBar::retranslate()
|
|
||||||
{
|
|
||||||
add_node_btn_->setToolTip(tr("Add Node"));
|
|
||||||
minimap_btn_->setToolTip(tr("Toggle Mini-Map"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeViewToolBar::update_icons()
|
|
||||||
{
|
|
||||||
add_node_btn_->setIcon(icon::add);
|
|
||||||
minimap_btn_->setIcon(icon::mini_map);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* Oak Video Editor - Non-Linear Video Editor
|
|
||||||
* Copyright (C) 2025 Olive CE 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 OAK_NODEVIEWTOOLBAR_H
|
|
||||||
#define OAK_NODEVIEWTOOLBAR_H
|
|
||||||
|
|
||||||
#include <QPushButton>
|
|
||||||
#include <QWidget>
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
class NodeViewToolBar : public QWidget {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
NodeViewToolBar(QWidget *parent = nullptr);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void set_mini_map_enabled(bool e)
|
|
||||||
{
|
|
||||||
minimap_btn_->setChecked(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void add_node_clicked();
|
|
||||||
|
|
||||||
void mini_map_enabled_toggled(bool e);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void changeEvent(QEvent *e) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void retranslate();
|
|
||||||
|
|
||||||
void update_icons();
|
|
||||||
|
|
||||||
QPushButton *add_node_btn_;
|
|
||||||
|
|
||||||
QPushButton *minimap_btn_;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // OAK_NODEVIEWTOOLBAR_H
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 "nodewidget.h"
|
|
||||||
|
|
||||||
#include <QVBoxLayout>
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
NodeWidget::NodeWidget(QWidget *parent)
|
|
||||||
: QWidget(parent)
|
|
||||||
{
|
|
||||||
QVBoxLayout *outer_layout = new QVBoxLayout(this);
|
|
||||||
outer_layout->setContentsMargins(0, 0, 0, 0);
|
|
||||||
|
|
||||||
toolbar_ = new NodeViewToolBar();
|
|
||||||
outer_layout->addWidget(toolbar_);
|
|
||||||
|
|
||||||
// Create NodeView widget
|
|
||||||
node_view_ = new NodeView(this);
|
|
||||||
outer_layout->addWidget(node_view_);
|
|
||||||
|
|
||||||
// Connect toolbar to NodeView
|
|
||||||
connect(toolbar_, &NodeViewToolBar::mini_map_enabled_toggled, node_view_,
|
|
||||||
&NodeView::set_mini_map_enabled);
|
|
||||||
connect(toolbar_, &NodeViewToolBar::add_node_clicked, node_view_,
|
|
||||||
&NodeView::show_add_menu);
|
|
||||||
|
|
||||||
// Set defaults
|
|
||||||
toolbar_->set_mini_map_enabled(true);
|
|
||||||
node_view_->set_mini_map_enabled(true);
|
|
||||||
|
|
||||||
setSizePolicy(node_view_->sizePolicy());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 OAK_NODEWIDGET_H
|
|
||||||
#define OAK_NODEWIDGET_H
|
|
||||||
|
|
||||||
#include <QWidget>
|
|
||||||
|
|
||||||
#include "nodeview.h"
|
|
||||||
#include "nodeviewtoolbar.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
class NodeWidget : public QWidget {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
NodeWidget(QWidget *parent = nullptr);
|
|
||||||
|
|
||||||
NodeView *view() const
|
|
||||||
{
|
|
||||||
return node_view_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_contexts(const QVector<Node *> &nodes)
|
|
||||||
{
|
|
||||||
node_view_->set_contexts(nodes);
|
|
||||||
toolbar_->setEnabled(!nodes.isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
NodeView *node_view_;
|
|
||||||
|
|
||||||
NodeViewToolBar *toolbar_;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // OAK_NODEWIDGET_H
|
|
||||||
@@ -1,947 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 "projectexplorer.h"
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QDesktopServices>
|
|
||||||
#include <QDir>
|
|
||||||
#include <QFileDialog>
|
|
||||||
#include <QMessageBox>
|
|
||||||
#include <QProcess>
|
|
||||||
#include <QUrl>
|
|
||||||
#include <QVBoxLayout>
|
|
||||||
|
|
||||||
#include "common/define.h"
|
|
||||||
#include "core.h"
|
|
||||||
#include "dialog/footageproperties/footageproperties.h"
|
|
||||||
#include "dialog/proxy/proxydialog.h"
|
|
||||||
#include "dialog/sequence/sequence.h"
|
|
||||||
#include "projectexplorerundo.h"
|
|
||||||
#include "oakengine/footage.h"
|
|
||||||
#include "oakengine/node.h"
|
|
||||||
#include "oakengine/task.h"
|
|
||||||
#include "widget/menu/menu.h"
|
|
||||||
#include "widget/menu/menushared.h"
|
|
||||||
#include "node/nodeundo.h"
|
|
||||||
#include "window/mainwindow/mainwindow.h"
|
|
||||||
#include "window/mainwindow/mainwindowundo.h"
|
|
||||||
#include "widget/timelinewidget/timelinewidget.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
QVector<Footage *> get_selected_proxy_footage(const QVector<Node *> &items)
|
|
||||||
{
|
|
||||||
QVector<Footage *> footage;
|
|
||||||
for (Node *node : items) {
|
|
||||||
Footage *candidate = dynamic_cast<Footage *>(node);
|
|
||||||
if (!candidate || !candidate->get_first_enabled_video_stream().is_valid() ||
|
|
||||||
footage.contains(candidate)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
footage.append(candidate);
|
|
||||||
}
|
|
||||||
return footage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Proxy generation driven by the liboakengine C ABI facade
|
|
||||||
*
|
|
||||||
* Replaces the direct ProxyManager::get_or_start_proxy() drive: the actual
|
|
||||||
* transcode and its synchronous wait live behind
|
|
||||||
* oakengine_footage_proxy_generate() (which also records the proxy state
|
|
||||||
* on the footage and invalidates it), while the task stays on the
|
|
||||||
* TaskManager queue like before.
|
|
||||||
*/
|
|
||||||
class FacadeProxyTask : public Task {
|
|
||||||
public:
|
|
||||||
FacadeProxyTask(Footage *footage)
|
|
||||||
: footage_(footage)
|
|
||||||
{
|
|
||||||
set_title(tr("Generating proxy for \"%1\"")
|
|
||||||
.arg(footage->get_label_or_name()));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual bool run() override
|
|
||||||
{
|
|
||||||
OakEngineFootage *handle = oakengine_footage_borrow(
|
|
||||||
reinterpret_cast<OakEngineNode *>(footage_));
|
|
||||||
const int rc = oakengine_footage_proxy_generate(handle);
|
|
||||||
oakengine_footage_free(handle);
|
|
||||||
if (rc != OAKENGINE_OK) {
|
|
||||||
char err[512];
|
|
||||||
err[0] = '\0';
|
|
||||||
oakengine_footage_last_error(err, sizeof(err));
|
|
||||||
set_error(err[0] ? QString::fromUtf8(err) :
|
|
||||||
tr("Proxy generation failed"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Footage *footage_;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ProjectExplorer::ProjectExplorer(QWidget *parent)
|
|
||||||
: QWidget(parent)
|
|
||||||
, model_(this)
|
|
||||||
{
|
|
||||||
// Create layout
|
|
||||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
|
||||||
layout->setSpacing(0);
|
|
||||||
layout->setContentsMargins(0, 0, 0, 0);
|
|
||||||
|
|
||||||
// Set up navigation bar
|
|
||||||
nav_bar_ = new ProjectExplorerNavigation(this);
|
|
||||||
connect(nav_bar_, &ProjectExplorerNavigation::size_changed, this,
|
|
||||||
&ProjectExplorer::size_changed_slot);
|
|
||||||
connect(nav_bar_, &ProjectExplorerNavigation::directory_up_clicked, this,
|
|
||||||
&ProjectExplorer::dir_up_slot);
|
|
||||||
layout->addWidget(nav_bar_);
|
|
||||||
|
|
||||||
// Set up stacked widget
|
|
||||||
stacked_widget_ = new QStackedWidget(this);
|
|
||||||
layout->addWidget(stacked_widget_);
|
|
||||||
|
|
||||||
// Set up sort filter proxy model
|
|
||||||
sort_model_.setSourceModel(&model_);
|
|
||||||
sort_model_.setFilterCaseSensitivity(Qt::CaseInsensitive);
|
|
||||||
sort_model_.setSortRole(ProjectViewModel::k_inner_text_role);
|
|
||||||
|
|
||||||
// Add tree view to stacked widget
|
|
||||||
tree_view_ = new ProjectExplorerTreeView(stacked_widget_);
|
|
||||||
tree_view_->setSortingEnabled(true);
|
|
||||||
tree_view_->sortByColumn(0, Qt::AscendingOrder);
|
|
||||||
tree_view_->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
||||||
add_view(tree_view_);
|
|
||||||
|
|
||||||
// Add list view to stacked widget
|
|
||||||
list_view_ = new ProjectExplorerListView(stacked_widget_);
|
|
||||||
list_view_->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
||||||
add_view(list_view_);
|
|
||||||
|
|
||||||
// Add icon view to stacked widget
|
|
||||||
icon_view_ = new ProjectExplorerIconView(stacked_widget_);
|
|
||||||
icon_view_->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
||||||
add_view(icon_view_);
|
|
||||||
|
|
||||||
// Set default view to tree view
|
|
||||||
set_view_type(ProjectToolbar::tree_view);
|
|
||||||
|
|
||||||
// Set default icon size
|
|
||||||
size_changed_slot(k_project_icon_size_default);
|
|
||||||
|
|
||||||
connect(tree_view_, &ProjectExplorerTreeView::customContextMenuRequested,
|
|
||||||
this, &ProjectExplorer::show_context_menu);
|
|
||||||
connect(list_view_, &ProjectExplorerListView::customContextMenuRequested,
|
|
||||||
this, &ProjectExplorer::show_context_menu);
|
|
||||||
connect(icon_view_, &ProjectExplorerIconView::customContextMenuRequested,
|
|
||||||
this, &ProjectExplorer::show_context_menu);
|
|
||||||
|
|
||||||
update_nav_bar_text();
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProjectToolbar::ViewType &ProjectExplorer::view_type() const
|
|
||||||
{
|
|
||||||
return view_type_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::set_view_type(ProjectToolbar::ViewType type)
|
|
||||||
{
|
|
||||||
view_type_ = type;
|
|
||||||
|
|
||||||
// Set widget based on view type
|
|
||||||
switch (view_type_) {
|
|
||||||
case ProjectToolbar::tree_view:
|
|
||||||
stacked_widget_->setCurrentWidget(tree_view_);
|
|
||||||
nav_bar_->setVisible(false);
|
|
||||||
break;
|
|
||||||
case ProjectToolbar::list_view:
|
|
||||||
stacked_widget_->setCurrentWidget(list_view_);
|
|
||||||
nav_bar_->setVisible(true);
|
|
||||||
break;
|
|
||||||
case ProjectToolbar::icon_view:
|
|
||||||
stacked_widget_->setCurrentWidget(icon_view_);
|
|
||||||
nav_bar_->setVisible(true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::edit(Node *item)
|
|
||||||
{
|
|
||||||
current_view()->edit(
|
|
||||||
sort_model_.mapFromSource(model_.create_index_from_item(item)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::add_view(QAbstractItemView *view)
|
|
||||||
{
|
|
||||||
view->setModel(&sort_model_);
|
|
||||||
view->setEditTriggers(QAbstractItemView::SelectedClicked);
|
|
||||||
connect(view, &QAbstractItemView::doubleClicked, this,
|
|
||||||
&ProjectExplorer::item_double_clicked_slot);
|
|
||||||
connect(view->selectionModel(), &QItemSelectionModel::selectionChanged,
|
|
||||||
this, &ProjectExplorer::view_selection_changed);
|
|
||||||
connect(view, SIGNAL(double_clicked_empty_area()), this,
|
|
||||||
SLOT(view_empty_area_double_clicked_slot()));
|
|
||||||
stacked_widget_->addWidget(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::browse_to_folder(const QModelIndex &index)
|
|
||||||
{
|
|
||||||
// Set appropriate views to this index
|
|
||||||
icon_view_->setRootIndex(index);
|
|
||||||
list_view_->setRootIndex(index);
|
|
||||||
|
|
||||||
// Set navbar text to folder's name
|
|
||||||
update_nav_bar_text();
|
|
||||||
|
|
||||||
// Set directory up enabled button based on whether we're in root or not
|
|
||||||
nav_bar_->set_dir_up_enabled(index.isValid());
|
|
||||||
}
|
|
||||||
|
|
||||||
int ProjectExplorer::confirm_item_deletion(Node *item)
|
|
||||||
{
|
|
||||||
QMessageBox msgbox(this);
|
|
||||||
msgbox.setWindowTitle(tr("Confirm Item Deletion"));
|
|
||||||
msgbox.setIcon(QMessageBox::Warning);
|
|
||||||
|
|
||||||
QStringList connected_nodes_names;
|
|
||||||
foreach (const Node::OutputConnection &connected,
|
|
||||||
item->output_connections()) {
|
|
||||||
if (!dynamic_cast<Folder *>(connected.second.node())) {
|
|
||||||
connected_nodes_names.append(
|
|
||||||
get_human_readable_node_name(connected.second.node()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
msgbox.setText(
|
|
||||||
tr("The item \"%1\" is currently connected to the following nodes:\n\n"
|
|
||||||
"%2\n\n"
|
|
||||||
"Are you sure you wish to delete this footage?")
|
|
||||||
.arg(get_human_readable_node_name(item),
|
|
||||||
connected_nodes_names.join('\n')));
|
|
||||||
|
|
||||||
// Set up buttons
|
|
||||||
msgbox.addButton(QMessageBox::Yes);
|
|
||||||
msgbox.addButton(QMessageBox::YesToAll);
|
|
||||||
msgbox.addButton(QMessageBox::No);
|
|
||||||
msgbox.addButton(QMessageBox::Cancel);
|
|
||||||
|
|
||||||
// Run messagebox
|
|
||||||
return msgbox.exec();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ProjectExplorer::delete_items_internal(const QVector<Node *> &selected,
|
|
||||||
bool &check_if_item_is_in_use,
|
|
||||||
MultiUndoCommand *command)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < selected.size(); i++) {
|
|
||||||
// Delete sequences first
|
|
||||||
Node *node = selected.at(i);
|
|
||||||
|
|
||||||
bool can_delete_item = true;
|
|
||||||
|
|
||||||
if (check_if_item_is_in_use) {
|
|
||||||
foreach (const Node::OutputConnection &oc,
|
|
||||||
node->output_connections()) {
|
|
||||||
Folder *folder_test = dynamic_cast<Folder *>(oc.second.node());
|
|
||||||
if (!folder_test) {
|
|
||||||
// This sequence outputs to SOMETHING, confirm the user if they want to delete this
|
|
||||||
int r = confirm_item_deletion(node);
|
|
||||||
|
|
||||||
switch (r) {
|
|
||||||
case QMessageBox::No:
|
|
||||||
can_delete_item = false;
|
|
||||||
break;
|
|
||||||
case QMessageBox::Cancel:
|
|
||||||
return false;
|
|
||||||
case QMessageBox::YesToAll:
|
|
||||||
check_if_item_is_in_use = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (can_delete_item) {
|
|
||||||
Sequence *sequence = dynamic_cast<Sequence *>(node);
|
|
||||||
if (sequence &&
|
|
||||||
Core::instance()->main_window()->is_sequence_open(sequence)) {
|
|
||||||
command->add_child(new CloseSequenceCommand(sequence));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node->folder()) {
|
|
||||||
command->add_child(
|
|
||||||
new Folder::RemoveElementCommand(node->folder(), node));
|
|
||||||
}
|
|
||||||
|
|
||||||
command->add_child(
|
|
||||||
new NodeRemoveWithExclusiveDependenciesAndDisconnect(node));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ProjectExplorer::get_human_readable_node_name(Node *node)
|
|
||||||
{
|
|
||||||
if (node->get_label().isEmpty()) {
|
|
||||||
return node->name();
|
|
||||||
} else {
|
|
||||||
return tr("%1 (%2)").arg(node->get_label(), node->name());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::update_nav_bar_text()
|
|
||||||
{
|
|
||||||
QString absolute;
|
|
||||||
|
|
||||||
Folder *f = static_cast<Folder *>(
|
|
||||||
sort_model_.mapToSource(list_view_->rootIndex()).internalPointer());
|
|
||||||
while (f && f != project()->root()) {
|
|
||||||
absolute.prepend(QStringLiteral("%1 / ").arg(f->get_label()));
|
|
||||||
f = f->folder();
|
|
||||||
}
|
|
||||||
|
|
||||||
absolute.prepend(QStringLiteral("/ "));
|
|
||||||
|
|
||||||
nav_bar_->set_text(absolute);
|
|
||||||
}
|
|
||||||
|
|
||||||
QAbstractItemView *ProjectExplorer::current_view() const
|
|
||||||
{
|
|
||||||
return static_cast<QAbstractItemView *>(stacked_widget_->currentWidget());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::view_empty_area_double_clicked_slot()
|
|
||||||
{
|
|
||||||
emit double_clicked_item(nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::item_double_clicked_slot(const QModelIndex &index)
|
|
||||||
{
|
|
||||||
// Retrieve source item from index
|
|
||||||
Node *i =
|
|
||||||
static_cast<Node *>(sort_model_.mapToSource(index).internalPointer());
|
|
||||||
|
|
||||||
// If the item is a folder, browse to it
|
|
||||||
if (dynamic_cast<Folder *>(i) &&
|
|
||||||
(view_type() == ProjectToolbar::list_view ||
|
|
||||||
view_type() == ProjectToolbar::icon_view)) {
|
|
||||||
browse_to_folder(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emit a signal
|
|
||||||
emit double_clicked_item(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::size_changed_slot(int s)
|
|
||||||
{
|
|
||||||
icon_view_->setGridSize(QSize(s, s));
|
|
||||||
|
|
||||||
list_view_->setIconSize(QSize(s, s));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::dir_up_slot()
|
|
||||||
{
|
|
||||||
QModelIndex current_root = icon_view_->rootIndex();
|
|
||||||
|
|
||||||
if (current_root.isValid()) {
|
|
||||||
QModelIndex parent = current_root.parent();
|
|
||||||
|
|
||||||
browse_to_folder(parent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::rename_selected_item()
|
|
||||||
{
|
|
||||||
auto indexes = current_view()->selectionModel()->selectedRows();
|
|
||||||
if (!indexes.empty()) {
|
|
||||||
current_view()->edit(indexes.first());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::set_search_filter(const QString &s)
|
|
||||||
{
|
|
||||||
sort_model_.setFilterFixedString(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::show_context_menu()
|
|
||||||
{
|
|
||||||
Menu menu;
|
|
||||||
Menu new_menu;
|
|
||||||
|
|
||||||
context_menu_items_ = selected_items();
|
|
||||||
|
|
||||||
if (context_menu_items_.isEmpty()) {
|
|
||||||
// Items to show if no items are selected
|
|
||||||
|
|
||||||
// "New" menu
|
|
||||||
new_menu.setTitle(tr("&New"));
|
|
||||||
MenuShared::instance()->add_items_for_new_menu(&new_menu);
|
|
||||||
menu.addMenu(&new_menu);
|
|
||||||
|
|
||||||
// "Import" action
|
|
||||||
QAction *import_action = menu.addAction(tr("&Import..."));
|
|
||||||
connect(import_action, &QAction::triggered, Core::instance(),
|
|
||||||
&Core::dialog_import_show);
|
|
||||||
} else {
|
|
||||||
// Actions to add when only one item is selected
|
|
||||||
if (context_menu_items_.size() == 1) {
|
|
||||||
Node *context_menu_item = context_menu_items_.first();
|
|
||||||
|
|
||||||
if (dynamic_cast<Folder *>(context_menu_item)) {
|
|
||||||
QAction *open_in_new_tab =
|
|
||||||
menu.addAction(tr("Open in New Tab"));
|
|
||||||
connect(open_in_new_tab, &QAction::triggered, this,
|
|
||||||
&ProjectExplorer::open_context_menu_item_in_new_tab);
|
|
||||||
|
|
||||||
QAction *open_in_new_window =
|
|
||||||
menu.addAction(tr("Open in New Window"));
|
|
||||||
connect(open_in_new_window, &QAction::triggered, this,
|
|
||||||
&ProjectExplorer::open_context_menu_item_in_new_window);
|
|
||||||
|
|
||||||
} else if (dynamic_cast<Footage *>(context_menu_item)) {
|
|
||||||
QString reveal_text;
|
|
||||||
|
|
||||||
#if defined(Q_OS_WINDOWS)
|
|
||||||
reveal_text = tr("Reveal in Explorer");
|
|
||||||
#elif defined(Q_OS_MAC)
|
|
||||||
reveal_text = tr("Reveal in Finder");
|
|
||||||
#else
|
|
||||||
reveal_text = tr("Reveal in File Manager");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
QAction *reveal_action = menu.addAction(reveal_text);
|
|
||||||
connect(reveal_action, &QAction::triggered, this,
|
|
||||||
&ProjectExplorer::reveal_selected_footage);
|
|
||||||
|
|
||||||
QAction *replace_action = menu.addAction(tr("Replace Footage"));
|
|
||||||
connect(replace_action, &QAction::triggered, this,
|
|
||||||
&ProjectExplorer::replace_selected_footage);
|
|
||||||
}
|
|
||||||
|
|
||||||
menu.addSeparator();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool all_items_are_footage = true;
|
|
||||||
bool all_items_have_video_streams = true;
|
|
||||||
bool all_items_are_footage_or_sequence = true;
|
|
||||||
|
|
||||||
foreach (Node *i, context_menu_items_) {
|
|
||||||
Footage *footage_cast_test = dynamic_cast<Footage *>(i);
|
|
||||||
Sequence *sequence_cast_test = dynamic_cast<Sequence *>(i);
|
|
||||||
|
|
||||||
if (footage_cast_test &&
|
|
||||||
!footage_cast_test->has_enabled_video_streams()) {
|
|
||||||
all_items_have_video_streams = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!footage_cast_test) {
|
|
||||||
all_items_are_footage = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!footage_cast_test && !sequence_cast_test) {
|
|
||||||
all_items_are_footage_or_sequence = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (all_items_are_footage && all_items_have_video_streams) {
|
|
||||||
const QVector<Footage *> proxy_footage =
|
|
||||||
get_selected_proxy_footage(context_menu_items_);
|
|
||||||
|
|
||||||
Menu *proxy_menu = new Menu(tr("Proxy"), &menu);
|
|
||||||
menu.addMenu(proxy_menu);
|
|
||||||
|
|
||||||
QAction *generate_proxy =
|
|
||||||
proxy_menu->addAction(tr("Generate Proxy"));
|
|
||||||
generate_proxy->setEnabled(!proxy_footage.isEmpty());
|
|
||||||
connect(generate_proxy, &QAction::triggered, this,
|
|
||||||
&ProjectExplorer::generate_proxies_for_selected_footage);
|
|
||||||
|
|
||||||
QAction *use_proxy = proxy_menu->addAction(tr("Use Proxy"));
|
|
||||||
use_proxy->setCheckable(true);
|
|
||||||
use_proxy->setEnabled(!proxy_footage.isEmpty());
|
|
||||||
use_proxy->setChecked(
|
|
||||||
!proxy_footage.isEmpty() &&
|
|
||||||
std::all_of(proxy_footage.cbegin(), proxy_footage.cend(),
|
|
||||||
[](const Footage *footage) {
|
|
||||||
return footage->proxy_enabled();
|
|
||||||
}));
|
|
||||||
connect(use_proxy, &QAction::triggered, this,
|
|
||||||
&ProjectExplorer::set_selected_footage_proxy_enabled);
|
|
||||||
|
|
||||||
QAction *reveal_proxy = proxy_menu->addAction(tr("Reveal Proxy"));
|
|
||||||
reveal_proxy->setEnabled(
|
|
||||||
std::any_of(proxy_footage.cbegin(), proxy_footage.cend(),
|
|
||||||
[](const Footage *footage) {
|
|
||||||
return !footage->proxy_path().isEmpty();
|
|
||||||
}));
|
|
||||||
connect(reveal_proxy, &QAction::triggered, this,
|
|
||||||
&ProjectExplorer::reveal_proxy_for_selected_footage);
|
|
||||||
|
|
||||||
QAction *delete_proxy = proxy_menu->addAction(tr("Delete Proxy"));
|
|
||||||
delete_proxy->setEnabled(
|
|
||||||
std::any_of(proxy_footage.cbegin(), proxy_footage.cend(),
|
|
||||||
[](const Footage *footage) {
|
|
||||||
return !footage->proxy_path().isEmpty();
|
|
||||||
}));
|
|
||||||
connect(delete_proxy, &QAction::triggered, this,
|
|
||||||
&ProjectExplorer::delete_proxies_for_selected_footage);
|
|
||||||
|
|
||||||
QAction *proxy_settings =
|
|
||||||
proxy_menu->addAction(tr("Proxy Settings..."));
|
|
||||||
connect(proxy_settings, &QAction::triggered, this,
|
|
||||||
&ProjectExplorer::show_proxy_dialog_for_selected_footage);
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_UNUSED(all_items_are_footage_or_sequence)
|
|
||||||
|
|
||||||
if (context_menu_items_.size() == 1) {
|
|
||||||
menu.addSeparator();
|
|
||||||
|
|
||||||
auto rename_action = menu.addAction(tr("Rename"));
|
|
||||||
connect(rename_action, &QAction::triggered, this,
|
|
||||||
&ProjectExplorer::rename_selected_item);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto delete_action = menu.addAction(tr("Delete"));
|
|
||||||
connect(delete_action, &QAction::triggered, this,
|
|
||||||
&ProjectExplorer::delete_selected);
|
|
||||||
|
|
||||||
if (context_menu_items_.size() == 1) {
|
|
||||||
menu.addSeparator();
|
|
||||||
|
|
||||||
QAction *properties_action = menu.addAction(tr("P&roperties"));
|
|
||||||
connect(properties_action, &QAction::triggered, this,
|
|
||||||
&ProjectExplorer::show_item_properties_dialog);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
menu.exec(QCursor::pos());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::show_item_properties_dialog()
|
|
||||||
{
|
|
||||||
Node *sel = context_menu_items_.first();
|
|
||||||
|
|
||||||
// FIXME: Support for multiple items
|
|
||||||
if (dynamic_cast<Footage *>(sel)) {
|
|
||||||
FootagePropertiesDialog fpd(this, static_cast<Footage *>(sel));
|
|
||||||
fpd.exec();
|
|
||||||
|
|
||||||
} else if (dynamic_cast<Folder *>(sel)) {
|
|
||||||
Core::instance()->label_nodes(context_menu_items_);
|
|
||||||
|
|
||||||
} else if (dynamic_cast<Sequence *>(sel)) {
|
|
||||||
SequenceDialog sd(static_cast<Sequence *>(sel),
|
|
||||||
SequenceDialog::k_existing, this);
|
|
||||||
sd.exec();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::reveal_selected_footage()
|
|
||||||
{
|
|
||||||
Footage *footage = static_cast<Footage *>(context_menu_items_.first());
|
|
||||||
|
|
||||||
#if defined(Q_OS_WINDOWS)
|
|
||||||
// Explorer
|
|
||||||
QStringList args;
|
|
||||||
args << "/select," << QDir::toNativeSeparators(footage->filename());
|
|
||||||
QProcess::startDetached("explorer", args);
|
|
||||||
#elif defined(Q_OS_MAC)
|
|
||||||
QStringList args;
|
|
||||||
args << "-e";
|
|
||||||
args << "tell application \"Finder\"";
|
|
||||||
args << "-e";
|
|
||||||
args << "activate";
|
|
||||||
args << "-e";
|
|
||||||
args << "select POSIX file \"" + footage->filename() + "\"";
|
|
||||||
args << "-e";
|
|
||||||
args << "end tell";
|
|
||||||
QProcess::startDetached("osascript", args);
|
|
||||||
#else
|
|
||||||
QDesktopServices::openUrl(QUrl::fromLocalFile(
|
|
||||||
QFileInfo(footage->filename()).dir().absolutePath()));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::replace_selected_footage()
|
|
||||||
{
|
|
||||||
Footage *footage = static_cast<Footage *>(context_menu_items_.first());
|
|
||||||
|
|
||||||
QString file =
|
|
||||||
QFileDialog::getOpenFileName(this, tr("Replace Footage"), QString(),
|
|
||||||
Core::footage_file_dialog_filter());
|
|
||||||
if (!file.isEmpty()) {
|
|
||||||
if (!Core::is_footage_extension_allowed(file)) {
|
|
||||||
QMessageBox::warning(
|
|
||||||
this, tr("Unsupported media"),
|
|
||||||
tr("This file type is not allowed by the current media type "
|
|
||||||
"filter."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change the filename through the facade relink (reprobes the new
|
|
||||||
// file and resets proxy/stream state); the label policy stays here.
|
|
||||||
OakEngineFootage *facade_handle = oakengine_footage_borrow(
|
|
||||||
reinterpret_cast<OakEngineNode *>(footage));
|
|
||||||
const int relink_rc = oakengine_footage_relink(
|
|
||||||
facade_handle, file.toUtf8().constData());
|
|
||||||
oakengine_footage_free(facade_handle);
|
|
||||||
if (relink_rc != OAKENGINE_OK) {
|
|
||||||
char err[512];
|
|
||||||
err[0] = '\0';
|
|
||||||
oakengine_footage_last_error(err, sizeof(err));
|
|
||||||
QMessageBox::warning(
|
|
||||||
this, tr("Cannot replace footage"),
|
|
||||||
err[0] ? QString::fromUtf8(err) :
|
|
||||||
tr("The file could not be used as media."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (QFileInfo(footage->filename()).fileName() ==
|
|
||||||
footage->get_label()) {
|
|
||||||
// Footage label == filename, change label too
|
|
||||||
oakengine_node_set_label(
|
|
||||||
reinterpret_cast<OakEngineNode *>(footage),
|
|
||||||
QFileInfo(file).fileName().toUtf8().constData());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::open_context_menu_item_in_new_tab()
|
|
||||||
{
|
|
||||||
Core::instance()->main_window()->open_folder(
|
|
||||||
static_cast<Folder *>(context_menu_items_.first()), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::open_context_menu_item_in_new_window()
|
|
||||||
{
|
|
||||||
Core::instance()->main_window()->open_folder(
|
|
||||||
static_cast<Folder *>(context_menu_items_.first()), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::generate_proxies_for_selected_footage()
|
|
||||||
{
|
|
||||||
if (!project()) {
|
|
||||||
qWarning() << "GenerateProxiesForSelectedFootage: no project";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QVector<Footage *> footage =
|
|
||||||
get_selected_proxy_footage(context_menu_items_);
|
|
||||||
qDebug()
|
|
||||||
<< "GenerateProxiesForSelectedFootage: starting proxy generation for"
|
|
||||||
<< footage.size() << "footage item(s)";
|
|
||||||
for (Footage *item : footage) {
|
|
||||||
const VideoParams video = item->get_first_enabled_video_stream();
|
|
||||||
if (!video.is_valid()) {
|
|
||||||
qWarning()
|
|
||||||
<< "GenerateProxiesForSelectedFootage: skipping item with no valid video stream"
|
|
||||||
<< item->filename();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Queue one facade-backed task per footage item (same queueing
|
|
||||||
// semantics as the old per-footage proxy tasks).
|
|
||||||
oakengine_task_manager_add(
|
|
||||||
reinterpret_cast<OakEngineTask *>(new FacadeProxyTask(item)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::set_selected_footage_proxy_enabled(bool enabled)
|
|
||||||
{
|
|
||||||
const QVector<Footage *> footage =
|
|
||||||
get_selected_proxy_footage(context_menu_items_);
|
|
||||||
qDebug() << "ProjectExplorer::SetSelectedFootageProxyEnabled:" << enabled
|
|
||||||
<< "footage count=" << footage.size();
|
|
||||||
for (Footage *item : footage) {
|
|
||||||
if (item->proxy_path().isEmpty()) {
|
|
||||||
qDebug()
|
|
||||||
<< " skipping item with empty proxy path" << item->filename();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
OakEngineFootage *handle = oakengine_footage_borrow(
|
|
||||||
reinterpret_cast<OakEngineNode *>(item));
|
|
||||||
oakengine_footage_proxy_set_enabled(handle, enabled ? 1 : 0);
|
|
||||||
oakengine_footage_free(handle);
|
|
||||||
// The facade call toggles the flag; cache invalidation for the UI
|
|
||||||
// stays here.
|
|
||||||
item->invalidate_all(Footage::k_filename_input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::reveal_proxy_for_selected_footage()
|
|
||||||
{
|
|
||||||
const QVector<Footage *> footage =
|
|
||||||
get_selected_proxy_footage(context_menu_items_);
|
|
||||||
for (Footage *item : footage) {
|
|
||||||
char proxy_path[4096];
|
|
||||||
proxy_path[0] = '\0';
|
|
||||||
OakEngineFootage *handle = oakengine_footage_borrow(
|
|
||||||
reinterpret_cast<OakEngineNode *>(item));
|
|
||||||
oakengine_footage_proxy_get_path(handle, proxy_path,
|
|
||||||
sizeof(proxy_path));
|
|
||||||
oakengine_footage_free(handle);
|
|
||||||
if (proxy_path[0] == '\0') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const QString path = QString::fromUtf8(proxy_path);
|
|
||||||
|
|
||||||
#if defined(Q_OS_WINDOWS)
|
|
||||||
QStringList args;
|
|
||||||
args << "/select," << QDir::toNativeSeparators(path);
|
|
||||||
QProcess::startDetached(QStringLiteral("explorer"), args);
|
|
||||||
#elif defined(Q_OS_MAC)
|
|
||||||
QStringList args;
|
|
||||||
args << "-e";
|
|
||||||
args << "tell application \"Finder\"";
|
|
||||||
args << "-e";
|
|
||||||
args << "activate";
|
|
||||||
args << "-e";
|
|
||||||
args << "select POSIX file \"" + path + "\"";
|
|
||||||
args << "-e";
|
|
||||||
args << "end tell";
|
|
||||||
QProcess::startDetached(QStringLiteral("osascript"), args);
|
|
||||||
#else
|
|
||||||
QDesktopServices::openUrl(QUrl::fromLocalFile(
|
|
||||||
QFileInfo(path).dir().absolutePath()));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::delete_proxies_for_selected_footage()
|
|
||||||
{
|
|
||||||
const QVector<Footage *> footage =
|
|
||||||
get_selected_proxy_footage(context_menu_items_);
|
|
||||||
for (Footage *item : footage) {
|
|
||||||
if (item->proxy_path().isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Facade delete: removes the file, clears the proxy state and
|
|
||||||
// invalidates the footage.
|
|
||||||
OakEngineFootage *handle = oakengine_footage_borrow(
|
|
||||||
reinterpret_cast<OakEngineNode *>(item));
|
|
||||||
oakengine_footage_proxy_delete(handle);
|
|
||||||
oakengine_footage_free(handle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::show_proxy_dialog_for_selected_footage()
|
|
||||||
{
|
|
||||||
ProxyDialog d(this, get_selected_proxy_footage(context_menu_items_));
|
|
||||||
d.exec();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::view_selection_changed()
|
|
||||||
{
|
|
||||||
QItemSelectionModel *model = static_cast<QItemSelectionModel *>(sender());
|
|
||||||
|
|
||||||
QModelIndexList selection = model->selectedIndexes();
|
|
||||||
|
|
||||||
QVector<Node *> nodes;
|
|
||||||
|
|
||||||
foreach (const QModelIndex &index, selection) {
|
|
||||||
Node *sel = static_cast<Node *>(
|
|
||||||
sort_model_.mapToSource(index).internalPointer());
|
|
||||||
if (!nodes.contains(sel)) {
|
|
||||||
nodes.append(sel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nodes.isEmpty()) {
|
|
||||||
nodes.append(get_root());
|
|
||||||
}
|
|
||||||
|
|
||||||
emit selection_changed(nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
Project *ProjectExplorer::project() const
|
|
||||||
{
|
|
||||||
return model_.project();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::set_project(Project *p)
|
|
||||||
{
|
|
||||||
model_.set_project(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
Folder *ProjectExplorer::get_root() const
|
|
||||||
{
|
|
||||||
QModelIndex root_index = sort_model_.mapToSource(tree_view_->rootIndex());
|
|
||||||
|
|
||||||
if (!root_index.isValid()) {
|
|
||||||
return project()->root();
|
|
||||||
}
|
|
||||||
|
|
||||||
return static_cast<Folder *>(root_index.internalPointer());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::set_root(Folder *item)
|
|
||||||
{
|
|
||||||
QModelIndex index =
|
|
||||||
sort_model_.mapFromSource(model_.create_index_from_item(item));
|
|
||||||
|
|
||||||
browse_to_folder(index);
|
|
||||||
tree_view_->setRootIndex(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<Node *> ProjectExplorer::selected_items() const
|
|
||||||
{
|
|
||||||
// Determine which view is active and get its selected indexes
|
|
||||||
QModelIndexList index_list =
|
|
||||||
current_view()->selectionModel()->selectedRows();
|
|
||||||
|
|
||||||
// Convert indexes to item objects
|
|
||||||
QVector<Node *> selected_items;
|
|
||||||
|
|
||||||
for (int i = 0; i < index_list.size(); i++) {
|
|
||||||
QModelIndex index = sort_model_.mapToSource(index_list.at(i));
|
|
||||||
|
|
||||||
Node *item = static_cast<Node *>(index.internalPointer());
|
|
||||||
|
|
||||||
selected_items.append(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
return selected_items;
|
|
||||||
}
|
|
||||||
|
|
||||||
Folder *ProjectExplorer::get_selected_folder() const
|
|
||||||
{
|
|
||||||
if (project() == nullptr) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Folder *folder = nullptr;
|
|
||||||
|
|
||||||
// Get the selected items from the panel
|
|
||||||
QVector<Node *> selected_nodes = selected_items();
|
|
||||||
|
|
||||||
// Heuristic for finding the selected folder:
|
|
||||||
//
|
|
||||||
// - If `folder` is nullptr, we set the first folder we find. Either the item itself if it's a folder, or the
|
|
||||||
// item's parent.
|
|
||||||
// - Otherwise, if all folders found are the same, we'll use that to import into.
|
|
||||||
// - If more than one folder is found, we play it safe and import into the root folder
|
|
||||||
|
|
||||||
for (int i = 0; i < selected_nodes.size(); i++) {
|
|
||||||
Node *sel_item = selected_nodes.at(i);
|
|
||||||
|
|
||||||
// If this item is not a folder, presumably it's parent is
|
|
||||||
if (!dynamic_cast<Folder *>(sel_item)) {
|
|
||||||
sel_item = sel_item->folder();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (folder == nullptr) {
|
|
||||||
// If the folder is nullptr, cache it as this folder
|
|
||||||
folder = static_cast<Folder *>(sel_item);
|
|
||||||
} else if (folder != sel_item) {
|
|
||||||
// If not, we've already cached a folder so we check if it's the same
|
|
||||||
// If it isn't, we "play it safe" and use the root folder
|
|
||||||
folder = nullptr;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we didn't pick up a folder from the heuristic above for whatever reason, use root
|
|
||||||
if (folder == nullptr) {
|
|
||||||
folder = project()->root();
|
|
||||||
}
|
|
||||||
|
|
||||||
return folder;
|
|
||||||
}
|
|
||||||
|
|
||||||
ProjectViewModel *ProjectExplorer::model()
|
|
||||||
{
|
|
||||||
return &model_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::select_all()
|
|
||||||
{
|
|
||||||
current_view()->selectAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::deselect_all()
|
|
||||||
{
|
|
||||||
current_view()->selectionModel()->clearSelection();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorer::delete_selected()
|
|
||||||
{
|
|
||||||
QVector<Node *> selected = selected_items();
|
|
||||||
|
|
||||||
if (selected.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MultiUndoCommand *command = new MultiUndoCommand();
|
|
||||||
|
|
||||||
bool check_if_item_is_in_use = true;
|
|
||||||
|
|
||||||
if (delete_items_internal(selected, check_if_item_is_in_use, command)) {
|
|
||||||
Core::instance()->undo_stack()->push(
|
|
||||||
command, tr("Deleted %1 Item(s)").arg(selected.size()));
|
|
||||||
} else {
|
|
||||||
delete command;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ProjectExplorer::select_item(Node *n, bool deselect_all_first)
|
|
||||||
{
|
|
||||||
if (deselect_all_first) {
|
|
||||||
deselect_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
QModelIndex index = model_.create_index_from_item(n);
|
|
||||||
|
|
||||||
if (index.isValid()) {
|
|
||||||
index = sort_model_.mapFromSource(index);
|
|
||||||
|
|
||||||
QModelIndex parent = index.parent();
|
|
||||||
if (view_type() == ProjectToolbar::tree_view) {
|
|
||||||
// Expand all folders until this index is visible
|
|
||||||
while (parent.isValid()) {
|
|
||||||
tree_view_->expand(parent);
|
|
||||||
parent = parent.parent();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
browse_to_folder(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
current_view()->selectionModel()->select(
|
|
||||||
index, QItemSelectionModel::Select | QItemSelectionModel::Rows);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 OAK_PROJECTEXPLORER_H
|
|
||||||
#define OAK_PROJECTEXPLORER_H
|
|
||||||
|
|
||||||
#include <QSortFilterProxyModel>
|
|
||||||
#include <QStackedWidget>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QTreeView>
|
|
||||||
|
|
||||||
#include "node/project.h"
|
|
||||||
#include "projectviewmodel.h"
|
|
||||||
#include "widget/projectexplorer/projectexplorericonview.h"
|
|
||||||
#include "widget/projectexplorer/projectexplorerlistview.h"
|
|
||||||
#include "widget/projectexplorer/projectexplorertreeview.h"
|
|
||||||
#include "widget/projectexplorer/projectexplorernavigation.h"
|
|
||||||
#include "widget/projecttoolbar/projecttoolbar.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief A widget for browsing through a Project structure.
|
|
||||||
*
|
|
||||||
* ProjectExplorer automatically handles the view<->model system using a ProjectViewModel. Therefore, all that needs to
|
|
||||||
* be provided is the Project structure itself.
|
|
||||||
*
|
|
||||||
* This widget contains three views, tree view, list view, and icon view. These can be switched at any time.
|
|
||||||
*/
|
|
||||||
class ProjectExplorer : public QWidget {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
ProjectExplorer(QWidget *parent);
|
|
||||||
|
|
||||||
const ProjectToolbar::ViewType &view_type() const;
|
|
||||||
|
|
||||||
Project *project() const;
|
|
||||||
void set_project(Project *p);
|
|
||||||
|
|
||||||
Folder *get_root() const;
|
|
||||||
void set_root(Folder *item);
|
|
||||||
|
|
||||||
QVector<Node *> selected_items() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Use a heuristic to determine which (if any) folder is selected
|
|
||||||
*
|
|
||||||
* Generally for some import/adding processes, we assume that if a folder is selected, the user probably wants to
|
|
||||||
* create the new object in it rather than in the root. If, however, more than one folder is selected, we can't
|
|
||||||
* truly determine any folder from this and just return the root instead.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
* A folder that's heuristically been determined as "selected", or the root directory if none, or nullptr if no
|
|
||||||
* project is open.
|
|
||||||
*/
|
|
||||||
Folder *get_selected_folder() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Access the ViewModel model of the project
|
|
||||||
*/
|
|
||||||
ProjectViewModel *model();
|
|
||||||
|
|
||||||
void select_all();
|
|
||||||
|
|
||||||
void deselect_all();
|
|
||||||
|
|
||||||
void delete_selected();
|
|
||||||
|
|
||||||
bool select_item(Node *n, bool deselect_all_first = true);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void set_view_type(ProjectToolbar::ViewType type);
|
|
||||||
|
|
||||||
void edit(Node *item);
|
|
||||||
|
|
||||||
void rename_selected_item();
|
|
||||||
|
|
||||||
void set_search_filter(const QString &s);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
/**
|
|
||||||
* @brief Emitted when an Item is double clicked
|
|
||||||
*
|
|
||||||
* @param item
|
|
||||||
*
|
|
||||||
* The Item that was double clicked, or nullptr if empty area was double clicked
|
|
||||||
*/
|
|
||||||
void double_clicked_item(Node *item);
|
|
||||||
|
|
||||||
void selection_changed(const QVector<Node *> &selected);
|
|
||||||
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* @brief Get all the blocks that solely rely on an input node
|
|
||||||
*
|
|
||||||
* Ignores blocks that depend on multiple inputs
|
|
||||||
*/
|
|
||||||
QList<Block *> get_footage_blocks(QList<Node *> nodes);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Simple convenience function for adding a view to this stacked widget
|
|
||||||
*
|
|
||||||
* Mainly for use in the constructor. Adds the view, connects its signals/slots, and sets the model.
|
|
||||||
*
|
|
||||||
* @param view
|
|
||||||
*
|
|
||||||
* View to add to the stack
|
|
||||||
*/
|
|
||||||
void add_view(QAbstractItemView *view);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Browse to a specific folder index in the model
|
|
||||||
*
|
|
||||||
* Only affects list_view_ and icon_view_.
|
|
||||||
*
|
|
||||||
* @param index
|
|
||||||
*
|
|
||||||
* Either an invalid index to return to the project root, or an index to a valid Folder object.
|
|
||||||
*/
|
|
||||||
void browse_to_folder(const QModelIndex &index);
|
|
||||||
|
|
||||||
int confirm_item_deletion(Node *item);
|
|
||||||
|
|
||||||
bool delete_items_internal(const QVector<Node *> &selected,
|
|
||||||
bool &check_if_item_is_in_use,
|
|
||||||
MultiUndoCommand *command);
|
|
||||||
|
|
||||||
static QString get_human_readable_node_name(Node *node);
|
|
||||||
|
|
||||||
void update_nav_bar_text();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get the currently active QAbstractItemView
|
|
||||||
*/
|
|
||||||
QAbstractItemView *current_view() const;
|
|
||||||
|
|
||||||
QStackedWidget *stacked_widget_;
|
|
||||||
|
|
||||||
ProjectExplorerNavigation *nav_bar_;
|
|
||||||
|
|
||||||
ProjectExplorerIconView *icon_view_;
|
|
||||||
ProjectExplorerListView *list_view_;
|
|
||||||
ProjectExplorerTreeView *tree_view_;
|
|
||||||
|
|
||||||
ProjectToolbar::ViewType view_type_;
|
|
||||||
|
|
||||||
QSortFilterProxyModel sort_model_;
|
|
||||||
ProjectViewModel model_;
|
|
||||||
|
|
||||||
QVector<Node *> context_menu_items_;
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void view_empty_area_double_clicked_slot();
|
|
||||||
|
|
||||||
void item_double_clicked_slot(const QModelIndex &index);
|
|
||||||
|
|
||||||
void size_changed_slot(int s);
|
|
||||||
|
|
||||||
void dir_up_slot();
|
|
||||||
|
|
||||||
void show_context_menu();
|
|
||||||
|
|
||||||
void show_item_properties_dialog();
|
|
||||||
|
|
||||||
void reveal_selected_footage();
|
|
||||||
|
|
||||||
void replace_selected_footage();
|
|
||||||
|
|
||||||
void open_context_menu_item_in_new_tab();
|
|
||||||
|
|
||||||
void open_context_menu_item_in_new_window();
|
|
||||||
|
|
||||||
void generate_proxies_for_selected_footage();
|
|
||||||
|
|
||||||
void set_selected_footage_proxy_enabled(bool enabled);
|
|
||||||
|
|
||||||
void reveal_proxy_for_selected_footage();
|
|
||||||
|
|
||||||
void delete_proxies_for_selected_footage();
|
|
||||||
|
|
||||||
void show_proxy_dialog_for_selected_footage();
|
|
||||||
|
|
||||||
void view_selection_changed();
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // OAK_PROJECTEXPLORER_H
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 "projectexplorericonview.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
ProjectExplorerIconView::ProjectExplorerIconView(QWidget *parent)
|
|
||||||
: ProjectExplorerListViewBase(parent)
|
|
||||||
{
|
|
||||||
setViewMode(QListView::IconMode);
|
|
||||||
|
|
||||||
setItemDelegate(&delegate_);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 OAK_PROJECTEXPLORERICONVIEW_H
|
|
||||||
#define OAK_PROJECTEXPLORERICONVIEW_H
|
|
||||||
|
|
||||||
#include "projectexplorerlistviewbase.h"
|
|
||||||
#include "projectexplorericonviewitemdelegate.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The view widget used when ProjectExplorer is in Icon View
|
|
||||||
*/
|
|
||||||
class ProjectExplorerIconView : public ProjectExplorerListViewBase {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
ProjectExplorerIconView(QWidget *parent);
|
|
||||||
|
|
||||||
private:
|
|
||||||
ProjectExplorerIconViewItemDelegate delegate_;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // OAK_PROJECTEXPLORERICONVIEW_H
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 "projectexplorericonviewitemdelegate.h"
|
|
||||||
|
|
||||||
#include <QPainter>
|
|
||||||
|
|
||||||
#include "common/qtutils.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
ProjectExplorerIconViewItemDelegate::ProjectExplorerIconViewItemDelegate(
|
|
||||||
QObject *parent)
|
|
||||||
: QStyledItemDelegate(parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
QSize ProjectExplorerIconViewItemDelegate::sizeHint(
|
|
||||||
const QStyleOptionViewItem &option, const QModelIndex &) const
|
|
||||||
{
|
|
||||||
Q_UNUSED(option)
|
|
||||||
|
|
||||||
return QSize(256, 256);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorerIconViewItemDelegate::paint(
|
|
||||||
QPainter *painter, const QStyleOptionViewItem &option,
|
|
||||||
const QModelIndex &index) const
|
|
||||||
{
|
|
||||||
QFontMetrics fm = painter->fontMetrics();
|
|
||||||
QRect img_rect = option.rect;
|
|
||||||
|
|
||||||
// Draw Text
|
|
||||||
if (fm.height() < option.rect.height() / 2) {
|
|
||||||
img_rect.setHeight(img_rect.height() - fm.height());
|
|
||||||
|
|
||||||
QRect text_rect = option.rect;
|
|
||||||
text_rect.setTop(text_rect.top() + option.rect.height() - fm.height());
|
|
||||||
|
|
||||||
QColor text_bgcolor;
|
|
||||||
QColor text_fgcolor;
|
|
||||||
|
|
||||||
if (option.state & QStyle::State_Selected) {
|
|
||||||
text_bgcolor = option.palette.highlight().color();
|
|
||||||
text_fgcolor = option.palette.highlightedText().color();
|
|
||||||
} else {
|
|
||||||
text_bgcolor = Qt::white;
|
|
||||||
text_fgcolor = Qt::black;
|
|
||||||
}
|
|
||||||
|
|
||||||
painter->fillRect(text_rect, text_bgcolor);
|
|
||||||
painter->setPen(text_fgcolor);
|
|
||||||
|
|
||||||
QString duration_str = index.data(Qt::UserRole).toString();
|
|
||||||
|
|
||||||
int timecode_width = QtUtils::q_font_metrics_width(fm, duration_str);
|
|
||||||
|
|
||||||
int max_name_width = option.rect.width();
|
|
||||||
|
|
||||||
if (timecode_width < option.rect.width() / 2) {
|
|
||||||
painter->drawText(
|
|
||||||
text_rect, static_cast<int>(Qt::AlignBottom | Qt::AlignRight),
|
|
||||||
index.data(Qt::UserRole).toString());
|
|
||||||
max_name_width -= timecode_width;
|
|
||||||
}
|
|
||||||
|
|
||||||
painter->drawText(text_rect,
|
|
||||||
static_cast<int>(Qt::AlignBottom | Qt::AlignLeft),
|
|
||||||
fm.elidedText(index.data(Qt::DisplayRole).toString(),
|
|
||||||
Qt::ElideRight, max_name_width));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw image
|
|
||||||
QIcon ico = index.data(Qt::DecorationRole).value<QIcon>();
|
|
||||||
QSize icon_size = ico.actualSize(img_rect.size());
|
|
||||||
img_rect =
|
|
||||||
QRect(img_rect.x() + (img_rect.width() / 2 - icon_size.width() / 2),
|
|
||||||
img_rect.y() + (img_rect.height() / 2 - icon_size.height() / 2),
|
|
||||||
icon_size.width(), icon_size.height());
|
|
||||||
painter->drawPixmap(img_rect, ico.pixmap(icon_size));
|
|
||||||
|
|
||||||
if (option.state & QStyle::State_Selected) {
|
|
||||||
QColor highlight_color = option.palette.highlight().color();
|
|
||||||
highlight_color.setAlphaF(0.5);
|
|
||||||
|
|
||||||
painter->setCompositionMode(QPainter::CompositionMode_SourceAtop);
|
|
||||||
painter->fillRect(img_rect, highlight_color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 OAK_PROJECTEXPLORERICONVIEWITEMDELEGATE_H
|
|
||||||
#define OAK_PROJECTEXPLORERICONVIEWITEMDELEGATE_H
|
|
||||||
|
|
||||||
#include <QStyledItemDelegate>
|
|
||||||
|
|
||||||
#include "common/define.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The delegate that's used to draw items when ProjectExplorer is in Icon view
|
|
||||||
*/
|
|
||||||
class ProjectExplorerIconViewItemDelegate : public QStyledItemDelegate {
|
|
||||||
public:
|
|
||||||
ProjectExplorerIconViewItemDelegate(QObject *parent = nullptr);
|
|
||||||
|
|
||||||
virtual QSize sizeHint(const QStyleOptionViewItem &option,
|
|
||||||
const QModelIndex &index) const override;
|
|
||||||
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option,
|
|
||||||
const QModelIndex &index) const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // OAK_PROJECTEXPLORERICONVIEWITEMDELEGATE_H
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 "projectexplorerlistview.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
ProjectExplorerListView::ProjectExplorerListView(QWidget *parent)
|
|
||||||
: ProjectExplorerListViewBase(parent)
|
|
||||||
{
|
|
||||||
setViewMode(QListView::ListMode);
|
|
||||||
|
|
||||||
setItemDelegate(&delegate_);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 OAK_PROJECTEXPLORERLISTVIEW_H
|
|
||||||
#define OAK_PROJECTEXPLORERLISTVIEW_H
|
|
||||||
|
|
||||||
#include "projectexplorerlistviewbase.h"
|
|
||||||
#include "projectexplorerlistviewitemdelegate.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The view widget used when ProjectExplorer is in List View
|
|
||||||
*/
|
|
||||||
class ProjectExplorerListView : public ProjectExplorerListViewBase {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
ProjectExplorerListView(QWidget *parent);
|
|
||||||
|
|
||||||
private:
|
|
||||||
ProjectExplorerListViewItemDelegate delegate_;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // OAK_PROJECTEXPLORERLISTVIEW_H
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 "projectexplorerlistviewbase.h"
|
|
||||||
|
|
||||||
#include <QMouseEvent>
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
ProjectExplorerListViewBase::ProjectExplorerListViewBase(QWidget *parent)
|
|
||||||
: QListView(parent)
|
|
||||||
{
|
|
||||||
// FIXME Is this necessary?
|
|
||||||
setMovement(QListView::Free);
|
|
||||||
|
|
||||||
// Set selection mode (allows multiple item selection)
|
|
||||||
setSelectionMode(QAbstractItemView::ExtendedSelection);
|
|
||||||
|
|
||||||
// Set resize mode
|
|
||||||
setResizeMode(QListView::Adjust);
|
|
||||||
|
|
||||||
// Set widget to emit a signal on right click
|
|
||||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorerListViewBase::mouseDoubleClickEvent(QMouseEvent *event)
|
|
||||||
{
|
|
||||||
// Cache here so if the index becomes invalid after the base call, we still know the truth
|
|
||||||
bool item_at_location = indexAt(event->pos()).isValid();
|
|
||||||
|
|
||||||
// Perform default double click functions
|
|
||||||
QListView::mouseDoubleClickEvent(event);
|
|
||||||
|
|
||||||
// QAbstractItemView already has a doubleClicked() signal, but we emit another here for double clicking empty space
|
|
||||||
if (!item_at_location) {
|
|
||||||
emit double_clicked_empty_area();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 OAK_PROJECTEXPLORERLISTVIEWBASE_H
|
|
||||||
#define OAK_PROJECTEXPLORERLISTVIEWBASE_H
|
|
||||||
|
|
||||||
#include <QListView>
|
|
||||||
|
|
||||||
#include "common/define.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief A QListView derivative that contains functionality used by both List view and Icon view (which are both based
|
|
||||||
* on QListView)
|
|
||||||
*/
|
|
||||||
class ProjectExplorerListViewBase : public QListView {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
ProjectExplorerListViewBase(QWidget *parent);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/**
|
|
||||||
* @brief Double click event override
|
|
||||||
*
|
|
||||||
* Function that signals DoubleClickedView().
|
|
||||||
*
|
|
||||||
* FIXME: This code is the same as the code in ProjectExplorerTreeView. Is there a way to merge these two through
|
|
||||||
* subclassing?
|
|
||||||
*/
|
|
||||||
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
/**
|
|
||||||
* @brief Unconditional double click signal
|
|
||||||
*
|
|
||||||
* Emits a signal when the view is double clicked but not on any particular item
|
|
||||||
*/
|
|
||||||
void double_clicked_empty_area();
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // OAK_PROJECTEXPLORERLISTVIEWBASE_H
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 "projectexplorerlistviewitemdelegate.h"
|
|
||||||
|
|
||||||
#include <QPainter>
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
ProjectExplorerListViewItemDelegate::ProjectExplorerListViewItemDelegate(
|
|
||||||
QObject *parent)
|
|
||||||
: QStyledItemDelegate(parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
QSize ProjectExplorerListViewItemDelegate::sizeHint(
|
|
||||||
const QStyleOptionViewItem &option, const QModelIndex &) const
|
|
||||||
{
|
|
||||||
return QSize(option.decorationSize.height(),
|
|
||||||
option.decorationSize.height());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorerListViewItemDelegate::paint(
|
|
||||||
QPainter *painter, const QStyleOptionViewItem &option,
|
|
||||||
const QModelIndex &index) const
|
|
||||||
{
|
|
||||||
QFontMetrics fm = painter->fontMetrics();
|
|
||||||
QRect img_rect = option.rect;
|
|
||||||
|
|
||||||
if (option.state & QStyle::State_Selected) {
|
|
||||||
painter->fillRect(option.rect, option.palette.highlight());
|
|
||||||
}
|
|
||||||
|
|
||||||
img_rect.setWidth(qMin(img_rect.width(), img_rect.height()));
|
|
||||||
|
|
||||||
QIcon ico = index.data(Qt::DecorationRole).value<QIcon>();
|
|
||||||
QSize icon_size = ico.actualSize(img_rect.size());
|
|
||||||
img_rect =
|
|
||||||
QRect(img_rect.x() + (img_rect.width() / 2 - icon_size.width() / 2),
|
|
||||||
img_rect.y() + (img_rect.height() / 2 - icon_size.height() / 2),
|
|
||||||
icon_size.width(), icon_size.height());
|
|
||||||
painter->drawPixmap(img_rect, ico.pixmap(icon_size));
|
|
||||||
|
|
||||||
QRect text_rect = option.rect;
|
|
||||||
text_rect.setLeft(text_rect.left() + option.rect.height());
|
|
||||||
|
|
||||||
int maximum_line_count = qMax(1, option.rect.height() / fm.height() - 1);
|
|
||||||
QString text;
|
|
||||||
if (maximum_line_count == 1) {
|
|
||||||
text = index.data(Qt::DisplayRole).toString();
|
|
||||||
} else {
|
|
||||||
text = index.data(Qt::ToolTipRole).toString();
|
|
||||||
if (text.isEmpty()) {
|
|
||||||
text = index.data(Qt::DisplayRole).toString();
|
|
||||||
} else {
|
|
||||||
QStringList strings = text.split("\n");
|
|
||||||
while (strings.size() > maximum_line_count) {
|
|
||||||
strings.removeLast();
|
|
||||||
}
|
|
||||||
text = strings.join("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
painter->setPen(option.state & QStyle::State_Selected ?
|
|
||||||
option.palette.highlightedText().color() :
|
|
||||||
option.palette.text().color());
|
|
||||||
|
|
||||||
painter->drawText(text_rect,
|
|
||||||
static_cast<int>(Qt::AlignLeft | Qt::AlignVCenter), text);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 OAK_PROJECTEXPLORERLISTVIEWITEMDELEGATE_H
|
|
||||||
#define OAK_PROJECTEXPLORERLISTVIEWITEMDELEGATE_H
|
|
||||||
|
|
||||||
#include <QStyledItemDelegate>
|
|
||||||
|
|
||||||
#include "common/define.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The delegate that's used to draw items when ProjectExplorer is in List view
|
|
||||||
*/
|
|
||||||
class ProjectExplorerListViewItemDelegate : public QStyledItemDelegate {
|
|
||||||
public:
|
|
||||||
ProjectExplorerListViewItemDelegate(QObject *parent = nullptr);
|
|
||||||
|
|
||||||
virtual QSize sizeHint(const QStyleOptionViewItem &option,
|
|
||||||
const QModelIndex &index) const override;
|
|
||||||
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option,
|
|
||||||
const QModelIndex &index) const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // OAK_PROJECTEXPLORERLISTVIEWITEMDELEGATE_H
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 "projectexplorernavigation.h"
|
|
||||||
|
|
||||||
#include <QEvent>
|
|
||||||
#include <QHBoxLayout>
|
|
||||||
|
|
||||||
#include "common/define.h"
|
|
||||||
#include "ui/icons/icons.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
ProjectExplorerNavigation::ProjectExplorerNavigation(QWidget *parent)
|
|
||||||
: QWidget(parent)
|
|
||||||
{
|
|
||||||
// Create widget layout
|
|
||||||
QHBoxLayout *layout = new QHBoxLayout(this);
|
|
||||||
layout->setContentsMargins(0, 0, 0, 0);
|
|
||||||
|
|
||||||
// Create "directory up" button
|
|
||||||
dir_up_btn_ = new QPushButton(this);
|
|
||||||
dir_up_btn_->setEnabled(false);
|
|
||||||
dir_up_btn_->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
|
|
||||||
layout->addWidget(dir_up_btn_);
|
|
||||||
connect(dir_up_btn_, SIGNAL(clicked(bool)), this,
|
|
||||||
SIGNAL(directory_up_clicked()));
|
|
||||||
|
|
||||||
// Create directory tree label
|
|
||||||
dir_lbl_ = new QLabel(this);
|
|
||||||
dir_lbl_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
|
|
||||||
layout->addWidget(dir_lbl_);
|
|
||||||
|
|
||||||
// Create size slider
|
|
||||||
size_slider_ = new QSlider(this);
|
|
||||||
size_slider_->setOrientation(Qt::Horizontal);
|
|
||||||
size_slider_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
|
|
||||||
layout->addWidget(size_slider_);
|
|
||||||
connect(size_slider_, SIGNAL(valueChanged(int)), this,
|
|
||||||
SIGNAL(size_changed(int)));
|
|
||||||
|
|
||||||
retranslate();
|
|
||||||
update_icons();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorerNavigation::set_text(const QString &s)
|
|
||||||
{
|
|
||||||
dir_lbl_->setText(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorerNavigation::set_dir_up_enabled(bool e)
|
|
||||||
{
|
|
||||||
dir_up_btn_->setEnabled(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorerNavigation::set_size_value(int s)
|
|
||||||
{
|
|
||||||
size_slider_->setValue(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorerNavigation::changeEvent(QEvent *e)
|
|
||||||
{
|
|
||||||
if (e->type() == QEvent::LanguageChange) {
|
|
||||||
retranslate();
|
|
||||||
} else if (e->type() == QEvent::StyleChange) {
|
|
||||||
update_icons();
|
|
||||||
}
|
|
||||||
QWidget::changeEvent(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorerNavigation::retranslate()
|
|
||||||
{
|
|
||||||
dir_up_btn_->setToolTip(tr("Go to parent folder"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorerNavigation::update_icons()
|
|
||||||
{
|
|
||||||
dir_up_btn_->setIcon(icon::dir_up);
|
|
||||||
size_slider_->setMinimum(k_project_icon_size_minimum);
|
|
||||||
size_slider_->setMaximum(k_project_icon_size_maximum);
|
|
||||||
size_slider_->setValue(k_project_icon_size_default);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 OAK_PROJECTEXPLORERLISTVIEWTOOLBAR_H
|
|
||||||
#define OAK_PROJECTEXPLORERLISTVIEWTOOLBAR_H
|
|
||||||
|
|
||||||
#include <QLabel>
|
|
||||||
#include <QPushButton>
|
|
||||||
#include <QSlider>
|
|
||||||
#include <QWidget>
|
|
||||||
|
|
||||||
#include "common/define.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief A navigation bar widget for ProjectExplorer's Icon and List views
|
|
||||||
*
|
|
||||||
* Unlike the Tree view, Icon and List don't follow a hierarchical view of information. This means there is no direct
|
|
||||||
* way of navigating in and out of folders in those view types. We solve this in two ways:
|
|
||||||
*
|
|
||||||
* * Double clicking a Folder in those views will enter that folder
|
|
||||||
* * This navigation bar offers a "directory up" button for leaving a folder
|
|
||||||
*
|
|
||||||
* This navbar also provides an icon size slider for those views (between kProjectIconSizeMinimum and
|
|
||||||
* kProjectIconSizeMaximum) as well as text that's intended to be set to the current Folder's name (or
|
|
||||||
* empty for the root folder).
|
|
||||||
*
|
|
||||||
* This widget does not actually communicate to Project or ProjectExplorer classes. It is simply UI widgets that are
|
|
||||||
* intended to be connected in ways that do. This is the primarily responsibility of ProjectExplorer.
|
|
||||||
*
|
|
||||||
* By default, the directory up button is disabled (assuming root folder), the text is empty, and the icon size slider
|
|
||||||
* is set to kProjectIconSizeDefault.
|
|
||||||
*/
|
|
||||||
class ProjectExplorerNavigation : public QWidget {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
ProjectExplorerNavigation(QWidget *parent);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Sets the text string
|
|
||||||
*
|
|
||||||
* This text is intended to be set to the current Folder's name
|
|
||||||
*
|
|
||||||
* @param s
|
|
||||||
*/
|
|
||||||
void set_text(const QString &s);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set whether the "directory up" button is enabled or not
|
|
||||||
*
|
|
||||||
* @param e
|
|
||||||
*/
|
|
||||||
void set_dir_up_enabled(bool e);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set the current value of the size slider
|
|
||||||
*
|
|
||||||
* NOTE: Does NOT emit SizeChanged().
|
|
||||||
*
|
|
||||||
* @param s
|
|
||||||
*
|
|
||||||
* New size value to set to
|
|
||||||
*/
|
|
||||||
void set_size_value(int s);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
/**
|
|
||||||
* @brief Signal emitted when the directory up button is clicked
|
|
||||||
*/
|
|
||||||
void directory_up_clicked();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Signal emitted when the icon size slider changes value
|
|
||||||
*
|
|
||||||
* @param size
|
|
||||||
*
|
|
||||||
* New size set in the slider
|
|
||||||
*/
|
|
||||||
void size_changed(int size);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void changeEvent(QEvent *) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void retranslate();
|
|
||||||
|
|
||||||
void update_icons();
|
|
||||||
|
|
||||||
QPushButton *dir_up_btn_;
|
|
||||||
|
|
||||||
QLabel *dir_lbl_;
|
|
||||||
|
|
||||||
QSlider *size_slider_;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // OAK_PROJECTEXPLORERLISTVIEWTOOLBAR_H
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 "projectexplorertreeview.h"
|
|
||||||
|
|
||||||
#include <QMouseEvent>
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
ProjectExplorerTreeView::ProjectExplorerTreeView(QWidget *parent)
|
|
||||||
: QTreeView(parent)
|
|
||||||
{
|
|
||||||
// Set selection mode (allows multiple item selection)
|
|
||||||
setSelectionMode(QAbstractItemView::ExtendedSelection);
|
|
||||||
|
|
||||||
// Allow dragging and dropping
|
|
||||||
setDragDropMode(QAbstractItemView::DragDrop);
|
|
||||||
|
|
||||||
// Enable dragging
|
|
||||||
setDragEnabled(true);
|
|
||||||
|
|
||||||
// Allow dropping from external sources
|
|
||||||
setAcceptDrops(true);
|
|
||||||
|
|
||||||
// Set context menu to emit a signal
|
|
||||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectExplorerTreeView::mouseDoubleClickEvent(QMouseEvent *event)
|
|
||||||
{
|
|
||||||
// Perform default double click functions
|
|
||||||
QTreeView::mouseDoubleClickEvent(event);
|
|
||||||
|
|
||||||
// QAbstractItemView already has a doubleClicked() signal, but we emit another here for double clicking empty space
|
|
||||||
if (!indexAt(event->pos()).isValid()) {
|
|
||||||
emit double_clicked_empty_area();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 OAK_PROJECTEXPLORERTREEVIEW_H
|
|
||||||
#define OAK_PROJECTEXPLORERTREEVIEW_H
|
|
||||||
|
|
||||||
#include <QTreeView>
|
|
||||||
|
|
||||||
#include "common/define.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The view widget used when ProjectExplorer is in Tree View
|
|
||||||
*
|
|
||||||
* A fairly simple subclass of QTreeView that provides a double clicked signal whether the index is valid or not
|
|
||||||
* (QAbstractItemView has a doubleClicked() signal but it's only emitted with a valid index).
|
|
||||||
*/
|
|
||||||
class ProjectExplorerTreeView : public QTreeView {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
ProjectExplorerTreeView(QWidget *parent);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/**
|
|
||||||
* @brief Double click event override
|
|
||||||
*
|
|
||||||
* Function that signals DoubleClickedView().
|
|
||||||
*
|
|
||||||
* FIXME: This code is the same as the code in ProjectExplorerListViewBase. Is there a way to merge these two through
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
/**
|
|
||||||
* @brief Unconditional double click signal
|
|
||||||
*
|
|
||||||
* Emits a signal when the view is double clicked but not on any particular item
|
|
||||||
*/
|
|
||||||
void double_clicked_empty_area();
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // OAK_PROJECTEXPLORERTREEVIEW_H
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 OAK_PROJECTEXPLORERUNDO_H
|
|
||||||
#define OAK_PROJECTEXPLORERUNDO_H
|
|
||||||
|
|
||||||
#include "undo/undocommand.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // OAK_PROJECTEXPLORERUNDO_H
|
|
||||||
@@ -1,573 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 "projectviewmodel.h"
|
|
||||||
#include "ui/icons/icons.h"
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QMimeData>
|
|
||||||
#include <QUrl>
|
|
||||||
|
|
||||||
#include "common/qtutils.h"
|
|
||||||
#include "core.h"
|
|
||||||
#include "node/nodeundo.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
ProjectViewModel::ProjectViewModel(QObject *parent)
|
|
||||||
: QAbstractItemModel(parent)
|
|
||||||
, project_(nullptr)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Project *ProjectViewModel::project() const
|
|
||||||
{
|
|
||||||
return project_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectViewModel::set_project(Project *p)
|
|
||||||
{
|
|
||||||
beginResetModel();
|
|
||||||
|
|
||||||
if (project_) {
|
|
||||||
disconnect_item(project_->root());
|
|
||||||
}
|
|
||||||
|
|
||||||
project_ = p;
|
|
||||||
|
|
||||||
if (project_) {
|
|
||||||
connect_item(project_->root());
|
|
||||||
}
|
|
||||||
|
|
||||||
endResetModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
QModelIndex ProjectViewModel::index(int row, int column,
|
|
||||||
const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
// I'm actually not 100% sure what this does, but it seems logical and was in the earlier code
|
|
||||||
if (!hasIndex(row, column, parent)) {
|
|
||||||
return QModelIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the parent object, we assume it's a folder since only folders can have children
|
|
||||||
Folder *item_parent = static_cast<Folder *>(get_item_object_from_index(parent));
|
|
||||||
|
|
||||||
// Return an index to this object
|
|
||||||
return createIndex(row, column, item_parent->item_child(row));
|
|
||||||
}
|
|
||||||
|
|
||||||
QModelIndex ProjectViewModel::parent(const QModelIndex &child) const
|
|
||||||
{
|
|
||||||
// Get the Item object from the index
|
|
||||||
Node *item = get_item_object_from_index(child);
|
|
||||||
|
|
||||||
// Get Item's parent object
|
|
||||||
Folder *par = item->folder();
|
|
||||||
|
|
||||||
// If the parent is the root, return an empty index
|
|
||||||
if (par == project_->root()) {
|
|
||||||
return QModelIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise return a true index to its parent
|
|
||||||
int parent_index = index_of_child(par);
|
|
||||||
|
|
||||||
// Make sure the index is valid (there's no reason it shouldn't be)
|
|
||||||
Q_ASSERT(parent_index > -1);
|
|
||||||
|
|
||||||
// Return an index to the parent
|
|
||||||
return createIndex(parent_index, 0, par);
|
|
||||||
}
|
|
||||||
|
|
||||||
int ProjectViewModel::rowCount(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
// If there's no project, there are obviously no items to show
|
|
||||||
if (project_ == nullptr) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the index is the root, return the root child count
|
|
||||||
if (parent == QModelIndex()) {
|
|
||||||
return project_->root()->item_child_count();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, the index must contain a valid pointer, so we just return its child count
|
|
||||||
return static_cast<Folder *>(get_item_object_from_index(parent))
|
|
||||||
->item_child_count();
|
|
||||||
}
|
|
||||||
|
|
||||||
int ProjectViewModel::columnCount(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
Q_UNUSED(parent)
|
|
||||||
|
|
||||||
// Not strictly necessary, but a decent visual cue that there's no project currently active
|
|
||||||
if (project_ == nullptr) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return k_column_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant ProjectViewModel::data(const QModelIndex &index, int role) const
|
|
||||||
{
|
|
||||||
Node *internal_item = get_item_object_from_index(index);
|
|
||||||
|
|
||||||
ColumnType column_type = static_cast<ColumnType>(index.column());
|
|
||||||
|
|
||||||
switch (role) {
|
|
||||||
case Qt::DisplayRole:
|
|
||||||
case k_inner_text_role: {
|
|
||||||
// Standard text role
|
|
||||||
|
|
||||||
switch (column_type) {
|
|
||||||
case k_name:
|
|
||||||
return internal_item->get_label();
|
|
||||||
case k_duration:
|
|
||||||
return internal_item->data(Node::duration);
|
|
||||||
case k_rate:
|
|
||||||
return internal_item->data(Node::frequency_rate);
|
|
||||||
case k_last_modified:
|
|
||||||
case k_created_time: {
|
|
||||||
qint64 using_time =
|
|
||||||
(column_type == k_last_modified) ?
|
|
||||||
internal_item->data(Node::modified_time).toLongLong() :
|
|
||||||
internal_item->data(Node::created_time).toLongLong();
|
|
||||||
|
|
||||||
if (using_time == 0) {
|
|
||||||
// 0 is the null value, return nothing
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant ret;
|
|
||||||
|
|
||||||
if (role == k_inner_text_role) {
|
|
||||||
// Use time value directly for correct sorting
|
|
||||||
ret = using_time;
|
|
||||||
} else {
|
|
||||||
// Display role, format to a human readable string
|
|
||||||
ret = QtUtils::get_formatted_date_time(
|
|
||||||
QDateTime::fromSecsSinceEpoch(using_time));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
case k_column_count:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
case Qt::EditRole:
|
|
||||||
if (column_type == k_name) {
|
|
||||||
return internal_item->get_label();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Qt::DecorationRole:
|
|
||||||
// If this is the first column, return the Item's icon
|
|
||||||
if (column_type == k_name) {
|
|
||||||
return icon::from_name(internal_item->data(Node::icon).toString());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Qt::ToolTipRole:
|
|
||||||
return internal_item->data(Node::tooltip);
|
|
||||||
}
|
|
||||||
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant ProjectViewModel::headerData(int section, Qt::Orientation orientation,
|
|
||||||
int role) const
|
|
||||||
{
|
|
||||||
// Check if we need text data (DisplayRole) and orientation is horizontal
|
|
||||||
// FIXME I'm not 100% sure what happens if the orientation is vertical/if that check is necessary
|
|
||||||
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
|
|
||||||
ColumnType column_type = static_cast<ColumnType>(section);
|
|
||||||
|
|
||||||
// Return the name based on the column's current type
|
|
||||||
switch (column_type) {
|
|
||||||
case k_name:
|
|
||||||
return tr("Name");
|
|
||||||
case k_duration:
|
|
||||||
return tr("Duration");
|
|
||||||
case k_rate:
|
|
||||||
return tr("Rate");
|
|
||||||
case k_last_modified:
|
|
||||||
return tr("Modified");
|
|
||||||
case k_created_time:
|
|
||||||
return tr("Created");
|
|
||||||
case k_column_count:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return QAbstractItemModel::headerData(section, orientation, role);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ProjectViewModel::hasChildren(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
// If it's a folder, we always return TRUE in order to always show the "expand triangle" icon,
|
|
||||||
// even when there are no "physical" children
|
|
||||||
Node *item = get_item_object_from_index(parent);
|
|
||||||
|
|
||||||
return dynamic_cast<Folder *>(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ProjectViewModel::setData(const QModelIndex &index, const QVariant &value,
|
|
||||||
int role)
|
|
||||||
{
|
|
||||||
// The name is editable
|
|
||||||
if (index.isValid() && index.column() == k_name && role == Qt::EditRole) {
|
|
||||||
Node *item = get_item_object_from_index(index);
|
|
||||||
|
|
||||||
QString new_name = value.toString();
|
|
||||||
|
|
||||||
if (!new_name.isEmpty()) {
|
|
||||||
NodeRenameCommand *nrc = new NodeRenameCommand();
|
|
||||||
|
|
||||||
nrc->add_node(item, value.toString());
|
|
||||||
|
|
||||||
Core::instance()->undo_stack()->push(
|
|
||||||
nrc, tr("Renamed Item \"%1\" to \"%2\"")
|
|
||||||
.arg(item->get_label(), new_name));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ProjectViewModel::canFetchMore(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
// Use the same hack that always returns true with folders so the expand triangle is always visible
|
|
||||||
return hasChildren(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
Qt::ItemFlags ProjectViewModel::flags(const QModelIndex &index) const
|
|
||||||
{
|
|
||||||
if (!index.isValid()) {
|
|
||||||
// Allow dropping files from external sources
|
|
||||||
return Qt::ItemIsDropEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
Qt::ItemFlags f = Qt::ItemIsDragEnabled | QAbstractItemModel::flags(index);
|
|
||||||
|
|
||||||
if (dynamic_cast<Folder *>(get_item_object_from_index(index))) {
|
|
||||||
f |= Qt::ItemIsDropEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the column is the kName column, that means it's editable
|
|
||||||
if (index.column() == k_name) {
|
|
||||||
f |= Qt::ItemIsEditable;
|
|
||||||
}
|
|
||||||
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList ProjectViewModel::mimeTypes() const
|
|
||||||
{
|
|
||||||
// Allow data from this model and a file list from external sources
|
|
||||||
return { Project::k_item_mime_type, QStringLiteral("text/uri-list") };
|
|
||||||
}
|
|
||||||
|
|
||||||
QMimeData *ProjectViewModel::mimeData(const QModelIndexList &indexes) const
|
|
||||||
{
|
|
||||||
// Compliance with Qt standard
|
|
||||||
if (indexes.isEmpty()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode mime data for the rows/items that were dragged
|
|
||||||
QMimeData *data = new QMimeData();
|
|
||||||
|
|
||||||
// Use QDataStream to stream the item data into a byte array
|
|
||||||
QByteArray encoded_data;
|
|
||||||
QDataStream stream(&encoded_data, QIODevice::WriteOnly);
|
|
||||||
|
|
||||||
// The indexes list includes indexes for each column which we don't use. To make sure each row only gets sent *once*,
|
|
||||||
// we keep a list of dragged items
|
|
||||||
QVector<void *> dragged_items;
|
|
||||||
|
|
||||||
foreach (QModelIndex index, indexes) {
|
|
||||||
if (index.isValid()) {
|
|
||||||
// Check if we've dragged this item before
|
|
||||||
if (!dragged_items.contains(index.internalPointer())) {
|
|
||||||
// If not, add it to the stream (and also keep track of it in the vector)
|
|
||||||
Node *item = static_cast<Node *>(index.internalPointer());
|
|
||||||
QVector<Track::Reference> streams;
|
|
||||||
|
|
||||||
if (ViewerOutput *footage =
|
|
||||||
dynamic_cast<ViewerOutput *>(item)) {
|
|
||||||
streams = footage->get_enabled_streams_as_references();
|
|
||||||
}
|
|
||||||
|
|
||||||
stream << streams << reinterpret_cast<quintptr>(item);
|
|
||||||
|
|
||||||
dragged_items.append(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set byte array as the mime data and return the mime data
|
|
||||||
data->setData(Project::k_item_mime_type, encoded_data);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ProjectViewModel::dropMimeData(const QMimeData *data,
|
|
||||||
Qt::DropAction action, int row, int column,
|
|
||||||
const QModelIndex &drop)
|
|
||||||
{
|
|
||||||
// Default recommended checks from https://doc.qt.io/qt-5/model-view-programming.html#using-drag-and-drop-with-item-views
|
|
||||||
if (!canDropMimeData(data, action, row, column, drop)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action == Qt::IgnoreAction) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Probe mime data for its format
|
|
||||||
QStringList mime_formats = data->formats();
|
|
||||||
|
|
||||||
if (mime_formats.contains(Project::k_item_mime_type)) {
|
|
||||||
// Data is drag/drop data from this model
|
|
||||||
QByteArray model_data = data->data(Project::k_item_mime_type);
|
|
||||||
|
|
||||||
// Use QDataStream to deserialize the data
|
|
||||||
QDataStream stream(&model_data, QIODevice::ReadOnly);
|
|
||||||
|
|
||||||
// Get the Item object that the items were dropped on
|
|
||||||
Folder *drop_location =
|
|
||||||
dynamic_cast<Folder *>(get_item_object_from_index(drop));
|
|
||||||
|
|
||||||
// If this is not a folder, we cannot drop these items here
|
|
||||||
if (!drop_location) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Variables to deserialize into
|
|
||||||
quintptr item_ptr;
|
|
||||||
QList<Track::Reference> streams;
|
|
||||||
|
|
||||||
// Loop through all data
|
|
||||||
MultiUndoCommand *move_command = new MultiUndoCommand();
|
|
||||||
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
while (!stream.atEnd()) {
|
|
||||||
stream >> streams >> item_ptr;
|
|
||||||
|
|
||||||
Node *item = reinterpret_cast<Node *>(item_ptr);
|
|
||||||
|
|
||||||
// Check if Item is already the drop location or if its parent is the drop location, in which case this is a
|
|
||||||
// no-op
|
|
||||||
|
|
||||||
if (item != drop_location && item->folder() != drop_location &&
|
|
||||||
(!dynamic_cast<Folder *>(item) ||
|
|
||||||
!item_is_parent_of_child(static_cast<Folder *>(item),
|
|
||||||
drop_location))) {
|
|
||||||
move_command->add_child(new NodeEdgeRemoveCommand(
|
|
||||||
item,
|
|
||||||
NodeInput(item->folder(), Folder::k_child_input,
|
|
||||||
item->folder()->index_of_child_in_array(item))));
|
|
||||||
move_command->add_child(
|
|
||||||
new FolderAddChild(drop_location, item));
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Core::instance()->undo_stack()->push(move_command,
|
|
||||||
tr("Move %1 Item(s)").arg(count));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} else if (mime_formats.contains(QStringLiteral("text/uri-list"))) {
|
|
||||||
// We received a list of files
|
|
||||||
QByteArray file_data = data->data(QStringLiteral("text/uri-list"));
|
|
||||||
|
|
||||||
// Use text stream to parse (just an easy way of sifting through line breaks
|
|
||||||
QTextStream stream(&file_data);
|
|
||||||
|
|
||||||
// Convert QByteArray to QStringList (which Core takes for importing)
|
|
||||||
QStringList urls;
|
|
||||||
while (!stream.atEnd()) {
|
|
||||||
QUrl url = stream.readLine();
|
|
||||||
|
|
||||||
if (!url.isEmpty()) {
|
|
||||||
urls.append(url.toLocalFile());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get folder dropped onto
|
|
||||||
Node *drop_item = get_item_object_from_index(drop);
|
|
||||||
|
|
||||||
// If we didn't drop onto an item, find the nearest parent folder (should eventually terminate at root either way)
|
|
||||||
if (!dynamic_cast<Folder *>(drop_item)) {
|
|
||||||
drop_item = drop_item->folder();
|
|
||||||
|
|
||||||
if (!drop_item) {
|
|
||||||
// Failed to find folder to place this in
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger an import
|
|
||||||
Core::instance()->import_files(urls, static_cast<Folder *>(drop_item));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ProjectViewModel::index_of_child(Node *item) const
|
|
||||||
{
|
|
||||||
// Find parent's index within its own parent
|
|
||||||
Folder *parent = item->folder();
|
|
||||||
|
|
||||||
if (parent) {
|
|
||||||
return parent->index_of_child(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Node *ProjectViewModel::get_item_object_from_index(const QModelIndex &index) const
|
|
||||||
{
|
|
||||||
if (index.isValid()) {
|
|
||||||
return static_cast<Node *>(index.internalPointer());
|
|
||||||
}
|
|
||||||
|
|
||||||
return project_ ? project_->root() : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ProjectViewModel::item_is_parent_of_child(Folder *parent, Node *child) const
|
|
||||||
{
|
|
||||||
// Loop through parent hierarchy checking if `parent` is one of its parents
|
|
||||||
do {
|
|
||||||
child = child->folder();
|
|
||||||
|
|
||||||
if (parent == child) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} while (child != nullptr);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectViewModel::connect_item(Node *n)
|
|
||||||
{
|
|
||||||
connect(n, &Node::label_changed, this, &ProjectViewModel::item_renamed);
|
|
||||||
|
|
||||||
Folder *f = dynamic_cast<Folder *>(n);
|
|
||||||
if (f) {
|
|
||||||
connect(f, &Folder::begin_insert_item, this,
|
|
||||||
&ProjectViewModel::folder_begin_insert_item);
|
|
||||||
connect(f, &Folder::end_insert_item, this,
|
|
||||||
&ProjectViewModel::folder_end_insert_item);
|
|
||||||
connect(f, &Folder::begin_remove_item, this,
|
|
||||||
&ProjectViewModel::folder_begin_remove_item);
|
|
||||||
connect(f, &Folder::end_remove_item, this,
|
|
||||||
&ProjectViewModel::folder_end_remove_item);
|
|
||||||
|
|
||||||
foreach (Node *c, f->children()) {
|
|
||||||
connect_item(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectViewModel::disconnect_item(Node *n)
|
|
||||||
{
|
|
||||||
disconnect(n, &Node::label_changed, this, &ProjectViewModel::item_renamed);
|
|
||||||
|
|
||||||
Folder *f = dynamic_cast<Folder *>(n);
|
|
||||||
if (f) {
|
|
||||||
disconnect(f, &Folder::begin_insert_item, this,
|
|
||||||
&ProjectViewModel::folder_begin_insert_item);
|
|
||||||
disconnect(f, &Folder::end_insert_item, this,
|
|
||||||
&ProjectViewModel::folder_end_insert_item);
|
|
||||||
disconnect(f, &Folder::begin_remove_item, this,
|
|
||||||
&ProjectViewModel::folder_begin_remove_item);
|
|
||||||
disconnect(f, &Folder::end_remove_item, this,
|
|
||||||
&ProjectViewModel::folder_end_remove_item);
|
|
||||||
|
|
||||||
foreach (Node *c, f->children()) {
|
|
||||||
disconnect_item(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectViewModel::folder_begin_insert_item(Node *n, int insert_index)
|
|
||||||
{
|
|
||||||
Folder *folder = static_cast<Folder *>(sender());
|
|
||||||
|
|
||||||
connect_item(n);
|
|
||||||
|
|
||||||
QModelIndex index;
|
|
||||||
|
|
||||||
if (folder != project_->root()) {
|
|
||||||
index = create_index_from_item(folder);
|
|
||||||
}
|
|
||||||
|
|
||||||
beginInsertRows(index, insert_index, insert_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectViewModel::folder_end_insert_item()
|
|
||||||
{
|
|
||||||
endInsertRows();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectViewModel::folder_begin_remove_item(Node *n, int child_index)
|
|
||||||
{
|
|
||||||
Folder *folder = static_cast<Folder *>(sender());
|
|
||||||
|
|
||||||
disconnect_item(n);
|
|
||||||
|
|
||||||
QModelIndex index;
|
|
||||||
|
|
||||||
if (folder != project_->root()) {
|
|
||||||
index = create_index_from_item(folder);
|
|
||||||
}
|
|
||||||
|
|
||||||
beginRemoveRows(index, child_index, child_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectViewModel::folder_end_remove_item()
|
|
||||||
{
|
|
||||||
endRemoveRows();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectViewModel::item_renamed()
|
|
||||||
{
|
|
||||||
Node *item = static_cast<Node *>(sender());
|
|
||||||
|
|
||||||
QModelIndex index = create_index_from_item(item);
|
|
||||||
|
|
||||||
emit dataChanged(index, index, { Qt::DisplayRole, Qt::EditRole });
|
|
||||||
}
|
|
||||||
|
|
||||||
QModelIndex ProjectViewModel::create_index_from_item(Node *item, int column)
|
|
||||||
{
|
|
||||||
return createIndex(index_of_child(item), column, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
/***
|
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
|
||||||
Copyright (C) 2022 Olive Team
|
|
||||||
Modifications Copyright (C) 2025 mikesolar
|
|
||||||
|
|
||||||
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 OAK_VIEWMODEL_H
|
|
||||||
#define OAK_VIEWMODEL_H
|
|
||||||
|
|
||||||
#include <QAbstractItemModel>
|
|
||||||
|
|
||||||
#include "node/block/block.h"
|
|
||||||
#include "node/project.h"
|
|
||||||
#include "undo/undocommand.h"
|
|
||||||
|
|
||||||
namespace olive
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief An adapter that interprets the data in a Project into a Qt item model for usage in ViewModel Views.
|
|
||||||
*
|
|
||||||
* Assuming a Project is currently "open" (i.e. the Project is connected to a ProjectExplorer/ProjectPanel through
|
|
||||||
* a ProjectViewModel), it may be better to make modifications (e.g. additions/removals/renames) through the
|
|
||||||
* ProjectViewModel so that the views can be efficiently and correctly updated. ProjectViewModel contains several
|
|
||||||
* "wrapper" functions for Project and Item functions that also signal any connected views to update accordingly.
|
|
||||||
*/
|
|
||||||
class ProjectViewModel : public QAbstractItemModel {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
enum ColumnType {
|
|
||||||
/// Media name
|
|
||||||
k_name,
|
|
||||||
|
|
||||||
/// Media duration
|
|
||||||
k_duration,
|
|
||||||
|
|
||||||
/// Media rate (frame rate for video, sample rate for audio)
|
|
||||||
k_rate,
|
|
||||||
|
|
||||||
/// Last modified time (for footage/files)
|
|
||||||
k_last_modified,
|
|
||||||
|
|
||||||
/// Creation time (for footage/files)
|
|
||||||
k_created_time,
|
|
||||||
|
|
||||||
/// Count
|
|
||||||
k_column_count
|
|
||||||
};
|
|
||||||
|
|
||||||
static const int k_inner_text_role = Qt::UserRole + 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief ProjectViewModel Constructor
|
|
||||||
*
|
|
||||||
* @param parent
|
|
||||||
* Parent object for memory handling
|
|
||||||
*/
|
|
||||||
ProjectViewModel(QObject *parent);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get currently active project
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
* Currently active project or nullptr if there is none
|
|
||||||
*/
|
|
||||||
Project *project() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set the project to adapt
|
|
||||||
*
|
|
||||||
* Any views attached to this model will get updated by this function.
|
|
||||||
*
|
|
||||||
* @param p
|
|
||||||
*
|
|
||||||
* Project to adapt, can be set to nullptr to "close" the project (will show an empty model that cannot be modified)
|
|
||||||
*/
|
|
||||||
void set_project(Project *p);
|
|
||||||
|
|
||||||
/** Compulsory Qt QAbstractItemModel overrides */
|
|
||||||
virtual QModelIndex
|
|
||||||
index(int row, int column,
|
|
||||||
const QModelIndex &parent = QModelIndex()) const override;
|
|
||||||
virtual QModelIndex parent(const QModelIndex &child) const override;
|
|
||||||
virtual int
|
|
||||||
rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
|
||||||
virtual int
|
|
||||||
columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
|
||||||
virtual QVariant data(const QModelIndex &index,
|
|
||||||
int role = Qt::DisplayRole) const override;
|
|
||||||
|
|
||||||
/** Optional Qt QAbstractItemModel overrides */
|
|
||||||
virtual QVariant headerData(int section, Qt::Orientation orientation,
|
|
||||||
int role = Qt::DisplayRole) const override;
|
|
||||||
virtual bool
|
|
||||||
hasChildren(const QModelIndex &parent = QModelIndex()) const override;
|
|
||||||
virtual bool setData(const QModelIndex &index, const QVariant &value,
|
|
||||||
int role = Qt::EditRole) override;
|
|
||||||
virtual bool canFetchMore(const QModelIndex &parent) const override;
|
|
||||||
|
|
||||||
/** Drag and drop support */
|
|
||||||
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
|
|
||||||
virtual QStringList mimeTypes() const override;
|
|
||||||
virtual QMimeData *mimeData(const QModelIndexList &indexes) const override;
|
|
||||||
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action,
|
|
||||||
int row, int column,
|
|
||||||
const QModelIndex &parent) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Convenience function for creating QModelIndexes from an Item object
|
|
||||||
*/
|
|
||||||
QModelIndex create_index_from_item(Node *item, int column = 0);
|
|
||||||
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* @brief Retrieve the index of `item` in its parent
|
|
||||||
*
|
|
||||||
* This function will return the index of a specified item in its parent according to whichever sorting algorithm
|
|
||||||
* is currently active.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
* Index of the specified item, or -1 if the item is root (in which case it has no parent).
|
|
||||||
*/
|
|
||||||
int index_of_child(Node *item) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Retrieves the Item object from a given index
|
|
||||||
*
|
|
||||||
* A convenience function for retrieving Item objects. If the index is not valid, this returns the root Item.
|
|
||||||
*/
|
|
||||||
Node *get_item_object_from_index(const QModelIndex &index) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Check if an Item is a parent of a Child
|
|
||||||
*
|
|
||||||
* Checks entire "parent hierarchy" of `child` to see if `parent` is one of its parents.
|
|
||||||
*/
|
|
||||||
bool item_is_parent_of_child(Folder *parent, Node *child) const;
|
|
||||||
|
|
||||||
void connect_item(Node *n);
|
|
||||||
|
|
||||||
void disconnect_item(Node *n);
|
|
||||||
|
|
||||||
Project *project_;
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void folder_begin_insert_item(Node *n, int insert_index);
|
|
||||||
|
|
||||||
void folder_end_insert_item();
|
|
||||||
|
|
||||||
void folder_begin_remove_item(Node *n, int child_index);
|
|
||||||
|
|
||||||
void folder_end_remove_item();
|
|
||||||
|
|
||||||
void item_renamed();
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // OAK_VIEWMODEL_H
|
|
||||||
@@ -39,9 +39,10 @@ jobs:
|
|||||||
ninja-build pkg-config nasm \
|
ninja-build pkg-config nasm \
|
||||||
qt6-base-dev qt6-base-dev-tools qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \
|
qt6-base-dev qt6-base-dev-tools qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \
|
||||||
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libavfilter-dev \
|
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libavfilter-dev \
|
||||||
|
ffmpeg \
|
||||||
libopencolorio-dev libopenimageio-dev libopenexr-dev libexpat1-dev \
|
libopencolorio-dev libopenimageio-dev libopenexr-dev libexpat1-dev \
|
||||||
portaudio19-dev libgl1-mesa-dev libgl1-mesa-dri libxkbcommon-dev ccache \
|
portaudio19-dev libgl1-mesa-dev libgl1-mesa-dri libxkbcommon-dev ccache \
|
||||||
xvfb libvulkan-dev mesa-vulkan-drivers vulkan-tools
|
xvfb libvulkan-dev mesa-vulkan-drivers vulkan-tools libshaderc-dev
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# macOS dependencies
|
# macOS dependencies
|
||||||
@@ -134,8 +135,9 @@ jobs:
|
|||||||
shell: msys2 {0}
|
shell: msys2 {0}
|
||||||
run: |
|
run: |
|
||||||
echo "OTIO_LOCATION=${PWD}/otio-install" >> "$GITHUB_ENV"
|
echo "OTIO_LOCATION=${PWD}/otio-install" >> "$GITHUB_ENV"
|
||||||
# No rpath on Windows: test executables need the OTIO DLLs on PATH
|
# No rpath on Windows: test executables need the OTIO DLLs on PATH.
|
||||||
echo "$(cygpath -w "${PWD}/otio-install/bin")" >> "$GITHUB_PATH"
|
# OTIO's MinGW install puts the DLLs in lib/, not bin/.
|
||||||
|
echo "$(cygpath -w "${PWD}/otio-install/lib")" >> "$GITHUB_PATH"
|
||||||
|
|
||||||
- name: Build OpenTimelineIO (Windows)
|
- name: Build OpenTimelineIO (Windows)
|
||||||
if: runner.os == 'Windows' && steps.otio-cache-win.outputs.cache-hit != 'true'
|
if: runner.os == 'Windows' && steps.otio-cache-win.outputs.cache-hit != 'true'
|
||||||
@@ -241,6 +243,31 @@ jobs:
|
|||||||
echo "OAK_OFX_PLUGIN_PATH=$PLUGIN_DIR" >> "$GITHUB_ENV"
|
echo "OAK_OFX_PLUGIN_PATH=$PLUGIN_DIR" >> "$GITHUB_ENV"
|
||||||
echo "OAK_OFX_PLUGIN_ID=net.sf.openfx.ChromaKeyerPlugin" >> "$GITHUB_ENV"
|
echo "OAK_OFX_PLUGIN_ID=net.sf.openfx.ChromaKeyerPlugin" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
# The Windows runners have no GPU: WGL falls back to the GDI software
|
||||||
|
# OpenGL 1.1 implementation and every render worker dies calling 3.2
|
||||||
|
# core entry points. Drop Mesa's llvmpipe opengl32.dll next to every
|
||||||
|
# binary that creates a GL context (the application directory wins the
|
||||||
|
# DLL search order over System32).
|
||||||
|
- name: Install Mesa software OpenGL (Windows)
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
shell: msys2 {0}
|
||||||
|
run: |
|
||||||
|
curl -sSL -o /tmp/mesa.7z \
|
||||||
|
https://github.com/pal1000/mesa-dist-win/releases/download/26.1.5/mesa3d-26.1.5-release-mingw.7z
|
||||||
|
"/c/Program Files/7-Zip/7z.exe" x /tmp/mesa.7z -o/tmp/mesa > /dev/null
|
||||||
|
ls -la /tmp/mesa /tmp/mesa/x64
|
||||||
|
for d in build/engine build/worker build/cli build/tests/gtest build/app; do
|
||||||
|
if [ -d "$d" ]; then
|
||||||
|
cp /tmp/mesa/x64/*.dll "$d/"
|
||||||
|
# Qt ignores an app-local opengl32.dll for WGL (it always binds
|
||||||
|
# the System32 one); its documented software-GL override is
|
||||||
|
# opengl32sw.dll, loaded when QT_OPENGL=software.
|
||||||
|
cp "$d/opengl32.dll" "$d/opengl32sw.dll"
|
||||||
|
ls "$d"/opengl32sw.dll "$d"/libgallium_wgl.dll
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "QT_OPENGL=software" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Run all tests (including previously environment-gated OFX tests)
|
# Run all tests (including previously environment-gated OFX tests)
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
@@ -250,6 +277,24 @@ jobs:
|
|||||||
QT_QPA_PLATFORM: offscreen
|
QT_QPA_PLATFORM: offscreen
|
||||||
run: xvfb-run -a ctest --test-dir build --output-on-failure -C ${{ env.CMAKE_BUILD_TYPE }}
|
run: xvfb-run -a ctest --test-dir build --output-on-failure -C ${{ env.CMAKE_BUILD_TYPE }}
|
||||||
|
|
||||||
|
# Diagnostic: if the Linux test run failed, re-run the suspect gtest
|
||||||
|
# filter under gdb to capture a native backtrace of the segfault.
|
||||||
|
# Also break on dlclose to see who unloads the render backend library
|
||||||
|
# before RenderManager's destructor calls into it.
|
||||||
|
- name: gtest segfault backtrace (Linux)
|
||||||
|
if: runner.os == 'Linux' && failure()
|
||||||
|
env:
|
||||||
|
QT_QPA_PLATFORM: offscreen
|
||||||
|
run: |
|
||||||
|
sudo apt-get update && sudo apt-get install -y gdb
|
||||||
|
xvfb-run -a gdb -batch \
|
||||||
|
-ex run \
|
||||||
|
-ex 'bt full' \
|
||||||
|
-ex 'info proc mappings' \
|
||||||
|
-ex 'info symbol $pc' \
|
||||||
|
--args ./build/tests/gtest/olive-gtest \
|
||||||
|
--gtest_filter='MainWindow.ConstructsOffscreenWithPanelsAndMenus' || true
|
||||||
|
|
||||||
- name: Test (macOS)
|
- name: Test (macOS)
|
||||||
if: runner.os == 'macOS'
|
if: runner.os == 'macOS'
|
||||||
run: ctest --test-dir build --output-on-failure -C ${{ env.CMAKE_BUILD_TYPE }}
|
run: ctest --test-dir build --output-on-failure -C ${{ env.CMAKE_BUILD_TYPE }}
|
||||||
@@ -259,6 +304,21 @@ jobs:
|
|||||||
shell: msys2 {0}
|
shell: msys2 {0}
|
||||||
run: ctest --test-dir build --output-on-failure -C ${{ env.CMAKE_BUILD_TYPE }}
|
run: ctest --test-dir build --output-on-failure -C ${{ env.CMAKE_BUILD_TYPE }}
|
||||||
|
|
||||||
|
# Diagnostic: if the Windows test run failed, show which DLLs the
|
||||||
|
# engine test executables cannot resolve (0xc0000135).
|
||||||
|
- name: Missing DLL diagnosis (Windows)
|
||||||
|
if: runner.os == 'Windows' && failure()
|
||||||
|
shell: msys2 {0}
|
||||||
|
run: |
|
||||||
|
echo "PATH=$PATH"
|
||||||
|
ldd build/engine/oakengine_ipc_test.exe | grep -i "not found" || true
|
||||||
|
objdump -p build/engine/oakengine_ipc_test.exe | grep "DLL Name" | sort -u
|
||||||
|
ls otio-install/lib otio-install/bin 2>/dev/null || true
|
||||||
|
echo "=== render worker stderr logs ==="
|
||||||
|
for f in "$TEMP"/oak-render-worker-*.stderr.log "$TMP"/oak-render-worker-*.stderr.log /tmp/oak-render-worker-*.stderr.log; do
|
||||||
|
[ -f "$f" ] && { echo "--- $f"; tail -50 "$f"; }
|
||||||
|
done
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Filtered gtest runs for clearer CI output
|
# Filtered gtest runs for clearer CI output
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|||||||
+13
-3
@@ -255,11 +255,19 @@ endif()
|
|||||||
|
|
||||||
# OTIO's macOS dylibs use @loader_path install names: every binary that
|
# OTIO's macOS dylibs use @loader_path install names: every binary that
|
||||||
# (transitively) links them needs a copy of the dylibs next to itself.
|
# (transitively) links them needs a copy of the dylibs next to itself.
|
||||||
|
# Windows has no rpath either, so its DLLs must also sit next to each
|
||||||
|
# executable (relying on PATH is fragile in CI shells).
|
||||||
# Call oak_copy_otio_runtime(<target>) for each executable/shared library.
|
# Call oak_copy_otio_runtime(<target>) for each executable/shared library.
|
||||||
if (OAK_BUNDLE_OTIO AND APPLE AND OTIO_LIBRARY_DIR)
|
if (OAK_BUNDLE_OTIO AND (APPLE OR WIN32) AND OTIO_LIBRARY_DIR)
|
||||||
|
if (APPLE)
|
||||||
file(GLOB OAK_OTIO_DYLIBS
|
file(GLOB OAK_OTIO_DYLIBS
|
||||||
"${OTIO_LIBRARY_DIR}/libopentimelineio*.dylib"
|
"${OTIO_LIBRARY_DIR}/libopentimelineio*.dylib"
|
||||||
"${OTIO_LIBRARY_DIR}/libopentime*.dylib")
|
"${OTIO_LIBRARY_DIR}/libopentime*.dylib")
|
||||||
|
else()
|
||||||
|
file(GLOB OAK_OTIO_DYLIBS
|
||||||
|
"${OTIO_LIBRARY_DIR}/libopentimelineio*.dll"
|
||||||
|
"${OTIO_LIBRARY_DIR}/libopentime*.dll")
|
||||||
|
endif()
|
||||||
function(oak_copy_otio_runtime target)
|
function(oak_copy_otio_runtime target)
|
||||||
add_custom_command(TARGET ${target} POST_BUILD
|
add_custom_command(TARGET ${target} POST_BUILD
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
@@ -353,8 +361,10 @@ add_subdirectory(third_party/openfx/HostSupport)
|
|||||||
add_subdirectory(engine)
|
add_subdirectory(engine)
|
||||||
add_subdirectory(cli)
|
add_subdirectory(cli)
|
||||||
|
|
||||||
# Everything above the engine library links against it
|
# Internal consumers (app, tests) link oakengine-obj directly instead of the
|
||||||
list(APPEND OLIVE_LIBRARIES oakengine)
|
# oakengine shared library (see app/CMakeLists.txt). Linking both breaks the
|
||||||
|
# Windows build: the DLL import library re-defines every symbol already
|
||||||
|
# provided by the object files (multiple definition errors at link time).
|
||||||
|
|
||||||
add_subdirectory(app)
|
add_subdirectory(app)
|
||||||
add_subdirectory(worker)
|
add_subdirectory(worker)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace olive {
|
|||||||
* Replaces engine's olive::debug_handler so oak-editor doesn't import
|
* Replaces engine's olive::debug_handler so oak-editor doesn't import
|
||||||
* that symbol. Only used in main.cpp's qInstallMessageHandler.
|
* that symbol. Only used in main.cpp's qInstallMessageHandler.
|
||||||
*/
|
*/
|
||||||
static void debug_handler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
[[maybe_unused]] [[maybe_unused]] static void debug_handler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
||||||
{
|
{
|
||||||
// Suppress noisy warnings from Qt's QXcbIntegration
|
// Suppress noisy warnings from Qt's QXcbIntegration
|
||||||
if (type == QtWarningMsg && msg.contains("QXcbIntegration")) {
|
if (type == QtWarningMsg && msg.contains("QXcbIntegration")) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef OAK_HTMLAPP_H
|
#ifndef OAK_HTMLAPP_H
|
||||||
#define OAK_HTML_H
|
#define OAK_HTMLAPP_H
|
||||||
|
|
||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
#include <QTextFragment>
|
#include <QTextFragment>
|
||||||
|
|||||||
+2
-2
@@ -98,7 +98,7 @@ Core::Core(const OakEngineAppParams *params)
|
|||||||
if (params) {
|
if (params) {
|
||||||
oakengine_app_create(params);
|
oakengine_app_create(params);
|
||||||
} else {
|
} else {
|
||||||
static const OakEngineAppParams default_params = {0};
|
static const OakEngineAppParams default_params = {};
|
||||||
oakengine_app_create(&default_params);
|
oakengine_app_create(&default_params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -773,7 +773,7 @@ void Core::start_gui(bool full_screen)
|
|||||||
main_window_ = new MainWindow();
|
main_window_ = new MainWindow();
|
||||||
|
|
||||||
// Route engine notifications to the UI
|
// Route engine notifications to the UI
|
||||||
connect(this, &Core::tool_changed, this, [this](const Tool::Item &) {});
|
connect(this, &Core::tool_changed, this, [](const Tool::Item &) {});
|
||||||
// Status-bar and lifecycle notifications are handled through the facade
|
// Status-bar and lifecycle notifications are handled through the facade
|
||||||
// (oakengine_app_show_status_message, oakengine_app_clear_status_message)
|
// (oakengine_app_show_status_message, oakengine_app_clear_status_message)
|
||||||
// which the engine forwards through registered callbacks. The main window
|
// which the engine forwards through registered callbacks. The main window
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ namespace
|
|||||||
|
|
||||||
// pix_fmt string (e.g. "yuv420p") to its index in the codec's supported
|
// pix_fmt string (e.g. "yuv420p") to its index in the codec's supported
|
||||||
// list; 0 (the codec's preferred format) when absent.
|
// list; 0 (the codec's preferred format) when absent.
|
||||||
int pix_fmt_index(int codec, const QString &pix_fmt)
|
[[maybe_unused]] [[maybe_unused]] int pix_fmt_index(int codec, const QString &pix_fmt)
|
||||||
{
|
{
|
||||||
if (pix_fmt.isEmpty()) {
|
if (pix_fmt.isEmpty()) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -87,7 +87,7 @@ Rational export_viewer_length(const OakEngineNode *viewer)
|
|||||||
|
|
||||||
// OakEngineEncodingParams (assembled by the dialog) -> facade POD. One-to-one with
|
// OakEngineEncodingParams (assembled by the dialog) -> facade POD. One-to-one with
|
||||||
// oak_export_options_ex; see oakengine/exporter.h for the field docs.
|
// oak_export_options_ex; see oakengine/exporter.h for the field docs.
|
||||||
oak_export_options_ex params_to_ex(const OakEngineEncodingParams *p)
|
[[maybe_unused]] [[maybe_unused]] oak_export_options_ex params_to_ex(const OakEngineEncodingParams *p)
|
||||||
{
|
{
|
||||||
oak_export_options_ex o = {};
|
oak_export_options_ex o = {};
|
||||||
|
|
||||||
@@ -939,7 +939,6 @@ OakEngineEncodingParams *ExportDialog::generate_params() const
|
|||||||
const int vh = static_cast<int>(video_tab_->height_slider()->get_value());
|
const int vh = static_cast<int>(video_tab_->height_slider()->get_value());
|
||||||
const Rational tb = get_selected_timebase();
|
const Rational tb = get_selected_timebase();
|
||||||
const int pix_fmt = video_tab_->pixel_format_field()->get_pixel_format();
|
const int pix_fmt = video_tab_->pixel_format_field()->get_pixel_format();
|
||||||
const int ch_count = oakengine_video_params_internal_channel_count();
|
|
||||||
const Rational par = video_tab_->pixel_aspect_combobox()->get_pixel_aspect_ratio();
|
const Rational par = video_tab_->pixel_aspect_combobox()->get_pixel_aspect_ratio();
|
||||||
const int interlace = video_tab_->interlaced_combobox()->get_interlace_mode();
|
const int interlace = video_tab_->interlaced_combobox()->get_interlace_mode();
|
||||||
|
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ void MarkerPropertiesDialog::accept()
|
|||||||
// Batch-set properties via facade (one undoable command)
|
// Batch-set properties via facade (one undoable command)
|
||||||
{
|
{
|
||||||
QVector<OakEngineMarker *> oak_markers;
|
QVector<OakEngineMarker *> oak_markers;
|
||||||
foreach (OakEngineMarker *m, markers_) {
|
for (OakEngineMarker *m : markers_) {
|
||||||
oak_markers.append(m);
|
oak_markers.append(m);
|
||||||
}
|
}
|
||||||
int color = color_menu_->get_selected_color();
|
int color = color_menu_->get_selected_color();
|
||||||
|
|||||||
+8
-4
@@ -228,7 +228,7 @@ int main(int argc, char *argv[])
|
|||||||
"main", "Decompress project file (No GUI)"));
|
"main", "Decompress project file (No GUI)"));
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
auto console_option = parser.AddOption(
|
auto console_option = parser.add_option(
|
||||||
{ QStringLiteral("c"), QStringLiteral("-console") },
|
{ QStringLiteral("c"), QStringLiteral("-console") },
|
||||||
QCoreApplication::translate("main", "Launch with debug console"));
|
QCoreApplication::translate("main", "Launch with debug console"));
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
@@ -333,15 +333,19 @@ int main(int argc, char *argv[])
|
|||||||
// so far.
|
// so far.
|
||||||
//
|
//
|
||||||
// https://bugreports.qt.io/browse/QTBUG-46140
|
// https://bugreports.qt.io/browse/QTBUG-46140
|
||||||
|
// An explicit QT_OPENGL (e.g. "software" on GPU-less CI runners with
|
||||||
|
// Mesa deployed as opengl32sw.dll) takes precedence over this default.
|
||||||
|
if (!qEnvironmentVariableIsSet("QT_OPENGL")) {
|
||||||
QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
|
QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
|
||||||
|
}
|
||||||
format.setVersion(3, 2);
|
format.setVersion(3, 2);
|
||||||
format.setProfile(QSurfaceFormat::CoreProfile);
|
format.setProfile(QSurfaceFormat::CoreProfile);
|
||||||
|
|
||||||
format.setDepthBufferSize(24);
|
format.setDepthBufferSize(24);
|
||||||
QSurfaceFormat::setDefaultFormat(format);
|
QSurfaceFormat::setDefaultFormat(format);
|
||||||
|
|
||||||
// Enable application automatically using higher resolution images from icons
|
// High-DPI pixmaps are always enabled in Qt 6 (AA_UseHighDpiPixmaps is
|
||||||
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
// deprecated and has no effect).
|
||||||
|
|
||||||
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
|
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
|
||||||
|
|
||||||
@@ -352,7 +356,7 @@ int main(int argc, char *argv[])
|
|||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// Since Oak Video Editor is linked with the console subsystem (for better POSIX compatibility), a console
|
// Since Oak Video Editor is linked with the console subsystem (for better POSIX compatibility), a console
|
||||||
// is created by default. If the user didn't request one, we free it here.
|
// is created by default. If the user didn't request one, we free it here.
|
||||||
if (!console_option->IsSet()) {
|
if (!console_option->is_set()) {
|
||||||
FreeConsole();
|
FreeConsole();
|
||||||
}
|
}
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
|
|||||||
+1
-2
@@ -49,8 +49,7 @@ PanelWidget::PanelWidget(const QString &object_name)
|
|||||||
|
|
||||||
View<QWidget>::setFocusPolicy(Qt::ClickFocus);
|
View<QWidget>::setFocusPolicy(Qt::ClickFocus);
|
||||||
|
|
||||||
connect(this, &PanelWidget::shown, this,
|
connect(this, &PanelWidget::shown, this, [this]() { QWidget::setFocus(); });
|
||||||
reinterpret_cast<void (PanelWidget::*)()>(&PanelWidget::setFocus));
|
|
||||||
|
|
||||||
PanelManager::instance()->register_panel(this);
|
PanelManager::instance()->register_panel(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -334,7 +334,7 @@ void CurveView::ContextMenuEvent(Menu &m)
|
|||||||
|
|
||||||
void CurveView::SceneRectUpdateEvent(QRectF &r)
|
void CurveView::SceneRectUpdateEvent(QRectF &r)
|
||||||
{
|
{
|
||||||
double min_val, max_val;
|
double min_val = 0, max_val = 0;
|
||||||
bool got_val = false;
|
bool got_val = false;
|
||||||
|
|
||||||
foreach (KeyframeViewInputConnection *con, track_connections_) {
|
foreach (KeyframeViewInputConnection *con, track_connections_) {
|
||||||
@@ -694,7 +694,7 @@ void CurveView::zoom_to_fit_internal(bool selected_only)
|
|||||||
bool got_val = false;
|
bool got_val = false;
|
||||||
|
|
||||||
Rational min_time, max_time;
|
Rational min_time, max_time;
|
||||||
double min_val, max_val;
|
double min_val = 0, max_val = 0;
|
||||||
|
|
||||||
foreach (KeyframeViewInputConnection *con, track_connections_) {
|
foreach (KeyframeViewInputConnection *con, track_connections_) {
|
||||||
foreach (const oak::Keyframe &key, con->get_keyframes()) {
|
foreach (const oak::Keyframe &key, con->get_keyframes()) {
|
||||||
|
|||||||
@@ -355,7 +355,7 @@ void CurveWidget::keyframe_type_button_triggered(bool checked)
|
|||||||
QVector<int> tracks;
|
QVector<int> tracks;
|
||||||
};
|
};
|
||||||
QVector<TypeGroup> groups;
|
QVector<TypeGroup> groups;
|
||||||
foreach (OakEngineKeyframe *item, selected) {
|
for (OakEngineKeyframe *item : selected) {
|
||||||
const oak::Keyframe key(item);
|
const oak::Keyframe key(item);
|
||||||
OakEngineNode *node = key.node().handle();
|
OakEngineNode *node = key.node().handle();
|
||||||
int g = 0;
|
int g = 0;
|
||||||
|
|||||||
@@ -64,8 +64,10 @@ bool HandMovableView::hand_press(QMouseEvent *event)
|
|||||||
setInteractive(false);
|
setInteractive(false);
|
||||||
|
|
||||||
// Transform mouse event to act like the left button is pressed
|
// Transform mouse event to act like the left button is pressed
|
||||||
QMouseEvent transformed(event->type(), event->pos(), Qt::LeftButton,
|
QMouseEvent transformed(event->type(), event->position(),
|
||||||
Qt::LeftButton, event->modifiers());
|
event->globalPosition(), Qt::LeftButton,
|
||||||
|
Qt::LeftButton, event->modifiers(),
|
||||||
|
event->pointingDevice());
|
||||||
|
|
||||||
transformed_pos_ = QPoint(0, 0);
|
transformed_pos_ = QPoint(0, 0);
|
||||||
|
|
||||||
@@ -83,22 +85,24 @@ bool HandMovableView::hand_move(QMouseEvent *event)
|
|||||||
// Transform mouse event to act like the left button is pressed
|
// Transform mouse event to act like the left button is pressed
|
||||||
QPoint adjustment(0, 0);
|
QPoint adjustment(0, 0);
|
||||||
|
|
||||||
QMouseEvent transformed(event->type(), event->pos() - transformed_pos_,
|
QMouseEvent transformed(event->type(),
|
||||||
Qt::LeftButton, Qt::LeftButton,
|
event->position() - transformed_pos_,
|
||||||
event->modifiers());
|
event->globalPosition(), Qt::LeftButton,
|
||||||
|
Qt::LeftButton, event->modifiers(),
|
||||||
|
event->pointingDevice());
|
||||||
|
|
||||||
if (event->pos().x() < 0) {
|
if (event->position().toPoint().x() < 0) {
|
||||||
transformed_pos_.setX(transformed_pos_.x() + width());
|
transformed_pos_.setX(transformed_pos_.x() + width());
|
||||||
adjustment.setX(width());
|
adjustment.setX(width());
|
||||||
} else if (event->pos().x() >= width()) {
|
} else if (event->position().toPoint().x() >= width()) {
|
||||||
transformed_pos_.setX(transformed_pos_.x() - width());
|
transformed_pos_.setX(transformed_pos_.x() - width());
|
||||||
adjustment.setX(-width());
|
adjustment.setX(-width());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event->pos().y() < 0) {
|
if (event->position().toPoint().y() < 0) {
|
||||||
transformed_pos_.setY(transformed_pos_.y() + height());
|
transformed_pos_.setY(transformed_pos_.y() + height());
|
||||||
adjustment.setY(height());
|
adjustment.setY(height());
|
||||||
} else if (event->pos().y() >= height()) {
|
} else if (event->position().toPoint().y() >= height()) {
|
||||||
transformed_pos_.setY(transformed_pos_.y() - height());
|
transformed_pos_.setY(transformed_pos_.y() - height());
|
||||||
adjustment.setY(-height());
|
adjustment.setY(-height());
|
||||||
}
|
}
|
||||||
@@ -116,8 +120,8 @@ bool HandMovableView::hand_release(QMouseEvent *event)
|
|||||||
{
|
{
|
||||||
if (dragging_hand_) {
|
if (dragging_hand_) {
|
||||||
// Transform mouse event to act like the left button is pressed
|
// Transform mouse event to act like the left button is pressed
|
||||||
QMouseEvent transformed(event->type(), event->localPos(),
|
QMouseEvent transformed(event->type(), event->position(),
|
||||||
event->windowPos(), event->screenPos(),
|
event->scenePosition(), event->globalPosition(),
|
||||||
Qt::LeftButton, Qt::LeftButton,
|
Qt::LeftButton, Qt::LeftButton,
|
||||||
event->modifiers(), event->source());
|
event->modifiers(), event->source());
|
||||||
|
|
||||||
@@ -194,7 +198,7 @@ void HandMovableView::wheelEvent(QWheelEvent *event)
|
|||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||||
event->position(), event->globalPosition(),
|
event->position(), event->globalPosition(),
|
||||||
#else
|
#else
|
||||||
event->pos(), event->globalPos(),
|
event->position().toPoint(), event->globalPos(),
|
||||||
#endif
|
#endif
|
||||||
event->pixelDelta(), angle_delta, event->buttons(),
|
event->pixelDelta(), angle_delta, event->buttons(),
|
||||||
event->modifiers(), event->phase(), event->inverted(),
|
event->modifiers(), event->phase(), event->inverted(),
|
||||||
@@ -209,7 +213,7 @@ void HandMovableView::wheelEvent(QWheelEvent *event)
|
|||||||
Qt::Horizontal;
|
Qt::Horizontal;
|
||||||
}
|
}
|
||||||
|
|
||||||
QWheelEvent e(event->pos(), event->globalPos(), event->pixelDelta(),
|
QWheelEvent e(event->position().toPoint(), event->globalPos(), event->pixelDelta(),
|
||||||
event->angleDelta(), event->delta(), orientation,
|
event->angleDelta(), event->delta(), orientation,
|
||||||
event->buttons(), event->modifiers());
|
event->buttons(), event->modifiers());
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ void KeyframeView::delete_selected()
|
|||||||
get_selected_keyframes();
|
get_selected_keyframes();
|
||||||
QVector<OakEngineKeyframe *> keys;
|
QVector<OakEngineKeyframe *> keys;
|
||||||
keys.reserve(int(selected.size()));
|
keys.reserve(int(selected.size()));
|
||||||
foreach (OakEngineKeyframe *key, selected) {
|
for (OakEngineKeyframe *key : selected) {
|
||||||
keys.append(key);
|
keys.append(key);
|
||||||
}
|
}
|
||||||
oakengine_keyframes_remove_many(
|
oakengine_keyframes_remove_many(
|
||||||
@@ -289,9 +289,9 @@ bool KeyframeView::paste(
|
|||||||
oakengine_clipboard_foreach_keyframe(
|
oakengine_clipboard_foreach_keyframe(
|
||||||
cb,
|
cb,
|
||||||
[](const char *, OakEngineKeyframe *kf, void *userdata) -> int {
|
[](const char *, OakEngineKeyframe *kf, void *userdata) -> int {
|
||||||
auto *ctx = static_cast<PasteCtx *>(userdata);
|
auto *paste_ctx = static_cast<PasteCtx *>(userdata);
|
||||||
ctx->min = std::min(ctx->min, key_time(kf));
|
paste_ctx->min = std::min(paste_ctx->min, key_time(kf));
|
||||||
ctx->total++;
|
paste_ctx->total++;
|
||||||
return 0;
|
return 0;
|
||||||
},
|
},
|
||||||
&ctx);
|
&ctx);
|
||||||
@@ -303,17 +303,17 @@ bool KeyframeView::paste(
|
|||||||
cb,
|
cb,
|
||||||
[](const char *node_id, OakEngineKeyframe *kf,
|
[](const char *node_id, OakEngineKeyframe *kf,
|
||||||
void *userdata) -> int {
|
void *userdata) -> int {
|
||||||
auto *ctx = static_cast<PasteCtx *>(userdata);
|
auto *paste_ctx = static_cast<PasteCtx *>(userdata);
|
||||||
auto &find_fn =
|
auto &find_fn =
|
||||||
*static_cast<std::function<oak::Node(const QString &)> *>(
|
*static_cast<std::function<oak::Node(const QString &)> *>(
|
||||||
ctx->find_fn);
|
paste_ctx->find_fn);
|
||||||
oak::Node node_with_id =
|
oak::Node node_with_id =
|
||||||
find_fn(QString::fromUtf8(node_id));
|
find_fn(QString::fromUtf8(node_id));
|
||||||
|
|
||||||
if (!node_with_id.is_null()) {
|
if (!node_with_id.is_null()) {
|
||||||
Rational t = key_time(kf) - ctx->min;
|
Rational t = key_time(kf) - paste_ctx->min;
|
||||||
t = ctx->self->get_adjusted_time(
|
t = paste_ctx->self->get_adjusted_time(
|
||||||
ctx->self->get_time_target(),
|
paste_ctx->self->get_time_target(),
|
||||||
node_with_id.handle(), t,
|
node_with_id.handle(), t,
|
||||||
k_transform_towards_input);
|
k_transform_towards_input);
|
||||||
key_set_time_live(kf, t);
|
key_set_time_live(kf, t);
|
||||||
@@ -333,7 +333,7 @@ bool KeyframeView::paste(
|
|||||||
void *rm = oakengine_node_remove_keyframe_command(
|
void *rm = oakengine_node_remove_keyframe_command(
|
||||||
existing);
|
existing);
|
||||||
oakengine_undo_command_multi_add_child(
|
oakengine_undo_command_multi_add_child(
|
||||||
ctx->command, rm);
|
paste_ctx->command, rm);
|
||||||
}
|
}
|
||||||
|
|
||||||
oak_node_value v;
|
oak_node_value v;
|
||||||
@@ -357,7 +357,7 @@ bool KeyframeView::paste(
|
|||||||
static_cast<float>(cp_in.y()),
|
static_cast<float>(cp_in.y()),
|
||||||
static_cast<float>(cp_out.x()),
|
static_cast<float>(cp_out.x()),
|
||||||
static_cast<float>(cp_out.y()));
|
static_cast<float>(cp_out.y()));
|
||||||
oakengine_undo_command_multi_add_child(ctx->command, cmd);
|
oakengine_undo_command_multi_add_child(paste_ctx->command, cmd);
|
||||||
} else {
|
} else {
|
||||||
oakengine_keyframe_dispose(kf);
|
oakengine_keyframe_dispose(kf);
|
||||||
}
|
}
|
||||||
@@ -760,7 +760,7 @@ void KeyframeView::show_context_menu()
|
|||||||
QVector<int> tracks;
|
QVector<int> tracks;
|
||||||
};
|
};
|
||||||
QVector<TypeGroup> groups;
|
QVector<TypeGroup> groups;
|
||||||
foreach (OakEngineKeyframe *item, get_selected_keyframes()) {
|
for (OakEngineKeyframe *item : get_selected_keyframes()) {
|
||||||
const oak::Keyframe key(item);
|
const oak::Keyframe key(item);
|
||||||
OakEngineNode *node = key.node().handle();
|
OakEngineNode *node = key.node().handle();
|
||||||
int g = 0;
|
int g = 0;
|
||||||
@@ -797,7 +797,7 @@ void KeyframeView::show_keyframe_properties_dialog()
|
|||||||
if (!get_selected_keyframes().empty()) {
|
if (!get_selected_keyframes().empty()) {
|
||||||
QVector<oak::Keyframe> keys;
|
QVector<oak::Keyframe> keys;
|
||||||
keys.reserve(int(get_selected_keyframes().size()));
|
keys.reserve(int(get_selected_keyframes().size()));
|
||||||
foreach (OakEngineKeyframe *key, get_selected_keyframes()) {
|
for (OakEngineKeyframe *key : get_selected_keyframes()) {
|
||||||
keys.append(oak::Keyframe(key));
|
keys.append(oak::Keyframe(key));
|
||||||
}
|
}
|
||||||
KeyframePropertiesDialog kd(keys, timebase(), this);
|
KeyframePropertiesDialog kd(keys, timebase(), this);
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ ManagedDisplayWidget::ManagedDisplayWidget(QWidget *parent)
|
|||||||
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
||||||
{
|
{
|
||||||
const QString backend = oak_query_string([](char *buf, int sz) {
|
const QString backend = oak_query_string([](char *buf, int sz) {
|
||||||
int backend = oakengine_render_manager_requested_backend();
|
int backend_id = oakengine_render_manager_requested_backend();
|
||||||
return oakengine_render_manager_backend_to_string(backend, buf,
|
return oakengine_render_manager_backend_to_string(backend_id, buf,
|
||||||
sz);
|
sz);
|
||||||
});
|
});
|
||||||
attached_renderer_ =
|
attached_renderer_ =
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
***/
|
***/
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
#include "menushared.h"
|
#include "menushared.h"
|
||||||
|
|
||||||
#include <QActionGroup>
|
#include <QActionGroup>
|
||||||
@@ -202,7 +203,7 @@ void MenuShared::add_items_for_edit_menu(Menu *m, bool for_clips)
|
|||||||
|
|
||||||
void MenuShared::add_items_for_addable_objects_menu(Menu *m)
|
void MenuShared::add_items_for_addable_objects_menu(Menu *m)
|
||||||
{
|
{
|
||||||
for (QAction *a : qAsConst(addable_items_)) {
|
for (QAction *a : std::as_const(addable_items_)) {
|
||||||
a->setChecked((a->data().toInt() ==
|
a->setChecked((a->data().toInt() ==
|
||||||
Core::instance()->get_selected_addable_object()));
|
Core::instance()->get_selected_addable_object()));
|
||||||
m->addAction(a);
|
m->addAction(a);
|
||||||
@@ -425,7 +426,7 @@ void MenuShared::retranslate()
|
|||||||
edit_split_item_->setText(tr("Split"));
|
edit_split_item_->setText(tr("Split"));
|
||||||
edit_speedduration_item_->setText(tr("Speed/Duration"));
|
edit_speedduration_item_->setText(tr("Speed/Duration"));
|
||||||
|
|
||||||
for (QAction *a : qAsConst(addable_items_)) {
|
for (QAction *a : std::as_const(addable_items_)) {
|
||||||
a->setText(Tool::get_addable_object_name(
|
a->setText(Tool::get_addable_object_name(
|
||||||
static_cast<Tool::AddableObject>(a->data().toInt())));
|
static_cast<Tool::AddableObject>(a->data().toInt())));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,9 +43,9 @@ NodeParamViewArrayWidget::NodeParamViewArrayWidget(oak::Node node,
|
|||||||
bridge_->subscribe(reinterpret_cast<void *>(node_.handle()),
|
bridge_->subscribe(reinterpret_cast<void *>(node_.handle()),
|
||||||
OAKENGINE_EVENT_NODE_INPUT_ARRAY_SIZE_CHANGED);
|
OAKENGINE_EVENT_NODE_INPUT_ARRAY_SIZE_CHANGED);
|
||||||
connect(bridge_, &EngineEventBridge::node_input_array_size_changed, this,
|
connect(bridge_, &EngineEventBridge::node_input_array_size_changed, this,
|
||||||
[this](OakEngineNode *, const QString &input, int old_size,
|
[this](OakEngineNode *, const QString &input_id, int old_size,
|
||||||
int new_size) {
|
int new_size) {
|
||||||
update_counter(input, old_size, new_size);
|
update_counter(input_id, old_size, new_size);
|
||||||
});
|
});
|
||||||
|
|
||||||
update_counter(input_, 0, oak::Input(node_.handle(), input_).array_size());
|
update_counter(input_, 0, oak::Input(node_.handle(), input_).array_size());
|
||||||
@@ -58,11 +58,11 @@ void NodeParamViewArrayWidget::mouseDoubleClickEvent(QMouseEvent *event)
|
|||||||
emit double_clicked();
|
emit double_clicked();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeParamViewArrayWidget::update_counter(const QString &input, int old_size,
|
void NodeParamViewArrayWidget::update_counter(const QString &changed_input, int old_size,
|
||||||
int new_size)
|
int new_size)
|
||||||
{
|
{
|
||||||
Q_UNUSED(old_size)
|
Q_UNUSED(old_size)
|
||||||
if (input == input_) {
|
if (changed_input == input_) {
|
||||||
count_lbl_->setText(tr("%n element(s)", nullptr, new_size));
|
count_lbl_->setText(tr("%n element(s)", nullptr, new_size));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,15 +93,15 @@ NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const oak::Input &input
|
|||||||
OAKENGINE_EVENT_NODE_INPUT_DISCONNECTED);
|
OAKENGINE_EVENT_NODE_INPUT_DISCONNECTED);
|
||||||
connect(bridge_, &EngineEventBridge::node_input_connected, this,
|
connect(bridge_, &EngineEventBridge::node_input_connected, this,
|
||||||
[this](OakEngineNode *source, OakEngineNode *output,
|
[this](OakEngineNode *source, OakEngineNode *output,
|
||||||
const QString &input, int element) {
|
const QString &input_id, int element) {
|
||||||
input_connected(output,
|
input_connected(output,
|
||||||
oak::Input(source, input, element));
|
oak::Input(source, input_id, element));
|
||||||
});
|
});
|
||||||
connect(bridge_, &EngineEventBridge::node_input_disconnected, this,
|
connect(bridge_, &EngineEventBridge::node_input_disconnected, this,
|
||||||
[this](OakEngineNode *source, OakEngineNode *output,
|
[this](OakEngineNode *source, OakEngineNode *output,
|
||||||
const QString &input, int element) {
|
const QString &input_id, int element) {
|
||||||
input_disconnected(output,
|
input_disconnected(output,
|
||||||
oak::Input(source, input, element));
|
oak::Input(source, input_id, element));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Creating the tree is expensive, hold off until the user specifically requests it
|
// Creating the tree is expensive, hold off until the user specifically requests it
|
||||||
|
|||||||
@@ -161,8 +161,8 @@ NodeParamViewWidgetBridge::NodeParamViewWidgetBridge(const oak::Input &input,
|
|||||||
connect(bridge_, &EngineEventBridge::node_input_value_changed, this,
|
connect(bridge_, &EngineEventBridge::node_input_value_changed, this,
|
||||||
&NodeParamViewWidgetBridge::input_value_changed);
|
&NodeParamViewWidgetBridge::input_value_changed);
|
||||||
connect(bridge_, &EngineEventBridge::node_input_property_changed, this,
|
connect(bridge_, &EngineEventBridge::node_input_property_changed, this,
|
||||||
[this](OakEngineNode *, const QString &input) {
|
[this](OakEngineNode *, const QString &input_id) {
|
||||||
property_changed(input);
|
property_changed(input_id);
|
||||||
});
|
});
|
||||||
connect(bridge_, &EngineEventBridge::node_input_data_type_changed, this,
|
connect(bridge_, &EngineEventBridge::node_input_data_type_changed, this,
|
||||||
&NodeParamViewWidgetBridge::input_data_type_changed);
|
&NodeParamViewWidgetBridge::input_data_type_changed);
|
||||||
@@ -728,6 +728,7 @@ void NodeParamViewWidgetBridge::widget_callback()
|
|||||||
case NodeValueType::k_samples:
|
case NodeValueType::k_samples:
|
||||||
case NodeValueType::k_video_params:
|
case NodeValueType::k_video_params:
|
||||||
case NodeValueType::k_audio_params:
|
case NodeValueType::k_audio_params:
|
||||||
|
case NodeValueType::k_push_button:
|
||||||
case NodeValueType::k_subtitle_params:
|
case NodeValueType::k_subtitle_params:
|
||||||
case NodeValueType::k_data_type_count:
|
case NodeValueType::k_data_type_count:
|
||||||
break;
|
break;
|
||||||
@@ -946,6 +947,7 @@ void NodeParamViewWidgetBridge::update_widget_values()
|
|||||||
case NodeValueType::k_samples:
|
case NodeValueType::k_samples:
|
||||||
case NodeValueType::k_video_params:
|
case NodeValueType::k_video_params:
|
||||||
case NodeValueType::k_audio_params:
|
case NodeValueType::k_audio_params:
|
||||||
|
case NodeValueType::k_push_button:
|
||||||
case NodeValueType::k_subtitle_params:
|
case NodeValueType::k_subtitle_params:
|
||||||
case NodeValueType::k_data_type_count:
|
case NodeValueType::k_data_type_count:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ void NodeTableView::set_time(const Rational &time)
|
|||||||
// Set data type name
|
// Set data type name
|
||||||
char name_buf[64];
|
char name_buf[64];
|
||||||
int len = oakengine_node_value_pretty_type_name(type, name_buf, sizeof(name_buf));
|
int len = oakengine_node_value_pretty_type_name(type, name_buf, sizeof(name_buf));
|
||||||
if (len > 0 && len < sizeof(name_buf)) {
|
if (len > 0 && len < static_cast<int>(sizeof(name_buf))) {
|
||||||
name_buf[len] = '\0';
|
name_buf[len] = '\0';
|
||||||
} else {
|
} else {
|
||||||
snprintf(name_buf, sizeof(name_buf), "Type %d", type);
|
snprintf(name_buf, sizeof(name_buf), "Type %d", type);
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ void NodeValueTree::set_node(const oak::Input &input, const Rational &time)
|
|||||||
setItemWidget(item, 0, radio);
|
setItemWidget(item, 0, radio);
|
||||||
char name_buf[64];
|
char name_buf[64];
|
||||||
int len = oakengine_node_value_pretty_type_name(type, name_buf, sizeof(name_buf));
|
int len = oakengine_node_value_pretty_type_name(type, name_buf, sizeof(name_buf));
|
||||||
if (len > 0 && len < sizeof(name_buf)) {
|
if (len > 0 && len < static_cast<int>(sizeof(name_buf))) {
|
||||||
name_buf[len] = '\0';
|
name_buf[len] = '\0';
|
||||||
} else {
|
} else {
|
||||||
snprintf(name_buf, sizeof(name_buf), "Type %d", type);
|
snprintf(name_buf, sizeof(name_buf), "Type %d", type);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
***/
|
***/
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
#include "nodeview.h"
|
#include "nodeview.h"
|
||||||
|
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
@@ -75,8 +76,8 @@ NodeView::NodeView(QWidget *parent)
|
|||||||
, overlay_view_(nullptr)
|
, overlay_view_(nullptr)
|
||||||
, scale_(1.0)
|
, scale_(1.0)
|
||||||
, dont_emit_selection_signals_(false)
|
, dont_emit_selection_signals_(false)
|
||||||
, show_in_param_editor_action_(nullptr)
|
|
||||||
, bridge_(new EngineEventBridge(this))
|
, bridge_(new EngineEventBridge(this))
|
||||||
|
, show_in_param_editor_action_(nullptr)
|
||||||
{
|
{
|
||||||
setScene(&scene_);
|
setScene(&scene_);
|
||||||
set_default_drag_mode(RubberBandDrag);
|
set_default_drag_mode(RubberBandDrag);
|
||||||
@@ -499,7 +500,7 @@ void NodeView::set_color_label(int index)
|
|||||||
// WRAPPER-GAP: oakengine_undo_* command assembly (no wrapper)
|
// WRAPPER-GAP: oakengine_undo_* command assembly (no wrapper)
|
||||||
void *command = oakengine_undo_command_create_multi();
|
void *command = oakengine_undo_command_create_multi();
|
||||||
|
|
||||||
for (const oak::Node &node : qAsConst(selected_nodes_)) {
|
for (const oak::Node &node : std::as_const(selected_nodes_)) {
|
||||||
oakengine_undo_command_multi_add_child(
|
oakengine_undo_command_multi_add_child(
|
||||||
command,
|
command,
|
||||||
oakengine_node_set_color_label_command(node.handle(), index));
|
oakengine_node_set_color_label_command(node.handle(), index));
|
||||||
@@ -528,8 +529,8 @@ void NodeView::keyPressEvent(QKeyEvent *event)
|
|||||||
case Qt::Key_Down: {
|
case Qt::Key_Down: {
|
||||||
// WRAPPER-GAP: oakengine_undo_* command assembly (no wrapper)
|
// WRAPPER-GAP: oakengine_undo_* command assembly (no wrapper)
|
||||||
void *pos_command = oakengine_undo_command_create_multi();
|
void *pos_command = oakengine_undo_command_create_multi();
|
||||||
for (const oak::Node &n : qAsConst(selected_nodes_)) {
|
for (const oak::Node &n : std::as_const(selected_nodes_)) {
|
||||||
for (const oak::Node &context : qAsConst(contexts_)) {
|
for (const oak::Node &context : std::as_const(contexts_)) {
|
||||||
QPointF old_pos;
|
QPointF old_pos;
|
||||||
bool old_expanded = false;
|
bool old_expanded = false;
|
||||||
if (context.context_position_of(n, &old_pos, &old_expanded)) {
|
if (context.context_position_of(n, &old_pos, &old_expanded)) {
|
||||||
@@ -595,7 +596,7 @@ void NodeView::mousePressEvent(QMouseEvent *event)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Get the item that the user clicked on, if any
|
// Get the item that the user clicked on, if any
|
||||||
QGraphicsItem *item = itemAt(event->pos());
|
QGraphicsItem *item = itemAt(event->position().toPoint());
|
||||||
|
|
||||||
if (event->button() == Qt::LeftButton) {
|
if (event->button() == Qt::LeftButton) {
|
||||||
// Sane defaults
|
// Sane defaults
|
||||||
@@ -657,7 +658,7 @@ void NodeView::mousePressEvent(QMouseEvent *event)
|
|||||||
scene_.addItem(create_edge_);
|
scene_.addItem(create_edge_);
|
||||||
|
|
||||||
// Position edge to mouse cursor
|
// Position edge to mouse cursor
|
||||||
position_new_edge(event->pos());
|
position_new_edge(event->position().toPoint());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -698,7 +699,7 @@ void NodeView::mouseMoveEvent(QMouseEvent *event)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (create_edge_) {
|
if (create_edge_) {
|
||||||
position_new_edge(event->pos());
|
position_new_edge(event->position().toPoint());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -708,7 +709,7 @@ void NodeView::mouseMoveEvent(QMouseEvent *event)
|
|||||||
|
|
||||||
// See if there are any items attached
|
// See if there are any items attached
|
||||||
if (!attached_items_.isEmpty()) {
|
if (!attached_items_.isEmpty()) {
|
||||||
process_moving_attached_nodes(event->pos());
|
process_moving_attached_nodes(event->position().toPoint());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void NodeView::mouseReleaseEvent(QMouseEvent *event)
|
void NodeView::mouseReleaseEvent(QMouseEvent *event)
|
||||||
@@ -729,11 +730,11 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event)
|
|||||||
bool had_attached_items = !attached_items_.isEmpty();
|
bool had_attached_items = !attached_items_.isEmpty();
|
||||||
|
|
||||||
if (!attached_items_.isEmpty()) {
|
if (!attached_items_.isEmpty()) {
|
||||||
select_context = get_context_at_mouse_pos(event->pos());
|
select_context = get_context_at_mouse_pos(event->position().toPoint());
|
||||||
|
|
||||||
if (!select_context.is_null()) {
|
if (!select_context.is_null()) {
|
||||||
select_nodes = process_dropping_attached_nodes(command, select_context,
|
select_nodes = process_dropping_attached_nodes(command, select_context,
|
||||||
event->pos());
|
event->position().toPoint());
|
||||||
} else {
|
} else {
|
||||||
QToolTip::showText(QCursor::pos(),
|
QToolTip::showText(QCursor::pos(),
|
||||||
tr("Nodes must be placed inside a context."));
|
tr("Nodes must be placed inside a context."));
|
||||||
@@ -780,7 +781,7 @@ void NodeView::mouseDoubleClickEvent(QMouseEvent *event)
|
|||||||
|
|
||||||
if (!(event->modifiers() & Qt::ControlModifier)) {
|
if (!(event->modifiers() & Qt::ControlModifier)) {
|
||||||
NodeViewItem *item_at_cursor =
|
NodeViewItem *item_at_cursor =
|
||||||
dynamic_cast<NodeViewItem *>(itemAt(event->pos()));
|
dynamic_cast<NodeViewItem *>(itemAt(event->position().toPoint()));
|
||||||
if (item_at_cursor) {
|
if (item_at_cursor) {
|
||||||
item_at_cursor->toggle_expanded();
|
item_at_cursor->toggle_expanded();
|
||||||
}
|
}
|
||||||
@@ -841,9 +842,9 @@ void NodeView::dragMoveEvent(QDragMoveEvent *event)
|
|||||||
if (attached_items_.empty()) {
|
if (attached_items_.empty()) {
|
||||||
event->ignore();
|
event->ignore();
|
||||||
} else {
|
} else {
|
||||||
process_moving_attached_nodes(event->pos());
|
process_moving_attached_nodes(event->position().toPoint());
|
||||||
|
|
||||||
if (get_context_at_mouse_pos(event->pos())) {
|
if (get_context_at_mouse_pos(event->position().toPoint())) {
|
||||||
event->accept();
|
event->accept();
|
||||||
} else {
|
} else {
|
||||||
event->ignore();
|
event->ignore();
|
||||||
@@ -853,12 +854,12 @@ void NodeView::dragMoveEvent(QDragMoveEvent *event)
|
|||||||
|
|
||||||
void NodeView::dropEvent(QDropEvent *event)
|
void NodeView::dropEvent(QDropEvent *event)
|
||||||
{
|
{
|
||||||
oak::Node drop_ctx = get_context_at_mouse_pos(event->pos());
|
oak::Node drop_ctx = get_context_at_mouse_pos(event->position().toPoint());
|
||||||
if (!drop_ctx.is_null()) {
|
if (!drop_ctx.is_null()) {
|
||||||
// WRAPPER-GAP: oakengine_undo_* command assembly (no wrapper)
|
// WRAPPER-GAP: oakengine_undo_* command assembly (no wrapper)
|
||||||
void *command = oakengine_undo_command_create_multi();
|
void *command = oakengine_undo_command_create_multi();
|
||||||
QVector<oak::Node> select_nodes =
|
QVector<oak::Node> select_nodes =
|
||||||
process_dropping_attached_nodes(command, drop_ctx, event->pos());
|
process_dropping_attached_nodes(command, drop_ctx, event->position().toPoint());
|
||||||
oakengine_undo_push(
|
oakengine_undo_push(
|
||||||
command, tr("Dropped %1 Node(s)").arg(select_nodes.size()).toUtf8().constData());
|
command, tr("Dropped %1 Node(s)").arg(select_nodes.size()).toUtf8().constData());
|
||||||
|
|
||||||
@@ -1192,7 +1193,7 @@ void NodeView::move_attached_nodes_to_cursor(const QPoint &p)
|
|||||||
{
|
{
|
||||||
QPointF item_pos = mapToScene(p);
|
QPointF item_pos = mapToScene(p);
|
||||||
|
|
||||||
for (const AttachedItem &i : qAsConst(attached_items_)) {
|
for (const AttachedItem &i : std::as_const(attached_items_)) {
|
||||||
if (i.item) {
|
if (i.item) {
|
||||||
i.item->setPos(item_pos + i.original_pos);
|
i.item->setPos(item_pos + i.original_pos);
|
||||||
}
|
}
|
||||||
@@ -1219,7 +1220,7 @@ void NodeView::process_moving_attached_nodes(const QPoint &pos)
|
|||||||
NodeViewEdge *new_drop_edge = nullptr;
|
NodeViewEdge *new_drop_edge = nullptr;
|
||||||
|
|
||||||
// See if there is an edge here
|
// See if there is an edge here
|
||||||
for (QGraphicsItem *item : qAsConst(items)) {
|
for (QGraphicsItem *item : std::as_const(items)) {
|
||||||
new_drop_edge = dynamic_cast<NodeViewEdge *>(item);
|
new_drop_edge = dynamic_cast<NodeViewEdge *>(item);
|
||||||
|
|
||||||
if (new_drop_edge) {
|
if (new_drop_edge) {
|
||||||
|
|||||||
@@ -118,8 +118,8 @@ NodeViewItem::NodeViewItem(oak::Node node, const QString &input,
|
|||||||
|
|
||||||
bridge_->subscribe(node_.handle(), OAKENGINE_EVENT_NODE_INPUT_ARRAY_SIZE_CHANGED);
|
bridge_->subscribe(node_.handle(), OAKENGINE_EVENT_NODE_INPUT_ARRAY_SIZE_CHANGED);
|
||||||
connect(bridge_, &EngineEventBridge::node_input_array_size_changed, this,
|
connect(bridge_, &EngineEventBridge::node_input_array_size_changed, this,
|
||||||
[this](OakEngineNode *, const QString &input, int, int) {
|
[this](OakEngineNode *, const QString &changed_input, int, int) {
|
||||||
input_array_size_changed(input);
|
input_array_size_changed(changed_input);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ public:
|
|||||||
ChannelLayoutComboBox(QWidget *parent = nullptr)
|
ChannelLayoutComboBox(QWidget *parent = nullptr)
|
||||||
: QComboBox(parent)
|
: QComboBox(parent)
|
||||||
{
|
{
|
||||||
foreach (const uint64_t &ch_layout,
|
for (const uint64_t &ch_layout :
|
||||||
AudioParams::k_supported_channel_layouts) {
|
AudioParams::k_supported_channel_layouts) {
|
||||||
this->addItem(HumanStrings::channel_layout_to_string(ch_layout),
|
this->addItem(HumanStrings::channel_layout_to_string(ch_layout),
|
||||||
QVariant::fromValue(ch_layout));
|
QVariant::fromValue(ch_layout));
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
clear();
|
clear();
|
||||||
foreach (const SampleFormat &of, formats) {
|
for (const SampleFormat &of : formats) {
|
||||||
add_format_item(of);
|
add_format_item(of);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public:
|
|||||||
SampleRateComboBox(QWidget *parent = nullptr)
|
SampleRateComboBox(QWidget *parent = nullptr)
|
||||||
: QComboBox(parent)
|
: QComboBox(parent)
|
||||||
{
|
{
|
||||||
foreach (int sr, AudioParams::k_supported_sample_rates) {
|
for (int sr : AudioParams::k_supported_sample_rates) {
|
||||||
this->addItem(HumanStrings::sample_rate_to_string(sr), sr);
|
this->addItem(HumanStrings::sample_rate_to_string(sr), sr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ void TimeBasedView::drawForeground(QPainter *painter, const QRectF &rect)
|
|||||||
if (snapped_) {
|
if (snapped_) {
|
||||||
painter->setPen(palette().text().color());
|
painter->setPen(palette().text().color());
|
||||||
|
|
||||||
foreach (const Rational &r, snap_time_) {
|
for (const Rational &r : snap_time_) {
|
||||||
double x = time_to_scene(r);
|
double x = time_to_scene(r);
|
||||||
|
|
||||||
painter->drawLine(x, rect.top(), x, rect.height());
|
painter->drawLine(x, rect.top(), x, rect.height());
|
||||||
|
|||||||
@@ -405,7 +405,7 @@ public:
|
|||||||
QRectF(rubberband_scene_start_, current).normalized();
|
QRectF(rubberband_scene_start_, current).normalized();
|
||||||
|
|
||||||
selected_ = rubberband_preselected_;
|
selected_ = rubberband_preselected_;
|
||||||
foreach (const DrawnObject &kp, drawn_objects_) {
|
for (const DrawnObject &kp : drawn_objects_) {
|
||||||
if (scene_rect.intersects(kp.second)) {
|
if (scene_rect.intersects(kp.second)) {
|
||||||
select(kp.first);
|
select(kp.first);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
***/
|
***/
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
#include "timebasedwidget.h"
|
#include "timebasedwidget.h"
|
||||||
|
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
@@ -48,8 +49,8 @@ TimeBasedWidget::TimeBasedWidget(bool ruler_text_visible,
|
|||||||
bool ruler_cache_status_visible,
|
bool ruler_cache_status_visible,
|
||||||
QWidget *parent)
|
QWidget *parent)
|
||||||
: TimelineScaledWidget(parent)
|
: TimelineScaledWidget(parent)
|
||||||
, viewer_node_(nullptr)
|
|
||||||
, bridge_(new EngineEventBridge(this))
|
, bridge_(new EngineEventBridge(this))
|
||||||
|
, viewer_node_(nullptr)
|
||||||
, auto_max_scrollbar_(false)
|
, auto_max_scrollbar_(false)
|
||||||
, toggle_show_all_(false)
|
, toggle_show_all_(false)
|
||||||
, auto_set_timebase_(true)
|
, auto_set_timebase_(true)
|
||||||
@@ -408,7 +409,7 @@ void TimeBasedWidget::connect_timeline_view(TimeBasedView *base)
|
|||||||
&QScrollBar::setValue);
|
&QScrollBar::setValue);
|
||||||
|
|
||||||
// Connect scrollbar to other scrollbars
|
// Connect scrollbar to other scrollbars
|
||||||
for (TimeBasedView *other : qAsConst(timeline_views_)) {
|
for (TimeBasedView *other : std::as_const(timeline_views_)) {
|
||||||
connect(other->horizontalScrollBar(), &QScrollBar::valueChanged,
|
connect(other->horizontalScrollBar(), &QScrollBar::valueChanged,
|
||||||
base->horizontalScrollBar(), &QScrollBar::setValue);
|
base->horizontalScrollBar(), &QScrollBar::setValue);
|
||||||
connect(base->horizontalScrollBar(), &QScrollBar::valueChanged,
|
connect(base->horizontalScrollBar(), &QScrollBar::valueChanged,
|
||||||
@@ -1229,7 +1230,7 @@ bool TimeBasedWidget::snap_point(const std::vector<Rational> &start_times,
|
|||||||
|
|
||||||
// Find all points at this movement
|
// Find all points at this movement
|
||||||
std::vector<Rational> snap_times;
|
std::vector<Rational> snap_times;
|
||||||
foreach (const SnapData &d, potential_snaps) {
|
for (const SnapData &d : potential_snaps) {
|
||||||
if (d.movement == *movement) {
|
if (d.movement == *movement) {
|
||||||
snap_times.push_back(d.time);
|
snap_times.push_back(d.time);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
***/
|
***/
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
#include "timelinewidget.h"
|
#include "timelinewidget.h"
|
||||||
#include "timelinewidgetwaveformsync.h"
|
#include "timelinewidgetwaveformsync.h"
|
||||||
|
|
||||||
@@ -2715,7 +2716,7 @@ void TimelineWidget::cache_clips_in_out()
|
|||||||
|
|
||||||
const TimeRange r(Rational(int(wa.in_num), int(wa.in_den)),
|
const TimeRange r(Rational(int(wa.in_num), int(wa.in_den)),
|
||||||
Rational(int(wa.out_num), int(wa.out_den)));
|
Rational(int(wa.out_num), int(wa.out_den)));
|
||||||
for (OakEngineBlock *b : qAsConst(selected_blocks_)) {
|
for (OakEngineBlock *b : std::as_const(selected_blocks_)) {
|
||||||
if (OakEngineBlock *clip = block_as_clip(b)) {
|
if (OakEngineBlock *clip = block_as_clip(b)) {
|
||||||
if (OakEngineNode *connected = clip_connected_node(clip)) {
|
if (OakEngineNode *connected = clip_connected_node(clip)) {
|
||||||
TimeRange adjusted =
|
TimeRange adjusted =
|
||||||
@@ -2751,7 +2752,7 @@ void TimelineWidget::multicam_enabled_triggered(bool e)
|
|||||||
{
|
{
|
||||||
void *command = oakengine_undo_command_create_multi();
|
void *command = oakengine_undo_command_create_multi();
|
||||||
|
|
||||||
for (OakEngineBlock *b : qAsConst(selected_blocks_)) {
|
for (OakEngineBlock *b : std::as_const(selected_blocks_)) {
|
||||||
if (OakEngineBlock *c = block_as_clip(b)) {
|
if (OakEngineBlock *c = block_as_clip(b)) {
|
||||||
OakEngineNode *viewer = oakengine_clip_get_connected_viewer(c);
|
OakEngineNode *viewer = oakengine_clip_get_connected_viewer(c);
|
||||||
OakEngineSequence *s = oakengine_node_is_sequence(viewer) ?
|
OakEngineSequence *s = oakengine_node_is_sequence(viewer) ?
|
||||||
@@ -3399,7 +3400,7 @@ TimelineWidget::generate_existing_paste_map(void *clipboard)
|
|||||||
|
|
||||||
for (int i = 0; i < node_count; i++) {
|
for (int i = 0; i < node_count; i++) {
|
||||||
OakEngineNode *n = oakengine_clipboard_get_loaded_node_at(cb, i);
|
OakEngineNode *n = oakengine_clipboard_get_loaded_node_at(cb, i);
|
||||||
for (OakEngineBlock *b : qAsConst(this->selected_blocks_)) {
|
for (OakEngineBlock *b : std::as_const(this->selected_blocks_)) {
|
||||||
// WRAPPER-GAP: no C ABI for Node::get_context_positions(); the
|
// WRAPPER-GAP: no C ABI for Node::get_context_positions(); the
|
||||||
// block's track-owning sequence is the context that matters here
|
// block's track-owning sequence is the context that matters here
|
||||||
// (blocks on a timeline always live in their sequence's context).
|
// (blocks on a timeline always live in their sequence's context).
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ TimelineView::TimelineView(Qt::Alignment vertical_alignment, QWidget *parent)
|
|||||||
void TimelineView::mousePressEvent(QMouseEvent *event)
|
void TimelineView::mousePressEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
// If we click on marker, jump to that point in the timeline
|
// If we click on marker, jump to that point in the timeline
|
||||||
QPointF scene_pos = mapToScene(event->pos());
|
QPointF scene_pos = mapToScene(event->position().toPoint());
|
||||||
for (auto it = clip_marker_rects_.cbegin(); it != clip_marker_rects_.cend();
|
for (auto it = clip_marker_rects_.cbegin(); it != clip_marker_rects_.cend();
|
||||||
it++) {
|
it++) {
|
||||||
if (it.value().contains(scene_pos)) {
|
if (it.value().contains(scene_pos)) {
|
||||||
@@ -358,7 +358,7 @@ void TimelineView::mouseDoubleClickEvent(QMouseEvent *event)
|
|||||||
void TimelineView::dragEnterEvent(QDragEnterEvent *event)
|
void TimelineView::dragEnterEvent(QDragEnterEvent *event)
|
||||||
{
|
{
|
||||||
TimelineViewMouseEvent timeline_event = CreateMouseEvent(
|
TimelineViewMouseEvent timeline_event = CreateMouseEvent(
|
||||||
event->pos(), Qt::NoButton, event->keyboardModifiers());
|
event->position().toPoint(), Qt::NoButton, event->modifiers());
|
||||||
|
|
||||||
timeline_event.set_mime_data(event->mimeData());
|
timeline_event.set_mime_data(event->mimeData());
|
||||||
timeline_event.SetEvent(event);
|
timeline_event.SetEvent(event);
|
||||||
@@ -369,7 +369,7 @@ void TimelineView::dragEnterEvent(QDragEnterEvent *event)
|
|||||||
void TimelineView::dragMoveEvent(QDragMoveEvent *event)
|
void TimelineView::dragMoveEvent(QDragMoveEvent *event)
|
||||||
{
|
{
|
||||||
TimelineViewMouseEvent timeline_event = CreateMouseEvent(
|
TimelineViewMouseEvent timeline_event = CreateMouseEvent(
|
||||||
event->pos(), Qt::NoButton, event->keyboardModifiers());
|
event->position().toPoint(), Qt::NoButton, event->modifiers());
|
||||||
|
|
||||||
timeline_event.set_mime_data(event->mimeData());
|
timeline_event.set_mime_data(event->mimeData());
|
||||||
timeline_event.SetEvent(event);
|
timeline_event.SetEvent(event);
|
||||||
@@ -385,7 +385,7 @@ void TimelineView::dragLeaveEvent(QDragLeaveEvent *event)
|
|||||||
void TimelineView::dropEvent(QDropEvent *event)
|
void TimelineView::dropEvent(QDropEvent *event)
|
||||||
{
|
{
|
||||||
TimelineViewMouseEvent timeline_event = CreateMouseEvent(
|
TimelineViewMouseEvent timeline_event = CreateMouseEvent(
|
||||||
event->pos(), Qt::NoButton, event->keyboardModifiers());
|
event->position().toPoint(), Qt::NoButton, event->modifiers());
|
||||||
|
|
||||||
timeline_event.set_mime_data(event->mimeData());
|
timeline_event.set_mime_data(event->mimeData());
|
||||||
timeline_event.SetEvent(event);
|
timeline_event.SetEvent(event);
|
||||||
@@ -446,7 +446,7 @@ void TimelineView::drawForeground(QPainter *painter, const QRectF &rect)
|
|||||||
static_cast<TrackReference::Type>(connected_track_type_)) {
|
static_cast<TrackReference::Type>(connected_track_type_)) {
|
||||||
int track_index = it.key().index();
|
int track_index = it.key().index();
|
||||||
|
|
||||||
foreach (const TimeRange &range, it.value()) {
|
for (const TimeRange &range : it.value()) {
|
||||||
painter->drawRect(time_to_scene(range.in()),
|
painter->drawRect(time_to_scene(range.in()),
|
||||||
get_track_y(track_index),
|
get_track_y(track_index),
|
||||||
time_to_scene(range.length()),
|
time_to_scene(range.length()),
|
||||||
@@ -594,7 +594,7 @@ TimelineCoordinate TimelineView::scene_to_coordinate(const QPointF &pt)
|
|||||||
|
|
||||||
TimelineViewMouseEvent TimelineView::CreateMouseEvent(QMouseEvent *event)
|
TimelineViewMouseEvent TimelineView::CreateMouseEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
return CreateMouseEvent(event->pos(), event->button(), event->modifiers());
|
return CreateMouseEvent(event->position().toPoint(), event->button(), event->modifiers());
|
||||||
}
|
}
|
||||||
|
|
||||||
TimelineViewMouseEvent
|
TimelineViewMouseEvent
|
||||||
|
|||||||
@@ -499,7 +499,7 @@ void SeekableWidget::deselect_all_markers()
|
|||||||
void SeekableWidget::set_marker_color(int c)
|
void SeekableWidget::set_marker_color(int c)
|
||||||
{
|
{
|
||||||
QVector<OakEngineMarker *> oak_markers;
|
QVector<OakEngineMarker *> oak_markers;
|
||||||
foreach (OakEngineMarker *marker,
|
for (OakEngineMarker *marker :
|
||||||
selection_manager_.get_selected_objects()) {
|
selection_manager_.get_selected_objects()) {
|
||||||
oak_markers.append(marker);
|
oak_markers.append(marker);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
***/
|
***/
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
#include "viewer.h"
|
#include "viewer.h"
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
@@ -172,11 +173,16 @@ const Rational k_video_playback_interval = Rational(1, 10);
|
|||||||
|
|
||||||
ViewerWidget::ViewerWidget(ViewerDisplayWidget *display, QWidget *parent)
|
ViewerWidget::ViewerWidget(ViewerDisplayWidget *display, QWidget *parent)
|
||||||
: super(false, true, parent)
|
: super(false, true, parent)
|
||||||
|
, overlay_(nullptr)
|
||||||
|
, info_chip_(nullptr)
|
||||||
|
, safe_frame_btn_(nullptr)
|
||||||
|
, overlay_zoom_index_(5)
|
||||||
, playback_speed_(0)
|
, playback_speed_(0)
|
||||||
, color_menu_enabled_(true)
|
, color_menu_enabled_(true)
|
||||||
, time_changed_from_timer_(false)
|
, time_changed_from_timer_(false)
|
||||||
, prequeuing_video_(false)
|
, prequeuing_video_(false)
|
||||||
, prequeuing_audio_(0)
|
, prequeuing_audio_(0)
|
||||||
|
, audio_processor_(oakengine_audio_processor_create())
|
||||||
, record_armed_(false)
|
, record_armed_(false)
|
||||||
, recording_(false)
|
, recording_(false)
|
||||||
, first_requeue_watcher_(nullptr)
|
, first_requeue_watcher_(nullptr)
|
||||||
@@ -185,11 +191,6 @@ ViewerWidget::ViewerWidget(ViewerDisplayWidget *display, QWidget *parent)
|
|||||||
, ignore_scrub_(0)
|
, ignore_scrub_(0)
|
||||||
, multicam_panel_(nullptr)
|
, multicam_panel_(nullptr)
|
||||||
, bridge_(new EngineEventBridge(this))
|
, bridge_(new EngineEventBridge(this))
|
||||||
, audio_processor_(oakengine_audio_processor_create())
|
|
||||||
, overlay_(nullptr)
|
|
||||||
, info_chip_(nullptr)
|
|
||||||
, safe_frame_btn_(nullptr)
|
|
||||||
, overlay_zoom_index_(5)
|
|
||||||
{
|
{
|
||||||
// Set up main layout
|
// Set up main layout
|
||||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||||
@@ -958,13 +959,13 @@ void ViewerWidget::detect_multicam_node(const Rational &time)
|
|||||||
const Rational seq_tb = sequence_timebase(seq);
|
const Rational seq_tb = sequence_timebase(seq);
|
||||||
|
|
||||||
// Prefer selected nodes
|
// Prefer selected nodes
|
||||||
for (OakEngineNode *n : qAsConst(node_view_selected_)) {
|
for (OakEngineNode *n : std::as_const(node_view_selected_)) {
|
||||||
// MultiCam check via facade predicate (replaces
|
// MultiCam check via facade predicate (replaces
|
||||||
// dynamic_cast<MultiCamNode*>)
|
// dynamic_cast<MultiCamNode*>)
|
||||||
if (oakengine_node_is_multicam(n)) {
|
if (oakengine_node_is_multicam(n)) {
|
||||||
multicam = n;
|
multicam = n;
|
||||||
// Found multicam, now try to find corresponding clip from selected timeline blocks
|
// Found multicam, now try to find corresponding clip from selected timeline blocks
|
||||||
for (OakEngineBlock *b : qAsConst(timeline_selected_blocks_)) {
|
for (OakEngineBlock *b : std::as_const(timeline_selected_blocks_)) {
|
||||||
// Clip check via facade predicate (replaces
|
// Clip check via facade predicate (replaces
|
||||||
// dynamic_cast<ClipBlock*>)
|
// dynamic_cast<ClipBlock*>)
|
||||||
if (oakengine_node_is_clip(
|
if (oakengine_node_is_clip(
|
||||||
@@ -983,7 +984,7 @@ void ViewerWidget::detect_multicam_node(const Rational &time)
|
|||||||
|
|
||||||
// Next, prefer multicam from selected block
|
// Next, prefer multicam from selected block
|
||||||
if (!multicam) {
|
if (!multicam) {
|
||||||
for (OakEngineBlock *b : qAsConst(timeline_selected_blocks_)) {
|
for (OakEngineBlock *b : std::as_const(timeline_selected_blocks_)) {
|
||||||
if (block_range_contains(b, seq_tb, time) &&
|
if (block_range_contains(b, seq_tb, time) &&
|
||||||
oakengine_node_is_clip(
|
oakengine_node_is_clip(
|
||||||
reinterpret_cast<OakEngineNode *>(b))) {
|
reinterpret_cast<OakEngineNode *>(b))) {
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent)
|
|||||||
, blank_shader_(nullptr)
|
, blank_shader_(nullptr)
|
||||||
, signal_cursor_color_(false)
|
, signal_cursor_color_(false)
|
||||||
, gizmos_(nullptr)
|
, gizmos_(nullptr)
|
||||||
|
, gizmo_params_(empty_video_params())
|
||||||
, current_gizmo_(nullptr)
|
, current_gizmo_(nullptr)
|
||||||
, gizmo_drag_started_(false)
|
, gizmo_drag_started_(false)
|
||||||
, show_subtitles_(true)
|
, show_subtitles_(true)
|
||||||
@@ -83,7 +84,6 @@ ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent)
|
|||||||
, add_band_(false)
|
, add_band_(false)
|
||||||
, queue_starved_(false)
|
, queue_starved_(false)
|
||||||
, text_edit_(nullptr)
|
, text_edit_(nullptr)
|
||||||
, gizmo_params_(empty_video_params())
|
|
||||||
{
|
{
|
||||||
connect(Core::instance(), &Core::tool_changed, this,
|
connect(Core::instance(), &Core::tool_changed, this,
|
||||||
&ViewerDisplayWidget::tool_changed);
|
&ViewerDisplayWidget::tool_changed);
|
||||||
@@ -1083,7 +1083,7 @@ void ViewerDisplayWidget::open_text_gizmo(void *text, QMouseEvent *event)
|
|||||||
|
|
||||||
// Start text cursor where the user clicked
|
// Start text cursor where the user clicked
|
||||||
if (event) {
|
if (event) {
|
||||||
QPoint click_pos = text_transform_inverted_.map(event->pos()) -
|
QPoint click_pos = text_transform_inverted_.map(event->position().toPoint()) -
|
||||||
text_edit_pos_.toPoint();
|
text_edit_pos_.toPoint();
|
||||||
text_edit_->setTextCursor(text_edit_->cursorForPosition(click_pos));
|
text_edit_->setTextCursor(text_edit_->cursorForPosition(click_pos));
|
||||||
}
|
}
|
||||||
@@ -1099,7 +1099,7 @@ bool ViewerDisplayWidget::on_mouse_press(QMouseEvent *event)
|
|||||||
{
|
{
|
||||||
if (is_hand_drag(event)) {
|
if (is_hand_drag(event)) {
|
||||||
// Handle hand drag
|
// Handle hand drag
|
||||||
hand_last_drag_pos_ = event->pos();
|
hand_last_drag_pos_ = event->position().toPoint();
|
||||||
hand_dragging_ = true;
|
hand_dragging_ = true;
|
||||||
emit hand_drag_started();
|
emit hand_drag_started();
|
||||||
inner_widget()->setCursor(Qt::ClosedHandCursor);
|
inner_widget()->setCursor(Qt::ClosedHandCursor);
|
||||||
@@ -1115,15 +1115,15 @@ bool ViewerDisplayWidget::on_mouse_press(QMouseEvent *event)
|
|||||||
Tool::k_addable_shape ||
|
Tool::k_addable_shape ||
|
||||||
Core::instance()->get_selected_addable_object() ==
|
Core::instance()->get_selected_addable_object() ==
|
||||||
Tool::k_addable_title)) {
|
Tool::k_addable_title)) {
|
||||||
add_band_start_ = event->pos();
|
add_band_start_ = event->position().toPoint();
|
||||||
add_band_end_ = add_band_start_;
|
add_band_end_ = add_band_start_;
|
||||||
add_band_ = true;
|
add_band_ = true;
|
||||||
|
|
||||||
} else if ((current_gizmo_ = try_gizmo_press(
|
} else if ((current_gizmo_ = try_gizmo_press(
|
||||||
gizmo_last_draw_transform_inverted_.map(
|
gizmo_last_draw_transform_inverted_.map(
|
||||||
event->pos())))) {
|
event->position().toPoint())))) {
|
||||||
// Handle gizmo click
|
// Handle gizmo click
|
||||||
gizmo_start_drag_ = event->pos();
|
gizmo_start_drag_ = event->position().toPoint();
|
||||||
gizmo_last_drag_ = gizmo_start_drag_;
|
gizmo_last_drag_ = gizmo_start_drag_;
|
||||||
const TimeRange gizmo_time = generate_gizmo_time();
|
const TimeRange gizmo_time = generate_gizmo_time();
|
||||||
oakengine_gizmo_set_globals(
|
oakengine_gizmo_set_globals(
|
||||||
@@ -1132,7 +1132,7 @@ bool ViewerDisplayWidget::on_mouse_press(QMouseEvent *event)
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Handle standard drag
|
// Handle standard drag
|
||||||
emit drag_started(event->pos());
|
emit drag_started(event->position().toPoint());
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -1146,10 +1146,10 @@ bool ViewerDisplayWidget::on_mouse_move(QMouseEvent *event)
|
|||||||
// Handle hand dragging
|
// Handle hand dragging
|
||||||
if (hand_dragging_) {
|
if (hand_dragging_) {
|
||||||
// Emit movement
|
// Emit movement
|
||||||
emit hand_drag_moved(event->x() - hand_last_drag_pos_.x(),
|
emit hand_drag_moved(event->position().toPoint().x() - hand_last_drag_pos_.x(),
|
||||||
event->y() - hand_last_drag_pos_.y());
|
event->position().toPoint().y() - hand_last_drag_pos_.y());
|
||||||
|
|
||||||
hand_last_drag_pos_ = event->pos();
|
hand_last_drag_pos_ = event->position().toPoint();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -1157,7 +1157,7 @@ bool ViewerDisplayWidget::on_mouse_move(QMouseEvent *event)
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
} else if (add_band_) {
|
} else if (add_band_) {
|
||||||
add_band_end_ = event->pos();
|
add_band_end_ = event->position().toPoint();
|
||||||
update();
|
update();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -1184,11 +1184,11 @@ bool ViewerDisplayWidget::on_mouse_move(QMouseEvent *event)
|
|||||||
gizmo_drag_started_ = true;
|
gizmo_drag_started_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QPointF v = screen_to_scene_point(event->pos());
|
QPointF v = screen_to_scene_point(event->position().toPoint());
|
||||||
switch (drag_behavior) {
|
switch (drag_behavior) {
|
||||||
case 1:
|
case 1:
|
||||||
v -= screen_to_scene_point(gizmo_last_drag_);
|
v -= screen_to_scene_point(gizmo_last_drag_);
|
||||||
gizmo_last_drag_ = event->pos();
|
gizmo_last_drag_ = event->position().toPoint();
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
v -= screen_to_scene_point(gizmo_start_drag_);
|
v -= screen_to_scene_point(gizmo_start_drag_);
|
||||||
@@ -1249,7 +1249,7 @@ bool ViewerDisplayWidget::on_mouse_double_click(QMouseEvent *event)
|
|||||||
if (text_edit_ && forward_mouse_event_to_text_edit(event)) {
|
if (text_edit_ && forward_mouse_event_to_text_edit(event)) {
|
||||||
return true;
|
return true;
|
||||||
} else if (event->button() == Qt::LeftButton && gizmos_) {
|
} else if (event->button() == Qt::LeftButton && gizmos_) {
|
||||||
QPointF ptr = transform_viewer_space_to_buffer_space(event->pos());
|
QPointF ptr = transform_viewer_space_to_buffer_space(event->position().toPoint());
|
||||||
OakEngineNode *gizmos_handle =
|
OakEngineNode *gizmos_handle =
|
||||||
gizmos_;
|
gizmos_;
|
||||||
// A text gizmo only exists on a text v3 node, so the node type id is
|
// A text gizmo only exists on a text v3 node, so the node type id is
|
||||||
@@ -1303,7 +1303,7 @@ void ViewerDisplayWidget::emit_color_at_cursor(QMouseEvent *e)
|
|||||||
|
|
||||||
if (texture_) {
|
if (texture_) {
|
||||||
QPointF pixel_pos =
|
QPointF pixel_pos =
|
||||||
generate_display_transform().inverted().map(e->pos());
|
generate_display_transform().inverted().map(e->position().toPoint());
|
||||||
oak_video_params tp = {};
|
oak_video_params tp = {};
|
||||||
oakengine_display_texture_get_params(texture_, &tp);
|
oakengine_display_texture_get_params(texture_, &tp);
|
||||||
pixel_pos /= (tp.divider > 0 ? tp.divider : 1);
|
pixel_pos /= (tp.divider > 0 ? tp.divider : 1);
|
||||||
@@ -1458,9 +1458,9 @@ template <typename T> void ViewerDisplayWidget::forward_drag_event_to_text_edit(
|
|||||||
if constexpr (std::is_same_v<T, QDragLeaveEvent>) {
|
if constexpr (std::is_same_v<T, QDragLeaveEvent>) {
|
||||||
text_edit_->dragLeaveEvent(e);
|
text_edit_->dragLeaveEvent(e);
|
||||||
} else {
|
} else {
|
||||||
T relay(adjust_pos_by_v_align(get_virtual_pos_for_text_edit(e->pos())).toPoint(),
|
T relay(adjust_pos_by_v_align(get_virtual_pos_for_text_edit(e->position().toPoint())).toPoint(),
|
||||||
e->possibleActions(), e->mimeData(), e->mouseButtons(),
|
e->possibleActions(), e->mimeData(), e->buttons(),
|
||||||
e->keyboardModifiers());
|
e->modifiers());
|
||||||
|
|
||||||
if (e->type() == QEvent::DragEnter) {
|
if (e->type() == QEvent::DragEnter) {
|
||||||
text_edit_->dragEnterEvent(static_cast<QDragEnterEvent *>(&relay));
|
text_edit_->dragEnterEvent(static_cast<QDragEnterEvent *>(&relay));
|
||||||
@@ -1484,12 +1484,12 @@ bool ViewerDisplayWidget::forward_mouse_event_to_text_edit(QMouseEvent *event,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Transform screen mouse coords to world mouse coords
|
// Transform screen mouse coords to world mouse coords
|
||||||
QPointF local_pos = get_virtual_pos_for_text_edit(event->pos());
|
QPointF local_pos = get_virtual_pos_for_text_edit(event->position().toPoint());
|
||||||
|
|
||||||
if (event->type() == QEvent::MouseMove &&
|
if (event->type() == QEvent::MouseMove &&
|
||||||
event->buttons() == Qt::NoButton) {
|
event->buttons() == Qt::NoButton) {
|
||||||
QPointF mapped =
|
QPointF mapped =
|
||||||
text_transform_inverted_.map(event->pos()) - text_edit_pos_;
|
text_transform_inverted_.map(event->position().toPoint()) - text_edit_pos_;
|
||||||
if (mapped.x() >= 0 && mapped.y() >= 0 &&
|
if (mapped.x() >= 0 && mapped.y() >= 0 &&
|
||||||
mapped.x() < text_edit_->width() &&
|
mapped.x() < text_edit_->width() &&
|
||||||
mapped.y() < text_edit_->height()) {
|
mapped.y() < text_edit_->height()) {
|
||||||
@@ -1504,7 +1504,7 @@ bool ViewerDisplayWidget::forward_mouse_event_to_text_edit(QMouseEvent *event,
|
|||||||
local_pos.y() < 0 || local_pos.y() >= text_edit_->height()) {
|
local_pos.y() < 0 || local_pos.y() >= text_edit_->height()) {
|
||||||
// Allow clicking other gizmos so the user can resize while the text editor is active
|
// Allow clicking other gizmos so the user can resize while the text editor is active
|
||||||
if ((current_gizmo_ = try_gizmo_press(
|
if ((current_gizmo_ = try_gizmo_press(
|
||||||
gizmo_last_draw_transform_inverted_.map(event->pos())))) {
|
gizmo_last_draw_transform_inverted_.map(event->position().toPoint())))) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
close_text_editor();
|
close_text_editor();
|
||||||
@@ -1515,8 +1515,8 @@ bool ViewerDisplayWidget::forward_mouse_event_to_text_edit(QMouseEvent *event,
|
|||||||
|
|
||||||
local_pos = adjust_pos_by_v_align(local_pos);
|
local_pos = adjust_pos_by_v_align(local_pos);
|
||||||
|
|
||||||
QMouseEvent derived(event->type(), local_pos, event->windowPos(),
|
QMouseEvent derived(event->type(), local_pos, event->scenePosition(),
|
||||||
event->screenPos(), event->button(), event->buttons(),
|
event->globalPosition(), event->button(), event->buttons(),
|
||||||
event->modifiers(), event->source());
|
event->modifiers(), event->source());
|
||||||
return forward_event_to_text_edit(&derived);
|
return forward_event_to_text_edit(&derived);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ void ViewerTextEditor::update_tool_bar(ViewerTextEditorToolBar *toolbar,
|
|||||||
}
|
}
|
||||||
|
|
||||||
QString style = f.fontStyleName().toString();
|
QString style = f.fontStyleName().toString();
|
||||||
QStringList styles = QFontDatabase().styles(family);
|
QStringList styles = QFontDatabase::styles(family);
|
||||||
if (!styles.isEmpty() && (style.isEmpty() || !styles.contains(style))) {
|
if (!styles.isEmpty() && (style.isEmpty() || !styles.contains(style))) {
|
||||||
// There seems to be no better way to find the "regular" style outside of this heuristic.
|
// There seems to be no better way to find the "regular" style outside of this heuristic.
|
||||||
// Feel free to add more if a font isn't working right.
|
// Feel free to add more if a font isn't working right.
|
||||||
@@ -298,8 +298,8 @@ void ViewerTextEditor::apply_style(QTextCharFormat *format,
|
|||||||
{
|
{
|
||||||
// NOTE: Windows appears to require setting weight and italic manually, while macOS and Linux are
|
// NOTE: Windows appears to require setting weight and italic manually, while macOS and Linux are
|
||||||
// perfectly fine with just the style name
|
// perfectly fine with just the style name
|
||||||
format->setFontWeight(QFontDatabase().weight(family, style));
|
format->setFontWeight(QFontDatabase::weight(family, style));
|
||||||
format->setFontItalic(QFontDatabase().italic(family, style));
|
format->setFontItalic(QFontDatabase::italic(family, style));
|
||||||
|
|
||||||
format->setFontStyleName(style);
|
format->setFontStyleName(style);
|
||||||
}
|
}
|
||||||
@@ -594,7 +594,7 @@ void ViewerTextEditorToolBar::update_font_style_list(const QString &family)
|
|||||||
|
|
||||||
style_combo_->blockSignals(true);
|
style_combo_->blockSignals(true);
|
||||||
style_combo_->clear();
|
style_combo_->clear();
|
||||||
QStringList l = QFontDatabase().styles(family);
|
QStringList l = QFontDatabase::styles(family);
|
||||||
foreach (const QString &style, l) {
|
foreach (const QString &style, l) {
|
||||||
style_combo_->addItem(style);
|
style_combo_->addItem(style);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,15 +164,15 @@ MainWindow::~MainWindow()
|
|||||||
|
|
||||||
void MainWindow::load_layout(const SerializedLayoutInfo &info)
|
void MainWindow::load_layout(const SerializedLayoutInfo &info)
|
||||||
{
|
{
|
||||||
foreach (OakEngineNode *folder, info.open_folders) {
|
for (OakEngineNode *folder : info.open_folders) {
|
||||||
open_folder(folder, true);
|
open_folder(folder, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (OakEngineNode *sequence, info.open_sequences) {
|
for (OakEngineNode *sequence : info.open_sequences) {
|
||||||
open_sequence(sequence, info.open_sequences.size() == 1);
|
open_sequence(sequence, info.open_sequences.size() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (OakEngineNode *viewer, info.open_viewers) {
|
for (OakEngineNode *viewer : info.open_viewers) {
|
||||||
open_node_in_viewer(viewer);
|
open_node_in_viewer(viewer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,15 +470,15 @@ void MainWindow::set_application_progress_status(ProgressStatus status)
|
|||||||
#if defined(Q_OS_WINDOWS)
|
#if defined(Q_OS_WINDOWS)
|
||||||
if (taskbar_interface_) {
|
if (taskbar_interface_) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case kProgressShow:
|
case k_progress_show:
|
||||||
taskbar_interface_->SetProgressState(
|
taskbar_interface_->SetProgressState(
|
||||||
reinterpret_cast<HWND>(this->winId()), TBPF_NORMAL);
|
reinterpret_cast<HWND>(this->winId()), TBPF_NORMAL);
|
||||||
break;
|
break;
|
||||||
case kProgressNone:
|
case k_progress_none:
|
||||||
taskbar_interface_->SetProgressState(
|
taskbar_interface_->SetProgressState(
|
||||||
reinterpret_cast<HWND>(this->winId()), TBPF_NOPROGRESS);
|
reinterpret_cast<HWND>(this->winId()), TBPF_NOPROGRESS);
|
||||||
break;
|
break;
|
||||||
case kProgressError:
|
case k_progress_error:
|
||||||
taskbar_interface_->SetProgressState(
|
taskbar_interface_->SetProgressState(
|
||||||
reinterpret_cast<HWND>(this->winId()), TBPF_ERROR);
|
reinterpret_cast<HWND>(this->winId()), TBPF_ERROR);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ if (BUILD_TESTS)
|
|||||||
)
|
)
|
||||||
set_tests_properties(oak_cli_transcode PROPERTIES
|
set_tests_properties(oak_cli_transcode PROPERTIES
|
||||||
SKIP_RETURN_CODE 2
|
SKIP_RETURN_CODE 2
|
||||||
|
TIMEOUT 300
|
||||||
)
|
)
|
||||||
add_test(NAME oak_cli_transcode_verify
|
add_test(NAME oak_cli_transcode_verify
|
||||||
COMMAND ${CMAKE_COMMAND} -DOUT=${CMAKE_CURRENT_BINARY_DIR}/oak_cli_transcode.mp4 -P ${CMAKE_CURRENT_SOURCE_DIR}/verify_transcode_mp4.cmake
|
COMMAND ${CMAKE_COMMAND} -DOUT=${CMAKE_CURRENT_BINARY_DIR}/oak_cli_transcode.mp4 -P ${CMAKE_CURRENT_SOURCE_DIR}/verify_transcode_mp4.cmake
|
||||||
|
|||||||
@@ -380,7 +380,7 @@ public:
|
|||||||
|
|
||||||
void shift(const Rational &diff)
|
void shift(const Rational &diff)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < array_.size(); i++) {
|
for (size_t i = 0; i < array_.size(); i++) {
|
||||||
array_[i] += diff;
|
array_[i] += diff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,280 @@
|
|||||||
|
# Eliminating EventBridge: Good First Issues
|
||||||
|
|
||||||
|
Goal: the app stops receiving engine events via `EngineEventBridge` /
|
||||||
|
`oakengine_event_subscribe`; the engine becomes a pure library (its internal
|
||||||
|
Qt signals are for its own use only).
|
||||||
|
|
||||||
|
Each issue is independent and can be claimed and shipped separately (do the
|
||||||
|
two infrastructure ones first — they simplify everything else).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ground rules
|
||||||
|
|
||||||
|
- **Run the tests before submitting, every time**:
|
||||||
|
```bash
|
||||||
|
cmake --build cmake-build-debug -j8 && cd cmake-build-debug && ctest -j4
|
||||||
|
```
|
||||||
|
Zero build errors and 122/122 tests passing are the definition of done,
|
||||||
|
plus the manual acceptance checks listed in each issue.
|
||||||
|
- Only three migration patterns, copy them:
|
||||||
|
- **A. app-internal Qt signal**: the event is app-initiated (playhead,
|
||||||
|
edits, undo) — emit an app-internal signal at the origin and re-point
|
||||||
|
subscribers to it.
|
||||||
|
- **B. keep async**: truly async events (tasks, caches, audio beats) go
|
||||||
|
through one dispatcher (issue 0b) that queues everything back to the
|
||||||
|
GUI thread.
|
||||||
|
- **C. delete**: the call site already knows — refresh inline and drop
|
||||||
|
the subscription.
|
||||||
|
- When done: remove the matching `bridge_->subscribe` /
|
||||||
|
`oakengine_event_subscribe` calls and check off the item here.
|
||||||
|
- Architecture background: `docs/zh/plans/eliminate-event-bridge.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Infrastructure (do first)
|
||||||
|
|
||||||
|
### issue 0a — Fix the audio event ID collision (real bug, half a day)
|
||||||
|
`engine/include/oakengine/events.h:210`: `AUDIO_MANAGER_OUTPUT_NOTIFY = 141`
|
||||||
|
collides with `PLAYBACK_CACHE_INVALIDATED = 141` at :213; the case 141 in
|
||||||
|
events.cpp only performs the PlaybackCache conversion,
|
||||||
|
`AudioManager::output_notify` is never wired up, and the audio keep-alive
|
||||||
|
subscription in `viewer.cpp:1406` never actually fires.
|
||||||
|
|
||||||
|
In other words: `AUDIO_MANAGER_OUTPUT_NOTIFY` shares ID 141 with
|
||||||
|
`PLAYBACK_CACHE_INVALIDATED`; the audio notification is never wired up, so
|
||||||
|
the viewer's audio keep-alive subscription never fires.
|
||||||
|
- Change: assign OUTPUT_NOTIFY a fresh ID (>=144) and wire up AudioManager
|
||||||
|
in events.cpp.
|
||||||
|
- Acceptance: audio keeps playing past 5 seconds on long footage without
|
||||||
|
cutting out.
|
||||||
|
- **Required: build + ctest all green (see ground rules).**
|
||||||
|
|
||||||
|
### issue 0b — Single async event dispatcher (1 day)
|
||||||
|
Create `app/asyncengineevents.{h,cpp}` (a QObject held as a Core singleton)
|
||||||
|
subscribing only to the TASK family (120–127),
|
||||||
|
PLAYBACK_CACHE_VALIDATED/INVALIDATED, FRAME_CACHE_INVALIDATED, and
|
||||||
|
AUDIO_OUTPUT_PARAMS/NOTIFY. C callbacks just do
|
||||||
|
`QMetaObject::invokeMethod(this, ..., Qt::QueuedConnection)` and then emit
|
||||||
|
typed Qt signals.
|
||||||
|
|
||||||
|
In other words: one dispatcher subscribing only to the truly async events;
|
||||||
|
C callbacks just queue to the GUI thread and re-emit typed Qt signals.
|
||||||
|
- Acceptance: transcode once — the status bar progress still works. All
|
||||||
|
later (b) migrations must use it instead of their own subscriptions.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
### issue 0c — App-internal PlaybackController (1 day)
|
||||||
|
Create `app/playback/playbackcontroller.{h,cpp}` with a
|
||||||
|
`playhead_changed(oak::Node viewer, Rational)` signal. Route every
|
||||||
|
`oakengine_viewer_set_playhead` call site in the app (ViewerWidget playback
|
||||||
|
loop, timelinewidget, timeruler, multicam, export) through it and emit the
|
||||||
|
signal there.
|
||||||
|
|
||||||
|
In other words: one app-side controller that every
|
||||||
|
`oakengine_viewer_set_playhead` call site goes through; it re-broadcasts
|
||||||
|
`playhead_changed` as an app-internal Qt signal.
|
||||||
|
- Acceptance: all views keep updating during playback/drag/seek (bridge may
|
||||||
|
coexist until later issues migrate each subscriber).
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Playhead migrations (pattern A, about half a day each)
|
||||||
|
|
||||||
|
### issue 1 — timebasedview playhead subscription
|
||||||
|
`app/widget/timebased/timebasedview.cpp:168` (raw C callback). Reconnect to
|
||||||
|
`PlaybackController::playhead_changed`.
|
||||||
|
- Acceptance: the ruler playhead line moves during playback; no raw C
|
||||||
|
subscription remains.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
### issue 2 — NodeParamViewWidgetBridge playhead
|
||||||
|
`app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp:1140` (raw C).
|
||||||
|
- Acceptance: keyframe-interpolated slider values follow the playhead
|
||||||
|
during playback.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
### issue 3 — NodeParamViewKeyframeControl playhead
|
||||||
|
`app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp:222` (raw C).
|
||||||
|
- Acceptance: prev/next/toggle keyframe buttons have the correct state as
|
||||||
|
the playhead moves.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
### issue 4 — NodeParamViewConnectedLabel playhead
|
||||||
|
`app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp:131` (raw C).
|
||||||
|
- Acceptance: the value tree refreshes with the playhead.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
### issue 5 — ExportDialog playhead
|
||||||
|
`app/dialog/export/export.cpp:311` (raw C).
|
||||||
|
- Acceptance: the export dialog's in/out times stay in sync with the
|
||||||
|
playhead.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
### issue 6 — timelinewidget / viewerdisplay playhead family (bridge subscriptions)
|
||||||
|
`app/widget/timelinewidget/timelinewidget.cpp` (around :741-743) and the
|
||||||
|
related viewerdisplay subscriptions. Reconnect to PlaybackController.
|
||||||
|
- Acceptance: timeline timecode/playhead position display correctly.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Undo & modified migrations (pattern A, about half a day each)
|
||||||
|
|
||||||
|
### issue 7 — historywidget UNDO_INDEX_CHANGED
|
||||||
|
`app/widget/history/historywidget.cpp:33,127` (two raw C callbacks).
|
||||||
|
Emit an app-internal `undo_index_changed(int)` at Core's undo/redo/push
|
||||||
|
exit points and reconnect historywidget to it.
|
||||||
|
- Acceptance: after undo/redo the history list resets its model and selects
|
||||||
|
the correct row.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
### issue 8 — core.cpp PROJECT_MODIFIED_CHANGED
|
||||||
|
`app/core.cpp:1108` (raw C). The modified flag is driven by the undo stack,
|
||||||
|
so the app can derive it at push/undo/redo/load; drive `setWindowModified`
|
||||||
|
from an app-internal signal instead.
|
||||||
|
- Acceptance: the title bar modified marker is correct after
|
||||||
|
edit/undo/save.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Structure migrations (patterns C/A, 0.5–1 day each)
|
||||||
|
|
||||||
|
### issue 9 — seekablewidget marker/workarea subscriptions
|
||||||
|
`app/widget/seekable/seekablewidget.cpp:95-101,146-154`.
|
||||||
|
Edits go through undo commands: call `viewport()->update()` directly where
|
||||||
|
the command runs; also refresh after undo (reuse the issue 7 signal).
|
||||||
|
- Acceptance: the ruler refreshes immediately after adding/removing markers
|
||||||
|
or changing the workarea; undo is equally correct.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
### issue 10 — resizabletimelinescrollbar marker/workarea
|
||||||
|
`app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp:83-98,124-129`.
|
||||||
|
- Acceptance: markers render correctly on the scrollbar.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
### issue 11 — nodeviewitem label/color/message/array subscriptions
|
||||||
|
`app/widget/nodeview/nodeviewitem.cpp:85-119`.
|
||||||
|
label/color edits refresh in place; array size goes through the structure
|
||||||
|
command call sites; undo is covered by the issue 7 signal.
|
||||||
|
- Acceptance: node blocks refresh immediately after rename/recolor/array
|
||||||
|
add/remove.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
### issue 12 — NodeParamViewItem / arraywidget / keyframecontrol bridge subscriptions
|
||||||
|
`app/widget/nodeparamview/nodeparamviewitem.cpp:95-296`,
|
||||||
|
`nodeparamviewarraywidget.cpp:43-45`, and the keyframe family in
|
||||||
|
keyframecontrol.
|
||||||
|
- Acceptance: parameter item label/flags/array/keyframe buttons refresh
|
||||||
|
correctly with edits and undo.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
### issue 13 — nodeparamviewwidgetbridge parameter value subscriptions
|
||||||
|
`app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp:161-178`.
|
||||||
|
The slider edit path sets values directly; re-read uniformly after
|
||||||
|
undo/load.
|
||||||
|
- Acceptance: slider values are correct after edit, undo, and load.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
### issue 14 — NodeParamView group passthrough / context subscriptions
|
||||||
|
`app/widget/nodeparamview/nodeparamview.cpp:189-205,350-352,871-874`.
|
||||||
|
- Acceptance: the parameter panel rebuilds correctly when groups open/close
|
||||||
|
or nodes are added to/removed from contexts.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
### issue 15 — nodeviewcontext node/edge add-remove subscriptions (structure core)
|
||||||
|
`app/widget/nodeview/nodeviewcontext.cpp:99-138,180-183,427-430`.
|
||||||
|
Additions/removals come from: app edit commands (handled at call sites),
|
||||||
|
undo (rebuild covered by the issue 7 signal), and project load (add a new
|
||||||
|
"project load finished" hook — Core broadcasts it after TaskDialog success,
|
||||||
|
replacing the batch event-driven graph build).
|
||||||
|
- Acceptance: node/edge add/remove refreshes immediately; undo restores
|
||||||
|
correctly; reopening a project shows the complete view.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
### issue 16 — nodeview NODE_REMOVED_FROM_GRAPH
|
||||||
|
`app/widget/nodeview/nodeview.cpp:91,1911` and `mainwindow.cpp:58,351`.
|
||||||
|
Handle at the delete-node command site plus the undo signal.
|
||||||
|
- Acceptance: after deleting a node its item/ViewerPanel closes; undo
|
||||||
|
restores it.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
### issue 17 — timelinewidget track/block structure subscriptions
|
||||||
|
`app/widget/timelinewidget/timelinewidget.cpp:665-753,2177-2228`.
|
||||||
|
block/track add/remove creates/removes items directly at the timeline
|
||||||
|
command sites; undo and load rebuild uniformly.
|
||||||
|
- Acceptance: clip/track add/remove refreshes immediately; undo/load is
|
||||||
|
correct.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
### issue 18 — trackviewitem index/muted
|
||||||
|
`app/widget/timelinewidget/trackview/trackviewitem.cpp:66,117`.
|
||||||
|
- Acceptance: track move/mute state refreshes immediately.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
### issue 19 — projectviewmodel folder/label subscriptions
|
||||||
|
`app/panel/project/projectviewmodel.cpp:45-61,505-512`.
|
||||||
|
Import/create-folder/rename go through the command sites; on load
|
||||||
|
completion do a uniform model reset (reuse the issue 15 hook).
|
||||||
|
- Acceptance: the project browser refreshes immediately on add/remove/
|
||||||
|
rename.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
### issue 20 — misc leftovers
|
||||||
|
multicamwidget (:107/114 raw C), manageddisplay (OCIO, :143),
|
||||||
|
audiowaveformview (:57,76), viewerdisplay (subtitles, :100,264),
|
||||||
|
panel/timebased (label, :162).
|
||||||
|
- Acceptance: each UI refreshes with edits.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Async migrations (pattern B, about half a day each, depends on 0b)
|
||||||
|
|
||||||
|
### issue 21 — mainstatusbar / taskmanager / taskviewitem / task dialog
|
||||||
|
`app/window/mainwindow/mainstatusbar.cpp:88-146`,
|
||||||
|
`app/panel/taskmanager/taskmanager.cpp:41-56`,
|
||||||
|
`app/widget/taskview/taskviewitem.cpp:82-88`,
|
||||||
|
`app/dialog/task/task.cpp:47-48`. Reconnect all of them to the dispatcher's
|
||||||
|
task signals.
|
||||||
|
- Acceptance: transcode/export task progress bars and the task list update
|
||||||
|
in real time.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
### issue 22 — timeruler cache bar + viewer cache invalidated
|
||||||
|
`app/widget/timeruler/timeruler.cpp:82-113`,
|
||||||
|
`app/widget/viewer/viewer.cpp:366-394`.
|
||||||
|
- Acceptance: the green cache bar grows with background rendering during
|
||||||
|
playback; edits invalidate the cache bar correctly.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
### issue 23 — viewer audio keep-alive (AUDIO_OUTPUT_NOTIFY)
|
||||||
|
`app/widget/viewer/viewer.cpp:1406` (raw C, currently broken by the ID
|
||||||
|
collision). Depends on 0a+0b.
|
||||||
|
- Acceptance: audio keeps playing continuously on long footage.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Final teardown (do last)
|
||||||
|
|
||||||
|
### issue 24 — Delete EngineEventBridge
|
||||||
|
After every issue above is checked off:
|
||||||
|
`grep -rn "EngineEventBridge\|bridge_->subscribe" app` should find zero
|
||||||
|
references. Delete `app/engineeventbridge.{h,cpp}` and its CMake entries.
|
||||||
|
- Acceptance: build passes; full functional regression
|
||||||
|
(playback/node graph/timeline/params/history/tasks).
|
||||||
|
- **Required: build + ctest all green + complete manual regression.**
|
||||||
|
|
||||||
|
### issue 25 — Narrow the engine event surface
|
||||||
|
`engine/src/capi/events.cpp` keeps only the async events the dispatcher
|
||||||
|
needs plus UNDO_INDEX_CHANGED / PROJECT_MODIFIED_CHANGED (the minimal
|
||||||
|
exception reserved for external writers such as a future AI assistant);
|
||||||
|
the synchronous event constants in `engine/include/oakengine/events.h` are
|
||||||
|
deleted or moved to an internal header; the engine's internal Qt signals
|
||||||
|
stay unchanged.
|
||||||
|
- Acceptance: ctest all green; nm confirms the C ABI no longer exposes the
|
||||||
|
synchronous event subscription surface.
|
||||||
|
- **Required: build + ctest all green.**
|
||||||
@@ -114,6 +114,9 @@ target_include_directories(oakengine
|
|||||||
target_link_libraries(oakengine PUBLIC ${OLIVE_LIBRARIES} OfxHost)
|
target_link_libraries(oakengine PUBLIC ${OLIVE_LIBRARIES} OfxHost)
|
||||||
# OAKENGINE_BUILD marks the library side of the C ABI export macros (dllexport)
|
# OAKENGINE_BUILD marks the library side of the C ABI export macros (dllexport)
|
||||||
target_compile_definitions(oakengine-obj PRIVATE ${OLIVE_DEFINITIONS} OAKENGINE_BUILD)
|
target_compile_definitions(oakengine-obj PRIVATE ${OLIVE_DEFINITIONS} OAKENGINE_BUILD)
|
||||||
|
# Consumers that link the object library directly (app, tests, Windows oakgl)
|
||||||
|
# must see plain C declarations, not __declspec(dllimport) ones.
|
||||||
|
target_compile_definitions(oakengine-obj INTERFACE OAKENGINE_STATIC)
|
||||||
target_compile_options(oakengine-obj PRIVATE ${OLIVE_COMPILE_OPTIONS})
|
target_compile_options(oakengine-obj PRIVATE ${OLIVE_COMPILE_OPTIONS})
|
||||||
|
|
||||||
# Version script: only oakengine_* + render-backend plugin ABI are exported;
|
# Version script: only oakengine_* + render-backend plugin ABI are exported;
|
||||||
|
|||||||
@@ -324,6 +324,14 @@ bool FFmpegEncoder::open()
|
|||||||
|
|
||||||
bool FFmpegEncoder::write_frame(FramePtr frame, Rational time)
|
bool FFmpegEncoder::write_frame(FramePtr frame, Rational time)
|
||||||
{
|
{
|
||||||
|
// The render worker pool finishes tickets without a result when no
|
||||||
|
// worker is available (or the worker crashed); a null frame must fail
|
||||||
|
// the encode cleanly instead of crashing the export task.
|
||||||
|
if (!frame) {
|
||||||
|
qWarning() << "FFmpegEncoder::write_frame called with null frame";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// We may need to convert this frame to a frame that the bridge will understand
|
// We may need to convert this frame to a frame that the bridge will understand
|
||||||
if (frame->format() != video_conversion_fmt_) {
|
if (frame->format() != video_conversion_fmt_) {
|
||||||
frame = frame->convert(video_conversion_fmt_);
|
frame = frame->convert(video_conversion_fmt_);
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ void Html::write_char_format(QString *style, const QTextCharFormat &fmt)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fmt.foreground().style() != Qt::NoBrush) {
|
if (fmt.foreground().style() != Qt::NoBrush) {
|
||||||
const QColor &color = fmt.foreground().color();
|
const QColor color = fmt.foreground().color();
|
||||||
QString cs;
|
QString cs;
|
||||||
|
|
||||||
if (color.alpha() == 255) {
|
if (color.alpha() == 255) {
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ public:
|
|||||||
~Arena()
|
~Arena()
|
||||||
{
|
{
|
||||||
std::list<Element *> copy = lent_elements_;
|
std::list<Element *> copy = lent_elements_;
|
||||||
foreach (Element *e, copy) {
|
for (Element *e : copy) {
|
||||||
e->release();
|
e->release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,7 +348,7 @@ public:
|
|||||||
QMutexLocker locker(&lock_);
|
QMutexLocker locker(&lock_);
|
||||||
|
|
||||||
// Attempt to get an element from an arena
|
// Attempt to get an element from an arena
|
||||||
foreach (Arena *a, arenas_) {
|
for (Arena *a : arenas_) {
|
||||||
ElementPtr e = a->Get();
|
ElementPtr e = a->Get();
|
||||||
|
|
||||||
if (e) {
|
if (e) {
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ PixelFormat OIIOUtils::get_format_from_oiio_basetype(OIIO::TypeDesc::BASETYPE ty
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case OIIO::TypeDesc::UNKNOWN:
|
case OIIO::TypeDesc::UNKNOWN:
|
||||||
case OIIO::TypeDesc::NONE:
|
case OIIO::TypeDesc::NONE:
|
||||||
|
#if OIIO_VERSION >= 20500
|
||||||
|
case OIIO::TypeDesc::USTRINGHASH:
|
||||||
|
#endif
|
||||||
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OIIO::TypeDesc::INT8:
|
case OIIO::TypeDesc::INT8:
|
||||||
|
|||||||
@@ -544,11 +544,12 @@ void EngineCore::save_autorecovery()
|
|||||||
{
|
{
|
||||||
QFile realname_file(project_autorecovery_dir.filePath(
|
QFile realname_file(project_autorecovery_dir.filePath(
|
||||||
QStringLiteral("realname.txt")));
|
QStringLiteral("realname.txt")));
|
||||||
realname_file.open(QFile::WriteOnly);
|
if (realname_file.open(QFile::WriteOnly)) {
|
||||||
realname_file.write(
|
realname_file.write(
|
||||||
open_project_->pretty_filename().toUtf8());
|
open_project_->pretty_filename().toUtf8());
|
||||||
realname_file.close();
|
realname_file.close();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int64_t max_recoveries_per_file =
|
int64_t max_recoveries_per_file =
|
||||||
OAK_CONFIG("AutorecoveryMaximum").toLongLong();
|
OAK_CONFIG("AutorecoveryMaximum").toLongLong();
|
||||||
|
|||||||
@@ -30,7 +30,11 @@
|
|||||||
* is still built with default symbol visibility, so the legacy C++ symbols
|
* is still built with default symbol visibility, so the legacy C++ symbols
|
||||||
* remain exported alongside the C ABI.
|
* remain exported alongside the C ABI.
|
||||||
*/
|
*/
|
||||||
#if defined(_WIN32) || defined(__CYGWIN__)
|
#if defined(OAKENGINE_STATIC)
|
||||||
|
/* Internal consumers link the engine object files directly (oakengine-obj)
|
||||||
|
instead of the shared library; no dllimport/dllexport is wanted. */
|
||||||
|
#define OAKENGINE_API
|
||||||
|
#elif defined(_WIN32) || defined(__CYGWIN__)
|
||||||
#ifdef OAKENGINE_BUILD
|
#ifdef OAKENGINE_BUILD
|
||||||
#define OAKENGINE_API __declspec(dllexport)
|
#define OAKENGINE_API __declspec(dllexport)
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -2386,14 +2386,14 @@ TimeRange Node::transform_time_to(TimeRange time, Node *target,
|
|||||||
Node *from = this;
|
Node *from = this;
|
||||||
Node *to = target;
|
Node *to = target;
|
||||||
|
|
||||||
if (dir == k_transform_towards_input) {
|
if (dir == k_towards_input) {
|
||||||
std::swap(from, to);
|
std::swap(from, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::list<NodeInput> path = find_path(from, to, path_index);
|
std::list<NodeInput> path = find_path(from, to, path_index);
|
||||||
|
|
||||||
if (!path.empty()) {
|
if (!path.empty()) {
|
||||||
if (dir == k_transform_towards_input) {
|
if (dir == k_towards_input) {
|
||||||
for (auto it = path.crbegin(); it != path.crend(); it++) {
|
for (auto it = path.crbegin(); it != path.crend(); it++) {
|
||||||
const NodeInput &i = (*it);
|
const NodeInput &i = (*it);
|
||||||
time = i.node()->input_time_adjustment(i.input(), i.element(),
|
time = i.node()->input_time_adjustment(i.input(), i.element(),
|
||||||
@@ -2614,7 +2614,7 @@ void Node::invalidate_from_keyframe_time_change()
|
|||||||
|
|
||||||
// Invalidate entire area surrounding the keyframe (either where it currently is, or where it used to be before it
|
// Invalidate entire area surrounding the keyframe (either where it currently is, or where it used to be before it
|
||||||
// was resorted in the if block above)
|
// was resorted in the if block above)
|
||||||
foreach (const TimeRange &r, invalidate_range) {
|
for (const TimeRange &r : invalidate_range) {
|
||||||
parameter_value_changed(key->key_track_ref().input(), r);
|
parameter_value_changed(key->key_track_ref().input(), r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -929,8 +929,8 @@ public:
|
|||||||
static QString get_category_name(const CategoryID &c);
|
static QString get_category_name(const CategoryID &c);
|
||||||
|
|
||||||
enum TransformTimeDirection {
|
enum TransformTimeDirection {
|
||||||
k_transform_towards_input,
|
k_towards_input,
|
||||||
k_transform_towards_output
|
k_towards_output
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -232,8 +232,6 @@ QHash<QString, QVariant> build_default_values(
|
|||||||
ofx_type == kOfxParamTypePushButton) {
|
ofx_type == kOfxParamTypePushButton) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto &props = param.second->getProperties();
|
|
||||||
bool is_secret = props.getIntProperty(kOfxParamPropSecret) != 0;
|
|
||||||
const QString input_id =
|
const QString input_id =
|
||||||
QString::fromStdString(param.second->getName());
|
QString::fromStdString(param.second->getName());
|
||||||
if (input_id.isEmpty()) {
|
if (input_id.isEmpty()) {
|
||||||
@@ -263,10 +261,10 @@ clip_label_for_name(const std::string &name,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (desc) {
|
if (desc) {
|
||||||
const std::string &label =
|
const std::string ¶m_label =
|
||||||
desc->getProps().getStringProperty(kOfxPropLabel);
|
desc->getProps().getStringProperty(kOfxPropLabel);
|
||||||
if (!label.empty()) {
|
if (!param_label.empty()) {
|
||||||
return QString::fromStdString(label);
|
return QString::fromStdString(param_label);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,9 +430,9 @@ olive::plugin::PluginNode::PluginNode(OFX::Host::ImageEffect::Instance *plugin)
|
|||||||
const int value_count = props.getDimension(kOfxParamPropChoiceEnum);
|
const int value_count = props.getDimension(kOfxParamPropChoiceEnum);
|
||||||
|
|
||||||
for (int i = 0; i < label_count; ++i) {
|
for (int i = 0; i < label_count; ++i) {
|
||||||
const std::string &label =
|
const std::string &choice_label =
|
||||||
props.getStringProperty(kOfxParamPropChoiceOption, i);
|
props.getStringProperty(kOfxParamPropChoiceOption, i);
|
||||||
option_labels.append(QString::fromStdString(label));
|
option_labels.append(QString::fromStdString(choice_label));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < value_count; ++i) {
|
for (int i = 0; i < value_count; ++i) {
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual void process_samples(const NodeValueRow &values,
|
virtual void process_samples(const NodeValueRow &values,
|
||||||
const SampleBuffer &input, SampleBuffer &output,
|
const SampleBuffer &input, SampleBuffer &output,
|
||||||
int index) const;
|
int index) const override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief If Value() pushes a GenerateJob, override this function for the image to create
|
* @brief If Value() pushes a GenerateJob, override this function for the image to create
|
||||||
@@ -71,7 +71,7 @@ public:
|
|||||||
*
|
*
|
||||||
* The destination buffer. It will already be allocated and ready for writing to.
|
* The destination buffer. It will already be allocated and ready for writing to.
|
||||||
*/
|
*/
|
||||||
virtual void generate_frame(FramePtr frame, const GenerateJob &job) const;
|
virtual void generate_frame(FramePtr frame, const GenerateJob &job) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString sub_category_;
|
QString sub_category_;
|
||||||
|
|||||||
@@ -377,8 +377,8 @@ void Footage::value(const NodeValueRow &value, const NodeGlobals &globals,
|
|||||||
this, QStringLiteral("length"));
|
this, QStringLiteral("length"));
|
||||||
|
|
||||||
// Push each stream as a footage job
|
// Push each stream as a footage job
|
||||||
for (int i = 0; i < get_total_stream_count(); i++) {
|
for (int si = 0; si < get_total_stream_count(); si++) {
|
||||||
Track::Reference ref = get_reference_from_real_index(i);
|
Track::Reference ref = get_reference_from_real_index(si);
|
||||||
FootageJob job(globals.time(), decoder_, filename(), ref.type(),
|
FootageJob job(globals.time(), decoder_, filename(), ref.type(),
|
||||||
get_length(), globals.loop_mode());
|
get_length(), globals.loop_mode());
|
||||||
|
|
||||||
@@ -427,9 +427,10 @@ void Footage::value(const NodeValueRow &value, const NodeGlobals &globals,
|
|||||||
ProxyManager::k_proxy_ready &&
|
ProxyManager::k_proxy_ready &&
|
||||||
ProxyManager::proxy_filename_has_audio(proxy_path_)) {
|
ProxyManager::proxy_filename_has_audio(proxy_path_)) {
|
||||||
int audio_rank = 0;
|
int audio_rank = 0;
|
||||||
for (int i = 0; i < get_total_stream_count(); i++) {
|
for (int sj = 0; sj < get_total_stream_count(); sj++) {
|
||||||
const Track::Reference other =
|
const Track::Reference other =
|
||||||
get_reference_from_real_index(i);
|
get_reference_from_real_index(sj);
|
||||||
|
|
||||||
if (other.type() == Track::k_audio &&
|
if (other.type() == Track::k_audio &&
|
||||||
get_audio_params(other.index()).stream_index() <
|
get_audio_params(other.index()).stream_index() <
|
||||||
ap.stream_index()) {
|
ap.stream_index()) {
|
||||||
@@ -448,8 +449,8 @@ void Footage::value(const NodeValueRow &value, const NodeGlobals &globals,
|
|||||||
// Media is offline: push a generated warning frame for each video
|
// Media is offline: push a generated warning frame for each video
|
||||||
// stream so missing media is clearly visible in the timeline instead
|
// stream so missing media is clearly visible in the timeline instead
|
||||||
// of a transparent/black hole. generate_frame() draws the slat.
|
// of a transparent/black hole. generate_frame() draws the slat.
|
||||||
for (int i = 0; i < get_total_stream_count(); i++) {
|
for (int si = 0; si < get_total_stream_count(); si++) {
|
||||||
Track::Reference ref = get_reference_from_real_index(i);
|
Track::Reference ref = get_reference_from_real_index(si);
|
||||||
if (ref.type() != Track::k_video) {
|
if (ref.type() != Track::k_video) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ void SerializedLayoutInfo::to_xml(QXmlStreamWriter *writer) const
|
|||||||
|
|
||||||
writer->writeStartElement(QStringLiteral("folders"));
|
writer->writeStartElement(QStringLiteral("folders"));
|
||||||
|
|
||||||
foreach (Folder *folder, open_folders) {
|
for (Folder *folder : open_folders) {
|
||||||
writer->writeTextElement(
|
writer->writeTextElement(
|
||||||
QStringLiteral("folder"),
|
QStringLiteral("folder"),
|
||||||
QString::number(reinterpret_cast<quintptr>(folder)));
|
QString::number(reinterpret_cast<quintptr>(folder)));
|
||||||
@@ -38,7 +38,7 @@ void SerializedLayoutInfo::to_xml(QXmlStreamWriter *writer) const
|
|||||||
|
|
||||||
writer->writeStartElement(QStringLiteral("timeline"));
|
writer->writeStartElement(QStringLiteral("timeline"));
|
||||||
|
|
||||||
foreach (Sequence *sequence, open_sequences) {
|
for (Sequence *sequence : open_sequences) {
|
||||||
writer->writeTextElement(
|
writer->writeTextElement(
|
||||||
QStringLiteral("sequence"),
|
QStringLiteral("sequence"),
|
||||||
QString::number(reinterpret_cast<quintptr>(sequence)));
|
QString::number(reinterpret_cast<quintptr>(sequence)));
|
||||||
@@ -48,7 +48,7 @@ void SerializedLayoutInfo::to_xml(QXmlStreamWriter *writer) const
|
|||||||
|
|
||||||
writer->writeStartElement(QStringLiteral("viewers"));
|
writer->writeStartElement(QStringLiteral("viewers"));
|
||||||
|
|
||||||
foreach (ViewerOutput *viewer, open_viewers) {
|
for (ViewerOutput *viewer : open_viewers) {
|
||||||
writer->writeTextElement(
|
writer->writeTextElement(
|
||||||
QStringLiteral("viewer"),
|
QStringLiteral("viewer"),
|
||||||
QString::number(reinterpret_cast<quintptr>(viewer)));
|
QString::number(reinterpret_cast<quintptr>(viewer)));
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include "timeformat.h"
|
#include "timeformat.h"
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
|
#include <QTimeZone>
|
||||||
|
|
||||||
namespace olive
|
namespace olive
|
||||||
{
|
{
|
||||||
@@ -75,7 +76,8 @@ void TimeFormatNode::value(const NodeValueRow &value,
|
|||||||
qint64 ms_since_epoch = value[k_time_input].to_double() * 1000;
|
qint64 ms_since_epoch = value[k_time_input].to_double() * 1000;
|
||||||
bool time_is_local = value[k_local_time_input].to_bool();
|
bool time_is_local = value[k_local_time_input].to_bool();
|
||||||
QDateTime dt = QDateTime::fromMSecsSinceEpoch(
|
QDateTime dt = QDateTime::fromMSecsSinceEpoch(
|
||||||
ms_since_epoch, time_is_local ? Qt::LocalTime : Qt::UTC);
|
ms_since_epoch,
|
||||||
|
time_is_local ? QTimeZone::systemTimeZone() : QTimeZone::utc());
|
||||||
QString format = value[k_format_input].to_string();
|
QString format = value[k_format_input].to_string();
|
||||||
QString output = dt.toString(format);
|
QString output = dt.toString(format);
|
||||||
table->push(NodeValue(NodeValue::k_text, output, this));
|
table->push(NodeValue(NodeValue::k_text, output, this));
|
||||||
|
|||||||
@@ -502,9 +502,7 @@ void NodeTraverser::resolve_jobs(NodeValue &val)
|
|||||||
}
|
}
|
||||||
|
|
||||||
val.set_value(tex);
|
val.set_value(tex);
|
||||||
} else if (plugin::PluginJob *plugin_job =
|
} else if (dynamic_cast<plugin::PluginJob *>(base_job)) {
|
||||||
dynamic_cast<plugin::PluginJob *>(
|
|
||||||
base_job)) {
|
|
||||||
VideoParams tex_params = job_tex->params();
|
VideoParams tex_params = job_tex->params();
|
||||||
// Force internal working format (F32) for plugin processing,
|
// Force internal working format (F32) for plugin processing,
|
||||||
// matching FootageJob/GenerateJob behavior.
|
// matching FootageJob/GenerateJob behavior.
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
/* Render backend plugin ABI (oakgl / oakvulkan dlopen) */
|
/* Render backend plugin ABI (oakgl / oakvulkan dlopen) */
|
||||||
_ZN5olive8Renderer*;
|
_ZN5olive8Renderer*;
|
||||||
_ZTVN5olive8RendererE;
|
_ZTVN5olive8RendererE;
|
||||||
|
_ZTIN5olive8RendererE;
|
||||||
|
_ZTSN5olive8RendererE;
|
||||||
_ZN5olive11VideoParams19get_bytes_per_pixelE*;
|
_ZN5olive11VideoParams19get_bytes_per_pixelE*;
|
||||||
_ZN5olive11VideoParams21get_bytes_per_channelE*;
|
_ZN5olive11VideoParams21get_bytes_per_channelE*;
|
||||||
_ZN5olive13FileFunctions19read_file_as_stringE*;
|
_ZN5olive13FileFunctions19read_file_as_stringE*;
|
||||||
|
|||||||
@@ -37,8 +37,11 @@ static const char *pixel_depth_to_ofx(core::PixelFormat format)
|
|||||||
return kOfxBitDepthShort;
|
return kOfxBitDepthShort;
|
||||||
case core::PixelFormat::f16:
|
case core::PixelFormat::f16:
|
||||||
return kOfxBitDepthHalf;
|
return kOfxBitDepthHalf;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
case core::PixelFormat::f32:
|
case core::PixelFormat::f32:
|
||||||
return kOfxBitDepthFloat;
|
return kOfxBitDepthFloat;
|
||||||
|
case core::PixelFormat::u10:
|
||||||
case core::PixelFormat::invalid:
|
case core::PixelFormat::invalid:
|
||||||
case core::PixelFormat::count:
|
case core::PixelFormat::count:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -79,18 +79,18 @@ public:
|
|||||||
makeDescriptor(const std::string &bundle_path,
|
makeDescriptor(const std::string &bundle_path,
|
||||||
OFX::Host::ImageEffect::ImageEffectPlugin *plugin) override;
|
OFX::Host::ImageEffect::ImageEffectPlugin *plugin) override;
|
||||||
/// vmessage
|
/// vmessage
|
||||||
virtual OfxStatus vmessage(const char *type, const char *id,
|
OfxStatus vmessage(const char *type, const char *id,
|
||||||
const char *format, va_list args);
|
const char *format, va_list args) override;
|
||||||
|
|
||||||
/// vmessage
|
/// vmessage
|
||||||
virtual OfxStatus setPersistentMessage(const char *type, const char *id,
|
OfxStatus setPersistentMessage(const char *type, const char *id,
|
||||||
const char *format, va_list args);
|
const char *format, va_list args) override;
|
||||||
/// vmessage
|
/// vmessage
|
||||||
virtual OfxStatus clearPersistentMessage();
|
OfxStatus clearPersistentMessage() override;
|
||||||
|
|
||||||
#ifdef OFX_SUPPORTS_OPENGLRENDER
|
#ifdef OFX_SUPPORTS_OPENGLRENDER
|
||||||
/// @see OfxImageEffectOpenGLRenderSuiteV1.flushResources()
|
/// @see OfxImageEffectOpenGLRenderSuiteV1.flushResources()
|
||||||
virtual OfxStatus flushOpenGLResources() const
|
virtual OfxStatus flushOpenGLResources() const override
|
||||||
{
|
{
|
||||||
return kOfxStatFailed;
|
return kOfxStatFailed;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -209,13 +209,13 @@ public:
|
|||||||
|
|
||||||
/// get the current time on the timeline. This is not necessarily the same
|
/// get the current time on the timeline. This is not necessarily the same
|
||||||
/// time as being passed to an action (eg render)
|
/// time as being passed to an action (eg render)
|
||||||
virtual double timeLineGetTime();
|
double timeLineGetTime() override;
|
||||||
|
|
||||||
/// set the timeline to a specific time
|
/// set the timeline to a specific time
|
||||||
virtual void timeLineGotoTime(double t);
|
void timeLineGotoTime(double t) override;
|
||||||
|
|
||||||
/// get the first and last times available on the effect's timeline
|
/// get the first and last times available on the effect's timeline
|
||||||
virtual void timeLineGetBounds(double &t1, double &t2);
|
void timeLineGetBounds(double &t1, double &t2) override;
|
||||||
|
|
||||||
void setCustomInArgs(const std::string &action,
|
void setCustomInArgs(const std::string &action,
|
||||||
OFX::Host::Property::Set &in_args) override;
|
OFX::Host::Property::Set &in_args) override;
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ public:
|
|||||||
{
|
{
|
||||||
node_ = new_node;
|
node_ = new_node;
|
||||||
}
|
}
|
||||||
OfxStatus get(int &a)
|
OfxStatus get(int &a) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
std::lock_guard<std::mutex> lock(no_node_mutex_);
|
std::lock_guard<std::mutex> lock(no_node_mutex_);
|
||||||
@@ -151,7 +151,7 @@ public:
|
|||||||
a = 0;
|
a = 0;
|
||||||
return kOfxStatErrValue;
|
return kOfxStatErrValue;
|
||||||
}
|
}
|
||||||
OfxStatus get(OfxTime time, int &data)
|
OfxStatus get(OfxTime time, int &data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
std::lock_guard<std::mutex> lock(no_node_mutex_);
|
std::lock_guard<std::mutex> lock(no_node_mutex_);
|
||||||
@@ -170,7 +170,7 @@ public:
|
|||||||
data = 0;
|
data = 0;
|
||||||
return kOfxStatErrValue;
|
return kOfxStatErrValue;
|
||||||
}
|
}
|
||||||
OfxStatus set(int data)
|
OfxStatus set(int data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
std::lock_guard<std::mutex> lock(no_node_mutex_);
|
std::lock_guard<std::mutex> lock(no_node_mutex_);
|
||||||
@@ -186,7 +186,7 @@ public:
|
|||||||
submit_undo_command(node_, command, param_change_label(descriptor_));
|
submit_undo_command(node_, command, param_change_label(descriptor_));
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus set(OfxTime time, int data)
|
OfxStatus set(OfxTime time, int data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
std::lock_guard<std::mutex> lock(no_node_mutex_);
|
std::lock_guard<std::mutex> lock(no_node_mutex_);
|
||||||
@@ -233,7 +233,7 @@ public:
|
|||||||
{
|
{
|
||||||
node_ = new_node;
|
node_ = new_node;
|
||||||
}
|
}
|
||||||
OfxStatus get(double &data)
|
OfxStatus get(double &data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
data = has_value_ ? value_ : 0.0;
|
data = has_value_ ? value_ : 0.0;
|
||||||
@@ -253,7 +253,7 @@ public:
|
|||||||
data = 0.0;
|
data = 0.0;
|
||||||
return kOfxStatErrValue;
|
return kOfxStatErrValue;
|
||||||
}
|
}
|
||||||
OfxStatus get(OfxTime time, double &data)
|
OfxStatus get(OfxTime time, double &data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
data = has_value_ ? value_ : 0.0;
|
data = has_value_ ? value_ : 0.0;
|
||||||
@@ -273,7 +273,7 @@ public:
|
|||||||
data = 0.0;
|
data = 0.0;
|
||||||
return kOfxStatErrValue;
|
return kOfxStatErrValue;
|
||||||
}
|
}
|
||||||
OfxStatus set(double data)
|
OfxStatus set(double data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
value_ = data;
|
value_ = data;
|
||||||
@@ -293,7 +293,7 @@ public:
|
|||||||
submit_undo_command(node_, command, param_change_label(descriptor_));
|
submit_undo_command(node_, command, param_change_label(descriptor_));
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus set(OfxTime time, double data)
|
OfxStatus set(OfxTime time, double data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
value_ = data;
|
value_ = data;
|
||||||
@@ -313,11 +313,11 @@ public:
|
|||||||
submit_undo_command(node_, command, param_change_label(descriptor_));
|
submit_undo_command(node_, command, param_change_label(descriptor_));
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus derive(OfxTime, double &)
|
OfxStatus derive(OfxTime, double &) override
|
||||||
{
|
{
|
||||||
return kOfxStatErrUnsupported;
|
return kOfxStatErrUnsupported;
|
||||||
}
|
}
|
||||||
OfxStatus integrate(OfxTime, OfxTime, double &)
|
OfxStatus integrate(OfxTime, OfxTime, double &) override
|
||||||
{
|
{
|
||||||
return kOfxStatErrUnsupported;
|
return kOfxStatErrUnsupported;
|
||||||
}
|
}
|
||||||
@@ -352,7 +352,7 @@ public:
|
|||||||
{
|
{
|
||||||
node_ = new_node;
|
node_ = new_node;
|
||||||
}
|
}
|
||||||
OfxStatus get(bool &data)
|
OfxStatus get(bool &data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
data = has_value_ ? value_ : false;
|
data = has_value_ ? value_ : false;
|
||||||
@@ -367,7 +367,7 @@ public:
|
|||||||
data = default_value();
|
data = default_value();
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus get(OfxTime time, bool &data)
|
OfxStatus get(OfxTime time, bool &data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
data = has_value_ ? value_ : false;
|
data = has_value_ ? value_ : false;
|
||||||
@@ -392,7 +392,7 @@ public:
|
|||||||
data = default_value();
|
data = default_value();
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus set(bool data)
|
OfxStatus set(bool data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
value_ = data;
|
value_ = data;
|
||||||
@@ -406,7 +406,7 @@ public:
|
|||||||
submit_undo_command(node_, command, param_change_label(descriptor_));
|
submit_undo_command(node_, command, param_change_label(descriptor_));
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus set(OfxTime time, bool data)
|
OfxStatus set(OfxTime time, bool data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
value_ = data;
|
value_ = data;
|
||||||
@@ -452,7 +452,7 @@ public:
|
|||||||
{
|
{
|
||||||
node_ = new_node;
|
node_ = new_node;
|
||||||
}
|
}
|
||||||
OfxStatus get(int &data)
|
OfxStatus get(int &data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
data = has_value_ ? value_ : 0;
|
data = has_value_ ? value_ : 0;
|
||||||
@@ -467,7 +467,7 @@ public:
|
|||||||
data = 0;
|
data = 0;
|
||||||
return kOfxStatErrValue;
|
return kOfxStatErrValue;
|
||||||
}
|
}
|
||||||
OfxStatus get(OfxTime time, int &data)
|
OfxStatus get(OfxTime time, int &data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
data = has_value_ ? value_ : 0;
|
data = has_value_ ? value_ : 0;
|
||||||
@@ -482,7 +482,7 @@ public:
|
|||||||
data = 0;
|
data = 0;
|
||||||
return kOfxStatErrValue;
|
return kOfxStatErrValue;
|
||||||
}
|
}
|
||||||
OfxStatus set(int data)
|
OfxStatus set(int data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
value_ = data;
|
value_ = data;
|
||||||
@@ -496,7 +496,7 @@ public:
|
|||||||
submit_undo_command(node_, command, param_change_label(descriptor_));
|
submit_undo_command(node_, command, param_change_label(descriptor_));
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus set(OfxTime time, int data)
|
OfxStatus set(OfxTime time, int data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
value_ = data;
|
value_ = data;
|
||||||
@@ -534,7 +534,7 @@ public:
|
|||||||
{
|
{
|
||||||
node_ = new_node;
|
node_ = new_node;
|
||||||
}
|
}
|
||||||
OfxStatus get(double &r, double &g, double &b, double &a)
|
OfxStatus get(double &r, double &g, double &b, double &a) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
if (has_value_) {
|
if (has_value_) {
|
||||||
@@ -557,7 +557,7 @@ public:
|
|||||||
a = static_cast<double>(c.alpha());
|
a = static_cast<double>(c.alpha());
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus get(OfxTime time, double &r, double &g, double &b, double &a)
|
OfxStatus get(OfxTime time, double &r, double &g, double &b, double &a) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
if (has_value_) {
|
if (has_value_) {
|
||||||
@@ -581,7 +581,7 @@ public:
|
|||||||
a = static_cast<double>(c.alpha());
|
a = static_cast<double>(c.alpha());
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus set(double r, double g, double b, double a)
|
OfxStatus set(double r, double g, double b, double a) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
value_[0] = r;
|
value_[0] = r;
|
||||||
@@ -599,7 +599,7 @@ public:
|
|||||||
submit_undo_command(node_, command, param_change_label(descriptor_));
|
submit_undo_command(node_, command, param_change_label(descriptor_));
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus set(OfxTime time, double r, double g, double b, double a)
|
OfxStatus set(OfxTime time, double r, double g, double b, double a) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
value_[0] = r;
|
value_[0] = r;
|
||||||
@@ -646,7 +646,7 @@ public:
|
|||||||
{
|
{
|
||||||
node_ = new_node;
|
node_ = new_node;
|
||||||
}
|
}
|
||||||
OfxStatus get(double &r, double &g, double &b)
|
OfxStatus get(double &r, double &g, double &b) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
if (has_value_) {
|
if (has_value_) {
|
||||||
@@ -667,7 +667,7 @@ public:
|
|||||||
b = static_cast<double>(c.blue());
|
b = static_cast<double>(c.blue());
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus get(OfxTime time, double &r, double &g, double &b)
|
OfxStatus get(OfxTime time, double &r, double &g, double &b) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
if (has_value_) {
|
if (has_value_) {
|
||||||
@@ -689,7 +689,7 @@ public:
|
|||||||
b = static_cast<double>(c.blue());
|
b = static_cast<double>(c.blue());
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus set(double r, double g, double b)
|
OfxStatus set(double r, double g, double b) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
value_[0] = r;
|
value_[0] = r;
|
||||||
@@ -706,7 +706,7 @@ public:
|
|||||||
submit_undo_command(node_, command, param_change_label(descriptor_));
|
submit_undo_command(node_, command, param_change_label(descriptor_));
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus set(OfxTime time, double r, double g, double b)
|
OfxStatus set(OfxTime time, double r, double g, double b) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
value_[0] = r;
|
value_[0] = r;
|
||||||
@@ -751,7 +751,7 @@ public:
|
|||||||
{
|
{
|
||||||
node_ = new_node;
|
node_ = new_node;
|
||||||
}
|
}
|
||||||
OfxStatus get(double &x, double &y)
|
OfxStatus get(double &x, double &y) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
if (has_value_) {
|
if (has_value_) {
|
||||||
@@ -774,7 +774,7 @@ public:
|
|||||||
}
|
}
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus get(OfxTime time, double &x, double &y)
|
OfxStatus get(OfxTime time, double &x, double &y) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
if (has_value_) {
|
if (has_value_) {
|
||||||
@@ -798,7 +798,7 @@ public:
|
|||||||
}
|
}
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus set(double x, double y)
|
OfxStatus set(double x, double y) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
value_[0] = x;
|
value_[0] = x;
|
||||||
@@ -820,7 +820,7 @@ public:
|
|||||||
submit_undo_command(node_, command, param_change_label(descriptor_));
|
submit_undo_command(node_, command, param_change_label(descriptor_));
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus set(OfxTime time, double x, double y)
|
OfxStatus set(OfxTime time, double x, double y) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
value_[0] = x;
|
value_[0] = x;
|
||||||
@@ -869,7 +869,7 @@ public:
|
|||||||
{
|
{
|
||||||
node_ = new_node;
|
node_ = new_node;
|
||||||
}
|
}
|
||||||
OfxStatus get(int &x, int &y)
|
OfxStatus get(int &x, int &y) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
if (has_value_) {
|
if (has_value_) {
|
||||||
@@ -886,7 +886,7 @@ public:
|
|||||||
y = static_cast<int>(vec.y());
|
y = static_cast<int>(vec.y());
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus get(OfxTime time, int &x, int &y)
|
OfxStatus get(OfxTime time, int &x, int &y) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
if (has_value_) {
|
if (has_value_) {
|
||||||
@@ -904,7 +904,7 @@ public:
|
|||||||
y = static_cast<int>(vec.y());
|
y = static_cast<int>(vec.y());
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus set(int x, int y)
|
OfxStatus set(int x, int y) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
value_[0] = x;
|
value_[0] = x;
|
||||||
@@ -919,7 +919,7 @@ public:
|
|||||||
submit_undo_command(node_, command, param_change_label(descriptor_));
|
submit_undo_command(node_, command, param_change_label(descriptor_));
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus set(OfxTime time, int x, int y)
|
OfxStatus set(OfxTime time, int x, int y) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
value_[0] = x;
|
value_[0] = x;
|
||||||
@@ -961,7 +961,7 @@ public:
|
|||||||
{
|
{
|
||||||
node_ = new_node;
|
node_ = new_node;
|
||||||
}
|
}
|
||||||
OfxStatus get(double &x, double &y, double &z)
|
OfxStatus get(double &x, double &y, double &z) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
if (has_value_) {
|
if (has_value_) {
|
||||||
@@ -987,7 +987,7 @@ public:
|
|||||||
}
|
}
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus get(OfxTime time, double &x, double &y, double &z)
|
OfxStatus get(OfxTime time, double &x, double &y, double &z) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
if (has_value_) {
|
if (has_value_) {
|
||||||
@@ -1014,7 +1014,7 @@ public:
|
|||||||
}
|
}
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus set(double x, double y, double z)
|
OfxStatus set(double x, double y, double z) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
value_[0] = x;
|
value_[0] = x;
|
||||||
@@ -1038,7 +1038,7 @@ public:
|
|||||||
submit_undo_command(node_, command, param_change_label(descriptor_));
|
submit_undo_command(node_, command, param_change_label(descriptor_));
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus set(OfxTime time, double x, double y, double z)
|
OfxStatus set(OfxTime time, double x, double y, double z) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
value_[0] = x;
|
value_[0] = x;
|
||||||
@@ -1091,7 +1091,7 @@ public:
|
|||||||
{
|
{
|
||||||
node_ = new_node;
|
node_ = new_node;
|
||||||
}
|
}
|
||||||
OfxStatus get(int &x, int &y, int &z)
|
OfxStatus get(int &x, int &y, int &z) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
if (has_value_) {
|
if (has_value_) {
|
||||||
@@ -1110,7 +1110,7 @@ public:
|
|||||||
z = static_cast<int>(vec.z());
|
z = static_cast<int>(vec.z());
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus get(OfxTime time, int &x, int &y, int &z)
|
OfxStatus get(OfxTime time, int &x, int &y, int &z) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
if (has_value_) {
|
if (has_value_) {
|
||||||
@@ -1130,7 +1130,7 @@ public:
|
|||||||
z = static_cast<int>(vec.z());
|
z = static_cast<int>(vec.z());
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus set(int x, int y, int z)
|
OfxStatus set(int x, int y, int z) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
value_[0] = x;
|
value_[0] = x;
|
||||||
@@ -1146,7 +1146,7 @@ public:
|
|||||||
submit_undo_command(node_, command, param_change_label(descriptor_));
|
submit_undo_command(node_, command, param_change_label(descriptor_));
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus set(OfxTime time, int x, int y, int z)
|
OfxStatus set(OfxTime time, int x, int y, int z) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
value_[0] = x;
|
value_[0] = x;
|
||||||
@@ -1198,7 +1198,7 @@ public:
|
|||||||
{
|
{
|
||||||
node_ = new_node;
|
node_ = new_node;
|
||||||
}
|
}
|
||||||
OfxStatus get(std::string &data)
|
OfxStatus get(std::string &data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
data = has_value_ ? value_ : std::string();
|
data = has_value_ ? value_ : std::string();
|
||||||
@@ -1213,7 +1213,7 @@ public:
|
|||||||
data.clear();
|
data.clear();
|
||||||
return kOfxStatErrValue;
|
return kOfxStatErrValue;
|
||||||
}
|
}
|
||||||
OfxStatus get(OfxTime time, std::string &data)
|
OfxStatus get(OfxTime time, std::string &data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
data = has_value_ ? value_ : std::string();
|
data = has_value_ ? value_ : std::string();
|
||||||
@@ -1228,7 +1228,7 @@ public:
|
|||||||
data.clear();
|
data.clear();
|
||||||
return kOfxStatErrValue;
|
return kOfxStatErrValue;
|
||||||
}
|
}
|
||||||
OfxStatus set(const char *data)
|
OfxStatus set(const char *data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
value_ = data ? data : "";
|
value_ = data ? data : "";
|
||||||
@@ -1243,7 +1243,7 @@ public:
|
|||||||
submit_undo_command(node_, command, param_change_label(descriptor_));
|
submit_undo_command(node_, command, param_change_label(descriptor_));
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus set(OfxTime time, const char *data)
|
OfxStatus set(OfxTime time, const char *data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
value_ = data ? data : "";
|
value_ = data ? data : "";
|
||||||
@@ -1282,7 +1282,7 @@ public:
|
|||||||
{
|
{
|
||||||
node_ = new_node;
|
node_ = new_node;
|
||||||
}
|
}
|
||||||
OfxStatus get(std::string &data)
|
OfxStatus get(std::string &data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
data = has_value_ ? value_ : std::string();
|
data = has_value_ ? value_ : std::string();
|
||||||
@@ -1301,7 +1301,7 @@ public:
|
|||||||
data.clear();
|
data.clear();
|
||||||
return kOfxStatErrValue;
|
return kOfxStatErrValue;
|
||||||
}
|
}
|
||||||
OfxStatus get(OfxTime time, std::string &data)
|
OfxStatus get(OfxTime time, std::string &data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
data = has_value_ ? value_ : std::string();
|
data = has_value_ ? value_ : std::string();
|
||||||
@@ -1320,7 +1320,7 @@ public:
|
|||||||
data.clear();
|
data.clear();
|
||||||
return kOfxStatErrValue;
|
return kOfxStatErrValue;
|
||||||
}
|
}
|
||||||
OfxStatus set(const char *data)
|
OfxStatus set(const char *data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
value_ = data ? data : "";
|
value_ = data ? data : "";
|
||||||
@@ -1335,7 +1335,7 @@ public:
|
|||||||
submit_undo_command(node_, command, param_change_label(descriptor_));
|
submit_undo_command(node_, command, param_change_label(descriptor_));
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus set(OfxTime time, const char *data)
|
OfxStatus set(OfxTime time, const char *data) override
|
||||||
{
|
{
|
||||||
if (!node_) {
|
if (!node_) {
|
||||||
value_ = data ? data : "";
|
value_ = data ? data : "";
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ void AudioWaveformCache::write_waveform(const TimeRange &range,
|
|||||||
const AudioVisualWaveform *waveform)
|
const AudioVisualWaveform *waveform)
|
||||||
{
|
{
|
||||||
// Write each valid range to the segments
|
// Write each valid range to the segments
|
||||||
foreach (const TimeRange &r, valid_ranges) {
|
for (const TimeRange &r : valid_ranges) {
|
||||||
if (waveform) {
|
if (waveform) {
|
||||||
waveforms_->overwrite_sums(*waveform, r.in(), r.in() - range.in(),
|
waveforms_->overwrite_sums(*waveform, r.in(), r.in() - range.in(),
|
||||||
r.length());
|
r.length());
|
||||||
|
|||||||
@@ -18,7 +18,11 @@ DynamicRenderer::DynamicRenderer(const QString &backend, QObject *parent)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tears down the backend in the reverse order used by Load(): release renderer
|
// Tears down the backend in the reverse order used by Load(): release renderer
|
||||||
// resources, destroy the opaque backend object, then unload the shared library.
|
// resources, then destroy the opaque backend object. The shared library itself
|
||||||
|
// is deliberately NOT unloaded: multiple DynamicRenderer instances can wrap the
|
||||||
|
// same backend library, and one instance's dlclose can unmap code that other
|
||||||
|
// instances (or Qt) still reference, producing calls into unmapped memory.
|
||||||
|
// Backend libraries stay mapped until process exit.
|
||||||
DynamicRenderer::~DynamicRenderer()
|
DynamicRenderer::~DynamicRenderer()
|
||||||
{
|
{
|
||||||
destroy();
|
destroy();
|
||||||
@@ -27,9 +31,6 @@ DynamicRenderer::~DynamicRenderer()
|
|||||||
destroy_(handle_);
|
destroy_(handle_);
|
||||||
handle_ = nullptr;
|
handle_ = nullptr;
|
||||||
}
|
}
|
||||||
if (library_.isLoaded()) {
|
|
||||||
library_.unload();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Builds the private backend library path for the current platform.
|
// Builds the private backend library path for the current platform.
|
||||||
|
|||||||
@@ -373,7 +373,7 @@ bool DiskCacheFolder::delete_least_recent()
|
|||||||
auto hash_to_delete = disk_data_.begin();
|
auto hash_to_delete = disk_data_.begin();
|
||||||
|
|
||||||
if (disk_data_.begin() != disk_data_.end()) {
|
if (disk_data_.begin() != disk_data_.end()) {
|
||||||
for (auto it = disk_data_.begin() + 1; it != disk_data_.end(); it++) {
|
for (auto it = std::next(disk_data_.begin()); it != disk_data_.end(); it++) {
|
||||||
if (it->access_time < hash_to_delete->access_time) {
|
if (it->access_time < hash_to_delete->access_time) {
|
||||||
hash_to_delete = it;
|
hash_to_delete = it;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -558,6 +558,8 @@ void OpenGLRenderer::blit(QVariant s, AcceleratedJob &a_job,
|
|||||||
// over/underflows if the number is large enough, but the likelihood of that is quite low.
|
// over/underflows if the number is large enough, but the likelihood of that is quite low.
|
||||||
functions_->glUniform1i(variable_location, value.to_int());
|
functions_->glUniform1i(variable_location, value.to_int());
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
case NodeValue::k_float:
|
case NodeValue::k_float:
|
||||||
// kFloat technically specifies a double but as above, OpenGL doesn't support those.
|
// kFloat technically specifies a double but as above, OpenGL doesn't support those.
|
||||||
functions_->glUniform1f(variable_location, value.to_double());
|
functions_->glUniform1f(variable_location, value.to_double());
|
||||||
@@ -828,7 +830,7 @@ void OpenGLRenderer::blit(QVariant s, AcceleratedJob &a_job,
|
|||||||
vert_vbo.destroy();
|
vert_vbo.destroy();
|
||||||
vao.release();
|
vao.release();
|
||||||
vao.destroy();
|
vao.destroy();
|
||||||
} catch (std::bad_cast e) {
|
} catch (const std::bad_cast &e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -975,7 +977,6 @@ GLuint OpenGLRenderer::compile_shader(GLenum type, const QString &code)
|
|||||||
{
|
{
|
||||||
const bool is_gles = context_ && context_->isOpenGLES();
|
const bool is_gles = context_ && context_->isOpenGLES();
|
||||||
const int major = context_ ? context_->format().majorVersion() : 0;
|
const int major = context_ ? context_->format().majorVersion() : 0;
|
||||||
const int minor = context_ ? context_->format().minorVersion() : 0;
|
|
||||||
const bool is_gles2 = is_gles && (major < 3);
|
const bool is_gles2 = is_gles && (major < 3);
|
||||||
const QString gles_preamble =
|
const QString gles_preamble =
|
||||||
is_gles2 ? QStringLiteral("#version 100\n\n"
|
is_gles2 ? QStringLiteral("#version 100\n\n"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user