diff --git a/.bak/nodeview/nodeview.cpp b/.bak/nodeview/nodeview.cpp deleted file mode 100644 index f7bd014c8..000000000 --- a/.bak/nodeview/nodeview.cpp +++ /dev/null @@ -1,1982 +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 . - -***/ - -#include "nodeview.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "node/audio/volume/volume.h" -#include "node/distort/transform/transformdistortnode.h" -#include "node/group/group.h" -#include "node/nodeundo.h" -#include "node/project/serializer/serializer.h" -#include "panel/panelmanager.h" -#include "node/traverser.h" -#include "ui/icons/icons.h" -#include "widget/menu/factorymenu.h" -#include "widget/menu/menushared.h" -#include "widget/timebased/timebasedview.h" - -#define super HandMovableView - -namespace olive -{ - -const double NodeView::k_minimum_scale = 0.1; -const int NodeView::k_maximum_contexts = 10; - -NodeView::NodeView(QWidget *parent) - : HandMovableView(parent) - , drop_edge_(nullptr) - , create_edge_(nullptr) - , create_edge_output_item_(nullptr) - , create_edge_input_item_(nullptr) - , overlay_view_(nullptr) - , scale_(1.0) - , dont_emit_selection_signals_(false) - , show_in_param_editor_action_(nullptr) -{ - setScene(&scene_); - set_default_drag_mode(RubberBandDrag); - setContextMenuPolicy(Qt::CustomContextMenu); - setMouseTracking(true); - setRenderHint(QPainter::Antialiasing); - setViewportUpdateMode(FullViewportUpdate); - - connect(this, &NodeView::customContextMenuRequested, this, - &NodeView::show_context_menu); - - connect_selection_changed_signal(); - - set_flow_direction(NodeViewCommon::k_left_to_right); - - show_in_param_editor_action_ = - new QAction(tr("Show in Parameter Editor"), this); - Menu::conform_item(show_in_param_editor_action_, - QStringLiteral("shownodeparams"), - QKeySequence(tr("Shift+P"))); - show_in_param_editor_action_->setShortcutContext(Qt::WindowShortcut); - addAction(show_in_param_editor_action_); - connect(show_in_param_editor_action_, &QAction::triggered, this, - &NodeView::show_selected_node_in_param_editor); - - update_scene_bounding_rect(); - connect(&scene_, &QGraphicsScene::changed, this, - &NodeView::update_scene_bounding_rect); - - minimap_ = new NodeViewMiniMap(&scene_, this); - minimap_->show(); - connect(minimap_, &NodeViewMiniMap::resized, this, - &NodeView::reposition_mini_map); - connect(minimap_, &NodeViewMiniMap::move_to_scene_point, this, - &NodeView::move_to_scene_point); - connect(horizontalScrollBar(), &QScrollBar::valueChanged, this, - &NodeView::update_viewport_on_mini_map); - connect(verticalScrollBar(), &QScrollBar::valueChanged, this, - &NodeView::update_viewport_on_mini_map); - - viewport()->installEventFilter(this); -} - -NodeView::~NodeView() -{ - // Unset the current graph - clear_graph(); -} - -void NodeView::set_contexts(const QVector &nodes) -{ - if (overlay_view_) { - close_overlay(); - } - - // Remove contexts that are no longer in the list - foreach (Node *n, contexts_) { - if (!nodes.contains(n)) { - remove_context(n); - } - } - - // Add contexts that are now in the list - foreach (Node *n, nodes) { - if (scene_.context_map().size() >= k_maximum_contexts) { - break; - } - - if (!contexts_.contains(n)) { - add_context(n); - } - } - - contexts_ = nodes; - - center_on_items_bounding_rect(); -} - -void NodeView::close_contexts_belonging_to_project(Project *project) -{ - QVector new_contexts = contexts_; - - for (auto it = new_contexts.begin(); it != new_contexts.end();) { - if ((*it)->project() == project) { - it = new_contexts.erase(it); - } else { - it++; - } - } - - set_contexts(new_contexts); -} - -void NodeView::clear_graph() -{ - set_contexts(QVector()); -} - -void NodeView::delete_selected() -{ - NodeViewDeleteCommand *command = new NodeViewDeleteCommand(); - - int count = 0; - - foreach (NodeViewContext *ctx, scene_.context_map()) { - count += ctx->delete_selected(command); - } - - Core::instance()->undo_stack()->push(command, - tr("Deleted %1 Node(s)").arg(count)); -} - -void NodeView::select_all() -{ - // Optimization: rather than respond to every single item being selected, ignore the signal and - // then handle them all at the end. - disconnect_selection_changed_signal(); - - scene_.select_all(); - - connect_selection_changed_signal(); - - update_selection_cache(); -} - -void NodeView::deselect_all() -{ - if (selected_nodes_.isEmpty()) { - return; - } - - // Optimization: rather than respond to every single item being selected, ignore the signal and - // then handle them all at the end. - disconnect_selection_changed_signal(); - - scene_.deselect_all(); - - connect_selection_changed_signal(); - - // Just emit all the nodes that are currently selected as no longer selected - emit nodes_deselected(selected_nodes_); - selected_nodes_.clear(); - emit node_selection_changed(selected_nodes_); - emit node_selection_changed_with_contexts(QVector()); -} - -void NodeView::select(const QVector &nodes, - bool center_view_on_item) -{ - // Optimization: rather than respond to every single item being selected, ignore the signal and - // then handle them all at the end. - disconnect_selection_changed_signal(); - - QVector deselections = selected_nodes_; - QVector new_selections; - - scene_.deselect_all(); - - foreach (const Node::ContextPair &p, nodes) { - NodeViewContext *ctx = scene_.context_map().value(p.context); - if (ctx) { - NodeViewItem *item = ctx->get_item_from_map(p.node); - if (item) { - item->setSelected(true); - } - } - } - - // Center on something - if (center_view_on_item && !nodes.isEmpty()) { - QMetaObject::invokeMethod(this, "center_on_node", Qt::QueuedConnection, - OLIVE_NS_ARG(Node *, nodes.first().node)); - } - - connect_selection_changed_signal(); - - // Don't signal when this function was likely triggered from another widget's signal anyway - dont_emit_selection_signals_ = true; - update_selection_cache(); - dont_emit_selection_signals_ = false; -} - -void NodeView::copy_selected(bool cut) -{ - if (selected_nodes_.isEmpty()) { - return; - } - - QString copy_str; - QXmlStreamWriter writer(©_str); - - ProjectSerializer::SaveData sdata(ProjectSerializer::k_only_nodes); - sdata.set_only_serialize_nodes_and_resolve_groups(selected_nodes_); - - ProjectSerializer::SerializedProperties properties; - - for (Node *n : selected_nodes_) { - NodeViewItem *item = get_assumed_item_for_selected_node(n); - - if (item) { - Node::Position pos = item->get_node_position_data(); - - properties[n][QStringLiteral("x")] = - QString::number(pos.position.x()); - properties[n][QStringLiteral("y")] = - QString::number(pos.position.y()); - properties[n][QStringLiteral("expanded")] = - QString::number(pos.expanded); - } - } - - sdata.set_properties(properties); - - ProjectSerializer::save(&writer, sdata); - - Core::copy_string_to_clipboard(copy_str); - - if (cut) { - delete_selected(); - } -} - -void NodeView::paste() -{ - if (contexts_.isEmpty()) { - return; - } - - ProjectSerializer::Result res = - ProjectSerializer::paste(ProjectSerializer::k_only_nodes); - if (res.get_load_data().nodes.isEmpty()) { - return; - } - - Node::PositionMap map; - - for (auto it = res.get_load_data().properties.cbegin(); - it != res.get_load_data().properties.cend(); it++) { - Node::Position pos; - - const QMap &node_props = it.value(); - pos.position.setX(node_props.value(QStringLiteral("x")).toDouble()); - pos.position.setY(node_props.value(QStringLiteral("y")).toDouble()); - pos.expanded = node_props.value(QStringLiteral("expanded")).toDouble(); - - map.insert(it.key(), pos); - } - - post_paste(res.get_load_data().nodes, map); -} - -void NodeView::duplicate() -{ - if (!selected_nodes_.isEmpty()) { - QVector selected = selected_nodes_; - QVector new_nodes; - Node::PositionMap map; - - new_nodes.resize(selected.size()); - - // Create copies of each selected node, checking for groups and adding children if necessary - for (int i = 0; i < selected.size(); i++) { - new_nodes[i] = selected.at(i)->copy(); - - if (NodeGroup *g = dynamic_cast(selected.at(i))) { - for (auto it = g->get_context_positions().cbegin(); - it != g->get_context_positions().cend(); it++) { - if (!selected.contains(it.key())) { - // This should automatically recurse if this is a group inside a group - selected.append(it.key()); - } - } - new_nodes.resize(selected.size()); - } - } - - // Get positions in contexts, add input passthroughs, and copy input values/keyframes - for (int i = 0; i < new_nodes.size(); i++) { - Node *og = selected.at(i); - Node *copy = new_nodes.at(i); - - Node::Position pos; - if (get_assumed_position_for_selected_node(og, &pos)) { - map.insert(copy, pos); - } - - for (auto it = og->get_context_positions().cbegin(); - it != og->get_context_positions().cend(); it++) { - Node *child_og = it.key(); - int child_index = selected.indexOf(child_og); - - if (child_index != -1) { - Node *child_copy = new_nodes.at(child_index); - - copy->set_node_position_in_context(child_copy, it.value()); - } - } - - if (NodeGroup *src_group = dynamic_cast(og)) { - NodeGroup *dst_group = static_cast(copy); - - for (auto it = src_group->get_input_passthroughs().cbegin(); - it != src_group->get_input_passthroughs().cend(); it++) { - NodeInput input = it->second; - input.set_node( - new_nodes.at(selected.indexOf(input.node()))); - dst_group->add_input_passthrough(input, it->first); - } - - dst_group->set_output_passthrough(new_nodes.at( - selected.indexOf(src_group->get_output_passthrough()))); - } - - Node::copy_inputs(selected.at(i), new_nodes.at(i), false); - } - - // Copy connections - Node::copy_dependency_graph(selected, new_nodes, nullptr); - - // Set root level context positions and attach to - post_paste(new_nodes, map); - } -} - -void NodeView::set_color_label(int index) -{ - MultiUndoCommand *command = new MultiUndoCommand(); - - for (Node *node : qAsConst(selected_nodes_)) { - command->add_child(new NodeOverrideColorCommand(node, index)); - } - - Core::instance()->undo_stack()->push( - command, tr("Set Color of %1 Node(s)").arg(selected_nodes_.size())); -} - -void NodeView::zoom_in() -{ - zoom_from_keyboard(1.25); -} - -void NodeView::zoom_out() -{ - zoom_from_keyboard(0.8); -} - -void NodeView::keyPressEvent(QKeyEvent *event) -{ - switch (event->key()) { - case Qt::Key_Left: - case Qt::Key_Right: - case Qt::Key_Up: - case Qt::Key_Down: { - MultiUndoCommand *pos_command = new MultiUndoCommand(); - for (Node *n : qAsConst(selected_nodes_)) { - for (Node *context : qAsConst(contexts_)) { - if (context->context_contains_node(n)) { - Node::Position old_pos = - context->get_node_position_in_context(n); - - // Determine one pixel in scene units - double movement_amt = 1.0 / scale_; - - // Translate to 2D movement - QPointF node_movement; - switch (event->key()) { - case Qt::Key_Left: - node_movement.setX(-movement_amt); - break; - case Qt::Key_Right: - node_movement.setX(movement_amt); - break; - case Qt::Key_Up: - node_movement.setY(-movement_amt); - break; - case Qt::Key_Down: - node_movement.setY(movement_amt); - break; - } - - // Translate from screen units into node units - node_movement = NodeViewItem::screen_to_node_point( - node_movement, scene_.get_flow_direction()); - - // Move command - pos_command->add_child(new NodeSetPositionCommand( - n, context, old_pos + node_movement)); - } - } - } - Core::instance()->undo_stack()->push( - pos_command, tr("Moved %1 Node(s)").arg(selected_nodes_.size())); - break; - } - case Qt::Key_Escape: - if (!attached_items_.isEmpty()) { - detach_items_from_cursor(); - break; - } - - emit esc_pressed(); - - /* fall through */ - default: - super::keyPressEvent(event); - break; - } -} - -void NodeView::mousePressEvent(QMouseEvent *event) -{ - // Handle mouse press event - if (hand_press(event)) - return; - - // Get the item that the user clicked on, if any - QGraphicsItem *item = itemAt(event->pos()); - - if (event->button() == Qt::LeftButton) { - // Sane defaults - create_edge_already_exists_ = false; - create_edge_from_output_ = true; - create_edge_input_.reset(); - - if (event->modifiers() & Qt::ControlModifier) { - NodeViewItem *mouse_item = dynamic_cast(item); - - if (mouse_item) { - if (mouse_item->is_output_item()) { - create_edge_output_item_ = mouse_item; - } else { - create_edge_input_item_ = mouse_item; - create_edge_input_ = mouse_item->get_input(); - create_edge_from_output_ = false; - } - - // Highlight start item for better user experience - mouse_item->set_highlighted(true); - } - } - - if (!create_edge_output_item_ && !create_edge_input_item_) { - // Determine if user clicked on a connector - if (NodeViewItemConnector *connector = - dynamic_cast(item)) { - NodeViewItem *attached = - static_cast(connector->parentItem()); - - if (connector->is_output()) { - create_edge_output_item_ = attached; - } else { - create_edge_input_item_ = attached; - - if (!create_edge_input_item_->edges().isEmpty()) { - // Drag existing edge instead - create_edge_ = create_edge_input_item_->edges().first(); - create_edge_input_item_ = nullptr; - create_edge_output_item_ = create_edge_->from_item(); - create_edge_already_exists_ = true; - } else { - create_edge_from_output_ = false; - create_edge_input_ = - create_edge_input_item_->get_input(); - } - } - } - } - - if ((create_edge_output_item_ || create_edge_input_item_) && - !create_edge_already_exists_) { - // Create a new edge from this output - create_edge_ = new NodeViewEdge(); - create_edge_->set_curved(scene_.get_edges_are_curved()); - - // Add edge to scene - scene_.addItem(create_edge_); - - // Position edge to mouse cursor - position_new_edge(event->pos()); - return; - } - } - - // Handle selections with the right mouse button - if (event->button() == Qt::RightButton) { - if (!item || !item->isSelected()) { - // Qt doesn't do this by default for some reason - if (!(event->modifiers() & Qt::ShiftModifier)) { - scene_.clearSelection(); - } - - // If there's an item here, select it - if (item) { - item->setSelected(true); - } - } - } - - if (attached_items_.isEmpty()) { - // Default QGraphicsView functionality (selecting, dragging, etc.) - super::mousePressEvent(event); - } - - // For any selected item, store its position in case the user is dragging it somewhere else - auto selected_items = scene_.get_selected_items(); - foreach (NodeViewItem *i, selected_items) { - // Ignore items attached to the cursor - if (!is_item_attached_to_cursor(i)) { - dragging_items_.insert(i, i->get_node_position()); - } - } -} - -void NodeView::mouseMoveEvent(QMouseEvent *event) -{ - if (hand_move(event)) - return; - - if (create_edge_) { - position_new_edge(event->pos()); - return; - } - - if (attached_items_.isEmpty()) { - super::mouseMoveEvent(event); - } - - // See if there are any items attached - if (!attached_items_.isEmpty()) { - process_moving_attached_nodes(event->pos()); - } -} - -void NodeView::mouseReleaseEvent(QMouseEvent *event) -{ - if (hand_release(event)) - return; - - if (create_edge_) { - end_edge_drag(); - } - - MultiUndoCommand *command = new MultiUndoCommand(); - - Node *select_context = nullptr; - QVector select_nodes; - - bool had_attached_items = !attached_items_.isEmpty(); - - if (!attached_items_.isEmpty()) { - select_context = get_context_at_mouse_pos(event->pos()); - - if (select_context) { - select_nodes = process_dropping_attached_nodes(command, select_context, - event->pos()); - } else { - QToolTip::showText(QCursor::pos(), - tr("Nodes must be placed inside a context.")); - } - } - - QList> dragged_items; - for (auto it = dragging_items_.cbegin(); it != dragging_items_.cend(); - it++) { - dragged_items.append(it.key()); - } - - foreach (NodeViewItem *i, dragged_items) { - if (!i) { - continue; - } - QPointF current_pos = i->get_node_position(); - if (dragging_items_.value(i) != current_pos) { - command->add_child(new NodeSetPositionCommand( - i->get_node(), i->get_context(), current_pos)); - } - } - - Core::instance()->undo_stack()->push( - command, tr("Moved %1 Node(s)").arg(dragging_items_.size())); - - dragging_items_.clear(); - - if (!had_attached_items) { - super::mouseReleaseEvent(event); - } - - if (select_context) { - deselect_all(); - scene_.context_map().value(select_context)->select(select_nodes); - } -} - -void NodeView::mouseDoubleClickEvent(QMouseEvent *event) -{ - super::mouseDoubleClickEvent(event); - - if (!(event->modifiers() & Qt::ControlModifier)) { - NodeViewItem *item_at_cursor = - dynamic_cast(itemAt(event->pos())); - if (item_at_cursor) { - item_at_cursor->toggle_expanded(); - } - } -} - -void NodeView::dragEnterEvent(QDragEnterEvent *event) -{ - if (contexts_.empty()) { - event->ignore(); - return; - } - - QStringList mime_fmts = event->mimeData()->formats(); - - if (mime_fmts.contains(Project::k_item_mime_type)) { - QByteArray model_data = event->mimeData()->data(Project::k_item_mime_type); - QDataStream stream(&model_data, QIODevice::ReadOnly); - - // Variables to deserialize into - quintptr item_ptr; - QVector enabled_streams; - QVector new_attached; - - int y = 0; - - while (!stream.atEnd()) { - stream >> enabled_streams >> item_ptr; - - // Get Item object - Node *item = reinterpret_cast(item_ptr); - - if (ViewerOutput *f = dynamic_cast(item)) { - NodeViewItem *new_item; - - new_item = new NodeViewItem(f, nullptr); - new_item->set_flow_direction(scene_.get_flow_direction()); - new_item->set_node_position(QPointF(0, y)); - y++; - scene_.addItem(new_item); - - new_attached.append({ new_item, f, new_item->pos() }); - } - } - - if (new_attached.empty()) { - event->ignore(); - } else { - set_attached_items(new_attached); - - event->accept(); - } - } -} - -void NodeView::dragMoveEvent(QDragMoveEvent *event) -{ - if (attached_items_.empty()) { - event->ignore(); - } else { - process_moving_attached_nodes(event->pos()); - - if (get_context_at_mouse_pos(event->pos())) { - event->accept(); - } else { - event->ignore(); - } - } -} - -void NodeView::dropEvent(QDropEvent *event) -{ - if (Node *drop_ctx = get_context_at_mouse_pos(event->pos())) { - MultiUndoCommand *command = new MultiUndoCommand(); - QVector select_nodes = - process_dropping_attached_nodes(command, drop_ctx, event->pos()); - Core::instance()->undo_stack()->push( - command, tr("Dropped %1 Node(s)").arg(select_nodes.size())); - - deselect_all(); - scene_.context_map().value(drop_ctx)->select(select_nodes); - - event->accept(); - } else { - detach_items_from_cursor(false); - event->ignore(); - } -} - -void NodeView::dragLeaveEvent(QDragLeaveEvent *event) -{ - if (attached_items_.empty()) { - event->ignore(); - } else { - detach_items_from_cursor(false); - - event->accept(); - } -} - -void NodeView::resizeEvent(QResizeEvent *event) -{ - super::resizeEvent(event); - - reposition_mini_map(); - - if (overlay_view_) { - resize_overlay(); - } -} - -void NodeView::update_selection_cache() -{ - QVector current_selection = scene_.get_selected_items(); - - QVector selected; - QVector deselected; - - QVector sel_with_ctx(current_selection.size()); - - // Determine which nodes are newly selected - for (int j = 0; j < current_selection.size(); j++) { - NodeViewItem *i = current_selection.at(j); - Node *n = i->get_node(); - if (!selected_nodes_.contains(n)) { - selected.append(n); - selected_nodes_.append(n); - } - - sel_with_ctx[j] = { n, i->get_context() }; - } - - // Determine which nodes are newly deselected - if (current_selection.isEmpty()) { - // All nodes that were selected have been deselected, so we'll just set them all to `deselected` - deselected = selected_nodes_; - selected_nodes_.clear(); - } else { - foreach (Node *n, selected_nodes_) { - bool still_selected = false; - - foreach (NodeViewItem *i, current_selection) { - if (i->get_node() == n) { - still_selected = true; - break; - } - } - - if (!still_selected) { - deselected.append(n); - selected_nodes_.removeOne(n); - } - } - } - - if (!deselected.isEmpty()) { - emit nodes_deselected(deselected); - } - - if (!selected.isEmpty()) { - emit nodes_selected(selected); - } - - if (!dont_emit_selection_signals_) { - emit node_selection_changed(selected_nodes_); - emit node_selection_changed_with_contexts(sel_with_ctx); - } -} - -void NodeView::show_context_menu(const QPoint &pos) -{ - if (contexts_.isEmpty()) { - return; - } - - Menu m; - - MenuShared::instance()->add_items_for_edit_menu(&m, false); - - m.addSeparator(); - - QVector selected = scene_.get_selected_items(); - - NodeViewItem *item_under_cursor = dynamic_cast(itemAt(pos)); - - if (item_under_cursor && !selected.contains(item_under_cursor)) { - // Right-clicked a node that isn't part of the current selection, - // make the clicked node the sole selection so context-menu actions - // operate on it. - scene_.clearSelection(); - item_under_cursor->setSelected(true); - selected = scene_.get_selected_items(); - } - - if (item_under_cursor && !selected.isEmpty()) { - // Grouping - if (selected.size() == 1 && - dynamic_cast(selected.first()->get_node())) { - QAction *ungroup_action = m.addAction(tr("Ungroup")); - connect(ungroup_action, &QAction::triggered, this, - &NodeView::ungroup_nodes); - } else { - QAction *group_action = m.addAction(tr("Group")); - connect(group_action, &QAction::triggered, this, - &NodeView::group_nodes); - } - - // Color menu - MenuShared::instance()->add_color_coding_menu(&m); - - // Show in Viewer option for nodes based on Viewer - if (ViewerOutput *viewer = - dynamic_cast(selected.first()->get_node())) { - Q_UNUSED(viewer) - m.addSeparator(); - QAction *open_in_viewer_action = m.addAction(tr("Open in Viewer")); - connect(open_in_viewer_action, &QAction::triggered, this, - &NodeView::open_selected_node_in_viewer); - } - - m.addSeparator(); - - // Show in Parameter Editor - QAction *show_in_param_editor_action = - m.addAction(tr("Show in Parameter Editor")); - show_in_param_editor_action->setShortcut( - show_in_param_editor_action_->shortcut()); - connect(show_in_param_editor_action, &QAction::triggered, this, - &NodeView::show_selected_node_in_param_editor); - - // Properties - QAction *properties_action = m.addAction(tr("P&roperties")); - connect(properties_action, &QAction::triggered, this, - &NodeView::show_node_properties); - - } else { - QAction *curved_action = m.addAction(tr("Smooth Edges")); - curved_action->setCheckable(true); - curved_action->setChecked(scene_.get_edges_are_curved()); - connect(curved_action, &QAction::triggered, &scene_, - &NodeViewScene::set_edges_are_curved); - - m.addSeparator(); - - Menu *direction_menu = new Menu(tr("Direction"), &m); - m.addMenu(direction_menu); - - direction_menu->add_action_with_data(tr("Top to Bottom"), - NodeViewCommon::k_top_to_bottom, - scene_.get_flow_direction()); - - direction_menu->add_action_with_data(tr("Bottom to Top"), - NodeViewCommon::k_bottom_to_top, - scene_.get_flow_direction()); - - direction_menu->add_action_with_data(tr("Left to Right"), - NodeViewCommon::k_left_to_right, - scene_.get_flow_direction()); - - direction_menu->add_action_with_data(tr("Right to Left"), - NodeViewCommon::k_right_to_left, - scene_.get_flow_direction()); - - connect(direction_menu, &Menu::triggered, this, - &NodeView::context_menu_set_direction); - - m.addSeparator(); - - Menu *add_menu = create_add_menu(&m); - m.addMenu(add_menu); - } - - m.exec(mapToGlobal(pos)); -} - -void NodeView::create_node_slot(QAction *action) -{ - Node *new_node = create_node_from_menu_action(action); - - if (new_node) { - NodeViewItem *new_item = new NodeViewItem(new_node, nullptr); - new_item->set_flow_direction(scene_.get_flow_direction()); - scene_.addItem(new_item); - - QVector new_attached; - - new_attached.append({ new_item, new_node, QPointF(0, 0) }); - - if (NodeGroup *new_group = dynamic_cast(new_node)) { - for (auto it = new_group->get_context_positions().cbegin(); - it != new_group->get_context_positions().cend(); it++) { - new_attached.append({ nullptr, it.key(), QPointF(0, 0) }); - } - } - - set_attached_items(new_attached); - } -} - -void NodeView::context_menu_set_direction(QAction *action) -{ - set_flow_direction( - static_cast(action->data().toInt())); -} - -void NodeView::open_selected_node_in_viewer() -{ - // Find first viewer in list of selected nodes and open it - foreach (Node *n, selected_nodes_) { - if (ViewerOutput *viewer = dynamic_cast(n)) { - Core::instance()->open_node_in_viewer(viewer); - break; - } - } -} - -void NodeView::update_scene_bounding_rect() -{ - // Get current items bounding rect - QRectF r = scene_.itemsBoundingRect(); - - // Adjust so that it fills the view - r.adjust(-width(), -height(), width(), height()); - - // Set it - scene_.setSceneRect(r); -} - -void NodeView::center_on_items_bounding_rect() -{ - centerOn(scene_.itemsBoundingRect().center()); -} - -void NodeView::center_on_node(OakEngineNode *n) -{ - foreach (NodeViewContext *ctx, scene_.context_map()) { - if (NodeViewItem *item = ctx->get_item_from_map( - reinterpret_cast(n))) { - centerOn(item); - break; - } - } -} - -void NodeView::reposition_mini_map() -{ - if (minimap_->isVisible()) { - int margin = fontMetrics().height(); - - int w = width() - minimap_->width() - margin; - int h = height() - minimap_->height() - margin; - - if (verticalScrollBar()->isVisible()) { - w -= verticalScrollBar()->width(); - } - - if (horizontalScrollBar()->isVisible()) { - h -= horizontalScrollBar()->height(); - } - - minimap_->move(w, h); - - update_viewport_on_mini_map(); - } -} - -void NodeView::update_viewport_on_mini_map() -{ - if (minimap_->isVisible()) { - minimap_->set_viewport_rect(mapToScene(viewport()->rect())); - } -} - -void NodeView::move_to_scene_point(const QPointF &pos) -{ - centerOn(pos); -} - -void NodeView::node_removed_from_graph() -{ - Node *context = static_cast(sender()); - - remove_context(context); - - contexts_.removeOne(context); -} - -void NodeView::detach_items_from_cursor(bool delete_nodes_too) -{ - foreach (const AttachedItem &ai, attached_items_) { - delete ai.item; - - if (delete_nodes_too) { - qDebug() << "deleting" << ai.node; - delete ai.node; - } - } - - attached_items_.clear(); -} - -void NodeView::set_flow_direction(NodeViewCommon::FlowDirection dir) -{ - scene_.set_flow_direction(dir); -} - -void NodeView::move_attached_nodes_to_cursor(const QPoint &p) -{ - QPointF item_pos = mapToScene(p); - - for (const AttachedItem &i : qAsConst(attached_items_)) { - if (i.item) { - i.item->setPos(item_pos + i.original_pos); - } - } -} - -void NodeView::process_moving_attached_nodes(const QPoint &pos) -{ - // Move those items to the cursor - move_attached_nodes_to_cursor(pos); - - // See if the user clicked on an edge (only when dropping single nodes) - if (attached_items_.size() == 1) { - Node *attached_node = attached_items_.first().item->get_node(); - - QRect edge_detect_rect(pos, pos); - - int edge_detect_radius = fontMetrics().height(); - edge_detect_rect.adjust(-edge_detect_radius, -edge_detect_radius, - edge_detect_radius, edge_detect_radius); - - QList items = this->items(edge_detect_rect); - - NodeViewEdge *new_drop_edge = nullptr; - - // See if there is an edge here - for (QGraphicsItem *item : qAsConst(items)) { - new_drop_edge = dynamic_cast(item); - - if (new_drop_edge) { - drop_input_.reset(); - - NodeValue::Type drop_edge_data_type = - new_drop_edge->input().get_data_type(); - - // Determine best input to connect to our new node - if (attached_node->get_effect_input().is_valid()) { - // If node specifies an effect input, use that immediately - drop_input_ = attached_node->get_effect_input(); - } else { - // Otherwise, we may have to iterate to find a valid one - for (const QString &input : attached_node->inputs()) { - if (input == Node::k_enabled_input) { - // Ignore enabled input - continue; - } - - NodeInput i(attached_node, input); - - if (attached_node->is_input_connectable(input)) { - if (attached_node->get_input_data_type(input) == - drop_edge_data_type) { - // Found exactly the type we're looking for, set and break this loop - drop_input_ = i; - break; - } else if (!drop_input_.is_valid()) { - // Default to first connectable input - drop_input_ = i; - } - } - } - } - - if (attached_node->inputs_from(new_drop_edge->input().node(), - true)) { - drop_input_.reset(); - } - - if (drop_input_.is_valid()) { - break; - } else { - new_drop_edge = nullptr; - } - } - } - - if (drop_edge_ != new_drop_edge) { - if (drop_edge_) { - drop_edge_->set_highlighted(false); - } - - drop_edge_ = new_drop_edge; - - if (drop_edge_) { - drop_edge_->set_highlighted(true); - } - } - } -} - -QVector -NodeView::process_dropping_attached_nodes(MultiUndoCommand *command, - Node *select_context, const QPoint &pos) -{ - QVector select_nodes; - - // Make a copy - QVector attached = attached_items_; - - for (int i = 0; i < attached.size(); i++) { - const AttachedItem &ai = attached.at(i); - - if (ai.node->inputs_from(select_context, true)) { - attached.removeAt(i); - } else if (select_context->context_contains_node(ai.node)) { - select_nodes.append(ai.node); - attached.removeAt(i); - } - } - - { - MultiUndoCommand *add_command = new MultiUndoCommand(); - - foreach (const AttachedItem &ai, attached) { - // Add node to the same graph that the context is in - if (ai.node->parent() != select_context->parent()) { - add_command->add_child( - new NodeAddCommand(select_context->parent(), ai.node)); - if (ai.node->is_item() && !ai.node->folder()) { - add_command->add_child(new FolderAddChild( - select_context->parent()->root(), ai.node)); - } - } - - // Add node to the context - if (ai.item) { - select_nodes.append(ai.node); - add_command->add_child(new NodeSetPositionCommand( - ai.node, select_context, - scene_.context_map() - .value(select_context) - ->map_scene_pos_to_node_pos_in_context(ai.item->pos()))); - } - } - - if (add_command->child_count()) { - add_command->redo_now(); - command->add_child(add_command); - } else { - delete add_command; - } - } - - { - // Dropped attached item onto an edge, connect it between them - MultiUndoCommand *drop_edge_command = new MultiUndoCommand(); - if (attached.size() == 1) { - Node *dropping_node = nullptr; - - foreach (const AttachedItem &ai, attached) { - if (ai.item && !ai.node->inputs_from(select_context, true)) { - dropping_node = ai.node; - break; - } - } - - if (dropping_node && drop_edge_) { - // Remove old edge - drop_edge_command->add_child(new NodeEdgeRemoveCommand( - drop_edge_->output(), drop_edge_->input())); - - // Place new edges - drop_edge_command->add_child( - new NodeEdgeAddCommand(drop_edge_->output(), drop_input_)); - drop_edge_command->add_child( - new NodeEdgeAddCommand(dropping_node, drop_edge_->input())); - } - - drop_edge_ = nullptr; - } - if (drop_edge_command->child_count()) { - drop_edge_command->redo_now(); - command->add_child(drop_edge_command); - } else { - delete drop_edge_command; - } - } - - detach_items_from_cursor(false); - - return select_nodes; -} - -Node *NodeView::get_context_at_mouse_pos(const QPoint &p) -{ - QList items_at_cursor = this->items(p); - foreach (QGraphicsItem *i, items_at_cursor) { - if (NodeViewContext *context_item = - dynamic_cast(i)) { - return context_item->get_context(); - } - } - - return nullptr; -} - -void NodeView::connect_selection_changed_signal() -{ - connect(&scene_, &QGraphicsScene::selectionChanged, this, - &NodeView::update_selection_cache); -} - -void NodeView::disconnect_selection_changed_signal() -{ - disconnect(&scene_, &QGraphicsScene::selectionChanged, this, - &NodeView::update_selection_cache); -} - -void NodeView::zoom_into_cursor_position(QWheelEvent *event, double multiplier, - const QPointF &cursor_pos) -{ - Q_UNUSED(event) - - double test_scale = scale_ * multiplier; - - if (test_scale > k_minimum_scale) { - int anchor_x = - qRound(double(cursor_pos.x() + horizontalScrollBar()->value()) / - scale_ * test_scale - - cursor_pos.x()); - int anchor_y = - qRound(double(cursor_pos.y() + verticalScrollBar()->value()) / - scale_ * test_scale - - cursor_pos.y()); - - scale(multiplier, multiplier); - - this->horizontalScrollBar()->setValue(anchor_x); - this->verticalScrollBar()->setValue(anchor_y); - - scale_ = test_scale; - } -} - -bool NodeView::event(QEvent *event) -{ - if (event->type() == QEvent::ShortcutOverride) { - QKeyEvent *se = static_cast(event); - if (se->key() == Qt::Key_Left || se->key() == Qt::Key_Right || - se->key() == Qt::Key_Up || se->key() == Qt::Key_Down) { - se->accept(); - return true; - } - } - - return super::event(event); -} - -bool NodeView::eventFilter(QObject *object, QEvent *event) -{ - return super::eventFilter(object, event); -} - -void NodeView::changeEvent(QEvent *e) -{ - // Add translation code - - super::changeEvent(e); -} - -void NodeView::zoom_from_keyboard(double multiplier) -{ - QPoint cursor_pos = mapFromGlobal(QCursor::pos()); - - // If the cursor is not currently within the widget, zoom into the center - if (!rect().contains(cursor_pos)) { - cursor_pos = QPoint(width() / 2, height() / 2); - } - - zoom_into_cursor_position(nullptr, multiplier, cursor_pos); -} - -void NodeView::clear_create_edge_input_if_necessary() -{ - if (create_edge_from_output_ && create_edge_input_.is_valid()) { - create_edge_input_.reset(); - } -} - -QPointF NodeView::get_estimated_position_for_context(NodeViewItem *item, - Node *context) const -{ - return item->get_node_position() - context_offsets_.value(context); -} - -NodeViewItem *NodeView::get_assumed_item_for_selected_node(Node *node) -{ - // Try to find corresponding selected item - foreach (NodeViewContext *ctx, scene_.context_map()) { - NodeViewItem *item = ctx->get_item_from_map(node); - if (item && item->get_node() == node && item->isSelected()) { - // Good enough - return item; - } - } - - return nullptr; -} - -bool NodeView::get_assumed_position_for_selected_node(Node *node, - Node::Position *pos) -{ - if (NodeViewItem *item = get_assumed_item_for_selected_node(node)) { - *pos = item->get_node_position_data(); - return true; - } else { - return false; - } -} - -Menu *NodeView::create_add_menu(Menu *parent) -{ - Menu *add_menu = create_node_menu(parent); - add_menu->setTitle(tr("Add")); - connect(add_menu, &Menu::triggered, this, &NodeView::create_node_slot); - return add_menu; -} - -void NodeView::position_new_edge(const QPoint &pos) -{ - // Determine scene coordinate - QPointF scene_pt = mapToScene(pos); - - // Find if the cursor is currently inside an item - NodeViewItem *item_at_cursor = dynamic_cast(itemAt(pos)); - - NodeViewItem *source_item = create_edge_from_output_ ? - create_edge_output_item_ : - create_edge_input_item_; - NodeViewItem *&opposing_item = create_edge_from_output_ ? - create_edge_input_item_ : - create_edge_output_item_; - - // Filter out connecting to self - if (item_at_cursor && item_at_cursor->get_node() == source_item->get_node()) { - item_at_cursor = nullptr; - } - - // Collapse any items that the cursor is no longer inside - int i = create_edge_expanded_items_.size() - 1; - for (; i >= 0; i--) { - NodeViewItem *nvi = create_edge_expanded_items_.at(i); - QPointF local_pt = nvi->mapFromScene(scene_pt); - - if (nvi->scene() == &scene_ && - (nvi->contains(local_pt) || - (!nvi->is_output_item() && - nvi->parentItem()->contains( - nvi->parentItem()->mapFromScene(scene_pt)) && - local_pt.y() > nvi->rect().bottom()))) { - break; - } else { - // Collapsing an item will destroy its children, so if the cursor item happens to be a child - // of the item we're about to collapse, set it to null - if (item_at_cursor && item_at_cursor->parentItem() == nvi) { - item_at_cursor = nullptr; - } - - if (opposing_item && opposing_item->parentItem() == nvi) { - opposing_item = nullptr; - clear_create_edge_input_if_necessary(); - } - - collapse_item(nvi); - } - } - create_edge_expanded_items_.resize(i + 1); - - // Expand item if possible - if (item_at_cursor && item_at_cursor->can_be_expanded() && - !item_at_cursor->is_expanded() && create_edge_from_output_) { - expand_item(item_at_cursor); - create_edge_expanded_items_.append(item_at_cursor); - } - - // Filter out connecting to a node that connects to us or an item of the same type - if (item_at_cursor && - ((create_edge_from_output_ && source_item->get_node()->inputs_from( - item_at_cursor->get_node(), true)) || - (!create_edge_from_output_ && item_at_cursor->get_node()->inputs_from( - source_item->get_node(), true)) || - (create_edge_from_output_ == item_at_cursor->is_output_item()))) { - item_at_cursor = nullptr; - } - - // Filter out "output node" of the context, we assume users won't want to fetch the output of this - if (item_at_cursor && !create_edge_from_output_ && - item_at_cursor->is_labelled_as_output_of_context()) { - item_at_cursor = nullptr; - } - - // If the item has changed - if (item_at_cursor != opposing_item) { - // If we had a destination active, disconnect from it since the item has changed - if (opposing_item) { - opposing_item->set_highlighted(false); - opposing_item = nullptr; - } - - // Clear cached input - clear_create_edge_input_if_necessary(); - - // If this is an input and we're - opposing_item = item_at_cursor; - - if (opposing_item) { - opposing_item->set_highlighted(true); - if (!opposing_item->is_output_item()) { - create_edge_input_ = opposing_item->get_input(); - } - } - } - - QPointF output_point = create_edge_output_item_ ? - create_edge_output_item_->get_output_point() : - scene_pt; - QPointF input_point = create_edge_input_.is_valid() ? - create_edge_input_item_->get_input_point() : - scene_pt; - - create_edge_->set_points(output_point, input_point); - create_edge_->set_connected(create_edge_output_item_ && - create_edge_input_.is_valid()); -} - -void NodeView::group_nodes() -{ - // Get items - QVector items = scene_.get_selected_items(); - if (items.isEmpty()) { - return; - } - - // Get node context - Node *context = items.first()->get_context(); - QPointF avg_pos = items.first()->get_node_position(); - for (int i = 1; i < items.size(); i++) { - if (items.at(i)->get_context() != context) { - QMessageBox::critical( - this, tr("Failed to group nodes"), - tr("Nodes can only be grouped if they're in the same context.")); - return; - } - - avg_pos += items.at(i)->get_node_position(); - } - avg_pos /= items.size(); - - // Create group - NodeGroup *group = new NodeGroup(); - - // Add group to graph and context - MultiUndoCommand *command = new MultiUndoCommand(); - - // Add nodes to group - Node *output_passthrough = nullptr; - QVector nodes_to_group = selected_nodes_; - deselect_all(); - foreach (Node *n, nodes_to_group) { - command->add_child( - new NodeRemovePositionFromContextCommand(n, context)); - command->add_child(new NodeSetPositionCommand( - n, group, context->get_node_position_data_in_context(n))); - - for (auto it = n->inputs().cbegin(); it != n->inputs().cend(); it++) { - NodeInput input(n, *it, -1); - - if (!input.is_connected() || - !nodes_to_group.contains(input.get_connected_output())) { - command->add_child( - new NodeGroupAddInputPassthrough(group, input)); - } - } - - if (!output_passthrough) { - // Default to the first node we find that doesn't output to a node inside the group - output_passthrough = nodes_to_group.first(); - foreach (Node *potential_in, nodes_to_group) { - if (potential_in != n && !potential_in->inputs_from(n, false)) { - output_passthrough = n; - break; - } - } - } - } - - // Set output passthrough - command->add_child( - new NodeGroupSetOutputPassthrough(group, output_passthrough)); - - // Add group to graph - command->add_child(new NodeAddCommand(context->parent(), group)); - command->add_child(new NodeSetPositionCommand(group, context, avg_pos)); - - // Do command - Core::instance()->label_nodes({ group }, command); - - Core::instance()->undo_stack()->push(command, tr("Grouped Nodes")); -} - -void NodeView::ungroup_nodes() -{ - NodeViewItem *group_item = nullptr; - QVector items = scene_.get_selected_items(); - if (items.isEmpty()) { - return; - } - - NodeGroup *group = nullptr; - foreach (NodeViewItem *i, items) { - if ((group = dynamic_cast(i->get_node()))) { - group_item = i; - break; - } - } - - if (!group_item) { - return; - } - - MultiUndoCommand *command = new MultiUndoCommand(); - - Node *context = group_item->get_context(); - - command->add_child( - new NodeRemovePositionFromContextCommand(group, context)); - command->add_child(new NodeRemoveAndDisconnectCommand(group)); - - for (auto it = group->get_context_positions().cbegin(); - it != group->get_context_positions().cend(); it++) { - command->add_child( - new NodeRemovePositionFromContextCommand(it.key(), group)); - command->add_child(new NodeSetPositionCommand( - it.key(), context, group->get_node_position_data_in_context(it.key()))); - } - - Core::instance()->undo_stack()->push(command, tr("Ungrouped Nodes")); -} - -void NodeView::show_node_properties() -{ - Node *first_node = selected_nodes_.first(); - - if (NodeGroup *group = dynamic_cast(first_node)) { - if (!overlay_view_) { - overlay_view_ = new NodeView(this); - overlay_view_->show(); - - QPushButton *overlay_close_btn = new QPushButton(overlay_view_); - overlay_close_btn->setIcon(icon::error); - int offset = overlay_close_btn->sizeHint().width() / 2; - overlay_close_btn->move(offset, offset); - overlay_close_btn->show(); - - connect(overlay_view_, &NodeView::nodes_selected, this, - &NodeView::nodes_selected); - connect(overlay_view_, &NodeView::nodes_deselected, this, - &NodeView::nodes_deselected); - connect(overlay_view_, &NodeView::node_group_opened, this, - &NodeView::node_group_opened); - connect(overlay_view_, &NodeView::node_group_closed, this, - &NodeView::node_group_closed); - connect(overlay_view_, &NodeView::esc_pressed, this, - &NodeView::close_overlay); - connect(overlay_close_btn, &QPushButton::clicked, this, - &NodeView::close_overlay); - - const QColor &bgcol = overlay_view_->palette().base().color(); - overlay_view_->setStyleSheet( - QStringLiteral( - "QGraphicsView { background: rgba(%1, %2, %3, 0.8); }") - .arg(QString::number(bgcol.red()), - QString::number(bgcol.green()), - QString::number(bgcol.blue()))); - - overlay_close_btn->setStyleSheet( - QStringLiteral("background: transparent; border: none;")); - } - overlay_view_->set_contexts({ group }); - resize_overlay(); - QMetaObject::invokeMethod(overlay_view_, - &NodeView::center_on_items_bounding_rect, - Qt::QueuedConnection); - overlay_view_->setFocus(); - - emit nodes_deselected(selected_nodes_); - emit node_selection_changed(QVector()); - emit node_selection_changed_with_contexts(QVector()); - overlay_view_->select_all(); - - emit node_group_opened(group); - } else { - label_selected_nodes(); - } -} - -void NodeView::show_selected_node_in_param_editor() -{ - QVector selected = scene_.get_selected_items(); - if (selected.isEmpty()) { - return; - } - - QVector selection_with_contexts; - selection_with_contexts.reserve(selected.size()); - foreach (NodeViewItem *item, selected) { - if (item && item->get_node()) { - selection_with_contexts.append( - Node::ContextPair{ item->get_node(), item->get_context() }); - } - } - - if (selection_with_contexts.isEmpty()) { - return; - } - - if (PanelManager::instance()) { - if (PanelWidget *panel = PanelManager::instance()->get_panel_with_name( - QStringLiteral("ParamPanel"))) { - panel->show(); - QMetaObject::invokeMethod(panel, &PanelWidget::raise, - Qt::QueuedConnection); - QMetaObject::invokeMethod( - panel, - [panel]() { - panel->activateWindow(); - panel->setFocus(Qt::OtherFocusReason); - }, - Qt::QueuedConnection); - } - } - - emit node_selection_changed_with_contexts(selection_with_contexts); -} - -void NodeView::label_selected_nodes() -{ - Core::instance()->label_nodes(selected_nodes_); -} - -void NodeView::item_about_to_be_deleted(NodeViewItem *item) -{ - dragging_items_.remove(item); - - if (create_edge_) { - // Item should be removed from scene, but not yet deleted, allowing a safe PositionNewEdge call - // to disconnect - position_new_edge(mapFromGlobal(QCursor::pos())); - - QGraphicsItem *test = item; - do { - if (test == item) { - break; - } - - test = test->parentItem(); - } while (test); - - if (test == item) { - // Cancel edge function - end_edge_drag(true); - } - } -} - -void NodeView::close_overlay() -{ - if (overlay_view_->overlay_view_) { - overlay_view_->close_overlay(); - } - - overlay_view_->deleteLater(); - overlay_view_ = nullptr; - emit node_group_closed(); -} - -void NodeView::add_context(Node *n) -{ - NodeViewContext *ctx = scene_.add_context(n); - - connect(ctx, &NodeViewContext::item_about_to_be_deleted, this, - &NodeView::item_about_to_be_deleted); - - connect(n, &Node::removed_from_graph, this, &NodeView::node_removed_from_graph); -} - -void NodeView::remove_context(Node *n) -{ - scene_.remove_context(n); - disconnect(n, &Node::removed_from_graph, this, - &NodeView::node_removed_from_graph); -} - -bool NodeView::is_item_attached_to_cursor(NodeViewItem *item) const -{ - foreach (const AttachedItem &ai, attached_items_) { - if (ai.item == item) { - return true; - } - } - - return false; -} - -void NodeView::expand_item(NodeViewItem *item) -{ - item->set_expanded(true); - item->setZValue(100); -} - -void NodeView::collapse_item(NodeViewItem *item) -{ - item->set_expanded(false); - item->setZValue(0); -} - -void NodeView::end_edge_drag(bool cancel) -{ - // Check if the edge was reconnected to the same place as before - MultiUndoCommand *command = new MultiUndoCommand(); - - bool reconnected_to_itself = false; - - if (create_edge_already_exists_) { - if (!cancel) { - if (create_edge_output_item_ == create_edge_->from_item() && - create_edge_->input() == create_edge_input_) { - reconnected_to_itself = true; - } else { - // We are moving (or removing) an existing edge - command->add_child(new NodeEdgeRemoveCommand( - create_edge_->output(), create_edge_->input())); - } - } - } else { - // We're creating a new edge, which means this UI object is only temporary - delete create_edge_; - } - - create_edge_ = nullptr; - - // Clear highlight if we set one - if (create_edge_output_item_) { - create_edge_output_item_->set_highlighted(false); - } - if (create_edge_input_item_) { - create_edge_input_item_->set_highlighted(false); - } - - QString command_name; - - NodeInput &creating_input = create_edge_input_; - if (create_edge_output_item_ && create_edge_input_item_ && !cancel) { - if (creating_input.is_valid()) { - // Make connection - if (!reconnected_to_itself) { - Node *creating_output = create_edge_output_item_->get_node(); - - while (NodeGroup *output_group = - dynamic_cast(creating_output)) { - creating_output = output_group->get_output_passthrough(); - } - - while (NodeGroup *input_group = - dynamic_cast(creating_input.node())) { - creating_input = - input_group->get_input_from_id(creating_input.input()); - } - - if (creating_input.is_connected()) { - Node::OutputConnection existing_edge_to_remove = { - creating_input.get_connected_output(), creating_input - }; - - Node *already_connected_output = - creating_input.get_connected_output(); - NodeViewContext *ctx = - get_context_item_from_node_item(create_edge_input_item_); - if (ctx && !ctx->get_item_from_map(already_connected_output)) { - if (QMessageBox::warning( - this, QString(), - tr("Input \"%1\" is currently connected to node \"%2\", which is not visible in this context. " - "By connecting this, that connection will be removed. Do you wish to continue?") - .arg(creating_input.name(), - already_connected_output - ->get_label_and_name()), - QMessageBox::Yes | QMessageBox::No) == - QMessageBox::No) { - cancel = true; - } - } - - if (!cancel) { - command->add_child(new NodeEdgeRemoveCommand( - existing_edge_to_remove.first, - existing_edge_to_remove.second)); - } - } - - if (!cancel) { - command->add_child(new NodeEdgeAddCommand(creating_output, - creating_input)); - - command_name = Node::get_connect_command_string( - creating_output, creating_input); - - // If the output is not in the input's context, add it now. We check the item rather than - // the node itself, because sometimes a node may not be in the context but another node - // representing it will be (e.g. groups) - if (!scene_.context_map() - .value(create_edge_input_item_->get_context()) - ->get_item_from_map(creating_output)) { - command->add_child(new NodeSetPositionCommand( - creating_output, - create_edge_input_item_->get_context(), - scene_.context_map() - .value(create_edge_input_item_->get_context()) - ->map_scene_pos_to_node_pos_in_context( - create_edge_output_item_->scenePos()))); - } - } - } - } - } - - creating_input.reset(); - create_edge_output_item_ = nullptr; - create_edge_input_item_ = nullptr; - - // Collapse any items we expanded - for (auto it = create_edge_expanded_items_.crbegin(); - it != create_edge_expanded_items_.crend(); it++) { - collapse_item(*it); - } - create_edge_expanded_items_.clear(); - - Core::instance()->undo_stack()->push(command, command_name); -} - -void NodeView::post_paste(const QVector &new_nodes, - const Node::PositionMap &map) -{ - QVector new_attached; - - NodeViewItem *first_item = nullptr; - - for (int i = 0; i < new_nodes.size(); i++) { - Node *node = new_nodes.at(i); - - // Determine if item had a position, if not don't create an item for it - NodeViewItem *new_item; - - if (map.contains(node)) { - new_item = new NodeViewItem(node, nullptr); - new_item->set_flow_direction(scene_.get_flow_direction()); - new_item->set_node_position(map.value(node)); - scene_.addItem(new_item); - - if (!first_item) { - first_item = new_item; - } - } else { - new_item = nullptr; - } - - new_attached.append({ new_item, node, QPointF(0, 0) }); - } - - // Correct positions - if (first_item) { - for (int i = 0; i < new_attached.size(); i++) { - AttachedItem &ai = new_attached[i]; - - if (ai.item) { - ai.original_pos = ai.item->pos() - first_item->pos(); - } - } - } - - set_attached_items(new_attached); -} - -void NodeView::resize_overlay() -{ - overlay_view_->resize(this->size()); -} - -NodeViewContext *NodeView::get_context_item_from_node_item(NodeViewItem *item) -{ - QGraphicsItem *i = item; - while ((i = i->parentItem())) { - if (NodeViewContext *nvc = dynamic_cast(i)) { - return nvc; - } - } - return nullptr; -} - -void NodeView::set_attached_items(const QVector &items) -{ - // Detach anything currently attached - detach_items_from_cursor(); - - attached_items_ = items; - - // Move to cursor - move_attached_nodes_to_cursor(mapFromGlobal(QCursor::pos())); -} - -} diff --git a/.bak/nodeview/nodeview.h b/.bak/nodeview/nodeview.h deleted file mode 100644 index 9298180d6..000000000 --- a/.bak/nodeview/nodeview.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OAK_NODEVIEW_H -#define OAK_NODEVIEW_H - -#include -#include - -#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 &nodes); - - const QVector &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 &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 &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 &nodes); - - void nodes_deselected(const QVector &nodes); - - void node_selection_changed(const QVector &nodes); - void - node_selection_changed_with_contexts(const QVector &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 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 &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 &items); - QVector 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 create_edge_expanded_items_; - - NodeViewScene scene_; - - QVector selected_nodes_; - - QVector contexts_; - QVector last_set_filter_nodes_; - QMap context_offsets_; - - QMap 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 diff --git a/.bak/nodeview/nodeviewcommon.h b/.bak/nodeview/nodeviewcommon.h deleted file mode 100644 index c6562e5c4..000000000 --- a/.bak/nodeview/nodeviewcommon.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OAK_NODEVIEWCOMMON_H -#define OAK_NODEVIEWCOMMON_H - -#include - -#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 diff --git a/.bak/nodeview/nodeviewcontext.cpp b/.bak/nodeview/nodeviewcontext.cpp deleted file mode 100644 index 4175a3c2a..000000000 --- a/.bak/nodeview/nodeviewcontext.cpp +++ /dev/null @@ -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 . - */ - -#include "nodeviewcontext.h" - -#include -#include -#include -#include -#include -#include - -#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(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(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(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 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(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 &nodes) -{ - foreach (Node *n, nodes) { - if (NodeViewItem *item = item_map_.value(n)) { - item->setSelected(true); - } - } -} - -QVector NodeViewContext::get_selected_items() const -{ - QVector 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(sender()); - - add_node_internal(node, item_map_.value(group)); -} - -void NodeViewContext::group_removed_node(Node *node) -{ - NodeGroup *group = static_cast(sender()); - - if (item_map_.value(node) == item_map_.value(group)) { - item_map_.remove(node); - } -} - -} diff --git a/.bak/nodeview/nodeviewcontext.h b/.bak/nodeview/nodeviewcontext.h deleted file mode 100644 index f62ef6ff4..000000000 --- a/.bak/nodeview/nodeviewcontext.h +++ /dev/null @@ -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 . - */ - -#ifndef OAK_NODEVIEWCONTEXT_H -#define OAK_NODEVIEWCONTEXT_H - -#include -#include - -#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 &nodes); - - QVector 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 item_map_; - - QVector edges_; - -private slots: - void group_added_node(Node *node); - - void group_removed_node(Node *node); -}; - -} - -#endif // OAK_NODEVIEWCONTEXT_H diff --git a/.bak/nodeview/nodeviewedge.cpp b/.bak/nodeview/nodeviewedge.cpp deleted file mode 100644 index 7625ef94a..000000000 --- a/.bak/nodeview/nodeviewedge.cpp +++ /dev/null @@ -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 . - -***/ - -#include "nodeviewedge.h" - -#include -#include -#include -#include - -#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)); -} - -} diff --git a/.bak/nodeview/nodeviewedge.h b/.bak/nodeview/nodeviewedge.h deleted file mode 100644 index db33ab60d..000000000 --- a/.bak/nodeview/nodeviewedge.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OAK_NODEEDGEITEM_H -#define OAK_NODEEDGEITEM_H - -#include -#include - -#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 diff --git a/.bak/nodeview/nodeviewitem.cpp b/.bak/nodeview/nodeviewitem.cpp deleted file mode 100644 index 320e0469b..000000000 --- a/.bak/nodeview/nodeviewitem.cpp +++ /dev/null @@ -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 . - -***/ - -#include "nodeviewitem.h" - -#include -#include -#include -#include -#include - -#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 NodeViewItem::get_all_edges_recursively() const -{ - QVector 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 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 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 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(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(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(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(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(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; -} - -} diff --git a/.bak/nodeview/nodeviewitem.h b/.bak/nodeview/nodeviewitem.h deleted file mode 100644 index 93d4f13ac..000000000 --- a/.bak/nodeview/nodeviewitem.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OAK_NODEVIEWITEM_H -#define OAK_NODEVIEWITEM_H - -#include -#include -#include -#include - -#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 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 &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 children_; - - /// Sizing variables to use when drawing - int node_border_width_; - - /** - * @brief Expanded state - */ - bool expanded_; - - bool highlighted_; - - NodeViewCommon::FlowDirection flow_dir_; - - QVector 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 diff --git a/.bak/nodeview/nodeviewitemconnector.cpp b/.bak/nodeview/nodeviewitemconnector.cpp deleted file mode 100644 index 8e05ade87..000000000 --- a/.bak/nodeview/nodeviewitemconnector.cpp +++ /dev/null @@ -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 . - -***/ - -#include "nodeviewitemconnector.h" - -#include -#include -#include -#include - -#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; -} - -} diff --git a/.bak/nodeview/nodeviewitemconnector.h b/.bak/nodeview/nodeviewitemconnector.h deleted file mode 100644 index 253e15014..000000000 --- a/.bak/nodeview/nodeviewitemconnector.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OAK_NODEVIEWITEMCONNECTOR_H -#define OAK_NODEVIEWITEMCONNECTOR_H - -#include - -#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 diff --git a/.bak/nodeview/nodeviewminimap.cpp b/.bak/nodeview/nodeviewminimap.cpp deleted file mode 100644 index 3e7baace5..000000000 --- a/.bak/nodeview/nodeviewminimap.cpp +++ /dev/null @@ -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 . - -***/ - -#include "nodeviewminimap.h" - -#include - -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())); -} - -} diff --git a/.bak/nodeview/nodeviewminimap.h b/.bak/nodeview/nodeviewminimap.h deleted file mode 100644 index de7176d50..000000000 --- a/.bak/nodeview/nodeviewminimap.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OAK_NODEVIEWMINIMAP_H -#define OAK_NODEVIEWMINIMAP_H - -#include - -#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 diff --git a/.bak/nodeview/nodeviewscene.cpp b/.bak/nodeview/nodeviewscene.cpp deleted file mode 100644 index bb302efcc..000000000 --- a/.bak/nodeview/nodeviewscene.cpp +++ /dev/null @@ -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 . - -***/ - -#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 NodeViewScene::get_selected_items() const -{ - QVector 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_); - } - } -} - -} diff --git a/.bak/nodeview/nodeviewscene.h b/.bak/nodeview/nodeviewscene.h deleted file mode 100644 index 297f16447..000000000 --- a/.bak/nodeview/nodeviewscene.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OAK_NODEVIEWSCENE_H -#define OAK_NODEVIEWSCENE_H - -#include -#include - -#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 get_selected_items() const; - - const QHash &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 context_map_; - - Project *graph_; - - NodeViewCommon::FlowDirection direction_; - - bool curved_edges_; -}; - -} - -#endif // OAK_NODEVIEWSCENE_H diff --git a/.bak/nodeview/nodeviewtoolbar.cpp b/.bak/nodeview/nodeviewtoolbar.cpp deleted file mode 100644 index 54d377407..000000000 --- a/.bak/nodeview/nodeviewtoolbar.cpp +++ /dev/null @@ -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 . - */ - -#include "nodeviewtoolbar.h" - -#include -#include - -#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); -} - -} diff --git a/.bak/nodeview/nodeviewtoolbar.h b/.bak/nodeview/nodeviewtoolbar.h deleted file mode 100644 index 9dbe49bbf..000000000 --- a/.bak/nodeview/nodeviewtoolbar.h +++ /dev/null @@ -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 . - */ - -#ifndef OAK_NODEVIEWTOOLBAR_H -#define OAK_NODEVIEWTOOLBAR_H - -#include -#include - -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 diff --git a/.bak/nodeview/nodewidget.cpp b/.bak/nodeview/nodewidget.cpp deleted file mode 100644 index 0f3b62e90..000000000 --- a/.bak/nodeview/nodewidget.cpp +++ /dev/null @@ -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 . - -***/ - -#include "nodewidget.h" - -#include - -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()); -} - -} diff --git a/.bak/nodeview/nodewidget.h b/.bak/nodeview/nodewidget.h deleted file mode 100644 index 2715a2863..000000000 --- a/.bak/nodeview/nodewidget.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OAK_NODEWIDGET_H -#define OAK_NODEWIDGET_H - -#include - -#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 &nodes) - { - node_view_->set_contexts(nodes); - toolbar_->setEnabled(!nodes.isEmpty()); - } - -private: - NodeView *node_view_; - - NodeViewToolBar *toolbar_; -}; - -} - -#endif // OAK_NODEWIDGET_H diff --git a/.bak/projectexplorer/projectexplorer.cpp b/.bak/projectexplorer/projectexplorer.cpp deleted file mode 100644 index 26db30d42..000000000 --- a/.bak/projectexplorer/projectexplorer.cpp +++ /dev/null @@ -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 . - -***/ - -#include "projectexplorer.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#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 get_selected_proxy_footage(const QVector &items) -{ - QVector footage; - for (Node *node : items) { - Footage *candidate = dynamic_cast(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(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(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 &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(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(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( - 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(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(sort_model_.mapToSource(index).internalPointer()); - - // If the item is a folder, browse to it - if (dynamic_cast(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(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(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(i); - Sequence *sequence_cast_test = dynamic_cast(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 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(sel)) { - FootagePropertiesDialog fpd(this, static_cast(sel)); - fpd.exec(); - - } else if (dynamic_cast(sel)) { - Core::instance()->label_nodes(context_menu_items_); - - } else if (dynamic_cast(sel)) { - SequenceDialog sd(static_cast(sel), - SequenceDialog::k_existing, this); - sd.exec(); - } -} - -void ProjectExplorer::reveal_selected_footage() -{ - Footage *footage = static_cast(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(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(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(footage), - QFileInfo(file).fileName().toUtf8().constData()); - } - } -} - -void ProjectExplorer::open_context_menu_item_in_new_tab() -{ - Core::instance()->main_window()->open_folder( - static_cast(context_menu_items_.first()), false); -} - -void ProjectExplorer::open_context_menu_item_in_new_window() -{ - Core::instance()->main_window()->open_folder( - static_cast(context_menu_items_.first()), true); -} - -void ProjectExplorer::generate_proxies_for_selected_footage() -{ - if (!project()) { - qWarning() << "GenerateProxiesForSelectedFootage: no project"; - return; - } - - const QVector 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(new FacadeProxyTask(item))); - } -} - -void ProjectExplorer::set_selected_footage_proxy_enabled(bool enabled) -{ - const QVector 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(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 = - 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(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 = - 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(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(sender()); - - QModelIndexList selection = model->selectedIndexes(); - - QVector nodes; - - foreach (const QModelIndex &index, selection) { - Node *sel = static_cast( - 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(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 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 selected_items; - - for (int i = 0; i < index_list.size(); i++) { - QModelIndex index = sort_model_.mapToSource(index_list.at(i)); - - Node *item = static_cast(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 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(sel_item)) { - sel_item = sel_item->folder(); - } - - if (folder == nullptr) { - // If the folder is nullptr, cache it as this folder - folder = static_cast(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 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; -} - -} diff --git a/.bak/projectexplorer/projectexplorer.h b/.bak/projectexplorer/projectexplorer.h deleted file mode 100644 index 4f79ceaf2..000000000 --- a/.bak/projectexplorer/projectexplorer.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OAK_PROJECTEXPLORER_H -#define OAK_PROJECTEXPLORER_H - -#include -#include -#include -#include - -#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 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 &selected); - -private: - /** - * @brief Get all the blocks that solely rely on an input node - * - * Ignores blocks that depend on multiple inputs - */ - QList get_footage_blocks(QList 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 &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 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 diff --git a/.bak/projectexplorer/projectexplorericonview.cpp b/.bak/projectexplorer/projectexplorericonview.cpp deleted file mode 100644 index 8bf040870..000000000 --- a/.bak/projectexplorer/projectexplorericonview.cpp +++ /dev/null @@ -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 . - -***/ - -#include "projectexplorericonview.h" - -namespace olive -{ - -ProjectExplorerIconView::ProjectExplorerIconView(QWidget *parent) - : ProjectExplorerListViewBase(parent) -{ - setViewMode(QListView::IconMode); - - setItemDelegate(&delegate_); -} - -} diff --git a/.bak/projectexplorer/projectexplorericonview.h b/.bak/projectexplorer/projectexplorericonview.h deleted file mode 100644 index ce5871c78..000000000 --- a/.bak/projectexplorer/projectexplorericonview.h +++ /dev/null @@ -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 . - -***/ - -#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 diff --git a/.bak/projectexplorer/projectexplorericonviewitemdelegate.cpp b/.bak/projectexplorer/projectexplorericonviewitemdelegate.cpp deleted file mode 100644 index 692ddf4ce..000000000 --- a/.bak/projectexplorer/projectexplorericonviewitemdelegate.cpp +++ /dev/null @@ -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 . - -***/ - -#include "projectexplorericonviewitemdelegate.h" - -#include - -#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(Qt::AlignBottom | Qt::AlignRight), - index.data(Qt::UserRole).toString()); - max_name_width -= timecode_width; - } - - painter->drawText(text_rect, - static_cast(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(); - 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); - } -} - -} diff --git a/.bak/projectexplorer/projectexplorericonviewitemdelegate.h b/.bak/projectexplorer/projectexplorericonviewitemdelegate.h deleted file mode 100644 index d7c2d01e3..000000000 --- a/.bak/projectexplorer/projectexplorericonviewitemdelegate.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OAK_PROJECTEXPLORERICONVIEWITEMDELEGATE_H -#define OAK_PROJECTEXPLORERICONVIEWITEMDELEGATE_H - -#include - -#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 diff --git a/.bak/projectexplorer/projectexplorerlistview.cpp b/.bak/projectexplorer/projectexplorerlistview.cpp deleted file mode 100644 index b8574a1e5..000000000 --- a/.bak/projectexplorer/projectexplorerlistview.cpp +++ /dev/null @@ -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 . - -***/ - -#include "projectexplorerlistview.h" - -namespace olive -{ - -ProjectExplorerListView::ProjectExplorerListView(QWidget *parent) - : ProjectExplorerListViewBase(parent) -{ - setViewMode(QListView::ListMode); - - setItemDelegate(&delegate_); -} - -} diff --git a/.bak/projectexplorer/projectexplorerlistview.h b/.bak/projectexplorer/projectexplorerlistview.h deleted file mode 100644 index 2a34429f8..000000000 --- a/.bak/projectexplorer/projectexplorerlistview.h +++ /dev/null @@ -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 . - -***/ - -#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 diff --git a/.bak/projectexplorer/projectexplorerlistviewbase.cpp b/.bak/projectexplorer/projectexplorerlistviewbase.cpp deleted file mode 100644 index 848528713..000000000 --- a/.bak/projectexplorer/projectexplorerlistviewbase.cpp +++ /dev/null @@ -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 . - -***/ - -#include "projectexplorerlistviewbase.h" - -#include - -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(); - } -} - -} diff --git a/.bak/projectexplorer/projectexplorerlistviewbase.h b/.bak/projectexplorer/projectexplorerlistviewbase.h deleted file mode 100644 index 2169b573a..000000000 --- a/.bak/projectexplorer/projectexplorerlistviewbase.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OAK_PROJECTEXPLORERLISTVIEWBASE_H -#define OAK_PROJECTEXPLORERLISTVIEWBASE_H - -#include - -#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 diff --git a/.bak/projectexplorer/projectexplorerlistviewitemdelegate.cpp b/.bak/projectexplorer/projectexplorerlistviewitemdelegate.cpp deleted file mode 100644 index fa2d243a0..000000000 --- a/.bak/projectexplorer/projectexplorerlistviewitemdelegate.cpp +++ /dev/null @@ -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 . - -***/ - -#include "projectexplorerlistviewitemdelegate.h" - -#include - -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(); - 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(Qt::AlignLeft | Qt::AlignVCenter), text); -} - -} diff --git a/.bak/projectexplorer/projectexplorerlistviewitemdelegate.h b/.bak/projectexplorer/projectexplorerlistviewitemdelegate.h deleted file mode 100644 index 7297fdf33..000000000 --- a/.bak/projectexplorer/projectexplorerlistviewitemdelegate.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OAK_PROJECTEXPLORERLISTVIEWITEMDELEGATE_H -#define OAK_PROJECTEXPLORERLISTVIEWITEMDELEGATE_H - -#include - -#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 diff --git a/.bak/projectexplorer/projectexplorernavigation.cpp b/.bak/projectexplorer/projectexplorernavigation.cpp deleted file mode 100644 index ea7b90d7f..000000000 --- a/.bak/projectexplorer/projectexplorernavigation.cpp +++ /dev/null @@ -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 . - -***/ - -#include "projectexplorernavigation.h" - -#include -#include - -#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); -} - -} diff --git a/.bak/projectexplorer/projectexplorernavigation.h b/.bak/projectexplorer/projectexplorernavigation.h deleted file mode 100644 index 4bf12fa30..000000000 --- a/.bak/projectexplorer/projectexplorernavigation.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OAK_PROJECTEXPLORERLISTVIEWTOOLBAR_H -#define OAK_PROJECTEXPLORERLISTVIEWTOOLBAR_H - -#include -#include -#include -#include - -#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 diff --git a/.bak/projectexplorer/projectexplorertreeview.cpp b/.bak/projectexplorer/projectexplorertreeview.cpp deleted file mode 100644 index 76ae834dc..000000000 --- a/.bak/projectexplorer/projectexplorertreeview.cpp +++ /dev/null @@ -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 . - -***/ - -#include "projectexplorertreeview.h" - -#include - -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(); - } -} - -} diff --git a/.bak/projectexplorer/projectexplorertreeview.h b/.bak/projectexplorer/projectexplorertreeview.h deleted file mode 100644 index a334c36d5..000000000 --- a/.bak/projectexplorer/projectexplorertreeview.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OAK_PROJECTEXPLORERTREEVIEW_H -#define OAK_PROJECTEXPLORERTREEVIEW_H - -#include - -#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 diff --git a/.bak/projectexplorer/projectexplorerundo.h b/.bak/projectexplorer/projectexplorerundo.h deleted file mode 100644 index 0dc337a2d..000000000 --- a/.bak/projectexplorer/projectexplorerundo.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OAK_PROJECTEXPLORERUNDO_H -#define OAK_PROJECTEXPLORERUNDO_H - -#include "undo/undocommand.h" - -namespace olive -{ - -} - -#endif // OAK_PROJECTEXPLORERUNDO_H diff --git a/.bak/projectexplorer/projectviewmodel.cpp b/.bak/projectexplorer/projectviewmodel.cpp deleted file mode 100644 index 3b6e6c19e..000000000 --- a/.bak/projectexplorer/projectviewmodel.cpp +++ /dev/null @@ -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 . - -***/ - -#include "projectviewmodel.h" -#include "ui/icons/icons.h" - -#include -#include -#include - -#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(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(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(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(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(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(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 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(index.internalPointer()); - QVector streams; - - if (ViewerOutput *footage = - dynamic_cast(item)) { - streams = footage->get_enabled_streams_as_references(); - } - - stream << streams << reinterpret_cast(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(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 streams; - - // Loop through all data - MultiUndoCommand *move_command = new MultiUndoCommand(); - - int count = 0; - - while (!stream.atEnd()) { - stream >> streams >> item_ptr; - - Node *item = reinterpret_cast(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(item) || - !item_is_parent_of_child(static_cast(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(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(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(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(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(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(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(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(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); -} - -} diff --git a/.bak/projectexplorer/projectviewmodel.h b/.bak/projectexplorer/projectviewmodel.h deleted file mode 100644 index 85cddb825..000000000 --- a/.bak/projectexplorer/projectviewmodel.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OAK_VIEWMODEL_H -#define OAK_VIEWMODEL_H - -#include - -#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 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 19d03b571..e6ceefe73 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-24.04, macos-15, windows-latest] + os: [warp-ubuntu-latest-x64-16x, warp-macos-15-arm64-12x, warp-windows-latest-x64-32x] env: CMAKE_BUILD_TYPE: Release CCACHE_DIR: ${{ github.workspace }}/.ccache @@ -39,8 +39,10 @@ jobs: ninja-build pkg-config nasm \ 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 \ + ffmpeg \ libopencolorio-dev libopenimageio-dev libopenexr-dev libexpat1-dev \ - portaudio19-dev libgl1-mesa-dev libxkbcommon-dev ccache + portaudio19-dev libgl1-mesa-dev libgl1-mesa-dri libxkbcommon-dev ccache \ + xvfb libvulkan-dev mesa-vulkan-drivers vulkan-tools libshaderc-dev # ------------------------------------------------------------------ # macOS dependencies @@ -133,8 +135,9 @@ jobs: shell: msys2 {0} run: | echo "OTIO_LOCATION=${PWD}/otio-install" >> "$GITHUB_ENV" - # No rpath on Windows: test executables need the OTIO DLLs on PATH - echo "$(cygpath -w "${PWD}/otio-install/bin")" >> "$GITHUB_PATH" + # No rpath on Windows: test executables need the OTIO DLLs on 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) if: runner.os == 'Windows' && steps.otio-cache-win.outputs.cache-hit != 'true' @@ -172,7 +175,6 @@ jobs: cmake -S . -B build -G Ninja \ -DBUILD_TESTS=ON \ -DBUILD_QT6=ON \ - -DCMAKE_DISABLE_FIND_PACKAGE_Vulkan=ON \ -DOCIO_LOCATION=/usr \ -DOTIO_LOCATION=${OTIO_LOCATION} \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \ @@ -241,6 +243,31 @@ jobs: echo "OAK_OFX_PLUGIN_PATH=$PLUGIN_DIR" >> "$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) # ------------------------------------------------------------------ @@ -248,7 +275,25 @@ jobs: if: runner.os == 'Linux' env: QT_QPA_PLATFORM: offscreen - run: 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) if: runner.os == 'macOS' @@ -259,6 +304,21 @@ jobs: shell: msys2 {0} 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 # ------------------------------------------------------------------ @@ -266,7 +326,7 @@ jobs: if: runner.os == 'Linux' env: QT_QPA_PLATFORM: offscreen - run: ./build/tests/gtest/olive-gtest --gtest_filter="PluginSmoke*" + run: xvfb-run -a ./build/tests/gtest/olive-gtest --gtest_filter="PluginSmoke*" - name: Plugin Smoke Tests (macOS) if: runner.os == 'macOS' @@ -281,7 +341,7 @@ jobs: if: runner.os == 'Linux' env: QT_QPA_PLATFORM: offscreen - run: ./build/tests/gtest/olive-gtest --gtest_filter="PluginIntegration.*:PluginMisc.*" + run: xvfb-run -a ./build/tests/gtest/olive-gtest --gtest_filter="PluginIntegration.*:PluginMisc.*" - name: OFX Integration Tests (macOS) if: runner.os == 'macOS' @@ -291,7 +351,7 @@ jobs: if: runner.os == 'Linux' env: QT_QPA_PLATFORM: offscreen - run: ./build/tests/gtest/olive-gtest --gtest_filter="AudioSmoke*" + run: xvfb-run -a ./build/tests/gtest/olive-gtest --gtest_filter="AudioSmoke*" - name: Audio Smoke Tests (macOS) if: runner.os == 'macOS' @@ -306,7 +366,7 @@ jobs: if: runner.os == 'Linux' env: QT_QPA_PLATFORM: offscreen - run: ./build/tests/gtest/olive-gtest --gtest_filter="ViewerSmoke*" + run: xvfb-run -a ./build/tests/gtest/olive-gtest --gtest_filter="ViewerSmoke*" - name: Viewer Smoke Tests (macOS) if: runner.os == 'macOS' diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ebeb7960..4bf9e0799 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -255,11 +255,19 @@ endif() # OTIO's macOS dylibs use @loader_path install names: every binary that # (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() for each executable/shared library. -if (OAK_BUNDLE_OTIO AND APPLE AND OTIO_LIBRARY_DIR) - file(GLOB OAK_OTIO_DYLIBS - "${OTIO_LIBRARY_DIR}/libopentimelineio*.dylib" - "${OTIO_LIBRARY_DIR}/libopentime*.dylib") +if (OAK_BUNDLE_OTIO AND (APPLE OR WIN32) AND OTIO_LIBRARY_DIR) + if (APPLE) + file(GLOB OAK_OTIO_DYLIBS + "${OTIO_LIBRARY_DIR}/libopentimelineio*.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) add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different @@ -353,8 +361,10 @@ add_subdirectory(third_party/openfx/HostSupport) add_subdirectory(engine) add_subdirectory(cli) -# Everything above the engine library links against it -list(APPEND OLIVE_LIBRARIES oakengine) +# Internal consumers (app, tests) link oakengine-obj directly instead of the +# 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(worker) diff --git a/app/common/debugapp.h b/app/common/debugapp.h index 3b036e501..09d66a68f 100644 --- a/app/common/debugapp.h +++ b/app/common/debugapp.h @@ -38,7 +38,7 @@ namespace olive { * Replaces engine's olive::debug_handler so oak-editor doesn't import * 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 if (type == QtWarningMsg && msg.contains("QXcbIntegration")) { diff --git a/app/common/htmlapp.h b/app/common/htmlapp.h index f62415a0d..a0b5b9c98 100644 --- a/app/common/htmlapp.h +++ b/app/common/htmlapp.h @@ -17,7 +17,7 @@ */ #ifndef OAK_HTMLAPP_H -#define OAK_HTML_H +#define OAK_HTMLAPP_H #include #include diff --git a/app/core.cpp b/app/core.cpp index 673ef1728..363b28e42 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -98,7 +98,7 @@ Core::Core(const OakEngineAppParams *params) if (params) { oakengine_app_create(params); } else { - static const OakEngineAppParams default_params = {0}; + static const OakEngineAppParams default_params = {}; oakengine_app_create(&default_params); } @@ -773,7 +773,7 @@ void Core::start_gui(bool full_screen) main_window_ = new MainWindow(); // 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 // (oakengine_app_show_status_message, oakengine_app_clear_status_message) // which the engine forwards through registered callbacks. The main window diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 20c280963..eadb5bbf2 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -61,7 +61,7 @@ namespace // pix_fmt string (e.g. "yuv420p") to its index in the codec's supported // 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()) { return 0; @@ -87,7 +87,7 @@ Rational export_viewer_length(const OakEngineNode *viewer) // 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 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 = {}; @@ -939,7 +939,6 @@ OakEngineEncodingParams *ExportDialog::generate_params() const const int vh = static_cast(video_tab_->height_slider()->get_value()); const Rational tb = get_selected_timebase(); 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 int interlace = video_tab_->interlaced_combobox()->get_interlace_mode(); diff --git a/app/dialog/markerproperties/markerpropertiesdialog.cpp b/app/dialog/markerproperties/markerpropertiesdialog.cpp index 42f42b5fe..aa3954db0 100644 --- a/app/dialog/markerproperties/markerpropertiesdialog.cpp +++ b/app/dialog/markerproperties/markerpropertiesdialog.cpp @@ -145,7 +145,7 @@ void MarkerPropertiesDialog::accept() // Batch-set properties via facade (one undoable command) { QVector oak_markers; - foreach (OakEngineMarker *m, markers_) { + for (OakEngineMarker *m : markers_) { oak_markers.append(m); } int color = color_menu_->get_selected_color(); diff --git a/app/main.cpp b/app/main.cpp index 1d8e10ee3..fb8189f82 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -228,7 +228,7 @@ int main(int argc, char *argv[]) "main", "Decompress project file (No GUI)")); #ifdef _WIN32 - auto console_option = parser.AddOption( + auto console_option = parser.add_option( { QStringLiteral("c"), QStringLiteral("-console") }, QCoreApplication::translate("main", "Launch with debug console")); #endif // _WIN32 @@ -333,15 +333,19 @@ int main(int argc, char *argv[]) // so far. // // https://bugreports.qt.io/browse/QTBUG-46140 - QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL); + // 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); + } format.setVersion(3, 2); format.setProfile(QSurfaceFormat::CoreProfile); format.setDepthBufferSize(24); QSurfaceFormat::setDefaultFormat(format); - // Enable application automatically using higher resolution images from icons - QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); + // High-DPI pixmaps are always enabled in Qt 6 (AA_UseHighDpiPixmaps is + // deprecated and has no effect). QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); @@ -352,7 +356,7 @@ int main(int argc, char *argv[]) #ifdef _WIN32 // 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. - if (!console_option->IsSet()) { + if (!console_option->is_set()) { FreeConsole(); } #endif // _WIN32 diff --git a/app/panel/panel.cpp b/app/panel/panel.cpp index 2778b38c2..8ac925222 100644 --- a/app/panel/panel.cpp +++ b/app/panel/panel.cpp @@ -49,8 +49,7 @@ PanelWidget::PanelWidget(const QString &object_name) View::setFocusPolicy(Qt::ClickFocus); - connect(this, &PanelWidget::shown, this, - reinterpret_cast(&PanelWidget::setFocus)); + connect(this, &PanelWidget::shown, this, [this]() { QWidget::setFocus(); }); PanelManager::instance()->register_panel(this); } diff --git a/app/widget/curvewidget/curveview.cpp b/app/widget/curvewidget/curveview.cpp index ab3be0903..528c386ea 100644 --- a/app/widget/curvewidget/curveview.cpp +++ b/app/widget/curvewidget/curveview.cpp @@ -334,7 +334,7 @@ void CurveView::ContextMenuEvent(Menu &m) void CurveView::SceneRectUpdateEvent(QRectF &r) { - double min_val, max_val; + double min_val = 0, max_val = 0; bool got_val = false; foreach (KeyframeViewInputConnection *con, track_connections_) { @@ -694,7 +694,7 @@ void CurveView::zoom_to_fit_internal(bool selected_only) bool got_val = false; Rational min_time, max_time; - double min_val, max_val; + double min_val = 0, max_val = 0; foreach (KeyframeViewInputConnection *con, track_connections_) { foreach (const oak::Keyframe &key, con->get_keyframes()) { diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index 3edeae813..ad8033896 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -355,7 +355,7 @@ void CurveWidget::keyframe_type_button_triggered(bool checked) QVector tracks; }; QVector groups; - foreach (OakEngineKeyframe *item, selected) { + for (OakEngineKeyframe *item : selected) { const oak::Keyframe key(item); OakEngineNode *node = key.node().handle(); int g = 0; diff --git a/app/widget/handmovableview/handmovableview.cpp b/app/widget/handmovableview/handmovableview.cpp index 70ec3518f..cf38b0795 100644 --- a/app/widget/handmovableview/handmovableview.cpp +++ b/app/widget/handmovableview/handmovableview.cpp @@ -64,8 +64,10 @@ bool HandMovableView::hand_press(QMouseEvent *event) setInteractive(false); // Transform mouse event to act like the left button is pressed - QMouseEvent transformed(event->type(), event->pos(), Qt::LeftButton, - Qt::LeftButton, event->modifiers()); + QMouseEvent transformed(event->type(), event->position(), + event->globalPosition(), Qt::LeftButton, + Qt::LeftButton, event->modifiers(), + event->pointingDevice()); 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 QPoint adjustment(0, 0); - QMouseEvent transformed(event->type(), event->pos() - transformed_pos_, - Qt::LeftButton, Qt::LeftButton, - event->modifiers()); + QMouseEvent transformed(event->type(), + event->position() - transformed_pos_, + 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()); adjustment.setX(width()); - } else if (event->pos().x() >= width()) { + } else if (event->position().toPoint().x() >= width()) { transformed_pos_.setX(transformed_pos_.x() - width()); adjustment.setX(-width()); } - if (event->pos().y() < 0) { + if (event->position().toPoint().y() < 0) { transformed_pos_.setY(transformed_pos_.y() + height()); adjustment.setY(height()); - } else if (event->pos().y() >= height()) { + } else if (event->position().toPoint().y() >= height()) { transformed_pos_.setY(transformed_pos_.y() - height()); adjustment.setY(-height()); } @@ -116,8 +120,8 @@ bool HandMovableView::hand_release(QMouseEvent *event) { if (dragging_hand_) { // Transform mouse event to act like the left button is pressed - QMouseEvent transformed(event->type(), event->localPos(), - event->windowPos(), event->screenPos(), + QMouseEvent transformed(event->type(), event->position(), + event->scenePosition(), event->globalPosition(), Qt::LeftButton, Qt::LeftButton, event->modifiers(), event->source()); @@ -194,7 +198,7 @@ void HandMovableView::wheelEvent(QWheelEvent *event) #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) event->position(), event->globalPosition(), #else - event->pos(), event->globalPos(), + event->position().toPoint(), event->globalPos(), #endif event->pixelDelta(), angle_delta, event->buttons(), event->modifiers(), event->phase(), event->inverted(), @@ -209,7 +213,7 @@ void HandMovableView::wheelEvent(QWheelEvent *event) 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->buttons(), event->modifiers()); #endif diff --git a/app/widget/keyframeview/keyframeview.cpp b/app/widget/keyframeview/keyframeview.cpp index 156745cbb..1c86070e8 100644 --- a/app/widget/keyframeview/keyframeview.cpp +++ b/app/widget/keyframeview/keyframeview.cpp @@ -83,7 +83,7 @@ void KeyframeView::delete_selected() get_selected_keyframes(); QVector keys; keys.reserve(int(selected.size())); - foreach (OakEngineKeyframe *key, selected) { + for (OakEngineKeyframe *key : selected) { keys.append(key); } oakengine_keyframes_remove_many( @@ -289,9 +289,9 @@ bool KeyframeView::paste( oakengine_clipboard_foreach_keyframe( cb, [](const char *, OakEngineKeyframe *kf, void *userdata) -> int { - auto *ctx = static_cast(userdata); - ctx->min = std::min(ctx->min, key_time(kf)); - ctx->total++; + auto *paste_ctx = static_cast(userdata); + paste_ctx->min = std::min(paste_ctx->min, key_time(kf)); + paste_ctx->total++; return 0; }, &ctx); @@ -303,17 +303,17 @@ bool KeyframeView::paste( cb, [](const char *node_id, OakEngineKeyframe *kf, void *userdata) -> int { - auto *ctx = static_cast(userdata); + auto *paste_ctx = static_cast(userdata); auto &find_fn = *static_cast *>( - ctx->find_fn); + paste_ctx->find_fn); oak::Node node_with_id = find_fn(QString::fromUtf8(node_id)); if (!node_with_id.is_null()) { - Rational t = key_time(kf) - ctx->min; - t = ctx->self->get_adjusted_time( - ctx->self->get_time_target(), + Rational t = key_time(kf) - paste_ctx->min; + t = paste_ctx->self->get_adjusted_time( + paste_ctx->self->get_time_target(), node_with_id.handle(), t, k_transform_towards_input); key_set_time_live(kf, t); @@ -333,7 +333,7 @@ bool KeyframeView::paste( void *rm = oakengine_node_remove_keyframe_command( existing); oakengine_undo_command_multi_add_child( - ctx->command, rm); + paste_ctx->command, rm); } oak_node_value v; @@ -357,7 +357,7 @@ bool KeyframeView::paste( static_cast(cp_in.y()), static_cast(cp_out.x()), static_cast(cp_out.y())); - oakengine_undo_command_multi_add_child(ctx->command, cmd); + oakengine_undo_command_multi_add_child(paste_ctx->command, cmd); } else { oakengine_keyframe_dispose(kf); } @@ -760,7 +760,7 @@ void KeyframeView::show_context_menu() QVector tracks; }; QVector groups; - foreach (OakEngineKeyframe *item, get_selected_keyframes()) { + for (OakEngineKeyframe *item : get_selected_keyframes()) { const oak::Keyframe key(item); OakEngineNode *node = key.node().handle(); int g = 0; @@ -797,7 +797,7 @@ void KeyframeView::show_keyframe_properties_dialog() if (!get_selected_keyframes().empty()) { QVector keys; keys.reserve(int(get_selected_keyframes().size())); - foreach (OakEngineKeyframe *key, get_selected_keyframes()) { + for (OakEngineKeyframe *key : get_selected_keyframes()) { keys.append(oak::Keyframe(key)); } KeyframePropertiesDialog kd(keys, timebase(), this); diff --git a/app/widget/manageddisplay/manageddisplay.cpp b/app/widget/manageddisplay/manageddisplay.cpp index 228fa9218..52f336348 100644 --- a/app/widget/manageddisplay/manageddisplay.cpp +++ b/app/widget/manageddisplay/manageddisplay.cpp @@ -50,8 +50,8 @@ ManagedDisplayWidget::ManagedDisplayWidget(QWidget *parent) #ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND { const QString backend = oak_query_string([](char *buf, int sz) { - int backend = oakengine_render_manager_requested_backend(); - return oakengine_render_manager_backend_to_string(backend, buf, + int backend_id = oakengine_render_manager_requested_backend(); + return oakengine_render_manager_backend_to_string(backend_id, buf, sz); }); attached_renderer_ = diff --git a/app/widget/menu/menushared.cpp b/app/widget/menu/menushared.cpp index 691c0e367..5e3ac47ab 100644 --- a/app/widget/menu/menushared.cpp +++ b/app/widget/menu/menushared.cpp @@ -19,6 +19,7 @@ ***/ +#include #include "menushared.h" #include @@ -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) { - for (QAction *a : qAsConst(addable_items_)) { + for (QAction *a : std::as_const(addable_items_)) { a->setChecked((a->data().toInt() == Core::instance()->get_selected_addable_object())); m->addAction(a); @@ -425,7 +426,7 @@ void MenuShared::retranslate() edit_split_item_->setText(tr("Split")); 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( static_cast(a->data().toInt()))); } diff --git a/app/widget/nodeparamview/nodeparamviewarraywidget.cpp b/app/widget/nodeparamview/nodeparamviewarraywidget.cpp index 27dcd62d3..69b986c11 100644 --- a/app/widget/nodeparamview/nodeparamviewarraywidget.cpp +++ b/app/widget/nodeparamview/nodeparamviewarraywidget.cpp @@ -43,9 +43,9 @@ NodeParamViewArrayWidget::NodeParamViewArrayWidget(oak::Node node, bridge_->subscribe(reinterpret_cast(node_.handle()), OAKENGINE_EVENT_NODE_INPUT_ARRAY_SIZE_CHANGED); 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) { - 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()); @@ -58,11 +58,11 @@ void NodeParamViewArrayWidget::mouseDoubleClickEvent(QMouseEvent *event) 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) { Q_UNUSED(old_size) - if (input == input_) { + if (changed_input == input_) { count_lbl_->setText(tr("%n element(s)", nullptr, new_size)); } } diff --git a/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp b/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp index d3719b37e..017444c86 100644 --- a/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp +++ b/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp @@ -93,15 +93,15 @@ NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const oak::Input &input OAKENGINE_EVENT_NODE_INPUT_DISCONNECTED); connect(bridge_, &EngineEventBridge::node_input_connected, this, [this](OakEngineNode *source, OakEngineNode *output, - const QString &input, int element) { + const QString &input_id, int element) { input_connected(output, - oak::Input(source, input, element)); + oak::Input(source, input_id, element)); }); connect(bridge_, &EngineEventBridge::node_input_disconnected, this, [this](OakEngineNode *source, OakEngineNode *output, - const QString &input, int element) { + const QString &input_id, int element) { 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 diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index 15bf47f81..8717ef636 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -161,8 +161,8 @@ NodeParamViewWidgetBridge::NodeParamViewWidgetBridge(const oak::Input &input, connect(bridge_, &EngineEventBridge::node_input_value_changed, this, &NodeParamViewWidgetBridge::input_value_changed); connect(bridge_, &EngineEventBridge::node_input_property_changed, this, - [this](OakEngineNode *, const QString &input) { - property_changed(input); + [this](OakEngineNode *, const QString &input_id) { + property_changed(input_id); }); connect(bridge_, &EngineEventBridge::node_input_data_type_changed, this, &NodeParamViewWidgetBridge::input_data_type_changed); @@ -728,6 +728,7 @@ void NodeParamViewWidgetBridge::widget_callback() case NodeValueType::k_samples: case NodeValueType::k_video_params: case NodeValueType::k_audio_params: + case NodeValueType::k_push_button: case NodeValueType::k_subtitle_params: case NodeValueType::k_data_type_count: break; @@ -946,6 +947,7 @@ void NodeParamViewWidgetBridge::update_widget_values() case NodeValueType::k_samples: case NodeValueType::k_video_params: case NodeValueType::k_audio_params: + case NodeValueType::k_push_button: case NodeValueType::k_subtitle_params: case NodeValueType::k_data_type_count: break; diff --git a/app/widget/nodetableview/nodetableview.cpp b/app/widget/nodetableview/nodetableview.cpp index 3ec17189a..c2af88e70 100644 --- a/app/widget/nodetableview/nodetableview.cpp +++ b/app/widget/nodetableview/nodetableview.cpp @@ -158,7 +158,7 @@ void NodeTableView::set_time(const Rational &time) // Set data type name char name_buf[64]; 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(sizeof(name_buf))) { name_buf[len] = '\0'; } else { snprintf(name_buf, sizeof(name_buf), "Type %d", type); diff --git a/app/widget/nodevaluetree/nodevaluetree.cpp b/app/widget/nodevaluetree/nodevaluetree.cpp index c06343e51..8705c5683 100644 --- a/app/widget/nodevaluetree/nodevaluetree.cpp +++ b/app/widget/nodevaluetree/nodevaluetree.cpp @@ -106,7 +106,7 @@ void NodeValueTree::set_node(const oak::Input &input, const Rational &time) setItemWidget(item, 0, radio); char name_buf[64]; 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(sizeof(name_buf))) { name_buf[len] = '\0'; } else { snprintf(name_buf, sizeof(name_buf), "Type %d", type); diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 280548461..4342c6727 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -19,6 +19,7 @@ ***/ +#include #include "nodeview.h" #include @@ -75,8 +76,8 @@ NodeView::NodeView(QWidget *parent) , overlay_view_(nullptr) , scale_(1.0) , dont_emit_selection_signals_(false) - , show_in_param_editor_action_(nullptr) , bridge_(new EngineEventBridge(this)) + , show_in_param_editor_action_(nullptr) { setScene(&scene_); set_default_drag_mode(RubberBandDrag); @@ -499,7 +500,7 @@ void NodeView::set_color_label(int index) // WRAPPER-GAP: oakengine_undo_* command assembly (no wrapper) 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( command, oakengine_node_set_color_label_command(node.handle(), index)); @@ -528,8 +529,8 @@ void NodeView::keyPressEvent(QKeyEvent *event) case Qt::Key_Down: { // WRAPPER-GAP: oakengine_undo_* command assembly (no wrapper) void *pos_command = oakengine_undo_command_create_multi(); - for (const oak::Node &n : qAsConst(selected_nodes_)) { - for (const oak::Node &context : qAsConst(contexts_)) { + for (const oak::Node &n : std::as_const(selected_nodes_)) { + for (const oak::Node &context : std::as_const(contexts_)) { QPointF old_pos; bool old_expanded = false; if (context.context_position_of(n, &old_pos, &old_expanded)) { @@ -595,7 +596,7 @@ void NodeView::mousePressEvent(QMouseEvent *event) return; // 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) { // Sane defaults @@ -657,7 +658,7 @@ void NodeView::mousePressEvent(QMouseEvent *event) scene_.addItem(create_edge_); // Position edge to mouse cursor - position_new_edge(event->pos()); + position_new_edge(event->position().toPoint()); return; } } @@ -698,7 +699,7 @@ void NodeView::mouseMoveEvent(QMouseEvent *event) return; if (create_edge_) { - position_new_edge(event->pos()); + position_new_edge(event->position().toPoint()); return; } @@ -708,7 +709,7 @@ void NodeView::mouseMoveEvent(QMouseEvent *event) // See if there are any items attached if (!attached_items_.isEmpty()) { - process_moving_attached_nodes(event->pos()); + process_moving_attached_nodes(event->position().toPoint()); } } void NodeView::mouseReleaseEvent(QMouseEvent *event) @@ -729,11 +730,11 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) bool had_attached_items = !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()) { select_nodes = process_dropping_attached_nodes(command, select_context, - event->pos()); + event->position().toPoint()); } else { QToolTip::showText(QCursor::pos(), tr("Nodes must be placed inside a context.")); @@ -780,7 +781,7 @@ void NodeView::mouseDoubleClickEvent(QMouseEvent *event) if (!(event->modifiers() & Qt::ControlModifier)) { NodeViewItem *item_at_cursor = - dynamic_cast(itemAt(event->pos())); + dynamic_cast(itemAt(event->position().toPoint())); if (item_at_cursor) { item_at_cursor->toggle_expanded(); } @@ -841,9 +842,9 @@ void NodeView::dragMoveEvent(QDragMoveEvent *event) if (attached_items_.empty()) { event->ignore(); } 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(); } else { event->ignore(); @@ -853,12 +854,12 @@ void NodeView::dragMoveEvent(QDragMoveEvent *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()) { // WRAPPER-GAP: oakengine_undo_* command assembly (no wrapper) void *command = oakengine_undo_command_create_multi(); QVector select_nodes = - process_dropping_attached_nodes(command, drop_ctx, event->pos()); + process_dropping_attached_nodes(command, drop_ctx, event->position().toPoint()); oakengine_undo_push( 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); - for (const AttachedItem &i : qAsConst(attached_items_)) { + for (const AttachedItem &i : std::as_const(attached_items_)) { if (i.item) { 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; // See if there is an edge here - for (QGraphicsItem *item : qAsConst(items)) { + for (QGraphicsItem *item : std::as_const(items)) { new_drop_edge = dynamic_cast(item); if (new_drop_edge) { diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 9d6c3e3a6..0426b8c4a 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -118,8 +118,8 @@ NodeViewItem::NodeViewItem(oak::Node node, const QString &input, bridge_->subscribe(node_.handle(), OAKENGINE_EVENT_NODE_INPUT_ARRAY_SIZE_CHANGED); connect(bridge_, &EngineEventBridge::node_input_array_size_changed, this, - [this](OakEngineNode *, const QString &input, int, int) { - input_array_size_changed(input); + [this](OakEngineNode *, const QString &changed_input, int, int) { + input_array_size_changed(changed_input); }); } diff --git a/app/widget/standardcombos/channellayoutcombobox.h b/app/widget/standardcombos/channellayoutcombobox.h index 9e4efe2e0..3e1910bb8 100644 --- a/app/widget/standardcombos/channellayoutcombobox.h +++ b/app/widget/standardcombos/channellayoutcombobox.h @@ -37,8 +37,8 @@ public: ChannelLayoutComboBox(QWidget *parent = nullptr) : QComboBox(parent) { - foreach (const uint64_t &ch_layout, - AudioParams::k_supported_channel_layouts) { + for (const uint64_t &ch_layout : + AudioParams::k_supported_channel_layouts) { this->addItem(HumanStrings::channel_layout_to_string(ch_layout), QVariant::fromValue(ch_layout)); } diff --git a/app/widget/standardcombos/sampleformatcombobox.h b/app/widget/standardcombos/sampleformatcombobox.h index 6240ec09a..d7644b1dd 100644 --- a/app/widget/standardcombos/sampleformatcombobox.h +++ b/app/widget/standardcombos/sampleformatcombobox.h @@ -55,7 +55,7 @@ public: } clear(); - foreach (const SampleFormat &of, formats) { + for (const SampleFormat &of : formats) { add_format_item(of); } diff --git a/app/widget/standardcombos/sampleratecombobox.h b/app/widget/standardcombos/sampleratecombobox.h index 79c36776b..752e428cd 100644 --- a/app/widget/standardcombos/sampleratecombobox.h +++ b/app/widget/standardcombos/sampleratecombobox.h @@ -38,7 +38,7 @@ public: SampleRateComboBox(QWidget *parent = nullptr) : 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); } } diff --git a/app/widget/timebased/timebasedview.cpp b/app/widget/timebased/timebasedview.cpp index d5a196875..ba1ca3aa5 100644 --- a/app/widget/timebased/timebasedview.cpp +++ b/app/widget/timebased/timebasedview.cpp @@ -215,7 +215,7 @@ void TimeBasedView::drawForeground(QPainter *painter, const QRectF &rect) if (snapped_) { painter->setPen(palette().text().color()); - foreach (const Rational &r, snap_time_) { + for (const Rational &r : snap_time_) { double x = time_to_scene(r); painter->drawLine(x, rect.top(), x, rect.height()); diff --git a/app/widget/timebased/timebasedviewselectionmanager.h b/app/widget/timebased/timebasedviewselectionmanager.h index 2c0ef74b4..dcd0e6392 100644 --- a/app/widget/timebased/timebasedviewselectionmanager.h +++ b/app/widget/timebased/timebasedviewselectionmanager.h @@ -405,7 +405,7 @@ public: QRectF(rubberband_scene_start_, current).normalized(); selected_ = rubberband_preselected_; - foreach (const DrawnObject &kp, drawn_objects_) { + for (const DrawnObject &kp : drawn_objects_) { if (scene_rect.intersects(kp.second)) { select(kp.first); } diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index 97274b212..5486a69c6 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -19,6 +19,7 @@ ***/ +#include #include "timebasedwidget.h" #include @@ -48,8 +49,8 @@ TimeBasedWidget::TimeBasedWidget(bool ruler_text_visible, bool ruler_cache_status_visible, QWidget *parent) : TimelineScaledWidget(parent) - , viewer_node_(nullptr) , bridge_(new EngineEventBridge(this)) + , viewer_node_(nullptr) , auto_max_scrollbar_(false) , toggle_show_all_(false) , auto_set_timebase_(true) @@ -408,7 +409,7 @@ void TimeBasedWidget::connect_timeline_view(TimeBasedView *base) &QScrollBar::setValue); // Connect scrollbar to other scrollbars - for (TimeBasedView *other : qAsConst(timeline_views_)) { + for (TimeBasedView *other : std::as_const(timeline_views_)) { connect(other->horizontalScrollBar(), &QScrollBar::valueChanged, base->horizontalScrollBar(), &QScrollBar::setValue); connect(base->horizontalScrollBar(), &QScrollBar::valueChanged, @@ -1229,7 +1230,7 @@ bool TimeBasedWidget::snap_point(const std::vector &start_times, // Find all points at this movement std::vector snap_times; - foreach (const SnapData &d, potential_snaps) { + for (const SnapData &d : potential_snaps) { if (d.movement == *movement) { snap_times.push_back(d.time); } diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index d3bbfa357..f4cba9506 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -19,6 +19,7 @@ ***/ +#include #include "timelinewidget.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)), 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 (OakEngineNode *connected = clip_connected_node(clip)) { TimeRange adjusted = @@ -2751,7 +2752,7 @@ void TimelineWidget::multicam_enabled_triggered(bool e) { 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)) { OakEngineNode *viewer = oakengine_clip_get_connected_viewer(c); 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++) { 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 // block's track-owning sequence is the context that matters here // (blocks on a timeline always live in their sequence's context). diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index 2ac4f226d..a917128e4 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -575,7 +575,7 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command) oakengine_node_connect_command( footage_stream.footage, reinterpret_cast(transform), - QLatin1String(oakengine_transform_texture_input_id()).toUtf8().constData(), + oakengine_transform_texture_input_id(), -1)); oakengine_undo_command_multi_add_child( command, @@ -607,7 +607,7 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command) oakengine_node_connect_command( footage_stream.footage, reinterpret_cast(volume_node), - QLatin1String(oakengine_volume_samples_input_id()).toUtf8().constData(), + oakengine_volume_samples_input_id(), -1)); oakengine_undo_command_multi_add_child( command, diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index d641ab832..ff6af334a 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -887,7 +887,7 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event) oakengine_node_connect_command( reinterpret_cast(it.value()), reinterpret_cast(cp_in_transition), - QLatin1String(oakengine_transition_in_block_input_id()).toUtf8().constData(), + oakengine_transition_in_block_input_id(), -1)); } @@ -900,7 +900,7 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event) oakengine_node_connect_command( reinterpret_cast(it.value()), reinterpret_cast(cp_out_transition), - QLatin1String(oakengine_transition_out_block_input_id()).toUtf8().constData(), + oakengine_transition_out_block_input_id(), -1)); } } diff --git a/app/widget/timelinewidget/tool/transition.cpp b/app/widget/timelinewidget/tool/transition.cpp index f8bdae141..11ea32987 100644 --- a/app/widget/timelinewidget/tool/transition.cpp +++ b/app/widget/timelinewidget/tool/transition.cpp @@ -198,7 +198,7 @@ void TransitionTool::mouse_release(TimelineViewMouseEvent *event) oakengine_node_connect_command( reinterpret_cast(out_block), transition, - QLatin1String(oakengine_transition_out_block_input_id()).toUtf8().constData(), + oakengine_transition_out_block_input_id(), -1)); oakengine_undo_command_multi_add_child( @@ -206,7 +206,7 @@ void TransitionTool::mouse_release(TimelineViewMouseEvent *event) oakengine_node_connect_command( reinterpret_cast(in_block), transition, - QLatin1String(oakengine_transition_in_block_input_id()).toUtf8().constData(), + oakengine_transition_in_block_input_id(), -1)); oakengine_undo_command_multi_add_child(command, oakengine_node_set_position_command(reinterpret_cast(out_block), reinterpret_cast(transition), -1, -0.5, 0)); diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index cd2a55b50..edb5b8ef8 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -263,7 +263,7 @@ TimelineView::TimelineView(Qt::Alignment vertical_alignment, QWidget *parent) void TimelineView::mousePressEvent(QMouseEvent *event) { // 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(); it++) { if (it.value().contains(scene_pos)) { @@ -358,7 +358,7 @@ void TimelineView::mouseDoubleClickEvent(QMouseEvent *event) void TimelineView::dragEnterEvent(QDragEnterEvent *event) { 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.SetEvent(event); @@ -369,7 +369,7 @@ void TimelineView::dragEnterEvent(QDragEnterEvent *event) void TimelineView::dragMoveEvent(QDragMoveEvent *event) { 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.SetEvent(event); @@ -385,7 +385,7 @@ void TimelineView::dragLeaveEvent(QDragLeaveEvent *event) void TimelineView::dropEvent(QDropEvent *event) { 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.SetEvent(event); @@ -446,7 +446,7 @@ void TimelineView::drawForeground(QPainter *painter, const QRectF &rect) static_cast(connected_track_type_)) { 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()), get_track_y(track_index), time_to_scene(range.length()), @@ -594,7 +594,7 @@ TimelineCoordinate TimelineView::scene_to_coordinate(const QPointF &pt) TimelineViewMouseEvent TimelineView::CreateMouseEvent(QMouseEvent *event) { - return CreateMouseEvent(event->pos(), event->button(), event->modifiers()); + return CreateMouseEvent(event->position().toPoint(), event->button(), event->modifiers()); } TimelineViewMouseEvent diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index a85cc2dd5..4e33cfc42 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -499,8 +499,8 @@ void SeekableWidget::deselect_all_markers() void SeekableWidget::set_marker_color(int c) { QVector oak_markers; - foreach (OakEngineMarker *marker, - selection_manager_.get_selected_objects()) { + for (OakEngineMarker *marker : + selection_manager_.get_selected_objects()) { oak_markers.append(marker); } oakengine_marker_set_properties( diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 43d599ef3..373e1de27 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -19,6 +19,7 @@ ***/ +#include #include "viewer.h" #include @@ -172,11 +173,16 @@ const Rational k_video_playback_interval = Rational(1, 10); ViewerWidget::ViewerWidget(ViewerDisplayWidget *display, QWidget *parent) : super(false, true, parent) + , overlay_(nullptr) + , info_chip_(nullptr) + , safe_frame_btn_(nullptr) + , overlay_zoom_index_(5) , playback_speed_(0) , color_menu_enabled_(true) , time_changed_from_timer_(false) , prequeuing_video_(false) , prequeuing_audio_(0) + , audio_processor_(oakengine_audio_processor_create()) , record_armed_(false) , recording_(false) , first_requeue_watcher_(nullptr) @@ -185,11 +191,6 @@ ViewerWidget::ViewerWidget(ViewerDisplayWidget *display, QWidget *parent) , ignore_scrub_(0) , multicam_panel_(nullptr) , 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 QVBoxLayout *layout = new QVBoxLayout(this); @@ -958,13 +959,13 @@ void ViewerWidget::detect_multicam_node(const Rational &time) const Rational seq_tb = sequence_timebase(seq); // 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 // dynamic_cast) if (oakengine_node_is_multicam(n)) { multicam = n; // 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 // dynamic_cast) if (oakengine_node_is_clip( @@ -983,7 +984,7 @@ void ViewerWidget::detect_multicam_node(const Rational &time) // Next, prefer multicam from selected block 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) && oakengine_node_is_clip( reinterpret_cast(b))) { diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index fa62a100e..ece7b9f77 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -69,6 +69,7 @@ ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) , blank_shader_(nullptr) , signal_cursor_color_(false) , gizmos_(nullptr) + , gizmo_params_(empty_video_params()) , current_gizmo_(nullptr) , gizmo_drag_started_(false) , show_subtitles_(true) @@ -83,7 +84,6 @@ ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) , add_band_(false) , queue_starved_(false) , text_edit_(nullptr) - , gizmo_params_(empty_video_params()) { connect(Core::instance(), &Core::tool_changed, this, &ViewerDisplayWidget::tool_changed); @@ -1083,7 +1083,7 @@ void ViewerDisplayWidget::open_text_gizmo(void *text, QMouseEvent *event) // Start text cursor where the user clicked 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_->setTextCursor(text_edit_->cursorForPosition(click_pos)); } @@ -1099,7 +1099,7 @@ bool ViewerDisplayWidget::on_mouse_press(QMouseEvent *event) { if (is_hand_drag(event)) { // Handle hand drag - hand_last_drag_pos_ = event->pos(); + hand_last_drag_pos_ = event->position().toPoint(); hand_dragging_ = true; emit hand_drag_started(); inner_widget()->setCursor(Qt::ClosedHandCursor); @@ -1115,15 +1115,15 @@ bool ViewerDisplayWidget::on_mouse_press(QMouseEvent *event) Tool::k_addable_shape || Core::instance()->get_selected_addable_object() == Tool::k_addable_title)) { - add_band_start_ = event->pos(); + add_band_start_ = event->position().toPoint(); add_band_end_ = add_band_start_; add_band_ = true; } else if ((current_gizmo_ = try_gizmo_press( gizmo_last_draw_transform_inverted_.map( - event->pos())))) { + event->position().toPoint())))) { // Handle gizmo click - gizmo_start_drag_ = event->pos(); + gizmo_start_drag_ = event->position().toPoint(); gizmo_last_drag_ = gizmo_start_drag_; const TimeRange gizmo_time = generate_gizmo_time(); oakengine_gizmo_set_globals( @@ -1132,7 +1132,7 @@ bool ViewerDisplayWidget::on_mouse_press(QMouseEvent *event) } else { // Handle standard drag - emit drag_started(event->pos()); + emit drag_started(event->position().toPoint()); } return true; @@ -1146,10 +1146,10 @@ bool ViewerDisplayWidget::on_mouse_move(QMouseEvent *event) // Handle hand dragging if (hand_dragging_) { // Emit movement - emit hand_drag_moved(event->x() - hand_last_drag_pos_.x(), - event->y() - hand_last_drag_pos_.y()); + emit hand_drag_moved(event->position().toPoint().x() - hand_last_drag_pos_.x(), + event->position().toPoint().y() - hand_last_drag_pos_.y()); - hand_last_drag_pos_ = event->pos(); + hand_last_drag_pos_ = event->position().toPoint(); return true; @@ -1157,7 +1157,7 @@ bool ViewerDisplayWidget::on_mouse_move(QMouseEvent *event) return true; } else if (add_band_) { - add_band_end_ = event->pos(); + add_band_end_ = event->position().toPoint(); update(); return true; @@ -1184,11 +1184,11 @@ bool ViewerDisplayWidget::on_mouse_move(QMouseEvent *event) gizmo_drag_started_ = true; } - QPointF v = screen_to_scene_point(event->pos()); + QPointF v = screen_to_scene_point(event->position().toPoint()); switch (drag_behavior) { case 1: v -= screen_to_scene_point(gizmo_last_drag_); - gizmo_last_drag_ = event->pos(); + gizmo_last_drag_ = event->position().toPoint(); break; case 2: 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)) { return true; } 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 = gizmos_; // 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_) { QPointF pixel_pos = - generate_display_transform().inverted().map(e->pos()); + generate_display_transform().inverted().map(e->position().toPoint()); oak_video_params tp = {}; oakengine_display_texture_get_params(texture_, &tp); pixel_pos /= (tp.divider > 0 ? tp.divider : 1); @@ -1458,9 +1458,9 @@ template void ViewerDisplayWidget::forward_drag_event_to_text_edit( if constexpr (std::is_same_v) { text_edit_->dragLeaveEvent(e); } else { - T relay(adjust_pos_by_v_align(get_virtual_pos_for_text_edit(e->pos())).toPoint(), - e->possibleActions(), e->mimeData(), e->mouseButtons(), - e->keyboardModifiers()); + T relay(adjust_pos_by_v_align(get_virtual_pos_for_text_edit(e->position().toPoint())).toPoint(), + e->possibleActions(), e->mimeData(), e->buttons(), + e->modifiers()); if (e->type() == QEvent::DragEnter) { text_edit_->dragEnterEvent(static_cast(&relay)); @@ -1484,12 +1484,12 @@ bool ViewerDisplayWidget::forward_mouse_event_to_text_edit(QMouseEvent *event, } // 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 && event->buttons() == Qt::NoButton) { 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 && mapped.x() < text_edit_->width() && 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()) { // Allow clicking other gizmos so the user can resize while the text editor is active 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; } else { 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); - QMouseEvent derived(event->type(), local_pos, event->windowPos(), - event->screenPos(), event->button(), event->buttons(), + QMouseEvent derived(event->type(), local_pos, event->scenePosition(), + event->globalPosition(), event->button(), event->buttons(), event->modifiers(), event->source()); return forward_event_to_text_edit(&derived); } diff --git a/app/widget/viewer/viewertexteditor.cpp b/app/widget/viewer/viewertexteditor.cpp index b6586245f..6ba960fa7 100644 --- a/app/widget/viewer/viewertexteditor.cpp +++ b/app/widget/viewer/viewertexteditor.cpp @@ -185,7 +185,7 @@ void ViewerTextEditor::update_tool_bar(ViewerTextEditorToolBar *toolbar, } QString style = f.fontStyleName().toString(); - QStringList styles = QFontDatabase().styles(family); + QStringList styles = QFontDatabase::styles(family); if (!styles.isEmpty() && (style.isEmpty() || !styles.contains(style))) { // 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. @@ -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 // perfectly fine with just the style name - format->setFontWeight(QFontDatabase().weight(family, style)); - format->setFontItalic(QFontDatabase().italic(family, style)); + format->setFontWeight(QFontDatabase::weight(family, style)); + format->setFontItalic(QFontDatabase::italic(family, style)); format->setFontStyleName(style); } @@ -594,7 +594,7 @@ void ViewerTextEditorToolBar::update_font_style_list(const QString &family) style_combo_->blockSignals(true); style_combo_->clear(); - QStringList l = QFontDatabase().styles(family); + QStringList l = QFontDatabase::styles(family); foreach (const QString &style, l) { style_combo_->addItem(style); } diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 04a930e56..335a50b9e 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -164,15 +164,15 @@ MainWindow::~MainWindow() void MainWindow::load_layout(const SerializedLayoutInfo &info) { - foreach (OakEngineNode *folder, info.open_folders) { + for (OakEngineNode *folder : info.open_folders) { open_folder(folder, true); } - foreach (OakEngineNode *sequence, info.open_sequences) { + for (OakEngineNode *sequence : info.open_sequences) { 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); } @@ -470,15 +470,15 @@ void MainWindow::set_application_progress_status(ProgressStatus status) #if defined(Q_OS_WINDOWS) if (taskbar_interface_) { switch (status) { - case kProgressShow: + case k_progress_show: taskbar_interface_->SetProgressState( reinterpret_cast(this->winId()), TBPF_NORMAL); break; - case kProgressNone: + case k_progress_none: taskbar_interface_->SetProgressState( reinterpret_cast(this->winId()), TBPF_NOPROGRESS); break; - case kProgressError: + case k_progress_error: taskbar_interface_->SetProgressState( reinterpret_cast(this->winId()), TBPF_ERROR); break; diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt index 7ece20cf9..49186bba8 100644 --- a/cli/CMakeLists.txt +++ b/cli/CMakeLists.txt @@ -103,6 +103,7 @@ if (BUILD_TESTS) ) set_tests_properties(oak_cli_transcode PROPERTIES SKIP_RETURN_CODE 2 + TIMEOUT 300 ) 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 diff --git a/core/include/olive/core/util/timerange.h b/core/include/olive/core/util/timerange.h index 8d8297557..ebb91bfdb 100644 --- a/core/include/olive/core/util/timerange.h +++ b/core/include/olive/core/util/timerange.h @@ -380,7 +380,7 @@ public: 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; } } diff --git a/docs/plans/eliminate-event-bridge-issues.md b/docs/plans/eliminate-event-bridge-issues.md new file mode 100644 index 000000000..c5c2b23a4 --- /dev/null +++ b/docs/plans/eliminate-event-bridge-issues.md @@ -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.** diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 3987fb61a..ed751c92c 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -55,6 +55,13 @@ add_library(oakengine-obj OBJECT add_library(oakengine SHARED $) +if (WIN32) + # Windows DLLs cannot have undefined symbols; embed the version objects + # here too (each module keeps its own copy, unlike ELF where the final + # executable provides the definition). + target_sources(oakengine PRIVATE $) +endif () + if (COMMAND oak_copy_otio_runtime) oak_copy_otio_runtime(oakengine) endif() @@ -107,6 +114,9 @@ target_include_directories(oakengine target_link_libraries(oakengine PUBLIC ${OLIVE_LIBRARIES} OfxHost) # OAKENGINE_BUILD marks the library side of the C ABI export macros (dllexport) 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}) # Version script: only oakengine_* + render-backend plugin ABI are exported; @@ -158,12 +168,25 @@ if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND) endif () endforeach () - add_library(oakgl SHARED - render/opengl/openglbackend_c.cpp - render/opengl/openglrenderer.cpp - render/opengl/openglrenderer.h - ) - target_link_libraries(oakgl PRIVATE oakengine) + if (WIN32) + # Engine-internal C++ symbols are not exported from liboakengine.dll, + # so embed the object library like the engine tests do. oakengine-obj + # already contains openglrenderer.cpp and its moc output, so on + # Windows the plugin only needs the C entry point (duplicating the + # sources would cause duplicate definitions at link time). MinGW + # compares type_info by name, so cross-DLL RTTI still works. + add_library(oakgl SHARED render/opengl/openglbackend_c.cpp) + set_target_properties(oakgl PROPERTIES AUTOMOC OFF) + target_link_libraries(oakgl PRIVATE oakengine-obj + $) + else () + add_library(oakgl SHARED + render/opengl/openglbackend_c.cpp + render/opengl/openglrenderer.cpp + render/opengl/openglrenderer.h + ) + target_link_libraries(oakgl PRIVATE oakengine) + endif () if (COMMAND oak_copy_otio_runtime) oak_copy_otio_runtime(oakgl) endif() @@ -185,12 +208,27 @@ if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND) ) if (Vulkan_FOUND) - add_library(oakvulkan SHARED - render/vulkan/vulkanbackend_c.cpp - render/vulkan/vulkanrenderer.cpp - render/vulkan/vulkanrenderer.h - ) - target_link_libraries(oakvulkan PRIVATE oakengine) + if (WIN32) + # Engine-internal C++ symbols are not exported from + # liboakengine.dll; link the object library like the engine tests + # (plus version obj for k_app_version). Unlike openglrenderer.cpp, + # vulkanrenderer.cpp is NOT part of oakengine-obj, so the plugin + # must compile it itself. + add_library(oakvulkan SHARED + render/vulkan/vulkanbackend_c.cpp + render/vulkan/vulkanrenderer.cpp + render/vulkan/vulkanrenderer.h + ) + target_link_libraries(oakvulkan PRIVATE oakengine-obj + $) + else () + add_library(oakvulkan SHARED + render/vulkan/vulkanbackend_c.cpp + render/vulkan/vulkanrenderer.cpp + render/vulkan/vulkanrenderer.h + ) + target_link_libraries(oakvulkan PRIVATE oakengine) + endif () if (COMMAND oak_copy_otio_runtime) oak_copy_otio_runtime(oakvulkan) endif() @@ -289,6 +327,12 @@ if (BUILD_TESTS) if (UNIX AND NOT APPLE) target_link_options(${name} PRIVATE "LINKER:--export-dynamic") endif () + if (WIN32) + # The test embeds oakengine-obj; its OAKENGINE_API references must + # match the library side (dllexport), otherwise MinGW looks for + # __imp_* import stubs that only a real DLL import lib provides. + target_compile_definitions(${name} PRIVATE OAKENGINE_BUILD) + endif () # Freshly linked binaries can exceed the 5s default discovery # timeout on first run (dyld cold cache + Qt/OCIO init) if (WIN32) diff --git a/engine/codec/ffmpeg/ffmpegencoder.cpp b/engine/codec/ffmpeg/ffmpegencoder.cpp index 34d5e4146..f816d6453 100644 --- a/engine/codec/ffmpeg/ffmpegencoder.cpp +++ b/engine/codec/ffmpeg/ffmpegencoder.cpp @@ -324,6 +324,14 @@ bool FFmpegEncoder::open() 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 if (frame->format() != video_conversion_fmt_) { frame = frame->convert(video_conversion_fmt_); diff --git a/engine/common/html.cpp b/engine/common/html.cpp index 1a9b3ec33..40932f562 100644 --- a/engine/common/html.cpp +++ b/engine/common/html.cpp @@ -275,7 +275,7 @@ void Html::write_char_format(QString *style, const QTextCharFormat &fmt) } if (fmt.foreground().style() != Qt::NoBrush) { - const QColor &color = fmt.foreground().color(); + const QColor color = fmt.foreground().color(); QString cs; if (color.alpha() == 255) { diff --git a/engine/common/memorypool.h b/engine/common/memorypool.h index 0968152c6..1614893eb 100644 --- a/engine/common/memorypool.h +++ b/engine/common/memorypool.h @@ -225,7 +225,7 @@ public: ~Arena() { std::list copy = lent_elements_; - foreach (Element *e, copy) { + for (Element *e : copy) { e->release(); } @@ -348,7 +348,7 @@ public: QMutexLocker locker(&lock_); // Attempt to get an element from an arena - foreach (Arena *a, arenas_) { + for (Arena *a : arenas_) { ElementPtr e = a->Get(); if (e) { diff --git a/engine/common/oiioutils.cpp b/engine/common/oiioutils.cpp index e6cc033cf..a31b469e5 100644 --- a/engine/common/oiioutils.cpp +++ b/engine/common/oiioutils.cpp @@ -49,6 +49,10 @@ PixelFormat OIIOUtils::get_format_from_oiio_basetype(OIIO::TypeDesc::BASETYPE ty switch (type) { case OIIO::TypeDesc::UNKNOWN: case OIIO::TypeDesc::NONE: +#if OIIO_VERSION >= 20500 + case OIIO::TypeDesc::USTRINGHASH: +#endif + default: break; case OIIO::TypeDesc::INT8: diff --git a/engine/coreengine.cpp b/engine/coreengine.cpp index 716ed1165..66ab2bd0b 100644 --- a/engine/coreengine.cpp +++ b/engine/coreengine.cpp @@ -544,10 +544,11 @@ void EngineCore::save_autorecovery() { QFile realname_file(project_autorecovery_dir.filePath( QStringLiteral("realname.txt"))); - realname_file.open(QFile::WriteOnly); - realname_file.write( - open_project_->pretty_filename().toUtf8()); - realname_file.close(); + if (realname_file.open(QFile::WriteOnly)) { + realname_file.write( + open_project_->pretty_filename().toUtf8()); + realname_file.close(); + } } int64_t max_recoveries_per_file = diff --git a/engine/include/oakengine/export.h b/engine/include/oakengine/export.h index 608840ce6..bbca9c8f3 100644 --- a/engine/include/oakengine/export.h +++ b/engine/include/oakengine/export.h @@ -30,7 +30,11 @@ * is still built with default symbol visibility, so the legacy C++ symbols * 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 #define OAKENGINE_API __declspec(dllexport) #else diff --git a/engine/include/oakengine/serializer.h b/engine/include/oakengine/serializer.h index df8a71a9a..c85e78cc7 100644 --- a/engine/include/oakengine/serializer.h +++ b/engine/include/oakengine/serializer.h @@ -251,7 +251,6 @@ OAKENGINE_API int oakengine_clipboard_foreach_connection( } #include Q_DECLARE_OPAQUE_POINTER(OakEngineClipboard *) -Q_DECLARE_OPAQUE_POINTER(OakEngineMarker *) #endif #endif /* OAKENGINE_SERIALIZER_H */ diff --git a/engine/include/oakengine/timeline.h b/engine/include/oakengine/timeline.h index 046a47101..6c37b3e6e 100644 --- a/engine/include/oakengine/timeline.h +++ b/engine/include/oakengine/timeline.h @@ -1316,6 +1316,7 @@ oakengine_clip_get_connected_viewer(const OakEngineBlock *clip); Q_DECLARE_OPAQUE_POINTER(OakEngineBlock *) Q_DECLARE_OPAQUE_POINTER(OakEngineClip *) Q_DECLARE_OPAQUE_POINTER(OakEngineMarkerList *) +Q_DECLARE_OPAQUE_POINTER(OakEngineMarker *) Q_DECLARE_OPAQUE_POINTER(OakEngineWorkarea *) Q_DECLARE_OPAQUE_POINTER(OakEngineTrack *) Q_DECLARE_OPAQUE_POINTER(OakEngineTrackList *) diff --git a/engine/node/node.cpp b/engine/node/node.cpp index 800219105..112f22fed 100644 --- a/engine/node/node.cpp +++ b/engine/node/node.cpp @@ -21,7 +21,9 @@ #include "node.h" +#ifndef _WIN32 #include +#endif #include #include @@ -1939,6 +1941,7 @@ void Node::report_invalid_input(const char *attempted_action, const QString &id, << "Failed to" << attempted_action << "parameter" << id << "element" << element << "in node" << this->id() << "- input doesn't exist"; +#ifndef _WIN32 if (qEnvironmentVariableIsSet("OAK_DEBUG_INVALID_INPUT")) { void *frames[32]; const int n = backtrace(frames, 32); @@ -1950,6 +1953,7 @@ void Node::report_invalid_input(const char *attempted_action, const QString &id, free(symbols); } } +#endif } NodeInputImmediate *Node::create_immediate(const QString &input) @@ -2382,14 +2386,14 @@ TimeRange Node::transform_time_to(TimeRange time, Node *target, Node *from = this; Node *to = target; - if (dir == k_transform_towards_input) { + if (dir == k_towards_input) { std::swap(from, to); } std::list path = find_path(from, to, path_index); if (!path.empty()) { - if (dir == k_transform_towards_input) { + if (dir == k_towards_input) { for (auto it = path.crbegin(); it != path.crend(); it++) { const NodeInput &i = (*it); time = i.node()->input_time_adjustment(i.input(), i.element(), @@ -2610,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 // 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); } diff --git a/engine/node/node.h b/engine/node/node.h index 037ee99c2..584cf29b9 100644 --- a/engine/node/node.h +++ b/engine/node/node.h @@ -929,8 +929,8 @@ public: static QString get_category_name(const CategoryID &c); enum TransformTimeDirection { - k_transform_towards_input, - k_transform_towards_output + k_towards_input, + k_towards_output }; /** diff --git a/engine/node/plugins/plugin.cpp b/engine/node/plugins/plugin.cpp index 0d16f0f5a..f3944b315 100644 --- a/engine/node/plugins/plugin.cpp +++ b/engine/node/plugins/plugin.cpp @@ -232,8 +232,6 @@ QHash build_default_values( ofx_type == kOfxParamTypePushButton) { continue; } - const auto &props = param.second->getProperties(); - bool is_secret = props.getIntProperty(kOfxParamPropSecret) != 0; const QString input_id = QString::fromStdString(param.second->getName()); if (input_id.isEmpty()) { @@ -263,10 +261,10 @@ clip_label_for_name(const std::string &name, } if (desc) { - const std::string &label = + const std::string ¶m_label = desc->getProps().getStringProperty(kOfxPropLabel); - if (!label.empty()) { - return QString::fromStdString(label); + if (!param_label.empty()) { + 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); for (int i = 0; i < label_count; ++i) { - const std::string &label = + const std::string &choice_label = 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) { diff --git a/engine/node/plugins/plugin.h b/engine/node/plugins/plugin.h index 7933b5378..67ef7754b 100644 --- a/engine/node/plugins/plugin.h +++ b/engine/node/plugins/plugin.h @@ -62,7 +62,7 @@ public: */ virtual void process_samples(const NodeValueRow &values, 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 @@ -71,7 +71,7 @@ public: * * 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: QString sub_category_; diff --git a/engine/node/project/footage/footage.cpp b/engine/node/project/footage/footage.cpp index 66f64a226..c137bd8e6 100644 --- a/engine/node/project/footage/footage.cpp +++ b/engine/node/project/footage/footage.cpp @@ -377,8 +377,8 @@ void Footage::value(const NodeValueRow &value, const NodeGlobals &globals, this, QStringLiteral("length")); // Push each stream as a footage job - for (int i = 0; i < get_total_stream_count(); i++) { - Track::Reference ref = get_reference_from_real_index(i); + for (int si = 0; si < get_total_stream_count(); si++) { + Track::Reference ref = get_reference_from_real_index(si); FootageJob job(globals.time(), decoder_, filename(), ref.type(), get_length(), globals.loop_mode()); @@ -427,9 +427,10 @@ void Footage::value(const NodeValueRow &value, const NodeGlobals &globals, ProxyManager::k_proxy_ready && ProxyManager::proxy_filename_has_audio(proxy_path_)) { 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 = - get_reference_from_real_index(i); + get_reference_from_real_index(sj); + if (other.type() == Track::k_audio && get_audio_params(other.index()).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 // stream so missing media is clearly visible in the timeline instead // of a transparent/black hole. generate_frame() draws the slat. - for (int i = 0; i < get_total_stream_count(); i++) { - Track::Reference ref = get_reference_from_real_index(i); + for (int si = 0; si < get_total_stream_count(); si++) { + Track::Reference ref = get_reference_from_real_index(si); if (ref.type() != Track::k_video) { continue; } diff --git a/engine/node/project/serializer/serializedlayoutinfo.cpp b/engine/node/project/serializer/serializedlayoutinfo.cpp index 729010455..e3aef389b 100644 --- a/engine/node/project/serializer/serializedlayoutinfo.cpp +++ b/engine/node/project/serializer/serializedlayoutinfo.cpp @@ -28,7 +28,7 @@ void SerializedLayoutInfo::to_xml(QXmlStreamWriter *writer) const writer->writeStartElement(QStringLiteral("folders")); - foreach (Folder *folder, open_folders) { + for (Folder *folder : open_folders) { writer->writeTextElement( QStringLiteral("folder"), QString::number(reinterpret_cast(folder))); @@ -38,7 +38,7 @@ void SerializedLayoutInfo::to_xml(QXmlStreamWriter *writer) const writer->writeStartElement(QStringLiteral("timeline")); - foreach (Sequence *sequence, open_sequences) { + for (Sequence *sequence : open_sequences) { writer->writeTextElement( QStringLiteral("sequence"), QString::number(reinterpret_cast(sequence))); @@ -48,7 +48,7 @@ void SerializedLayoutInfo::to_xml(QXmlStreamWriter *writer) const writer->writeStartElement(QStringLiteral("viewers")); - foreach (ViewerOutput *viewer, open_viewers) { + for (ViewerOutput *viewer : open_viewers) { writer->writeTextElement( QStringLiteral("viewer"), QString::number(reinterpret_cast(viewer))); diff --git a/engine/node/time/timeformat/timeformat.cpp b/engine/node/time/timeformat/timeformat.cpp index 170fffb94..d0ddb4ff2 100644 --- a/engine/node/time/timeformat/timeformat.cpp +++ b/engine/node/time/timeformat/timeformat.cpp @@ -22,6 +22,7 @@ #include "timeformat.h" #include +#include namespace olive { @@ -75,7 +76,8 @@ void TimeFormatNode::value(const NodeValueRow &value, qint64 ms_since_epoch = value[k_time_input].to_double() * 1000; bool time_is_local = value[k_local_time_input].to_bool(); 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 output = dt.toString(format); table->push(NodeValue(NodeValue::k_text, output, this)); diff --git a/engine/node/traverser.cpp b/engine/node/traverser.cpp index c1b1b0692..9a1769095 100644 --- a/engine/node/traverser.cpp +++ b/engine/node/traverser.cpp @@ -502,9 +502,7 @@ void NodeTraverser::resolve_jobs(NodeValue &val) } val.set_value(tex); - } else if (plugin::PluginJob *plugin_job = - dynamic_cast( - base_job)) { + } else if (dynamic_cast(base_job)) { VideoParams tex_params = job_tex->params(); // Force internal working format (F32) for plugin processing, // matching FootageJob/GenerateJob behavior. diff --git a/engine/oakengine.ver b/engine/oakengine.ver index ca27bc4a7..e18e2b2e0 100644 --- a/engine/oakengine.ver +++ b/engine/oakengine.ver @@ -4,6 +4,8 @@ /* Render backend plugin ABI (oakgl / oakvulkan dlopen) */ _ZN5olive8Renderer*; _ZTVN5olive8RendererE; + _ZTIN5olive8RendererE; + _ZTSN5olive8RendererE; _ZN5olive11VideoParams19get_bytes_per_pixelE*; _ZN5olive11VideoParams21get_bytes_per_channelE*; _ZN5olive13FileFunctions19read_file_as_stringE*; diff --git a/engine/pluginSupport/image.cpp b/engine/pluginSupport/image.cpp index a6a2cd218..8d54c9352 100644 --- a/engine/pluginSupport/image.cpp +++ b/engine/pluginSupport/image.cpp @@ -37,8 +37,11 @@ static const char *pixel_depth_to_ofx(core::PixelFormat format) return kOfxBitDepthShort; case core::PixelFormat::f16: return kOfxBitDepthHalf; + default: + break; case core::PixelFormat::f32: return kOfxBitDepthFloat; + case core::PixelFormat::u10: case core::PixelFormat::invalid: case core::PixelFormat::count: break; diff --git a/engine/pluginSupport/olivehost.h b/engine/pluginSupport/olivehost.h index 9e976cbe1..aaddf4a51 100644 --- a/engine/pluginSupport/olivehost.h +++ b/engine/pluginSupport/olivehost.h @@ -79,18 +79,18 @@ public: makeDescriptor(const std::string &bundle_path, OFX::Host::ImageEffect::ImageEffectPlugin *plugin) override; /// vmessage - virtual OfxStatus vmessage(const char *type, const char *id, - const char *format, va_list args); + OfxStatus vmessage(const char *type, const char *id, + const char *format, va_list args) override; /// vmessage - virtual OfxStatus setPersistentMessage(const char *type, const char *id, - const char *format, va_list args); + OfxStatus setPersistentMessage(const char *type, const char *id, + const char *format, va_list args) override; /// vmessage - virtual OfxStatus clearPersistentMessage(); + OfxStatus clearPersistentMessage() override; #ifdef OFX_SUPPORTS_OPENGLRENDER /// @see OfxImageEffectOpenGLRenderSuiteV1.flushResources() - virtual OfxStatus flushOpenGLResources() const + virtual OfxStatus flushOpenGLResources() const override { return kOfxStatFailed; }; diff --git a/engine/pluginSupport/oliveplugininstance.h b/engine/pluginSupport/oliveplugininstance.h index 405395ab6..8768deb63 100644 --- a/engine/pluginSupport/oliveplugininstance.h +++ b/engine/pluginSupport/oliveplugininstance.h @@ -209,13 +209,13 @@ public: /// get the current time on the timeline. This is not necessarily the same /// time as being passed to an action (eg render) - virtual double timeLineGetTime(); + double timeLineGetTime() override; /// 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 - virtual void timeLineGetBounds(double &t1, double &t2); + void timeLineGetBounds(double &t1, double &t2) override; void setCustomInArgs(const std::string &action, OFX::Host::Property::Set &in_args) override; diff --git a/engine/pluginSupport/paraminstance.h b/engine/pluginSupport/paraminstance.h index 663e8c777..4a81301db 100644 --- a/engine/pluginSupport/paraminstance.h +++ b/engine/pluginSupport/paraminstance.h @@ -132,7 +132,7 @@ public: { node_ = new_node; } - OfxStatus get(int &a) + OfxStatus get(int &a) override { if (!node_) { std::lock_guard lock(no_node_mutex_); @@ -151,7 +151,7 @@ public: a = 0; return kOfxStatErrValue; } - OfxStatus get(OfxTime time, int &data) + OfxStatus get(OfxTime time, int &data) override { if (!node_) { std::lock_guard lock(no_node_mutex_); @@ -170,7 +170,7 @@ public: data = 0; return kOfxStatErrValue; } - OfxStatus set(int data) + OfxStatus set(int data) override { if (!node_) { std::lock_guard lock(no_node_mutex_); @@ -186,7 +186,7 @@ public: submit_undo_command(node_, command, param_change_label(descriptor_)); return kOfxStatOK; } - OfxStatus set(OfxTime time, int data) + OfxStatus set(OfxTime time, int data) override { if (!node_) { std::lock_guard lock(no_node_mutex_); @@ -233,7 +233,7 @@ public: { node_ = new_node; } - OfxStatus get(double &data) + OfxStatus get(double &data) override { if (!node_) { data = has_value_ ? value_ : 0.0; @@ -253,7 +253,7 @@ public: data = 0.0; return kOfxStatErrValue; } - OfxStatus get(OfxTime time, double &data) + OfxStatus get(OfxTime time, double &data) override { if (!node_) { data = has_value_ ? value_ : 0.0; @@ -273,7 +273,7 @@ public: data = 0.0; return kOfxStatErrValue; } - OfxStatus set(double data) + OfxStatus set(double data) override { if (!node_) { value_ = data; @@ -293,7 +293,7 @@ public: submit_undo_command(node_, command, param_change_label(descriptor_)); return kOfxStatOK; } - OfxStatus set(OfxTime time, double data) + OfxStatus set(OfxTime time, double data) override { if (!node_) { value_ = data; @@ -313,11 +313,11 @@ public: submit_undo_command(node_, command, param_change_label(descriptor_)); return kOfxStatOK; } - OfxStatus derive(OfxTime, double &) + OfxStatus derive(OfxTime, double &) override { return kOfxStatErrUnsupported; } - OfxStatus integrate(OfxTime, OfxTime, double &) + OfxStatus integrate(OfxTime, OfxTime, double &) override { return kOfxStatErrUnsupported; } @@ -352,7 +352,7 @@ public: { node_ = new_node; } - OfxStatus get(bool &data) + OfxStatus get(bool &data) override { if (!node_) { data = has_value_ ? value_ : false; @@ -367,7 +367,7 @@ public: data = default_value(); return kOfxStatOK; } - OfxStatus get(OfxTime time, bool &data) + OfxStatus get(OfxTime time, bool &data) override { if (!node_) { data = has_value_ ? value_ : false; @@ -392,7 +392,7 @@ public: data = default_value(); return kOfxStatOK; } - OfxStatus set(bool data) + OfxStatus set(bool data) override { if (!node_) { value_ = data; @@ -406,7 +406,7 @@ public: submit_undo_command(node_, command, param_change_label(descriptor_)); return kOfxStatOK; } - OfxStatus set(OfxTime time, bool data) + OfxStatus set(OfxTime time, bool data) override { if (!node_) { value_ = data; @@ -452,7 +452,7 @@ public: { node_ = new_node; } - OfxStatus get(int &data) + OfxStatus get(int &data) override { if (!node_) { data = has_value_ ? value_ : 0; @@ -467,7 +467,7 @@ public: data = 0; return kOfxStatErrValue; } - OfxStatus get(OfxTime time, int &data) + OfxStatus get(OfxTime time, int &data) override { if (!node_) { data = has_value_ ? value_ : 0; @@ -482,7 +482,7 @@ public: data = 0; return kOfxStatErrValue; } - OfxStatus set(int data) + OfxStatus set(int data) override { if (!node_) { value_ = data; @@ -496,7 +496,7 @@ public: submit_undo_command(node_, command, param_change_label(descriptor_)); return kOfxStatOK; } - OfxStatus set(OfxTime time, int data) + OfxStatus set(OfxTime time, int data) override { if (!node_) { value_ = data; @@ -534,7 +534,7 @@ public: { 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 (has_value_) { @@ -557,7 +557,7 @@ public: a = static_cast(c.alpha()); 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 (has_value_) { @@ -581,7 +581,7 @@ public: a = static_cast(c.alpha()); 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_) { value_[0] = r; @@ -599,7 +599,7 @@ public: submit_undo_command(node_, command, param_change_label(descriptor_)); 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_) { value_[0] = r; @@ -646,7 +646,7 @@ public: { node_ = new_node; } - OfxStatus get(double &r, double &g, double &b) + OfxStatus get(double &r, double &g, double &b) override { if (!node_) { if (has_value_) { @@ -667,7 +667,7 @@ public: b = static_cast(c.blue()); 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 (has_value_) { @@ -689,7 +689,7 @@ public: b = static_cast(c.blue()); return kOfxStatOK; } - OfxStatus set(double r, double g, double b) + OfxStatus set(double r, double g, double b) override { if (!node_) { value_[0] = r; @@ -706,7 +706,7 @@ public: submit_undo_command(node_, command, param_change_label(descriptor_)); 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_) { value_[0] = r; @@ -751,7 +751,7 @@ public: { node_ = new_node; } - OfxStatus get(double &x, double &y) + OfxStatus get(double &x, double &y) override { if (!node_) { if (has_value_) { @@ -774,7 +774,7 @@ public: } return kOfxStatOK; } - OfxStatus get(OfxTime time, double &x, double &y) + OfxStatus get(OfxTime time, double &x, double &y) override { if (!node_) { if (has_value_) { @@ -798,7 +798,7 @@ public: } return kOfxStatOK; } - OfxStatus set(double x, double y) + OfxStatus set(double x, double y) override { if (!node_) { value_[0] = x; @@ -820,7 +820,7 @@ public: submit_undo_command(node_, command, param_change_label(descriptor_)); return kOfxStatOK; } - OfxStatus set(OfxTime time, double x, double y) + OfxStatus set(OfxTime time, double x, double y) override { if (!node_) { value_[0] = x; @@ -869,7 +869,7 @@ public: { node_ = new_node; } - OfxStatus get(int &x, int &y) + OfxStatus get(int &x, int &y) override { if (!node_) { if (has_value_) { @@ -886,7 +886,7 @@ public: y = static_cast(vec.y()); return kOfxStatOK; } - OfxStatus get(OfxTime time, int &x, int &y) + OfxStatus get(OfxTime time, int &x, int &y) override { if (!node_) { if (has_value_) { @@ -904,7 +904,7 @@ public: y = static_cast(vec.y()); return kOfxStatOK; } - OfxStatus set(int x, int y) + OfxStatus set(int x, int y) override { if (!node_) { value_[0] = x; @@ -919,7 +919,7 @@ public: submit_undo_command(node_, command, param_change_label(descriptor_)); return kOfxStatOK; } - OfxStatus set(OfxTime time, int x, int y) + OfxStatus set(OfxTime time, int x, int y) override { if (!node_) { value_[0] = x; @@ -961,7 +961,7 @@ public: { node_ = new_node; } - OfxStatus get(double &x, double &y, double &z) + OfxStatus get(double &x, double &y, double &z) override { if (!node_) { if (has_value_) { @@ -987,7 +987,7 @@ public: } 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 (has_value_) { @@ -1014,7 +1014,7 @@ public: } return kOfxStatOK; } - OfxStatus set(double x, double y, double z) + OfxStatus set(double x, double y, double z) override { if (!node_) { value_[0] = x; @@ -1038,7 +1038,7 @@ public: submit_undo_command(node_, command, param_change_label(descriptor_)); 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_) { value_[0] = x; @@ -1091,7 +1091,7 @@ public: { node_ = new_node; } - OfxStatus get(int &x, int &y, int &z) + OfxStatus get(int &x, int &y, int &z) override { if (!node_) { if (has_value_) { @@ -1110,7 +1110,7 @@ public: z = static_cast(vec.z()); 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 (has_value_) { @@ -1130,7 +1130,7 @@ public: z = static_cast(vec.z()); return kOfxStatOK; } - OfxStatus set(int x, int y, int z) + OfxStatus set(int x, int y, int z) override { if (!node_) { value_[0] = x; @@ -1146,7 +1146,7 @@ public: submit_undo_command(node_, command, param_change_label(descriptor_)); 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_) { value_[0] = x; @@ -1198,7 +1198,7 @@ public: { node_ = new_node; } - OfxStatus get(std::string &data) + OfxStatus get(std::string &data) override { if (!node_) { data = has_value_ ? value_ : std::string(); @@ -1213,7 +1213,7 @@ public: data.clear(); return kOfxStatErrValue; } - OfxStatus get(OfxTime time, std::string &data) + OfxStatus get(OfxTime time, std::string &data) override { if (!node_) { data = has_value_ ? value_ : std::string(); @@ -1228,7 +1228,7 @@ public: data.clear(); return kOfxStatErrValue; } - OfxStatus set(const char *data) + OfxStatus set(const char *data) override { if (!node_) { value_ = data ? data : ""; @@ -1243,7 +1243,7 @@ public: submit_undo_command(node_, command, param_change_label(descriptor_)); return kOfxStatOK; } - OfxStatus set(OfxTime time, const char *data) + OfxStatus set(OfxTime time, const char *data) override { if (!node_) { value_ = data ? data : ""; @@ -1282,7 +1282,7 @@ public: { node_ = new_node; } - OfxStatus get(std::string &data) + OfxStatus get(std::string &data) override { if (!node_) { data = has_value_ ? value_ : std::string(); @@ -1301,7 +1301,7 @@ public: data.clear(); return kOfxStatErrValue; } - OfxStatus get(OfxTime time, std::string &data) + OfxStatus get(OfxTime time, std::string &data) override { if (!node_) { data = has_value_ ? value_ : std::string(); @@ -1320,7 +1320,7 @@ public: data.clear(); return kOfxStatErrValue; } - OfxStatus set(const char *data) + OfxStatus set(const char *data) override { if (!node_) { value_ = data ? data : ""; @@ -1335,7 +1335,7 @@ public: submit_undo_command(node_, command, param_change_label(descriptor_)); return kOfxStatOK; } - OfxStatus set(OfxTime time, const char *data) + OfxStatus set(OfxTime time, const char *data) override { if (!node_) { value_ = data ? data : ""; diff --git a/engine/render/audiowaveformcache.cpp b/engine/render/audiowaveformcache.cpp index 9117d2d0c..13a3b9852 100644 --- a/engine/render/audiowaveformcache.cpp +++ b/engine/render/audiowaveformcache.cpp @@ -37,7 +37,7 @@ void AudioWaveformCache::write_waveform(const TimeRange &range, const AudioVisualWaveform *waveform) { // Write each valid range to the segments - foreach (const TimeRange &r, valid_ranges) { + for (const TimeRange &r : valid_ranges) { if (waveform) { waveforms_->overwrite_sums(*waveform, r.in(), r.in() - range.in(), r.length()); diff --git a/engine/render/backend/dynamicrenderer.cpp b/engine/render/backend/dynamicrenderer.cpp index 91de5d87a..8c131740c 100644 --- a/engine/render/backend/dynamicrenderer.cpp +++ b/engine/render/backend/dynamicrenderer.cpp @@ -18,7 +18,11 @@ DynamicRenderer::DynamicRenderer(const QString &backend, QObject *parent) } // 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() { destroy(); @@ -27,9 +31,6 @@ DynamicRenderer::~DynamicRenderer() destroy_(handle_); handle_ = nullptr; } - if (library_.isLoaded()) { - library_.unload(); - } } // Builds the private backend library path for the current platform. diff --git a/engine/render/diskmanager.cpp b/engine/render/diskmanager.cpp index 1cc317ddc..9fd6d5dce 100644 --- a/engine/render/diskmanager.cpp +++ b/engine/render/diskmanager.cpp @@ -373,7 +373,7 @@ bool DiskCacheFolder::delete_least_recent() auto hash_to_delete = disk_data_.begin(); 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) { hash_to_delete = it; } diff --git a/engine/render/ipc/sharedmemoryregion.cpp b/engine/render/ipc/sharedmemoryregion.cpp index 38a481478..cef61cdb7 100644 --- a/engine/render/ipc/sharedmemoryregion.cpp +++ b/engine/render/ipc/sharedmemoryregion.cpp @@ -66,9 +66,9 @@ QString SharedMemoryRegion::make_key(qint64 owner_pid, int worker_index) #if defined(Q_OS_WIN) -bool SharedMemoryRegion::Open(const QString &key, size_t size, Mode mode) +bool SharedMemoryRegion::open(const QString &key, size_t size, Mode mode) { - Close(); + close(); key_ = key; size_ = size; @@ -78,7 +78,7 @@ bool SharedMemoryRegion::Open(const QString &key, size_t size, Mode mode) const QString mapping_name = QStringLiteral("Local\\") + key; const std::wstring wname = mapping_name.toStdWString(); - if (mode == kCreate) { + if (mode == k_create) { const DWORD size_high = static_cast((quint64(size) >> 32) & 0xFFFFFFFF); const DWORD size_low = static_cast(quint64(size) & 0xFFFFFFFF); @@ -114,13 +114,13 @@ bool SharedMemoryRegion::Open(const QString &key, size_t size, Mode mode) return false; } - if (mode == kCreate) { + if (mode == k_create) { memset(data_, 0, size); } return true; } -void SharedMemoryRegion::Close() +void SharedMemoryRegion::close() { if (data_) { UnmapViewOfFile(data_); diff --git a/engine/render/opengl/openglrenderer.cpp b/engine/render/opengl/openglrenderer.cpp index 106f22d27..209d179f3 100644 --- a/engine/render/opengl/openglrenderer.cpp +++ b/engine/render/opengl/openglrenderer.cpp @@ -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. functions_->glUniform1i(variable_location, value.to_int()); break; + default: + break; case NodeValue::k_float: // kFloat technically specifies a double but as above, OpenGL doesn't support those. functions_->glUniform1f(variable_location, value.to_double()); @@ -828,7 +830,7 @@ void OpenGLRenderer::blit(QVariant s, AcceleratedJob &a_job, vert_vbo.destroy(); vao.release(); 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 int major = context_ ? context_->format().majorVersion() : 0; - const int minor = context_ ? context_->format().minorVersion() : 0; const bool is_gles2 = is_gles && (major < 3); const QString gles_preamble = is_gles2 ? QStringLiteral("#version 100\n\n" diff --git a/engine/render/playbackcache.cpp b/engine/render/playbackcache.cpp index b31f1672f..a7cceb764 100644 --- a/engine/render/playbackcache.cpp +++ b/engine/render/playbackcache.cpp @@ -191,7 +191,7 @@ void PlaybackCache::draw(QPainter *p, const Rational &start, double scale, { p->fillRect(rect, Qt::red); - foreach (const TimeRange &range, get_validated_ranges()) { + for (const TimeRange &range : get_validated_ranges()) { int range_left = rect.left() + (range.in() - start).to_double() * scale; if (range_left >= rect.right()) { continue; @@ -288,11 +288,11 @@ TimeRangeList PlaybackCache::get_invalidated_ranges(TimeRange intersecting) cons invalidated.insert(intersecting); - foreach (const TimeRange &range, validated_) { + for (const TimeRange &range : validated_) { invalidated.remove(range); } - foreach (const TimeRange &range, passthroughs_) { + for (const TimeRange &range : passthroughs_) { invalidated.remove(range); } diff --git a/engine/render/plugin/pluginrenderer.cpp b/engine/render/plugin/pluginrenderer.cpp index bfcf75738..bc58213b3 100644 --- a/engine/render/plugin/pluginrenderer.cpp +++ b/engine/render/plugin/pluginrenderer.cpp @@ -69,9 +69,9 @@ static int get_ofx_av_pixel_format(const OFX::Host::ImageEffect::Image &image, int *bytes_per_pixel) { - const std::string &depth = + [[maybe_unused]] const std::string &depth = image.getStringProperty(kOfxImageEffectPropPixelDepth); - const std::string &components = + [[maybe_unused]] const std::string &components = image.getStringProperty(kOfxImageEffectPropComponents); olive::core::PixelFormat pixel_format = olive::core::PixelFormat::invalid; @@ -289,7 +289,7 @@ get_destination_av_pixel_format(const olive::VideoParams ¶ms); // 作用:读取 clip 偏好(像素深度与分量)并更新 VideoParams。 // Purpose: Apply clip preferences (depth/components) into VideoParams. -static bool +[[maybe_unused]] static bool apply_clip_preferences_to_params(const OFX::Host::ImageEffect::ClipInstance &clip, olive::VideoParams *params) { @@ -298,7 +298,7 @@ apply_clip_preferences_to_params(const OFX::Host::ImageEffect::ClipInstance &cli } olive::core::PixelFormat format = olive::core::PixelFormat::invalid; - const std::string &depth = clip.getPixelDepth(); + [[maybe_unused]] const std::string &depth = clip.getPixelDepth(); if (depth == kOfxBitDepthByte) { format = olive::core::PixelFormat::u8; } else if (depth == kOfxBitDepthShort) { @@ -310,7 +310,7 @@ apply_clip_preferences_to_params(const OFX::Host::ImageEffect::ClipInstance &cli } int channels = 0; - const std::string &components = clip.getComponents(); + [[maybe_unused]] const std::string &components = clip.getComponents(); if (components == kOfxImageComponentRGBA) { channels = 4; } else if (components == kOfxImageComponentRGB) { @@ -359,6 +359,8 @@ static const char *ofx_depth_from_pixel_format(olive::core::PixelFormat format) return kOfxBitDepthShort; case olive::core::PixelFormat::f16: return kOfxBitDepthHalf; + default: + break; case olive::core::PixelFormat::f32: return kOfxBitDepthFloat; case olive::core::PixelFormat::invalid: @@ -460,7 +462,7 @@ static bool params_convertible(const olive::VideoParams ¶ms) // 作用:在 clip 偏好无效时,选择一个插件支持的输出格式。 // Purpose: Pick a supported output format when clip preferences are invalid. -static void +[[maybe_unused]] static void choose_supported_output_params(const OFX::Host::ImageEffect::Instance &instance, const OFX::Host::ImageEffect::ClipInstance &clip, const olive::VideoParams &preferred, @@ -522,7 +524,7 @@ convert_texture_for_params(olive::TexturePtr src, // 作用:根据插件能力与偏好选择输入格式并执行转换。 // Purpose: Select a supported input format and convert texture for the clip. -static olive::TexturePtr +[[maybe_unused]] static olive::TexturePtr convert_texture_for_clip(const OFX::Host::ImageEffect::Instance &instance, const OFX::Host::ImageEffect::ClipInstance &clip, olive::TexturePtr src, @@ -670,7 +672,7 @@ convert_texture_for_clip(const OFX::Host::ImageEffect::Instance &instance, // 作用:从 OFX Image 复制数据到 AVFrame(按图像属性推导格式)。 // Purpose: Copy OFX Image data into an AVFrame with inferred format. -static olive::AVFramePtr +[[maybe_unused]] static olive::AVFramePtr create_avframe_from_ofx_image(OFX::Host::ImageEffect::Image &image) { void *data_ptr = image.getPointerProperty(kOfxImagePropData); @@ -1323,10 +1325,9 @@ static void log_image_props(const char *label, int rod[4] = { 0, 0, 0, 0 }; image->getIntPropertyN(kOfxImagePropBounds, bounds, 4); image->getIntPropertyN(kOfxImagePropRegionOfDefinition, rod, 4); - const int row_bytes = image->getIntProperty(kOfxImagePropRowBytes); - const std::string &depth = + [[maybe_unused]] const std::string &depth = image->getStringProperty(kOfxImageEffectPropPixelDepth); - const std::string &components = + [[maybe_unused]] const std::string &components = image->getStringProperty(kOfxImageEffectPropComponents); /*qWarning().noquote() << "OFX image props" << label @@ -1370,7 +1371,7 @@ static void schedule_error_dialog_and_undo(const QString &message) } } -static olive::AVFramePtr download_texture_to_frame(const olive::TexturePtr &tex) +[[maybe_unused]] static olive::AVFramePtr download_texture_to_frame(const olive::TexturePtr &tex) { if (!tex || tex->is_dummy() || !tex->renderer()) { return nullptr; @@ -1425,7 +1426,7 @@ select_best_plugin_input_format(const OFX::Host::ImageEffect::Descriptor &desc) bool supports_f16 = false; for (int i = 0; i < dim; ++i) { - const std::string &depth = + [[maybe_unused]] const std::string &depth = props.getStringProperty(kOfxImageEffectPropSupportedPixelDepths, i); if (depth == kOfxBitDepthFloat) { supports_f32 = true; @@ -1541,8 +1542,8 @@ void olive::plugin::PluginRenderer::render_plugin( if (!tex->is_dummy() && tex->renderer()) { return true; } - AVFramePtr frame = tex->frame(); - return frame && frame->data(0); + AVFramePtr av_frame = tex->frame(); + return av_frame && av_frame->data(0); }; std::map input_textures; std::map input_clips; @@ -1817,12 +1818,10 @@ void olive::plugin::PluginRenderer::render_plugin( } // Diagnostic: peek at first few pixels void *img_data = output_image->getPointerProperty(kOfxImagePropData); - bool img_black = true; if (img_data) { float *f = static_cast(img_data); for (int i = 0; i < 16; ++i) { if (f[i] != 0.0f) { - img_black = false; break; } } diff --git a/engine/render/renderjobtracker.cpp b/engine/render/renderjobtracker.cpp index c5b28465d..274db924a 100644 --- a/engine/render/renderjobtracker.cpp +++ b/engine/render/renderjobtracker.cpp @@ -36,7 +36,7 @@ void RenderJobTracker::insert(const TimeRange &range, JobTime job_time) void RenderJobTracker::insert(const TimeRangeList &ranges, JobTime job_time) { - foreach (const TimeRange &r, ranges) { + for (const TimeRange &r : ranges) { insert(r, job_time); } } diff --git a/engine/src/capi/events.cpp b/engine/src/capi/events.cpp index d802abb04..54696362f 100644 --- a/engine/src/capi/events.cpp +++ b/engine/src/capi/events.cpp @@ -189,7 +189,7 @@ bool connect_node_event(olive::Node *node, int32_t event_id, case OAKENGINE_EVENT_NODE_INPUT_DISCONNECTED: { const bool connected = event_id == OAKENGINE_EVENT_NODE_INPUT_CONNECTED; - auto deliver = [fn, userdata, node, connected, event_id]( + auto deliver = [fn, userdata, node, event_id]( Node *output, const NodeInput &input) { const QByteArray utf = input.input().toUtf8(); invoke(fn, userdata, event_id, node, input.element(), 0, output, 0, diff --git a/engine/src/capi/footage.cpp b/engine/src/capi/footage.cpp index 3263df101..3d61135c8 100644 --- a/engine/src/capi/footage.cpp +++ b/engine/src/capi/footage.cpp @@ -328,7 +328,11 @@ bool video_stream_at(const olive::Footage *f, int index, // Internal cross-family accessor (not part of the public C ABI): returns // the borrowed project node of an import handle, or nullptr for probe // handles and NULL. Used by the timeline editing primitives. +#if defined(_WIN32) +extern "C" void * +#else extern "C" __attribute__((visibility("hidden"))) void * +#endif oakengine_capi_footage_node(OakEngineFootage *h) { if (!h) { diff --git a/engine/src/capi/plugin.cpp b/engine/src/capi/plugin.cpp index 8882855ae..4ec60a54b 100644 --- a/engine/src/capi/plugin.cpp +++ b/engine/src/capi/plugin.cpp @@ -61,13 +61,13 @@ int oakengine_plugin_set_progress_reporter_factory( oakengine_plugin_reporter_create_fn create, oakengine_plugin_reporter_destroy_fn destroy, oakengine_plugin_reporter_is_cancelled_fn is_cancelled, - oakengine_plugin_reporter_set_progress_fn set_progress, + oakengine_plugin_reporter_set_progress_fn set_progress_fn, void *userdata) { g_reporter_create = create; g_reporter_destroy = destroy; g_reporter_is_cancelled = is_cancelled; - g_reporter_set_progress = set_progress; + g_reporter_set_progress = set_progress_fn; g_reporter_userdata = userdata; // Register factory with the engine. @@ -90,12 +90,12 @@ int oakengine_plugin_set_progress_reporter_factory( CAdapter(void *reporter, oakengine_plugin_reporter_destroy_fn destroy, oakengine_plugin_reporter_is_cancelled_fn is_cancelled, - oakengine_plugin_reporter_set_progress_fn set_progress, + oakengine_plugin_reporter_set_progress_fn set_progress_fn, void *userdata) : PluginProgressReporter() , reporter_(reporter) , destroy_(destroy) - , set_progress_(set_progress) + , set_progress_(set_progress_fn) , userdata_(userdata) {} ~CAdapter() override { diff --git a/engine/src/capi/timeline.cpp b/engine/src/capi/timeline.cpp index be06e7552..3d7ce055e 100644 --- a/engine/src/capi/timeline.cpp +++ b/engine/src/capi/timeline.cpp @@ -2390,7 +2390,9 @@ int oakengine_clip_set_media_in(OakEngineClip *self, int64_t media_in_ts, set_seq_error(QStringLiteral("invalid clip handle")); return OAKENGINE_E_INVALID; } + olive::ClipBlock *clip = reinterpret_cast(self); + const olive::Sequence *sequence = clip->track() ? clip->track()->sequence() : nullptr; if (!sequence) { @@ -2427,7 +2429,9 @@ int oakengine_clip_set_media_in_rational(OakEngineClip *self, int64_t num, set_seq_error(QStringLiteral("invalid rational denominator")); return OAKENGINE_E_INVALID; } + olive::ClipBlock *clip = reinterpret_cast(self); + const olive::Rational time(static_cast(num), static_cast(den)); if (undoable) { push_or_run(new olive::BlockSetMediaInCommand(clip, time), @@ -2444,7 +2448,7 @@ void oakengine_clip_request_invalidate(OakEngineClip *self, int64_t in_ts, if (!self) { return; } - olive::ClipBlock *clip = reinterpret_cast(self); + // Forward to the clip's cache invalidation. Q_UNUSED(in_ts) Q_UNUSED(out_ts) @@ -2489,7 +2493,9 @@ void oakengine_clip_request_invalidate_connected(OakEngineClip *self, if (!self) { return; } + olive::ClipBlock *clip = reinterpret_cast(self); + olive::TimeRange intersect; if (in_den != 0 && out_den != 0) { intersect = olive::TimeRange( diff --git a/engine/src/capi/worker.cpp b/engine/src/capi/worker.cpp index dc30a010b..3c5fe1afb 100644 --- a/engine/src/capi/worker.cpp +++ b/engine/src/capi/worker.cpp @@ -410,6 +410,8 @@ private: it.key()); } + log_error(QStringLiteral("LoadGraph: deserialized ok, %1 nodes") + .arg(node_by_token.size())); QJsonObject ack; ack["type"] = QStringLiteral("graph_loaded"); ack["nodes"] = node_by_token.size(); @@ -449,6 +451,9 @@ private: return true; } + log_error(QStringLiteral("render_frame: ticket %1 node %2") + .arg(message.ticket_id) + .arg(message.node_uuid)); olive::Node *node = find_node(message.node_uuid); if (!node) { *response = @@ -726,6 +731,17 @@ olive::Renderer *create_renderer(const char *backend, bool *valid) } if (!ctx || !ctx->isValid()) { renderer_valid = false; + } else { + // Windows CI runners without a GPU hand back the GDI software + // OpenGL 1.1 implementation here; log what we actually got so a + // later crash in 3.2 core calls is attributable. + log_error(QStringLiteral("OpenGL context version: %1.%2 (%3)") + .arg(ctx->format().majorVersion()) + .arg(ctx->format().minorVersion()) + .arg(ctx->format().profile() == + QSurfaceFormat::CoreProfile ? + "core" : + "compatibility/none")); } } if (!renderer_valid) { @@ -849,7 +865,11 @@ int oakengine_worker_session_shutdown_requested(const OakWorkerSession *self) int oakengine_worker_main(int argc, char **argv) { - QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL); + // QT_OPENGL (e.g. "software" with Mesa opengl32sw.dll on GPU-less CI) + // takes precedence over this default. + if (!qEnvironmentVariableIsSet("QT_OPENGL")) { + QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL); + } QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); install_surface_format(); diff --git a/engine/task/export/export.cpp b/engine/task/export/export.cpp index ffaa19532..fa8cb6f8e 100644 --- a/engine/task/export/export.cpp +++ b/engine/task/export/export.cpp @@ -211,6 +211,21 @@ bool ExportTask::run() bool ExportTask::frame_downloaded(FramePtr f, const Rational &time) { + // The worker pool finishes tickets without a result when no worker is + // available (or every worker crashed). Grinding through the whole + // timeline at several seconds per dead worker looks like a hang, so + // fail the export after a short streak of missing frames. + if (!f) { + if (++null_frame_streak_ >= 8) { + set_error(tr("Render workers failed to deliver %1 consecutive " + "frames; aborting export") + .arg(null_frame_streak_)); + return false; + } + } else { + null_frame_streak_ = 0; + } + Rational actual_time = time - export_range_.in(); time_map_.insert(actual_time, f); diff --git a/engine/task/export/export.h b/engine/task/export/export.h index 20111b98d..5522a21d8 100644 --- a/engine/task/export/export.h +++ b/engine/task/export/export.h @@ -74,6 +74,8 @@ private: int64_t frame_time_; + int null_frame_streak_ = 0; + Rational audio_time_; TimeRange export_range_; diff --git a/engine/task/project/loadotio/loadotio.cpp b/engine/task/project/loadotio/loadotio.cpp index e407c1d56..4ea804d8d 100644 --- a/engine/task/project/loadotio/loadotio.cpp +++ b/engine/task/project/loadotio/loadotio.cpp @@ -109,7 +109,7 @@ bool LoadOTIOTask::run() // Generate a list of sequences with the same names as the timelines. // Assumes each timeline has a unique name. int unnamed_sequence_count = 0; - foreach (auto timeline, timelines) { + for (auto timeline : timelines) { Sequence *sequence = new Sequence(); if (!timeline->name().empty()) { sequence->set_label(QString::fromStdString(timeline->name())); @@ -124,7 +124,7 @@ bool LoadOTIOTask::run() timeline_sequnce_map.insert(timeline, sequence); // Get number of clips for loading bar - foreach (auto track, timeline->tracks()->children()) { + for (auto track : timeline->tracks()->children()) { auto otio_track = static_cast(track.value); number_of_clips += otio_track->children().size(); } diff --git a/engine/task/project/saveotio/saveotio.cpp b/engine/task/project/saveotio/saveotio.cpp index 75062ecee..0eac31175 100644 --- a/engine/task/project/saveotio/saveotio.cpp +++ b/engine/task/project/saveotio/saveotio.cpp @@ -64,7 +64,7 @@ bool SaveOTIOTask::run() serialized.push_back(otio_timeline); } else { // Delete all existing timelines - foreach (auto s, serialized) { + for (auto s : serialized) { s->possibly_delete(); } @@ -91,7 +91,7 @@ bool SaveOTIOTask::run() collection->possibly_delete(); // Delete all existing timelines - foreach (auto s, serialized) { + for (auto s : serialized) { s->possibly_delete(); } } diff --git a/engine/task/render/render.cpp b/engine/task/render/render.cpp index c9944210a..4028f5fe4 100644 --- a/engine/task/render/render.cpp +++ b/engine/task/render/render.cpp @@ -59,7 +59,7 @@ bool RenderTask::render(ColorManager *manager, const TimeRangeList &video_range, // Store real time before any rendering takes place // Queue audio jobs - foreach (const TimeRange &range, audio_range) { + for (const TimeRange &range : audio_range) { // Don't count audio progress, since it's generally a lot faster than video and is weighted at // 50%, which makes the progress bar look weird to the uninitiated //total_length += r.length().toDouble(); diff --git a/engine/tests/oakengine_disk_test.cpp b/engine/tests/oakengine_disk_test.cpp index e345124f5..2453992dd 100644 --- a/engine/tests/oakengine_disk_test.cpp +++ b/engine/tests/oakengine_disk_test.cpp @@ -32,6 +32,7 @@ #if defined(_WIN32) #include #include +#include #else #include #include @@ -113,20 +114,18 @@ static void test_open_folder_handle(void) EXPECT_TRUE(empty_folder == folder); // A different path opens a distinct folder. - char tmp[256]; - snprintf(tmp, sizeof(tmp), + char tmp[512]; #if defined(_WIN32) - "%s\\oakengine_disk_test_folder_XXXXXX", -#else - "%s/oakengine_disk_test_folder_XXXXXX", -#endif - getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp"); - -#if defined(_WIN32) - char *tmpdir = _mktemp(tmp); - EXPECT_TRUE(tmpdir != NULL); + char base[MAX_PATH]; + const DWORD len = GetTempPathA(MAX_PATH, base); + EXPECT_TRUE(len > 0 && len < MAX_PATH); + snprintf(tmp, sizeof(tmp), "%soakengine_disk_test_folder_%lu", base, + (unsigned long)GetCurrentProcessId()); + char *tmpdir = tmp; EXPECT_TRUE(_mkdir(tmpdir) == 0); #else + snprintf(tmp, sizeof(tmp), "%s/oakengine_disk_test_folder_XXXXXX", + getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp"); char *tmpdir = mkdtemp(tmp); EXPECT_TRUE(tmpdir != NULL); #endif @@ -149,20 +148,18 @@ static void test_open_folder_handle(void) static void test_clear_cache(void) { // Create a temporary cache directory and seed it with a file. - char path[256]; - snprintf(path, sizeof(path), + char path[512]; #if defined(_WIN32) - "%s\\oakengine_disk_test_cache_XXXXXX", -#else - "%s/oakengine_disk_test_cache_XXXXXX", -#endif - getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp"); - -#if defined(_WIN32) - char *tmpdir = _mktemp(path); - EXPECT_TRUE(tmpdir != NULL); + char base[MAX_PATH]; + const DWORD len = GetTempPathA(MAX_PATH, base); + EXPECT_TRUE(len > 0 && len < MAX_PATH); + snprintf(path, sizeof(path), "%soakengine_disk_test_cache_%lu", base, + (unsigned long)GetCurrentProcessId()); + char *tmpdir = path; EXPECT_TRUE(_mkdir(tmpdir) == 0); #else + snprintf(path, sizeof(path), "%s/oakengine_disk_test_cache_XXXXXX", + getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp"); char *tmpdir = mkdtemp(path); EXPECT_TRUE(tmpdir != NULL); #endif @@ -244,20 +241,18 @@ static void test_set_default_cache_path(void) EXPECT_TRUE(oakengine_disk_get_default_cache_path(original, sizeof(original)) > 0); - char tmp[256]; - snprintf(tmp, sizeof(tmp), + char tmp[512]; #if defined(_WIN32) - "%s\\oakengine_disk_test_default_XXXXXX", -#else - "%s/oakengine_disk_test_default_XXXXXX", -#endif - getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp"); - -#if defined(_WIN32) - char *tmpdir = _mktemp(tmp); - EXPECT_TRUE(tmpdir != NULL); + char base[MAX_PATH]; + const DWORD len = GetTempPathA(MAX_PATH, base); + EXPECT_TRUE(len > 0 && len < MAX_PATH); + snprintf(tmp, sizeof(tmp), "%soakengine_disk_test_default_%lu", base, + (unsigned long)GetCurrentProcessId()); + char *tmpdir = tmp; EXPECT_TRUE(_mkdir(tmpdir) == 0); #else + snprintf(tmp, sizeof(tmp), "%s/oakengine_disk_test_default_XXXXXX", + getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp"); char *tmpdir = mkdtemp(tmp); EXPECT_TRUE(tmpdir != NULL); #endif diff --git a/engine/tests/oakengine_footage_test.cpp b/engine/tests/oakengine_footage_test.cpp index c16c10883..9aedaae7b 100644 --- a/engine/tests/oakengine_footage_test.cpp +++ b/engine/tests/oakengine_footage_test.cpp @@ -49,12 +49,27 @@ static char g_tmpdir[4096]; +// The engine stores project filenames with native separators (backslashes on +// Windows); normalize a returned path to forward slashes before comparing +// against the paths these tests construct. +static void to_forward_slashes(char *s) +{ + for (; *s; ++s) { + if (*s == '\\') { + *s = '/'; + } + } +} + static void make_tmpdir(void) { #if defined(_WIN32) char base[MAX_PATH]; const DWORD len = GetTempPathA(MAX_PATH, base); EXPECT_TRUE(len > 0 && len < MAX_PATH); + // Resolve 8.3 short names (e.g. RUNNER~1) so string comparisons + // against engine-canonicalized paths hold. + GetLongPathNameA(base, base, MAX_PATH); snprintf(g_tmpdir, sizeof(g_tmpdir), "%soakengine_footage_test_%lu", base, (unsigned long)GetCurrentProcessId()); EXPECT_TRUE(_mkdir(g_tmpdir) == 0); @@ -607,9 +622,11 @@ static void test_project_extras(void) // set_filename round-trips through the plain filename getter. char target[4096]; snprintf(target, sizeof(target), "%s/roundtrip.ove", g_tmpdir); + to_forward_slashes(target); EXPECT_TRUE(oakengine_project_set_filename(project, target) == OAKENGINE_OK); EXPECT_TRUE(oakengine_project_filename(project, buf, sizeof(buf)) > 0); - EXPECT_TRUE(strcmp(buf, target) == 0); + to_forward_slashes(buf); + EXPECT_STREQ(buf, target); EXPECT_TRUE(oakengine_project_set_filename(project, NULL) == OAKENGINE_E_INVALID); EXPECT_TRUE(oakengine_project_set_filename(NULL, target) == diff --git a/engine/tests/oakengine_init_test.cpp b/engine/tests/oakengine_init_test.cpp index 853da1cc2..c2fc8f240 100644 --- a/engine/tests/oakengine_init_test.cpp +++ b/engine/tests/oakengine_init_test.cpp @@ -49,12 +49,27 @@ static char g_tmpdir[4096]; +// The engine stores project filenames with native separators (backslashes on +// Windows); normalize a returned path to forward slashes before comparing +// against the paths these tests construct. +static void to_forward_slashes(char *s) +{ + for (; *s; ++s) { + if (*s == '\\') { + *s = '/'; + } + } +} + static void make_tmpdir(void) { #if defined(_WIN32) char base[MAX_PATH]; const DWORD len = GetTempPathA(MAX_PATH, base); EXPECT_TRUE(len > 0 && len < MAX_PATH); + // Resolve 8.3 short names (e.g. RUNNER~1) so string comparisons + // against engine-canonicalized paths hold. + GetLongPathNameA(base, base, MAX_PATH); snprintf(g_tmpdir, sizeof(g_tmpdir), "%soakengine_init_test_%lu", base, (unsigned long)GetCurrentProcessId()); EXPECT_TRUE(_mkdir(g_tmpdir) == 0); @@ -68,6 +83,10 @@ static void make_path(char *dst, size_t cap, const char *filename) { const int n = snprintf(dst, cap, "%s/%s", g_tmpdir, filename); EXPECT_TRUE(n > 0 && (size_t)n < cap); + // g_tmpdir comes from GetTempPathA on Windows (backslashes); the + // engine's forward-slash-normalized filename must compare against the + // same form. + to_forward_slashes(dst); } static int file_exists(const char *path) @@ -231,7 +250,8 @@ static void test_sequence_and_save_load(void) char filename[4096]; EXPECT_TRUE(oakengine_project_filename(p, filename, sizeof(filename)) == (int)strlen(path)); - EXPECT_TRUE(strcmp(filename, path) == 0); + to_forward_slashes(filename); + EXPECT_STREQ(filename, path); // Undo removes the sequence, redo brings the same object back. EXPECT_TRUE(oakengine_project_undo(p) == OAKENGINE_OK); @@ -258,7 +278,8 @@ static void test_sequence_and_save_load(void) EXPECT_TRUE(strcmp(name, "roundtrip") == 0); EXPECT_TRUE(oakengine_project_filename(q, filename, sizeof(filename)) == (int)strlen(path)); - EXPECT_TRUE(strcmp(filename, path) == 0); + to_forward_slashes(filename); + EXPECT_STREQ(filename, path); // The sequence survived the round trip, workarea included (the workarea // is serialized by ViewerOutput::save_custom()). diff --git a/engine/timeline/timelineundogeneral.cpp b/engine/timeline/timelineundogeneral.cpp index 98454b59b..a11f3c42f 100644 --- a/engine/timeline/timelineundogeneral.cpp +++ b/engine/timeline/timelineundogeneral.cpp @@ -19,6 +19,7 @@ ***/ +#include #include "timelineundogeneral.h" #include "node/block/clip/clip.h" @@ -322,7 +323,7 @@ void TrackListInsertGaps::prepare() QVector blocks_to_append_gap_to; QVector tracks_to_append_gap_to; - for (Track *track : qAsConst(working_tracks_)) { + for (Track *track : std::as_const(working_tracks_)) { for (Block *b : track->blocks()) { if (dynamic_cast(b) && b->in() <= point_ && b->out() >= point_) { diff --git a/engine/timeline/timelineundoripple.cpp b/engine/timeline/timelineundoripple.cpp index ad730b59a..dffde59a4 100644 --- a/engine/timeline/timelineundoripple.cpp +++ b/engine/timeline/timelineundoripple.cpp @@ -19,6 +19,7 @@ ***/ +#include #include "timelineundoripple.h" #include "timelineundocommon.h" @@ -391,7 +392,7 @@ void TimelineRippleDeleteGapsAtRegionsCommand::prepare() QHash> requested_gaps; // Convert regions to gaps - for (const QPair ®ion : qAsConst(regions_)) { + for (const QPair ®ion : std::as_const(regions_)) { Track *track = region.first; const TimeRange &range = region.second; diff --git a/shared/include/oakutil/oakvideo.h b/shared/include/oakutil/oakvideo.h index 49dda9729..1f9c4fb0a 100644 --- a/shared/include/oakutil/oakvideo.h +++ b/shared/include/oakutil/oakvideo.h @@ -171,7 +171,7 @@ public: ColorTransform(const QString &display, const QString &view, const QString &look) - : is_display_(true), output_(display), view_(view), look_(look) + : output_(display), is_display_(true), view_(view), look_(look) { } diff --git a/tests/gtest/audio_smoke_test.cpp b/tests/gtest/audio_smoke_test.cpp index c7e9def15..ded252b55 100644 --- a/tests/gtest/audio_smoke_test.cpp +++ b/tests/gtest/audio_smoke_test.cpp @@ -44,7 +44,7 @@ namespace test // Helper Functions // ============================================================================ -static AudioParams make_audio_params(int sample_rate, uint64_t channel_layout, +[[maybe_unused]] static AudioParams make_audio_params(int sample_rate, uint64_t channel_layout, SampleFormat format) { return AudioParams(sample_rate, channel_layout, format); @@ -951,7 +951,7 @@ TEST(AudioSmokeThread, ConcurrentWaveformAccess) std::atomic success_count{ 0 }; for (int t = 0; t < num_threads; ++t) { - threads.emplace_back([&waveform, &success_count, num_ops_per_thread]() { + threads.emplace_back([&waveform, &success_count]() { for (int i = 0; i < num_ops_per_thread; ++i) { // Read summary from different times auto summary = waveform.get_summary_from_time( diff --git a/tests/gtest/common_filefunctions_test.cpp b/tests/gtest/common_filefunctions_test.cpp index ebaff51ec..967ada99b 100644 --- a/tests/gtest/common_filefunctions_test.cpp +++ b/tests/gtest/common_filefunctions_test.cpp @@ -50,7 +50,7 @@ TEST(CommonFileFunctions, GetSafeTemporaryFilename) EXPECT_TRUE(first.contains(QStringLiteral(".tmp0."))); QFile f(first); - f.open(QIODevice::WriteOnly); + (void)f.open(QIODevice::WriteOnly); f.close(); QString second = olive::FileFunctions::get_safe_temporary_filename(base); @@ -80,19 +80,19 @@ TEST(CommonFileFunctions, RenameFileAllowOverwrite) QString to = dir.filePath(QStringLiteral("to.txt")); QFile f(from); - f.open(QIODevice::WriteOnly); + (void)f.open(QIODevice::WriteOnly); f.write("source"); f.close(); QFile t(to); - t.open(QIODevice::WriteOnly); + (void)t.open(QIODevice::WriteOnly); t.write("existing"); t.close(); EXPECT_TRUE(olive::FileFunctions::rename_file_allow_overwrite(from, to)); EXPECT_FALSE(QFileInfo::exists(from)); QFile result(to); - result.open(QIODevice::ReadOnly); + (void)result.open(QIODevice::ReadOnly); EXPECT_EQ(result.readAll(), QByteArray("source")); } @@ -105,7 +105,7 @@ TEST(CommonFileFunctions, CanCopyDirectoryWithoutOverwriting) QString src_file = QDir(src.path()).filePath(QStringLiteral("file.txt")); QFile f(src_file); - f.open(QIODevice::WriteOnly); + (void)f.open(QIODevice::WriteOnly); f.close(); EXPECT_TRUE(olive::FileFunctions::can_copy_directory_without_overwriting( @@ -113,7 +113,7 @@ TEST(CommonFileFunctions, CanCopyDirectoryWithoutOverwriting) QString dst_file = QDir(dst.path()).filePath(QStringLiteral("file.txt")); QFile g(dst_file); - g.open(QIODevice::WriteOnly); + (void)g.open(QIODevice::WriteOnly); g.close(); EXPECT_FALSE(olive::FileFunctions::can_copy_directory_without_overwriting( @@ -129,7 +129,7 @@ TEST(CommonFileFunctions, CopyDirectory) QString src_file = QDir(src.path()).filePath(QStringLiteral("file.txt")); QFile f(src_file); - f.open(QIODevice::WriteOnly); + (void)f.open(QIODevice::WriteOnly); f.write("copied"); f.close(); @@ -208,20 +208,20 @@ TEST(CommonFileFunctions, CopyDirectoryWithOverwrite) QString src_file = QDir(src.path()).filePath(QStringLiteral("file.txt")); QFile f(src_file); - f.open(QIODevice::WriteOnly); + (void)f.open(QIODevice::WriteOnly); f.write("new content"); f.close(); QString dst_file = QDir(dst.path()).filePath(QStringLiteral("file.txt")); QFile g(dst_file); - g.open(QIODevice::WriteOnly); + (void)g.open(QIODevice::WriteOnly); g.write("old content"); g.close(); olive::FileFunctions::copy_directory(src.path(), dst.path(), true); QFile result(dst_file); - result.open(QIODevice::ReadOnly); + (void)result.open(QIODevice::ReadOnly); EXPECT_EQ(result.readAll(), QByteArray("new content")); } diff --git a/tests/gtest/dynamic_render_backend_test.cpp b/tests/gtest/dynamic_render_backend_test.cpp index c0e1d9e53..0bb62a2e8 100644 --- a/tests/gtest/dynamic_render_backend_test.cpp +++ b/tests/gtest/dynamic_render_backend_test.cpp @@ -372,9 +372,11 @@ TEST(DynamicRenderBackend, VulkanIterativeBlitPingPong) QByteArray dst_data(k_size * k_size * 4, 0); dst->download(dst_data.data(), k_size); - // After two halving passes, red is 255 * 0.5 * 0.5. UNORM conversion floors - // the intermediate value, so the result is 63 rather than 64. - EXPECT_EQ(static_cast(dst_data[0]), 63u); + // After two halving passes, red is 255 * 0.5 * 0.5. UNORM conversion may + // floor (63) or round-to-nearest (64) depending on the driver (llvmpipe + // rounds); both are spec-conformant. + const uint8_t red = static_cast(dst_data[0]); + EXPECT_TRUE(red == 63u || red == 64u) << "red = " << int(red); EXPECT_EQ(static_cast(dst_data[1]), 0u); EXPECT_EQ(static_cast(dst_data[2]), 0u); EXPECT_EQ(static_cast(dst_data[3]), 255u); diff --git a/tests/gtest/mainwindow_test.cpp b/tests/gtest/mainwindow_test.cpp index e5d90200d..e5a415c7c 100644 --- a/tests/gtest/mainwindow_test.cpp +++ b/tests/gtest/mainwindow_test.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -174,6 +175,14 @@ TEST(MainWindow, ConstructsOffscreenWithPanelsAndMenus) // runners on the offscreen QPA), constructing it crashes in GL code // ("QOpenGLFunctions created with non-current context"). Probe first and // skip where GL is unavailable. + // MainWindow instantiates viewer panels containing QOpenGLWidget. The + // offscreen QPA cannot paint QOpenGLWidget at all ("QOpenGLWidget is not + // supported on this platform") — pumping events then crashes inside Qt's + // backing store flush. Skip there regardless of whether a bare + // QOpenGLContext can be created. + if (QGuiApplication::platformName() == QStringLiteral("offscreen")) { + GTEST_SKIP() << "offscreen QPA cannot paint QOpenGLWidget"; + } QOffscreenSurface probe_surface; probe_surface.create(); QOpenGLContext probe_context; diff --git a/tests/gtest/node_value_extended_test.cpp b/tests/gtest/node_value_extended_test.cpp index 36ca49a3a..5c666297b 100644 --- a/tests/gtest/node_value_extended_test.cpp +++ b/tests/gtest/node_value_extended_test.cpp @@ -64,8 +64,8 @@ TEST(NodeValueExtended, ColorMatrixBezierAccessors) TEST(NodeValueExtended, ScalarAccessors) { - olive::NodeValue boolean(olive::NodeValue::k_boolean, true); - EXPECT_TRUE(boolean.to_bool()); + olive::NodeValue bool_val(olive::NodeValue::k_boolean, true); + EXPECT_TRUE(bool_val.to_bool()); olive::NodeValue floating(olive::NodeValue::k_float, 2.75); EXPECT_DOUBLE_EQ(floating.to_double(), 2.75); diff --git a/tests/gtest/panel_test.cpp b/tests/gtest/panel_test.cpp index 3893cb1a3..fc0772eaa 100644 --- a/tests/gtest/panel_test.cpp +++ b/tests/gtest/panel_test.cpp @@ -138,7 +138,7 @@ inline oak::Project to_oak_project(Project *p) } // The process-wide undo stack previously reached via Core::undo_stack() -inline UndoStack *app_undo_stack() +[[maybe_unused]] inline UndoStack *app_undo_stack() { return static_cast(oakengine_app_undo_stack()); } diff --git a/tests/gtest/plugin_smoke_test.cpp b/tests/gtest/plugin_smoke_test.cpp index 026f97f7a..f424b7a26 100644 --- a/tests/gtest/plugin_smoke_test.cpp +++ b/tests/gtest/plugin_smoke_test.cpp @@ -170,7 +170,7 @@ TEST(PluginSmokeThread, ConcurrentImageAllocation) std::atomic success_count{ 0 }; for (int t = 0; t < num_threads; ++t) { - threads.emplace_back([&success_count, num_allocs_per_thread, t]() { + threads.emplace_back([&success_count, t]() { for (int i = 0; i < num_allocs_per_thread; ++i) { OFX::Host::ImageEffect::ClipDescriptor desc( kOfxImageEffectOutputClipName); diff --git a/tests/gtest/viewer_smoke_test.cpp b/tests/gtest/viewer_smoke_test.cpp index c272720c2..54921e933 100644 --- a/tests/gtest/viewer_smoke_test.cpp +++ b/tests/gtest/viewer_smoke_test.cpp @@ -462,7 +462,7 @@ TEST(ViewerSmokeThread, ConcurrentQueueAccess) for (int t = 0; t < num_threads; ++t) { threads.emplace_back( - [&queue, &append_count, t, num_frames_per_thread]() { + [&queue, &append_count, t]() { for (int i = 0; i < num_frames_per_thread; ++i) { ViewerPlaybackFrame frame{ Rational(t * num_frames_per_thread + i, 24), QVariant()