From c004aa01759615f43452459e9654c89fa029bc13 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 27 Jul 2021 16:48:39 -0700 Subject: [PATCH 01/13] project: implemented modified and created date columns --- app/common/qtutils.cpp | 14 ++++++ app/common/qtutils.h | 6 +++ app/node/node.h | 3 ++ app/node/project/footage/footage.cpp | 23 ++++++++++ app/node/project/footage/footage.h | 3 ++ app/node/project/projectviewmodel.cpp | 46 +++++++++++++++---- app/node/project/projectviewmodel.h | 15 ++++-- .../projectexplorer/projectexplorer.cpp | 1 + 8 files changed, 99 insertions(+), 12 deletions(-) diff --git a/app/common/qtutils.cpp b/app/common/qtutils.cpp index 515a2bf67..757ca2617 100644 --- a/app/common/qtutils.cpp +++ b/app/common/qtutils.cpp @@ -58,4 +58,18 @@ int QtUtils::MessageBox(QWidget *parent, QMessageBox::Icon icon, const QString & return b.exec(); } +QDateTime QtUtils::GetCreationDate(const QFileInfo &info) +{ +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) + return info.created(); +#else + return info.birthTime(); +#endif +} + +QString QtUtils::GetFormattedDateTime(const QDateTime &dt) +{ + return dt.toString(Qt::TextDate); +} + } diff --git a/app/common/qtutils.h b/app/common/qtutils.h index b4c511167..2247f89d8 100644 --- a/app/common/qtutils.h +++ b/app/common/qtutils.h @@ -27,6 +27,8 @@ * */ +#include +#include #include #include #include @@ -54,6 +56,10 @@ public: static int MessageBox(QWidget *parent, QMessageBox::Icon icon, const QString& title, const QString& message, QMessageBox::StandardButtons buttons = QMessageBox::Ok); + static QDateTime GetCreationDate(const QFileInfo &info); + + static QString GetFormattedDateTime(const QDateTime &dt); + }; } diff --git a/app/node/node.h b/app/node/node.h index 31c144d8f..5bce23c06 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -183,6 +183,9 @@ public: virtual QString duration() const {return QString();} + virtual qint64 creation_time() const {return 0;} + virtual qint64 mod_time() const {return 0;} + virtual QString rate() const {return QString();} const QVector& inputs() const diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index addfa110c..3448bbdee 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -27,6 +27,7 @@ #include "codec/decoder.h" #include "common/clamp.h" #include "common/filefunctions.h" +#include "common/qtutils.h" #include "common/xmlutils.h" #include "config/config.h" #include "core.h" @@ -483,6 +484,28 @@ rational Footage::AdjustTimeByLoopMode(rational time, Footage::LoopMode loop_mod return time; } +qint64 Footage::creation_time() const +{ + QFileInfo info(filename()); + + if (info.exists()) { + return QtUtils::GetCreationDate(info).toSecsSinceEpoch(); + } + + return 0; +} + +qint64 Footage::mod_time() const +{ + QFileInfo info(filename()); + + if (info.exists()) { + return info.lastModified().toSecsSinceEpoch(); + } + + return 0; +} + void Footage::UpdateTooltip() { if (valid_) { diff --git a/app/node/project/footage/footage.h b/app/node/project/footage/footage.h index 4b29118c5..9f6b45c72 100644 --- a/app/node/project/footage/footage.h +++ b/app/node/project/footage/footage.h @@ -193,6 +193,9 @@ public: static rational AdjustTimeByLoopMode(rational time, LoopMode loop_mode, const rational& length, VideoParams::Type type, const rational &timebase); + virtual qint64 creation_time() const; + virtual qint64 mod_time() const; + static const QString kFilenameInput; static const QString kLoopModeInput; diff --git a/app/node/project/projectviewmodel.cpp b/app/node/project/projectviewmodel.cpp index ecc08b5dd..92b958460 100644 --- a/app/node/project/projectviewmodel.cpp +++ b/app/node/project/projectviewmodel.cpp @@ -24,6 +24,7 @@ #include #include +#include "common/qtutils.h" #include "core.h" #include "widget/nodeview/nodeviewundo.h" #include "widget/nodeparamview/nodeparamviewundo.h" @@ -34,10 +35,6 @@ ProjectViewModel::ProjectViewModel(QObject *parent) : QAbstractItemModel(parent), project_(nullptr) { - // FIXME: make this configurable - columns_.append(kName); - columns_.append(kDuration); - columns_.append(kRate); } Project *ProjectViewModel::project() const @@ -124,17 +121,18 @@ int ProjectViewModel::columnCount(const QModelIndex &parent) const return 0; } - return columns_.size(); + return kColumnCount; } QVariant ProjectViewModel::data(const QModelIndex &index, int role) const { Node* internal_item = GetItemObjectFromIndex(index); - ColumnType column_type = columns_.at(index.column()); + ColumnType column_type = static_cast(index.column()); switch (role) { case Qt::DisplayRole: + case kInnerTextRole: { // Standard text role @@ -145,6 +143,30 @@ QVariant ProjectViewModel::data(const QModelIndex &index, int role) const return internal_item->duration(); case kRate: return internal_item->rate(); + case kLastModified: + case kCreatedTime: + { + qint64 using_time = (column_type == kLastModified) ? internal_item->mod_time() : internal_item->creation_time(); + + if (using_time == 0) { + // 0 is the null value, return nothing + break; + } + + QVariant ret; + + if (role == kInnerTextRole) { + // Use time value directly for correct sorting + ret = using_time; + } else { + // Display role, format to a human readable string + ret = QtUtils::GetFormattedDateTime(QDateTime::fromSecsSinceEpoch(using_time)); + } + + return ret; + } + case kColumnCount: + break; } } break; @@ -166,7 +188,7 @@ QVariant ProjectViewModel::headerData(int section, Qt::Orientation orientation, // Check if we need text data (DisplayRole) and orientation is horizontal // FIXME I'm not 100% sure what happens if the orientation is vertical/if that check is necessary if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { - ColumnType column_type = columns_.at(section); + ColumnType column_type = static_cast(section); // Return the name based on the column's current type switch (column_type) { @@ -176,6 +198,12 @@ QVariant ProjectViewModel::headerData(int section, Qt::Orientation orientation, return tr("Duration"); case kRate: return tr("Rate"); + case kLastModified: + return tr("Modified"); + case kCreatedTime: + return tr("Created"); + case kColumnCount: + break; } } @@ -194,7 +222,7 @@ bool ProjectViewModel::hasChildren(const QModelIndex &parent) const bool ProjectViewModel::setData(const QModelIndex &index, const QVariant &value, int role) { // The name is editable - if (index.isValid() && columns_.at(index.column()) == kName && role == Qt::EditRole) { + if (index.isValid() && index.column() == kName && role == Qt::EditRole) { Node* item = GetItemObjectFromIndex(index); QString new_name = value.toString(); @@ -233,7 +261,7 @@ Qt::ItemFlags ProjectViewModel::flags(const QModelIndex &index) const } // If the column is the kName column, that means it's editable - if (columns_.at(index.column()) == kName) { + if (index.column() == kName) { f |= Qt::ItemIsEditable; } diff --git a/app/node/project/projectviewmodel.h b/app/node/project/projectviewmodel.h index 6ffe36f2e..9528439f0 100644 --- a/app/node/project/projectviewmodel.h +++ b/app/node/project/projectviewmodel.h @@ -49,9 +49,20 @@ public: kDuration, /// Media rate (frame rate for video, sample rate for audio) - kRate + kRate, + + /// Last modified time (for footage/files) + kLastModified, + + /// Creation time (for footage/files) + kCreatedTime, + + /// Count + kColumnCount }; + static const int kInnerTextRole = Qt::UserRole + 1; + /** * @brief ProjectViewModel Constructor * @@ -137,8 +148,6 @@ private: Project* project_; - QVector columns_; - private slots: void FolderBeginInsertItem(Node *n, int insert_index); diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index d229cf112..fdd74e977 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -65,6 +65,7 @@ ProjectExplorer::ProjectExplorer(QWidget *parent) : // Set up sort filter proxy model sort_model_.setSourceModel(&model_); + sort_model_.setSortRole(ProjectViewModel::kInnerTextRole); // Add tree view to stacked widget tree_view_ = new ProjectExplorerTreeView(stacked_widget_); From 06071da028b06562a3fdadd125e95730370314bc Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 27 Jul 2021 17:05:52 -0700 Subject: [PATCH 02/13] fixed clang compile warning --- app/node/project/footage/footage.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/node/project/footage/footage.h b/app/node/project/footage/footage.h index 9f6b45c72..abfc7f873 100644 --- a/app/node/project/footage/footage.h +++ b/app/node/project/footage/footage.h @@ -193,8 +193,8 @@ public: static rational AdjustTimeByLoopMode(rational time, LoopMode loop_mode, const rational& length, VideoParams::Type type, const rational &timebase); - virtual qint64 creation_time() const; - virtual qint64 mod_time() const; + virtual qint64 creation_time() const override; + virtual qint64 mod_time() const override; static const QString kFilenameInput; static const QString kLoopModeInput; From 5254be3fb5d3755745a0b2612331c1c29300cc0f Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 27 Jul 2021 18:52:18 -0700 Subject: [PATCH 03/13] folder: make ListChildrenOfType recursive Most functions assume this is recursive but it wasn't. Now it is. --- app/node/project/folder/folder.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/node/project/folder/folder.h b/app/node/project/folder/folder.h index ce71033b6..502dbe14f 100644 --- a/app/node/project/folder/folder.h +++ b/app/node/project/folder/folder.h @@ -104,10 +104,14 @@ public: foreach (Node* node, item_children_) { T* cast_test = dynamic_cast(node); - if (cast_test) { list.append(cast_test); } + + Folder *folder_test = dynamic_cast(node); + if (folder_test) { + list.append(folder_test->ListChildrenOfType()); + } } return list; From 841f6b08529860f22c0bf12e40b9fa1a582bf397 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 29 Jul 2021 13:21:37 -0700 Subject: [PATCH 04/13] sequencedialog: fix bug when undoable was false --- app/dialog/sequence/sequence.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/app/dialog/sequence/sequence.cpp b/app/dialog/sequence/sequence.cpp index e7570e690..e7f89ce80 100644 --- a/app/dialog/sequence/sequence.cpp +++ b/app/dialog/sequence/sequence.cpp @@ -136,6 +136,7 @@ void SequenceDialog::accept() sequence_->SetVideoParams(video_params); sequence_->SetAudioParams(audio_params); sequence_->SetLabel(name_field_->text()); + sequence_->SetAutoCacheEnabled(parameter_tab_->GetSelectedPreviewAutoCache()); } QDialog::accept(); From bbd6678ec34b8cdfa3b4c84891340ff562dbfd79 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 29 Jul 2021 13:22:01 -0700 Subject: [PATCH 05/13] timeline: initialized variable --- app/widget/timelinewidget/tool/transition.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/timelinewidget/tool/transition.cpp b/app/widget/timelinewidget/tool/transition.cpp index 4df27dc19..20ae40afa 100644 --- a/app/widget/timelinewidget/tool/transition.cpp +++ b/app/widget/timelinewidget/tool/transition.cpp @@ -38,7 +38,7 @@ void TransitionTool::HoverMove(TimelineViewMouseEvent *event) { ClipBlock *primary = nullptr; ClipBlock *secondary = nullptr; - Timeline::MovementMode trim_mode; + Timeline::MovementMode trim_mode = Timeline::kNone; rational transition_start_point; GetBlocksAtCoord(event->GetCoordinates(), &primary, &secondary, &trim_mode, &transition_start_point); From df4138e5beb24fe4e89922530b63c1d50d51e820 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 29 Jul 2021 13:22:40 -0700 Subject: [PATCH 06/13] node: provide convenience function for signing hashes with ID --- app/node/distort/transform/transformdistortnode.cpp | 2 +- app/node/math/merge/merge.cpp | 2 +- app/node/node.cpp | 11 +++++++---- app/node/node.h | 2 ++ 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/node/distort/transform/transformdistortnode.cpp b/app/node/distort/transform/transformdistortnode.cpp index b16f6ffed..c61895514 100644 --- a/app/node/distort/transform/transformdistortnode.cpp +++ b/app/node/distort/transform/transformdistortnode.cpp @@ -335,7 +335,7 @@ void TransformDistortNode::Hash(const QString &output, QCryptographicHash &hash, if (!matrix.isIdentity()) { // Add fingerprint - hash.addData(id().toUtf8()); + HashAddNodeSignature(hash, output); hash.addData(reinterpret_cast(&matrix), sizeof(matrix)); } } diff --git a/app/node/math/merge/merge.cpp b/app/node/math/merge/merge.cpp index 137d710f8..373c7c170 100644 --- a/app/node/math/merge/merge.cpp +++ b/app/node/math/merge/merge.cpp @@ -123,7 +123,7 @@ void MergeNode::Hash(const QString &output, QCryptographicHash &hash, const rati if (!passthrough_base && !passthrough_blend) { // This merge will actually do something so we add a fingerprint - hash.addData(id().toUtf8()); + HashAddNodeSignature(hash, output); } if (!passthrough_base) { diff --git a/app/node/node.cpp b/app/node/node.cpp index 23f022641..3e79ba4b1 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -1253,6 +1253,12 @@ bool Node::AreLinked(Node *a, Node *b) return a->links_.contains(b); } +void Node::HashAddNodeSignature(QCryptographicHash &hash, const QString &output) const +{ + hash.addData(id().toUtf8()); + hash.addData(output.toUtf8()); +} + void Node::InsertInput(const QString &id, NodeValue::Type type, const QVariant &default_value, Node::InputFlags flags, int index) { if (id.isEmpty()) { @@ -1446,11 +1452,8 @@ void Node::SetLabel(const QString &s) void Node::Hash(const QString &output, QCryptographicHash &hash, const rational& time, const VideoParams &video_params) const { - Q_UNUSED(output) - // Add this Node's ID and output being used - hash.addData(id().toUtf8()); - hash.addData(output.toUtf8()); + HashAddNodeSignature(hash, output); auto inputs = inputs_for_output(output); foreach (const QString& input, inputs) { diff --git a/app/node/node.h b/app/node/node.h index 5bce23c06..37109178d 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -852,6 +852,8 @@ protected: }; + void HashAddNodeSignature(QCryptographicHash &hash, const QString &output) const; + void InsertInput(const QString& id, NodeValue::Type type, const QVariant& default_value, InputFlags flags, int index); void PrependInput(const QString& id, NodeValue::Type type, const QVariant& default_value, InputFlags flags = InputFlags(kInputFlagNormal)) From 8f7adaa5d78c0d96a0a59e6ef4b673beadf32327 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 29 Jul 2021 13:23:01 -0700 Subject: [PATCH 07/13] transition: fixed bug --- app/node/block/transition/transition.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/node/block/transition/transition.cpp b/app/node/block/transition/transition.cpp index 9da041906..53269d95e 100644 --- a/app/node/block/transition/transition.cpp +++ b/app/node/block/transition/transition.cpp @@ -283,7 +283,7 @@ void TransitionBlock::InputDisconnectedEvent(const QString &input, int element, if (input == kOutBlockInput) { if (connected_out_block_) { - connected_out_block_->set_in_transition(nullptr); + connected_out_block_->set_out_transition(nullptr); connected_out_block_ = nullptr; } } else if (input == kInBlockInput) { From c3faa4968607bb4b949dce09eca3d08bdd7d334c Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 29 Jul 2021 13:23:24 -0700 Subject: [PATCH 08/13] transition: reworked hashing --- app/node/block/block.cpp | 14 ++++++++++++++ app/node/block/block.h | 2 ++ app/node/block/clip/clip.cpp | 9 +-------- app/node/block/transition/transition.cpp | 18 ++++++++++-------- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index cdee4463b..260a3f19c 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -215,6 +215,20 @@ void Block::LinkChangeEvent() } } +bool Block::HashPassthrough(const QString &input, const QString &output, QCryptographicHash &hash, const rational &time, const VideoParams &video_params) const +{ + if (IsInputConnected(input)) { + rational t = InputTimeAdjustment(input, -1, TimeRange(time, time)).in(); + + NodeOutput output = GetConnectedOutput(input); + output.node()->Hash(output.output(), hash, t, video_params); + + return true; + } + + return false; +} + void Block::set_length_internal(const rational &length) { SetStandardValue(kLengthInput, QVariant::fromValue(length)); diff --git a/app/node/block/block.h b/app/node/block/block.h index 7593b1a81..c3d7f5a90 100644 --- a/app/node/block/block.h +++ b/app/node/block/block.h @@ -179,6 +179,8 @@ protected: virtual void LinkChangeEvent() override; + bool HashPassthrough(const QString &input, const QString& output, QCryptographicHash &hash, const rational &time, const VideoParams& video_params) const; + Block* previous_; Block* next_; diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 7db010721..83b8ad520 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -117,14 +117,7 @@ void ClipBlock::Retranslate() void ClipBlock::Hash(const QString &out, QCryptographicHash &hash, const rational &time, const VideoParams &video_params) const { - Q_UNUSED(out) - - if (IsInputConnected(kBufferIn)) { - rational t = InputTimeAdjustment(kBufferIn, -1, TimeRange(time, time)).in(); - - NodeOutput output = GetConnectedOutput(kBufferIn); - output.node()->Hash(output.output(), hash, t, video_params); - } + HashPassthrough(kBufferIn, out, hash, time, video_params); } } diff --git a/app/node/block/transition/transition.cpp b/app/node/block/transition/transition.cpp index 53269d95e..2d91c7d47 100644 --- a/app/node/block/transition/transition.cpp +++ b/app/node/block/transition/transition.cpp @@ -121,16 +121,18 @@ double TransitionBlock::GetInProgress(const double &time) const void TransitionBlock::Hash(const QString &output, QCryptographicHash &hash, const rational &time, const VideoParams &video_params) const { - Node::Hash(output, hash, time, video_params); + if (HashPassthrough(kInBlockInput, output, hash, time, video_params) || HashPassthrough(kOutBlockInput, output, hash, time, video_params)) { + HashAddNodeSignature(hash, output); - double time_dbl = time.toDouble(); - double all_prog = GetTotalProgress(time_dbl); - double in_prog = GetInProgress(time_dbl); - double out_prog = GetOutProgress(time_dbl); + double time_dbl = time.toDouble(); + double all_prog = GetTotalProgress(time_dbl); + double in_prog = GetInProgress(time_dbl); + double out_prog = GetOutProgress(time_dbl); - hash.addData(reinterpret_cast(&all_prog), sizeof(double)); - hash.addData(reinterpret_cast(&in_prog), sizeof(double)); - hash.addData(reinterpret_cast(&out_prog), sizeof(double)); + hash.addData(reinterpret_cast(&all_prog), sizeof(all_prog)); + hash.addData(reinterpret_cast(&in_prog), sizeof(in_prog)); + hash.addData(reinterpret_cast(&out_prog), sizeof(out_prog)); + } } double TransitionBlock::GetInternalTransitionTime(const double &time) const From cd1254a2c43cf70954f155bfee7d4c3678e80d15 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 29 Jul 2021 13:38:06 -0700 Subject: [PATCH 09/13] compile: fixed shadowed variable --- app/node/block/block.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index 260a3f19c..c0584f8d0 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -220,8 +220,8 @@ bool Block::HashPassthrough(const QString &input, const QString &output, QCrypto if (IsInputConnected(input)) { rational t = InputTimeAdjustment(input, -1, TimeRange(time, time)).in(); - NodeOutput output = GetConnectedOutput(input); - output.node()->Hash(output.output(), hash, t, video_params); + NodeOutput out = GetConnectedOutput(input); + out.node()->Hash(out.output(), hash, t, video_params); return true; } From 5e35ce695151a17ddeb6f8c4c8ff6b9332a7a390 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 29 Jul 2021 13:38:16 -0700 Subject: [PATCH 10/13] cmake: set win32 executable --- app/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 792835af4..f2e95c1aa 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -100,6 +100,10 @@ endif() if (WIN32) # Set Windows application icon target_sources(olive-editor PRIVATE packaging/windows/resources.rc) + + set_target_properties(olive-editor PROPERTIES + WIN32_EXECUTABLE TRUE + ) elseif(APPLE) # Set Mac application icon set(OLIVE_ICON packaging/macos/olive.icns) From 7ae03a31b8a74a5a7c6962cd662b8330196554e4 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 30 Jul 2021 13:21:48 -0700 Subject: [PATCH 11/13] transition: fixed hash issue --- app/node/block/transition/transition.cpp | 2 +- app/node/output/track/track.h | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/node/block/transition/transition.cpp b/app/node/block/transition/transition.cpp index 2d91c7d47..2603f0acd 100644 --- a/app/node/block/transition/transition.cpp +++ b/app/node/block/transition/transition.cpp @@ -219,7 +219,7 @@ NodeValueTable TransitionBlock::Value(const QString &output, NodeValueDatabase & void TransitionBlock::InvalidateCache(const TimeRange &range, const QString &from, int element, InvalidateCacheOptions options) { - TimeRange r; + TimeRange r = range; if (from == kOutBlockInput || from == kInBlockInput) { Block *n = dynamic_cast(GetConnectedNode(from)); diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 1234d160e..82367cf44 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -62,22 +62,31 @@ public: static rational TransformTimeForBlock(const Block* block, const rational& time) { + if (time == RATIONAL_MAX || time == RATIONAL_MIN) { + return time; + } + return time - block->in(); } static TimeRange TransformRangeForBlock(const Block* block, const TimeRange& range) { - return range - block->in(); + return TimeRange(TransformTimeForBlock(block, range.in()), TransformTimeForBlock(block, range.out())); } static rational TransformTimeFromBlock(const Block* block, const rational& time) { + if (time == RATIONAL_MAX || time == RATIONAL_MIN) { + return time; + } + return time + block->in(); } static TimeRange TransformRangeFromBlock(const Block* block, const TimeRange& range) { return range + block->in(); + return TimeRange(TransformTimeFromBlock(block, range.in()), TransformTimeFromBlock(block, range.out())); } const double& GetTrackHeight() const; From 01d435d80db5cc2fe9c6548723ed36f6a3787198 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 30 Jul 2021 13:22:10 -0700 Subject: [PATCH 12/13] timeline: fixed commit issue --- app/node/output/track/track.h | 1 - 1 file changed, 1 deletion(-) diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 82367cf44..56e0ef82d 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -85,7 +85,6 @@ public: static TimeRange TransformRangeFromBlock(const Block* block, const TimeRange& range) { - return range + block->in(); return TimeRange(TransformTimeFromBlock(block, range.in()), TransformTimeFromBlock(block, range.out())); } From 930d8309d38143994c407da55cf6d49116f73077 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 30 Jul 2021 19:17:26 -0700 Subject: [PATCH 13/13] timeline: implemented nudging #1692 --- app/common/timerange.cpp | 7 ++ app/common/timerange.h | 11 +++ app/panel/timeline/timeline.cpp | 58 +++++++++------- app/panel/timeline/timeline.h | 9 +++ app/widget/panel/panel.h | 4 ++ app/widget/timelinewidget/timelinewidget.cpp | 67 ++++++++++++++++++- app/widget/timelinewidget/timelinewidget.h | 20 ++++-- .../timelinewidgetselections.cpp | 13 ++++ .../timelinewidget/timelinewidgetselections.h | 9 +++ app/widget/timelinewidget/tool/edit.cpp | 6 +- app/widget/timelinewidget/tool/pointer.cpp | 6 +- app/widget/timelinewidget/tool/ripple.cpp | 2 +- app/window/mainwindow/mainmenu.cpp | 15 +++++ app/window/mainwindow/mainmenu.h | 5 ++ 14 files changed, 195 insertions(+), 37 deletions(-) diff --git a/app/common/timerange.cpp b/app/common/timerange.cpp index 3f0f08829..4595c993c 100644 --- a/app/common/timerange.cpp +++ b/app/common/timerange.cpp @@ -202,6 +202,13 @@ void TimeRangeList::remove(const TimeRange &remove) util_remove(&array_, remove); } +void TimeRangeList::remove(const TimeRangeList &list) +{ + for (const TimeRange &r : list) { + remove(r); + } +} + bool TimeRangeList::contains(const TimeRange &range, bool in_inclusive, bool out_inclusive) const { for (int i=0;i static void util_remove(QVector *list, const TimeRange &remove) @@ -148,6 +149,16 @@ public: return array_.constEnd(); } + const_iterator cbegin() const + { + return begin(); + } + + const_iterator cend() const + { + return end(); + } + const TimeRange& first() const { return array_.first(); diff --git a/app/panel/timeline/timeline.cpp b/app/panel/timeline/timeline.cpp index 390de75cd..903320c3e 100644 --- a/app/panel/timeline/timeline.cpp +++ b/app/panel/timeline/timeline.cpp @@ -38,67 +38,67 @@ TimelinePanel::TimelinePanel(QWidget *parent) : void TimelinePanel::SplitAtPlayhead() { - static_cast(GetTimeBasedWidget())->SplitAtPlayhead(); + timeline_widget()->SplitAtPlayhead(); } QByteArray TimelinePanel::SaveSplitterState() const { - return static_cast(GetTimeBasedWidget())->SaveSplitterState(); + return timeline_widget()->SaveSplitterState(); } void TimelinePanel::RestoreSplitterState(const QByteArray &state) { - static_cast(GetTimeBasedWidget())->RestoreSplitterState(state); + timeline_widget()->RestoreSplitterState(state); } void TimelinePanel::SelectAll() { - static_cast(GetTimeBasedWidget())->SelectAll(); + timeline_widget()->SelectAll(); } void TimelinePanel::DeselectAll() { - static_cast(GetTimeBasedWidget())->DeselectAll(); + timeline_widget()->DeselectAll(); } void TimelinePanel::RippleToIn() { - static_cast(GetTimeBasedWidget())->RippleToIn(); + timeline_widget()->RippleToIn(); } void TimelinePanel::RippleToOut() { - static_cast(GetTimeBasedWidget())->RippleToOut(); + timeline_widget()->RippleToOut(); } void TimelinePanel::EditToIn() { - static_cast(GetTimeBasedWidget())->EditToIn(); + timeline_widget()->EditToIn(); } void TimelinePanel::EditToOut() { - static_cast(GetTimeBasedWidget())->EditToOut(); + timeline_widget()->EditToOut(); } void TimelinePanel::DeleteSelected() { - static_cast(GetTimeBasedWidget())->DeleteSelected(false); + timeline_widget()->DeleteSelected(false); } void TimelinePanel::RippleDelete() { - static_cast(GetTimeBasedWidget())->DeleteSelected(true); + timeline_widget()->DeleteSelected(true); } void TimelinePanel::IncreaseTrackHeight() { - static_cast(GetTimeBasedWidget())->IncreaseTrackHeight(); + timeline_widget()->IncreaseTrackHeight(); } void TimelinePanel::DecreaseTrackHeight() { - static_cast(GetTimeBasedWidget())->DecreaseTrackHeight(); + timeline_widget()->DecreaseTrackHeight(); } void TimelinePanel::Insert() @@ -121,57 +121,67 @@ void TimelinePanel::Overwrite() void TimelinePanel::ToggleLinks() { - static_cast(GetTimeBasedWidget())->ToggleLinksOnSelected(); + timeline_widget()->ToggleLinksOnSelected(); } void TimelinePanel::CutSelected() { - static_cast(GetTimeBasedWidget())->CopySelected(true); + timeline_widget()->CopySelected(true); } void TimelinePanel::CopySelected() { - static_cast(GetTimeBasedWidget())->CopySelected(false); + timeline_widget()->CopySelected(false); } void TimelinePanel::Paste() { - static_cast(GetTimeBasedWidget())->Paste(false); + timeline_widget()->Paste(false); } void TimelinePanel::PasteInsert() { - static_cast(GetTimeBasedWidget())->Paste(true); + timeline_widget()->Paste(true); } void TimelinePanel::DeleteInToOut() { - static_cast(GetTimeBasedWidget())->DeleteInToOut(false); + timeline_widget()->DeleteInToOut(false); } void TimelinePanel::RippleDeleteInToOut() { - static_cast(GetTimeBasedWidget())->DeleteInToOut(true); + timeline_widget()->DeleteInToOut(true); } void TimelinePanel::ToggleSelectedEnabled() { - static_cast(GetTimeBasedWidget())->ToggleSelectedEnabled(); + timeline_widget()->ToggleSelectedEnabled(); } void TimelinePanel::SetColorLabel(int index) { - static_cast(GetTimeBasedWidget())->SetColorLabel(index); + timeline_widget()->SetColorLabel(index); +} + +void TimelinePanel::NudgeLeft() +{ + timeline_widget()->NudgeLeft(); +} + +void TimelinePanel::NudgeRight() +{ + timeline_widget()->NudgeRight(); } void TimelinePanel::InsertFootageAtPlayhead(const QVector &footage) { - static_cast(GetTimeBasedWidget())->InsertFootageAtPlayhead(footage); + timeline_widget()->InsertFootageAtPlayhead(footage); } void TimelinePanel::OverwriteFootageAtPlayhead(const QVector &footage) { - static_cast(GetTimeBasedWidget())->OverwriteFootageAtPlayhead(footage); + timeline_widget()->OverwriteFootageAtPlayhead(footage); } void TimelinePanel::Retranslate() diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h index 5b853dce3..63a83fcff 100644 --- a/app/panel/timeline/timeline.h +++ b/app/panel/timeline/timeline.h @@ -35,6 +35,11 @@ class TimelinePanel : public TimeBasedPanel public: TimelinePanel(QWidget* parent); + inline TimelineWidget *timeline_widget() const + { + return static_cast(GetTimeBasedWidget()); + } + void SplitAtPlayhead(); QByteArray SaveSplitterState() const; @@ -83,6 +88,10 @@ public: virtual void SetColorLabel(int index) override; + virtual void NudgeLeft() override; + + virtual void NudgeRight() override; + void InsertFootageAtPlayhead(const QVector &footage); void OverwriteFootageAtPlayhead(const QVector &footage); diff --git a/app/widget/panel/panel.h b/app/widget/panel/panel.h index 651dc9be8..51c989af2 100644 --- a/app/widget/panel/panel.h +++ b/app/widget/panel/panel.h @@ -170,6 +170,10 @@ public: virtual void SetColorLabel(int){} + virtual void NudgeLeft(){} + + virtual void NudgeRight(){} + signals: void CloseRequested(); diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 6d5d10014..8dd77583f 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -504,7 +504,7 @@ void TimelineWidget::DeleteSelected(bool ripple) ReplaceBlocksWithGaps(clips_to_delete, true, command); // Remove all selections - command->add_child(new SetSelectionsCommand(this, TimelineWidgetSelections(), GetSelections())); + command->add_child(new SetSelectionsCommand(this, TimelineWidgetSelections(), GetSelections(), false)); // Insert ripple command now that it's all cleaned up gaps if (ripple) { @@ -724,6 +724,20 @@ void TimelineWidget::SetColorLabel(int index) } } +void TimelineWidget::NudgeLeft() +{ + if (GetConnectedNode()) { + NudgeInternal(-timebase()); + } +} + +void TimelineWidget::NudgeRight() +{ + if (GetConnectedNode()) { + NudgeInternal(timebase()); + } +} + void TimelineWidget::InsertGapsAt(const rational &earliest_point, const rational &insert_length, MultiUndoCommand *command) { for (int i=0;iadd_child(new TrackReplaceBlockWithGapCommand(b->track(), b, false)); + command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(b->track()->type()), b->track()->Index(), b, b->in() + amount)); + } + + // Nudge selections + TimelineWidgetSelections new_sel = GetSelections(); + new_sel.ShiftTime(amount); + command->add_child(new TimelineWidget::SetSelectionsCommand(this, new_sel, GetSelections(), true)); + + Core::instance()->undo_stack()->pushIfHasChildren(command); +} + void TimelineWidget::SetViewBeamCursor(const TimelineCoordinate &coord) { foreach (TimelineAndTrackView* tview, views_) { @@ -1428,6 +1459,31 @@ QVector TimelineWidget::GetBlocksInGlobalRect(const QPoint &p1, const Q return blocks_in_rect; } +QVector TimelineWidget::GetBlocksInSelection(const TimelineWidgetSelections &sel) +{ + QVector blocks; + + for (auto it=sel.cbegin(); it!=sel.cend(); it++) { + const Track::Reference &ref = it.key(); + const TimeRangeList &list = it.value(); + + for (const TimeRange &r : list) { + Track *track = GetTrackFromReference(ref); + if (track) { + for (Block *b : track->Blocks()) { + if (r.Contains(b->range())) { + blocks.append(b); + } else if (b->in() >= r.out()) { + break; + } + } + } + } + } + + return blocks; +} + void TimelineWidget::HideSnaps() { foreach (TimelineAndTrackView* tview, views_) { @@ -1474,7 +1530,7 @@ void TimelineWidget::MoveRubberBandSelect(bool enable_selecting, bool select_lin QVector items_in_rubberband = GetBlocksInGlobalRect(drag_origin_, rubberband_now); // Reset selection to whatever it was before - SetSelections(rubberband_old_selections_); + SetSelections(rubberband_old_selections_, false); // Add any blocks in rubberband rubberband_now_selected_.clear(); @@ -1544,8 +1600,13 @@ void TimelineWidget::RemoveSelection(Block *item) } } -void TimelineWidget::SetSelections(const TimelineWidgetSelections &s) +void TimelineWidget::SetSelections(const TimelineWidgetSelections &s, bool process_block_changes) { + if (process_block_changes) { + SignalDeselectedBlocks(GetBlocksInSelection(selections_.Subtracted(s))); + SignalSelectedBlocks(GetBlocksInSelection(s.Subtracted(selections_))); + } + selections_ = s; UpdateViewports(); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 176486f8e..47bd24794 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -91,6 +91,10 @@ public: void SetColorLabel(int index); + void NudgeLeft(); + + void NudgeRight(); + /** * @brief Timelines should always be connected to sequences */ @@ -133,7 +137,7 @@ public: return selections_; } - void SetSelections(const TimelineWidgetSelections &s); + void SetSelections(const TimelineWidgetSelections &s, bool process_block_changes); Track* GetTrackFromReference(const Track::Reference& ref) const; @@ -208,10 +212,11 @@ public: class SetSelectionsCommand : public UndoCommand { public: - SetSelectionsCommand(TimelineWidget* timeline, const TimelineWidgetSelections& now, const TimelineWidgetSelections& old) : + SetSelectionsCommand(TimelineWidget* timeline, const TimelineWidgetSelections& now, const TimelineWidgetSelections& old, bool process_block_changes) : timeline_(timeline), old_(old), - now_(now) + now_(now), + process_block_changes_(process_block_changes) { } @@ -220,18 +225,19 @@ public: protected: virtual void redo() override { - timeline_->SetSelections(now_); + timeline_->SetSelections(now_, process_block_changes_); } virtual void undo() override { - timeline_->SetSelections(old_); + timeline_->SetSelections(old_, process_block_changes_); } private: TimelineWidget* timeline_; TimelineWidgetSelections old_; TimelineWidgetSelections now_; + bool process_block_changes_; }; @@ -271,6 +277,8 @@ private: QVector GetBlocksInGlobalRect(const QPoint &p1, const QPoint &p2); + QVector GetBlocksInSelection(const TimelineWidgetSelections &sel); + QPoint drag_origin_; QRubberBand rubberband_; @@ -307,6 +315,8 @@ private: void UpdateViewTimebases(); + void NudgeInternal(const rational &amount); + private slots: void ViewMousePressed(TimelineViewMouseEvent* event); void ViewMouseMoved(TimelineViewMouseEvent* event); diff --git a/app/widget/timelinewidget/timelinewidgetselections.cpp b/app/widget/timelinewidget/timelinewidgetselections.cpp index 7c20e8d9c..0578db6d8 100644 --- a/app/widget/timelinewidget/timelinewidgetselections.cpp +++ b/app/widget/timelinewidget/timelinewidgetselections.cpp @@ -68,4 +68,17 @@ void TimelineWidgetSelections::TrimOut(const rational &diff) } } +void TimelineWidgetSelections::Subtract(const TimelineWidgetSelections &selections) +{ + for (auto it=selections.cbegin(); it!=selections.cend(); it++) { + const Track::Reference &track = it.key(); + const TimeRangeList &their_list = it.value(); + + if (this->contains(track)) { + TimeRangeList &our_list = (*this)[it.key()]; + our_list.remove(their_list); + } + } +} + } diff --git a/app/widget/timelinewidget/timelinewidgetselections.h b/app/widget/timelinewidget/timelinewidgetselections.h index 3681b5667..e2803f8c7 100644 --- a/app/widget/timelinewidget/timelinewidgetselections.h +++ b/app/widget/timelinewidget/timelinewidgetselections.h @@ -41,6 +41,15 @@ public: void TrimOut(const rational& diff); + void Subtract(const TimelineWidgetSelections &selections); + + TimelineWidgetSelections Subtracted(const TimelineWidgetSelections &selections) const + { + TimelineWidgetSelections copy = *this; + copy.Subtract(selections); + return copy; + } + }; } diff --git a/app/widget/timelinewidget/tool/edit.cpp b/app/widget/timelinewidget/tool/edit.cpp index 991b1618b..ce7511b95 100644 --- a/app/widget/timelinewidget/tool/edit.cpp +++ b/app/widget/timelinewidget/tool/edit.cpp @@ -48,7 +48,7 @@ void EditTool::MouseMove(TimelineViewMouseEvent *event) } } - parent()->SetSelections(start_selections_); + parent()->SetSelections(start_selections_, false); parent()->AddSelection(TimeRange(start_coord_.GetFrame(), end_frame), start_coord_.GetTrack()); } else { @@ -73,6 +73,10 @@ void EditTool::MouseMove(TimelineViewMouseEvent *event) void EditTool::MouseRelease(TimelineViewMouseEvent *event) { + auto current_sel = parent()->GetSelections(); + parent()->SetSelections(start_selections_, false); + parent()->SetSelections(current_sel, true); + dragging_ = false; } diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 14cc30169..95b4b61db 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -574,7 +574,7 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event) } else { new_sel.TrimOut(reference_ghost->GetOutAdjustment()); } - command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections())); + command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), false)); } } @@ -620,7 +620,7 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event) TimelineWidgetSelections new_sel = parent()->GetSelections(); new_sel.ShiftTime(blocks_moving.first().ghost->GetInAdjustment()); new_sel.ShiftTracks(drag_track_type_, blocks_moving.first().ghost->GetTrackAdjustment()); - command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections())); + command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), false)); } if (!blocks_sliding.isEmpty()) { @@ -681,7 +681,7 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event) // Adjust selections TimelineWidgetSelections new_sel = parent()->GetSelections(); new_sel.ShiftTime(movement); - command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections())); + command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), false)); } } diff --git a/app/widget/timelinewidget/tool/ripple.cpp b/app/widget/timelinewidget/tool/ripple.cpp index 3d0861a28..919c67edb 100644 --- a/app/widget/timelinewidget/tool/ripple.cpp +++ b/app/widget/timelinewidget/tool/ripple.cpp @@ -160,7 +160,7 @@ void RippleTool::FinishDrag(TimelineViewMouseEvent *event) } else { new_sel.TrimOut(reference_ghost->GetOutAdjustment()); } - command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections())); + command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), false)); Core::instance()->undo_stack()->push(command); } else { diff --git a/app/window/mainwindow/mainmenu.cpp b/app/window/mainwindow/mainmenu.cpp index b6b37eed9..b53d94e63 100644 --- a/app/window/mainwindow/mainmenu.cpp +++ b/app/window/mainwindow/mainmenu.cpp @@ -108,6 +108,9 @@ MainMenu::MainMenu(MainWindow *parent) : edit_edit_to_in_item_ = edit_menu_->AddItem("edittoin", this, &MainMenu::EditToInTriggered, "Ctrl+Alt+Q"); edit_edit_to_out_item_ = edit_menu_->AddItem("edittoout", this, &MainMenu::EditToOutTriggered, "Ctrl+Alt+W"); edit_menu_->addSeparator(); + edit_nudge_left_item_ = edit_menu_->AddItem("nudgeleft", this, &MainMenu::NudgeLeftTriggered, "Alt+Left"); + edit_nudge_right_item_ = edit_menu_->AddItem("nudgeright", this, &MainMenu::NudgeRightTriggered, "Alt+Right"); + edit_menu_->addSeparator(); MenuShared::instance()->AddItemsForInOutMenu(edit_menu_); edit_delete_inout_item_ = edit_menu_->AddItem("deleteinout", this, &MainMenu::DeleteInOutTriggered, ";"); edit_ripple_delete_inout_item_ = edit_menu_->AddItem("rippledeleteinout", this, &MainMenu::RippleDeleteInOutTriggered, "'"); @@ -537,6 +540,16 @@ void MainMenu::EditToOutTriggered() PanelManager::instance()->CurrentlyFocused()->EditToOut(); } +void MainMenu::NudgeLeftTriggered() +{ + PanelManager::instance()->CurrentlyFocused()->NudgeLeft(); +} + +void MainMenu::NudgeRightTriggered() +{ + PanelManager::instance()->CurrentlyFocused()->NudgeRight(); +} + void MainMenu::ActionSearchTriggered() { ActionSearch as(parentWidget()); @@ -662,6 +675,8 @@ void MainMenu::Retranslate() edit_ripple_to_out_item_->setText(tr("Ripple to Out Point")); edit_edit_to_in_item_->setText(tr("Edit to In Point")); edit_edit_to_out_item_->setText(tr("Edit to Out Point")); + edit_nudge_left_item_->setText(tr("Nudge Left")); + edit_nudge_right_item_->setText(tr("Nudge Right")); edit_delete_inout_item_->setText(tr("Delete In/Out Point")); edit_ripple_delete_inout_item_->setText(tr("Ripple Delete In/Out Point")); edit_set_marker_item_->setText(tr("Set/Edit Marker")); diff --git a/app/window/mainwindow/mainmenu.h b/app/window/mainwindow/mainmenu.h index ae8ea40e8..baa864b9b 100644 --- a/app/window/mainwindow/mainmenu.h +++ b/app/window/mainwindow/mainmenu.h @@ -154,6 +154,9 @@ private slots: void EditToInTriggered(); void EditToOutTriggered(); + void NudgeLeftTriggered(); + void NudgeRightTriggered(); + void ActionSearchTriggered(); void ShuttleLeftTriggered(); @@ -218,6 +221,8 @@ private: QAction* edit_ripple_to_out_item_; QAction* edit_edit_to_in_item_; QAction* edit_edit_to_out_item_; + QAction* edit_nudge_left_item_; + QAction* edit_nudge_right_item_; QAction* edit_delete_inout_item_; QAction* edit_ripple_delete_inout_item_; QAction* edit_set_marker_item_;