From 0703a5851b3ff5c777e63ae5ea5c0b3a48187e88 Mon Sep 17 00:00:00 2001 From: BrimsonBhin <40442071+BrimsonBhin@users.noreply.github.com> Date: Thu, 23 Apr 2020 22:02:47 -0700 Subject: [PATCH 01/69] Gaussian Blur: Use straight multiply MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Results are identical (tested with Olive 0.1.x). Using a straight multiply is faster if the compiler doesn't optimize pow(). It's also safer because with pow(), [the result is undefined if x<0 or if x=0 and y≤0](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/pow.xhtml). --- app/shaders/blur.frag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/shaders/blur.frag b/app/shaders/blur.frag index 1e520a996..65092c9cf 100644 --- a/app/shaders/blur.frag +++ b/app/shaders/blur.frag @@ -29,7 +29,7 @@ out vec4 fragColor; // Double gaussian formula, actually used in the code below // Should be faster than the single gaussian above since it doesn't need sqrt() float gaussian2(float x, float y, float sigma) { - return (1.0/(pow(sigma, 2.0)*2.0*M_PI))*exp(-0.5*((pow(x, 2.0) + pow(y, 2.0))/pow(sigma, 2.0))); + return (1.0/((sigma*sigma)*2.0*M_PI))*exp(-0.5*(((x*x) + (y*y))/(sigma*sigma))); } void main(void) { From 690bdb658150d71631d1f70b527a26bef6c30bfe Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 27 Apr 2020 14:01:44 +1000 Subject: [PATCH 02/69] timeline: make sure track length is updated if a block is disconnected --- app/node/output/track/track.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 24497f684..3adc7e6b7 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -556,9 +556,7 @@ void TrackOutput::BlockDisconnected(NodeEdgePtr edge) block_cache_.removeAt(index_of_block); // If there were blocks following this one, update their ins/outs - if (index_of_block < block_cache_.size()) { - UpdateInOutFrom(index_of_block); - } + UpdateInOutFrom(index_of_block); // Join the previous and next blocks together if (connected_block->previous()) { From b947b2224a41bee855935986a6442dfa218cc350 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 27 Apr 2020 16:08:48 +1000 Subject: [PATCH 03/69] nodeview: support multiple flow directions --- app/widget/keyframeview/keyframeviewbase.cpp | 2 +- app/widget/menu/menu.cpp | 11 + app/widget/menu/menu.h | 4 + app/widget/menu/menushared.cpp | 9 +- app/widget/menu/menushared.h | 2 +- app/widget/nodeview/CMakeLists.txt | 1 + app/widget/nodeview/nodeview.cpp | 65 +++++- app/widget/nodeview/nodeview.h | 12 ++ app/widget/nodeview/nodeviewcommon.h | 50 +++++ app/widget/nodeview/nodeviewedge.cpp | 24 ++- app/widget/nodeview/nodeviewedge.h | 8 + app/widget/nodeview/nodeviewitem.cpp | 75 +++++-- app/widget/nodeview/nodeviewitem.h | 16 ++ app/widget/nodeview/nodeviewscene.cpp | 203 +++++++------------ app/widget/nodeview/nodeviewscene.h | 30 ++- app/window/mainwindow/mainmenu.cpp | 2 +- 16 files changed, 338 insertions(+), 176 deletions(-) create mode 100644 app/widget/nodeview/nodeviewcommon.h diff --git a/app/widget/keyframeview/keyframeviewbase.cpp b/app/widget/keyframeview/keyframeviewbase.cpp index 2baac4e4c..8c830f615 100644 --- a/app/widget/keyframeview/keyframeviewbase.cpp +++ b/app/widget/keyframeview/keyframeviewbase.cpp @@ -391,7 +391,7 @@ void KeyframeViewBase::ShowContextMenu() { Menu m; - MenuShared::instance()->AddItemsForEditMenu(&m); + MenuShared::instance()->AddItemsForEditMenu(&m, false); QAction* linear_key_action = nullptr; QAction* bezier_key_action = nullptr; diff --git a/app/widget/menu/menu.cpp b/app/widget/menu/menu.cpp index 0ffc65e95..5515c65da 100644 --- a/app/widget/menu/menu.cpp +++ b/app/widget/menu/menu.cpp @@ -50,6 +50,17 @@ Menu::Menu(const QString &s, QWidget *parent) : Init(); } +QAction *Menu::AddActionWithData(const QString &text, const QVariant &data, const QVariant &compare) +{ + QAction* a = addAction(text); + + a->setData(data); + a->setCheckable(true); + a->setChecked(data == compare); + + return a; +} + QAction* Menu::InsertAlphabetically(const QString &s) { QAction* action = new QAction(s, this); diff --git a/app/widget/menu/menu.h b/app/widget/menu/menu.h index 4151cfd79..edafaf85d 100644 --- a/app/widget/menu/menu.h +++ b/app/widget/menu/menu.h @@ -131,6 +131,10 @@ public: return a; } + QAction* AddActionWithData(const QString& text, + const QVariant& data, + const QVariant& compare); + QAction *InsertAlphabetically(const QString& s); void InsertAlphabetically(QAction* entry); void InsertAlphabetically(Menu* menu); diff --git a/app/widget/menu/menushared.cpp b/app/widget/menu/menushared.cpp index 96015d2b8..2d68e3a3d 100644 --- a/app/widget/menu/menushared.cpp +++ b/app/widget/menu/menushared.cpp @@ -79,7 +79,7 @@ void MenuShared::AddItemsForNewMenu(Menu *m) m->addAction(new_folder_item_); } -void MenuShared::AddItemsForEditMenu(Menu *m) +void MenuShared::AddItemsForEditMenu(Menu *m, bool for_clips) { m->addAction(edit_cut_item_); m->addAction(edit_copy_item_); @@ -87,8 +87,11 @@ void MenuShared::AddItemsForEditMenu(Menu *m) m->addAction(edit_paste_insert_item_); m->addAction(edit_duplicate_item_); m->addAction(edit_delete_item_); - m->addAction(edit_ripple_delete_item_); - m->addAction(edit_split_item_); + + if (for_clips) { + m->addAction(edit_ripple_delete_item_); + m->addAction(edit_split_item_); + } } void MenuShared::AddItemsForInOutMenu(Menu *m) diff --git a/app/widget/menu/menushared.h b/app/widget/menu/menushared.h index 81848b3db..684616610 100644 --- a/app/widget/menu/menushared.h +++ b/app/widget/menu/menushared.h @@ -39,7 +39,7 @@ public: void Retranslate(); void AddItemsForNewMenu(Menu* m); - void AddItemsForEditMenu(Menu* m); + void AddItemsForEditMenu(Menu* m, bool for_clips); void AddItemsForInOutMenu(Menu* m); void AddItemsForClipEditMenu(Menu* m); diff --git a/app/widget/nodeview/CMakeLists.txt b/app/widget/nodeview/CMakeLists.txt index 9682e3972..c36c19882 100644 --- a/app/widget/nodeview/CMakeLists.txt +++ b/app/widget/nodeview/CMakeLists.txt @@ -18,6 +18,7 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} widget/nodeview/nodeview.h widget/nodeview/nodeview.cpp + widget/nodeview/nodeviewcommon.h widget/nodeview/nodeviewedge.h widget/nodeview/nodeviewedge.cpp widget/nodeview/nodeviewitem.h diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 5483d50cf..4f57c3d2d 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -25,6 +25,7 @@ #include "core.h" #include "nodeviewundo.h" #include "node/factory.h" +#include "widget/menu/menushared.h" OLIVE_NAMESPACE_ENTER @@ -37,13 +38,14 @@ NodeView::NodeView(QWidget *parent) : setScene(&scene_); setDragMode(RubberBandDrag); setContextMenuPolicy(Qt::CustomContextMenu); + setMouseTracking(true); + setRenderHint(QPainter::Antialiasing); connect(&scene_, &QGraphicsScene::changed, this, &NodeView::ItemsChanged); connect(&scene_, &QGraphicsScene::selectionChanged, this, &NodeView::SceneSelectionChangedSlot); connect(this, &NodeView::customContextMenuRequested, this, &NodeView::ShowContextMenu); - setMouseTracking(true); - setRenderHint(QPainter::Antialiasing); + SetFlowDirection(NodeViewCommon::kTopToBottom); } NodeView::~NodeView() @@ -321,10 +323,42 @@ void NodeView::ShowContextMenu(const QPoint &pos) Menu m; - Menu* add_menu = NodeFactory::CreateMenu(&m); - add_menu->setTitle(tr("Add")); - connect(add_menu, &Menu::triggered, this, &NodeView::CreateNodeSlot); - m.addMenu(add_menu); + MenuShared::instance()->AddItemsForEditMenu(&m, false); + + m.addSeparator(); + + if (itemAt(pos)) { + QAction* autopos = m.addAction(tr("Auto-Position")); + connect(autopos, &QAction::triggered, this, &NodeView::AutoPositionDescendents); + } else { + Menu* direction_menu = new Menu(tr("Direction"), &m); + m.addMenu(direction_menu); + + direction_menu->AddActionWithData(tr("Top to Bottom"), + NodeViewCommon::kTopToBottom, + scene_.GetFlowDirection()); + + direction_menu->AddActionWithData(tr("Bottom to Top"), + NodeViewCommon::kBottomToTop, + scene_.GetFlowDirection()); + + direction_menu->AddActionWithData(tr("Left to Right"), + NodeViewCommon::kLeftToRight, + scene_.GetFlowDirection()); + + direction_menu->AddActionWithData(tr("Right to Left"), + NodeViewCommon::kRightToLeft, + scene_.GetFlowDirection()); + + connect(direction_menu, &Menu::triggered, this, &NodeView::ContextMenuSetDirection); + + m.addSeparator(); + + Menu* add_menu = NodeFactory::CreateMenu(&m); + add_menu->setTitle(tr("Add")); + connect(add_menu, &Menu::triggered, this, &NodeView::CreateNodeSlot); + m.addMenu(add_menu); + } m.exec(mapToGlobal(pos)); } @@ -341,6 +375,20 @@ void NodeView::CreateNodeSlot(QAction *action) } } +void NodeView::ContextMenuSetDirection(QAction *action) +{ + SetFlowDirection(static_cast(action->data().toInt())); +} + +void NodeView::AutoPositionDescendents() +{ + QList selected = scene_.GetSelectedNodes(); + + foreach (Node* n, selected) { + scene_.ReorganizeFrom(n); + } +} + void NodeView::PlaceNode(NodeViewItem *n, const QPointF &pos) { QRectF destination_rect = n->rect(); @@ -487,4 +535,9 @@ void NodeView::DetachItemFromCursor() AttachItemToCursor(nullptr); } +void NodeView::SetFlowDirection(NodeViewCommon::FlowDirection dir) +{ + scene_.SetFlowDirection(dir); +} + OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 05d72464e..0bfcb37bb 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -85,6 +85,8 @@ private: void DetachItemFromCursor(); + void SetFlowDirection(NodeViewCommon::FlowDirection dir); + NodeGraph* graph_; NodeViewItem* attached_item_; @@ -117,6 +119,16 @@ private slots: */ void CreateNodeSlot(QAction* action); + /** + * @brief Receiver for setting the direction from the context menu + */ + void ContextMenuSetDirection(QAction* action); + + /** + * @brief Receiver for auto-position descendents menu action + */ + void AutoPositionDescendents(); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeview/nodeviewcommon.h b/app/widget/nodeview/nodeviewcommon.h new file mode 100644 index 000000000..c54ffe89f --- /dev/null +++ b/app/widget/nodeview/nodeviewcommon.h @@ -0,0 +1,50 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef NODEVIEWCOMMON_H +#define NODEVIEWCOMMON_H + +#include + +#include "common/define.h" + +OLIVE_NAMESPACE_ENTER + +class NodeViewCommon { +public: + enum FlowDirection { + kTopToBottom, + kBottomToTop, + kLeftToRight, + kRightToLeft + }; + + static Qt::Orientation GetFlowOrientation(FlowDirection dir) { + if (dir == kTopToBottom || dir == kBottomToTop) { + return Qt::Vertical; + } else { + return Qt::Horizontal; + } + } +}; + +OLIVE_NAMESPACE_EXIT + +#endif // NODEVIEWCOMMON_H diff --git a/app/widget/nodeview/nodeviewedge.cpp b/app/widget/nodeview/nodeviewedge.cpp index 333385e22..5ca752d29 100644 --- a/app/widget/nodeview/nodeviewedge.cpp +++ b/app/widget/nodeview/nodeviewedge.cpp @@ -35,7 +35,8 @@ NodeViewEdge::NodeViewEdge(QGraphicsItem *parent) : QGraphicsPathItem(parent), edge_(nullptr), color_group_(QPalette::Active), - color_role_(QPalette::Text) + color_role_(QPalette::Text), + flow_dir_(NodeViewCommon::kLeftToRight) { // Ensures this UI object is drawn behind other objects setZValue(-1); @@ -75,7 +76,8 @@ void NodeViewEdge::Adjust() } // Draw a line between the two - SetPoints(output->GetParamPoint(edge_->output()), input->GetParamPoint(edge_->input())); + SetPoints(output->GetParamPoint(edge_->output()), + input->GetParamPoint(edge_->input())); } void NodeViewEdge::SetConnected(bool c) @@ -103,12 +105,26 @@ void NodeViewEdge::SetHighlighted(bool e) void NodeViewEdge::SetPoints(const QPointF &start, const QPointF &end) { QPainterPath path; - double half_x = lerp(start.x(), end.x(), 0.5); path.moveTo(start); - path.cubicTo(QPointF(half_x, start.y()), QPointF(half_x, end.y()), end); + + if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal) { + double half_x = lerp(start.x(), end.x(), 0.5); + path.cubicTo(QPointF(half_x, start.y()), QPointF(half_x, end.y()), end); + } else { + double half_y = lerp(start.y(), end.y(), 0.5); + path.cubicTo(QPointF(start.x(), half_y), QPointF(end.x(), half_y), end); + } + setPath(path); } +void NodeViewEdge::SetFlowDirection(NodeViewCommon::FlowDirection dir) +{ + flow_dir_ = dir; + + Adjust(); +} + void NodeViewEdge::UpdatePen() { setPen(QPen(qApp->palette().color(color_group_, color_role_), edge_width_)); diff --git a/app/widget/nodeview/nodeviewedge.h b/app/widget/nodeview/nodeviewedge.h index 1f9b66876..b180333a4 100644 --- a/app/widget/nodeview/nodeviewedge.h +++ b/app/widget/nodeview/nodeviewedge.h @@ -25,6 +25,7 @@ #include #include "node/edge.h" +#include "nodeviewcommon.h" OLIVE_NAMESPACE_ENTER @@ -83,6 +84,11 @@ public: */ void SetPoints(const QPointF& start, const QPointF& end); + /** + * @brief Sets the direction nodes are flowing + */ + void SetFlowDirection(NodeViewCommon::FlowDirection dir); + private: void UpdatePen(); @@ -94,6 +100,8 @@ private: QPalette::ColorRole color_role_; + NodeViewCommon::FlowDirection flow_dir_; + }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index fe42d818d..7afe12687 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -46,7 +46,8 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) : expanded_(false), standard_click_(false), highlighted_index_(-1), - node_edge_change_command_(nullptr) + node_edge_change_command_(nullptr), + flow_dir_(NodeViewCommon::kLeftToRight) { // Set flags for this widget setFlag(QGraphicsItem::ItemIsMovable); @@ -57,26 +58,36 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) : // We use font metrics to set all the UI measurements for DPI-awareness // - QFont default_font; - QFontMetrics font_metrics(default_font); - // Set border width - node_border_width_ = font_metrics.height() / 12; + node_border_width_ = DefaultItemBorder(); - // Set text and icon padding - int node_text_padding = font_metrics.height() / 4; - - // Not particularly great way of using text scaling to set the width (DPI-awareness, etc.) - int widget_width = QFontMetricsWidth(font_metrics, "HHHHHHHHHHHHHH"); - - // Use the current default font height to size this widget - // Set default "collapsed" size - int widget_height = font_metrics.height() + node_text_padding * 2; + int widget_width = DefaultItemWidth(); + int widget_height = DefaultItemHeight(); title_bar_rect_ = QRectF(-widget_width/2, -widget_height/2, widget_width, widget_height); setRect(title_bar_rect_); } +int NodeViewItem::DefaultTextPadding() +{ + return QFontMetrics(QFont()).height() / 4; +} + +int NodeViewItem::DefaultItemHeight() +{ + return QFontMetrics(QFont()).height() + DefaultTextPadding() * 2; +} + +int NodeViewItem::DefaultItemWidth() +{ + return QFontMetricsWidth(QFontMetrics(QFont()), "HHHHHHHHHHHHHH");; +} + +int NodeViewItem::DefaultItemBorder() +{ + return QFontMetrics(QFont()).height() / 12; +} + void NodeViewItem::SetNode(Node *n) { node_ = n; @@ -203,6 +214,7 @@ void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event) // Create draggable object dragging_edge_ = new NodeViewEdge(); + dragging_edge_->SetFlowDirection(flow_dir_); // Set up a QUndoCommand to make this action undoable node_edge_change_command_ = new QUndoCommand(); @@ -330,7 +342,9 @@ void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) if (!cached_drop_item_->GetNode()->OutputsTo(node_)) { drag_dest_param_ = comp_param; highlight_their_index = i; - end_point = cached_drop_item_->mapToScene(cached_drop_item_->GetInputPoint(i)); + + QPointF end_point_local = cached_drop_item_->GetInputPoint(i); + end_point = cached_drop_item_->mapToScene(end_point_local); } break; @@ -439,7 +453,19 @@ QRectF NodeViewItem::GetInputRect(int index) const QPointF NodeViewItem::GetParamPoint(NodeParam *param) const { if (param->type() == NodeParam::kOutput) { - return pos() + QPointF(rect().right(), rect().center().y()); + + switch (flow_dir_) { + case NodeViewCommon::kLeftToRight: + default: + return pos() + QPointF(rect().right(), rect().center().y()); + case NodeViewCommon::kRightToLeft: + return pos() + QPointF(rect().left(), rect().center().y()); + case NodeViewCommon::kTopToBottom: + return pos() + QPointF(rect().center().x(), rect().bottom()); + case NodeViewCommon::kBottomToTop: + return pos() + QPointF(rect().center().x(), rect().top()); + } + } else { NodeInput* input = static_cast(param); @@ -452,11 +478,26 @@ QPointF NodeViewItem::GetParamPoint(NodeParam *param) const } } +void NodeViewItem::SetFlowDirection(NodeViewCommon::FlowDirection dir) +{ + flow_dir_ = dir; +} + QPointF NodeViewItem::GetInputPoint(int index) const { QRectF input_rect = GetInputRect(index); - return QPointF(input_rect.left(), input_rect.center().y()); + switch (flow_dir_) { + case NodeViewCommon::kLeftToRight: + default: + return QPointF(input_rect.left(), input_rect.center().y()); + case NodeViewCommon::kRightToLeft: + return QPointF(input_rect.right(), input_rect.center().y()); + case NodeViewCommon::kTopToBottom: + return QPointF(input_rect.center().x(), input_rect.top()); + case NodeViewCommon::kBottomToTop: + return QPointF(input_rect.center().x(), input_rect.bottom()); + } } OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index 1ed17f844..28bc9a932 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -28,6 +28,7 @@ #include #include "node/node.h" +#include "nodeviewcommon.h" #include "nodeviewedge.h" #include "nodeviewitemwidgetproxy.h" @@ -71,6 +72,19 @@ public: */ QPointF GetParamPoint(NodeParam* param) const; + /** + * @brief Sets the direction nodes are flowing + */ + void SetFlowDirection(NodeViewCommon::FlowDirection dir); + + static int DefaultTextPadding(); + + static int DefaultItemHeight(); + + static int DefaultItemWidth(); + + static int DefaultItemBorder(); + protected: virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; @@ -153,6 +167,8 @@ private: */ QUndoCommand* node_edge_change_command_; + NodeViewCommon::FlowDirection flow_dir_; + }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeview/nodeviewscene.cpp b/app/widget/nodeview/nodeviewscene.cpp index 7de9ce7b4..a1604d0c1 100644 --- a/app/widget/nodeview/nodeviewscene.cpp +++ b/app/widget/nodeview/nodeviewscene.cpp @@ -20,14 +20,35 @@ #include "nodeviewscene.h" +#include "nodeviewedge.h" +#include "nodeviewitem.h" + OLIVE_NAMESPACE_ENTER NodeViewScene::NodeViewScene(QObject *parent) : QGraphicsScene(parent), - graph_(nullptr) + graph_(nullptr), + direction_(NodeViewCommon::kLeftToRight) { - connect(&reorganize_timer_, &QTimer::timeout, &reorganize_timer_, &QTimer::stop); - connect(&reorganize_timer_, &QTimer::timeout, this, &NodeViewScene::Reorganize); +} + +void NodeViewScene::SetFlowDirection(NodeViewCommon::FlowDirection direction) +{ + direction_ = direction; + + { + QHash::const_iterator i; + for (i=item_map_.constBegin(); i!=item_map_.constEnd(); i++) { + i.value()->SetFlowDirection(direction_); + } + } + + { + QHash::const_iterator i; + for (i=edge_map_.constBegin(); i!=edge_map_.constEnd(); i++) { + i.value()->SetFlowDirection(direction_); + } + } } void NodeViewScene::clear() @@ -35,7 +56,7 @@ void NodeViewScene::clear() // Deselect everything (prevents signals that a selection has changed after deleting an object) DeselectAll(); - // HACK: QGraphicsScene contains some sort of internal hashing of the selected items which doesn't update unless + // HACK: QGraphicsScene contains some sort of internal caching of the selected items which doesn't update unless // we call a function like this. That means even though we deselect all items above, QGraphicsScene will // continue to incorrectly signal selectionChanged() when items that were selected (but are now not) get // deleted. Calling this function appears to update the internal cache and prevent this. @@ -134,6 +155,7 @@ void NodeViewScene::AddNode(Node* node) NodeViewItem* item = new NodeViewItem(); item->SetNode(node); + item->SetFlowDirection(direction_); addItem(item); item_map_.insert(node, item); @@ -151,8 +173,6 @@ void NodeViewScene::AddNode(Node* node) } } } - - QueueReorganize(); } void NodeViewScene::RemoveNode(Node *node) @@ -165,11 +185,10 @@ void NodeViewScene::AddEdge(NodeEdgePtr edge) NodeViewEdge* edge_ui = new NodeViewEdge(); edge_ui->SetEdge(edge); + edge_ui->SetFlowDirection(direction_); addItem(edge_ui); edge_map_.insert(edge.get(), edge_ui); - - QueueReorganize(); } void NodeViewScene::RemoveEdge(NodeEdgePtr edge) @@ -177,145 +196,77 @@ void NodeViewScene::RemoveEdge(NodeEdgePtr edge) delete edge_map_.take(edge.get()); } -void NodeViewScene::QueueReorganize() +Qt::Orientation NodeViewScene::GetFlowOrientation() const { - // Avoids the fairly complex Reorganize() function every single time a connection or node is added - - reorganize_timer_.stop(); - reorganize_timer_.start(20); + return NodeViewCommon::GetFlowOrientation(direction_); } -QList NodeViewScene::GetNodeDirectDescendants(Node* n, const QList connected_nodes, QList& processed_nodes) +NodeViewCommon::FlowDirection NodeViewScene::GetFlowDirection() const { - QList direct_descendants = connected_nodes; - - processed_nodes.append(n); - - // Remove any nodes that aren't necessarily attached directly - for (int i=0;ioutput()->edges().size();j++) { - Node* this_output_connection = connected->output()->edges().at(j)->input()->parentNode(); - if (!processed_nodes.contains(this_output_connection)) { - direct_descendants.removeAt(i); - i--; - break; - } - } - } - - return direct_descendants; + return direction_; } -int NodeViewScene::FindWeightsInternal(Node *node, QHash &weights, QList& weighted_nodes) +void NodeViewScene::ReorganizeFrom(Node* n) { - QList connected_nodes = node->GetImmediateDependencies(); + QList immediates = n->GetImmediateDependencies(); - int weight = 0; - - if (!connected_nodes.isEmpty()) { - QList direct_descendants = GetNodeDirectDescendants(node, connected_nodes, weighted_nodes); - - foreach (Node* dep, direct_descendants) { - weight += FindWeightsInternal(dep, weights, weighted_nodes); - } - } - - weight = qMax(weight, 1); - - weights.insert(node, weight); - - return weight; -} - -void NodeViewScene::ReorganizeInternal(NodeViewItem* src_item, QHash& weights, QList& positioned_nodes) -{ - if (!src_item) { + if (immediates.isEmpty()) { + // Nothing to do return; } - Node* n = src_item->GetNode(); + NodeViewItem* parent_item = NodeToUIObject(n); - QList connected_nodes = n->GetImmediateDependencies(); + int item_sz, item_padding, layer_diff, total_top; - if (connected_nodes.isEmpty()) { - return; + if (GetFlowOrientation() == Qt::Vertical) { + item_sz = NodeViewItem::DefaultItemWidth(); + item_padding = item_sz / 2; + layer_diff = NodeViewItem::DefaultItemHeight() * 2; + total_top = parent_item->x(); + } else { + item_sz = NodeViewItem::DefaultItemHeight(); + item_padding = item_sz; + layer_diff = NodeViewItem::DefaultItemWidth() * 3 / 2; + total_top = parent_item->y(); } - QList direct_descendants = GetNodeDirectDescendants(n, connected_nodes, positioned_nodes); + int item_sz_with_padding = item_sz + item_padding; - int descendant_weight = 0; - foreach (Node* dep, direct_descendants) { - descendant_weight += weights.value(dep); + int total_sz = item_sz_with_padding * immediates.size() - item_padding; + + total_top -= total_sz / 2; + total_top += item_sz / 2; + + int item_layer_pos; + + switch (direction_) { + case NodeViewCommon::kTopToBottom: + item_layer_pos = parent_item->pos().y() - layer_diff; + break; + case NodeViewCommon::kLeftToRight: + item_layer_pos = parent_item->pos().x() - layer_diff; + break; + case NodeViewCommon::kBottomToTop: + item_layer_pos = parent_item->pos().y() + layer_diff; + break; + case NodeViewCommon::kRightToLeft: + item_layer_pos = parent_item->pos().x() + layer_diff; + break; } - qreal center_y = src_item->y(); - qreal total_height = descendant_weight * src_item->rect().height() + (direct_descendants.size()-1) * src_item->rect().height()/2; - double item_top = center_y - (total_height/2) + src_item->rect().height()/2; + for (int i=0;isetPos(total_top + item_sz_with_padding * i, + item_layer_pos); + } else { + item->setPos(item_layer_pos, + total_top + item_sz_with_padding * i); } - double item_y = item_top; - - // Multiply the index by the item height (with 1.5 for padding) - item_y += weight_index * src_item->rect().height() * 1.5; - - QPointF item_pos(src_item->pos().x() - item->rect().width() * 3 / 2, - item_y); - - item->setPos(item_pos); - - weight_index += weights.value(connected); - } - - // Recursively work on each node - foreach (Node* connected, connected_nodes) { - NodeViewItem* item = NodeToUIObject(connected); - - if (!item) { - continue; - } - - ReorganizeInternal(item, weights, positioned_nodes); - } -} - -void NodeViewScene::Reorganize() -{ - if (!graph_) { - return; - } - - QList end_nodes; - - // Calculate the nodes that don't output to anything, they'll be our anchors - foreach (Node* node, graph_->nodes()) { - if (!node->HasConnectedOutputs()) { - end_nodes.append(node); - } - } - - QList processed_nodes; - - QHash node_weights; - foreach (Node* end_node, end_nodes) { - FindWeightsInternal(end_node, node_weights, processed_nodes); - } - - processed_nodes.clear(); - - foreach (Node* end_node, end_nodes) { - ReorganizeInternal(NodeToUIObject(end_node), node_weights, processed_nodes); + ReorganizeFrom(immediates.at(i)); } } diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index 436045700..48d4adba2 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -25,8 +25,8 @@ #include #include "node/graph.h" -#include "widget/nodeview/nodeviewedge.h" -#include "widget/nodeview/nodeviewitem.h" +#include "nodeviewedge.h" +#include "nodeviewitem.h" OLIVE_NAMESPACE_ENTER @@ -69,6 +69,16 @@ public: const QHash& item_map() const; const QHash& edge_map() const; + Qt::Orientation GetFlowOrientation() const; + + NodeViewCommon::FlowDirection GetFlowDirection() const; + void SetFlowDirection(NodeViewCommon::FlowDirection direction); + + /** + * @brief Automatically reposition the nodes based on their connections + */ + void ReorganizeFrom(Node* n); + public slots: /** * @brief Slot when a Node is added to a graph (SetGraph() connects this) @@ -103,27 +113,13 @@ public slots: void RemoveEdge(NodeEdgePtr edge); private: - void QueueReorganize(); - - QList GetNodeDirectDescendants(Node* n, const QList connected_nodes, QList& processed_nodes); - - int FindWeightsInternal(Node* node, QHash& weights, QList& weighted_nodes); - - void ReorganizeInternal(NodeViewItem *src_item, QHash& weights, QList &positioned_nodes); - QHash item_map_; QHash edge_map_; - QTimer reorganize_timer_; - NodeGraph* graph_; -private slots: - /** - * @brief Automatically reposition the nodes based on their connections - */ - void Reorganize(); + NodeViewCommon::FlowDirection direction_; }; diff --git a/app/window/mainwindow/mainmenu.cpp b/app/window/mainwindow/mainmenu.cpp index e35b83531..6af40d4a1 100644 --- a/app/window/mainwindow/mainmenu.cpp +++ b/app/window/mainwindow/mainmenu.cpp @@ -83,7 +83,7 @@ MainMenu::MainMenu(MainWindow *parent) : edit_menu_->addAction(edit_redo_item_); edit_menu_->addSeparator(); - MenuShared::instance()->AddItemsForEditMenu(edit_menu_); + MenuShared::instance()->AddItemsForEditMenu(edit_menu_, true); edit_menu_->addSeparator(); edit_select_all_item_ = edit_menu_->AddItem("selectall", this, &MainMenu::SelectAllTriggered, "Ctrl+A"); edit_deselect_all_item_ = edit_menu_->AddItem("deselectall", this, &MainMenu::DeselectAllTriggered, "Ctrl+Shift+A"); From 3e28c74d2060f6c8db1d06176f5af8917f4a440f Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 28 Apr 2020 00:24:37 +1000 Subject: [PATCH 04/69] projectpanel: connected open/save/undo/redo buttons These were waiting until opening/saving projects was implemented, which it was about a month ago. Now they are connected. --- app/panel/project/project.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/panel/project/project.cpp b/app/panel/project/project.cpp index b8f5d42e0..51fd9388e 100644 --- a/app/panel/project/project.cpp +++ b/app/panel/project/project.cpp @@ -49,12 +49,16 @@ ProjectPanel::ProjectPanel(QWidget *parent) : layout->addWidget(toolbar); // Make toolbar connections - connect(toolbar, SIGNAL(NewClicked()), this, SLOT(ShowNewMenu())); + connect(toolbar, &ProjectToolbar::NewClicked, this, &ProjectPanel::ShowNewMenu); + connect(toolbar, &ProjectToolbar::OpenClicked, Core::instance(), &Core::OpenProject); + connect(toolbar, &ProjectToolbar::SaveClicked, Core::instance(), &Core::SaveActiveProject); + connect(toolbar, &ProjectToolbar::UndoClicked, Core::instance()->undo_stack(), &QUndoStack::undo); + connect(toolbar, &ProjectToolbar::RedoClicked, Core::instance()->undo_stack(), &QUndoStack::redo); // Set up main explorer object explorer_ = new ProjectExplorer(this); layout->addWidget(explorer_); - connect(explorer_, SIGNAL(DoubleClickedItem(Item*)), this, SLOT(ItemDoubleClickSlot(Item*))); + connect(explorer_, &ProjectExplorer::DoubleClickedItem, this, &ProjectPanel::ItemDoubleClickSlot); // Set toolbar's view to the explorer's view toolbar->SetView(explorer_->view_type()); From e154b5d5d40a8103bf06f4f8edae4dd228e4c922 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 28 Apr 2020 00:28:38 +1000 Subject: [PATCH 05/69] nodeview: make auto-position a manual and specific function, allow different flow directions The first in many NodeView improvements. Up until now, nodes have been forcibly auto-arranged, which has worked as a reasonable stop-gap just so that all the nodes are visible during testing/development. However this does not make a good user experience. We move the auto-arrange code to a manual function that can be used when the user or program deems it necessary, and also abstract the node positions so that the view can ultimately determine the exact amount of spacing (useful for changing DPIs) or orientation, so users can direct the flow whichever way they want. --- app/node/node.cpp | 2 + app/node/node.h | 5 ++ app/widget/nodeview/nodeview.cpp | 2 +- app/widget/nodeview/nodeviewcommon.h | 8 +++ app/widget/nodeview/nodeviewitem.cpp | 77 +++++++++++++++++++++++++-- app/widget/nodeview/nodeviewitem.h | 7 +++ app/widget/nodeview/nodeviewscene.cpp | 67 +++++++---------------- app/widget/nodeview/nodeviewscene.h | 7 +++ 8 files changed, 124 insertions(+), 51 deletions(-) diff --git a/app/node/node.cpp b/app/node/node.cpp index ffde76468..8f42b9dc2 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -558,6 +558,8 @@ const QPointF &Node::GetPosition() void Node::SetPosition(const QPointF &pos) { position_ = pos; + + emit PositionChanged(position_); } void Node::AddInput(NodeInput *input) diff --git a/app/node/node.h b/app/node/node.h index 16b33c8c1..656713224 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -384,6 +384,11 @@ signals: */ void EdgeRemoved(NodeEdgePtr edge); + /** + * @brief Signal emitted whenever the position is set through SetPosition() + */ + void PositionChanged(const QPointF& pos); + private: /** * @brief Add a parameter to this node diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 4f57c3d2d..6f005a46c 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -45,7 +45,7 @@ NodeView::NodeView(QWidget *parent) : connect(&scene_, &QGraphicsScene::selectionChanged, this, &NodeView::SceneSelectionChangedSlot); connect(this, &NodeView::customContextMenuRequested, this, &NodeView::ShowContextMenu); - SetFlowDirection(NodeViewCommon::kTopToBottom); + SetFlowDirection(NodeViewCommon::kLeftToRight); } NodeView::~NodeView() diff --git a/app/widget/nodeview/nodeviewcommon.h b/app/widget/nodeview/nodeviewcommon.h index c54ffe89f..cc2b870fe 100644 --- a/app/widget/nodeview/nodeviewcommon.h +++ b/app/widget/nodeview/nodeviewcommon.h @@ -43,6 +43,14 @@ public: return Qt::Horizontal; } } + + static bool DirectionsAreOpposing(FlowDirection a, FlowDirection b) { + return ((a == NodeViewCommon::kLeftToRight && b == NodeViewCommon::kRightToLeft) + || (a == NodeViewCommon::kRightToLeft && b == NodeViewCommon::kLeftToRight) + || (a == NodeViewCommon::kTopToBottom && b == NodeViewCommon::kBottomToTop) + || (a == NodeViewCommon::kBottomToTop && b == NodeViewCommon::kTopToBottom)); + } + }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 7afe12687..9c3b122e6 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -68,6 +68,57 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) : setRect(title_bar_rect_); } +QPointF NodeViewItem::GetNodePosition() const +{ + QPointF node_pos; + + qreal adjusted_x = pos().x() / DefaultItemHorizontalPadding(); + qreal adjusted_y = pos().y() / DefaultItemVerticalPadding(); + + switch (flow_dir_) { + case NodeViewCommon::kLeftToRight: + node_pos.setX(adjusted_x); + node_pos.setY(adjusted_y); + break; + case NodeViewCommon::kRightToLeft: + node_pos.setX(-adjusted_x); + node_pos.setY(adjusted_y); + break; + case NodeViewCommon::kTopToBottom: + node_pos.setX(adjusted_y); + node_pos.setY(adjusted_x); + break; + case NodeViewCommon::kBottomToTop: + node_pos.setX(-adjusted_y); + node_pos.setY(adjusted_x); + break; + } + + return node_pos; +} + +void NodeViewItem::SetNodePosition(const QPointF &pos) +{ + switch (flow_dir_) { + case NodeViewCommon::kLeftToRight: + setPos(pos.x() * DefaultItemHorizontalPadding(), + pos.y() * DefaultItemVerticalPadding()); + break; + case NodeViewCommon::kRightToLeft: + setPos(-pos.x() * DefaultItemHorizontalPadding(), + pos.y() * DefaultItemVerticalPadding()); + break; + case NodeViewCommon::kTopToBottom: + setPos(pos.y() * DefaultItemHorizontalPadding(), + pos.x() * DefaultItemVerticalPadding()); + break; + case NodeViewCommon::kBottomToTop: + setPos(pos.y() * DefaultItemHorizontalPadding(), + -pos.x() * DefaultItemVerticalPadding()); + break; + } +} + int NodeViewItem::DefaultTextPadding() { return QFontMetrics(QFont()).height() / 4; @@ -88,6 +139,24 @@ int NodeViewItem::DefaultItemBorder() return QFontMetrics(QFont()).height() / 12; } +qreal NodeViewItem::DefaultItemHorizontalPadding() const +{ + if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal) { + return DefaultItemWidth() * 1.5; + } else { + return DefaultItemWidth() * 1.25; + } +} + +qreal NodeViewItem::DefaultItemVerticalPadding() const +{ + if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal) { + return DefaultItemHeight() * 1.5; + } else { + return DefaultItemHeight() * 2.0; + } +} + void NodeViewItem::SetNode(Node *n) { node_ = n; @@ -241,8 +310,8 @@ void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event) drag_source_ = static_cast(scene())->NodeToUIObject(drag_src_param_->parentNode()); // Get the opposing parameter's rect center using the line's current coordinates - // (we use the current coordinates because a complex formula is used for the line's coords if the opposing - // node is collapsed, therefore it's easier to just retrieve it from line itself) + // (we use the current coordinates because a complex formula is used for the line's coords if + // the opposing node is collapsed, therefore it's easier to just retrieve it from line itself) NodeViewEdge* existing_edge_ui = static_cast(scene())->EdgeToUIObject(edge); QPainterPath existing_edge_line = existing_edge_ui->path(); QPointF edge_start = existing_edge_line.pointAtPercent(0); @@ -422,7 +491,9 @@ void NodeViewItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) QVariant NodeViewItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) { if (change == ItemPositionHasChanged && node_) { - node_->SetPosition(value.toPointF()); + node_->blockSignals(true); + node_->SetPosition(GetNodePosition()); + node_->blockSignals(false); } return QGraphicsItem::itemChange(change, value); diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index 28bc9a932..9d1999b28 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -46,6 +46,9 @@ class NodeViewItem : public QGraphicsRectItem public: NodeViewItem(QGraphicsItem* parent = nullptr); + QPointF GetNodePosition() const; + void SetNodePosition(const QPointF& pos); + /** * @brief Set the Node to correspond to this widget */ @@ -85,6 +88,10 @@ public: static int DefaultItemBorder(); + qreal DefaultItemHorizontalPadding() const; + + qreal DefaultItemVerticalPadding() const; + protected: virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; diff --git a/app/widget/nodeview/nodeviewscene.cpp b/app/widget/nodeview/nodeviewscene.cpp index a1604d0c1..bcadfd733 100644 --- a/app/widget/nodeview/nodeviewscene.cpp +++ b/app/widget/nodeview/nodeviewscene.cpp @@ -37,13 +37,18 @@ void NodeViewScene::SetFlowDirection(NodeViewCommon::FlowDirection direction) direction_ = direction; { + // Iterate over node items setting direction QHash::const_iterator i; for (i=item_map_.constBegin(); i!=item_map_.constEnd(); i++) { i.value()->SetFlowDirection(direction_); + + // Update position too + i.value()->SetNodePosition(i.key()->GetPosition()); } } { + // Iterate over edge items setting direction QHash::const_iterator i; for (i=edge_map_.constBegin(); i!=edge_map_.constEnd(); i++) { i.value()->SetFlowDirection(direction_); @@ -173,10 +178,14 @@ void NodeViewScene::AddNode(Node* node) } } } + + connect(node, &Node::PositionChanged, this, &NodeViewScene::NodePositionChanged); } void NodeViewScene::RemoveNode(Node *node) { + disconnect(node, &Node::PositionChanged, this, &NodeViewScene::NodePositionChanged); + delete item_map_.take(node); } @@ -215,59 +224,23 @@ void NodeViewScene::ReorganizeFrom(Node* n) return; } - NodeViewItem* parent_item = NodeToUIObject(n); + QPointF parent_pos = n->GetPosition(); - int item_sz, item_padding, layer_diff, total_top; - - if (GetFlowOrientation() == Qt::Vertical) { - item_sz = NodeViewItem::DefaultItemWidth(); - item_padding = item_sz / 2; - layer_diff = NodeViewItem::DefaultItemHeight() * 2; - total_top = parent_item->x(); - } else { - item_sz = NodeViewItem::DefaultItemHeight(); - item_padding = item_sz; - layer_diff = NodeViewItem::DefaultItemWidth() * 3 / 2; - total_top = parent_item->y(); - } - - int item_sz_with_padding = item_sz + item_padding; - - int total_sz = item_sz_with_padding * immediates.size() - item_padding; - - total_top -= total_sz / 2; - total_top += item_sz / 2; - - int item_layer_pos; - - switch (direction_) { - case NodeViewCommon::kTopToBottom: - item_layer_pos = parent_item->pos().y() - layer_diff; - break; - case NodeViewCommon::kLeftToRight: - item_layer_pos = parent_item->pos().x() - layer_diff; - break; - case NodeViewCommon::kBottomToTop: - item_layer_pos = parent_item->pos().y() + layer_diff; - break; - case NodeViewCommon::kRightToLeft: - item_layer_pos = parent_item->pos().x() + layer_diff; - break; - } + qreal child_x = parent_pos.x() - 1.0; + qreal children_height = immediates.size()-1; + qreal children_y = parent_pos.y() - children_height * 0.5; for (int i=0;isetPos(total_top + item_sz_with_padding * i, - item_layer_pos); - } else { - item->setPos(item_layer_pos, - total_top + item_sz_with_padding * i); - } + immediates.at(i)->SetPosition(QPointF(child_x, + children_y + i)); ReorganizeFrom(immediates.at(i)); } } +void NodeViewScene::NodePositionChanged(const QPointF &pos) +{ + item_map_.value(static_cast(sender()))->SetNodePosition(pos); +} + OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index 48d4adba2..ee4040c5f 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -121,6 +121,13 @@ private: NodeViewCommon::FlowDirection direction_; +private slots: + /** + * @brief Receiver for whenever a node position changes + */ + void NodePositionChanged(const QPointF& pos); + + }; OLIVE_NAMESPACE_EXIT From 0faf682225d3dea2393d973577d824d85997a81c Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 28 Apr 2020 02:47:28 +1000 Subject: [PATCH 06/69] core: fixed bug that didn't register ProjectPtr correctly and broke some functionality as a result --- app/core.cpp | 2 +- app/core.h | 4 ++-- app/project/projectloadmanager.h | 2 +- app/project/projectsavemanager.h | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index f41294ff4..9dea0b981 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -538,7 +538,7 @@ void Core::DeclareTypesForQt() qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); - qRegisterMetaType(); + qRegisterMetaType(); } void Core::StartGUI(bool full_screen) diff --git a/app/core.h b/app/core.h index 20ef0a4ea..8c6043e8a 100644 --- a/app/core.h +++ b/app/core.h @@ -477,12 +477,12 @@ private: private slots: void SaveAutorecovery(); - void ProjectSaveSucceeded(ProjectPtr p); + void ProjectSaveSucceeded(OLIVE_NAMESPACE::ProjectPtr p); /** * @brief Adds a project to the "open projects" list */ - void AddOpenProject(ProjectPtr p); + void AddOpenProject(OLIVE_NAMESPACE::ProjectPtr p); void ImportTaskComplete(QUndoCommand* command); diff --git a/app/project/projectloadmanager.h b/app/project/projectloadmanager.h index 1e656ff26..6343293c6 100644 --- a/app/project/projectloadmanager.h +++ b/app/project/projectloadmanager.h @@ -36,7 +36,7 @@ protected: virtual void Action() override; signals: - void ProjectLoaded(ProjectPtr project); + void ProjectLoaded(OLIVE_NAMESPACE::ProjectPtr project); private: QString filename_; diff --git a/app/project/projectsavemanager.h b/app/project/projectsavemanager.h index 88be5f840..fb5d1fe79 100644 --- a/app/project/projectsavemanager.h +++ b/app/project/projectsavemanager.h @@ -33,7 +33,7 @@ public: ProjectSaveManager(ProjectPtr project); signals: - void ProjectSaveSucceeded(ProjectPtr p); + void ProjectSaveSucceeded(OLIVE_NAMESPACE::ProjectPtr p); protected: virtual void Action() override; From b57d12868f4c801e19cb445041cbb26cfd7f91ed Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 28 Apr 2020 03:20:08 +1000 Subject: [PATCH 07/69] nodeview: allow dropping a node on any edge Before we checked data types to ensure compatibility, but some nodes (e.g. the math node) may be compatible despite a not apparently compatible data type. We allow users to make any connection quickly this way. --- app/widget/nodeview/nodeview.cpp | 39 ++++++++++++++++---------------- app/widget/nodeview/nodeview.h | 2 +- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 6f005a46c..223263bd5 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -45,7 +45,7 @@ NodeView::NodeView(QWidget *parent) : connect(&scene_, &QGraphicsScene::selectionChanged, this, &NodeView::SceneSelectionChangedSlot); connect(this, &NodeView::customContextMenuRequested, this, &NodeView::ShowContextMenu); - SetFlowDirection(NodeViewCommon::kLeftToRight); + SetFlowDirection(NodeViewCommon::kTopToBottom); } NodeView::~NodeView() @@ -226,7 +226,7 @@ void NodeView::mousePressEvent(QMouseEvent *event) new NodeEdgeRemoveCommand(old_edge, command); // Place new edges - new NodeEdgeAddCommand(old_edge->output(), drop_compatible_input_, command); + new NodeEdgeAddCommand(old_edge->output(), drop_input_, command); new NodeEdgeAddCommand(dropping_node->output(), old_edge->input(), command); Core::instance()->undo_stack()->push(command); @@ -255,31 +255,32 @@ void NodeView::mouseMoveEvent(QMouseEvent *event) NodeViewEdge* new_drop_edge = nullptr; + // See if there is an edge here foreach (QGraphicsItem* item, items) { - NodeViewEdge* edge = dynamic_cast(item); + new_drop_edge = dynamic_cast(item); - if (edge) { - // Try to place this node inside this edge + if (new_drop_edge) { + drop_input_ = nullptr; - // See if the node we're dropping has an input of a compatible data type - NodeInput* edges_input = edge->edge()->input(); - NodeParam::DataType input_type = edges_input->data_type(); + foreach (NodeParam* param, attached_item_->GetNode()->parameters()) { + if (param->type() == NodeParam::kInput) { + NodeInput* input = static_cast(param); - NodeInput* compatible_input = nullptr; - - foreach (NodeParam* drop_node_param, attached_item_->GetNode()->parameters()) { - if (drop_node_param->type() == NodeParam::kInput - && static_cast(drop_node_param)->data_type() & input_type) { - compatible_input = static_cast(drop_node_param); - break; + if (input->IsConnectable()) { + if (input->data_type() & new_drop_edge->edge()->input()->data_type()) { + drop_input_ = input; + break; + } else if (!drop_input_) { + drop_input_ = input; + } + } } } - if (compatible_input) { - new_drop_edge = edge; - drop_compatible_input_ = compatible_input; - + if (drop_input_) { break; + } else { + new_drop_edge = nullptr; } } } diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 0bfcb37bb..6a8c93779 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -92,7 +92,7 @@ private: NodeViewItem* attached_item_; NodeViewEdge* drop_edge_; - NodeInput* drop_compatible_input_; + NodeInput* drop_input_; NodeViewScene scene_; From 3c01b14a14afceecacbf152c3690daa49329c44b Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 28 Apr 2020 03:32:50 +1000 Subject: [PATCH 08/69] palette: fixed disabled text color --- app/ui/style/olive-dark/palette.ini | 2 +- app/widget/nodeview/nodeviewitem.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/ui/style/olive-dark/palette.ini b/app/ui/style/olive-dark/palette.ini index 91d617459..7f93ffd25 100644 --- a/app/ui/style/olive-dark/palette.ini +++ b/app/ui/style/olive-dark/palette.ini @@ -4,7 +4,6 @@ Base=#191919 BrightText=#FF0000 Button=#353535 ButtonText=#FFFFFF -Disabled-ButtonText=#808080 Highlight=#2A82DA HighlightedText=#FFFFFF Link=#2A82DA @@ -16,3 +15,4 @@ WindowText=#FFFFFF [Disabled] ButtonText=#808080 +Text=#A0A0A0 diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 9c3b122e6..014105560 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -423,7 +423,7 @@ void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) cached_drop_item_->SetHighlightedIndex(highlight_their_index); } - dragging_edge_->SetConnected(drag_dest_param_ != nullptr); + dragging_edge_->SetConnected(drag_dest_param_); dragging_edge_->SetPoints(dragging_edge_start_, end_point); From d74d6a1542d9bb0777f130351fa532b4c632025f Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 28 Apr 2020 03:43:35 +1000 Subject: [PATCH 09/69] nodes: implementing saving/loading node positions from project file --- app/common/xmlutils.cpp | 13 ++++++++++++- app/node/node.cpp | 6 +++++- app/node/node.h | 2 +- app/widget/nodeview/nodeviewitem.cpp | 2 +- app/widget/nodeview/nodeviewscene.cpp | 2 +- 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/app/common/xmlutils.cpp b/app/common/xmlutils.cpp index 0b39e6d32..087648822 100644 --- a/app/common/xmlutils.cpp +++ b/app/common/xmlutils.cpp @@ -26,15 +26,25 @@ OLIVE_NAMESPACE_ENTER -Node* XMLLoadNode(QXmlStreamReader* reader) { +Node* XMLLoadNode(QXmlStreamReader* reader) +{ QString node_id; quintptr node_ptr = 0; + QPointF node_pos; XMLAttributeLoop(reader, attr) { if (attr.name() == QStringLiteral("id")) { node_id = attr.value().toString(); } else if (attr.name() == QStringLiteral("ptr")) { node_ptr = attr.value().toULongLong(); + } else if (attr.name() == QStringLiteral("pos")) { + QStringList pos = attr.value().toString().split(':'); + + // Protection in case this file has been messed with + if (pos.size() == 2) { + node_pos.setX(pos.at(0).toDouble()); + node_pos.setY(pos.at(1).toDouble()); + } } } @@ -47,6 +57,7 @@ Node* XMLLoadNode(QXmlStreamReader* reader) { if (node) { node->setProperty("xml_ptr", node_ptr); + node->SetPosition(node_pos); } else { qWarning() << "Failed to load" << node_id << "- no node with that ID is installed"; } diff --git a/app/node/node.cpp b/app/node/node.cpp index 8f42b9dc2..cea3c1417 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -103,6 +103,10 @@ void Node::Save(QXmlStreamWriter *writer, const QString &custom_name) const writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(this))); + writer->writeAttribute(QStringLiteral("pos"), + QStringLiteral("%1:%2").arg(QString::number(GetPosition().x()), + QString::number(GetPosition().y()))); + foreach (NodeParam* param, parameters()) { param->Save(writer); } @@ -550,7 +554,7 @@ NodeValue Node::InputValueFromTable(NodeInput *input, NodeValueDatabase &db, boo } } -const QPointF &Node::GetPosition() +const QPointF &Node::GetPosition() const { return position_; } diff --git a/app/node/node.h b/app/node/node.h index 656713224..4c4826b07 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -342,7 +342,7 @@ public: virtual NodeValue InputValueFromTable(NodeInput* input, NodeValueDatabase &db, bool take) const; - const QPointF& GetPosition(); + const QPointF& GetPosition() const; void SetPosition(const QPointF& pos); diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 014105560..f3d0d3b9d 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -176,7 +176,7 @@ void NodeViewItem::SetNode(Node *n) } } - setPos(node_->GetPosition()); + SetNodePosition(node_->GetPosition()); } update(); diff --git a/app/widget/nodeview/nodeviewscene.cpp b/app/widget/nodeview/nodeviewscene.cpp index bcadfd733..628e72517 100644 --- a/app/widget/nodeview/nodeviewscene.cpp +++ b/app/widget/nodeview/nodeviewscene.cpp @@ -159,8 +159,8 @@ void NodeViewScene::AddNode(Node* node) { NodeViewItem* item = new NodeViewItem(); - item->SetNode(node); item->SetFlowDirection(direction_); + item->SetNode(node); addItem(item); item_map_.insert(node, item); From 06ab2d3a0b3e42f22e6282edd9855dbf9aa12a45 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 28 Apr 2020 04:00:27 +1000 Subject: [PATCH 10/69] fixed namespace issue that broke loading projects from file --- app/core.cpp | 2 +- app/node/input.h | 2 +- app/node/node.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index 9dea0b981..15699cb5c 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -536,7 +536,7 @@ void Core::DeclareTypesForQt() qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); - qRegisterMetaType(); + qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); } diff --git a/app/node/input.h b/app/node/input.h index c672907ee..f14521bc1 100644 --- a/app/node/input.h +++ b/app/node/input.h @@ -279,7 +279,7 @@ public: QList GetImmediateDependencies() const; signals: - void ValueChanged(const TimeRange& range); + void ValueChanged(const OLIVE_NAMESPACE::TimeRange& range); void KeyframeEnableChanged(bool); diff --git a/app/node/node.h b/app/node/node.h index 4c4826b07..0aec6ecb1 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -425,7 +425,7 @@ private: QPointF position_; private slots: - void InputChanged(const TimeRange &range); + void InputChanged(const OLIVE_NAMESPACE::TimeRange &range); void InputConnectionChanged(NodeEdgePtr edge); From 64ce4642c650a75351ffdf6524b53b243a1a3945 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 28 Apr 2020 15:26:22 +1000 Subject: [PATCH 11/69] linux: fixes bug where layout was wonky on startup for some systems --- app/window/mainwindow/mainwindow.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 1a83578e5..067e2603b 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -33,18 +33,21 @@ OLIVE_NAMESPACE_ENTER MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { -#ifdef Q_OS_WINDOWS - // Qt on Windows has a bug that "de-maximizes" the window when widgets are added, resizing the window beforehand - // works around that issue and we just set it to whatever size is available + // Resizes main window to desktop geometry on startup. Fixes the following issues: + // * Qt on Windows has a bug that "de-maximizes" the window when widgets are added, resizing the + // window beforehand works around that issue and we just set it to whatever size is available. + // * On Linux, it seems the window starts off at a vastly different size and then maximizes + // which throws off the proportions and makes the resulting layout wonky. resize(qApp->desktop()->availableGeometry(this).size()); +#ifdef Q_OS_WINDOWS // Set up taskbar button progress bar (used for some modal tasks like exporting) taskbar_btn_id_ = RegisterWindowMessage("TaskbarButtonCreated"); taskbar_interface_ = nullptr; #endif - // Create empty central widget - we don't actually want a central widget but some of Qt's docking/undocking fails - // without it + // Create empty central widget - we don't actually want a central widget (so we set its maximum + // size to 0,0) but some of Qt's docking/undocking fails without it QWidget* centralWidget = new QWidget(this); centralWidget->setMaximumSize(QSize(0, 0)); setCentralWidget(centralWidget); From 1b2c68c92764efd9cdac0ad861c30dffdeca6f61 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 28 Apr 2020 15:39:31 +1000 Subject: [PATCH 12/69] preferencesaudiotab: fixed bug that didn't disable UI when refreshing audio devices --- .../preferences/tabs/preferencesaudiotab.cpp | 20 +++++++++++++------ .../preferences/tabs/preferencesaudiotab.h | 8 ++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/app/dialog/preferences/tabs/preferencesaudiotab.cpp b/app/dialog/preferences/tabs/preferencesaudiotab.cpp index af70cb1d2..dfe3536a7 100644 --- a/app/dialog/preferences/tabs/preferencesaudiotab.cpp +++ b/app/dialog/preferences/tabs/preferencesaudiotab.cpp @@ -22,7 +22,6 @@ #include #include -#include #include "audio/audiomanager.h" #include "config/config.h" @@ -80,14 +79,14 @@ PreferencesAudioTab::PreferencesAudioTab() row++; - QPushButton* refresh_devices = new QPushButton(tr("Refresh Devices")); - audio_tab_layout->addWidget(refresh_devices, row, 1); + refresh_devices_btn_ = new QPushButton(tr("Refresh Devices")); + audio_tab_layout->addWidget(refresh_devices_btn_, row, 1); row++; RetrieveDeviceLists(); - connect(refresh_devices, &QPushButton::clicked, this, &PreferencesAudioTab::RefreshDevices); + connect(refresh_devices_btn_, &QPushButton::clicked, this, &PreferencesAudioTab::RefreshDevices); connect(AudioManager::instance(), &AudioManager::OutputListReady, this, &PreferencesAudioTab::RetrieveOutputList); connect(AudioManager::instance(), &AudioManager::InputListReady, this, &PreferencesAudioTab::RetrieveInputList); } @@ -151,6 +150,8 @@ void PreferencesAudioTab::RetrieveOutputList() AudioManager::instance()->IsRefreshingOutputs(), AudioManager::instance()->ListOutputDevices(), Config::Current()["AudioOutput"].toString()); + + UpdateRefreshButtonEnabled(); } void PreferencesAudioTab::RetrieveInputList() @@ -159,6 +160,8 @@ void PreferencesAudioTab::RetrieveInputList() AudioManager::instance()->IsRefreshingInputs(), AudioManager::instance()->ListInputDevices(), Config::Current()["AudioInput"].toString()); + + UpdateRefreshButtonEnabled(); } void PreferencesAudioTab::RetrieveDeviceLists() @@ -167,17 +170,22 @@ void PreferencesAudioTab::RetrieveDeviceLists() RetrieveInputList(); } +void PreferencesAudioTab::UpdateRefreshButtonEnabled() +{ + refresh_devices_btn_->setEnabled(audio_output_devices_->isEnabled() + && audio_input_devices_->isEnabled()); +} + void PreferencesAudioTab::PopulateComboBox(QComboBox *cb, bool still_refreshing, const QList &list, const QString& preferred) { cb->clear(); - cb->setEnabled(still_refreshing); + cb->setEnabled(!still_refreshing); if (still_refreshing) { cb->addItem(tr("Please wait...")); } else { bool found_preferred_device = false; - cb->setEnabled(true); // Add null default item cb->addItem(tr("Default"), QVariant()); diff --git a/app/dialog/preferences/tabs/preferencesaudiotab.h b/app/dialog/preferences/tabs/preferencesaudiotab.h index f91445774..dcf9252b7 100644 --- a/app/dialog/preferences/tabs/preferencesaudiotab.h +++ b/app/dialog/preferences/tabs/preferencesaudiotab.h @@ -23,6 +23,7 @@ #include #include +#include #include "preferencestab.h" @@ -57,6 +58,11 @@ private: */ QComboBox* recording_combobox_; + /** + * @brief Button that triggers a refresh of the available audio devices + */ + QPushButton* refresh_devices_btn_; + private slots: void RefreshDevices(); @@ -67,6 +73,8 @@ private slots: private: void RetrieveDeviceLists(); + void UpdateRefreshButtonEnabled(); + static void PopulateComboBox(QComboBox* cb, bool still_refreshing, const QList& list, const QString &preferred); }; From 5c088b9f1a5efb2cadf3ee74069c13a8c7cde169 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 28 Apr 2020 16:04:50 +1000 Subject: [PATCH 13/69] mainwindow: show task manager if status bar is double clicked Minor UI improvement. --- app/window/mainwindow/mainstatusbar.cpp | 7 +++++++ app/window/mainwindow/mainstatusbar.h | 7 +++++++ app/window/mainwindow/mainwindow.cpp | 7 +++++++ app/window/mainwindow/mainwindow.h | 2 ++ 4 files changed, 23 insertions(+) diff --git a/app/window/mainwindow/mainstatusbar.cpp b/app/window/mainwindow/mainstatusbar.cpp index 36a96d47b..568718e44 100644 --- a/app/window/mainwindow/mainstatusbar.cpp +++ b/app/window/mainwindow/mainstatusbar.cpp @@ -76,4 +76,11 @@ void MainStatusBar::UpdateStatus() } } +void MainStatusBar::mouseDoubleClickEvent(QMouseEvent* e) +{ + QStatusBar::mouseDoubleClickEvent(e); + + emit DoubleClicked(); +} + OLIVE_NAMESPACE_EXIT diff --git a/app/window/mainwindow/mainstatusbar.h b/app/window/mainwindow/mainstatusbar.h index 1a704860e..96e8dee5e 100644 --- a/app/window/mainwindow/mainstatusbar.h +++ b/app/window/mainwindow/mainstatusbar.h @@ -33,11 +33,18 @@ OLIVE_NAMESPACE_ENTER */ class MainStatusBar : public QStatusBar { + Q_OBJECT public: MainStatusBar(QWidget* parent = nullptr); void ConnectTaskManager(TaskManager* manager); +signals: + void DoubleClicked(); + +protected: + virtual void mouseDoubleClickEvent(QMouseEvent* e) override; + private slots: void UpdateStatus(); diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 067e2603b..5b19b855c 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -65,6 +65,7 @@ MainWindow::MainWindow(QWidget *parent) : // Create and set status bar MainStatusBar* status_bar = new MainStatusBar(this); status_bar->ConnectTaskManager(TaskManager::instance()); + connect(status_bar, &MainStatusBar::DoubleClicked, this, &MainWindow::StatusBarDoubleClicked); setStatusBar(status_bar); // Create standard panels @@ -391,6 +392,12 @@ bool MainWindow::nativeEvent(const QByteArray &eventType, void *message, long *r } #endif +void MainWindow::StatusBarDoubleClicked() +{ + task_man_panel_->show(); + task_man_panel_->raise(); +} + void MainWindow::UpdateTitle() { if (Core::instance()->GetActiveProject()) { diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index 4f2d52c6e..643e72133 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -147,6 +147,8 @@ private slots: void LoadLayoutInternal(QXmlStreamReader* reader, XMLNodeData *xml_data); + void StatusBarDoubleClicked(); + }; OLIVE_NAMESPACE_EXIT From 4906bb7d537e39835c11f1c402a547525d078bb3 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 28 Apr 2020 17:08:52 +1000 Subject: [PATCH 14/69] scopes: don't use shared context OpenGL shared contexts (through Qt at least) seem to be buggy. Scopes have been shifted to a non-shared approach as a result. --- app/main.cpp | 3 - app/panel/panelmanager.h | 4 + app/panel/scope/scope.cpp | 11 +-- app/panel/scope/scope.h | 4 - app/panel/viewer/viewerbase.cpp | 23 +----- app/panel/viewer/viewerbase.h | 6 -- app/render/backend/opengl/openglproxy.cpp | 2 +- app/render/backend/opengl/opengltexture.cpp | 15 ++++ app/render/backend/opengl/opengltexture.h | 3 + .../backend/opengl/opengltexturecache.cpp | 10 +++ .../backend/opengl/opengltexturecache.h | 2 + app/widget/scope/waveform/waveform.cpp | 51 ++++++++++--- app/widget/scope/waveform/waveform.h | 8 +- app/widget/viewer/viewer.cpp | 7 -- app/widget/viewer/viewer.h | 15 ---- app/widget/viewer/viewerdisplay.cpp | 74 ++----------------- app/widget/viewer/viewerdisplay.h | 40 ---------- 17 files changed, 90 insertions(+), 188 deletions(-) diff --git a/app/main.cpp b/app/main.cpp index 859ed0b76..1b4b6e3a7 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -50,9 +50,6 @@ int main(int argc, char *argv[]) { format.setProfile(QSurfaceFormat::CoreProfile); QSurfaceFormat::setDefaultFormat(format); - // Try to share OpenGL contexts - QApplication::setAttribute(Qt::AA_ShareOpenGLContexts); - // Create application instance QApplication a(argc, argv); diff --git a/app/panel/panelmanager.h b/app/panel/panelmanager.h index 7acba73e3..9e773ec92 100644 --- a/app/panel/panelmanager.h +++ b/app/panel/panelmanager.h @@ -177,6 +177,10 @@ T *PanelManager::CreatePanel(QWidget *parent) panel->SetMovementLocked(locked_); + // Sane default for panel geometry + panel->resize(parent->size() / 3); + panel->move(panel->mapFromGlobal(parent->mapToGlobal(parent->pos()))); + // Connect destroy signal so we can remove it from focus history connect(panel, &PanelWidget::destroyed, this, &PanelManager::PanelDestroyed); diff --git a/app/panel/scope/scope.cpp b/app/panel/scope/scope.cpp index 222fcec64..5daa7e9e0 100644 --- a/app/panel/scope/scope.cpp +++ b/app/panel/scope/scope.cpp @@ -84,19 +84,10 @@ QString ScopePanel::TypeToName(ScopePanel::Type t) return QString(); } -void ScopePanel::SetDisplayReferredTexture(OpenGLTexture *texture) -{ - Q_UNUSED(texture) -} - void ScopePanel::SetReferenceBuffer(Frame *frame) { histogram_->SetBuffer(frame); -} - -void ScopePanel::SetReferenceTexture(OpenGLTexture *texture) -{ - waveform_view_->SetTexture(texture); + waveform_view_->SetBuffer(frame); } void ScopePanel::SetColorManager(ColorManager *manager) diff --git a/app/panel/scope/scope.h b/app/panel/scope/scope.h index f44e37aa6..3e771af69 100644 --- a/app/panel/scope/scope.h +++ b/app/panel/scope/scope.h @@ -50,12 +50,8 @@ public: static QString TypeToName(Type t); public slots: - void SetDisplayReferredTexture(OpenGLTexture* texture); - void SetReferenceBuffer(Frame* frame); - void SetReferenceTexture(OpenGLTexture* texture); - void SetColorManager(ColorManager* manager); protected: diff --git a/app/panel/viewer/viewerbase.cpp b/app/panel/viewer/viewerbase.cpp index a8d4d55b8..3cab17f98 100644 --- a/app/panel/viewer/viewerbase.cpp +++ b/app/panel/viewer/viewerbase.cpp @@ -25,8 +25,7 @@ OLIVE_NAMESPACE_ENTER ViewerPanelBase::ViewerPanelBase(const QString& object_name, QWidget *parent) : - TimeBasedPanel(object_name, parent), - scope_panel_count_(0) + TimeBasedPanel(object_name, parent) { } @@ -98,33 +97,13 @@ void ViewerPanelBase::CreateScopePanel(ScopePanel::Type type) p->SetType(type); - // If the scope closes, reduce the count (we do this because if no scopes are open, we can optimize the viewer slightly) - connect(p, &ScopePanel::CloseRequested, this, &ViewerPanelBase::ScopePanelClosed); - // Connect viewer widget texture drawing to scope panel - connect(vw, &ViewerWidget::DrewManagedTexture, p, &ScopePanel::SetDisplayReferredTexture); connect(vw, &ViewerWidget::LoadedBuffer, p, &ScopePanel::SetReferenceBuffer); - connect(vw, &ViewerWidget::LoadedTexture, p, &ScopePanel::SetReferenceTexture); connect(vw, &ViewerWidget::ColorManagerChanged, p, &ScopePanel::SetColorManager); p->SetColorManager(vw->color_manager()); - if (!scope_panel_count_) { - vw->SetEmitDrewManagedTextureEnabled(true); - } - - scope_panel_count_++; - vw->ForceUpdate(); } -void ViewerPanelBase::ScopePanelClosed() -{ - scope_panel_count_--; - - if (!scope_panel_count_) { - static_cast(GetTimeBasedWidget())->SetEmitDrewManagedTextureEnabled(false); - } -} - OLIVE_NAMESPACE_EXIT diff --git a/app/panel/viewer/viewerbase.h b/app/panel/viewer/viewerbase.h index 7d551141d..a0acd2d29 100644 --- a/app/panel/viewer/viewerbase.h +++ b/app/panel/viewer/viewerbase.h @@ -59,12 +59,6 @@ public: protected: void CreateScopePanel(ScopePanel::Type type); -private: - int scope_panel_count_; - -private slots: - void ScopePanelClosed(); - }; OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/opengl/openglproxy.cpp b/app/render/backend/opengl/openglproxy.cpp index d1f4f7ddd..194ac6dc4 100644 --- a/app/render/backend/opengl/openglproxy.cpp +++ b/app/render/backend/opengl/openglproxy.cpp @@ -132,7 +132,7 @@ void OpenGLProxy::FrameToValue(FramePtr frame, StreamPtr stream, NodeValueTable* VideoRenderingParams footage_params(frame->width(), frame->height(), frame->format()); - footage_tex_ref = texture_cache_.Get(ctx_, footage_params, frame->data(), frame->linesize_pixels()); + footage_tex_ref = texture_cache_.Get(ctx_, footage_params, frame); if (ocio_method == ColorManager::kOCIOFast) { if (!color_processor->IsEnabled()) { diff --git a/app/render/backend/opengl/opengltexture.cpp b/app/render/backend/opengl/opengltexture.cpp index c0c1621ea..d5013c10f 100644 --- a/app/render/backend/opengl/opengltexture.cpp +++ b/app/render/backend/opengl/opengltexture.cpp @@ -74,6 +74,11 @@ void OpenGLTexture::Create(QOpenGLContext *ctx, int width, int height, const Pix } void OpenGLTexture::Create(QOpenGLContext *ctx, FramePtr frame) +{ + Create(ctx, frame.get()); +} + +void OpenGLTexture::Create(QOpenGLContext *ctx, Frame *frame) { Create(ctx, frame->width(), frame->height(), frame->format(), frame->data(), frame->linesize_pixels()); } @@ -120,6 +125,16 @@ const GLuint &OpenGLTexture::texture() const return texture_; } +void OpenGLTexture::Upload(FramePtr frame) +{ + Upload(frame.get()); +} + +void OpenGLTexture::Upload(Frame *frame) +{ + Upload(frame->data(), frame->linesize_pixels()); +} + void OpenGLTexture::Upload(const void *data, int linesize) { if (!IsCreated()) { diff --git a/app/render/backend/opengl/opengltexture.h b/app/render/backend/opengl/opengltexture.h index 941e2aae8..70fa653f7 100644 --- a/app/render/backend/opengl/opengltexture.h +++ b/app/render/backend/opengl/opengltexture.h @@ -44,6 +44,7 @@ public: void Create(QOpenGLContext* ctx, int width, int height, const PixelFormat::Format &format, const void *data, int linesize); void Create(QOpenGLContext* ctx, int width, int height, const PixelFormat::Format &format); void Create(QOpenGLContext* ctx, FramePtr frame); + void Create(QOpenGLContext* ctx, Frame* frame); bool IsCreated() const; @@ -59,6 +60,8 @@ public: const GLuint& texture() const; + void Upload(FramePtr frame); + void Upload(Frame* frame); void Upload(const void *data, int linesize); public slots: diff --git a/app/render/backend/opengl/opengltexturecache.cpp b/app/render/backend/opengl/opengltexturecache.cpp index 95e0638ee..a54994467 100644 --- a/app/render/backend/opengl/opengltexturecache.cpp +++ b/app/render/backend/opengl/opengltexturecache.cpp @@ -29,6 +29,16 @@ OpenGLTextureCache::~OpenGLTextureCache() } } +OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext *ctx, const VideoRenderingParams ¶ms, FramePtr frame) +{ + return Get(ctx, params, frame.get()); +} + +OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext *ctx, const VideoRenderingParams ¶ms, Frame *frame) +{ + return Get(ctx, params, frame->data(), frame->linesize_pixels()); +} + OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext* ctx, const VideoRenderingParams ¶ms, const void *data, int linesize) { OpenGLTexturePtr texture = nullptr; diff --git a/app/render/backend/opengl/opengltexturecache.h b/app/render/backend/opengl/opengltexturecache.h index 7be07bfbb..2baa87a67 100644 --- a/app/render/backend/opengl/opengltexturecache.h +++ b/app/render/backend/opengl/opengltexturecache.h @@ -57,6 +57,8 @@ public: DISABLE_COPY_MOVE(OpenGLTextureCache) + ReferencePtr Get(QOpenGLContext *ctx, const VideoRenderingParams& params, FramePtr frame); + ReferencePtr Get(QOpenGLContext *ctx, const VideoRenderingParams& params, Frame* frame); ReferencePtr Get(QOpenGLContext *ctx, const VideoRenderingParams& params, const void *data, int linesize); ReferencePtr Get(QOpenGLContext *ctx, const VideoRenderingParams& params); diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp index 8a30dac66..64c71004d 100644 --- a/app/widget/scope/waveform/waveform.cpp +++ b/app/widget/scope/waveform/waveform.cpp @@ -28,27 +28,35 @@ OLIVE_NAMESPACE_ENTER WaveformScope::WaveformScope(QWidget* parent) : ManagedDisplayWidget(parent), - texture_(nullptr) + buffer_(nullptr) { EnableDefaultContextMenu(); } -void WaveformScope::SetTexture(OpenGLTexture *texture) +void WaveformScope::SetBuffer(Frame *frame) { - texture_ = texture; + buffer_ = frame; - update(); + UploadTextureFromBuffer(); } void WaveformScope::initializeGL() { ManagedDisplayWidget::initializeGL(); + makeCurrent(); pipeline_ = OpenGLShader::Create(); pipeline_->create(); pipeline_->addShaderFromSourceCode(QOpenGLShader::Vertex, OpenGLShader::CodeDefaultVertex()); pipeline_->addShaderFromSourceCode(QOpenGLShader::Fragment, Node::ReadFileAsString(":/shaders/rgbwaveform.frag")); pipeline_->link(); + doneCurrent(); + + connect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &WaveformScope::CleanUp, Qt::DirectConnection); + + if (buffer_) { + UploadTextureFromBuffer(); + } } void WaveformScope::paintGL() @@ -56,12 +64,12 @@ void WaveformScope::paintGL() context()->functions()->glClearColor(0, 0, 0, 0); context()->functions()->glClear(GL_COLOR_BUFFER_BIT); - if (!pipeline_ || !texture_) { + if (!pipeline_ || !texture_.IsCreated()) { return; } pipeline_->bind(); - pipeline_->setUniformValue("ove_resolution", texture_->width(), texture_->height()); + pipeline_->setUniformValue("ove_resolution", texture_.width(), texture_.height()); pipeline_->setUniformValue("ove_viewport", width(), height()); // The general size of a pixel @@ -69,17 +77,42 @@ void WaveformScope::paintGL() pipeline_->release(); - texture_->Bind(); + texture_.Bind(); OpenGLRenderFunctions::Blit(pipeline_); - texture_->Release(); + texture_.Release(); +} + +void WaveformScope::UploadTextureFromBuffer() +{ + makeCurrent(); + + if (!texture_.IsCreated() + || texture_.width() != buffer_->width() + || texture_.height() != buffer_->height() + || texture_.format() != buffer_->format()) { + texture_.Destroy(); + texture_.Create(context(), buffer_); + } else { + texture_.Upload(buffer_); + } + + doneCurrent(); + + update(); } void WaveformScope::CleanUp() { + qDebug() << "Cleaned up..."; + + makeCurrent(); + pipeline_ = nullptr; - texture_ = nullptr; + texture_.Destroy(); + + doneCurrent(); } OLIVE_NAMESPACE_EXIT diff --git a/app/widget/scope/waveform/waveform.h b/app/widget/scope/waveform/waveform.h index f9f930c5c..efb7a08b4 100644 --- a/app/widget/scope/waveform/waveform.h +++ b/app/widget/scope/waveform/waveform.h @@ -36,7 +36,7 @@ public: WaveformScope(QWidget* parent = nullptr); public slots: - void SetTexture(OpenGLTexture* texture); + void SetBuffer(Frame* frame); protected: virtual void initializeGL() override; @@ -44,9 +44,13 @@ protected: virtual void paintGL() override; private: + void UploadTextureFromBuffer(); + OpenGLShaderPtr pipeline_; - OpenGLTexture* texture_; + OpenGLTexture texture_; + + Frame* buffer_; private slots: void CleanUp(); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 4107ceab1..d4ebb84f0 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -66,8 +66,6 @@ ViewerWidget::ViewerWidget(QWidget *parent) : connect(main_widget, &ViewerDisplayWidget::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu); connect(main_widget, &ViewerDisplayWidget::CursorColor, this, &ViewerWidget::CursorColor); connect(main_widget, &ViewerDisplayWidget::LoadedBuffer, this, &ViewerWidget::LoadedBuffer); - connect(main_widget, &ViewerDisplayWidget::LoadedTexture, this, &ViewerWidget::LoadedTexture); - connect(main_widget, &ViewerDisplayWidget::DrewManagedTexture, this, &ViewerWidget::DrewManagedTexture); connect(main_widget, &ViewerDisplayWidget::ColorProcessorChanged, this, &ViewerWidget::ColorProcessorChanged); connect(main_widget, &ViewerDisplayWidget::ColorManagerChanged, this, &ViewerWidget::ColorManagerChanged); connect(sizer_, &ViewerSizer::RequestMatrix, main_widget, &ViewerDisplayWidget::SetMatrix); @@ -745,11 +743,6 @@ void ViewerWidget::SetSignalCursorColorEnabled(bool e) } } -void ViewerWidget::SetEmitDrewManagedTextureEnabled(bool e) -{ - main_gl_widget()->SetEmitDrewManagedTextureEnabled(e); -} - void ViewerWidget::TimebaseChangedEvent(const rational &timebase) { TimeBasedWidget::TimebaseChangedEvent(timebase); diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 8cf6bb24c..57b6e2970 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -107,11 +107,6 @@ public slots: */ void SetSignalCursorColorEnabled(bool e); - /** - * @brief Wrapper for ViewerGLWidget::SetEmitDrewManagedTextureEnabled() - */ - void SetEmitDrewManagedTextureEnabled(bool e); - signals: /** * @brief Wrapper for ViewerGLWidget::CursorColor() @@ -123,16 +118,6 @@ signals: */ void LoadedBuffer(Frame* load_buffer); - /** - * @brief Wrapper for ViewerGLWidget::LoadedTexture() - */ - void LoadedTexture(OpenGLTexture* texture); - - /** - * @brief Wrapper for ViewerGLWidget::DrewManagedTexture() - */ - void DrewManagedTexture(OpenGLTexture* texture); - /** * @brief Request a scope panel * diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 640539a61..779f65237 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -42,10 +42,8 @@ bool ViewerDisplayWidget::nouveau_check_done_ = false; ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) : ManagedDisplayWidget(parent), - managed_copy_pipeline_(nullptr), has_image_(false), - signal_cursor_color_(false), - enable_display_referred_signal_(false) + signal_cursor_color_(false) { } @@ -91,14 +89,12 @@ void ViewerDisplayWidget::SetImage(const QString &fn) input->read_image(input->spec().format, load_buffer_.data(), OIIO::AutoStride, load_buffer_.linesize_bytes()); input->close(); - emit LoadedBuffer(&load_buffer_); - - texture_.Upload(load_buffer_.data(), load_buffer_.linesize_pixels()); - - emit LoadedTexture(&texture_); + texture_.Upload(&load_buffer_); doneCurrent(); + emit LoadedBuffer(&load_buffer_); + has_image_ = true; #if OIIO_VERSION < 10903 @@ -138,7 +134,7 @@ void ViewerDisplayWidget::SetImageFromLoadBuffer(Frame *in_buffer) || texture_.format() != in_buffer->format()) { texture_.Create(context(), in_buffer->width(), in_buffer->height(), in_buffer->format(), in_buffer->data(), load_buffer_.linesize_pixels()); } else { - texture_.Upload(in_buffer->data(), load_buffer_.linesize_pixels()); + texture_.Upload(in_buffer); } doneCurrent(); @@ -147,18 +143,6 @@ void ViewerDisplayWidget::SetImageFromLoadBuffer(Frame *in_buffer) update(); } -void ViewerDisplayWidget::SetEmitDrewManagedTextureEnabled(bool e) -{ - enable_display_referred_signal_ = e; - - if (!enable_display_referred_signal_) { - // Destroy the texture now - managed_texture_.Destroy(); - managed_copy_pipeline_ = nullptr; - framebuffer_.Destroy(); - } -} - void ViewerDisplayWidget::ConnectSibling(ViewerDisplayWidget *sibling) { connect(this, &ViewerDisplayWidget::LoadedBuffer, sibling, &ViewerDisplayWidget::SetImageFromLoadBuffer, Qt::QueuedConnection); @@ -243,33 +227,6 @@ void ViewerDisplayWidget::paintGL() // We only draw if we have a pipeline if (has_image_ && color_service() && texture_.IsCreated()) { - // If we're distributing our display-referred final buffer, we'll have to make a copy of it - if (enable_display_referred_signal_) { - - if (!managed_texture_.IsCreated() - || managed_texture_.width() != texture_.width() - || managed_texture_.height() != texture_.height() - || managed_texture_.format() != texture_.format()) { - managed_texture_.Destroy(); - - managed_texture_.Create(context(), texture_.width(), texture_.height(), texture_.format()); - } - - if (!managed_copy_pipeline_) { - managed_copy_pipeline_ = OpenGLShader::CreateDefault(); - } - - if (!framebuffer_.IsCreated()) { - framebuffer_.Create(context()); - } - - framebuffer_.Attach(&managed_texture_); - framebuffer_.Bind(); - - f->glViewport(0, 0, managed_texture_.width(), managed_texture_.height()); - - } - // Bind retrieved texture f->glBindTexture(GL_TEXTURE_2D, texture_.texture()); @@ -279,24 +236,6 @@ void ViewerDisplayWidget::paintGL() // Release retrieved texture f->glBindTexture(GL_TEXTURE_2D, 0); - if (enable_display_referred_signal_) { - - framebuffer_.Release(); - framebuffer_.Detach(); - - emit DrewManagedTexture(&managed_texture_); - - // Bind retrieved texture - managed_texture_.Bind(); - - f->glViewport(0, 0, width(), height()); - - OpenGLRenderFunctions::Blit(managed_copy_pipeline_); - - // Bind retrieved texture - managed_texture_.Release(); - - } } // Draw action/title safe areas @@ -348,10 +287,7 @@ void ViewerDisplayWidget::ContextCleanup() { makeCurrent(); - managed_copy_pipeline_ = nullptr; texture_.Destroy(); - managed_texture_.Destroy(); - framebuffer_.Destroy(); doneCurrent(); } diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index 6289433a4..43e42eed2 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -101,15 +101,6 @@ public slots: */ void SetImageFromLoadBuffer(Frame* in_buffer); - /** - * @brief Enables or disables DrewManagedTexture() - * - * To emit a display referred texture, it needs to be copied after the color transform is complete. This naturally - * adds extra GPU cycles that are wasted if there's nothing receiving the signal. Therefore, the signal is disabled - * by default. - */ - void SetEmitDrewManagedTextureEnabled(bool e); - signals: /** * @brief Signal emitted when the user starts dragging from the viewer @@ -130,18 +121,6 @@ signals: */ void LoadedBuffer(Frame* load_buffer); - /** - * @brief Signal emitted when a buffer is loaded into a texture - * - * This texture will be the direct output of the renderer in reference space in GPU VRAM. - */ - void LoadedTexture(OpenGLTexture* texture); - - /** - * @brief Emitted when the a texture has been transformed to display - */ - void DrewManagedTexture(OpenGLTexture* texture); - protected: /** * @brief Override the mouse press event simply to emit the DragStarted() signal @@ -173,23 +152,6 @@ private: */ OpenGLTexture texture_; - /** - * @brief Internal framebuffer used to draw to managed_texture_ - */ - OpenGLFramebuffer framebuffer_; - - /** - * @brief Internal referenceto the OpenGL texture that's been managed - * - * Kept so that scopes can use the display-referred buffer without having to transform again. - */ - OpenGLTexture managed_texture_; - - /** - * @brief Pipeline used to draw to managed_texture_ - */ - OpenGLShaderPtr managed_copy_pipeline_; - /** * @brief Drawing matrix (defaults to identity) */ @@ -210,8 +172,6 @@ private: ViewerSafeMarginInfo safe_margin_; - bool enable_display_referred_signal_; - private slots: /** * @brief Slot to connect just before the OpenGL context is destroyed to clean up resources From ad419754a951028d5ec4708d4f3f5ec89d6d5c1b Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 28 Apr 2020 17:40:22 +1000 Subject: [PATCH 15/69] waveformscope: fixed double free on close --- app/panel/panelmanager.cpp | 2 ++ app/widget/scope/waveform/waveform.cpp | 19 ++++++++++++++----- app/widget/scope/waveform/waveform.h | 2 ++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/app/panel/panelmanager.cpp b/app/panel/panelmanager.cpp index f85e6824f..252deae05 100644 --- a/app/panel/panelmanager.cpp +++ b/app/panel/panelmanager.cpp @@ -36,6 +36,8 @@ PanelManager::PanelManager(QObject *parent) : void PanelManager::DeleteAllPanels() { foreach (PanelWidget* panel, focus_history_) { + // We don't need to run this signal anymore since we're destroying and clearing everything + disconnect(panel, &PanelWidget::destroyed, this, &PanelManager::PanelDestroyed); delete panel; } focus_history_.clear(); diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp index 64c71004d..d8f6b6e71 100644 --- a/app/widget/scope/waveform/waveform.cpp +++ b/app/widget/scope/waveform/waveform.cpp @@ -33,6 +33,15 @@ WaveformScope::WaveformScope(QWidget* parent) : EnableDefaultContextMenu(); } +WaveformScope::~WaveformScope() +{ + CleanUp(); + + if (context()) { + disconnect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &WaveformScope::CleanUp); + } +} + void WaveformScope::SetBuffer(Frame *frame) { buffer_ = frame; @@ -54,9 +63,7 @@ void WaveformScope::initializeGL() connect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &WaveformScope::CleanUp, Qt::DirectConnection); - if (buffer_) { - UploadTextureFromBuffer(); - } + UploadTextureFromBuffer(); } void WaveformScope::paintGL() @@ -86,6 +93,10 @@ void WaveformScope::paintGL() void WaveformScope::UploadTextureFromBuffer() { + if (!buffer_) { + return; + } + makeCurrent(); if (!texture_.IsCreated() @@ -105,8 +116,6 @@ void WaveformScope::UploadTextureFromBuffer() void WaveformScope::CleanUp() { - qDebug() << "Cleaned up..."; - makeCurrent(); pipeline_ = nullptr; diff --git a/app/widget/scope/waveform/waveform.h b/app/widget/scope/waveform/waveform.h index efb7a08b4..dd9d40452 100644 --- a/app/widget/scope/waveform/waveform.h +++ b/app/widget/scope/waveform/waveform.h @@ -35,6 +35,8 @@ class WaveformScope : public ManagedDisplayWidget public: WaveformScope(QWidget* parent = nullptr); + virtual ~WaveformScope() override; + public slots: void SetBuffer(Frame* frame); From df7f0040ec55bedd82bee85d10378949e1f98b16 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 28 Apr 2020 21:08:36 +1000 Subject: [PATCH 16/69] mac: implemented support for retina displays --- app/main.cpp | 3 +++ cmake/MacOSXBundleInfo.plist.in | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 cmake/MacOSXBundleInfo.plist.in diff --git a/app/main.cpp b/app/main.cpp index 1b4b6e3a7..8713f4d96 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -50,6 +50,9 @@ int main(int argc, char *argv[]) { format.setProfile(QSurfaceFormat::CoreProfile); QSurfaceFormat::setDefaultFormat(format); + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); + // Create application instance QApplication a(argc, argv); diff --git a/cmake/MacOSXBundleInfo.plist.in b/cmake/MacOSXBundleInfo.plist.in new file mode 100644 index 000000000..ae08eb0d9 --- /dev/null +++ b/cmake/MacOSXBundleInfo.plist.in @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSignature + ???? + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + NSPrincipalClass + NSApplication + + From 61dd75a2ed0cff39e0f007feb9736848aaf41504 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 28 Apr 2020 23:55:40 +1000 Subject: [PATCH 17/69] scopes: only update if visible Updating scopes can require heavy processing so we limit updates to only when the scope is visible --- app/widget/scope/histogram/histogram.cpp | 11 ++++++++++- app/widget/scope/histogram/histogram.h | 2 ++ app/widget/scope/waveform/waveform.cpp | 9 ++++++++- app/widget/scope/waveform/waveform.h | 2 ++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/app/widget/scope/histogram/histogram.cpp b/app/widget/scope/histogram/histogram.cpp index ab7cb66e1..b9028f85a 100644 --- a/app/widget/scope/histogram/histogram.cpp +++ b/app/widget/scope/histogram/histogram.cpp @@ -49,7 +49,9 @@ void HistogramScope::SetBuffer(Frame* frame) { buffer_ = frame; - StartUpdate(); + if (isVisible()) { + StartUpdate(); + } } void HistogramScope::FinishedProcessing(QVector red, QVector green, QVector blue) @@ -111,6 +113,13 @@ void HistogramScope::StartUpdate() } } +void HistogramScope::showEvent(QShowEvent* e) +{ + ManagedDisplayWidget::showEvent(e); + + StartUpdate(); +} + HistogramScopeWorker::HistogramScopeWorker() : cancelled_(false) { diff --git a/app/widget/scope/histogram/histogram.h b/app/widget/scope/histogram/histogram.h index ea5175726..3efad3919 100644 --- a/app/widget/scope/histogram/histogram.h +++ b/app/widget/scope/histogram/histogram.h @@ -78,6 +78,8 @@ protected: virtual void ColorProcessorChangedEvent() override; + virtual void showEvent(QShowEvent* e) override; + private: void StartUpdate(); diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp index d8f6b6e71..21ef63dc3 100644 --- a/app/widget/scope/waveform/waveform.cpp +++ b/app/widget/scope/waveform/waveform.cpp @@ -49,6 +49,13 @@ void WaveformScope::SetBuffer(Frame *frame) UploadTextureFromBuffer(); } +void WaveformScope::showEvent(QShowEvent* e) +{ + ManagedDisplayWidget::showEvent(e); + + UploadTextureFromBuffer(); +} + void WaveformScope::initializeGL() { ManagedDisplayWidget::initializeGL(); @@ -93,7 +100,7 @@ void WaveformScope::paintGL() void WaveformScope::UploadTextureFromBuffer() { - if (!buffer_) { + if (!buffer_ || !isVisible()) { return; } diff --git a/app/widget/scope/waveform/waveform.h b/app/widget/scope/waveform/waveform.h index dd9d40452..6d2e808f5 100644 --- a/app/widget/scope/waveform/waveform.h +++ b/app/widget/scope/waveform/waveform.h @@ -45,6 +45,8 @@ protected: virtual void paintGL() override; + virtual void showEvent(QShowEvent* e) override; + private: void UploadTextureFromBuffer(); From a412e26d66f1a2b88b60edaa7c04027ae56e3cb3 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 29 Apr 2020 00:01:50 +1000 Subject: [PATCH 18/69] panels: fixed bug that added panel to list twice and led to double-free --- app/panel/panelmanager.cpp | 8 +++----- app/panel/panelmanager.h | 26 +++++++++++++++++--------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/app/panel/panelmanager.cpp b/app/panel/panelmanager.cpp index 252deae05..53903f81e 100644 --- a/app/panel/panelmanager.cpp +++ b/app/panel/panelmanager.cpp @@ -35,12 +35,10 @@ PanelManager::PanelManager(QObject *parent) : void PanelManager::DeleteAllPanels() { - foreach (PanelWidget* panel, focus_history_) { - // We don't need to run this signal anymore since we're destroying and clearing everything - disconnect(panel, &PanelWidget::destroyed, this, &PanelManager::PanelDestroyed); - delete panel; - } + // Prevent any confusion regarding focus history by clearing it first + QList copy = focus_history_; focus_history_.clear(); + qDeleteAll(copy); } const QList &PanelManager::panels() diff --git a/app/panel/panelmanager.h b/app/panel/panelmanager.h index 9e773ec92..caa7bae07 100644 --- a/app/panel/panelmanager.h +++ b/app/panel/panelmanager.h @@ -175,18 +175,26 @@ T *PanelManager::CreatePanel(QWidget *parent) { T* panel = new T(parent); - panel->SetMovementLocked(locked_); - - // Sane default for panel geometry - panel->resize(parent->size() / 3); - panel->move(panel->mapFromGlobal(parent->mapToGlobal(parent->pos()))); - - // Connect destroy signal so we can remove it from focus history - connect(panel, &PanelWidget::destroyed, this, &PanelManager::PanelDestroyed); - // Add panel to the bottom of the focus history focus_history_.append(panel); + panel->SetMovementLocked(locked_); + + // Sane default for panel size + panel->resize(parent->size() / 3); + + // We're about to center the panel relative to the parent (usually the main window), but for some + // reason this requires the panel to be shown first. + panel->show(); + + // Center the panel relative to the parent + QPoint parent_center = panel->mapFromGlobal(parent->mapToGlobal(parent->rect().center())); + QPoint panel_center = panel->rect().center(); + panel->move(parent_center - panel_center); + + // Connect destroy signal so we can remove it from focus history + connect(panel, &PanelWidget::destroyed, this, &PanelManager::PanelDestroyed, Qt::DirectConnection); + return panel; } From 3070aa7e40868133407423ee3aac0c49c9db7d44 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 29 Apr 2020 00:36:55 +1000 Subject: [PATCH 19/69] waveformscope: convert buffers to DVL before drawing the waveform --- app/widget/scope/waveform/waveform.cpp | 55 ++++++++++++++++++++------ app/widget/scope/waveform/waveform.h | 5 +++ 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp index 21ef63dc3..043820eb7 100644 --- a/app/widget/scope/waveform/waveform.cpp +++ b/app/widget/scope/waveform/waveform.cpp @@ -61,11 +61,15 @@ void WaveformScope::initializeGL() ManagedDisplayWidget::initializeGL(); makeCurrent(); + pipeline_ = OpenGLShader::Create(); pipeline_->create(); pipeline_->addShaderFromSourceCode(QOpenGLShader::Vertex, OpenGLShader::CodeDefaultVertex()); pipeline_->addShaderFromSourceCode(QOpenGLShader::Fragment, Node::ReadFileAsString(":/shaders/rgbwaveform.frag")); pipeline_->link(); + + framebuffer_.Create(context()); + doneCurrent(); connect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &WaveformScope::CleanUp, Qt::DirectConnection); @@ -75,27 +79,51 @@ void WaveformScope::initializeGL() void WaveformScope::paintGL() { - context()->functions()->glClearColor(0, 0, 0, 0); - context()->functions()->glClear(GL_COLOR_BUFFER_BIT); + QOpenGLFunctions* f = context()->functions(); + + f->glClearColor(0, 0, 0, 0); + f->glClear(GL_COLOR_BUFFER_BIT); if (!pipeline_ || !texture_.IsCreated()) { return; } - pipeline_->bind(); - pipeline_->setUniformValue("ove_resolution", texture_.width(), texture_.height()); - pipeline_->setUniformValue("ove_viewport", width(), height()); + { + // Convert reference frame to display space + framebuffer_.Attach(&managed_tex_); + framebuffer_.Bind(); - // The general size of a pixel - pipeline_->setUniformValue("threshold", 2.0f / static_cast(height())); + texture_.Bind(); - pipeline_->release(); + f->glViewport(0, 0, texture_.width(), texture_.height()); - texture_.Bind(); + color_service()->ProcessOpenGL(); - OpenGLRenderFunctions::Blit(pipeline_); + texture_.Release(); - texture_.Release(); + framebuffer_.Release(); + framebuffer_.Detach(); + } + + { + // Draw waveform through shader + pipeline_->bind(); + pipeline_->setUniformValue("ove_resolution", texture_.width(), texture_.height()); + pipeline_->setUniformValue("ove_viewport", width(), height()); + + // The general size of a pixel + pipeline_->setUniformValue("threshold", 2.0f / static_cast(height())); + + pipeline_->release(); + + f->glViewport(0, 0, width(), height()); + + managed_tex_.Bind(); + + OpenGLRenderFunctions::Blit(pipeline_); + + managed_tex_.Release(); + } } void WaveformScope::UploadTextureFromBuffer() @@ -111,7 +139,10 @@ void WaveformScope::UploadTextureFromBuffer() || texture_.height() != buffer_->height() || texture_.format() != buffer_->format()) { texture_.Destroy(); + managed_tex_.Destroy(); + texture_.Create(context(), buffer_); + managed_tex_.Create(context(), buffer_->width(), buffer_->height(), buffer_->format()); } else { texture_.Upload(buffer_); } @@ -127,6 +158,8 @@ void WaveformScope::CleanUp() pipeline_ = nullptr; texture_.Destroy(); + managed_tex_.Destroy(); + framebuffer_.Destroy(); doneCurrent(); } diff --git a/app/widget/scope/waveform/waveform.h b/app/widget/scope/waveform/waveform.h index 6d2e808f5..4243a9d3a 100644 --- a/app/widget/scope/waveform/waveform.h +++ b/app/widget/scope/waveform/waveform.h @@ -23,6 +23,7 @@ #include "codec/frame.h" #include "render/backend/opengl/openglcolorprocessor.h" +#include "render/backend/opengl/openglframebuffer.h" #include "render/backend/opengl/openglshader.h" #include "render/backend/opengl/opengltexture.h" #include "widget/manageddisplay/manageddisplay.h" @@ -54,6 +55,10 @@ private: OpenGLTexture texture_; + OpenGLTexture managed_tex_; + + OpenGLFramebuffer framebuffer_; + Frame* buffer_; private slots: From 69d399105a1aeca79a496555192b7b678e9b93d7 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 29 Apr 2020 01:24:21 +1000 Subject: [PATCH 20/69] manageddisplaywidgets: fixed issue that caused buffers to be drawn on the main window --- app/widget/manageddisplay/manageddisplay.cpp | 6 ++++-- app/widget/scope/waveform/waveform.cpp | 4 ---- app/widget/viewer/manageddisplayobject.cpp | 6 ------ app/widget/viewer/manageddisplayobject.h | 11 ----------- 4 files changed, 4 insertions(+), 23 deletions(-) delete mode 100644 app/widget/viewer/manageddisplayobject.cpp delete mode 100644 app/widget/viewer/manageddisplayobject.h diff --git a/app/widget/manageddisplay/manageddisplay.cpp b/app/widget/manageddisplay/manageddisplay.cpp index 63cb2dd69..8d3d720fe 100644 --- a/app/widget/manageddisplay/manageddisplay.cpp +++ b/app/widget/manageddisplay/manageddisplay.cpp @@ -142,9 +142,13 @@ void ManagedDisplayWidget::MenuLookSelect(QAction *action) void ManagedDisplayWidget::SetColorTransform(const ColorTransform &transform) { + makeCurrent(); + color_transform_ = transform; SetupColorProcessor(); ColorProcessorChangedEvent(); + + doneCurrent(); } void ManagedDisplayWidget::initializeGL() @@ -248,9 +252,7 @@ void ManagedDisplayWidget::SetupColorProcessor() color_manager_->GetReferenceColorSpace(), color_transform_); - makeCurrent(); color_service_->Enable(context(), true); - doneCurrent(); } catch (OCIO::Exception& e) { diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp index 043820eb7..0ce873bc5 100644 --- a/app/widget/scope/waveform/waveform.cpp +++ b/app/widget/scope/waveform/waveform.cpp @@ -60,8 +60,6 @@ void WaveformScope::initializeGL() { ManagedDisplayWidget::initializeGL(); - makeCurrent(); - pipeline_ = OpenGLShader::Create(); pipeline_->create(); pipeline_->addShaderFromSourceCode(QOpenGLShader::Vertex, OpenGLShader::CodeDefaultVertex()); @@ -70,8 +68,6 @@ void WaveformScope::initializeGL() framebuffer_.Create(context()); - doneCurrent(); - connect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &WaveformScope::CleanUp, Qt::DirectConnection); UploadTextureFromBuffer(); diff --git a/app/widget/viewer/manageddisplayobject.cpp b/app/widget/viewer/manageddisplayobject.cpp deleted file mode 100644 index f7541ca3f..000000000 --- a/app/widget/viewer/manageddisplayobject.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "manageddisplayobject.h" - -ManagedDisplayObject::ManagedDisplayObject() -{ - -} diff --git a/app/widget/viewer/manageddisplayobject.h b/app/widget/viewer/manageddisplayobject.h deleted file mode 100644 index 11de19c12..000000000 --- a/app/widget/viewer/manageddisplayobject.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef MANAGEDDISPLAYOBJECT_H -#define MANAGEDDISPLAYOBJECT_H - - -class ManagedDisplayObject -{ -public: - ManagedDisplayObject(); -}; - -#endif // MANAGEDDISPLAYOBJECT_H From 877e8d3afcd3220f8278a40e786d06704b6bd87c Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 29 Apr 2020 01:26:09 +1000 Subject: [PATCH 21/69] curveview: ensured curveview is updated with the correct length and playhead --- app/panel/param/param.cpp | 2 ++ app/widget/curvewidget/curvewidget.cpp | 8 ++++++++ app/widget/nodeparamview/nodeparamview.cpp | 9 ++++++++- app/widget/nodeparamview/nodeparamview.h | 2 ++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/panel/param/param.cpp b/app/panel/param/param.cpp index bdbb8e8e0..2544c7748 100644 --- a/app/panel/param/param.cpp +++ b/app/panel/param/param.cpp @@ -94,9 +94,11 @@ void ParamPanel::CreateCurvePanel(NodeInput *input) panel->SetInput(input); panel->SetTimebase(view->timebase()); panel->SetTimestamp(view->GetTimestamp()); + panel->SetTimeTarget(view->GetTimeTarget()); connect(view, &NodeParamView::TimebaseChanged, panel, &CurvePanel::SetTimebase); connect(view, &NodeParamView::TimeChanged, panel, &CurvePanel::SetTimestamp); + connect(view, &NodeParamView::TimeTargetChanged, panel, &CurvePanel::SetTimeTarget); connect(panel, &CurvePanel::TimeChanged, view, &NodeParamView::SetTimestamp); connect(panel, &CurvePanel::TimeChanged, view, &NodeParamView::TimeChanged); connect(panel, &CurvePanel::CloseRequested, this, &ParamPanel::ClosingCurvePanel); diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index 71c742619..b31e31ea8 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -200,6 +200,8 @@ void CurveWidget::ScaleChangedEvent(const double &scale) void CurveWidget::TimeTargetChangedEvent(Node *target) { + ConnectViewerNode(nullptr); + key_control_->SetTimeTarget(target); view_->SetTimeTarget(target); @@ -207,6 +209,12 @@ void CurveWidget::TimeTargetChangedEvent(Node *target) if (bridge_) { bridge_->SetTimeTarget(target); } + + // FIXME: If a non-viewer node is ever set here, it will fail to update the length + ViewerOutput* viewer = dynamic_cast(target); + if (viewer) { + ConnectViewerNode(viewer); + } } void CurveWidget::UpdateInputLabel() diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index b9d1e7366..8675794c1 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -156,7 +156,9 @@ void NodeParamView::SetNodes(QList nodes) items_.append(item); - QTimer::singleShot(1, item, &NodeParamViewItem::SignalAllKeyframes); + QMetaObject::invokeMethod(item, + "SignalAllKeyframes", + Qt::QueuedConnection); emit OpenedNode(node); } @@ -218,6 +220,11 @@ const QList &NodeParamView::nodes() return nodes_; } +Node *NodeParamView::GetTimeTarget() const +{ + return keyframe_view_->GetTimeTarget(); +} + void NodeParamView::UpdateItemTime(const int64_t ×tamp) { rational time = Timecode::timestamp_to_time(timestamp, keyframe_view_->timebase()); diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 097fcd4ae..be5cb5835 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -40,6 +40,8 @@ public: void SetNodes(QList nodes); const QList& nodes(); + Node* GetTimeTarget() const; + signals: void InputDoubleClicked(NodeInput* input); From f04fa12c06f52c38a8c24fd7622937640f31c35b Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 29 Apr 2020 03:56:50 +1000 Subject: [PATCH 22/69] nodeview: allow expanded views of nodes again Re-implementing expanding nodes to see the connections (but nicer this time). --- app/widget/nodeview/nodeviewedge.cpp | 28 ++++-- app/widget/nodeview/nodeviewedge.h | 2 +- app/widget/nodeview/nodeviewitem.cpp | 134 +++++++++++++++++---------- app/widget/nodeview/nodeviewitem.h | 9 +- 4 files changed, 113 insertions(+), 60 deletions(-) diff --git a/app/widget/nodeview/nodeviewedge.cpp b/app/widget/nodeview/nodeviewedge.cpp index 5ca752d29..a00439613 100644 --- a/app/widget/nodeview/nodeviewedge.cpp +++ b/app/widget/nodeview/nodeviewedge.cpp @@ -76,8 +76,9 @@ void NodeViewEdge::Adjust() } // Draw a line between the two - SetPoints(output->GetParamPoint(edge_->output()), - input->GetParamPoint(edge_->input())); + SetPoints(output->GetParamPoint(edge_->output(), output->pos()), + input->GetParamPoint(edge_->input(), output->pos()), + input->IsExpanded()); } void NodeViewEdge::SetConnected(bool c) @@ -102,19 +103,30 @@ void NodeViewEdge::SetHighlighted(bool e) UpdatePen(); } -void NodeViewEdge::SetPoints(const QPointF &start, const QPointF &end) +void NodeViewEdge::SetPoints(const QPointF &start, const QPointF &end, bool input_is_expanded) { QPainterPath path; path.moveTo(start); + double half_x = lerp(start.x(), end.x(), 0.5); + double half_y = lerp(start.y(), end.y(), 0.5); + + QPointF cp1, cp2; + if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal) { - double half_x = lerp(start.x(), end.x(), 0.5); - path.cubicTo(QPointF(half_x, start.y()), QPointF(half_x, end.y()), end); + cp1 = QPointF(half_x, start.y()); } else { - double half_y = lerp(start.y(), end.y(), 0.5); - path.cubicTo(QPointF(start.x(), half_y), QPointF(end.x(), half_y), end); + cp1 = QPointF(start.x(), half_y); } + if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal || input_is_expanded) { + cp2 = QPointF(half_x, end.y()); + } else { + cp2 = QPointF(end.x(), half_y); + } + + path.cubicTo(cp1, cp2, end); + setPath(path); } @@ -128,8 +140,6 @@ void NodeViewEdge::SetFlowDirection(NodeViewCommon::FlowDirection dir) void NodeViewEdge::UpdatePen() { setPen(QPen(qApp->palette().color(color_group_, color_role_), edge_width_)); - - //update(); } OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeview/nodeviewedge.h b/app/widget/nodeview/nodeviewedge.h index b180333a4..89a14d546 100644 --- a/app/widget/nodeview/nodeviewedge.h +++ b/app/widget/nodeview/nodeviewedge.h @@ -82,7 +82,7 @@ public: /** * @brief Set points to create curve from */ - void SetPoints(const QPointF& start, const QPointF& end); + void SetPoints(const QPointF& start, const QPointF& end, bool input_is_expanded); /** * @brief Sets the direction nodes are flowing diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index f3d0d3b9d..3e848911a 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -44,6 +44,7 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) : cached_drop_item_(nullptr), cached_drop_item_expanded_(false), expanded_(false), + hide_titlebar_(false), standard_click_(false), highlighted_index_(-1), node_edge_change_command_(nullptr), @@ -192,18 +193,26 @@ bool NodeViewItem::IsExpanded() const return expanded_; } -void NodeViewItem::SetExpanded(bool e) +void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) { - if (expanded_ == e) { + if (node_inputs_.isEmpty() + || (expanded_ == e && hide_titlebar_ == hide_titlebar)) { return; } expanded_ = e; + hide_titlebar_ = hide_titlebar; if (expanded_ && !node_inputs_.isEmpty()) { // Create new rect QRectF new_rect = title_bar_rect_; - new_rect.setHeight(new_rect.height() * node_inputs_.size()); + + if (hide_titlebar_) { + new_rect.setHeight(new_rect.height() * node_inputs_.size()); + } else { + new_rect.setHeight(new_rect.height() * (node_inputs_.size() + 1)); + } + setRect(new_rect); } else { setRect(title_bar_rect_); @@ -223,33 +232,14 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti // don't want here) QPalette app_pal = Core::instance()->main_window()->palette(); - { - QPen border_pen; - border_pen.setWidth(node_border_width_); - - QBrush bkg_color; - - if (option->state & QStyle::State_Selected) { - border_pen.setColor(app_pal.color(QPalette::Highlight)); - } else { - border_pen.setColor(css_proxy_.BorderColor()); - } - - if (IsExpanded()) { - bkg_color = app_pal.color(QPalette::Window); - } else { - bkg_color = css_proxy_.TitleBarColor(); - } - - painter->setPen(border_pen); - painter->setBrush(bkg_color); + // Draw background rect if expanded + if (IsExpanded()) { + painter->setPen(Qt::NoPen); + painter->setBrush(app_pal.color(QPalette::Window)); painter->drawRect(rect()); - } - painter->setPen(app_pal.color(QPalette::Text)); - - if (IsExpanded()) { + painter->setPen(app_pal.color(QPalette::Text)); for (int i=0;idrawText(input_rect, Qt::AlignCenter, node_inputs_.at(i)->name()); } + } - } else if (node_) { + // Draw the titlebar + if (!hide_titlebar_ && node_) { + + painter->setPen(Qt::black); + painter->setBrush(css_proxy_.TitleBarColor()); + + painter->drawRect(title_bar_rect_); + + painter->setPen(app_pal.color(QPalette::Text)); // Draw the text in a rect (the rect is sized around text already in the constructor) painter->drawText(title_bar_rect_, Qt::AlignCenter, node_->Name()); } + + // Draw final border + QPen border_pen; + border_pen.setWidth(node_border_width_); + + if (option->state & QStyle::State_Selected) { + border_pen.setColor(app_pal.color(QPalette::Highlight)); + } else { + border_pen.setColor(css_proxy_.BorderColor()); + } + + painter->setPen(border_pen); + painter->setBrush(Qt::NoBrush); + + painter->drawRect(rect()); } void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event) @@ -294,7 +308,7 @@ void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event) drag_src_param_ = param; // Set the starting position to the current param's connector - dragging_edge_start_ = GetParamPoint(param); + dragging_edge_start_ = GetParamPoint(param, QPointF()); } else if (param->type() == NodeParam::kInput) { // For an input param, we default to moving an existing edge @@ -369,6 +383,8 @@ void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) cached_drop_item_->SetExpanded(false); } + cached_drop_item_->SetHighlightedIndex(-1); + cached_drop_item_->setZValue(0); cached_drop_item_ = nullptr; } @@ -384,7 +400,7 @@ void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) cached_drop_item_expanded_ = !cached_drop_item_->IsExpanded(); if (cached_drop_item_expanded_) { - cached_drop_item_->SetExpanded(true); + cached_drop_item_->SetExpanded(true, true); } cached_drop_item_->setZValue(1); @@ -412,7 +428,7 @@ void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) drag_dest_param_ = comp_param; highlight_their_index = i; - QPointF end_point_local = cached_drop_item_->GetInputPoint(i); + QPointF end_point_local = cached_drop_item_->GetInputPoint(i, pos()); end_point = cached_drop_item_->mapToScene(end_point_local); } @@ -425,7 +441,9 @@ void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) dragging_edge_->SetConnected(drag_dest_param_); - dragging_edge_->SetPoints(dragging_edge_start_, end_point); + dragging_edge_->SetPoints(dragging_edge_start_, + end_point, + cached_drop_item_ ? cached_drop_item_->IsExpanded() : false); return; } @@ -446,8 +464,14 @@ void NodeViewItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) scene()->removeItem(dragging_edge_); // If we expanded an item in the drag, re-collapse it now - if (cached_drop_item_ != nullptr) { - cached_drop_item_->SetExpanded(false); + if (cached_drop_item_) { + if (cached_drop_item_expanded_) { + cached_drop_item_->SetExpanded(false); + } + + cached_drop_item_->SetHighlightedIndex(-1); + cached_drop_item_->setZValue(0); + cached_drop_item_ = nullptr; } @@ -488,6 +512,13 @@ void NodeViewItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) } } +void NodeViewItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + QGraphicsRectItem::mouseDoubleClickEvent(event); + + SetExpanded(!IsExpanded()); +} + QVariant NodeViewItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) { if (change == ItemPositionHasChanged && node_) { @@ -514,6 +545,10 @@ QRectF NodeViewItem::GetInputRect(int index) const { QRectF r = title_bar_rect_; + if (!hide_titlebar_) { + index++; + } + if (IsExpanded()) { r.translate(0, r.height() * index); } @@ -521,7 +556,7 @@ QRectF NodeViewItem::GetInputRect(int index) const return r; } -QPointF NodeViewItem::GetParamPoint(NodeParam *param) const +QPointF NodeViewItem::GetParamPoint(NodeParam *param, const QPointF& source_pos) const { if (param->type() == NodeParam::kOutput) { @@ -545,7 +580,7 @@ QPointF NodeViewItem::GetParamPoint(NodeParam *param) const input = static_cast(input->parent()); } - return pos() + GetInputPoint(node_inputs_.indexOf(input)); + return pos() + GetInputPoint(node_inputs_.indexOf(input), source_pos); } } @@ -554,20 +589,25 @@ void NodeViewItem::SetFlowDirection(NodeViewCommon::FlowDirection dir) flow_dir_ = dir; } -QPointF NodeViewItem::GetInputPoint(int index) const +QPointF NodeViewItem::GetInputPoint(int index, const QPointF& source_pos) const { QRectF input_rect = GetInputRect(index); - switch (flow_dir_) { - case NodeViewCommon::kLeftToRight: - default: - return QPointF(input_rect.left(), input_rect.center().y()); - case NodeViewCommon::kRightToLeft: - return QPointF(input_rect.right(), input_rect.center().y()); - case NodeViewCommon::kTopToBottom: - return QPointF(input_rect.center().x(), input_rect.top()); - case NodeViewCommon::kBottomToTop: - return QPointF(input_rect.center().x(), input_rect.bottom()); + Qt::Orientation flow_orientation = NodeViewCommon::GetFlowOrientation(flow_dir_); + + if (flow_orientation == Qt::Horizontal || IsExpanded()) { + if (flow_dir_ == NodeViewCommon::kLeftToRight + || (flow_orientation == Qt::Vertical && source_pos.x() < pos().x())) { + return QPointF(input_rect.left(), input_rect.center().y()); + } else { + return QPointF(input_rect.right(), input_rect.center().y()); + } + } else { + if (flow_dir_ == NodeViewCommon::kTopToBottom) { + return QPointF(input_rect.center().x(), input_rect.top()); + } else { + return QPointF(input_rect.center().x(), input_rect.bottom()); + } } } diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index 9d1999b28..2d76eaa98 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -67,13 +67,13 @@ public: /** * @brief Set expanded state */ - void SetExpanded(bool e); + void SetExpanded(bool e, bool hide_titlebar = false); void ToggleExpanded(); /** * @brief Returns GLOBAL point that edges should connect to for any NodeParam member of this object */ - QPointF GetParamPoint(NodeParam* param) const; + QPointF GetParamPoint(NodeParam* param, const QPointF &source_pos) const; /** * @brief Sets the direction nodes are flowing @@ -98,6 +98,7 @@ protected: virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override; virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; @@ -115,7 +116,7 @@ private: /** * @brief Returns local point that edges should connect to for a NodeInput in array node_inputs_[index] */ - QPointF GetInputPoint(int index) const; + QPointF GetInputPoint(int index, const QPointF &source_pos) const; /** * @brief Reference to attached Node @@ -157,6 +158,8 @@ private: */ bool expanded_; + bool hide_titlebar_; + /** * @brief Current click mode * From 366945fd29e0b944f2ce62a95b712a08cf790bcf Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 29 Apr 2020 03:57:49 +1000 Subject: [PATCH 23/69] add rdynamic to the linux build options This appears to be necessary for debug symbols to show in the crash handler. --- app/CMakeLists.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 24f7d9116..0c9c4661f 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -107,6 +107,14 @@ else() ) endif() +if(UNIX AND NOT APPLE) + target_compile_options( + ${OLIVE_TARGET} + PRIVATE + -rdynamic + ) +endif() + # Set include directories target_include_directories( ${OLIVE_TARGET} From 8ab91a70ccd6a903977d27d4a31ec2ba2f7bf6fb Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 29 Apr 2020 04:56:58 +1000 Subject: [PATCH 24/69] nodeview: implemented moving with either the hand tool or middle mouse button Includes splitting off this functionality from TimelineViewBase so it can be re-used in any QGraphicsView. --- app/widget/nodeview/nodeview.cpp | 21 +++- app/widget/nodeview/nodeview.h | 5 +- app/widget/timelinewidget/view/CMakeLists.txt | 2 + .../timelinewidget/view/handmovableview.cpp | 118 ++++++++++++++++++ .../timelinewidget/view/handmovableview.h | 59 +++++++++ .../timelinewidget/view/timelineviewbase.cpp | 90 +------------ .../timelinewidget/view/timelineviewbase.h | 19 +-- 7 files changed, 202 insertions(+), 112 deletions(-) create mode 100644 app/widget/timelinewidget/view/handmovableview.cpp create mode 100644 app/widget/timelinewidget/view/handmovableview.h diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 5483d50cf..fb301fd75 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -26,10 +26,12 @@ #include "nodeviewundo.h" #include "node/factory.h" +#define super HandMovableView + OLIVE_NAMESPACE_ENTER NodeView::NodeView(QWidget *parent) : - QGraphicsView(parent), + HandMovableView(parent), graph_(nullptr), attached_item_(nullptr), drop_edge_(nullptr) @@ -196,7 +198,7 @@ void NodeView::ItemsChanged() void NodeView::keyPressEvent(QKeyEvent *event) { - QGraphicsView::keyPressEvent(event); + super::keyPressEvent(event); if (event->key() == Qt::Key_Escape && attached_item_) { DetachItemFromCursor(); @@ -209,6 +211,8 @@ void NodeView::keyPressEvent(QKeyEvent *event) void NodeView::mousePressEvent(QMouseEvent *event) { + if (HandPress(event)) return; + if (attached_item_) { Node* dropping_node = attached_item_->GetNode(); @@ -233,12 +237,14 @@ void NodeView::mousePressEvent(QMouseEvent *event) drop_edge_ = nullptr; } - QGraphicsView::mousePressEvent(event); + super::mousePressEvent(event); } void NodeView::mouseMoveEvent(QMouseEvent *event) { - QGraphicsView::mouseMoveEvent(event); + if (HandMove(event)) return; + + super::mouseMoveEvent(event); if (attached_item_) { attached_item_->setPos(mapToScene(event->pos())); @@ -296,6 +302,13 @@ void NodeView::mouseMoveEvent(QMouseEvent *event) } } +void NodeView::mouseReleaseEvent(QMouseEvent *event) +{ + if (HandRelease(event)) return; + + super::mouseReleaseEvent(event); +} + void NodeView::wheelEvent(QWheelEvent *event) { if (event->modifiers() & Qt::ControlModifier) { diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 05d72464e..c4f00a3e2 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -26,6 +26,7 @@ #include "node/graph.h" #include "nodeviewscene.h" +#include "widget/timelinewidget/view/handmovableview.h" #include "widget/nodecopypaste/nodecopypaste.h" OLIVE_NAMESPACE_ENTER @@ -36,7 +37,7 @@ OLIVE_NAMESPACE_ENTER * 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 QGraphicsView, public NodeCopyPasteWidget +class NodeView : public HandMovableView, public NodeCopyPasteWidget { Q_OBJECT public: @@ -73,8 +74,8 @@ protected: virtual void keyPressEvent(QKeyEvent *event) override; virtual void mousePressEvent(QMouseEvent *event) override; - virtual void mouseMoveEvent(QMouseEvent *event) override; + virtual void mouseReleaseEvent(QMouseEvent* event) override; virtual void wheelEvent(QWheelEvent* event) override; diff --git a/app/widget/timelinewidget/view/CMakeLists.txt b/app/widget/timelinewidget/view/CMakeLists.txt index 5bbce0175..c2da0eead 100644 --- a/app/widget/timelinewidget/view/CMakeLists.txt +++ b/app/widget/timelinewidget/view/CMakeLists.txt @@ -16,6 +16,8 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} + widget/timelinewidget/view/handmovableview.h + widget/timelinewidget/view/handmovableview.cpp widget/timelinewidget/view/timelineplayhead.h widget/timelinewidget/view/timelineplayhead.cpp widget/timelinewidget/view/timelineview.h diff --git a/app/widget/timelinewidget/view/handmovableview.cpp b/app/widget/timelinewidget/view/handmovableview.cpp new file mode 100644 index 000000000..f0995eb7b --- /dev/null +++ b/app/widget/timelinewidget/view/handmovableview.cpp @@ -0,0 +1,118 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "handmovableview.h" + +#include + +#include "core.h" + +OLIVE_NAMESPACE_ENTER + +HandMovableView::HandMovableView(QWidget* parent) : + QGraphicsView(parent), + dragging_hand_(false) +{ + connect(Core::instance(), &Core::ToolChanged, this, &HandMovableView::ApplicationToolChanged); +} + +void HandMovableView::ApplicationToolChanged(Tool::Item tool) +{ + if (tool == Tool::kHand) { + setDragMode(ScrollHandDrag); + } else { + setDragMode(default_drag_mode_); + } + + ToolChangedEvent(tool); +} + +bool HandMovableView::HandPress(QMouseEvent *event) +{ + if (event->button() == Qt::MiddleButton) { + pre_hand_drag_mode_ = dragMode(); + dragging_hand_ = true; + + setDragMode(ScrollHandDrag); + + // Transform mouse event to act like the left button is pressed + QMouseEvent transformed(event->type(), + event->localPos(), + Qt::LeftButton, + Qt::LeftButton, + event->modifiers()); + + QGraphicsView::mousePressEvent(&transformed); + + return true; + } + + return false; +} + +bool HandMovableView::HandMove(QMouseEvent *event) +{ + if (dragging_hand_) { + // Transform mouse event to act like the left button is pressed + QMouseEvent transformed(event->type(), + event->localPos(), + Qt::LeftButton, + Qt::LeftButton, + event->modifiers()); + + QGraphicsView::mouseMoveEvent(&transformed); + } + return dragging_hand_; +} + +bool HandMovableView::HandRelease(QMouseEvent *event) +{ + if (dragging_hand_) { + // Transform mouse event to act like the left button is pressed + QMouseEvent transformed(event->type(), + event->localPos(), + Qt::LeftButton, + Qt::LeftButton, + event->modifiers()); + + QGraphicsView::mouseReleaseEvent(&transformed); + + setDragMode(pre_hand_drag_mode_); + + dragging_hand_ = false; + + return true; + } + + return false; +} + +void HandMovableView::SetDefaultDragMode(QGraphicsView::DragMode mode) +{ + default_drag_mode_ = mode; + setDragMode(default_drag_mode_); +} + +const QGraphicsView::DragMode &HandMovableView::GetDefaultDragMode() const +{ + return default_drag_mode_; +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/widget/timelinewidget/view/handmovableview.h b/app/widget/timelinewidget/view/handmovableview.h new file mode 100644 index 000000000..9224069c7 --- /dev/null +++ b/app/widget/timelinewidget/view/handmovableview.h @@ -0,0 +1,59 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef HANDMOVABLEVIEW_H +#define HANDMOVABLEVIEW_H + +#include + +#include "tool/tool.h" + +OLIVE_NAMESPACE_ENTER + +class HandMovableView : public QGraphicsView +{ + Q_OBJECT +public: + HandMovableView(QWidget* parent = nullptr); + +protected: + virtual void ToolChangedEvent(Tool::Item tool){Q_UNUSED(tool)} + + bool HandPress(QMouseEvent* event); + bool HandMove(QMouseEvent* event); + bool HandRelease(QMouseEvent* event); + + void SetDefaultDragMode(DragMode mode); + const DragMode& GetDefaultDragMode() const; + +private: + bool dragging_hand_; + DragMode pre_hand_drag_mode_; + + DragMode default_drag_mode_; + +private slots: + void ApplicationToolChanged(Tool::Item tool); + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // HANDMOVABLEVIEW_H diff --git a/app/widget/timelinewidget/view/timelineviewbase.cpp b/app/widget/timelinewidget/view/timelineviewbase.cpp index 31f53b586..9970e7115 100644 --- a/app/widget/timelinewidget/view/timelineviewbase.cpp +++ b/app/widget/timelinewidget/view/timelineviewbase.cpp @@ -34,12 +34,11 @@ OLIVE_NAMESPACE_ENTER const double TimelineViewBase::kMaximumScale = 8192; TimelineViewBase::TimelineViewBase(QWidget *parent) : - QGraphicsView(parent), + HandMovableView(parent), playhead_(0), playhead_scene_left_(-1), playhead_scene_right_(-1), dragging_playhead_(false), - dragging_hand_(false), limit_y_axis_(false) { setScene(&scene_); @@ -50,7 +49,6 @@ TimelineViewBase::TimelineViewBase(QWidget *parent) : SetDefaultDragMode(NoDrag); connect(&scene_, SIGNAL(changed(const QList&)), this, SLOT(UpdateSceneRect())); - connect(Core::instance(), &Core::ToolChanged, this, &TimelineViewBase::ApplicationToolChanged); SetMaximumScale(kMaximumScale); } @@ -100,17 +98,6 @@ rational TimelineViewBase::GetPlayheadTime() const return Timecode::timestamp_to_time(playhead_, timebase()); } -void TimelineViewBase::SetDefaultDragMode(QGraphicsView::DragMode mode) -{ - default_drag_mode_ = mode; - setDragMode(default_drag_mode_); -} - -const QGraphicsView::DragMode &TimelineViewBase::GetDefaultDragMode() const -{ - return default_drag_mode_; -} - bool TimelineViewBase::PlayheadPress(QMouseEvent *event) { QPointF scene_pos = mapToScene(event->pos()); @@ -148,70 +135,6 @@ bool TimelineViewBase::PlayheadRelease(QMouseEvent*) return false; } -bool TimelineViewBase::HandPress(QMouseEvent *event) -{ - if (event->button() == Qt::MiddleButton) { - pre_hand_drag_mode_ = dragMode(); - dragging_hand_ = true; - - setDragMode(ScrollHandDrag); - - // Transform mouse event to act like the left button is pressed - QMouseEvent transformed(event->type(), - event->localPos(), - Qt::LeftButton, - Qt::LeftButton, - event->modifiers()); - - QGraphicsView::mousePressEvent(&transformed); - - return true; - } - - return false; -} - -bool TimelineViewBase::HandMove(QMouseEvent *event) -{ - if (dragging_hand_) { - // Transform mouse event to act like the left button is pressed - QMouseEvent transformed(event->type(), - event->localPos(), - Qt::LeftButton, - Qt::LeftButton, - event->modifiers()); - - QGraphicsView::mouseMoveEvent(&transformed); - } - return dragging_hand_; -} - -bool TimelineViewBase::HandRelease(QMouseEvent *event) -{ - if (dragging_hand_) { - // Transform mouse event to act like the left button is pressed - QMouseEvent transformed(event->type(), - event->localPos(), - Qt::LeftButton, - Qt::LeftButton, - event->modifiers()); - - QGraphicsView::mouseReleaseEvent(&transformed); - - setDragMode(pre_hand_drag_mode_); - - dragging_hand_ = false; - - return true; - } - - return false; -} - -void TimelineViewBase::ToolChangedEvent(Tool::Item) -{ -} - qreal TimelineViewBase::GetPlayheadX() { return TimeToScene(Timecode::timestamp_to_time(playhead_, timebase())); @@ -305,15 +228,4 @@ void TimelineViewBase::SetLimitYAxis(bool) UpdateSceneRect(); } -void TimelineViewBase::ApplicationToolChanged(Tool::Item tool) -{ - if (tool == Tool::kHand) { - setDragMode(ScrollHandDrag); - } else { - setDragMode(default_drag_mode_); - } - - ToolChangedEvent(tool); -} - OLIVE_NAMESPACE_EXIT diff --git a/app/widget/timelinewidget/view/timelineviewbase.h b/app/widget/timelinewidget/view/timelineviewbase.h index c64718bb6..55eb9ff62 100644 --- a/app/widget/timelinewidget/view/timelineviewbase.h +++ b/app/widget/timelinewidget/view/timelineviewbase.h @@ -24,12 +24,13 @@ #include #include "core.h" +#include "handmovableview.h" #include "timelineplayhead.h" #include "widget/timelinewidget/timelinescaledobject.h" OLIVE_NAMESPACE_ENTER -class TimelineViewBase : public QGraphicsView, public TimelineScaledObject +class TimelineViewBase : public HandMovableView, public TimelineScaledObject { Q_OBJECT public: @@ -66,19 +67,10 @@ protected: rational GetPlayheadTime() const; - void SetDefaultDragMode(DragMode mode); - const DragMode& GetDefaultDragMode() const; - bool PlayheadPress(QMouseEvent* event); bool PlayheadMove(QMouseEvent* event); bool PlayheadRelease(QMouseEvent* event); - bool HandPress(QMouseEvent* event); - bool HandMove(QMouseEvent* event); - bool HandRelease(QMouseEvent* event); - - virtual void ToolChangedEvent(Tool::Item tool); - virtual void TimebaseChangedEvent(const rational &) override; private: @@ -93,15 +85,10 @@ private: bool dragging_playhead_; - bool dragging_hand_; - DragMode pre_hand_drag_mode_; - QGraphicsScene scene_; bool limit_y_axis_; - DragMode default_drag_mode_; - rational end_time_; private slots: @@ -118,8 +105,6 @@ private slots: */ void PageScrollToPlayhead(); - void ApplicationToolChanged(Tool::Item tool); - }; OLIVE_NAMESPACE_EXIT From c0b4cb28e07d3140126d128c977e1415897ab329 Mon Sep 17 00:00:00 2001 From: Troy James Sobotka Date: Thu, 23 Apr 2020 01:00:02 -0700 Subject: [PATCH 25/69] Temp waveform work. --- app/shaders/rgbwaveform.frag | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/app/shaders/rgbwaveform.frag b/app/shaders/rgbwaveform.frag index c97562daa..0c102f013 100644 --- a/app/shaders/rgbwaveform.frag +++ b/app/shaders/rgbwaveform.frag @@ -14,19 +14,28 @@ out vec4 fragColor; void main(void) { vec3 col = vec3(0.0); - float s = ove_texcoord.y * 1.8 - 0.15; - float maxb = s + threshold; - float minb = s - threshold; + float increment = 1.0 / 255.0; + float maxb = ove_texcoord.y + increment; + float minb = ove_texcoord.y - increment; int y_lim = int(ove_resolution.y); + vec3 cur_col = vec3(0.0); for (int i = 0; i < y_lim; i++) { - vec3 x = texture(ove_maintex, vec2(ove_texcoord.x, float(i) / float(ove_resolution.y))).rgb; - col += step(x, vec3(maxb)) * step(vec3(minb), x) / (ove_resolution.y * 0.125); + cur_col = texture2D( + ove_maintex, + vec2(ove_texcoord.x, float(i) / float(ove_resolution.y)) + ).rgb; - float l = dot(x, x); - col += step(l, maxb * maxb) * step(minb * minb, l) / (ove_resolution.y * 0.125); + col += step(vec3(ove_texcoord.y - increment), cur_col) * + step(cur_col, vec3(ove_texcoord.y + increment)); + + //float l = dot(x, x); + //col += step(l, maxb*maxb)*step(minb*minb, l) / (ove_resolution.y * 0.125); } - - fragColor = vec4(col, 1.0); + // if (ove_texcoord.y > (240.0 / 255.0)) + // { + // col = vec3(1.0, 0.0, 1.0); + // } + gl_FragColor = vec4(col, 1.0); } From f82bd39f0e71516a6ee191795a81b357321dac36 Mon Sep 17 00:00:00 2001 From: Troy James Sobotka Date: Mon, 27 Apr 2020 10:17:17 -0700 Subject: [PATCH 26/69] Minor tweaks Cleanup. --- app/shaders/rgbwaveform.frag | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/shaders/rgbwaveform.frag b/app/shaders/rgbwaveform.frag index 0c102f013..dbdf0cdc0 100644 --- a/app/shaders/rgbwaveform.frag +++ b/app/shaders/rgbwaveform.frag @@ -5,6 +5,9 @@ uniform sampler2D ove_maintex; uniform vec2 ove_resolution; +uniform vec2 ove_viewport; +varying vec2 ove_texcoord; + uniform float threshold; @@ -33,9 +36,6 @@ void main(void) { //float l = dot(x, x); //col += step(l, maxb*maxb)*step(minb*minb, l) / (ove_resolution.y * 0.125); } - // if (ove_texcoord.y > (240.0 / 255.0)) - // { - // col = vec3(1.0, 0.0, 1.0); - // } + gl_FragColor = vec4(col, 1.0); } From 5dd999b406cc8ef985ed85be4cce8e98a5da3cc6 Mon Sep 17 00:00:00 2001 From: Troy James Sobotka Date: Mon, 27 Apr 2020 23:17:54 -0700 Subject: [PATCH 27/69] First working pass of Waveform Remove the sum, add some commments, and make it scale to the full window. TODO: * Hook up CM. * Add UI for intensity * Add lines at 20% increments, plus under / overshoot? * Add UI for increments. --- app/shaders/rgbwaveform.frag | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/app/shaders/rgbwaveform.frag b/app/shaders/rgbwaveform.frag index dbdf0cdc0..3e1d0fd40 100644 --- a/app/shaders/rgbwaveform.frag +++ b/app/shaders/rgbwaveform.frag @@ -6,8 +6,6 @@ uniform sampler2D ove_maintex; uniform vec2 ove_resolution; uniform vec2 ove_viewport; -varying vec2 ove_texcoord; - uniform float threshold; @@ -17,10 +15,21 @@ out vec4 fragColor; void main(void) { vec3 col = vec3(0.0); - float increment = 1.0 / 255.0; + // Set an increment default to 10 bit encodings. This would likely be + // better served as a UI control, as waveforms will change their combing + // based on how granular the increment is set. For example, it can be + // challenging to spot 8 bit combing with an increment of 1. / 2.^8 - 1. + float increment = 1.0 / (pow(2, 10) - 1.0); float maxb = ove_texcoord.y + increment; float minb = ove_texcoord.y - increment; + // Intensity would make sense to also expose via the UI, as a density + // slider allows you to peek past certain values or reveal very low + // values. Hard coding it for now, as there isn't a clear way to have + // the various bit depth / code values always display at a consistent + // emission output strength. + float intensity = 0.10; + int y_lim = int(ove_resolution.y); vec3 cur_col = vec3(0.0); @@ -31,10 +40,18 @@ void main(void) { ).rgb; col += step(vec3(ove_texcoord.y - increment), cur_col) * - step(cur_col, vec3(ove_texcoord.y + increment)); + step(cur_col, vec3(ove_texcoord.y + increment)) * intensity; + + // TODO: Implement proper luma / luminance based values. + // This is simply taking the luminance weights derived from the + // OCIO configuration or the RGB to XYZ matrix and multiplying + // the above step approach by the luminance weights. + // EG: + // cur_lum = cur_col * lum_coeffs; + // + // col += step(vec3(ove_texcoord.y - increment), cur_lum) * + // step(cur_lum, vec3(ove_texcoord.y + increment)); - //float l = dot(x, x); - //col += step(l, maxb*maxb)*step(minb*minb, l) / (ove_resolution.y * 0.125); } gl_FragColor = vec4(col, 1.0); From b9a62eb1aa211a0720059ceb2dee3f37d1033700 Mon Sep 17 00:00:00 2001 From: Troy James Sobotka Date: Tue, 28 Apr 2020 08:59:55 -0700 Subject: [PATCH 28/69] Add lines Adding waveform lines. Still requires scaling to fit in arbitrary range. --- app/shaders/rgbwaveform.frag | 12 ++++++------ app/widget/scope/waveform/waveform.cpp | 27 ++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/app/shaders/rgbwaveform.frag b/app/shaders/rgbwaveform.frag index 3e1d0fd40..94537a703 100644 --- a/app/shaders/rgbwaveform.frag +++ b/app/shaders/rgbwaveform.frag @@ -6,8 +6,7 @@ uniform sampler2D ove_maintex; uniform vec2 ove_resolution; uniform vec2 ove_viewport; - -uniform float threshold; +uniform vec3 luma_coeffs; in vec2 ove_texcoord; @@ -33,6 +32,7 @@ void main(void) { int y_lim = int(ove_resolution.y); vec3 cur_col = vec3(0.0); + vec3 cur_lum = vec3(0.0); for (int i = 0; i < y_lim; i++) { cur_col = texture2D( ove_maintex, @@ -47,10 +47,10 @@ void main(void) { // OCIO configuration or the RGB to XYZ matrix and multiplying // the above step approach by the luminance weights. // EG: - // cur_lum = cur_col * lum_coeffs; - // - // col += step(vec3(ove_texcoord.y - increment), cur_lum) * - // step(cur_lum, vec3(ove_texcoord.y + increment)); + cur_lum = vec3(dot(cur_col, luma_coeffs)); + + col += step(vec3(ove_texcoord.y - increment), cur_lum) * + step(cur_lum, vec3(ove_texcoord.y + increment)) * intensity; } diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp index 0ce873bc5..530be3981 100644 --- a/app/widget/scope/waveform/waveform.cpp +++ b/app/widget/scope/waveform/waveform.cpp @@ -21,6 +21,10 @@ #include "waveform.h" +#include +#include +#include + #include "node/node.h" #include "render/backend/opengl/openglrenderfunctions.h" @@ -95,6 +99,22 @@ void WaveformScope::paintGL() color_service()->ProcessOpenGL(); + QVector ire_lines(6); + + for (int i=1; i <= 5; i++) { + ire_lines[i].setLine( + 0.0, height() * (i * 0.20), width(), height() * + (i * 0.20)); + } + ire_lines[0].setLine(0.0, 0.0, width(), 0.0); + ire_lines[5].setLine(0.0, height() - 1.0, width(), height() - 1.0); + + QPainter p(this); + p.setCompositionMode(QPainter::CompositionMode_Plus); + + p.setPen(Qt::red); + p.drawLines(ire_lines); + texture_.Release(); framebuffer_.Release(); @@ -107,8 +127,11 @@ void WaveformScope::paintGL() pipeline_->setUniformValue("ove_resolution", texture_.width(), texture_.height()); pipeline_->setUniformValue("ove_viewport", width(), height()); - // The general size of a pixel - pipeline_->setUniformValue("threshold", 2.0f / static_cast(height())); + GLfloat luma[3] = {0.0, 0.0, 0.0}; + + color_manager()->GetDefaultLumaCoefs(luma); + // qDebug() << "LUMA" << luma[0] << " " << luma[1] << " " << luma[2]; + pipeline_->setUniformValue("luma_coeffs", luma[0], luma[1], luma[2]); pipeline_->release(); From 4af10b2b5cebbb2b640916d6fd7333275de19ec4 Mon Sep 17 00:00:00 2001 From: Troy James Sobotka Date: Tue, 28 Apr 2020 15:05:11 -0700 Subject: [PATCH 29/69] Update Waveform Output Updates: * Added IRE text on 20% increments * Added waveform lines that work * Scaled waveform to a subsection of the surface --- app/shaders/rgbwaveform.frag | 50 +++++++++++------- app/widget/scope/waveform/waveform.cpp | 71 +++++++++++++++++++------- 2 files changed, 84 insertions(+), 37 deletions(-) diff --git a/app/shaders/rgbwaveform.frag b/app/shaders/rgbwaveform.frag index 94537a703..3641541fa 100644 --- a/app/shaders/rgbwaveform.frag +++ b/app/shaders/rgbwaveform.frag @@ -8,6 +8,11 @@ uniform vec2 ove_resolution; uniform vec2 ove_viewport; uniform vec3 luma_coeffs; +uniform float waveform_scale; +uniform vec2 waveform_dims; +uniform vec4 waveform_region; +uniform vec4 waveform_uv; + in vec2 ove_texcoord; out vec4 fragColor; @@ -19,8 +24,8 @@ void main(void) { // based on how granular the increment is set. For example, it can be // challenging to spot 8 bit combing with an increment of 1. / 2.^8 - 1. float increment = 1.0 / (pow(2, 10) - 1.0); - float maxb = ove_texcoord.y + increment; - float minb = ove_texcoord.y - increment; + float maxb = waveform_dims.y + increment; + float minb = waveform_dims.y - increment; // Intensity would make sense to also expose via the UI, as a density // slider allows you to peek past certain values or reveal very low @@ -29,29 +34,38 @@ void main(void) { // emission output strength. float intensity = 0.10; - int y_lim = int(ove_resolution.y); + int y_lim = int(waveform_dims.y); vec3 cur_col = vec3(0.0); vec3 cur_lum = vec3(0.0); - for (int i = 0; i < y_lim; i++) { - cur_col = texture2D( - ove_maintex, - vec2(ove_texcoord.x, float(i) / float(ove_resolution.y)) - ).rgb; - col += step(vec3(ove_texcoord.y - increment), cur_col) * - step(cur_col, vec3(ove_texcoord.y + increment)) * intensity; + if ( + (gl_FragCoord.x >= waveform_region.x) && + (gl_FragCoord.y >= waveform_region.y) && + (gl_FragCoord.x < waveform_region.z) && + (gl_FragCoord.y < waveform_region.w) + ) { + // col = vec3(0.5, 0.5, 0.0); + // int start = int(waveform_region.y); + int stop = int(waveform_dims.y); + float ratio = 0.0; + float waveform_x = (ove_texcoord.x - waveform_uv.x) / waveform_scale; + float waveform_y = (ove_texcoord.y - waveform_uv.y) / waveform_scale; + for (int i = 0; i < waveform_dims.y; i++) { + ratio = float(i) / float(waveform_dims.y); + cur_col = texture2D( + ove_maintex, + vec2(waveform_x, ratio) + ).rgb; - // TODO: Implement proper luma / luminance based values. - // This is simply taking the luminance weights derived from the - // OCIO configuration or the RGB to XYZ matrix and multiplying - // the above step approach by the luminance weights. - // EG: - cur_lum = vec3(dot(cur_col, luma_coeffs)); + col += step(vec3(waveform_y - increment), cur_col) * + step(cur_col, vec3(waveform_y + increment)) * intensity; - col += step(vec3(ove_texcoord.y - increment), cur_lum) * - step(cur_lum, vec3(ove_texcoord.y + increment)) * intensity; + cur_lum = vec3(dot(cur_col, luma_coeffs)); + col += step(vec3(waveform_y - increment), cur_lum) * + step(cur_lum, vec3(waveform_y + increment)) * intensity; + } } gl_FragColor = vec4(col, 1.0); diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp index 530be3981..b81875b83 100644 --- a/app/widget/scope/waveform/waveform.cpp +++ b/app/widget/scope/waveform/waveform.cpp @@ -99,22 +99,6 @@ void WaveformScope::paintGL() color_service()->ProcessOpenGL(); - QVector ire_lines(6); - - for (int i=1; i <= 5; i++) { - ire_lines[i].setLine( - 0.0, height() * (i * 0.20), width(), height() * - (i * 0.20)); - } - ire_lines[0].setLine(0.0, 0.0, width(), 0.0); - ire_lines[5].setLine(0.0, height() - 1.0, width(), height() - 1.0); - - QPainter p(this); - p.setCompositionMode(QPainter::CompositionMode_Plus); - - p.setPen(Qt::red); - p.drawLines(ire_lines); - texture_.Release(); framebuffer_.Release(); @@ -126,13 +110,36 @@ void WaveformScope::paintGL() pipeline_->bind(); pipeline_->setUniformValue("ove_resolution", texture_.width(), texture_.height()); pipeline_->setUniformValue("ove_viewport", width(), height()); - GLfloat luma[3] = {0.0, 0.0, 0.0}; - color_manager()->GetDefaultLumaCoefs(luma); - // qDebug() << "LUMA" << luma[0] << " " << luma[1] << " " << luma[2]; pipeline_->setUniformValue("luma_coeffs", luma[0], luma[1], luma[2]); + // Scale of the waveform relative to the viewport surface. + float waveform_scale = 0.80; + pipeline_->setUniformValue("waveform_scale", waveform_scale); + float waveform_dim_x = width() * waveform_scale; + float waveform_dim_y = height() * waveform_scale; + pipeline_->setUniformValue( + "waveform_dims", waveform_dim_x, waveform_dim_y); + + float waveform_start_dim_x = (width() - waveform_dim_x) / 2.0; + float waveform_start_dim_y = (height() - waveform_dim_y) / 2.0; + float waveform_end_dim_x = width() - waveform_start_dim_x; + float waveform_end_dim_y = height() - waveform_start_dim_y; + pipeline_->setUniformValue( + "waveform_region", + waveform_start_dim_x, waveform_start_dim_y, + waveform_end_dim_x, waveform_end_dim_y); + + float waveform_start_uv_x = waveform_start_dim_x / width(); + float waveform_start_uv_y = waveform_start_dim_y / height(); + float waveform_end_uv_x = waveform_end_dim_x / width(); + float waveform_end_uv_y = waveform_end_dim_y / height(); + pipeline_->setUniformValue( + "waveform_uv", + waveform_start_uv_x, waveform_start_uv_y, + waveform_end_uv_x, waveform_end_uv_y); + pipeline_->release(); f->glViewport(0, 0, width(), height()); @@ -142,6 +149,32 @@ void WaveformScope::paintGL() OpenGLRenderFunctions::Blit(pipeline_); managed_tex_.Release(); + + QVector ire_lines(6); + QPainter p(this); + QFontMetrics font_metrics = QFontMetrics(QFont()); + QString label; + int font_width = 0; + + p.setCompositionMode(QPainter::CompositionMode_Plus); + + p.setPen(QColor(0.0, 0.6 * 255.0, 0.0)); + p.setFont(QFont()); + + for (int i=0; i <= 5; i++) { + ire_lines[i].setLine( + waveform_start_dim_x, + (waveform_dim_y * (i * 0.20)) + waveform_start_dim_y, + waveform_end_dim_x, + (waveform_dim_y * (i * 0.20)) + waveform_start_dim_y); + label = QString::number(i * 20); + font_width = font_metrics.width(label); + p.drawText( + waveform_start_dim_x - font_width, + (waveform_dim_y * (i * 0.20)) + waveform_start_dim_y, + label); + } + p.drawLines(ire_lines); } } From c06bdc5eac7fe9a8ca6d8a23d30f04ba23f05881 Mon Sep 17 00:00:00 2001 From: Troy James Sobotka Date: Tue, 28 Apr 2020 15:39:16 -0700 Subject: [PATCH 30/69] Update flourishes Centre and align text. --- app/widget/scope/waveform/waveform.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp index b81875b83..5ac659c3b 100644 --- a/app/widget/scope/waveform/waveform.cpp +++ b/app/widget/scope/waveform/waveform.cpp @@ -154,7 +154,8 @@ void WaveformScope::paintGL() QPainter p(this); QFontMetrics font_metrics = QFontMetrics(QFont()); QString label; - int font_width = 0; + int font_x_offset = 0; + int font_y_offset = font_metrics.capHeight() / 2.0; p.setCompositionMode(QPainter::CompositionMode_Plus); @@ -167,11 +168,12 @@ void WaveformScope::paintGL() (waveform_dim_y * (i * 0.20)) + waveform_start_dim_y, waveform_end_dim_x, (waveform_dim_y * (i * 0.20)) + waveform_start_dim_y); - label = QString::number(i * 20); - font_width = font_metrics.width(label); + label = QString::number(1.0 - (i * 0.2), 'f', 1); + font_x_offset = font_metrics.width(label) + 4; + p.drawText( - waveform_start_dim_x - font_width, - (waveform_dim_y * (i * 0.20)) + waveform_start_dim_y, + waveform_start_dim_x - font_x_offset, + (waveform_dim_y * (i * 0.20)) + waveform_start_dim_y + font_y_offset, label); } p.drawLines(ire_lines); From 949e43cada9193c4678ca1b9c966b128794a3178 Mon Sep 17 00:00:00 2001 From: Troy James Sobotka Date: Tue, 28 Apr 2020 15:59:40 -0700 Subject: [PATCH 31/69] Update IRE increments Move to 10% increments. --- app/widget/scope/waveform/waveform.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp index 5ac659c3b..2f47c8dcf 100644 --- a/app/widget/scope/waveform/waveform.cpp +++ b/app/widget/scope/waveform/waveform.cpp @@ -150,10 +150,12 @@ void WaveformScope::paintGL() managed_tex_.Release(); - QVector ire_lines(6); QPainter p(this); QFontMetrics font_metrics = QFontMetrics(QFont()); QString label; + float ire_increment = 0.1; + float ire_steps = int(1.0 / ire_increment); + QVector ire_lines(ire_steps + 1); int font_x_offset = 0; int font_y_offset = font_metrics.capHeight() / 2.0; @@ -162,18 +164,18 @@ void WaveformScope::paintGL() p.setPen(QColor(0.0, 0.6 * 255.0, 0.0)); p.setFont(QFont()); - for (int i=0; i <= 5; i++) { + for (int i=0; i <= ire_steps; i++) { ire_lines[i].setLine( waveform_start_dim_x, - (waveform_dim_y * (i * 0.20)) + waveform_start_dim_y, + (waveform_dim_y * (i * ire_increment)) + waveform_start_dim_y, waveform_end_dim_x, - (waveform_dim_y * (i * 0.20)) + waveform_start_dim_y); - label = QString::number(1.0 - (i * 0.2), 'f', 1); + (waveform_dim_y * (i * ire_increment)) + waveform_start_dim_y); + label = QString::number(1.0 - (i * ire_increment), 'f', 1); font_x_offset = font_metrics.width(label) + 4; p.drawText( waveform_start_dim_x - font_x_offset, - (waveform_dim_y * (i * 0.20)) + waveform_start_dim_y + font_y_offset, + (waveform_dim_y * (i * ire_increment)) + waveform_start_dim_y + font_y_offset, label); } p.drawLines(ire_lines); From 999e841bc7733e13d80f60d6350bc1faace21793 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 29 Apr 2020 14:33:57 +1000 Subject: [PATCH 32/69] nodes: implemented short name Nodes can provide "short" names for use in the node graph, distinct from their full names shown everywhere else. --- app/node/block/transition/externaltransition.cpp | 5 +++++ app/node/block/transition/externaltransition.h | 1 + app/node/external.cpp | 5 +++++ app/node/external.h | 1 + app/node/generator/matrix/matrix.cpp | 5 +++++ app/node/generator/matrix/matrix.h | 1 + app/node/metareader.cpp | 12 ++++++++++++ app/node/metareader.h | 2 ++ app/node/node.cpp | 5 +++++ app/node/node.h | 7 +++++++ app/widget/nodeview/nodeviewitem.cpp | 2 +- 11 files changed, 45 insertions(+), 1 deletion(-) diff --git a/app/node/block/transition/externaltransition.cpp b/app/node/block/transition/externaltransition.cpp index 3de007d4d..74bbf817d 100644 --- a/app/node/block/transition/externaltransition.cpp +++ b/app/node/block/transition/externaltransition.cpp @@ -40,6 +40,11 @@ QString ExternalTransition::Name() const return meta_.Name(); } +QString ExternalTransition::ShortName() const +{ + return meta_.ShortName(); +} + QString ExternalTransition::id() const { return meta_.id(); diff --git a/app/node/block/transition/externaltransition.h b/app/node/block/transition/externaltransition.h index de16936f9..91cbd0ca9 100644 --- a/app/node/block/transition/externaltransition.h +++ b/app/node/block/transition/externaltransition.h @@ -35,6 +35,7 @@ public: virtual Node* copy() const override; virtual QString Name() const override; + virtual QString ShortName() const override; virtual QString id() const override; virtual QString Category() const override; virtual QString Description() const override; diff --git a/app/node/external.cpp b/app/node/external.cpp index 7bafc30e9..6d6a20ac9 100644 --- a/app/node/external.cpp +++ b/app/node/external.cpp @@ -42,6 +42,11 @@ QString ExternalNode::Name() const return meta_.Name(); } +QString ExternalNode::ShortName() const +{ + return meta_.ShortName(); +} + QString ExternalNode::id() const { return meta_.id(); diff --git a/app/node/external.h b/app/node/external.h index a20b11c15..b9a50a313 100644 --- a/app/node/external.h +++ b/app/node/external.h @@ -39,6 +39,7 @@ public: virtual Node* copy() const override; virtual QString Name() const override; + virtual QString ShortName() const override; virtual QString id() const override; virtual QString Category() const override; virtual QString Description() const override; diff --git a/app/node/generator/matrix/matrix.cpp b/app/node/generator/matrix/matrix.cpp index c967869ed..ac406d47f 100644 --- a/app/node/generator/matrix/matrix.cpp +++ b/app/node/generator/matrix/matrix.cpp @@ -59,6 +59,11 @@ QString MatrixGenerator::Name() const return tr("Orthographic Matrix"); } +QString MatrixGenerator::ShortName() const +{ + return tr("Ortho"); +} + QString MatrixGenerator::id() const { return QStringLiteral("org.olivevideoeditor.Olive.transform"); diff --git a/app/node/generator/matrix/matrix.h b/app/node/generator/matrix/matrix.h index 53c5c5356..7f723e4ab 100644 --- a/app/node/generator/matrix/matrix.h +++ b/app/node/generator/matrix/matrix.h @@ -34,6 +34,7 @@ public: virtual Node* copy() const override; virtual QString Name() const override; + virtual QString ShortName() const override; virtual QString id() const override; virtual QString Category() const override; virtual QString Description() const override; diff --git a/app/node/metareader.cpp b/app/node/metareader.cpp index b88b3d2e1..007d7ec7b 100644 --- a/app/node/metareader.cpp +++ b/app/node/metareader.cpp @@ -57,6 +57,15 @@ QString NodeMetaReader::Name() const return GetStringForCurrentLanguage(&names_); } +QString NodeMetaReader::ShortName() const +{ + if (short_names_.isEmpty()) { + return Name(); + } else { + return GetStringForCurrentLanguage(&short_names_); + } +} + const QString &NodeMetaReader::id() const { return id_; @@ -173,6 +182,9 @@ void NodeMetaReader::XMLReadEffect(QXmlStreamReader* reader) if (reader->name() == QStringLiteral("name")) { // Pick up name XMLReadLanguageString(reader, &names_); + } else if (reader->name() == QStringLiteral("shortnames")) { + // Pick up short name + XMLReadLanguageString(reader, &short_names_); } else if (reader->name() == QStringLiteral("category")) { // Pick up category XMLReadLanguageString(reader, &categories_); diff --git a/app/node/metareader.h b/app/node/metareader.h index dda052c83..13c965a87 100644 --- a/app/node/metareader.h +++ b/app/node/metareader.h @@ -35,6 +35,7 @@ public: NodeMetaReader(const QString& xml_meta_filename); QString Name() const; + QString ShortName() const; const QString& id() const; QString Category() const; QString Description() const; @@ -67,6 +68,7 @@ private: QString xml_filename_; LanguageMap names_; + LanguageMap short_names_; LanguageMap descriptions_; LanguageMap categories_; QMap param_names_; diff --git a/app/node/node.cpp b/app/node/node.cpp index cea3c1417..0aa807240 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -116,6 +116,11 @@ void Node::Save(QXmlStreamWriter *writer, const QString &custom_name) const writer->writeEndElement(); // node } +QString Node::ShortName() const +{ + return Name(); +} + QString Node::Category() const { // Return an empty category for any nodes that don't use one diff --git a/app/node/node.h b/app/node/node.h index 0aec6ecb1..6497082cb 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -91,6 +91,13 @@ public: */ virtual QString Name() const = 0; + /** + * @brief Returns a shortened name of this node if applicable + * + * Defaults to returning Name() but can be overridden. + */ + virtual QString ShortName() const; + /** * @brief Return the unique identifier of the node * diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 3e848911a..f4569dcf0 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -263,7 +263,7 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti painter->setPen(app_pal.color(QPalette::Text)); // Draw the text in a rect (the rect is sized around text already in the constructor) - painter->drawText(title_bar_rect_, Qt::AlignCenter, node_->Name()); + painter->drawText(title_bar_rect_, Qt::AlignCenter, node_->ShortName()); } From 1db5cf4de45bb376b1b85482b669a82f8c836f14 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 29 Apr 2020 14:36:06 +1000 Subject: [PATCH 33/69] nodeview: reduce width of items --- app/widget/nodeview/nodeviewitem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index f4569dcf0..76b875fe0 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -132,7 +132,7 @@ int NodeViewItem::DefaultItemHeight() int NodeViewItem::DefaultItemWidth() { - return QFontMetricsWidth(QFontMetrics(QFont()), "HHHHHHHHHHHHHH");; + return QFontMetricsWidth(QFontMetrics(QFont()), "HHHHHHHHHH");; } int NodeViewItem::DefaultItemBorder() From d83073cf8512045a200a3e15a5d708d9570c17c9 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 29 Apr 2020 14:49:43 +1000 Subject: [PATCH 34/69] nodeview: attach pasted nodes to the cursor --- app/widget/nodeview/nodeview.cpp | 145 ++++++++++++++++++------------- app/widget/nodeview/nodeview.h | 11 ++- 2 files changed, 96 insertions(+), 60 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 631be989a..ae48de055 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -34,7 +34,6 @@ OLIVE_NAMESPACE_ENTER NodeView::NodeView(QWidget *parent) : HandMovableView(parent), graph_(nullptr), - attached_item_(nullptr), drop_edge_(nullptr) { setScene(&scene_); @@ -185,7 +184,13 @@ void NodeView::Paste() Core::instance()->undo_stack()->pushIfHasChildren(command); if (!pasted_nodes.isEmpty()) { - // FIXME: Attach to cursor so user can drop in place + QList items; + + foreach (Node* p, pasted_nodes) { + items.append(scene_.NodeToUIObject(p)); + } + + AttachItemToCursor(items); } } @@ -202,7 +207,7 @@ void NodeView::keyPressEvent(QKeyEvent *event) { super::keyPressEvent(event); - if (event->key() == Qt::Key_Escape && attached_item_) { + if (event->key() == Qt::Key_Escape && !attached_items_.isEmpty()) { DetachItemFromCursor(); // We undo the last action which SHOULD be adding the node @@ -215,28 +220,30 @@ void NodeView::mousePressEvent(QMouseEvent *event) { if (HandPress(event)) return; - if (attached_item_) { - Node* dropping_node = attached_item_->GetNode(); - + if (!attached_items_.isEmpty()) { DetachItemFromCursor(); - if (drop_edge_) { - NodeEdgePtr old_edge = drop_edge_->edge(); + if (attached_items_.size() == 1) { + Node* dropping_node = attached_items_.first().item->GetNode(); - // We have everything we need to place the node in between - QUndoCommand* command = new QUndoCommand(); + if (drop_edge_) { + NodeEdgePtr old_edge = drop_edge_->edge(); - // Remove old edge - new NodeEdgeRemoveCommand(old_edge, command); + // We have everything we need to place the node in between + QUndoCommand* command = new QUndoCommand(); - // Place new edges - new NodeEdgeAddCommand(old_edge->output(), drop_input_, command); - new NodeEdgeAddCommand(dropping_node->output(), old_edge->input(), command); + // Remove old edge + new NodeEdgeRemoveCommand(old_edge, command); - Core::instance()->undo_stack()->push(command); + // Place new edges + new NodeEdgeAddCommand(old_edge->output(), drop_input_, command); + new NodeEdgeAddCommand(dropping_node->output(), old_edge->input(), command); + + Core::instance()->undo_stack()->push(command); + } + + drop_edge_ = nullptr; } - - drop_edge_ = nullptr; } super::mousePressEvent(event); @@ -248,58 +255,62 @@ void NodeView::mouseMoveEvent(QMouseEvent *event) super::mouseMoveEvent(event); - if (attached_item_) { - attached_item_->setPos(mapToScene(event->pos())); + if (!attached_items_.isEmpty()) { + MoveAttachedNodesToCursor(event->pos()); - // See if the user clicked on an edge - QRect edge_detect_rect(event->pos(), event->pos()); + // See if the user clicked on an edge (only when dropping single nodes) + if (attached_items_.size() == 1) { + Node* attached_node = attached_items_.first().item->GetNode(); - // FIXME: Hardcoded numbers - edge_detect_rect.adjust(-20, -20, 20, 20); + QRect edge_detect_rect(event->pos(), event->pos()); - QList items = this->items(edge_detect_rect); + // FIXME: Hardcoded numbers + edge_detect_rect.adjust(-20, -20, 20, 20); - NodeViewEdge* new_drop_edge = nullptr; + QList items = this->items(edge_detect_rect); - // See if there is an edge here - foreach (QGraphicsItem* item, items) { - new_drop_edge = dynamic_cast(item); + NodeViewEdge* new_drop_edge = nullptr; - if (new_drop_edge) { - drop_input_ = nullptr; + // See if there is an edge here + foreach (QGraphicsItem* item, items) { + new_drop_edge = dynamic_cast(item); - foreach (NodeParam* param, attached_item_->GetNode()->parameters()) { - if (param->type() == NodeParam::kInput) { - NodeInput* input = static_cast(param); + if (new_drop_edge) { + drop_input_ = nullptr; - if (input->IsConnectable()) { - if (input->data_type() & new_drop_edge->edge()->input()->data_type()) { - drop_input_ = input; - break; - } else if (!drop_input_) { - drop_input_ = input; + foreach (NodeParam* param, attached_node->parameters()) { + if (param->type() == NodeParam::kInput) { + NodeInput* input = static_cast(param); + + if (input->IsConnectable()) { + if (input->data_type() & new_drop_edge->edge()->input()->data_type()) { + drop_input_ = input; + break; + } else if (!drop_input_) { + drop_input_ = input; + } } } } - } - if (drop_input_) { - break; - } else { - new_drop_edge = nullptr; + if (drop_input_) { + break; + } else { + new_drop_edge = nullptr; + } } } - } - if (drop_edge_ != new_drop_edge) { - if (drop_edge_) { - drop_edge_->SetHighlighted(false); - } + if (drop_edge_ != new_drop_edge) { + if (drop_edge_) { + drop_edge_->SetHighlighted(false); + } - drop_edge_ = new_drop_edge; + drop_edge_ = new_drop_edge; - if (drop_edge_) { - drop_edge_->SetHighlighted(true); + if (drop_edge_) { + drop_edge_->SetHighlighted(true); + } } } } @@ -385,7 +396,7 @@ void NodeView::CreateNodeSlot(QAction *action) Core::instance()->undo_stack()->push(new NodeAddCommand(graph_, new_node)); NodeViewItem* item = scene_.NodeToUIObject(new_node); - AttachItemToCursor(item); + AttachItemToCursor({item}); } } @@ -537,16 +548,25 @@ void NodeView::PlaceNode(NodeViewItem *n, const QPointF &pos) } } -void NodeView::AttachItemToCursor(NodeViewItem *item) +void NodeView::AttachItemToCursor(const QList& items) { - attached_item_ = item; + DetachItemFromCursor(); - setMouseTracking(attached_item_); + if (!items.isEmpty()) { + foreach (NodeViewItem* i, items) { + attached_items_.append({i, i->pos() - items.first()->pos()}); + } + + setMouseTracking(true); + + MoveAttachedNodesToCursor(mapFromGlobal(QCursor::pos())); + } } void NodeView::DetachItemFromCursor() { - AttachItemToCursor(nullptr); + attached_items_.clear(); + setMouseTracking(false); } void NodeView::SetFlowDirection(NodeViewCommon::FlowDirection dir) @@ -554,4 +574,13 @@ void NodeView::SetFlowDirection(NodeViewCommon::FlowDirection dir) scene_.SetFlowDirection(dir); } +void NodeView::MoveAttachedNodesToCursor(const QPoint& p) +{ + QPointF item_pos = mapToScene(p); + + foreach (const AttachedItem& i, attached_items_) { + i.item->setPos(item_pos + i.original_pos); + } +} + OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 97230fd2d..0454e6943 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -82,15 +82,22 @@ protected: private: void PlaceNode(NodeViewItem* n, const QPointF& pos); - void AttachItemToCursor(NodeViewItem* item); + void AttachItemToCursor(const QList& item); void DetachItemFromCursor(); void SetFlowDirection(NodeViewCommon::FlowDirection dir); + void MoveAttachedNodesToCursor(const QPoint &p); + NodeGraph* graph_; - NodeViewItem* attached_item_; + struct AttachedItem { + NodeViewItem* item; + QPointF original_pos; + }; + + QList attached_items_; NodeViewEdge* drop_edge_; NodeInput* drop_input_; From 13dc79d0cdee98f67699d70851581e27e4f0c633 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 29 Apr 2020 15:04:53 +1000 Subject: [PATCH 35/69] nodeview: implemented duplicate feature --- app/panel/node/node.cpp | 5 ++ app/panel/node/node.h | 2 + app/widget/menu/menushared.cpp | 2 +- app/widget/nodeview/nodeview.cpp | 79 ++++++++++++++++++++++++++++---- app/widget/nodeview/nodeview.h | 8 +++- app/widget/panel/panel.h | 2 + 6 files changed, 85 insertions(+), 13 deletions(-) diff --git a/app/panel/node/node.cpp b/app/panel/node/node.cpp index cbeef8d2a..892b0c32f 100644 --- a/app/panel/node/node.cpp +++ b/app/panel/node/node.cpp @@ -73,6 +73,11 @@ void NodePanel::Paste() node_view_->Paste(); } +void NodePanel::Duplicate() +{ + node_view_->Duplicate(); +} + void NodePanel::Select(const QList &nodes) { node_view_->Select(nodes); diff --git a/app/panel/node/node.h b/app/panel/node/node.h index c2f07a482..9dc9533b8 100644 --- a/app/panel/node/node.h +++ b/app/panel/node/node.h @@ -47,6 +47,8 @@ public: virtual void Paste() override; + virtual void Duplicate() override; + public slots: void Select(const QList& nodes); void SelectWithDependencies(const QList& nodes); diff --git a/app/widget/menu/menushared.cpp b/app/widget/menu/menushared.cpp index 2d68e3a3d..563630fb7 100644 --- a/app/widget/menu/menushared.cpp +++ b/app/widget/menu/menushared.cpp @@ -188,7 +188,7 @@ void MenuShared::PasteInsertTriggered() void MenuShared::DuplicateTriggered() { - qDebug() << "FIXME: Stub"; + PanelManager::instance()->CurrentlyFocused()->Duplicate(); } void MenuShared::EnableDisableTriggered() diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index ae48de055..d6582cff9 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -184,13 +184,61 @@ void NodeView::Paste() Core::instance()->undo_stack()->pushIfHasChildren(command); if (!pasted_nodes.isEmpty()) { - QList items; + AttachNodesToCursor(pasted_nodes); + } +} - foreach (Node* p, pasted_nodes) { - items.append(scene_.NodeToUIObject(p)); +void NodeView::Duplicate() +{ + if (!graph_) { + return; + } + + QList selected = scene_.GetSelectedNodes(); + + if (selected.isEmpty()) { + return; + } + + QUndoCommand* command = new QUndoCommand(); + + QList duplicated_nodes; + + foreach (Node* n, selected) { + Node* copy = n->copy(); + + Node::CopyInputs(n, copy, false); + copy->SetPosition(n->GetPosition()); + + duplicated_nodes.append(copy); + + new NodeAddCommand(graph_, copy, command); + } + + for (int i=0;ioutput()->edges()) { + if (edge->input()->parentNode() == dst) { + new NodeEdgeAddCommand(duplicated_nodes.at(i)->output(), + duplicated_nodes.at(j)->GetInputWithID(edge->input()->id()), + command); + } + } } + } - AttachItemToCursor(items); + Core::instance()->undo_stack()->pushIfHasChildren(command); + + if (!duplicated_nodes.isEmpty()) { + AttachNodesToCursor(duplicated_nodes); } } @@ -208,7 +256,7 @@ void NodeView::keyPressEvent(QKeyEvent *event) super::keyPressEvent(event); if (event->key() == Qt::Key_Escape && !attached_items_.isEmpty()) { - DetachItemFromCursor(); + DetachItemsFromCursor(); // We undo the last action which SHOULD be adding the node // FIXME: Possible danger of this not being the case? @@ -221,7 +269,7 @@ void NodeView::mousePressEvent(QMouseEvent *event) if (HandPress(event)) return; if (!attached_items_.isEmpty()) { - DetachItemFromCursor(); + DetachItemsFromCursor(); if (attached_items_.size() == 1) { Node* dropping_node = attached_items_.first().item->GetNode(); @@ -396,7 +444,7 @@ void NodeView::CreateNodeSlot(QAction *action) Core::instance()->undo_stack()->push(new NodeAddCommand(graph_, new_node)); NodeViewItem* item = scene_.NodeToUIObject(new_node); - AttachItemToCursor({item}); + AttachItemsToCursor({item}); } } @@ -548,9 +596,20 @@ void NodeView::PlaceNode(NodeViewItem *n, const QPointF &pos) } } -void NodeView::AttachItemToCursor(const QList& items) +void NodeView::AttachNodesToCursor(const QList &nodes) { - DetachItemFromCursor(); + QList items; + + foreach (Node* p, nodes) { + items.append(scene_.NodeToUIObject(p)); + } + + AttachItemsToCursor(items); +} + +void NodeView::AttachItemsToCursor(const QList& items) +{ + DetachItemsFromCursor(); if (!items.isEmpty()) { foreach (NodeViewItem* i, items) { @@ -563,7 +622,7 @@ void NodeView::AttachItemToCursor(const QList& items) } } -void NodeView::DetachItemFromCursor() +void NodeView::DetachItemsFromCursor() { attached_items_.clear(); setMouseTracking(false); diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 0454e6943..c8ae334a0 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -64,6 +64,8 @@ public: void CopySelected(bool cut); void Paste(); + void Duplicate(); + signals: /** * @brief Signal emitted when the selected nodes have changed @@ -82,9 +84,11 @@ protected: private: void PlaceNode(NodeViewItem* n, const QPointF& pos); - void AttachItemToCursor(const QList& item); + void AttachNodesToCursor(const QList& nodes); - void DetachItemFromCursor(); + void AttachItemsToCursor(const QList& items); + + void DetachItemsFromCursor(); void SetFlowDirection(NodeViewCommon::FlowDirection dir); diff --git a/app/widget/panel/panel.h b/app/widget/panel/panel.h index a0008faf0..1d25f5eba 100644 --- a/app/widget/panel/panel.h +++ b/app/widget/panel/panel.h @@ -165,6 +165,8 @@ public: virtual void ToggleSelectedEnabled(){} + virtual void Duplicate(){} + signals: void CloseRequested(); From bf68a4d63119578184c6e5f1e56d66969ba49279 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 29 Apr 2020 15:35:05 +1000 Subject: [PATCH 36/69] nodes: added ability to label nodes --- app/common/xmlutils.cpp | 4 +++ app/node/node.cpp | 20 +++++++++++ app/node/node.h | 13 +++++++ .../nodeparamview/nodeparamviewitem.cpp | 8 ++++- app/widget/nodeparamview/nodeparamviewitem.h | 5 +-- app/widget/nodeview/nodeview.cpp | 35 ++++++++++++++++++- app/widget/nodeview/nodeview.h | 5 +++ app/widget/nodeview/nodeviewitem.cpp | 35 ++++++++++++++++++- app/widget/nodeview/nodeviewitem.h | 2 ++ app/widget/nodeview/nodeviewscene.cpp | 7 ++++ app/widget/nodeview/nodeviewscene.h | 4 +++ 11 files changed, 133 insertions(+), 5 deletions(-) diff --git a/app/common/xmlutils.cpp b/app/common/xmlutils.cpp index 087648822..9bea8f3b0 100644 --- a/app/common/xmlutils.cpp +++ b/app/common/xmlutils.cpp @@ -31,6 +31,7 @@ Node* XMLLoadNode(QXmlStreamReader* reader) QString node_id; quintptr node_ptr = 0; QPointF node_pos; + QString node_label; XMLAttributeLoop(reader, attr) { if (attr.name() == QStringLiteral("id")) { @@ -45,6 +46,8 @@ Node* XMLLoadNode(QXmlStreamReader* reader) node_pos.setX(pos.at(0).toDouble()); node_pos.setY(pos.at(1).toDouble()); } + } else if (attr.name() == QStringLiteral("label")) { + node_label = attr.value().toString(); } } @@ -58,6 +61,7 @@ Node* XMLLoadNode(QXmlStreamReader* reader) if (node) { node->setProperty("xml_ptr", node_ptr); node->SetPosition(node_pos); + node->SetLabel(node_label); } else { qWarning() << "Failed to load" << node_id << "- no node with that ID is installed"; } diff --git a/app/node/node.cpp b/app/node/node.cpp index 0aa807240..5b1111eba 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -107,6 +107,9 @@ void Node::Save(QXmlStreamWriter *writer, const QString &custom_name) const QStringLiteral("%1:%2").arg(QString::number(GetPosition().x()), QString::number(GetPosition().y()))); + writer->writeAttribute(QStringLiteral("label"), + GetLabel()); + foreach (NodeParam* param, parameters()) { param->Save(writer); } @@ -277,6 +280,20 @@ QList Node::GetOutputs() const return {output_}; } +const QString &Node::GetLabel() const +{ + return label_; +} + +void Node::SetLabel(const QString &s) +{ + if (label_ != s) { + label_ = s; + + emit LabelChanged(label_); + } +} + void Node::CopyInputs(Node *source, Node *destination, bool include_connections) { Q_ASSERT(source->id() == destination->id()); @@ -295,6 +312,9 @@ void Node::CopyInputs(Node *source, Node *destination, bool include_connections) NodeInput::CopyValues(src, dst, include_connections); } } + + destination->SetPosition(source->GetPosition()); + destination->SetLabel(source->GetLabel()); } bool Node::CanBeDeleted() const diff --git a/app/node/node.h b/app/node/node.h index 6497082cb..29d96313f 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -359,6 +359,9 @@ public: QList GetOutputs() const; + const QString& GetLabel() const; + void SetLabel(const QString& s); + protected: void AddInput(NodeInput* input); @@ -396,6 +399,11 @@ signals: */ void PositionChanged(const QPointF& pos); + /** + * @brief Signal emitted when SetLabel() is called + */ + void LabelChanged(const QString& s); + private: /** * @brief Add a parameter to this node @@ -431,6 +439,11 @@ private: */ QPointF position_; + /** + * @brief Custom user label for node + */ + QString label_; + private slots: void InputChanged(const OLIVE_NAMESPACE::TimeRange &range); diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 6d7a2f0ee..326f674b3 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -73,6 +73,8 @@ NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) : connect(title_bar_collapse_btn_, &QPushButton::toggled, body_, &NodeParamViewItemBody::setVisible); main_layout->addWidget(body_); + connect(node_, &Node::LabelChanged, this, &NodeParamViewItem::Retranslate); + Retranslate(); } @@ -111,7 +113,11 @@ void NodeParamViewItem::Retranslate() { node_->Retranslate(); - title_bar_lbl_->setText(node_->Name()); + if (node_->GetLabel().isEmpty()) { + title_bar_lbl_->setText(node_->Name()); + } else { + title_bar_lbl_->setText(tr("%1 (%2)").arg(node_->GetLabel(), node_->Name())); + } body_->Retranslate(); } diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 5209d68ed..6d6996219 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -129,8 +129,6 @@ protected: virtual void changeEvent(QEvent *e) override; private: - void Retranslate(); - NodeParamViewItemTitleBar* title_bar_; QLabel* title_bar_lbl_; @@ -143,6 +141,9 @@ private: rational time_; +private slots: + void Retranslate(); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index d6582cff9..4c8c9733a 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -20,6 +20,7 @@ #include "nodeview.h" +#include #include #include "core.h" @@ -208,7 +209,6 @@ void NodeView::Duplicate() Node* copy = n->copy(); Node::CopyInputs(n, copy, false); - copy->SetPosition(n->GetPosition()); duplicated_nodes.append(copy); @@ -401,6 +401,15 @@ void NodeView::ShowContextMenu(const QPoint &pos) m.addSeparator(); if (itemAt(pos)) { + QList selected = scene_.GetSelectedItems(); + + if (selected.size() == 1) { + QAction* label_action = m.addAction(tr("Label")); + connect(label_action, &QAction::triggered, this, &NodeView::ContextMenuLabelNode); + + m.addSeparator(); + } + QAction* autopos = m.addAction(tr("Auto-Position")); connect(autopos, &QAction::triggered, this, &NodeView::AutoPositionDescendents); } else { @@ -462,6 +471,30 @@ void NodeView::AutoPositionDescendents() } } +void NodeView::ContextMenuLabelNode() +{ + QList nodes = scene_.GetSelectedNodes(); + + if (nodes.isEmpty()) { + return; + } + + Node* n = nodes.first(); + + bool ok; + + QString s = QInputDialog::getText(this, + tr("Label Node"), + tr("Set node label"), + QLineEdit::Normal, + n->GetLabel(), + &ok); + + if (ok) { + n->SetLabel(s); + } +} + void NodeView::PlaceNode(NodeViewItem *n, const QPointF &pos) { QRectF destination_rect = n->rect(); diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index c8ae334a0..2701f4a1d 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -141,6 +141,11 @@ private slots: */ void AutoPositionDescendents(); + /** + * @brief Receiver for labelling a node from the context menu + */ + void ContextMenuLabelNode(); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 76b875fe0..45eafa7e2 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -135,6 +135,11 @@ int NodeViewItem::DefaultItemWidth() return QFontMetricsWidth(QFontMetrics(QFont()), "HHHHHHHHHH");; } +int NodeViewItem::DefaultMaximumTextWidth() +{ + return QFontMetricsWidth(QFontMetrics(QFont()), "HHHHHHHH");; +} + int NodeViewItem::DefaultItemBorder() { return QFontMetrics(QFont()).height() / 12; @@ -262,8 +267,36 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti painter->setPen(app_pal.color(QPalette::Text)); + QString node_label; + + if (node_->GetLabel().isEmpty()) { + node_label = node_->ShortName(); + } else { + node_label = node_->GetLabel(); + } + + { + QFont f; + QFontMetrics fm(f); + + int max_text_width = DefaultMaximumTextWidth(); + + if (QFontMetricsWidth(fm, node_label) > max_text_width) { + QString concatenated; + + do { + node_label.chop(1); + concatenated = QCoreApplication::translate("NodeViewItem", "%1...").arg(node_label); + } while (QFontMetricsWidth(fm, concatenated) > max_text_width); + + node_label = concatenated; + } + } + // Draw the text in a rect (the rect is sized around text already in the constructor) - painter->drawText(title_bar_rect_, Qt::AlignCenter, node_->ShortName()); + painter->drawText(title_bar_rect_, + Qt::AlignCenter, + node_label); } diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index 2d76eaa98..0a02b7e3e 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -86,6 +86,8 @@ public: static int DefaultItemWidth(); + static int DefaultMaximumTextWidth(); + static int DefaultItemBorder(); qreal DefaultItemHorizontalPadding() const; diff --git a/app/widget/nodeview/nodeviewscene.cpp b/app/widget/nodeview/nodeviewscene.cpp index 628e72517..b7e848996 100644 --- a/app/widget/nodeview/nodeviewscene.cpp +++ b/app/widget/nodeview/nodeviewscene.cpp @@ -180,10 +180,12 @@ void NodeViewScene::AddNode(Node* node) } connect(node, &Node::PositionChanged, this, &NodeViewScene::NodePositionChanged); + connect(node, &Node::LabelChanged, this, &NodeViewScene::NodeLabelChanged); } void NodeViewScene::RemoveNode(Node *node) { + disconnect(node, &Node::LabelChanged, this, &NodeViewScene::NodeLabelChanged); disconnect(node, &Node::PositionChanged, this, &NodeViewScene::NodePositionChanged); delete item_map_.take(node); @@ -243,4 +245,9 @@ void NodeViewScene::NodePositionChanged(const QPointF &pos) item_map_.value(static_cast(sender()))->SetNodePosition(pos); } +void NodeViewScene::NodeLabelChanged() +{ + item_map_.value(static_cast(sender()))->update(); +} + OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index ee4040c5f..0aa7b67ca 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -127,6 +127,10 @@ private slots: */ void NodePositionChanged(const QPointF& pos); + /** + * @brief Receiver for when a node's label has changed + */ + void NodeLabelChanged(); }; From dfedcb97b293a86c6861315ed101203f30184887 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 29 Apr 2020 15:56:24 +1000 Subject: [PATCH 37/69] waveform: use qfontmetrics macro for version compatibility --- app/widget/scope/waveform/waveform.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp index 2f47c8dcf..39c6f81cb 100644 --- a/app/widget/scope/waveform/waveform.cpp +++ b/app/widget/scope/waveform/waveform.cpp @@ -25,6 +25,7 @@ #include #include +#include "common/qtutils.h" #include "node/node.h" #include "render/backend/opengl/openglrenderfunctions.h" @@ -171,7 +172,7 @@ void WaveformScope::paintGL() waveform_end_dim_x, (waveform_dim_y * (i * ire_increment)) + waveform_start_dim_y); label = QString::number(1.0 - (i * ire_increment), 'f', 1); - font_x_offset = font_metrics.width(label) + 4; + font_x_offset = QFontMetricsWidth(font_metrics, label) + 4; p.drawText( waveform_start_dim_x - font_x_offset, From 544a8855d3f53e8744de04254cfcdfbeccdd220b Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 29 Apr 2020 15:56:46 +1000 Subject: [PATCH 38/69] waveform: update for glsl 150 --- app/shaders/rgbwaveform.frag | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/shaders/rgbwaveform.frag b/app/shaders/rgbwaveform.frag index 3641541fa..3e839b386 100644 --- a/app/shaders/rgbwaveform.frag +++ b/app/shaders/rgbwaveform.frag @@ -53,7 +53,7 @@ void main(void) { float waveform_y = (ove_texcoord.y - waveform_uv.y) / waveform_scale; for (int i = 0; i < waveform_dims.y; i++) { ratio = float(i) / float(waveform_dims.y); - cur_col = texture2D( + cur_col = texture( ove_maintex, vec2(waveform_x, ratio) ).rgb; @@ -68,5 +68,5 @@ void main(void) { } } - gl_FragColor = vec4(col, 1.0); + fragColor = vec4(col, 1.0); } From 4ac5513e3fbde6a4288d041bb47cb89ee0789184 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 29 Apr 2020 17:25:41 +1000 Subject: [PATCH 39/69] nodeview: implemented ability to select and delete node edges --- app/widget/nodeview/nodeview.cpp | 46 +++++++++++++++++++-------- app/widget/nodeview/nodeviewedge.cpp | 44 +++++++++++++++---------- app/widget/nodeview/nodeviewedge.h | 9 +++--- app/widget/nodeview/nodeviewscene.cpp | 15 +++++++++ app/widget/nodeview/nodeviewscene.h | 1 + 5 files changed, 82 insertions(+), 33 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 4c8c9733a..3e9653bd1 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -97,21 +97,33 @@ void NodeView::DeleteSelected() return; } - QList selected_nodes = scene_.GetSelectedNodes(); + QUndoCommand* command = new QUndoCommand(); - // Ensure no nodes are "undeletable" - for (int i=0;iCanBeDeleted()) { - selected_nodes.removeAt(i); - i--; + { + QList selected_edges = scene_.GetSelectedEdges(); + + foreach (NodeEdge* edge, selected_edges) { + new NodeEdgeRemoveCommand(edge->output(), edge->input(), command); } } - if (selected_nodes.isEmpty()) { - return; + { + QList selected_nodes = scene_.GetSelectedNodes(); + + // Ensure no nodes are "undeletable" + for (int i=0;iCanBeDeleted()) { + selected_nodes.removeAt(i); + i--; + } + } + + if (!selected_nodes.isEmpty()) { + new NodeRemoveCommand(graph_, selected_nodes, command); + } } - Core::instance()->undo_stack()->push(new NodeRemoveCommand(graph_, selected_nodes)); + Core::instance()->undo_stack()->pushIfHasChildren(command); } void NodeView::SelectAll() @@ -269,8 +281,6 @@ void NodeView::mousePressEvent(QMouseEvent *event) if (HandPress(event)) return; if (!attached_items_.isEmpty()) { - DetachItemsFromCursor(); - if (attached_items_.size() == 1) { Node* dropping_node = attached_items_.first().item->GetNode(); @@ -292,6 +302,8 @@ void NodeView::mousePressEvent(QMouseEvent *event) drop_edge_ = nullptr; } + + DetachItemsFromCursor(); } super::mousePressEvent(event); @@ -400,19 +412,26 @@ void NodeView::ShowContextMenu(const QPoint &pos) m.addSeparator(); - if (itemAt(pos)) { - QList selected = scene_.GetSelectedItems(); + QList selected = scene_.GetSelectedItems(); + + if (itemAt(pos) && !selected.isEmpty()) { if (selected.size() == 1) { + + // Label node action QAction* label_action = m.addAction(tr("Label")); connect(label_action, &QAction::triggered, this, &NodeView::ContextMenuLabelNode); m.addSeparator(); + } + // Auto-position action QAction* autopos = m.addAction(tr("Auto-Position")); connect(autopos, &QAction::triggered, this, &NodeView::AutoPositionDescendents); + } else { + Menu* direction_menu = new Menu(tr("Direction"), &m); m.addMenu(direction_menu); @@ -440,6 +459,7 @@ void NodeView::ShowContextMenu(const QPoint &pos) add_menu->setTitle(tr("Add")); connect(add_menu, &Menu::triggered, this, &NodeView::CreateNodeSlot); m.addMenu(add_menu); + } m.exec(mapToGlobal(pos)); diff --git a/app/widget/nodeview/nodeviewedge.cpp b/app/widget/nodeview/nodeviewedge.cpp index a00439613..205785cbf 100644 --- a/app/widget/nodeview/nodeviewedge.cpp +++ b/app/widget/nodeview/nodeviewedge.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "common/clamp.h" #include "common/lerp.h" @@ -34,10 +35,12 @@ OLIVE_NAMESPACE_ENTER NodeViewEdge::NodeViewEdge(QGraphicsItem *parent) : QGraphicsPathItem(parent), edge_(nullptr), - color_group_(QPalette::Active), - color_role_(QPalette::Text), + connected_(false), + highlighted_(false), flow_dir_(NodeViewCommon::kLeftToRight) { + setFlag(QGraphicsItem::ItemIsSelectable); + // Ensures this UI object is drawn behind other objects setZValue(-1); @@ -83,24 +86,16 @@ void NodeViewEdge::Adjust() void NodeViewEdge::SetConnected(bool c) { - if (c) { - color_group_ = QPalette::Active; - } else { - color_group_ = QPalette::Disabled; - } + connected_ = c; - UpdatePen(); + update(); } void NodeViewEdge::SetHighlighted(bool e) { - if (e) { - color_role_ = QPalette::Highlight; - } else { - color_role_ = QPalette::Text; - } + highlighted_ = e; - UpdatePen(); + update(); } void NodeViewEdge::SetPoints(const QPointF &start, const QPointF &end, bool input_is_expanded) @@ -137,9 +132,26 @@ void NodeViewEdge::SetFlowDirection(NodeViewCommon::FlowDirection dir) Adjust(); } -void NodeViewEdge::UpdatePen() +void NodeViewEdge::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) { - setPen(QPen(qApp->palette().color(color_group_, color_role_), edge_width_)); + 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; + } + + painter->setPen(QPen(qApp->palette().color(group, role), edge_width_)); + painter->setBrush(Qt::NoBrush); + painter->drawPath(path()); } OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeview/nodeviewedge.h b/app/widget/nodeview/nodeviewedge.h index 89a14d546..045955a05 100644 --- a/app/widget/nodeview/nodeviewedge.h +++ b/app/widget/nodeview/nodeviewedge.h @@ -89,16 +89,17 @@ public: */ void SetFlowDirection(NodeViewCommon::FlowDirection dir); -private: - void UpdatePen(); +protected: + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; +private: NodeEdgePtr edge_; int edge_width_; - QPalette::ColorGroup color_group_; + bool connected_; - QPalette::ColorRole color_role_; + bool highlighted_; NodeViewCommon::FlowDirection flow_dir_; diff --git a/app/widget/nodeview/nodeviewscene.cpp b/app/widget/nodeview/nodeviewscene.cpp index b7e848996..4b11dd423 100644 --- a/app/widget/nodeview/nodeviewscene.cpp +++ b/app/widget/nodeview/nodeviewscene.cpp @@ -145,6 +145,21 @@ QList NodeViewScene::GetSelectedItems() const return selected; } +QList NodeViewScene::GetSelectedEdges() const +{ + QList edges; + + QHash::const_iterator i; + + for (i=edge_map_.constBegin(); i!=edge_map_.constEnd(); i++) { + if (i.value()->isSelected()) { + edges.append(i.key()); + } + } + + return edges; +} + const QHash &NodeViewScene::item_map() const { return item_map_; diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index 0aa7b67ca..d512278e1 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -65,6 +65,7 @@ public: QList GetSelectedNodes() const; QList GetSelectedItems() const; + QList GetSelectedEdges() const; const QHash& item_map() const; const QHash& edge_map() const; From ba94aa0b2259d0acf8af4b54b33f2994ba407b93 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 29 Apr 2020 18:43:00 +1000 Subject: [PATCH 40/69] fixed some float typing for msvc build --- app/widget/scope/waveform/waveform.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp index 39c6f81cb..756771676 100644 --- a/app/widget/scope/waveform/waveform.cpp +++ b/app/widget/scope/waveform/waveform.cpp @@ -116,15 +116,15 @@ void WaveformScope::paintGL() pipeline_->setUniformValue("luma_coeffs", luma[0], luma[1], luma[2]); // Scale of the waveform relative to the viewport surface. - float waveform_scale = 0.80; + float waveform_scale = 0.80f; pipeline_->setUniformValue("waveform_scale", waveform_scale); float waveform_dim_x = width() * waveform_scale; float waveform_dim_y = height() * waveform_scale; pipeline_->setUniformValue( "waveform_dims", waveform_dim_x, waveform_dim_y); - float waveform_start_dim_x = (width() - waveform_dim_x) / 2.0; - float waveform_start_dim_y = (height() - waveform_dim_y) / 2.0; + float waveform_start_dim_x = (width() - waveform_dim_x) / 2.0f; + float waveform_start_dim_y = (height() - waveform_dim_y) / 2.0f; float waveform_end_dim_x = width() - waveform_start_dim_x; float waveform_end_dim_y = height() - waveform_start_dim_y; pipeline_->setUniformValue( @@ -154,11 +154,11 @@ void WaveformScope::paintGL() QPainter p(this); QFontMetrics font_metrics = QFontMetrics(QFont()); QString label; - float ire_increment = 0.1; + float ire_increment = 0.1f; float ire_steps = int(1.0 / ire_increment); QVector ire_lines(ire_steps + 1); int font_x_offset = 0; - int font_y_offset = font_metrics.capHeight() / 2.0; + int font_y_offset = font_metrics.capHeight() / 2.0f; p.setCompositionMode(QPainter::CompositionMode_Plus); From aecfa842c3cfa15678b704a04a865d8a98ba790c Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 30 Apr 2020 01:56:08 +1000 Subject: [PATCH 41/69] nodes: moved hashing to nodes so they can override behavior if they wish --- app/node/block/block.cpp | 12 +++ app/node/block/block.h | 2 + app/node/block/transition/transition.cpp | 13 +++ app/node/block/transition/transition.h | 2 + app/node/input/time/timeinput.cpp | 8 ++ app/node/input/time/timeinput.h | 2 + app/node/node.cpp | 71 ++++++++++++++ app/node/node.h | 4 + app/node/output/track/track.cpp | 10 ++ app/node/output/track/track.h | 2 + app/render/backend/renderworker.cpp | 2 +- app/render/backend/videorenderworker.cpp | 113 +---------------------- app/render/backend/videorenderworker.h | 2 - 13 files changed, 128 insertions(+), 115 deletions(-) diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index ccb3f0140..06e0237f6 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -234,6 +234,18 @@ void Block::SaveInternal(QXmlStreamWriter *writer) const } } +QList Block::GetInputsToHash() const +{ + QList inputs = Node::GetInputsToHash(); + + // Ignore these inputs + inputs.removeOne(media_in_input_); + inputs.removeOne(speed_input_); + inputs.removeOne(length_input_); + + return inputs; +} + void Block::LengthInputChanged() { emit LengthChanged(length()); diff --git a/app/node/block/block.h b/app/node/block/block.h index 26df9d13a..a36f91754 100644 --- a/app/node/block/block.h +++ b/app/node/block/block.h @@ -126,6 +126,8 @@ protected: virtual void SaveInternal(QXmlStreamWriter* writer) const override; + virtual QList GetInputsToHash() const override; + Block* previous_; Block* next_; diff --git a/app/node/block/transition/transition.cpp b/app/node/block/transition/transition.cpp index 91fe5adb6..cdb0b8489 100644 --- a/app/node/block/transition/transition.cpp +++ b/app/node/block/transition/transition.cpp @@ -129,6 +129,19 @@ double TransitionBlock::GetInProgress(const rational &time) const return clamp((GetInternalTransitionTime(time) - out_offset().toDouble()) / in_offset().toDouble(), 0.0, 1.0); } +void TransitionBlock::Hash(QCryptographicHash &hash, const rational &time) const +{ + Block::Hash(hash, time); + + double all_prog = GetTotalProgress(time); + double in_prog = GetInProgress(time); + double out_prog = GetOutProgress(time); + + hash.addData(reinterpret_cast(&all_prog), sizeof(double)); + hash.addData(reinterpret_cast(&in_prog), sizeof(double)); + hash.addData(reinterpret_cast(&out_prog), sizeof(double)); +} + double TransitionBlock::GetInternalTransitionTime(const rational &time) const { return time.toDouble() - in().toDouble(); diff --git a/app/node/block/transition/transition.h b/app/node/block/transition/transition.h index 74346c6a0..62f4280b5 100644 --- a/app/node/block/transition/transition.h +++ b/app/node/block/transition/transition.h @@ -47,6 +47,8 @@ public: double GetOutProgress(const rational& time) const; double GetInProgress(const rational& time) const; + virtual void Hash(QCryptographicHash& hash, const rational &time) const override; + private: double GetInternalTransitionTime(const rational& time) const; diff --git a/app/node/input/time/timeinput.cpp b/app/node/input/time/timeinput.cpp index 60b901b8b..fa15de735 100644 --- a/app/node/input/time/timeinput.cpp +++ b/app/node/input/time/timeinput.cpp @@ -62,4 +62,12 @@ NodeValueTable TimeInput::Value(NodeValueDatabase &value) const return table; } +void TimeInput::Hash(QCryptographicHash &hash, const rational &time) const +{ + Node::Hash(hash, time); + + // Make sure time is hashed + hash.addData(NodeParam::ValueToBytes(NodeParam::kRational, QVariant::fromValue(time))); +} + OLIVE_NAMESPACE_EXIT diff --git a/app/node/input/time/timeinput.h b/app/node/input/time/timeinput.h index 284faba4a..b912ed4b1 100644 --- a/app/node/input/time/timeinput.h +++ b/app/node/input/time/timeinput.h @@ -40,6 +40,8 @@ public: virtual NodeValueTable Value(NodeValueDatabase& value) const override; + virtual void Hash(QCryptographicHash& hash, const rational& time) const override; + }; OLIVE_NAMESPACE_EXIT diff --git a/app/node/node.cpp b/app/node/node.cpp index 5b1111eba..49c48cfdb 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -25,6 +25,9 @@ #include #include "common/xmlutils.h" +#include "project/project.h" +#include "project/item/footage/footage.h" +#include "project/item/footage/imagestream.h" OLIVE_NAMESPACE_ENTER @@ -232,6 +235,11 @@ void Node::SaveInternal(QXmlStreamWriter *) const { } +QList Node::GetInputsToHash() const +{ + return GetInputsIncludingArrays(); +} + QString Node::ReadFileAsString(const QString &filename) { QFile f(filename); @@ -294,6 +302,69 @@ void Node::SetLabel(const QString &s) } } +void Node::Hash(QCryptographicHash &hash, const rational& time) const +{ + // Add this Node's ID + hash.addData(id().toUtf8()); + + QList inputs = GetInputsToHash(); + + foreach (NodeInput* input, inputs) { + // For each input, try to hash its value + + // Get time adjustment + // For a single frame, we only care about one of the times + rational input_time = InputTimeAdjustment(input, TimeRange(time, time)).in(); + + if (input->IsConnected()) { + // Traverse down this edge + input->get_connected_node()->Hash(hash, input_time); + } else { + // Grab the value at this time + QVariant value = input->get_value_at_time(input_time); + hash.addData(NodeParam::ValueToBytes(input->data_type(), value)); + } + + // We have one exception for FOOTAGE types, since we resolve the footage into a frame in the renderer + if (input->data_type() == NodeParam::kFootage) { + StreamPtr stream = input->get_standard_value().value(); + + if (stream) { + // Add footage details to hash + + // Footage filename + hash.addData(stream->footage()->filename().toUtf8()); + + // Footage last modified date + hash.addData(stream->footage()->timestamp().toString().toUtf8()); + + // Footage stream + hash.addData(QString::number(stream->index()).toUtf8()); + + if (stream->type() == Stream::kImage || stream->type() == Stream::kVideo) { + ImageStreamPtr image_stream = std::static_pointer_cast(stream); + + // Current color config and space + hash.addData(image_stream->footage()->project()->color_manager()->GetConfigFilename().toUtf8()); + hash.addData(image_stream->colorspace().toUtf8()); + + // Alpha associated setting + hash.addData(QString::number(image_stream->premultiplied_alpha()).toUtf8()); + } + + // Footage timestamp + if (stream->type() == Stream::kVideo) { + hash.addData(QStringLiteral("%1/%2").arg(QString::number(input_time.numerator()), + QString::number(input_time.denominator())).toUtf8()); + + hash.addData(QString::number(static_cast(stream.get())->start_time()).toUtf8()); + + } + } + } + } +} + void Node::CopyInputs(Node *source, Node *destination, bool include_connections) { Q_ASSERT(source->id() == destination->id()); diff --git a/app/node/node.h b/app/node/node.h index 29d96313f..3b5d09f09 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -362,6 +362,8 @@ public: const QString& GetLabel() const; void SetLabel(const QString& s); + virtual void Hash(QCryptographicHash& hash, const rational &time) const; + protected: void AddInput(NodeInput* input); @@ -373,6 +375,8 @@ protected: virtual void SaveInternal(QXmlStreamWriter* writer) const; + virtual QList GetInputsToHash() const; + public slots: signals: diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 3adc7e6b7..da545cd6e 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -418,6 +418,16 @@ NodeInputArray *TrackOutput::block_input() const return block_input_; } +void TrackOutput::Hash(QCryptographicHash &hash, const rational &time) const +{ + // Resolve block list + Block* b = BlockAtTime(time); + + if (b) { + return b->Hash(hash, time); + } +} + void TrackOutput::SetTrackName(const QString &name) { track_name_ = name; diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 89f58903a..0922d54fc 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -152,6 +152,8 @@ public: NodeInputArray* block_input() const; + virtual void Hash(QCryptographicHash& hash, const rational &time) const override; + public slots: void SetTrackName(const QString& name); diff --git a/app/render/backend/renderworker.cpp b/app/render/backend/renderworker.cpp index 947cb2f1e..8ba8409f2 100644 --- a/app/render/backend/renderworker.cpp +++ b/app/render/backend/renderworker.cpp @@ -78,7 +78,7 @@ void RenderWorker::RunNodeAccelerated(const Node *node, const TimeRange &range, StreamPtr RenderWorker::ResolveStreamFromInput(NodeInput *input) { - return input->get_value_at_time(0).value(); + return input->get_standard_value().value(); } DecoderPtr RenderWorker::ResolveDecoderFromInput(StreamPtr stream) diff --git a/app/render/backend/videorenderworker.cpp b/app/render/backend/videorenderworker.cpp index 5dabdc5cb..e9afae9f3 100644 --- a/app/render/backend/videorenderworker.cpp +++ b/app/render/backend/videorenderworker.cpp @@ -75,7 +75,7 @@ NodeValueTable VideoRenderWorker::RenderInternal(const NodeDependency& path, con hasher.addData(reinterpret_cast(&vfmt), sizeof(PixelFormat::Format)); hasher.addData(reinterpret_cast(&vmode), sizeof(RenderMode::Mode)); - HashNodeRecursively(&hasher, path.node(), path.in()); + path.node()->Hash(hasher, path.in()); hash = hasher.result(); } @@ -121,117 +121,6 @@ NodeValueTable VideoRenderWorker::RenderInternal(const NodeDependency& path, con return value; } -void VideoRenderWorker::HashNodeRecursively(QCryptographicHash *hash, const Node* n, const rational& time) -{ - // Resolve BlockList - if (n->IsTrack()) { - n = static_cast(n)->BlockAtTime(time); - - if (!n) { - return; - } - } - - // Add this Node's ID - hash->addData(n->id().toUtf8()); - - if (n->IsBlock() && static_cast(n)->type() == Block::kTransition) { - const TransitionBlock* transition = static_cast(n); - - double all_prog = transition->GetTotalProgress(time); - double in_prog = transition->GetInProgress(time); - double out_prog = transition->GetOutProgress(time); - - hash->addData(reinterpret_cast(&all_prog), sizeof(double)); - hash->addData(reinterpret_cast(&in_prog), sizeof(double)); - hash->addData(reinterpret_cast(&out_prog), sizeof(double)); - } - - foreach (NodeParam* param, n->parameters()) { - // For each input, try to hash its value - if (param->type() == NodeParam::kInput) { - NodeInput* input = static_cast(param); - - if (n->IsBlock()) { - const Block* b = static_cast(n); - - // Ignore some Block attributes when hashing - if (input == b->media_in_input() - || input == b->speed_input() - || input == b->length_input()) { - continue; - } - } - - // Get time adjustment - // For a single frame, we only care about one of the times - rational input_time = n->InputTimeAdjustment(input, TimeRange(time, time)).in(); - - if (input->IsConnected()) { - // Traverse down this edge - HashNodeRecursively(hash, input->get_connected_node(), input_time); - } else { - // Grab the value at this time - QVariant value = input->get_value_at_time(input_time); - hash->addData(NodeParam::ValueToBytes(input->data_type(), value)); - } - - // We have one exception for FOOTAGE types, since we resolve the footage into a frame in the renderer - if (input->data_type() == NodeParam::kFootage) { - StreamPtr stream = ResolveStreamFromInput(input); - - if (stream) { - DecoderPtr decoder = ResolveDecoderFromInput(stream); - - if (decoder) { - - // Add footage details to hash - - // Footage filename - hash->addData(stream->footage()->filename().toUtf8()); - - // Footage last modified date - hash->addData(stream->footage()->timestamp().toString().toUtf8()); - - // Footage stream - hash->addData(QString::number(stream->index()).toUtf8()); - - if (stream->type() == Stream::kImage || stream->type() == Stream::kVideo) { - ImageStreamPtr image_stream = std::static_pointer_cast(stream); - - // Current color config and space - hash->addData(image_stream->footage()->project()->color_manager()->GetConfigFilename().toUtf8()); - hash->addData(image_stream->colorspace().toUtf8()); - - // Alpha associated setting - hash->addData(QString::number(image_stream->premultiplied_alpha()).toUtf8()); - } - - // Footage timestamp - if (stream->type() == Stream::kVideo) { - hash->addData(QStringLiteral("%1/%2").arg(QString::number(input_time.numerator()), - QString::number(input_time.denominator())).toUtf8()); - - hash->addData(QString::number(static_cast(stream.get())->start_time()).toUtf8()); - /*Decoder::RetrieveState state = decoder->GetRetrieveState(input_time); - - if (state == Decoder::kReady) { - VideoStreamPtr video_stream = std::static_pointer_cast(stream); - - int64_t timestamp_here = video_stream->get_closest_timestamp_in_frame_index(input_time); - - hash->addData(QString::number(timestamp_here).toUtf8()); - } else { - ReportUnavailableFootage(stream, state, input_time); - }*/ - } - } - } - } - } - } -} - void VideoRenderWorker::SetParameters(const VideoRenderingParams &video_params) { video_params_ = video_params; diff --git a/app/render/backend/videorenderworker.h b/app/render/backend/videorenderworker.h index 6d43dbf6f..32476f199 100644 --- a/app/render/backend/videorenderworker.h +++ b/app/render/backend/videorenderworker.h @@ -105,8 +105,6 @@ protected: ColorProcessorCache* color_cache(); private: - void HashNodeRecursively(QCryptographicHash* hash, const Node *n, const rational &time); - void Download(const rational &time, QVariant texture, QString filename); void ResizeDownloadBuffer(); From ce42388d689bdd7a9e7bfbc7bb4474e292ded813 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 30 Apr 2020 17:19:43 +1000 Subject: [PATCH 42/69] export: allow users to override the thread count --- app/codec/encoder.cpp | 19 +++++++-- app/codec/encoder.h | 11 +++-- app/codec/ffmpeg/ffmpegencoder.cpp | 10 ++++- app/dialog/export/CMakeLists.txt | 2 + app/dialog/export/codec/h264section.cpp | 10 ++--- app/dialog/export/export.cpp | 2 + .../export/exportadvancedvideodialog.cpp | 42 +++++++++++++++++++ app/dialog/export/exportadvancedvideodialog.h | 27 ++++++++++++ app/dialog/export/exportvideotab.cpp | 27 +++++++++++- app/dialog/export/exportvideotab.h | 6 +++ 10 files changed, 141 insertions(+), 15 deletions(-) create mode 100644 app/dialog/export/exportadvancedvideodialog.cpp create mode 100644 app/dialog/export/exportadvancedvideodialog.h diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp index 26ef71989..c9cd17350 100644 --- a/app/codec/encoder.cpp +++ b/app/codec/encoder.cpp @@ -40,6 +40,7 @@ EncodingParams::EncodingParams() : video_bit_rate_(0), video_max_bit_rate_(0), video_buffer_size_(0), + video_threads_(0), audio_enabled_(false) { } @@ -63,26 +64,31 @@ void EncodingParams::EnableAudio(const AudioRenderingParams &audio_params, const audio_codec_ = acodec; } -void EncodingParams::SetVideoOption(const QString &key, const QString &value) +void EncodingParams::set_video_option(const QString &key, const QString &value) { video_opts_.insert(key, value); } -void EncodingParams::SetVideoBitRate(const int64_t &rate) +void EncodingParams::set_video_bit_rate(const int64_t &rate) { video_bit_rate_ = rate; } -void EncodingParams::SetVideoMaxBitRate(const int64_t &rate) +void EncodingParams::set_video_max_bit_rate(const int64_t &rate) { video_max_bit_rate_ = rate; } -void EncodingParams::SetVideoBufferSize(const int64_t &sz) +void EncodingParams::set_video_buffer_size(const int64_t &sz) { video_buffer_size_ = sz; } +void EncodingParams::set_video_threads(const int &threads) +{ + video_threads_ = threads; +} + const QString &EncodingParams::filename() const { return filename_; @@ -123,6 +129,11 @@ const int64_t &EncodingParams::video_buffer_size() const return video_buffer_size_; } +const int &EncodingParams::video_threads() const +{ + return video_threads_; +} + bool EncodingParams::audio_enabled() const { return audio_enabled_; diff --git a/app/codec/encoder.h b/app/codec/encoder.h index 198697991..05ce58760 100644 --- a/app/codec/encoder.h +++ b/app/codec/encoder.h @@ -43,10 +43,11 @@ public: void EnableVideo(const VideoRenderingParams& video_params, const QString& vcodec); void EnableAudio(const AudioRenderingParams& audio_params, const QString& acodec); - void SetVideoOption(const QString& key, const QString& value); - void SetVideoBitRate(const int64_t& rate); - void SetVideoMaxBitRate(const int64_t& rate); - void SetVideoBufferSize(const int64_t& sz); + void set_video_option(const QString& key, const QString& value); + void set_video_bit_rate(const int64_t& rate); + void set_video_max_bit_rate(const int64_t& rate); + void set_video_buffer_size(const int64_t& sz); + void set_video_threads(const int& threads); const QString& filename() const; @@ -57,6 +58,7 @@ public: const int64_t& video_bit_rate() const; const int64_t& video_max_bit_rate() const; const int64_t& video_buffer_size() const; + const int& video_threads() const; bool audio_enabled() const; const QString& audio_codec() const; @@ -75,6 +77,7 @@ private: int64_t video_bit_rate_; int64_t video_max_bit_rate_; int64_t video_buffer_size_; + int video_threads_; bool audio_enabled_; QString audio_codec_; diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index 24c753acd..b99f071c7 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -468,7 +468,15 @@ bool FFmpegEncoder::SetupCodecContext(AVStream* stream, AVCodecContext* codec_ct } AVDictionary* codec_opts = nullptr; - av_dict_set(&codec_opts, "threads", "auto", 0); + + // Set thread count + if (params().video_threads() == 0) { + av_dict_set(&codec_opts, "threads", "auto", 0); + } else { + char thread_val[10]; + sprintf_s(thread_val, 10, "%d", params().video_threads()); + av_dict_set(&codec_opts, "threads", thread_val, 0); + } // Try to open encoder error_code = avcodec_open2(codec_ctx, codec, &codec_opts); diff --git a/app/dialog/export/CMakeLists.txt b/app/dialog/export/CMakeLists.txt index 4a592ff21..b1ef3ae2c 100644 --- a/app/dialog/export/CMakeLists.txt +++ b/app/dialog/export/CMakeLists.txt @@ -20,6 +20,8 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/export/export.h dialog/export/export.cpp + dialog/export/exportadvancedvideodialog.h + dialog/export/exportadvancedvideodialog.cpp dialog/export/exportaudiotab.h dialog/export/exportaudiotab.cpp dialog/export/exportcodec.h diff --git a/app/dialog/export/codec/h264section.cpp b/app/dialog/export/codec/h264section.cpp index 8d64647a2..42833f665 100644 --- a/app/dialog/export/codec/h264section.cpp +++ b/app/dialog/export/codec/h264section.cpp @@ -78,7 +78,7 @@ void H264Section::AddOpts(EncodingParams *params) if (method == kConstantRateFactor) { // Simply set CRF value - params->SetVideoOption(QStringLiteral("crf"), QString::number(crf_section_->GetValue())); + params->set_video_option(QStringLiteral("crf"), QString::number(crf_section_->GetValue())); } else { @@ -95,11 +95,11 @@ void H264Section::AddOpts(EncodingParams *params) } // Disable CRF encoding - params->SetVideoOption(QStringLiteral("crf"), QStringLiteral("-1")); + params->set_video_option(QStringLiteral("crf"), QStringLiteral("-1")); - params->SetVideoBitRate(target_rate); - params->SetVideoMaxBitRate(max_rate); - params->SetVideoBufferSize(2000000); + params->set_video_bit_rate(target_rate); + params->set_video_max_bit_rate(max_rate); + params->set_video_buffer_size(2000000); } } diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 81b7f845b..bf1b083eb 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -531,6 +531,8 @@ ExportParams ExportDialog::GenerateParams() const params.EnableVideo(video_render_params, video_codec.id()); + params.set_video_threads(video_tab_->threads()); + video_tab_->GetCodecSection()->AddOpts(¶ms); params.set_color_transform(video_tab_->CurrentOCIOColorSpace()); diff --git a/app/dialog/export/exportadvancedvideodialog.cpp b/app/dialog/export/exportadvancedvideodialog.cpp new file mode 100644 index 000000000..bbaba2ee6 --- /dev/null +++ b/app/dialog/export/exportadvancedvideodialog.cpp @@ -0,0 +1,42 @@ +#include "exportadvancedvideodialog.h" + +#include +#include +#include + +OLIVE_NAMESPACE_ENTER + +ExportAdvancedVideoDialog::ExportAdvancedVideoDialog(QWidget *parent) : + QDialog(parent) +{ + setWindowTitle(tr("Advanced")); + + QGridLayout* layout = new QGridLayout(this); + + int row = 0; + + layout->addWidget(new QLabel(tr("Threads:")), row, 0); + + thread_slider_ = new IntegerSlider(); + thread_slider_->SetMinimum(0); + layout->addWidget(thread_slider_, row, 1); + + row++; + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(buttons, &QDialogButtonBox::accepted, this, &ExportAdvancedVideoDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &ExportAdvancedVideoDialog::reject); + layout->addWidget(buttons, row, 0, 1, 2); +} + +int ExportAdvancedVideoDialog::threads() const +{ + return static_cast(thread_slider_->GetValue()); +} + +void ExportAdvancedVideoDialog::set_threads(int t) +{ + thread_slider_->SetValue(t); +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/dialog/export/exportadvancedvideodialog.h b/app/dialog/export/exportadvancedvideodialog.h new file mode 100644 index 000000000..81b2c524f --- /dev/null +++ b/app/dialog/export/exportadvancedvideodialog.h @@ -0,0 +1,27 @@ +#ifndef EXPORTADVANCEDVIDEODIALOG_H +#define EXPORTADVANCEDVIDEODIALOG_H + +#include + +#include "render/backend/exportparams.h" +#include "widget/slider/integerslider.h" + +OLIVE_NAMESPACE_ENTER + +class ExportAdvancedVideoDialog : public QDialog +{ + Q_OBJECT +public: + ExportAdvancedVideoDialog(QWidget* parent = nullptr); + + int threads() const; + void set_threads(int t); + +private: + IntegerSlider* thread_slider_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // EXPORTADVANCEDVIDEODIALOG_H diff --git a/app/dialog/export/exportvideotab.cpp b/app/dialog/export/exportvideotab.cpp index 352d86910..ae4acfca7 100644 --- a/app/dialog/export/exportvideotab.cpp +++ b/app/dialog/export/exportvideotab.cpp @@ -24,8 +24,10 @@ #include #include #include +#include #include "core.h" +#include "exportadvancedvideodialog.h" #include "render/backend/exportparams.h" #include "render/colormanager.h" @@ -33,7 +35,8 @@ OLIVE_NAMESPACE_ENTER ExportVideoTab::ExportVideoTab(ColorManager* color_manager, QWidget *parent) : QWidget(parent), - color_manager_(color_manager) + color_manager_(color_manager), + threads_(0) { QVBoxLayout* outer_layout = new QVBoxLayout(this); @@ -106,6 +109,11 @@ H264Section *ExportVideoTab::h264_section() const return h264_section_; } +const int &ExportVideoTab::threads() const +{ + return threads_; +} + QWidget* ExportVideoTab::SetupResolutionSection() { int row = 0; @@ -196,6 +204,12 @@ QWidget *ExportVideoTab::SetupCodecSection() h264_section_ = new H264Section(); codec_stack_->addWidget(h264_section_); + row++; + + QPushButton* advanced_btn = new QPushButton(tr("Advanced")); + connect(advanced_btn, &QPushButton::clicked, this, &ExportVideoTab::OpenAdvancedDialog); + codec_layout->addWidget(advanced_btn, row, 1); + return codec_group; } @@ -204,4 +218,15 @@ void ExportVideoTab::MaintainAspectRatioChanged(bool val) scaling_method_combobox_->setEnabled(!val); } +void ExportVideoTab::OpenAdvancedDialog() +{ + ExportAdvancedVideoDialog d(this); + + d.set_threads(threads_); + + if (d.exec() == QDialog::Accepted) { + threads_ = d.threads(); + } +} + OLIVE_NAMESPACE_EXIT diff --git a/app/dialog/export/exportvideotab.h b/app/dialog/export/exportvideotab.h index 1332f14a5..7ad0c72d6 100644 --- a/app/dialog/export/exportvideotab.h +++ b/app/dialog/export/exportvideotab.h @@ -57,6 +57,8 @@ public: ImageSection* image_section() const; H264Section* h264_section() const; + const int& threads() const; + signals: void ColorSpaceChanged(const QString& colorspace); @@ -83,9 +85,13 @@ private: ColorManager* color_manager_; + int threads_; + private slots: void MaintainAspectRatioChanged(bool val); + void OpenAdvancedDialog(); + }; OLIVE_NAMESPACE_EXIT From 549ecc448ad5b472707b628ed31c45f8d8da47b9 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 30 Apr 2020 18:54:13 +1000 Subject: [PATCH 43/69] ffmpegencoder: use qstring rather than sprint_s (which is only available on msvc) --- app/codec/ffmpeg/ffmpegencoder.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index b99f071c7..86bbf9309 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -473,9 +473,8 @@ bool FFmpegEncoder::SetupCodecContext(AVStream* stream, AVCodecContext* codec_ct if (params().video_threads() == 0) { av_dict_set(&codec_opts, "threads", "auto", 0); } else { - char thread_val[10]; - sprintf_s(thread_val, 10, "%d", params().video_threads()); - av_dict_set(&codec_opts, "threads", thread_val, 0); + QString thread_val = QString::number(params().video_threads()); + av_dict_set(&codec_opts, "threads", thread_val.toUtf8(), 0); } // Try to open encoder From dc355dca46a89c8c1e2858449799be28c270b933 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 30 Apr 2020 18:54:30 +1000 Subject: [PATCH 44/69] export: multithread the color space conversion --- app/render/backend/exporter.cpp | 89 +++++++++++++++++++++------------ app/render/backend/exporter.h | 6 ++- 2 files changed, 61 insertions(+), 34 deletions(-) diff --git a/app/render/backend/exporter.cpp b/app/render/backend/exporter.cpp index aa8af3165..611c77fbe 100644 --- a/app/render/backend/exporter.cpp +++ b/app/render/backend/exporter.cpp @@ -20,6 +20,8 @@ #include "exporter.h" +#include + #include "render/backend/audio/audiobackend.h" #include "render/backend/opengl/openglbackend.h" #include "render/colormanager.h" @@ -177,17 +179,6 @@ void Exporter::EncodeFrame() while (cached_frames_.contains(waiting_for_frame_)) { FramePtr frame = cached_frames_.take(waiting_for_frame_); - // OCIO conversion requires a frame in 32F format - if (frame->format() != PixelFormat::PIX_FMT_RGBA32F) { - frame = PixelFormat::ConvertPixelFormat(frame, PixelFormat::PIX_FMT_RGBA32F); - } - - // Color conversion must be done with unassociated alpha, and the pipeline is always associated - ColorManager::DisassociateAlpha(frame); - - // Convert color space - color_processor_->ConvertFrame(frame); - // Encode (may require re-associating alpha?) QMetaObject::invokeMethod(encoder_, "WriteFrame", @@ -233,29 +224,13 @@ QMatrix4x4 Exporter::GenerateMatrix(ExportParams::VideoScalingMethod method, int return preview_matrix; } -void Exporter::FrameRendered(const rational &time, FramePtr value) +void Exporter::FrameRendered(const rational &time, FramePtr frame) { - debug_timer_.stop(); - - const QMap& time_hash_map = video_backend_->frame_cache()->time_hash_map(); - - QByteArray this_hash = time_hash_map.value(time); - - qDebug() << "Received" << this_hash.toHex(); - - QList matching_times = time_hash_map.keys(this_hash); - - foreach (const rational& t, matching_times) { - qDebug() << " Matches" << t.toDouble(); - - cached_frames_.insert(t, value); - } - - qDebug() << " Waiting for" << waiting_for_frame_.toDouble(); - - debug_timer_.start(); - - EncodeFrame(); + // Start color space conversion in another thread + QtConcurrent::run(this, + &Exporter::FrameColorConvert, + time, + frame); } void Exporter::AudioRendered() @@ -353,4 +328,52 @@ void Exporter::DebugTimerMessage() qDebug() << "Still waiting for" << waiting_for_frame_.toDouble(); } +void Exporter::FrameColorConvert(const rational &time, FramePtr frame) +{ + // OCIO conversion requires a frame in 32F format + if (frame->format() != PixelFormat::PIX_FMT_RGBA32F) { + frame = PixelFormat::ConvertPixelFormat(frame, PixelFormat::PIX_FMT_RGBA32F); + } + + // Color conversion must be done with unassociated alpha, and the pipeline is always associated + ColorManager::DisassociateAlpha(frame); + + // Convert color space + color_processor_->ConvertFrame(frame); + + // Re-associate alpha + ColorManager::ReassociateAlpha(frame); + + QMetaObject::invokeMethod(this, + "FrameColorFinished", + Qt::QueuedConnection, + OLIVE_NS_ARG(rational, time), + OLIVE_NS_ARG(FramePtr, frame)); +} + +void Exporter::FrameColorFinished(const rational &time, FramePtr frame) +{ + debug_timer_.stop(); + + const QMap& time_hash_map = video_backend_->frame_cache()->time_hash_map(); + + QByteArray this_hash = time_hash_map.value(time); + + qDebug() << "Received" << this_hash.toHex(); + + QList matching_times = time_hash_map.keys(this_hash); + + foreach (const rational& t, matching_times) { + qDebug() << " Matches" << t.toDouble(); + + cached_frames_.insert(t, frame); + } + + qDebug() << " Waiting for" << waiting_for_frame_.toDouble(); + + debug_timer_.start(); + + EncodeFrame(); +} + OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/exporter.h b/app/render/backend/exporter.h index 3aaec6986..c5907a9e9 100644 --- a/app/render/backend/exporter.h +++ b/app/render/backend/exporter.h @@ -101,7 +101,7 @@ private: QTimer debug_timer_; private slots: - void FrameRendered(const rational &time, FramePtr value); + void FrameRendered(const rational &time, FramePtr frame); void AudioRendered(); @@ -117,6 +117,10 @@ private slots: void DebugTimerMessage(); + void FrameColorConvert(const rational &time, FramePtr frame); + + void FrameColorFinished(const OLIVE_NAMESPACE::rational &time, OLIVE_NAMESPACE::FramePtr frame); + }; OLIVE_NAMESPACE_EXIT From 5a83f3db7906bbca4935f3ea1fc7aaf942226448 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 30 Apr 2020 23:44:50 +1000 Subject: [PATCH 45/69] exportdialog: prevent widths/heights from becoming <= 0 --- app/dialog/export/exportvideotab.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/dialog/export/exportvideotab.cpp b/app/dialog/export/exportvideotab.cpp index ae4acfca7..c6a5b1d98 100644 --- a/app/dialog/export/exportvideotab.cpp +++ b/app/dialog/export/exportvideotab.cpp @@ -126,6 +126,7 @@ QWidget* ExportVideoTab::SetupResolutionSection() layout->addWidget(new QLabel(tr("Width:")), row, 0); width_slider_ = new IntegerSlider(); + width_slider_->SetMinimum(1); layout->addWidget(width_slider_, row, 1); row++; @@ -133,6 +134,7 @@ QWidget* ExportVideoTab::SetupResolutionSection() layout->addWidget(new QLabel(tr("Height:")), row, 0); height_slider_ = new IntegerSlider(); + height_slider_->SetMinimum(1); layout->addWidget(height_slider_, row, 1); row++; From 65e117c3defc62a4bf0be34592d1adaaa0146ad0 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 1 May 2020 00:13:00 +1000 Subject: [PATCH 46/69] exportdialog: warn against a non-even resolution --- app/dialog/export/export.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index bf1b083eb..9041046ec 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -283,6 +283,17 @@ void ExportDialog::accept() return; } + // Validate video resolution + if (video_enabled_->isChecked()) { + if (video_tab_->width_slider()->GetValue() % 2 != 0 + || video_tab_->height_slider()->GetValue() % 2 != 0) { + QMessageBox::critical(this, + tr("Invalid parameters"), + tr("Width and height must be multiples of 2.")); + return; + } + } + // Set up export parameters exporter_ = new Exporter(viewer_node_, color_manager_, GenerateParams()); @@ -500,8 +511,10 @@ void ExportDialog::SetUIElementsEnabled(bool enabled) preferences_area_->setEnabled(enabled); buttons_->setEnabled(enabled); + progress_bar_->setEnabled(!enabled); export_cancel_btn_->setEnabled(!enabled); elapsed_label_->setEnabled(!enabled); + remaining_label_->setEnabled(!enabled); } ExportParams ExportDialog::GenerateParams() const @@ -632,7 +645,6 @@ void ExportDialog::ExporterIsDone() SetUIElementsEnabled(true); } - exporter_->deleteLater(); exporter_ = nullptr; cancelled_ = false; From f371aff448d1d8313e236cfd887a030e58995202 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 1 May 2020 00:35:13 +1000 Subject: [PATCH 47/69] exportdialog: move messageboxes to sheets Improves UI on macOS. --- app/dialog/export/export.cpp | 100 +++++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 33 deletions(-) diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 9041046ec..222b00e2e 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -238,10 +238,13 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : void ExportDialog::accept() { if (!video_enabled_->isChecked() && !audio_enabled_->isChecked()) { - QMessageBox::critical(this, - tr("Invalid parameters"), - tr("Both video and audio are disabled. There's nothing to export."), - QMessageBox::Ok); + QMessageBox b(this); + b.setIcon(QMessageBox::Critical); + b.setWindowModality(Qt::WindowModal); + b.setWindowTitle(tr("Invalid parameters")); + b.setText(tr("Both video and audio are disabled. There's nothing to export.")); + b.addButton(QMessageBox::Ok); + b.exec(); return; } @@ -251,10 +254,16 @@ void ExportDialog::accept() // If it doesn't, see if the user wants to append it automatically. If not, we don't abort the export. if (!filename_edit_->text().endsWith(necessary_ext, Qt::CaseInsensitive)) { - if (QMessageBox::warning(this, - tr("Invalid filename"), - tr("The filename must contain the extension \".%1\". Would you like to append it automatically?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + QMessageBox b(this); + b.setIcon(QMessageBox::Warning); + b.setWindowModality(Qt::WindowModal); + b.setWindowTitle(tr("Invalid filename")); + b.setText(tr("The filename must contain the extension \".%1\". Would you like to append it " + "automatically?")); + b.addButton(QMessageBox::Yes); + b.addButton(QMessageBox::No); + + if (b.exec() == QMessageBox::Yes) { filename_edit_->setText(filename_edit_->text().append(necessary_ext)); } else { return; @@ -267,29 +276,43 @@ void ExportDialog::accept() // If the directory does not exist, try to create it if (!QDir(file_info.path()).mkpath(QStringLiteral("."))) { - QMessageBox::critical(this, - tr("Failed to create output directory"), - tr("The intended output directory doesn't exist and Olive couldn't create it. Please choose a different filename."), - QMessageBox::Ok); + QMessageBox b(this); + b.setIcon(QMessageBox::Critical); + b.setWindowModality(Qt::WindowModal); + b.setWindowTitle(tr("Failed to create output directory")); + b.setText(tr("The intended output directory doesn't exist and Olive couldn't create it. " + "Please choose a different filename.")); + b.addButton(QMessageBox::Ok); + b.exec(); return; } // Validate if the file exists and whether the user wishes to overwrite it - if (file_info.exists() - && QMessageBox::warning(this, - tr("Confirm Overwrite"), - tr("The file \"%1\" already exists. Do you want to overwrite it?").arg(filename_edit_->text()), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { - return; + if (file_info.exists()) { + QMessageBox b(this); + b.setIcon(QMessageBox::Warning); + b.setWindowModality(Qt::WindowModal); + b.setWindowTitle(tr("Confirm Overwrite")); + b.setText(tr("The file \"%1\" already exists. Do you want to overwrite it?") + .arg(filename_edit_->text())); + b.addButton(QMessageBox::Yes); + b.addButton(QMessageBox::No); + + if (b.exec() == QMessageBox::No) { + return; + } } // Validate video resolution if (video_enabled_->isChecked()) { if (video_tab_->width_slider()->GetValue() % 2 != 0 || video_tab_->height_slider()->GetValue() % 2 != 0) { - QMessageBox::critical(this, - tr("Invalid parameters"), - tr("Width and height must be multiples of 2.")); + QMessageBox b(this); + b.setIcon(QMessageBox::Critical); + b.setWindowModality(Qt::WindowModal); + b.setWindowTitle(tr("Invalid parameters")); + b.setText(tr("Width and height must be multiples of 2.")); + b.exec(); return; } } @@ -318,10 +341,15 @@ void ExportDialog::accept() void ExportDialog::closeEvent(QCloseEvent *e) { if (exporter_) { - if (QMessageBox::question(this, - tr("Still Exporting"), - tr("This sequence is still being exported. Do you wish to cancel it?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + QMessageBox b(this); + b.setIcon(QMessageBox::Question); + b.setWindowModality(Qt::WindowModal); + b.setWindowTitle(tr("Still Exporting")); + b.setText(tr("This sequence is still being exported. Do you wish to cancel it?")); + b.addButton(QMessageBox::Yes); + b.addButton(QMessageBox::No); + + if (b.exec() == QMessageBox::Yes) { CancelExport(); } else { e->ignore(); @@ -624,10 +652,13 @@ void ExportDialog::ExporterIsDone() progress_timer_.stop(); if (exporter_->GetExportStatus()) { - QMessageBox::information(this, - tr("Export Status"), - tr("Export completed successfully."), - QMessageBox::Ok); + QMessageBox b(this); + b.setIcon(QMessageBox::Information); + b.setWindowModality(Qt::WindowModal); + b.setWindowTitle(tr("Export Status")); + b.setText(tr("Export completed successfully.")); + b.addButton(QMessageBox::Ok); + b.exec(); QDialog::accept(); } else { @@ -636,10 +667,13 @@ void ExportDialog::ExporterIsDone() Core::instance()->main_window()->SetTaskbarButtonState(TBPF_ERROR); #endif - QMessageBox::critical(this, - tr("Export Status"), - tr("Export failed: %1").arg(exporter_->GetExportError()), - QMessageBox::Ok); + QMessageBox b(this); + b.setIcon(QMessageBox::Critical); + b.setWindowModality(Qt::WindowModal); + b.setWindowTitle(tr("Export Status")); + b.setText(tr("Export failed: %1").arg(exporter_->GetExportError())); + b.addButton(QMessageBox::Ok); + b.exec(); } SetUIElementsEnabled(true); From afc9cff30152113e78df7803d9e9f41bb793a958 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 1 May 2020 00:36:37 +1000 Subject: [PATCH 48/69] exporter: fixed segfault when cancelling introduced by multithreaded color space conversion --- app/render/backend/exporter.cpp | 65 ++++++++++++++---------- app/render/backend/exporter.h | 6 +-- app/render/backend/videorenderbackend.h | 2 +- app/render/backend/videorenderworker.cpp | 4 +- app/render/backend/videorenderworker.h | 2 +- 5 files changed, 45 insertions(+), 34 deletions(-) diff --git a/app/render/backend/exporter.cpp b/app/render/backend/exporter.cpp index 611c77fbe..5ec95611c 100644 --- a/app/render/backend/exporter.cpp +++ b/app/render/backend/exporter.cpp @@ -224,13 +224,39 @@ QMatrix4x4 Exporter::GenerateMatrix(ExportParams::VideoScalingMethod method, int return preview_matrix; } -void Exporter::FrameRendered(const rational &time, FramePtr frame) +FramePtr FrameColorConvert(ColorProcessorPtr processor, FramePtr frame) +{ + qDebug() << "Converting" << frame->timestamp(); + + // OCIO conversion requires a frame in 32F format + if (frame->format() != PixelFormat::PIX_FMT_RGBA32F) { + frame = PixelFormat::ConvertPixelFormat(frame, PixelFormat::PIX_FMT_RGBA32F); + } + + // Color conversion must be done with unassociated alpha, and the pipeline is always associated + ColorManager::DisassociateAlpha(frame); + + // Convert color space + processor->ConvertFrame(frame); + + // Re-associate alpha + ColorManager::ReassociateAlpha(frame); + + return frame; +} + +void Exporter::FrameRendered(FramePtr frame) { // Start color space conversion in another thread - QtConcurrent::run(this, - &Exporter::FrameColorConvert, - time, - frame); + QFutureWatcher* watcher = new QFutureWatcher(); + + connect(watcher, &QFutureWatcher::finished, this, &Exporter::FrameColorFinished); + + QFuture future = QtConcurrent::run(FrameColorConvert, + color_processor_, + frame); + + watcher->setFuture(future); } void Exporter::AudioRendered() @@ -328,36 +354,21 @@ void Exporter::DebugTimerMessage() qDebug() << "Still waiting for" << waiting_for_frame_.toDouble(); } -void Exporter::FrameColorConvert(const rational &time, FramePtr frame) +void Exporter::FrameColorFinished() { - // OCIO conversion requires a frame in 32F format - if (frame->format() != PixelFormat::PIX_FMT_RGBA32F) { - frame = PixelFormat::ConvertPixelFormat(frame, PixelFormat::PIX_FMT_RGBA32F); + if (!video_backend_ && !audio_backend_) { + return; } - // Color conversion must be done with unassociated alpha, and the pipeline is always associated - ColorManager::DisassociateAlpha(frame); + QFutureWatcher* watcher = static_cast< QFutureWatcher* >(sender()); + FramePtr frame = watcher->result(); + watcher->deleteLater(); - // Convert color space - color_processor_->ConvertFrame(frame); - - // Re-associate alpha - ColorManager::ReassociateAlpha(frame); - - QMetaObject::invokeMethod(this, - "FrameColorFinished", - Qt::QueuedConnection, - OLIVE_NS_ARG(rational, time), - OLIVE_NS_ARG(FramePtr, frame)); -} - -void Exporter::FrameColorFinished(const rational &time, FramePtr frame) -{ debug_timer_.stop(); const QMap& time_hash_map = video_backend_->frame_cache()->time_hash_map(); - QByteArray this_hash = time_hash_map.value(time); + QByteArray this_hash = time_hash_map.value(frame->timestamp()); qDebug() << "Received" << this_hash.toHex(); diff --git a/app/render/backend/exporter.h b/app/render/backend/exporter.h index c5907a9e9..6c07a0dcd 100644 --- a/app/render/backend/exporter.h +++ b/app/render/backend/exporter.h @@ -101,7 +101,7 @@ private: QTimer debug_timer_; private slots: - void FrameRendered(const rational &time, FramePtr frame); + void FrameRendered(FramePtr frame); void AudioRendered(); @@ -117,9 +117,7 @@ private slots: void DebugTimerMessage(); - void FrameColorConvert(const rational &time, FramePtr frame); - - void FrameColorFinished(const OLIVE_NAMESPACE::rational &time, OLIVE_NAMESPACE::FramePtr frame); + void FrameColorFinished(); }; diff --git a/app/render/backend/videorenderbackend.h b/app/render/backend/videorenderbackend.h index 7391013c7..852d075ea 100644 --- a/app/render/backend/videorenderbackend.h +++ b/app/render/backend/videorenderbackend.h @@ -110,7 +110,7 @@ signals: void RangeInvalidated(const TimeRange& range); - void GeneratedFrame(const rational &time, FramePtr frame); + void GeneratedFrame(FramePtr frame); private: bool TimeIsQueued(const TimeRange &time) const; diff --git a/app/render/backend/videorenderworker.cpp b/app/render/backend/videorenderworker.cpp index e9afae9f3..f64a0bdbd 100644 --- a/app/render/backend/videorenderworker.cpp +++ b/app/render/backend/videorenderworker.cpp @@ -269,7 +269,9 @@ void VideoRenderWorker::Download(const rational& time, QVariant texture, QString TextureToBuffer(texture, frame->width(), frame->height(), frame_gen_mat_, frame->data(), frame->linesize_pixels()); } - emit GeneratedFrame(time, frame); + frame->set_timestamp(time); + + emit GeneratedFrame(frame); } } diff --git a/app/render/backend/videorenderworker.h b/app/render/backend/videorenderworker.h index 32476f199..b1813b9b8 100644 --- a/app/render/backend/videorenderworker.h +++ b/app/render/backend/videorenderworker.h @@ -79,7 +79,7 @@ signals: void HashAlreadyExists(NodeDependency path, qint64 job_time, QByteArray hash); - void GeneratedFrame(const rational &time, FramePtr frame); + void GeneratedFrame(FramePtr frame); void Aborted(); From a9208294a0152d45c07fd296b81c4352a871fdfe Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 1 May 2020 00:49:09 +1000 Subject: [PATCH 49/69] exportdialog: automatically snap resolutions to even numbers --- app/dialog/export/export.cpp | 28 +++++++++++++++++++++++++--- app/dialog/export/export.h | 2 ++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 222b00e2e..23480684b 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -417,10 +417,27 @@ void ExportDialog::ResolutionChanged() if (video_tab_->maintain_aspect_checkbox()->isChecked()) { // Keep aspect ratio maintained if (sender() == video_tab_->height_slider()) { - video_tab_->width_slider()->SetValue(qRound(static_cast(video_tab_->height_slider()->GetValue()) * video_aspect_ratio_)); + + // Convert height to float + double new_width = video_tab_->height_slider()->GetValue(); + + // Generate width from aspect ratio + new_width *= video_aspect_ratio_; + + // Align to even number and set + video_tab_->width_slider()->SetValue(AlignEvenNumber(new_width)); + } else { - // This catches both the width slider changing and the maintain aspect ratio checkbox changing - video_tab_->height_slider()->SetValue(qRound(static_cast(video_tab_->width_slider()->GetValue()) / video_aspect_ratio_)); + + // Convert width to float + double new_height = video_tab_->width_slider()->GetValue(); + + // Generate height from aspect ratio + new_height /= video_aspect_ratio_; + + // Align to even number and set + video_tab_->height_slider()->SetValue(AlignEvenNumber(new_height)); + } } @@ -545,6 +562,11 @@ void ExportDialog::SetUIElementsEnabled(bool enabled) remaining_label_->setEnabled(!enabled); } +int ExportDialog::AlignEvenNumber(double d) +{ + return qCeil(d * 0.5) * 2; +} + ExportParams ExportDialog::GenerateParams() const { RenderMode::Mode render_mode = RenderMode::kOnline; diff --git a/app/dialog/export/export.h b/app/dialog/export/export.h index 82d610ced..25dd21d7d 100644 --- a/app/dialog/export/export.h +++ b/app/dialog/export/export.h @@ -55,6 +55,8 @@ private: void SetUIElementsEnabled(bool enabled); + static int AlignEvenNumber(double d); + ExportParams GenerateParams() const; static QString TimeToString(int64_t ms); From 472030f8f0727f06746a40ded72bc414abf299f9 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 1 May 2020 01:38:31 +1000 Subject: [PATCH 50/69] oiiodecoder: implemented workaround for oiio stride bug in versions < 2.1.12 --- app/codec/oiio/oiiodecoder.cpp | 28 ++++++++++++++++++++++++---- app/codec/oiio/oiiodecoder.h | 3 +++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index 813011f6a..ebd522c9e 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -192,8 +192,7 @@ FramePtr OIIODecoder::RetrieveVideo(const rational &timecode, const int& divider if (divider == 1) { - // Just a simple copy - buffer_->get_pixels(OIIO::ROI(), buffer_->spec().format, frame->data(), OIIO::AutoStride, frame->linesize_bytes()); + BufferToFrame(buffer_, frame); } else { @@ -204,8 +203,7 @@ FramePtr OIIODecoder::RetrieveVideo(const rational &timecode, const int& divider qWarning() << "OIIO resize failed"; } - // Just a simple copy - dst.get_pixels(OIIO::ROI(), dst.spec().format, frame->data(), OIIO::AutoStride, frame->linesize_bytes()); + BufferToFrame(&dst, frame); } @@ -233,6 +231,28 @@ QString OIIODecoder::GetIndexFilename() return QString(); } +void OIIODecoder::BufferToFrame(OIIO::ImageBuf *buf, FramePtr frame) +{ +#if OIIO_VERSION < 20112 + // + // Workaround for OIIO bug that ignores destination stride in versions OLDER than 2.1.12 + // + // See more: https://github.com/OpenImageIO/oiio/pull/2487 + // + for (int i=0;ispec().height;i++) { + memcpy(frame->data() + i * frame->linesize_bytes(), + reinterpret_cast(buf->localpixels()) + i * buf->scanline_stride(), + frame->width() * PixelFormat::BytesPerPixel(frame->format())); + } +#else + buf->get_pixels(OIIO::ROI(), + buf->spec().format, + frame->data(), + OIIO::AutoStride, + frame->linesize_bytes()); +#endif +} + bool OIIODecoder::FileTypeIsSupported(const QString& fn) { // We prioritize OIIO over FFmpeg to pick up still images more effectively, but some OIIO decoders (notably OpenJPEG) diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index 787596691..aaa8be388 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -54,6 +54,9 @@ private: #else std::unique_ptr image_; #endif + + static void BufferToFrame(OIIO::ImageBuf* buf, FramePtr frame); + static bool FileTypeIsSupported(const QString& fn); static int GetImageSequenceDigitCount(const QString& filename); From 95b7c9770cbbba91dd32078309a9e9ca700f26d8 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 1 May 2020 02:04:26 +1000 Subject: [PATCH 51/69] manageddisplay: fixed segfault when closing exportdialog Really affected any color managed display widget, including scopes, viewers, and the export dialog. --- app/widget/manageddisplay/manageddisplay.cpp | 5 +++++ app/widget/manageddisplay/manageddisplay.h | 2 ++ 2 files changed, 7 insertions(+) diff --git a/app/widget/manageddisplay/manageddisplay.cpp b/app/widget/manageddisplay/manageddisplay.cpp index 8d3d720fe..2208dfeb8 100644 --- a/app/widget/manageddisplay/manageddisplay.cpp +++ b/app/widget/manageddisplay/manageddisplay.cpp @@ -32,6 +32,11 @@ ManagedDisplayWidget::ManagedDisplayWidget(QWidget *parent) : setContextMenuPolicy(Qt::CustomContextMenu); } +ManagedDisplayWidget::~ManagedDisplayWidget() +{ + ContextCleanup(); +} + void ManagedDisplayWidget::ConnectColorManager(ColorManager *color_manager) { if (color_manager_ == color_manager) { diff --git a/app/widget/manageddisplay/manageddisplay.h b/app/widget/manageddisplay/manageddisplay.h index e0e3e3b3e..697247129 100644 --- a/app/widget/manageddisplay/manageddisplay.h +++ b/app/widget/manageddisplay/manageddisplay.h @@ -35,6 +35,8 @@ class ManagedDisplayWidget : public QOpenGLWidget public: ManagedDisplayWidget(QWidget* parent = nullptr); + virtual ~ManagedDisplayWidget() override; + /** * @brief Disconnect a ColorManager (equivalent to ConnectColorManager(nullptr)) */ From 1f76c33f6b468da76eece256cfac3cb7be667377 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 1 May 2020 02:09:59 +1000 Subject: [PATCH 52/69] crashhandler: include git commit hash --- app/common/crashhandler.cpp | 3 ++- app/dialog/crashhandler/crashhandler.cpp | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/common/crashhandler.cpp b/app/common/crashhandler.cpp index b8ac83a0c..ed24cfe05 100644 --- a/app/common/crashhandler.cpp +++ b/app/common/crashhandler.cpp @@ -41,7 +41,8 @@ OLIVE_NAMESPACE_ENTER -void crash_handler(int sig) { +void crash_handler(int sig) +{ QString log_path = QDir(FileFunctions::GetTempFilePath()).filePath(QStringLiteral("olive_crash")); QFile output(log_path); diff --git a/app/dialog/crashhandler/crashhandler.cpp b/app/dialog/crashhandler/crashhandler.cpp index 7645eb5f9..9e4ed444f 100644 --- a/app/dialog/crashhandler/crashhandler.cpp +++ b/app/dialog/crashhandler/crashhandler.cpp @@ -46,6 +46,7 @@ CrashHandlerDialog::CrashHandlerDialog(const char *log_file) edit->append(QStringLiteral("Run Environment: %1").arg(QSysInfo::currentCpuArchitecture())); edit->append(QStringLiteral("Kernel: %1 %2").arg(QSysInfo::kernelType(), QSysInfo::kernelVersion())); edit->append(QStringLiteral("System: %1 (%2 %3)").arg(QSysInfo::prettyProductName(), QSysInfo::productType(), QSysInfo::productVersion())); + edit->append(QStringLiteral("Version: %1").arg(GITHASH); edit->append(QString()); QDialogButtonBox* buttons = new QDialogButtonBox(); From 8c8bb45f7f88a29a5577d420dc51a85827ef499e Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 1 May 2020 02:11:26 +1000 Subject: [PATCH 53/69] crashhandler: include git commit hash with crash report --- app/common/crashhandler.cpp | 2 +- app/dialog/crashhandler/crashhandler.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/common/crashhandler.cpp b/app/common/crashhandler.cpp index ed24cfe05..615541493 100644 --- a/app/common/crashhandler.cpp +++ b/app/common/crashhandler.cpp @@ -49,7 +49,7 @@ void crash_handler(int sig) output.open(QFile::WriteOnly); QTextStream ostream(&output); - ostream << "Signal: " << sig << "\n\n"; + ostream << "Version: " << GITHASH << "\nSignal: " << sig << "\n\n"; #if defined(Q_OS_WINDOWS) // Use Windows stackwalk API diff --git a/app/dialog/crashhandler/crashhandler.cpp b/app/dialog/crashhandler/crashhandler.cpp index 9e4ed444f..7645eb5f9 100644 --- a/app/dialog/crashhandler/crashhandler.cpp +++ b/app/dialog/crashhandler/crashhandler.cpp @@ -46,7 +46,6 @@ CrashHandlerDialog::CrashHandlerDialog(const char *log_file) edit->append(QStringLiteral("Run Environment: %1").arg(QSysInfo::currentCpuArchitecture())); edit->append(QStringLiteral("Kernel: %1 %2").arg(QSysInfo::kernelType(), QSysInfo::kernelVersion())); edit->append(QStringLiteral("System: %1 (%2 %3)").arg(QSysInfo::prettyProductName(), QSysInfo::productType(), QSysInfo::productVersion())); - edit->append(QStringLiteral("Version: %1").arg(GITHASH); edit->append(QString()); QDialogButtonBox* buttons = new QDialogButtonBox(); From a2cba8c103b9109833339db71fc6aa6007b82075 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 1 May 2020 03:42:31 +1000 Subject: [PATCH 54/69] pixelformat: respect linesizes when converting pixel formats --- app/codec/oiio/oiiodecoder.h | 4 ++-- app/render/pixelformat.cpp | 29 +++++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index aaa8be388..249a1a346 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -48,6 +48,8 @@ public: virtual QString GetIndexFilename() override; + static void BufferToFrame(OIIO::ImageBuf* buf, FramePtr frame); + private: #if OIIO_VERSION < 10903 OIIO::ImageInput* image_; @@ -55,8 +57,6 @@ private: std::unique_ptr image_; #endif - static void BufferToFrame(OIIO::ImageBuf* buf, FramePtr frame); - static bool FileTypeIsSupported(const QString& fn); static int GetImageSequenceDigitCount(const QString& filename); diff --git a/app/render/pixelformat.cpp b/app/render/pixelformat.cpp index aa05beac8..2ddd251c1 100644 --- a/app/render/pixelformat.cpp +++ b/app/render/pixelformat.cpp @@ -25,6 +25,7 @@ #include #include +#include "codec/oiio/oiiodecoder.h" #include "common/define.h" #include "core.h" @@ -213,19 +214,39 @@ FramePtr PixelFormat::ConvertPixelFormat(FramePtr frame, const PixelFormat::Form return frame; } + // Create a destination frame with the same parameters FramePtr converted = Frame::Create(); - - // Copy parameters converted->set_video_params(VideoRenderingParams(frame->video_params().width(), frame->video_params().height(), dest_format)); converted->set_timestamp(frame->timestamp()); converted->allocate(); - OIIO::ImageBuf src(OIIO::ImageSpec(frame->width(), frame->height(), ChannelCount(frame->format()), GetOIIOTypeDesc(frame->format())), frame->data()); - OIIO::ImageBuf dst(OIIO::ImageSpec(converted->width(), converted->height(), ChannelCount(converted->format()), GetOIIOTypeDesc(converted->format())), converted->data()); + // Do the conversion through OIIO - create a buffer for the source image + OIIO::ImageBuf src(OIIO::ImageSpec(frame->width(), + frame->height(), + ChannelCount(frame->format()), + GetOIIOTypeDesc(frame->format()))); + + // Set the pixels (this is necessary as opposed to an OIIO buffer wrapper since Frame has + // linesizes) + src.set_pixels(OIIO::ROI(), + GetOIIOTypeDesc(frame->format()), + frame->const_data(), + OIIO::AutoStride, + frame->linesize_bytes()); + + // Create a destination OIIO buffer with our destination format + OIIO::ImageBuf dst(OIIO::ImageSpec(converted->width(), + converted->height(), + ChannelCount(converted->format()), + GetOIIOTypeDesc(converted->format()))); if (dst.copy_pixels(src)) { + + // Convert our buffer back to a frame + OIIODecoder::BufferToFrame(&dst, converted); + return converted; } else { return nullptr; From 6f2993477dc00095287c842cf917f74928e8102d Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 1 May 2020 03:51:56 +1000 Subject: [PATCH 55/69] added null check for audio that hasn't been indexed yet --- app/render/backend/audio/audiobackend.cpp | 66 ++++++++++++----------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/app/render/backend/audio/audiobackend.cpp b/app/render/backend/audio/audiobackend.cpp index 6b6f81ab2..f7450542a 100644 --- a/app/render/backend/audio/audiobackend.cpp +++ b/app/render/backend/audio/audiobackend.cpp @@ -65,43 +65,47 @@ void AudioBackend::ThreadCompletedCache(NodeDependency dep, NodeValueTable data, if (job_time == render_job_info_.value(dep.range())) { render_job_info_.remove(dep.range()); - QByteArray cached_samples = data.Get(NodeParam::kSamples).value()->toPackedData(); + SampleBufferPtr cached_sample_ptr = data.Get(NodeParam::kSamples).value(); - int offset = params().time_to_bytes(dep.in()); - int length = params().time_to_bytes(dep.range().length()); - int out_point = qMin(offset + length, params().time_to_bytes(GetSequenceLength())); + if (cached_sample_ptr) { + QByteArray cached_samples = cached_sample_ptr->toPackedData(); - if (offset < out_point) { - if (offset + length > out_point) { - length = out_point - offset; - } + int offset = params().time_to_bytes(dep.in()); + int length = params().time_to_bytes(dep.range().length()); + int out_point = qMin(offset + length, params().time_to_bytes(GetSequenceLength())); - QFile f(CachePathName()); - if (f.open(QFile::ReadWrite)) { - - if (f.size() < out_point && !f.resize(out_point)) { - qCritical() << "Failed to resize file" << CachePathName(); + if (offset < out_point) { + if (offset + length > out_point) { + length = out_point - offset; } - if (!f.seek(offset)) { - qCritical() << "Failed to seek file" << CachePathName(); + QFile f(CachePathName()); + if (f.open(QFile::ReadWrite)) { + + if (f.size() < out_point && !f.resize(out_point)) { + qCritical() << "Failed to resize file" << CachePathName(); + } + + if (!f.seek(offset)) { + qCritical() << "Failed to seek file" << CachePathName(); + } + + // Replace data with this data + int copy_length = qMin(length, cached_samples.size()); + + f.write(cached_samples.data(), copy_length); + + if (copy_length < length) { + + // Fill in remainder with silence + QByteArray empty_space(length - copy_length, 0); + f.write(empty_space); + } + + f.close(); + } else { + qWarning() << "Failed to write to cached PCM file"; } - - // Replace data with this data - int copy_length = qMin(length, cached_samples.size()); - - f.write(cached_samples.data(), copy_length); - - if (copy_length < length) { - - // Fill in remainder with silence - QByteArray empty_space(length - copy_length, 0); - f.write(empty_space); - } - - f.close(); - } else { - qWarning() << "Failed to write to cached PCM file"; } } } From be5e5042bdb4a73376d8e58c8795405cedaae4fe Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 1 May 2020 04:08:35 +1000 Subject: [PATCH 56/69] timeline: prevent rubberband from showing if the user right clicked --- app/widget/timelinewidget/tool/pointer.cpp | 2 +- .../timelinewidget/view/timelineview.cpp | 34 +++++++++++-------- app/widget/timelinewidget/view/timelineview.h | 3 +- .../view/timelineviewmouseevent.cpp | 7 ++++ .../view/timelineviewmouseevent.h | 5 +++ 5 files changed, 34 insertions(+), 17 deletions(-) diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 848b15585..31d17bb65 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -91,7 +91,7 @@ void TimelineWidget::PointerTool::MousePress(TimelineViewMouseEvent *event) if (!(event->GetModifiers() & Qt::AltModifier)) { parent()->SetBlockLinksSelected(item->block(), true); } - } else { + } else if (event->GetButton() == Qt::LeftButton) { // Start rubberband drag parent()->StartRubberBandSelect(true, !(event->GetModifiers() & Qt::AltModifier)); diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 04dc87864..bcf13b5b7 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -81,7 +81,7 @@ void TimelineView::mousePressEvent(QMouseEvent *event) return; } - TimelineViewMouseEvent timeline_event = CreateMouseEvent(event->pos(), event->modifiers()); + TimelineViewMouseEvent timeline_event = CreateMouseEvent(event); emit MousePressed(&timeline_event); } @@ -98,7 +98,7 @@ void TimelineView::mouseMoveEvent(QMouseEvent *event) return; } - TimelineViewMouseEvent timeline_event = CreateMouseEvent(event->pos(), event->modifiers()); + TimelineViewMouseEvent timeline_event = CreateMouseEvent(event); emit MouseMoved(&timeline_event); } @@ -115,14 +115,14 @@ void TimelineView::mouseReleaseEvent(QMouseEvent *event) return; } - TimelineViewMouseEvent timeline_event = CreateMouseEvent(event->pos(), event->modifiers()); + TimelineViewMouseEvent timeline_event = CreateMouseEvent(event); emit MouseReleased(&timeline_event); } void TimelineView::mouseDoubleClickEvent(QMouseEvent *event) { - TimelineViewMouseEvent timeline_event = CreateMouseEvent(event->pos(), event->modifiers()); + TimelineViewMouseEvent timeline_event = CreateMouseEvent(event); emit MouseDoubleClicked(&timeline_event); } @@ -146,7 +146,7 @@ void TimelineView::wheelEvent(QWheelEvent *event) void TimelineView::dragEnterEvent(QDragEnterEvent *event) { - TimelineViewMouseEvent timeline_event = CreateMouseEvent(event->pos(), event->keyboardModifiers()); + TimelineViewMouseEvent timeline_event = CreateMouseEvent(event->pos(), Qt::NoButton, event->keyboardModifiers()); timeline_event.SetMimeData(event->mimeData()); timeline_event.SetEvent(event); @@ -156,7 +156,7 @@ void TimelineView::dragEnterEvent(QDragEnterEvent *event) void TimelineView::dragMoveEvent(QDragMoveEvent *event) { - TimelineViewMouseEvent timeline_event = CreateMouseEvent(event->pos(), event->keyboardModifiers()); + TimelineViewMouseEvent timeline_event = CreateMouseEvent(event->pos(), Qt::NoButton, event->keyboardModifiers()); timeline_event.SetMimeData(event->mimeData()); timeline_event.SetEvent(event); @@ -171,7 +171,7 @@ void TimelineView::dragLeaveEvent(QDragLeaveEvent *event) void TimelineView::dropEvent(QDropEvent *event) { - TimelineViewMouseEvent timeline_event = CreateMouseEvent(event->pos(), event->keyboardModifiers()); + TimelineViewMouseEvent timeline_event = CreateMouseEvent(event->pos(), Qt::NoButton, event->keyboardModifiers()); timeline_event.SetMimeData(event->mimeData()); timeline_event.SetEvent(event); @@ -273,17 +273,21 @@ TimelineCoordinate TimelineView::SceneToCoordinate(const QPointF& pt) return TimelineCoordinate(SceneToTime(pt.x()), TrackReference(ConnectedTrackType(), SceneToTrack(pt.y()))); } -TimelineViewMouseEvent TimelineView::CreateMouseEvent(const QPoint& pos, Qt::KeyboardModifiers modifiers) +TimelineViewMouseEvent TimelineView::CreateMouseEvent(QMouseEvent *event) +{ + return CreateMouseEvent(event->pos(), event->button(), event->modifiers()); +} + +TimelineViewMouseEvent TimelineView::CreateMouseEvent(const QPoint& pos, Qt::MouseButton button, Qt::KeyboardModifiers modifiers) { QPointF scene_pt = mapToScene(pos); - TimelineViewMouseEvent timeline_event(scene_pt.x(), - GetScale(), - timebase(), - TrackReference(ConnectedTrackType(), SceneToTrack(scene_pt.y())), - modifiers); - - return timeline_event; + return TimelineViewMouseEvent(scene_pt.x(), + GetScale(), + timebase(), + TrackReference(ConnectedTrackType(), SceneToTrack(scene_pt.y())), + button, + modifiers); } int TimelineView::GetHeightOfAllTracks() const diff --git a/app/widget/timelinewidget/view/timelineview.h b/app/widget/timelinewidget/view/timelineview.h index ecf66c2b7..53daceca0 100644 --- a/app/widget/timelinewidget/view/timelineview.h +++ b/app/widget/timelinewidget/view/timelineview.h @@ -100,7 +100,8 @@ private: TimelineCoordinate ScreenToCoordinate(const QPoint& pt); TimelineCoordinate SceneToCoordinate(const QPointF& pt); - TimelineViewMouseEvent CreateMouseEvent(const QPoint &pos, Qt::KeyboardModifiers modifiers); + TimelineViewMouseEvent CreateMouseEvent(QMouseEvent* event); + TimelineViewMouseEvent CreateMouseEvent(const QPoint &pos, Qt::MouseButton button, Qt::KeyboardModifiers modifiers); int GetHeightOfAllTracks() const; diff --git a/app/widget/timelinewidget/view/timelineviewmouseevent.cpp b/app/widget/timelinewidget/view/timelineviewmouseevent.cpp index fa8baf8df..4e4dc183c 100644 --- a/app/widget/timelinewidget/view/timelineviewmouseevent.cpp +++ b/app/widget/timelinewidget/view/timelineviewmouseevent.cpp @@ -30,11 +30,13 @@ TimelineViewMouseEvent::TimelineViewMouseEvent(const qreal &scene_x, const double &scale_x, const rational &timebase, const TrackReference &track, + const Qt::MouseButton &button, const Qt::KeyboardModifiers &modifiers) : scene_x_(scene_x), scale_x_(scale_x), timebase_(timebase), track_(track), + button_(button), modifiers_(modifiers), source_event_(nullptr), mime_data_(nullptr) @@ -81,6 +83,11 @@ const qreal &TimelineViewMouseEvent::GetSceneX() const return scene_x_; } +const Qt::MouseButton &TimelineViewMouseEvent::GetButton() const +{ + return button_; +} + void TimelineViewMouseEvent::accept() { if (source_event_ != nullptr) diff --git a/app/widget/timelinewidget/view/timelineviewmouseevent.h b/app/widget/timelinewidget/view/timelineviewmouseevent.h index 65264078c..b99db096f 100644 --- a/app/widget/timelinewidget/view/timelineviewmouseevent.h +++ b/app/widget/timelinewidget/view/timelineviewmouseevent.h @@ -36,6 +36,7 @@ public: const double& scale_x, const rational& timebase, const TrackReference &track, + const Qt::MouseButton &button, const Qt::KeyboardModifiers& modifiers = Qt::NoModifier); TimelineCoordinate GetCoordinates(bool round_time = false) const; @@ -61,6 +62,8 @@ public: const qreal& GetSceneX() const; + const Qt::MouseButton& GetButton() const; + void accept(); void ignore(); @@ -71,6 +74,8 @@ private: TrackReference track_; + Qt::MouseButton button_; + Qt::KeyboardModifiers modifiers_; QEvent* source_event_; From baf954a4e0d0a928df3217db0c03f0c2483f2620 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 1 May 2020 14:00:40 +1000 Subject: [PATCH 57/69] cmake: add minimum macos version --- app/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 0c9c4661f..b22b69ced 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -75,6 +75,8 @@ if(APPLE) MACOSX_BUNDLE_ICON_FILE olive.icns RESOURCE "${OLIVE_ICON}" ) + + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9") endif() # Set compiler definitions From 9335815d20924aec91201b0713ac98a5e093296c Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 1 May 2020 14:20:35 +1000 Subject: [PATCH 58/69] waveform: ensure waveform updates on null buffers --- app/widget/scope/waveform/waveform.cpp | 112 ++++++++++++------------- 1 file changed, 55 insertions(+), 57 deletions(-) diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp index 756771676..211e66ef3 100644 --- a/app/widget/scope/waveform/waveform.cpp +++ b/app/widget/scope/waveform/waveform.cpp @@ -85,11 +85,15 @@ void WaveformScope::paintGL() f->glClearColor(0, 0, 0, 0); f->glClear(GL_COLOR_BUFFER_BIT); - if (!pipeline_ || !texture_.IsCreated()) { - return; - } + float waveform_scale = 0.80f; + float waveform_dim_x = width() * waveform_scale; + float waveform_dim_y = height() * waveform_scale; + float waveform_start_dim_x = (width() - waveform_dim_x) / 2.0f; + float waveform_start_dim_y = (height() - waveform_dim_y) / 2.0f; + float waveform_end_dim_x = width() - waveform_start_dim_x; + float waveform_end_dim_y = height() - waveform_start_dim_y; - { + if (buffer_ && pipeline_ && texture_.IsCreated()) { // Convert reference frame to display space framebuffer_.Attach(&managed_tex_); framebuffer_.Bind(); @@ -104,9 +108,7 @@ void WaveformScope::paintGL() framebuffer_.Release(); framebuffer_.Detach(); - } - { // Draw waveform through shader pipeline_->bind(); pipeline_->setUniformValue("ove_resolution", texture_.width(), texture_.height()); @@ -116,17 +118,10 @@ void WaveformScope::paintGL() pipeline_->setUniformValue("luma_coeffs", luma[0], luma[1], luma[2]); // Scale of the waveform relative to the viewport surface. - float waveform_scale = 0.80f; pipeline_->setUniformValue("waveform_scale", waveform_scale); - float waveform_dim_x = width() * waveform_scale; - float waveform_dim_y = height() * waveform_scale; pipeline_->setUniformValue( "waveform_dims", waveform_dim_x, waveform_dim_y); - float waveform_start_dim_x = (width() - waveform_dim_x) / 2.0f; - float waveform_start_dim_y = (height() - waveform_dim_y) / 2.0f; - float waveform_end_dim_x = width() - waveform_start_dim_x; - float waveform_end_dim_y = height() - waveform_start_dim_y; pipeline_->setUniformValue( "waveform_region", waveform_start_dim_x, waveform_start_dim_y, @@ -150,62 +145,65 @@ void WaveformScope::paintGL() OpenGLRenderFunctions::Blit(pipeline_); managed_tex_.Release(); - - QPainter p(this); - QFontMetrics font_metrics = QFontMetrics(QFont()); - QString label; - float ire_increment = 0.1f; - float ire_steps = int(1.0 / ire_increment); - QVector ire_lines(ire_steps + 1); - int font_x_offset = 0; - int font_y_offset = font_metrics.capHeight() / 2.0f; - - p.setCompositionMode(QPainter::CompositionMode_Plus); - - p.setPen(QColor(0.0, 0.6 * 255.0, 0.0)); - p.setFont(QFont()); - - for (int i=0; i <= ire_steps; i++) { - ire_lines[i].setLine( - waveform_start_dim_x, - (waveform_dim_y * (i * ire_increment)) + waveform_start_dim_y, - waveform_end_dim_x, - (waveform_dim_y * (i * ire_increment)) + waveform_start_dim_y); - label = QString::number(1.0 - (i * ire_increment), 'f', 1); - font_x_offset = QFontMetricsWidth(font_metrics, label) + 4; - - p.drawText( - waveform_start_dim_x - font_x_offset, - (waveform_dim_y * (i * ire_increment)) + waveform_start_dim_y + font_y_offset, - label); - } - p.drawLines(ire_lines); } + + // Draw line overlays + QPainter p(this); + QFontMetrics font_metrics = QFontMetrics(QFont()); + QString label; + float ire_increment = 0.1f; + float ire_steps = int(1.0 / ire_increment); + QVector ire_lines(ire_steps + 1); + int font_x_offset = 0; + int font_y_offset = font_metrics.capHeight() / 2.0f; + + p.setCompositionMode(QPainter::CompositionMode_Plus); + + p.setPen(QColor(0.0, 0.6 * 255.0, 0.0)); + p.setFont(QFont()); + + for (int i=0; i <= ire_steps; i++) { + ire_lines[i].setLine( + waveform_start_dim_x, + (waveform_dim_y * (i * ire_increment)) + waveform_start_dim_y, + waveform_end_dim_x, + (waveform_dim_y * (i * ire_increment)) + waveform_start_dim_y); + label = QString::number(1.0 - (i * ire_increment), 'f', 1); + font_x_offset = QFontMetricsWidth(font_metrics, label) + 4; + + p.drawText( + waveform_start_dim_x - font_x_offset, + (waveform_dim_y * (i * ire_increment)) + waveform_start_dim_y + font_y_offset, + label); + } + p.drawLines(ire_lines); } void WaveformScope::UploadTextureFromBuffer() { - if (!buffer_ || !isVisible()) { + if (!isVisible()) { return; } - makeCurrent(); + if (buffer_) { + makeCurrent(); - if (!texture_.IsCreated() - || texture_.width() != buffer_->width() - || texture_.height() != buffer_->height() - || texture_.format() != buffer_->format()) { - texture_.Destroy(); - managed_tex_.Destroy(); + if (!texture_.IsCreated() + || texture_.width() != buffer_->width() + || texture_.height() != buffer_->height() + || texture_.format() != buffer_->format()) { + texture_.Destroy(); + managed_tex_.Destroy(); - texture_.Create(context(), buffer_); - managed_tex_.Create(context(), buffer_->width(), buffer_->height(), buffer_->format()); - } else { - texture_.Upload(buffer_); + texture_.Create(context(), buffer_); + managed_tex_.Create(context(), buffer_->width(), buffer_->height(), buffer_->format()); + } else { + texture_.Upload(buffer_); + } + + doneCurrent(); } - doneCurrent(); - update(); } From 2125293a3c8ac91b954ee3398d4f1e2c85e63296 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 1 May 2020 14:38:49 +1000 Subject: [PATCH 59/69] appveyor: use ninja to build and try to prevent deploying on PRs --- .appveyor/build.bat | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.appveyor/build.bat b/.appveyor/build.bat index 59eca0e85..8e2e80d70 100644 --- a/.appveyor/build.bat +++ b/.appveyor/build.bat @@ -34,10 +34,13 @@ REM Add Qt and FFmpeg directory to path set PATH=%PATH%;C:\Qt\5.13.2\msvc2017_64\bin;%APPVEYOR_BUILD_FOLDER%\%FFMPEG_VER%-dev REM Run cmake -cmake -G "NMake Makefiles" . -DCMAKE_TOOLCHAIN_FILE=c:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo +cmake -G "Ninja" . -DCMAKE_TOOLCHAIN_FILE=c:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -REM Build with JOM -C:\Qt\Tools\QtCreator\bin\jom.exe || exit /B 1 +REM Build with Ninja +ninja.exe || exit /B 1 + +REM If this is a pull request, no further packaging/deploying needs to be done +if "%APPVEYOR_PULL_REQUEST_NUMBER%" == "" goto end REM Start building package mkdir olive-editor From 6433a0bb8cf6a82f5074a10e510ff694255ac307 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 1 May 2020 14:40:07 +1000 Subject: [PATCH 60/69] appveyor: fixed incorrect comparison in last commit --- .appveyor/build.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor/build.bat b/.appveyor/build.bat index 8e2e80d70..e2c6b553f 100644 --- a/.appveyor/build.bat +++ b/.appveyor/build.bat @@ -40,7 +40,7 @@ REM Build with Ninja ninja.exe || exit /B 1 REM If this is a pull request, no further packaging/deploying needs to be done -if "%APPVEYOR_PULL_REQUEST_NUMBER%" == "" goto end +if NOT "%APPVEYOR_PULL_REQUEST_NUMBER%" == "" goto end REM Start building package mkdir olive-editor From 42907973895af2dbd38525c69b3360b4d9628e3b Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 1 May 2020 16:33:46 +1000 Subject: [PATCH 61/69] colormanager: use env variable as default config if it exists --- app/render/colormanager.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/render/colormanager.cpp b/app/render/colormanager.cpp index f28a8ac29..8c549e4af 100644 --- a/app/render/colormanager.cpp +++ b/app/render/colormanager.cpp @@ -61,6 +61,16 @@ OCIO::ConstConfigRcPtr ColorManager::GetDefaultConfig() void ColorManager::SetUpDefaultConfig() { + if (!qgetenv("OCIO").isEmpty()) { + try { + default_config_ = OCIO::Config::CreateFromEnv(); + + return; + } catch (OCIO::Exception& e) { + qWarning() << "Failed to load config from OCIO environment variable, loading default instead"; + } + } + // Kind of hacky, but it'll work QString dir = QDir(FileFunctions::GetTempFilePath()).filePath(QStringLiteral("ocioconf")); From ca323b7bfd474e93dfbe167279ce7b1ffaf5c1b0 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 1 May 2020 16:57:13 +1000 Subject: [PATCH 62/69] updated contributing page with more information --- CONTRIBUTING.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f74cc0883..e472c9cf3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,19 @@ # Contributing to Olive +Thank you for your interest in contributing to Olive! In order to keep the code as readable and maintainable as possible, code submitted should abide by the following standards: + ### Standards When contributing to Olive, it's recommended to use the following rules: -* [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) -* 120 column limit \ No newline at end of file +* The code style generally follows the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) including, but not limited to: +** Indentation is 2 spaces wide, spaces only (no tabs) +** `lowercase_underscored_variable_names` +** `lowercase_underscored_functions()` or `SentenceCaseFunctions()` +** `class SentenceCaseClassesAndStructs {}` +** `kSentenceCaseConstants` prepended with a lowercase `k` +** `UPPERCASE_UNDERSCORED_MACROS` for variables or same style as functions for macro functions +** `class_member_variables_` end with a `_` +* 100 column limit (where it doesn't impair readability) +* Unix line endings (only LF no CRLF) +* Javadoc documentation where appropriate From e67dabf1ffb3a97333b327ecef7cc2dc0ee406a6 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 1 May 2020 17:01:16 +1000 Subject: [PATCH 63/69] keyframes: implemented deleting keyframes with the delete shortcut --- app/node/input.cpp | 13 +++++++++++++ app/node/input.h | 5 +++++ app/panel/curve/curve.cpp | 5 +++++ app/panel/curve/curve.h | 2 ++ app/panel/param/param.cpp | 5 +++++ app/panel/param/param.h | 2 ++ app/widget/curvewidget/curvewidget.cpp | 5 +++++ app/widget/curvewidget/curvewidget.h | 2 ++ app/widget/keyframeview/keyframeviewbase.cpp | 19 +++++++++++++++++++ app/widget/keyframeview/keyframeviewbase.h | 2 ++ app/widget/nodeparamview/nodeparamview.cpp | 5 +++++ app/widget/nodeparamview/nodeparamview.h | 2 ++ 12 files changed, 67 insertions(+) diff --git a/app/node/input.cpp b/app/node/input.cpp index e025f5ed7..33ab85da4 100644 --- a/app/node/input.cpp +++ b/app/node/input.cpp @@ -737,6 +737,19 @@ void NodeInput::remove_keyframe(NodeKeyframePtr key) emit_time_range(time_affected); } +NodeKeyframePtr NodeInput::get_keyframe_shared_ptr_from_raw(NodeKeyframe* raw) const +{ + foreach (const KeyframeTrack& track, keyframe_tracks_) { + foreach (NodeKeyframePtr key, track) { + if (key.get() == raw) { + return key; + } + } + } + + return nullptr; +} + void NodeInput::KeyframeTimeChanged() { NodeKeyframe* key = static_cast(sender()); diff --git a/app/node/input.h b/app/node/input.h index f14521bc1..2f271a4a1 100644 --- a/app/node/input.h +++ b/app/node/input.h @@ -172,6 +172,11 @@ public: */ void remove_keyframe(NodeKeyframePtr key); + /** + * @brief Hacky convenience function to turn a raw pointer into a shared pointer + */ + NodeKeyframePtr get_keyframe_shared_ptr_from_raw(NodeKeyframe *raw) const; + /** * @brief Return whether a keyframe exists at this time * diff --git a/app/panel/curve/curve.cpp b/app/panel/curve/curve.cpp index 233b637f5..2c9fc0e4b 100644 --- a/app/panel/curve/curve.cpp +++ b/app/panel/curve/curve.cpp @@ -37,6 +37,11 @@ NodeInput *CurvePanel::GetInput() const return static_cast(GetTimeBasedWidget())->GetInput(); } +void CurvePanel::DeleteSelected() +{ + static_cast(GetTimeBasedWidget())->DeleteSelected(); +} + void CurvePanel::SetInput(NodeInput *input) { static_cast(GetTimeBasedWidget())->SetInput(input); diff --git a/app/panel/curve/curve.h b/app/panel/curve/curve.h index 761b00a0f..4b25b2b22 100644 --- a/app/panel/curve/curve.h +++ b/app/panel/curve/curve.h @@ -34,6 +34,8 @@ public: NodeInput* GetInput() const; + virtual void DeleteSelected() override; + public slots: void SetInput(NodeInput* input); diff --git a/app/panel/param/param.cpp b/app/panel/param/param.cpp index 2544c7748..3c3f64e10 100644 --- a/app/panel/param/param.cpp +++ b/app/panel/param/param.cpp @@ -59,6 +59,11 @@ void ParamPanel::SetTimestamp(const int64_t ×tamp) } } +void ParamPanel::DeleteSelected() +{ + static_cast(GetTimeBasedWidget())->DeleteSelected(); +} + void ParamPanel::Retranslate() { SetTitle(tr("Parameter Editor")); diff --git a/app/panel/param/param.h b/app/panel/param/param.h index 0ad5523c4..0f6d43d20 100644 --- a/app/panel/param/param.h +++ b/app/panel/param/param.h @@ -38,6 +38,8 @@ public slots: virtual void SetTimestamp(const int64_t& timestamp) override; + virtual void DeleteSelected() override; + signals: void TimeTargetChanged(Node* node); diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index b31e31ea8..4cd9dba3e 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -168,6 +168,11 @@ void CurveWidget::SetVerticalScale(const double &vscale) view_->SetYScale(vscale); } +void CurveWidget::DeleteSelected() +{ + view_->DeleteSelected(); +} + void CurveWidget::changeEvent(QEvent *e) { if (e->type() == QEvent::LanguageChange) { diff --git a/app/widget/curvewidget/curvewidget.h b/app/widget/curvewidget/curvewidget.h index b0276bb7f..21e0400d0 100644 --- a/app/widget/curvewidget/curvewidget.h +++ b/app/widget/curvewidget/curvewidget.h @@ -47,6 +47,8 @@ public: const double& GetVerticalScale(); void SetVerticalScale(const double& vscale); + void DeleteSelected(); + protected: virtual void changeEvent(QEvent *) override; diff --git a/app/widget/keyframeview/keyframeviewbase.cpp b/app/widget/keyframeview/keyframeviewbase.cpp index 8c830f615..65009bef4 100644 --- a/app/widget/keyframeview/keyframeviewbase.cpp +++ b/app/widget/keyframeview/keyframeviewbase.cpp @@ -73,6 +73,25 @@ void KeyframeViewBase::SetYScale(const double &y_scale) } } +void KeyframeViewBase::DeleteSelected() +{ + QUndoCommand* command = new QUndoCommand(); + + QMap::const_iterator i; + + for (i=item_map_.constBegin(); i!=item_map_.constEnd(); i++) { + if (i.value()->isSelected()) { + NodeInput* input_parent = i.key()->parent(); + + new NodeParamRemoveKeyframeCommand(input_parent, + input_parent->get_keyframe_shared_ptr_from_raw(i.key()), + command); + } + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); +} + void KeyframeViewBase::RemoveKeyframe(NodeKeyframePtr key) { KeyframeAboutToBeRemoved(key.get()); diff --git a/app/widget/keyframeview/keyframeviewbase.h b/app/widget/keyframeview/keyframeviewbase.h index 71b46f6a7..000394641 100644 --- a/app/widget/keyframeview/keyframeviewbase.h +++ b/app/widget/keyframeview/keyframeviewbase.h @@ -40,6 +40,8 @@ public: const double& GetYScale() const; void SetYScale(const double& y_scale); + void DeleteSelected(); + public slots: void RemoveKeyframe(NodeKeyframePtr key); diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 8675794c1..0ddc3e2ed 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -225,6 +225,11 @@ Node *NodeParamView::GetTimeTarget() const return keyframe_view_->GetTimeTarget(); } +void NodeParamView::DeleteSelected() +{ + keyframe_view_->DeleteSelected(); +} + void NodeParamView::UpdateItemTime(const int64_t ×tamp) { rational time = Timecode::timestamp_to_time(timestamp, keyframe_view_->timebase()); diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index be5cb5835..6f71b0994 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -42,6 +42,8 @@ public: Node* GetTimeTarget() const; + void DeleteSelected(); + signals: void InputDoubleClicked(NodeInput* input); From b39403a4e9dd5580b61125319ad9c38f33b940f9 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 1 May 2020 17:03:06 +1000 Subject: [PATCH 64/69] fixed formatting on contributing page --- CONTRIBUTING.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e472c9cf3..e9e35c5a9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,13 +7,13 @@ Thank you for your interest in contributing to Olive! In order to keep the code When contributing to Olive, it's recommended to use the following rules: * The code style generally follows the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) including, but not limited to: -** Indentation is 2 spaces wide, spaces only (no tabs) -** `lowercase_underscored_variable_names` -** `lowercase_underscored_functions()` or `SentenceCaseFunctions()` -** `class SentenceCaseClassesAndStructs {}` -** `kSentenceCaseConstants` prepended with a lowercase `k` -** `UPPERCASE_UNDERSCORED_MACROS` for variables or same style as functions for macro functions -** `class_member_variables_` end with a `_` + * Indentation is 2 spaces wide, spaces only (no tabs) + * `lowercase_underscored_variable_names` + * `lowercase_underscored_functions()` or `SentenceCaseFunctions()` + * `class SentenceCaseClassesAndStructs {}` + * `kSentenceCaseConstants` prepended with a lowercase `k` + * `UPPERCASE_UNDERSCORED_MACROS` for variables or same style as functions for macro functions + * `class_member_variables_` end with a `_` * 100 column limit (where it doesn't impair readability) * Unix line endings (only LF no CRLF) * Javadoc documentation where appropriate From 4a225aff852b655b33d2b918e32a43586c5d3fad Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 1 May 2020 17:55:04 +1000 Subject: [PATCH 65/69] colormanager: show OCIO error if env config failed to load --- app/render/colormanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/render/colormanager.cpp b/app/render/colormanager.cpp index 8c549e4af..3a907b3ae 100644 --- a/app/render/colormanager.cpp +++ b/app/render/colormanager.cpp @@ -67,7 +67,7 @@ void ColorManager::SetUpDefaultConfig() return; } catch (OCIO::Exception& e) { - qWarning() << "Failed to load config from OCIO environment variable, loading default instead"; + qWarning() << "Failed to load config from OCIO environment variable config:" << e.what(); } } From e7400adabc5bb76e8e741ed174bd7a475c51159b Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 1 May 2020 18:19:08 +1000 Subject: [PATCH 66/69] oiiodecoder: added compatibility for OIIO 1.x --- app/codec/oiio/oiiodecoder.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index ebd522c9e..52653df05 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -240,9 +240,15 @@ void OIIODecoder::BufferToFrame(OIIO::ImageBuf *buf, FramePtr frame) // See more: https://github.com/OpenImageIO/oiio/pull/2487 // for (int i=0;ispec().height;i++) { + int width_in_bytes = frame->width() * PixelFormat::BytesPerPixel(frame->format()); + memcpy(frame->data() + i * frame->linesize_bytes(), +#if OIIO_VERSION < 10903 + reinterpret_cast(buf->localpixels()) + i * width_in_bytes, +#else reinterpret_cast(buf->localpixels()) + i * buf->scanline_stride(), - frame->width() * PixelFormat::BytesPerPixel(frame->format())); +#endif + width_in_bytes); } #else buf->get_pixels(OIIO::ROI(), From 4f23db02db40784fe8b627c5b4af9ea2efedb7aa Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 2 May 2020 15:36:35 +1000 Subject: [PATCH 67/69] preferences: removed integer quality modes --- .../preferences/tabs/preferencesqualitytab.cpp | 18 ++++++++++++++---- .../preferences/tabs/preferencesqualitytab.h | 3 +++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/app/dialog/preferences/tabs/preferencesqualitytab.cpp b/app/dialog/preferences/tabs/preferencesqualitytab.cpp index 39e37ca4c..32884c5f3 100644 --- a/app/dialog/preferences/tabs/preferencesqualitytab.cpp +++ b/app/dialog/preferences/tabs/preferencesqualitytab.cpp @@ -26,7 +26,6 @@ #include "audio/sampleformat.h" #include "render/colormanager.h" -#include "render/pixelformat.h" OLIVE_NAMESPACE_ENTER @@ -49,12 +48,12 @@ PreferencesQualityTab::PreferencesQualityTab() quality_stack_ = new QStackedWidget(); offline_group_ = new PreferencesQualityGroup(tr("Offline Quality")); - offline_group_->bit_depth_combobox()->setCurrentIndex(PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOffline)); + offline_group_->SetBitDepth(PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOffline)); offline_group_->ocio_method()->setCurrentIndex(ColorManager::GetOCIOMethodForMode(RenderMode::kOffline)); quality_stack_->addWidget(offline_group_); online_group_ = new PreferencesQualityGroup(tr("Online Quality")); - online_group_->bit_depth_combobox()->setCurrentIndex(PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOnline)); + online_group_->SetBitDepth(PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOnline)); online_group_->ocio_method()->setCurrentIndex(ColorManager::GetOCIOMethodForMode(RenderMode::kOnline)); quality_stack_->addWidget(online_group_); @@ -92,7 +91,8 @@ PreferencesQualityGroup::PreferencesQualityGroup(const QString &title, QWidget * PixelFormat::Format pix_fmt = static_cast(i); // We always render with an alpha channel internally - if (PixelFormat::FormatHasAlphaChannel(pix_fmt)) { + if (PixelFormat::FormatHasAlphaChannel(pix_fmt) + && PixelFormat::FormatIsFloat(pix_fmt)) { bit_depth_combobox_->addItem(PixelFormat::GetName(pix_fmt), i); } @@ -112,6 +112,16 @@ PreferencesQualityGroup::PreferencesQualityGroup(const QString &title, QWidget * quality_outer_layout->addStretch(); } +void PreferencesQualityGroup::SetBitDepth(PixelFormat::Format f) +{ + for (int i=0;icount();i++) { + if (bit_depth_combobox_->itemData(i) == f) { + bit_depth_combobox_->setCurrentIndex(i); + break; + } + } +} + QComboBox *PreferencesQualityGroup::bit_depth_combobox() { return bit_depth_combobox_; diff --git a/app/dialog/preferences/tabs/preferencesqualitytab.h b/app/dialog/preferences/tabs/preferencesqualitytab.h index 1219c485d..beae8ff62 100644 --- a/app/dialog/preferences/tabs/preferencesqualitytab.h +++ b/app/dialog/preferences/tabs/preferencesqualitytab.h @@ -26,6 +26,7 @@ #include #include +#include "render/pixelformat.h" #include "preferencestab.h" OLIVE_NAMESPACE_ENTER @@ -36,6 +37,8 @@ class PreferencesQualityGroup : public QGroupBox public: PreferencesQualityGroup(const QString& title, QWidget* parent = nullptr); + void SetBitDepth(PixelFormat::Format f); + QComboBox* bit_depth_combobox(); QComboBox* ocio_method(); From 676b18ba6e050257d951ae925fbdb9de490eecf4 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 2 May 2020 15:45:16 +1000 Subject: [PATCH 68/69] timelineviewblockitem: fixed incorrectly positioned underline --- app/widget/timelinewidget/view/timelineviewblockitem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/timelinewidget/view/timelineviewblockitem.cpp b/app/widget/timelinewidget/view/timelineviewblockitem.cpp index 7e182657f..81edaa2ed 100644 --- a/app/widget/timelinewidget/view/timelineviewblockitem.cpp +++ b/app/widget/timelinewidget/view/timelineviewblockitem.cpp @@ -145,7 +145,7 @@ void TimelineViewBlockItem::paint(QPainter *painter, const QStyleOptionGraphicsI QFontMetrics fm = painter->fontMetrics(); int text_width = qMin(qRound(rect().width()), QFontMetricsWidth(fm, block_->block_name())); - QPointF underline_start = rect().topLeft() + QPointF(0, fm.height()); + QPointF underline_start = rect().topLeft() + QPointF(0, text_top + fm.height()); QPointF underline_end = underline_start + QPointF(text_width, 0); painter->drawLine(underline_start, underline_end); From 90597bd08a8d42d2870ffa25bc855a5cd4f37213 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 2 May 2020 16:28:06 +1000 Subject: [PATCH 69/69] mainmenu: fixed bug in window menu --- app/window/mainwindow/mainmenu.cpp | 47 ++++++++++++++++++------------ app/window/mainwindow/mainmenu.h | 5 ---- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/app/window/mainwindow/mainmenu.cpp b/app/window/mainwindow/mainmenu.cpp index 6af40d4a1..46e7527f5 100644 --- a/app/window/mainwindow/mainmenu.cpp +++ b/app/window/mainwindow/mainmenu.cpp @@ -186,7 +186,6 @@ MainMenu::MainMenu(MainWindow *parent) : // WINDOW MENU // window_menu_ = new Menu(this, this, &MainMenu::WindowMenuAboutToShow); - connect(window_menu_, &Menu::aboutToHide, this, &MainMenu::WindowMenuAboutToHide); window_menu_separator_ = window_menu_->addSeparator(); window_maximize_panel_item_ = window_menu_->AddItem("maximizepanel", parent, &MainWindow::ToggleMaximizedPanel, "`"); window_lock_layout_item_ = window_menu_->AddItem("lockpanels", PanelManager::instance(), &PanelManager::SetPanelsLocked); @@ -365,27 +364,37 @@ void MainMenu::PlaybackMenuAboutToShow() void MainMenu::WindowMenuAboutToShow() { - // QMainWindow generates a perfectly usable menu for this purpose, we just need to copy it to the window menu - QMenu* panel_menu = static_cast(parentWidget())->createPopupMenu(); - QList panel_menu_actions = panel_menu->actions(); - - // Make sure when we delete the panel_menu, it doesn't delete the actions - foreach (QAction* panel_action, panel_menu_actions) { - panel_action->setParent(window_menu_); - } - - delete panel_menu; - - window_menu_->insertActions(window_menu_separator_, panel_menu_actions); - - window_lock_layout_item_->setChecked(PanelManager::instance()->ArePanelsLocked()); -} - -void MainMenu::WindowMenuAboutToHide() -{ + // Remove any previous items while (window_menu_->actions().first() != window_menu_separator_) { window_menu_->removeAction(window_menu_->actions().first()); } + + QList panel_actions; + + // Alphabetize actions - keeps actions in a consistent order since PanelManager::panels() is + // ordered from most recently focused to least, which may be confusing user experience. + foreach (PanelWidget* panel, PanelManager::instance()->panels()) { + QAction* panel_action = panel->toggleViewAction(); + + bool inserted = false; + + for (int i=0;itext() > panel_action->text()) { + panel_actions.insert(i, panel_action); + inserted = true; + break; + } + } + + if (!inserted) { + panel_actions.append(panel_action); + } + } + + // Add new items + window_menu_->insertActions(window_menu_separator_, panel_actions); + + window_lock_layout_item_->setChecked(PanelManager::instance()->ArePanelsLocked()); } void MainMenu::PopulateOpenRecent() diff --git a/app/window/mainwindow/mainmenu.h b/app/window/mainwindow/mainmenu.h index 2b6f3149b..de74d47fd 100644 --- a/app/window/mainwindow/mainmenu.h +++ b/app/window/mainwindow/mainmenu.h @@ -94,11 +94,6 @@ private slots: */ void WindowMenuAboutToShow(); - /** - * @brief Slot triggered just before the Window menu hides - */ - void WindowMenuAboutToHide(); - /** * @brief Adds items to open recent menu */