diff --git a/app/common/decibel.h b/app/common/decibel.h index 40a88135b..569b0a969 100644 --- a/app/common/decibel.h +++ b/app/common/decibel.h @@ -24,14 +24,25 @@ #include #include +//#define ALLOW_RETURNING_INFINITY + namespace olive { class Decibel { public: + // In basically all circumstances, this should calculate to 0.0 linear + static constexpr double MINIMUM = -200.0; + static double fromLinear(double linear) { - return double(20.0) * std::log10(linear); + double v = double(20.0) * std::log10(linear); +#ifndef ALLOW_RETURNING_INFINITY + if (std::isinf(v)) { + return MINIMUM; + } +#endif + return v; } static double toLinear(double decibel) @@ -49,7 +60,11 @@ public: static double fromLogarithmic(double logarithmic) { if (logarithmic < 0.001) - return -200.0; +#ifdef ALLOW_RETURNING_INFINITY + return std::numeric_limits::infinity(); +#else + return MINIMUM; +#endif else if (logarithmic > 0.99) return 0; else diff --git a/app/dialog/otioproperties/otiopropertiesdialog.h b/app/dialog/otioproperties/otiopropertiesdialog.h index ad66bbd9f..1e748ad4e 100644 --- a/app/dialog/otioproperties/otiopropertiesdialog.h +++ b/app/dialog/otioproperties/otiopropertiesdialog.h @@ -8,7 +8,7 @@ #include "common/define.h" #include "opentimelineio/timeline.h" #include "node/project/sequence/sequence.h" -#include "node/project/project.h" +#include "node/project.h" namespace olive { diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 9424f2203..d30ec6ac2 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -69,7 +69,9 @@ ClipBlock::ClipBlock() : QString ClipBlock::Name() const { - if (track()) { + if (connected_viewer_ && !connected_viewer_->GetLabel().isEmpty()) { + return connected_viewer_->GetLabel(); + } else if (track()) { if (track()->type() == Track::kVideo) { return tr("Video Clip"); } else if (track()->type() == Track::kAudio) { diff --git a/app/node/gizmo/line.cpp b/app/node/gizmo/line.cpp index 33f1a45bc..d10cce5f2 100644 --- a/app/node/gizmo/line.cpp +++ b/app/node/gizmo/line.cpp @@ -29,9 +29,16 @@ LineGizmo::LineGizmo(QObject *parent) : void LineGizmo::Draw(QPainter *p) const { - p->setBrush(Qt::NoBrush); - p->setPen(QPen(Qt::white, 0)); + // Draw transposed black + QLineF transposed = p->transform().map(line_); + transposed.translate(1, 1); + transposed = p->transform().inverted().map(transposed); + p->setPen(QPen(Qt::black, 0)); + p->drawLine(transposed); + // Draw normal white polygon + p->setPen(QPen(Qt::white, 0)); + p->setBrush(Qt::NoBrush); p->drawLine(line_); } diff --git a/app/node/gizmo/path.cpp b/app/node/gizmo/path.cpp index cd9642aec..b9733e869 100644 --- a/app/node/gizmo/path.cpp +++ b/app/node/gizmo/path.cpp @@ -30,9 +30,16 @@ PathGizmo::PathGizmo(QObject *parent) : void PathGizmo::Draw(QPainter *p) const { + // Draw transposed black + QPainterPath transposed = p->transform().map(path_); + transposed.translate(1, 1); + transposed = p->transform().inverted().map(transposed); + p->setPen(QPen(Qt::black, 0)); + p->drawPath(transposed); + + // Draw normal white polygon p->setPen(QPen(Qt::white, 0)); p->setBrush(Qt::NoBrush); - p->drawPath(path_); } diff --git a/app/node/gizmo/point.cpp b/app/node/gizmo/point.cpp index d60ccf5bd..213948ba1 100644 --- a/app/node/gizmo/point.cpp +++ b/app/node/gizmo/point.cpp @@ -46,7 +46,7 @@ void PointGizmo::Draw(QPainter *p) const QRectF rect = GetDrawingRect(p->transform(), GetStandardRadius()); if (shape_ != kAnchorPoint) { - p->setPen(Qt::NoPen); + p->setPen(QPen(Qt::black, 0)); p->setBrush(Qt::white); } diff --git a/app/node/gizmo/polygon.cpp b/app/node/gizmo/polygon.cpp index 8279d7fea..602f4a984 100644 --- a/app/node/gizmo/polygon.cpp +++ b/app/node/gizmo/polygon.cpp @@ -29,9 +29,16 @@ PolygonGizmo::PolygonGizmo(QObject *parent) void PolygonGizmo::Draw(QPainter *p) const { + // Draw transposed black + QPolygonF transposed = p->transform().map(polygon_); + transposed.translate(1, 1); + transposed = p->transform().inverted().map(transposed); + p->setPen(QPen(Qt::black, 0)); + p->drawPolyline(transposed); + + // Draw normal white polygon p->setPen(QPen(Qt::white, 0)); p->setBrush(Qt::NoBrush); - p->drawPolyline(polygon_); } diff --git a/app/node/gizmo/text.cpp b/app/node/gizmo/text.cpp index 6d6816948..7b966ee40 100644 --- a/app/node/gizmo/text.cpp +++ b/app/node/gizmo/text.cpp @@ -32,6 +32,12 @@ TextGizmo::TextGizmo(QObject *parent) } +void TextGizmo::SetRect(const QRectF &r) +{ + rect_ = r; + emit RectChanged(rect_); +} + void TextGizmo::UpdateInputHtml(const QString &s, const rational &time) { if (input_.IsValid()) { @@ -41,4 +47,10 @@ void TextGizmo::UpdateInputHtml(const QString &s, const rational &time) } } +void TextGizmo::SetVerticalAlignment(Qt::Alignment va) +{ + valign_ = va; + emit VerticalAlignmentChanged(valign_); +} + } diff --git a/app/node/gizmo/text.h b/app/node/gizmo/text.h index 5c3218671..df2aca92a 100644 --- a/app/node/gizmo/text.h +++ b/app/node/gizmo/text.h @@ -33,7 +33,7 @@ public: explicit TextGizmo(QObject *parent = nullptr); const QRectF &GetRect() const { return rect_; } - void SetRect(const QRectF &r) { rect_ = r; } + void SetRect(const QRectF &r); const QString &GetHtml() const { return text_; } void SetHtml(const QString &t) { text_ = t; } @@ -42,21 +42,14 @@ public: void UpdateInputHtml(const QString &s, const rational &time); - Qt::Alignment GetVerticalAlignment() const - { - return valign_; - } - - void SetVerticalAlignment(Qt::Alignment va) - { - valign_ = va; - emit VerticalAlignmentChanged(valign_); - } + Qt::Alignment GetVerticalAlignment() const { return valign_; } + void SetVerticalAlignment(Qt::Alignment va); signals: void Activated(); void Deactivated(); void VerticalAlignmentChanged(Qt::Alignment va); + void RectChanged(const QRectF &r); private: QRectF rect_; diff --git a/app/node/group/group.cpp b/app/node/group/group.cpp index 9ca495e1f..d4afec050 100644 --- a/app/node/group/group.cpp +++ b/app/node/group/group.cpp @@ -160,6 +160,10 @@ bool NodeGroup::GetInner(NodeInput *input) { if (NodeGroup *g = dynamic_cast(input->node())) { const NodeInput &passthrough = g->GetInputFromID(input->input()); + if (!passthrough.IsValid()) { + return false; + } + input->set_node(passthrough.node()); input->set_input(passthrough.input()); return true; diff --git a/app/node/input/value/valuenode.cpp b/app/node/input/value/valuenode.cpp index 1303b91d3..5520d2cf9 100644 --- a/app/node/input/value/valuenode.cpp +++ b/app/node/input/value/valuenode.cpp @@ -35,6 +35,7 @@ const QVector ValueNode::kSupportedTypes = { NodeValue::kText, NodeValue::kMatrix, NodeValue::kFont, + NodeValue::kBoolean, }; #define super Node diff --git a/app/node/project/serializer/serializer220403.cpp b/app/node/project/serializer/serializer220403.cpp index a7f4eced6..64bb15551 100644 --- a/app/node/project/serializer/serializer220403.cpp +++ b/app/node/project/serializer/serializer220403.cpp @@ -329,6 +329,13 @@ ProjectSerializer220403::LoadData ProjectSerializer220403::Load(Project *project } } } + + // Clear duplicate label (to facilitate #2147) + if (ClipBlock *c = dynamic_cast(n)) { + if (c->connected_viewer() && c->GetLabel() == c->connected_viewer()->GetLabel()) { + c->SetLabel(QString()); + } + } } return load_data; diff --git a/app/task/project/loadotio/loadotio.cpp b/app/task/project/loadotio/loadotio.cpp index d6c2802fb..eeabdc092 100644 --- a/app/task/project/loadotio/loadotio.cpp +++ b/app/task/project/loadotio/loadotio.cpp @@ -40,12 +40,12 @@ #include "node/distort/transform/transformdistortnode.h" #include "node/generator/matrix/matrix.h" #include "node/math/math/math.h" +#include "node/nodeundo.h" #include "node/project/folder/folder.h" #include "node/project/footage/footage.h" #include "node/project/sequence/sequence.h" +#include "timeline/timelineundogeneral.h" #include "window/mainwindow/mainwindowundo.h" -#include "widget/nodeview/nodeviewundo.h" -#include "widget/timelinewidget/undo/timelineundogeneral.h" namespace olive { diff --git a/app/task/project/loadotio/loadotio.h b/app/task/project/loadotio/loadotio.h index 9ee9d3a2a..4c0785ff9 100644 --- a/app/task/project/loadotio/loadotio.h +++ b/app/task/project/loadotio/loadotio.h @@ -24,7 +24,7 @@ #ifdef USE_OTIO #include "common/otioutils.h" -#include "node/project/project.h" +#include "node/project.h" #include "task/project/load/loadbasetask.h" namespace olive { diff --git a/app/task/project/saveotio/saveotio.h b/app/task/project/saveotio/saveotio.h index 2fcbd1241..44678bd5c 100644 --- a/app/task/project/saveotio/saveotio.h +++ b/app/task/project/saveotio/saveotio.h @@ -27,7 +27,7 @@ #include #include "common/otioutils.h" -#include "node/project/project.h" +#include "node/project.h" #include "task/task.h" namespace olive { diff --git a/app/timeline/timelineundopointer.cpp b/app/timeline/timelineundopointer.cpp index 864ce468b..c54941704 100644 --- a/app/timeline/timelineundopointer.cpp +++ b/app/timeline/timelineundopointer.cpp @@ -203,6 +203,8 @@ void TrackSlideCommand::redo() in_adjacent_remove_command_->redo_now(); } + + we_removed_in_adjacent_ = true; } else { // Simply resize adjacent in_adjacent_->set_length_and_media_out(in_adjacent_->length() + movement_); @@ -225,6 +227,8 @@ void TrackSlideCommand::redo() out_adjacent_remove_command_->redo_now(); } + + we_removed_out_adjacent_ = true; } else { // Simply resize adjacent out_adjacent_->set_length_and_media_in(out_adjacent_->length() - movement_); @@ -246,9 +250,13 @@ void TrackSlideCommand::undo() // We created this, so we can remove it now track_->RippleRemoveBlock(in_adjacent_); in_adjacent_->setParent(&memory_manager_); - } else if (in_adjacent_remove_command_) { - // We removed this, so we can restore it now - in_adjacent_remove_command_->undo_now(); + } else if (we_removed_in_adjacent_) { + if (in_adjacent_remove_command_) { + // We removed this, so we can restore it now + in_adjacent_remove_command_->undo_now(); + } + + track_->InsertBlockBefore(in_adjacent_, blocks_.first()); } else { // Simply resize adjacent in_adjacent_->set_length_and_media_out(in_adjacent_->length() - movement_); @@ -259,8 +267,12 @@ void TrackSlideCommand::undo() // We created this, so we can remove it now track_->RippleRemoveBlock(out_adjacent_); out_adjacent_->setParent(&memory_manager_); - } else if (out_adjacent_remove_command_) { - out_adjacent_remove_command_->undo_now(); + } else if (we_removed_out_adjacent_) { + if (out_adjacent_remove_command_) { + out_adjacent_remove_command_->undo_now(); + } + + track_->InsertBlockAfter(out_adjacent_, blocks_.last()); } else { out_adjacent_->set_length_and_media_in(out_adjacent_->length() + movement_); } diff --git a/app/timeline/timelineundopointer.h b/app/timeline/timelineundopointer.h index 05adf590e..ad5acf197 100644 --- a/app/timeline/timelineundopointer.h +++ b/app/timeline/timelineundopointer.h @@ -116,8 +116,10 @@ public: track_(track), blocks_(moving_blocks), movement_(movement), + we_removed_in_adjacent_(false), in_adjacent_(in_adjacent), in_adjacent_remove_command_(nullptr), + we_removed_out_adjacent_(false), out_adjacent_(out_adjacent), out_adjacent_remove_command_(nullptr) { @@ -148,9 +150,11 @@ private: rational movement_; bool we_created_in_adjacent_; + bool we_removed_in_adjacent_; Block* in_adjacent_; UndoCommand* in_adjacent_remove_command_; bool we_created_out_adjacent_; + bool we_removed_out_adjacent_; Block* out_adjacent_; UndoCommand* out_adjacent_remove_command_; diff --git a/app/widget/curvewidget/curveview.cpp b/app/widget/curvewidget/curveview.cpp index df51b25b3..81d00bf2e 100644 --- a/app/widget/curvewidget/curveview.cpp +++ b/app/widget/curvewidget/curveview.cpp @@ -27,6 +27,7 @@ #include #include +#include "common/decibel.h" #include "common/qtutils.h" #include "node/nodeundo.h" #include "widget/keyframeview/keyframeviewundo.h" diff --git a/app/widget/handmovableview/handmovableview.cpp b/app/widget/handmovableview/handmovableview.cpp index 83052dea3..4a916a0da 100644 --- a/app/widget/handmovableview/handmovableview.cpp +++ b/app/widget/handmovableview/handmovableview.cpp @@ -157,7 +157,11 @@ bool HandMovableView::WheelEventIsAZoomEvent(QWheelEvent *event) qreal HandMovableView::GetScrollZoomMultiplier(QWheelEvent *event) { - return 1.0 + (static_cast(event->angleDelta().x() + event->angleDelta().y()) * 0.001); + qreal v = (static_cast(event->angleDelta().x() + event->angleDelta().y()) * 0.001); + if (event->inverted()) { + v = -v; + } + return 1.0 + v; } void HandMovableView::wheelEvent(QWheelEvent *event) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index cf12c44e5..c29006023 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -311,8 +311,7 @@ void NodeParamView::RequestEditTextInViewer() { NodeParamViewItem *item = static_cast(sender()); - focused_node_ = item; - emit FocusedNodeChanged(item->GetNode()); + SetSelectedNodes({item}); emit RequestViewerToStartEditingText(); } @@ -726,6 +725,7 @@ void NodeParamView::AddNode(Node *n, Node *ctx, NodeParamViewContext *context) connect(item, &NodeParamViewItem::ArrayExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate); connect(item, &NodeParamViewItem::ExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate); connect(item, &NodeParamViewItem::Moved, this, &NodeParamView::QueueKeyframePositionUpdate); + connect(item, &NodeParamViewItem::InputArraySizeChanged, this, &NodeParamView::InputArraySizeChanged); item->SetKeyframeConnections(keyframe_view_->AddKeyframesOfNode(n)); } @@ -999,4 +999,42 @@ void NodeParamView::GroupInputPassthroughRemoved(NodeGroup *group, const NodeInp } } +void NodeParamView::InputArraySizeChanged(const QString &input, int, int new_size) +{ + NodeParamViewItem *sender = static_cast(this->sender()); + + KeyframeView::NodeConnections &connections = sender->GetKeyframeConnections(); + KeyframeView::InputConnections &inputs = connections[input]; + + int adj_new_size = new_size + 1; + + if (adj_new_size != inputs.size()) { + if (adj_new_size < inputs.size()) { + // Remove elements from keyframe view + for (int i = adj_new_size; i < inputs.size(); i++) { + const KeyframeView::ElementConnections &ec = inputs.at(i); + for (auto kc : ec) { + keyframe_view_->RemoveKeyframesOfTrack(kc); + } + } + + // Resize vector to match new size + inputs.resize(adj_new_size); + } else { + // Add elements + int old_size = inputs.size(); + + // Resize vector to match + inputs.resize(adj_new_size); + + // Fill in extra elements + for (int i = old_size; i < inputs.size(); i++) { + inputs[i] = keyframe_view_->AddKeyframesOfElement(NodeInput(sender->GetNode(), input, i - 1)); + } + } + } + + QueueKeyframePositionUpdate(); +} + } diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 296abf392..da63e638a 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -190,6 +190,8 @@ private slots: void RequestEditTextInViewer(); + void InputArraySizeChanged(const QString &input, int old_size, int new_size); + }; } diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 41d09db57..304d80057 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -50,7 +50,8 @@ NodeParamViewItem::NodeParamViewItem(Node *node, NodeParamViewCheckBoxBehavior c body_(nullptr), node_(node), create_checkboxes_(create_checkboxes), - ctx_(nullptr) + ctx_(nullptr), + time_target_(nullptr) { node_->Retranslate(); @@ -58,6 +59,7 @@ NodeParamViewItem::NodeParamViewItem(Node *node, NodeParamViewCheckBoxBehavior c RecreateBody(); connect(node_, &Node::LabelChanged, this, &NodeParamViewItem::Retranslate); + connect(node_, &Node::InputArraySizeChanged, this, &NodeParamViewItem::InputArraySizeChanged); // FIXME: Implemented to pick up when an input is set to hidden or not - DEFINITELY not a fast // way of doing this, but "fine" for now. @@ -96,6 +98,7 @@ void NodeParamViewItem::RecreateBody() connect(body_, &NodeParamViewItemBody::RequestEditTextInViewer, this, &NodeParamViewItem::RequestEditTextInViewer); body_->Retranslate(); body_->SetTimebase(timebase_); + body_->SetTimeTarget(time_target_); SetBody(body_); } @@ -117,6 +120,7 @@ void NodeParamViewItem::SetInputChecked(const NodeInput &input, bool e) NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget *parent) : QWidget(parent), node_(node), + time_target_(nullptr), create_checkboxes_(create_checkboxes) { QGridLayout* root_layout = new QGridLayout(this); @@ -265,22 +269,32 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const if (node->IsInputConnectable(input)) { UpdateUIForEdgeConnection(input_ref); } + + SetTimeTargetOnInputUI(ui_objects); + SetTimebaseOnInputUI(ui_objects); } void NodeParamViewItemBody::SetTimeTarget(ViewerOutput *target) { + time_target_ = target; + foreach (const InputUI& ui_obj, input_ui_map_) { - // Only keyframable inputs have a key control widget - if (ui_obj.key_control) { - ui_obj.key_control->SetTimeTarget(target); - } - if (ui_obj.connected_label) { - ui_obj.connected_label->SetViewerNode(target); - } - ui_obj.widget_bridge->SetTimeTarget(target); + SetTimeTargetOnInputUI(ui_obj); } } +void NodeParamViewItemBody::SetTimeTargetOnInputUI(const InputUI &ui_obj) +{ + // Only keyframable inputs have a key control widget + if (ui_obj.key_control) { + ui_obj.key_control->SetTimeTarget(time_target_); + } + if (ui_obj.connected_label) { + ui_obj.connected_label->SetViewerNode(time_target_); + } + ui_obj.widget_bridge->SetTimeTarget(time_target_); +} + void NodeParamViewItemBody::Retranslate() { for (auto i=input_ui_map_.begin(); i!=input_ui_map_.end(); i++) { @@ -444,7 +458,7 @@ void NodeParamViewItemBody::ArrayAppendClicked() for (auto it=array_ui_.cbegin(); it!=array_ui_.cend(); it++) { if (it.value().append_btn == sender()) { NodeInput real_input = NodeGroup::ResolveInput(NodeInput(it.key().node, it.key().input)); - Core::instance()->undo_stack()->push(new NodeArrayInsertCommand(real_input.node(), real_input.input(), real_input.GetArraySize()+1)); + Core::instance()->undo_stack()->push(new NodeArrayInsertCommand(real_input.node(), real_input.input(), real_input.GetArraySize())); break; } } @@ -492,10 +506,15 @@ void NodeParamViewItemBody::SetTimebase(const rational& timebase) timebase_ = timebase; foreach (const InputUI& ui_obj, input_ui_map_) { - ui_obj.widget_bridge->SetTimebase(timebase); + SetTimebaseOnInputUI(ui_obj); } } +void NodeParamViewItemBody::SetTimebaseOnInputUI(const InputUI& ui_obj) +{ + ui_obj.widget_bridge->SetTimebase(timebase_); +} + void NodeParamViewItemBody::SetInputChecked(const NodeInput &input, bool e) { if (input_ui_map_.contains(input)) { diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index eb52e44d3..56149064c 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -104,6 +104,9 @@ private: NodeParamViewArrayButton* append_btn; }; + void SetTimeTargetOnInputUI(const InputUI &ui); + void SetTimebaseOnInputUI(const InputUI &ui); + Node *node_; QHash array_ui_; @@ -112,6 +115,8 @@ private: rational timebase_; + ViewerOutput *time_target_; + NodeParamViewCheckBoxBehavior create_checkboxes_; QHash input_group_lookup_; @@ -165,6 +170,8 @@ public: void SetTimeTarget(ViewerOutput* target) { + time_target_ = target; + body_->SetTimeTarget(target); } @@ -194,7 +201,7 @@ public: void SetInputChecked(const NodeInput &input, bool e); - const KeyframeView::NodeConnections &GetKeyframeConnections() const + KeyframeView::NodeConnections &GetKeyframeConnections() { return keyframe_connections_; } @@ -213,6 +220,8 @@ signals: void RequestEditTextInViewer(); + void InputArraySizeChanged(const QString &input, int old_size, int new_size); + protected slots: virtual void Retranslate() override; @@ -225,6 +234,8 @@ private: Node *ctx_; + ViewerOutput *time_target_; + rational timebase_; KeyframeView::NodeConnections keyframe_connections_; diff --git a/app/widget/timebased/timebasedview.cpp b/app/widget/timebased/timebasedview.cpp index 6576cf4b3..8f83970fb 100644 --- a/app/widget/timebased/timebasedview.cpp +++ b/app/widget/timebased/timebasedview.cpp @@ -133,6 +133,8 @@ void TimeBasedView::ZoomIntoCursorPosition(QWheelEvent *event, double scale_mult void TimeBasedView::SetYScale(const double &y_scale) { + Q_ASSERT(y_scale > 0); + y_scale_ = y_scale; if (y_axis_enabled_) { diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index ae42257da..bc6ff74e3 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -424,7 +424,6 @@ void ImportTool::DropGhosts(bool insert, MultiUndoCommand *parent_command) ClipBlock* clip = new ClipBlock(); block = clip; clip->set_media_in(ghost->GetMediaIn()); - clip->SetLabel(footage_stream.footage->GetLabel()); command->add_child(new NodeAddCommand(dst_graph, clip)); // Position clip in its own context diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 6c2296ab8..33adebd93 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -718,6 +718,7 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event) if (!relinks.empty()) { for (auto it=relinks.cbegin(); it!=relinks.cend(); it++) { + // Re-connect links on duplicate clips for (auto jt=it.key()->links().cbegin(); jt!=it.key()->links().cend(); jt++) { Node *link = *jt; Node *copy_link = relinks.value(link); @@ -725,6 +726,24 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event) command->add_child(new NodeLinkCommand(it.value(), copy_link, true)); } } + + // Re-connect transitions where applicable + if (ClipBlock *og_clip = dynamic_cast(it.key())) { + ClipBlock *cp_clip = static_cast(it.value()); + + TransitionBlock *og_in_transition = og_clip->in_transition(); + TransitionBlock *og_out_transition = og_clip->out_transition(); + + if (og_in_transition && relinks.contains(og_in_transition)) { + TransitionBlock *cp_in_transition = static_cast(relinks.value(og_in_transition)); + command->add_child(new NodeEdgeAddCommand(cp_clip, NodeInput(cp_in_transition, TransitionBlock::kInBlockInput))); + } + + if (og_out_transition && relinks.contains(og_out_transition)) { + TransitionBlock *cp_out_transition = static_cast(relinks.value(og_out_transition)); + command->add_child(new NodeEdgeAddCommand(cp_clip, NodeInput(cp_out_transition, TransitionBlock::kOutBlockInput))); + } + } } } diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 791e0454a..70e973ddd 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -1376,10 +1376,10 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos) Menu* zoom_menu = new Menu(tr("Zoom"), &menu); menu.addMenu(zoom_menu); - zoom_menu->addAction(tr("Fit"))->setData(0); + zoom_menu->addAction(tr("Fit"))->setData(-1); for (int i=0;iaddAction(tr("%1%").arg(z))->setData(z); + double z = ViewerSizer::kZoomLevels[i]; + zoom_menu->addAction(tr("%1%").arg(z * 100.0))->setData(z); } connect(zoom_menu, &QMenu::triggered, this, &ViewerWidget::SetZoomFromMenu); @@ -1822,7 +1822,8 @@ void ViewerWidget::UpdateRendererAudioParameters() void ViewerWidget::SetZoomFromMenu(QAction *action) { - sizer_->SetZoom(action->data().toInt()); + auto s = sizer_->GetContainerSize(); + sizer_->SetZoomAnchored(action->data().toDouble(), s.width()/2, s.height()/2); } void ViewerWidget::ViewerInvalidatedVideoRange(const TimeRange &range) diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 0fcb70e40..2a78df02c 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -677,6 +677,10 @@ QTransform ViewerDisplayWidget::GenerateGizmoTransform(NodeTraverser >, const NodeGizmo *ViewerDisplayWidget::TryGizmoPress(const NodeValueRow &row, const QPointF &p) { + if (!gizmos_) { + return nullptr; + } + for (auto it=gizmos_->GetGizmos().crbegin(); it!=gizmos_->GetGizmos().crend(); it++) { NodeGizmo *gizmo = *it; if (gizmo->IsVisible()) { @@ -704,7 +708,11 @@ NodeGizmo *ViewerDisplayWidget::TryGizmoPress(const NodeValueRow &row, const QPo void ViewerDisplayWidget::OpenTextGizmo(TextGizmo *text, QMouseEvent *event) { + GenerateGizmoTransforms(); + gizmos_->UpdateGizmoPositions(gizmo_db_, NodeGlobals(gizmo_params_, gizmo_audio_params_, gizmo_draw_time_, LoopMode::kLoopModeOff)); + active_text_gizmo_ = text; + connect(active_text_gizmo_, &TextGizmo::RectChanged, this, &ViewerDisplayWidget::UpdateActiveTextGizmoSize); text_transform_ = GenerateGizmoTransform(); text_transform_inverted_ = text_transform_.inverted(); @@ -736,9 +744,7 @@ void ViewerDisplayWidget::OpenTextGizmo(TextGizmo *text, QMouseEvent *event) connect(text_edit_, &ViewerTextEditor::destroyed, this, &ViewerDisplayWidget::TextEditDestroyed); // Set text editor's size to logical size - QRectF text_rect = text->GetRect(); - text_edit_pos_ = text_rect.topLeft(); - text_edit_->setGeometry(text_rect.toRect()); + QRectF text_rect = UpdateActiveTextGizmoSize(); // Emit text gizmo activation signal emit text->Activated(); @@ -808,9 +814,9 @@ bool ViewerDisplayWidget::OnMousePress(QMouseEvent *event) return true; - } else if (text_edit_) { + } else if (text_edit_ && ForwardMouseEventToTextEdit(event, true)) { - return ForwardMouseEventToTextEdit(event, true); + return true; } else if (event->button() == Qt::LeftButton) { @@ -821,8 +827,7 @@ bool ViewerDisplayWidget::OnMousePress(QMouseEvent *event) add_band_end_ = add_band_start_; add_band_ = true; - } else if (gizmos_ - && (current_gizmo_ = TryGizmoPress(gizmo_db_, gizmo_last_draw_transform_inverted_.map(event->pos())))) { + } else if ((current_gizmo_ = TryGizmoPress(gizmo_db_, gizmo_last_draw_transform_inverted_.map(event->pos())))) { // Handle gizmo click gizmo_start_drag_ = event->pos(); @@ -856,18 +861,9 @@ bool ViewerDisplayWidget::OnMouseMove(QMouseEvent *event) return true; - } else if (text_edit_) { + } else if (text_edit_ && ForwardMouseEventToTextEdit(event)) { - if (event->buttons() == Qt::NoButton) { - QPointF mapped = text_transform_inverted_.map(event->pos()) - text_edit_pos_; - if (mapped.x() >= 0 && mapped.y() >= 0 && mapped.x() < text_edit_->width() && mapped.y() < text_edit_->height()) { - inner_widget()->setCursor(Qt::IBeamCursor); - } else { - inner_widget()->unsetCursor(); - } - } - - return ForwardMouseEventToTextEdit(event); + return true; } else if (add_band_) { @@ -927,9 +923,9 @@ bool ViewerDisplayWidget::OnMouseRelease(QMouseEvent *e) return true; - } else if (text_edit_) { + } else if (text_edit_ && ForwardMouseEventToTextEdit(e)) { - return ForwardMouseEventToTextEdit(e); + return true; } else if (add_band_) { @@ -964,8 +960,8 @@ bool ViewerDisplayWidget::OnMouseRelease(QMouseEvent *e) bool ViewerDisplayWidget::OnMouseDoubleClick(QMouseEvent *event) { - if (text_edit_) { - return ForwardMouseEventToTextEdit(event); + if (text_edit_ && ForwardMouseEventToTextEdit(event)) { + return true; } else if (event->button() == Qt::LeftButton && gizmos_) { QPointF ptr = TransformViewerSpaceToBufferSpace(event->pos()); foreach (NodeGizmo *g, gizmos_->GetGizmos()) { @@ -1148,13 +1144,31 @@ void ViewerDisplayWidget::ForwardDragEventToTextEdit(T *e) bool ViewerDisplayWidget::ForwardMouseEventToTextEdit(QMouseEvent *event, bool check_if_outside) { + if (current_gizmo_) { + return false; + } + // Transform screen mouse coords to world mouse coords QPointF local_pos = GetVirtualPosForTextEdit(event->pos()); + if (event->type() == QEvent::MouseMove && event->buttons() == Qt::NoButton) { + QPointF mapped = text_transform_inverted_.map(event->pos()) - text_edit_pos_; + if (mapped.x() >= 0 && mapped.y() >= 0 && mapped.x() < text_edit_->width() && mapped.y() < text_edit_->height()) { + inner_widget()->setCursor(Qt::IBeamCursor); + } else { + inner_widget()->unsetCursor(); + } + } + if (check_if_outside) { if (local_pos.x() < 0 || local_pos.x() >= text_edit_->width() || local_pos.y() < 0 || local_pos.y() >= text_edit_->height()) { - CloseTextEditor(); - return true; + // Allow clicking other gizmos so the user can resize while the text editor is active + if ((current_gizmo_ = TryGizmoPress(gizmo_db_, gizmo_last_draw_transform_inverted_.map(event->pos())))) { + return false; + } else { + CloseTextEditor(); + return true; + } } } @@ -1167,7 +1181,11 @@ bool ViewerDisplayWidget::ForwardMouseEventToTextEdit(QMouseEvent *event, bool c bool ViewerDisplayWidget::ForwardEventToTextEdit(QEvent *event) { qApp->sendEvent(text_edit_->viewport(), event); - return event->isAccepted(); + bool e = event->isAccepted(); + if (e) { + update(); + } + return e; } QPointF ViewerDisplayWidget::AdjustPosByVAlign(QPointF p) @@ -1191,6 +1209,9 @@ void ViewerDisplayWidget::CloseTextEditor() { text_edit_->deleteLater(); text_edit_ = nullptr; + + disconnect(active_text_gizmo_, &TextGizmo::RectChanged, this, &ViewerDisplayWidget::UpdateActiveTextGizmoSize); + active_text_gizmo_ = nullptr; } void ViewerDisplayWidget::GenerateGizmoTransforms() @@ -1382,4 +1403,12 @@ void ViewerDisplayWidget::FocusChanged(QWidget *old, QWidget *now) } } +QRectF ViewerDisplayWidget::UpdateActiveTextGizmoSize() +{ + QRectF text_rect = active_text_gizmo_->GetRect(); + text_edit_pos_ = text_rect.topLeft(); + text_edit_->setGeometry(text_rect.toRect()); + return text_rect; +} + } diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index 2672b1b67..5ffcd2c4f 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -438,6 +438,8 @@ private slots: void FocusChanged(QWidget *old, QWidget *now); + QRectF UpdateActiveTextGizmoSize(); + }; diff --git a/app/widget/viewer/viewersizer.cpp b/app/widget/viewer/viewersizer.cpp index 505cb4eb9..86f4c6079 100644 --- a/app/widget/viewer/viewersizer.cpp +++ b/app/widget/viewer/viewersizer.cpp @@ -35,7 +35,7 @@ ViewerSizer::ViewerSizer(QWidget *parent) : width_(0), height_(0), pixel_aspect_(1), - zoom_(0), + zoom_(-1), current_widget_scale_(0) { horiz_scrollbar_ = new QScrollBar(Qt::Horizontal, this); @@ -64,6 +64,12 @@ void ViewerSizer::SetWidget(QWidget *widget) } } +QSize ViewerSizer::GetContainerSize() const +{ + double s = GetRealCurrentZoom(); + return QSize(std::min(this->width(), int(width_ * s)) - vert_scrollbar_->width(), std::min(int(height_ * s), this->height()) - horiz_scrollbar_->height()); +} + void ViewerSizer::SetChildSize(int width, int height) { width_ = width; @@ -79,13 +85,36 @@ void ViewerSizer::SetPixelAspectRatio(const rational &pixel_aspect) UpdateSize(); } -void ViewerSizer::SetZoom(int percent) +void ViewerSizer::SetZoom(double percent) { zoom_ = percent; UpdateSize(); } +void ViewerSizer::SetZoomAnchored(double next_scale, double cursor_x, double cursor_y) +{ + if (next_scale > 0) { + double cur_scale = GetRealCurrentZoom(); + + // Clamp scale within safe values + next_scale = std::clamp(next_scale, kZoomLevels[0], kZoomLevels[kZoomLevelCount-1]); + + int anchor_x = qRound(double(cursor_x + horiz_scrollbar_->value()) / cur_scale * next_scale - cursor_x); + int anchor_y = qRound(double(cursor_y + vert_scrollbar_->value()) / cur_scale * next_scale - cursor_y); + + SetZoom(next_scale); + + horiz_scrollbar_->setValue(anchor_x); + vert_scrollbar_->setValue(anchor_y); + } else { + SetZoom(-1); + + horiz_scrollbar_->setValue(0); + vert_scrollbar_->setValue(0); + } +} + void ViewerSizer::HandDragMove(int x, int y) { if (horiz_scrollbar_->isVisible()) { @@ -104,31 +133,9 @@ bool ViewerSizer::eventFilter(QObject *watched, QEvent *event) QWheelEvent *w = static_cast(event); if (HandMovableView::WheelEventIsAZoomEvent(w)) { - int x = w->angleDelta().x() + w->angleDelta().y(); - - int current_percent = zoom_; - if (current_percent == 0) { - // Currently set to "fit" - current_percent = current_widget_scale_; - } - - if (x > 0) { - // Zoom in - for (int i=kZoomLevelCount-2; i>=0; i--) { - if (current_percent >= kZoomLevels[i]) { - SetZoom(kZoomLevels[i+1]); - break; - } - } - } else if (x < 0) { - // Zoom out - for (int i=1; iposition(); + SetZoomAnchored(next_scale, cursor_pos.x(), cursor_pos.y()); } else { // Pass scroll values to scrollbars QPoint p = w->pixelDelta(); @@ -221,12 +228,12 @@ void ViewerSizer::UpdateSize() } - current_widget_scale_ = current_scale * 100; + current_widget_scale_ = current_scale; if (zoom_ > 0) { // Scale to get to the requested zoom - double zoom_diff = (zoom_ * 0.01) / current_scale; + double zoom_diff = zoom_ / current_scale; child_matrix.scale(zoom_diff, zoom_diff, 1.0); } @@ -238,7 +245,18 @@ void ViewerSizer::UpdateSize() int ViewerSizer::GetZoomedValue(int value) { - return qRound(value * static_cast(zoom_) * 0.01); + return qRound(value * zoom_); +} + +double ViewerSizer::GetRealCurrentZoom() const +{ + if (zoom_ < 0) { + // Currently set to "fit" + return current_widget_scale_; + } else { + // Explicit zoom set + return zoom_; + } } void ViewerSizer::ScrollBarMoved() diff --git a/app/widget/viewer/viewersizer.h b/app/widget/viewer/viewersizer.h index 90157be6d..189e99a27 100644 --- a/app/widget/viewer/viewersizer.h +++ b/app/widget/viewer/viewersizer.h @@ -51,8 +51,10 @@ public: */ void SetWidget(QWidget* widget); - static constexpr int kZoomLevelCount = 8; - static constexpr int kZoomLevels[kZoomLevelCount] = {10, 25, 50, 75, 100, 150, 200, 400}; + QSize GetContainerSize() const; + + static constexpr int kZoomLevelCount = 10; + static constexpr double kZoomLevels[kZoomLevelCount] = {0.05, 0.1, 0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 4.0, 8.0}; public slots: /** @@ -72,7 +74,8 @@ public slots: * * The number is an integer percentage (100 = 100%). Set to 0 to auto-fit. */ - void SetZoom(int percent); + void SetZoom(double percent); + void SetZoomAnchored(double percent, double cursor_x, double cursor_y); void HandDragMove(int x, int y); @@ -97,6 +100,8 @@ private: int GetZoomedValue(int value); + double GetRealCurrentZoom() const; + /** * @brief Reference to widget * @@ -115,8 +120,8 @@ private: /** * @brief Internal zoom value */ - int zoom_; - int current_widget_scale_; + double zoom_; + double current_widget_scale_; QScrollBar* horiz_scrollbar_; QScrollBar* vert_scrollbar_; diff --git a/app/widget/viewer/viewertexteditor.cpp b/app/widget/viewer/viewertexteditor.cpp index 7c8c27fad..1fe8dc4da 100644 --- a/app/widget/viewer/viewertexteditor.cpp +++ b/app/widget/viewer/viewertexteditor.cpp @@ -138,6 +138,7 @@ void ViewerTextEditor::Paint(QPainter *p, Qt::Alignment valign) const bool use_transparent_clone = true; if (transparent_clone_ && use_transparent_clone) { + transparent_clone_->setPageSize(this->document()->pageSize()); transparent_clone_->documentLayout()->draw(p, ctx); } else { document()->documentLayout()->draw(p, ctx); @@ -294,6 +295,9 @@ void ViewerTextEditor::DocumentChanged() forced_default_ = true; } } else { + if (default_fmt_.isEmpty()) { + default_fmt_ = document()->firstBlock().charFormat(); + } forced_default_ = false; } diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 6722ba6ae..5354bb313 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -86,6 +86,13 @@ MainWindow::MainWindow(QWidget *parent) : audio_monitor_panel_ = new AudioMonitorPanel(); scope_panel_ = new ScopePanel(); + // HACK: The pixel sampler is closed by default, which signals to Core that + // it's no longer visible. However KDDockWidgets doesn't appear to + // emit the "shown" signal before emitting the "hidden" signals, resulting + // in Core thinking there are -1 pixel samplers open. To mitigate that, + // we force "shown" to emit ourselves here. + emit pixel_sampler_panel_->shown(); + // Make node-related connections connect(node_panel_, &NodePanel::NodeSelectionChangedWithContexts, param_panel_, &ParamPanel::SetSelectedNodes); connect(node_panel_, &NodePanel::NodeGroupOpened, this, &MainWindow::NodePanelGroupOpenedOrClosed); diff --git a/tests/compositing/compositing-tests.cpp b/tests/compositing/compositing-tests.cpp index 8b831e4fd..ba1257f19 100644 --- a/tests/compositing/compositing-tests.cpp +++ b/tests/compositing/compositing-tests.cpp @@ -24,7 +24,7 @@ #include "node/distort/transform/transformdistortnode.h" #include "node/generator/solid/solid.h" #include "node/math/merge/merge.h" -#include "node/project/project.h" +#include "node/project.h" #include "render/rendermanager.h" namespace olive { diff --git a/tests/timeline/timeline-tests.cpp b/tests/timeline/timeline-tests.cpp index 034349e57..6cb72c387 100644 --- a/tests/timeline/timeline-tests.cpp +++ b/tests/timeline/timeline-tests.cpp @@ -23,12 +23,12 @@ #include "node/block/transition/crossdissolve/crossdissolvetransition.h" #include "node/math/math/math.h" #include "node/math/merge/merge.h" -#include "node/project/project.h" +#include "node/project.h" #include "node/project/sequence/sequence.h" -#include "undo/undocommand.h" -#include "widget/timelinewidget/undo/timelineundogeneral.h" -#include "widget/timelinewidget/undo/timelineundopointer.h" #include "testutil.h" +#include "timeline/timelineundogeneral.h" +#include "timeline/timelineundopointer.h" +#include "undo/undocommand.h" namespace olive {