From df97df635d587e2938d3907f26365297e501083c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 1 Oct 2022 13:04:51 -0700 Subject: [PATCH] timeline: implement nest function Also includes some improved cache/request code --- app/core.cpp | 4 +- app/core.h | 6 +- app/node/block/clip/clip.cpp | 4 +- app/node/output/track/track.cpp | 27 +++++ app/node/output/track/track.h | 5 + app/node/output/viewer/viewer.cpp | 10 +- app/panel/timeline/timeline.h | 5 + app/render/playbackcache.cpp | 7 ++ app/render/playbackcache.h | 18 +++- app/render/previewautocacher.cpp | 54 ++++++---- app/render/previewautocacher.h | 2 +- app/widget/menu/menushared.cpp | 2 +- app/widget/timelinewidget/timelinewidget.cpp | 102 ++++++++++++++++++- app/widget/timelinewidget/timelinewidget.h | 2 + app/widget/timelinewidget/tool/import.cpp | 21 ++-- app/widget/timelinewidget/tool/import.h | 6 +- 16 files changed, 230 insertions(+), 45 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index 0a90e92d4..1dd1317c2 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -1490,7 +1490,7 @@ bool Core::LabelNodes(const QVector &nodes, MultiUndoCommand *parent) return false; } -Sequence *Core::CreateNewSequenceForProject(Project* project) const +Sequence *Core::CreateNewSequenceForProject(const QString &format, Project* project) { Sequence* new_sequence = new Sequence(); @@ -1498,7 +1498,7 @@ Sequence *Core::CreateNewSequenceForProject(Project* project) const int sequence_number = 1; QString sequence_name; do { - sequence_name = tr("Sequence %1").arg(sequence_number); + sequence_name = format.arg(sequence_number); sequence_number++; } while (project->root()->ChildExistsWithName(sequence_name)); new_sequence->SetLabel(sequence_name); diff --git a/app/core.h b/app/core.h index a2ded04db..0d9d8df0d 100644 --- a/app/core.h +++ b/app/core.h @@ -255,7 +255,11 @@ public: /** * @brief Create a new sequence named appropriately for the active project */ - Sequence* CreateNewSequenceForProject(Project *project) const; + static Sequence* CreateNewSequenceForProject(const QString &format, Project *project); + static Sequence* CreateNewSequenceForProject(Project *project) + { + return CreateNewSequenceForProject(tr("Sequence %1"), project); + } /** * @brief Opens a project from the recently opened list diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 787cf5442..fd0ed6113 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -232,7 +232,7 @@ void ClipBlock::RequestRangeFromConnected(const TimeRange &range) { TimeRange thumb_range = range.Intersected(max_range); if (GetAdjustedThumbnailRange(&thumb_range)) { - emit connected->thumbnail_cache()->Request(thumb_range); + connected->thumbnail_cache()->Request(thumb_range); } } @@ -296,7 +296,7 @@ void ClipBlock::RequestRangeForCache(PlaybackCache *cache, const TimeRange &max_ } if (request) { - emit cache->Request(r); + cache->Request(r); } } diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index b2d3775f0..53f874366 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -404,6 +404,33 @@ QVector Track::BlocksAtTimeRange(const TimeRange &range) const return list; } +bool Track::IsRangeFree(const TimeRange &range) const +{ + Block *b = NearestBlockBeforeOrAt(range.in()); + if (!b) { + // No block here, assume track is empty here + return true; + } + + if (!dynamic_cast(b)) { + // There's a block at or around the start point that isn't a gap, range is not free + return false; + } + + while ((b = b->next())) { + if (b->in() >= range.out()) { + // This block is after the range, no longer relevant + break; + } else if (!dynamic_cast(b)) { + // Found a block in this range, range is not free + return false; + } + } + + // If we get here, we couldn't find anything in the way of this range + return true; +} + void Track::InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options) { TimeRange limited; diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 4994bf73c..34bb82f35 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -338,6 +338,11 @@ public: */ QVector BlocksAtTimeRange(const TimeRange& range) const; + /* + * @brief Returns whether a time range is empty or only has a gap + */ + bool IsRangeFree(const TimeRange &range) const; + const QVector &Blocks() const { return blocks_; diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 4dfce5b64..34ec101d6 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -224,16 +224,16 @@ void ViewerOutput::InvalidateCache(const TimeRange& range, const QString& from, if (Node *connected = GetConnectedOutput(from, element)) { if (from == kTextureInput) { - //emit connected->thumbnail_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); + //connected->thumbnail_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); if (autocache_input_video_) { TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetVideoLength())); - emit connected->video_frame_cache()->Request(range.Intersected(max_range)); + connected->video_frame_cache()->Request(range.Intersected(max_range)); } } else if (from == kSamplesInput) { TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetAudioLength())); - emit connected->waveform_cache()->Request(range.Intersected(max_range)); + connected->waveform_cache()->Request(range.Intersected(max_range)); if (autocache_input_audio_) { - emit connected->audio_playback_cache()->Request(range.Intersected(max_range)); + connected->audio_playback_cache()->Request(range.Intersected(max_range)); } } } @@ -393,7 +393,7 @@ void ViewerOutput::ConnectedToPreviewEvent() TimeRange max_range = InputTimeAdjustment(kSamplesInput, -1, TimeRange(0, GetAudioLength())); TimeRangeList invalid = connected->waveform_cache()->GetInvalidatedRanges(max_range); for (const TimeRange &r : invalid) { - emit connected->waveform_cache()->Request(r); + connected->waveform_cache()->Request(r); } } } diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h index 611fd275c..ae41d8c88 100644 --- a/app/panel/timeline/timeline.h +++ b/app/panel/timeline/timeline.h @@ -98,6 +98,11 @@ public: timeline_widget()->ShowSpeedDurationDialogForSelectedClips(); } + void NestSelectedClips() + { + timeline_widget()->NestSelectedClips(); + } + void InsertFootageAtPlayhead(const QVector &footage); void OverwriteFootageAtPlayhead(const QVector &footage); diff --git a/app/render/playbackcache.cpp b/app/render/playbackcache.cpp index bf606e153..7cc89d473 100644 --- a/app/render/playbackcache.cpp +++ b/app/render/playbackcache.cpp @@ -223,6 +223,13 @@ void PlaybackCache::InvalidateAll() Invalidate(TimeRange(0, RATIONAL_MAX)); } +void PlaybackCache::Request(const TimeRange &r) +{ + requested_.insert(r); + + emit Requested(r); +} + void PlaybackCache::Validate(const TimeRange &r, bool signal) { validated_.insert(r); diff --git a/app/render/playbackcache.h b/app/render/playbackcache.h index 3485fdcb4..8f483d1c5 100644 --- a/app/render/playbackcache.h +++ b/app/render/playbackcache.h @@ -98,15 +98,29 @@ public: const QVector &GetPassthroughs() const { return passthroughs_; } + void ClearRequestRange(const olive::TimeRange &r) + { + requested_.remove(r); + } + + void ResignalRequests() + { + for (const TimeRange &r : requested_) { + emit Requested(r); + } + } + public slots: void InvalidateAll(); + void Request(const olive::TimeRange &r); + signals: void Invalidated(const olive::TimeRange& r); void Validated(const olive::TimeRange& r); - void Request(const olive::TimeRange& r); + void Requested(const olive::TimeRange& r); void CancelAll(); @@ -124,6 +138,8 @@ protected: private: TimeRangeList validated_; + TimeRangeList requested_; + QUuid uuid_; bool saving_enabled_; diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 5a9a5a21e..827e4d910 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -100,6 +100,8 @@ void PreviewAutoCacher::VideoInvalidatedFromCache(const TimeRange &range) { PlaybackCache *cache = static_cast(sender()); + cache->ClearRequestRange(range); + VideoInvalidatedFromNode(cache, range); } @@ -107,6 +109,8 @@ void PreviewAutoCacher::AudioInvalidatedFromCache(const TimeRange &range) { PlaybackCache *cache = static_cast(sender()); + cache->ClearRequestRange(range); + AudioInvalidatedFromNode(cache, range); } @@ -240,7 +244,10 @@ void PreviewAutoCacher::VideoRendered() void PreviewAutoCacher::ProcessUpdateQueue() { // Iterate everything that happened to the graph and do the same thing on our end - foreach (const QueuedJob& job, graph_update_queue_) { + while (!graph_update_queue_.empty()) { + QueuedJob job = graph_update_queue_.front(); + graph_update_queue_.pop_front(); + switch (job.type) { case QueuedJob::kNodeAdded: AddNode(job.node); @@ -262,7 +269,6 @@ void PreviewAutoCacher::ProcessUpdateQueue() break; } } - graph_update_queue_.clear(); // Indicate that we have synchronized to this point, which is compared with the graph change // time to see if our copied graph is up to date @@ -368,22 +374,22 @@ void PreviewAutoCacher::InsertIntoCopyMap(Node *node, Node *copy) void PreviewAutoCacher::ConnectToNodeCache(Node *node) { connect(node->video_frame_cache(), - &PlaybackCache::Request, + &PlaybackCache::Requested, this, &PreviewAutoCacher::VideoInvalidatedFromCache); connect(node->thumbnail_cache(), - &PlaybackCache::Request, + &PlaybackCache::Requested, this, &PreviewAutoCacher::VideoInvalidatedFromCache); connect(node->audio_playback_cache(), - &PlaybackCache::Request, + &PlaybackCache::Requested, this, &PreviewAutoCacher::AudioInvalidatedFromCache); connect(node->waveform_cache(), - &PlaybackCache::Request, + &PlaybackCache::Requested, this, &PreviewAutoCacher::AudioInvalidatedFromCache); @@ -396,27 +402,32 @@ void PreviewAutoCacher::ConnectToNodeCache(Node *node) &PlaybackCache::CancelAll, this, &PreviewAutoCacher::CancelForCache); + + node->video_frame_cache()->ResignalRequests(); + node->thumbnail_cache()->ResignalRequests(); + node->audio_playback_cache()->ResignalRequests(); + node->waveform_cache()->ResignalRequests(); } void PreviewAutoCacher::DisconnectFromNodeCache(Node *node) { disconnect(node->video_frame_cache(), - &PlaybackCache::Request, + &PlaybackCache::Requested, this, &PreviewAutoCacher::VideoInvalidatedFromCache); disconnect(node->thumbnail_cache(), - &PlaybackCache::Request, + &PlaybackCache::Requested, this, &PreviewAutoCacher::VideoInvalidatedFromCache); disconnect(node->audio_playback_cache(), - &PlaybackCache::Request, + &PlaybackCache::Requested, this, &PreviewAutoCacher::AudioInvalidatedFromCache); disconnect(node->waveform_cache(), - &PlaybackCache::Request, + &PlaybackCache::Requested, this, &PreviewAutoCacher::AudioInvalidatedFromCache); @@ -466,6 +477,8 @@ void PreviewAutoCacher::StartCachingVideoRange(PlaybackCache *cache, const TimeR using_tb = viewer_node_->GetVideoParams().frame_rate_as_time_base(); } + cache->ClearRequestRange(range); + TimeRangeListFrameIterator iterator({range}, using_tb); pending_video_jobs_.push_back({node, cache, range, iterator}); video_cache_data_[cache].job_tracker.insert(TimeRange(iterator.Snap(range.in()), range.out()), graph_changed_time_); @@ -475,6 +488,9 @@ void PreviewAutoCacher::StartCachingVideoRange(PlaybackCache *cache, const TimeR void PreviewAutoCacher::StartCachingAudioRange(PlaybackCache *cache, const TimeRange &range) { Node *node = cache->parent(); + + cache->ClearRequestRange(range); + pending_audio_jobs_.push_back({node, cache, range}); audio_cache_data_[cache].job_tracker.insert(range, graph_changed_time_); TryRender(); @@ -486,6 +502,8 @@ void PreviewAutoCacher::VideoInvalidatedFromNode(PlaybackCache *cache, const Tim // want to dedicate all our rendering power to realtime feedback for the user //CancelVideoTasks(node); + cache->ClearRequestRange(range); + // If auto-cache is enabled and a slider is not being dragged, queue up to hash these frames if (!NodeInputDragger::IsInputBeingDragged()) { StartCachingVideoRange(cache, range); @@ -498,6 +516,8 @@ void PreviewAutoCacher::AudioInvalidatedFromNode(PlaybackCache *cache, const Tim // cancelled, so some areas may end up unrendered forever // ClearAudioQueue(); + cache->ClearRequestRange(range); + // If we're auto-caching audio or require realtime waveforms, we'll have to render this StartCachingAudioRange(cache, range); } @@ -553,37 +573,37 @@ void PreviewAutoCacher::SetRendersPaused(bool e) void PreviewAutoCacher::NodeAdded(Node *node) { - graph_update_queue_.append({QueuedJob::kNodeAdded, node, NodeInput(), nullptr}); + graph_update_queue_.push_back({QueuedJob::kNodeAdded, node, NodeInput(), nullptr}); UpdateGraphChangeValue(); } void PreviewAutoCacher::NodeRemoved(Node *node) { - graph_update_queue_.append({QueuedJob::kNodeRemoved, node, NodeInput(), nullptr}); + graph_update_queue_.push_back({QueuedJob::kNodeRemoved, node, NodeInput(), nullptr}); UpdateGraphChangeValue(); } void PreviewAutoCacher::EdgeAdded(Node *output, const NodeInput &input) { - graph_update_queue_.append({QueuedJob::kEdgeAdded, nullptr, input, output}); + graph_update_queue_.push_back({QueuedJob::kEdgeAdded, nullptr, input, output}); UpdateGraphChangeValue(); } void PreviewAutoCacher::EdgeRemoved(Node *output, const NodeInput &input) { - graph_update_queue_.append({QueuedJob::kEdgeRemoved, nullptr, input, output}); + graph_update_queue_.push_back({QueuedJob::kEdgeRemoved, nullptr, input, output}); UpdateGraphChangeValue(); } void PreviewAutoCacher::ValueChanged(const NodeInput &input) { - graph_update_queue_.append({QueuedJob::kValueChanged, nullptr, input, nullptr}); + graph_update_queue_.push_back({QueuedJob::kValueChanged, nullptr, input, nullptr}); UpdateGraphChangeValue(); } void PreviewAutoCacher::ValueHintChanged(const NodeInput &input) { - graph_update_queue_.append({QueuedJob::kValueHintChanged, nullptr, input, nullptr}); + graph_update_queue_.push_back({QueuedJob::kValueHintChanged, nullptr, input, nullptr}); UpdateGraphChangeValue(); } @@ -591,7 +611,7 @@ void PreviewAutoCacher::TryRender() { delayed_requeue_timer_.stop(); - if (!graph_update_queue_.isEmpty()) { + if (!graph_update_queue_.empty()) { // Check if we have jobs running in other threads that shouldn't be interrupted right now // NOTE: We don't check for downloads because, while they run in another thread, they don't // require any access to the graph and therefore don't risk race conditions. diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index 05e253851..2d5bfebd7 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -160,7 +160,7 @@ private: Project copied_project_; - QVector graph_update_queue_; + std::list graph_update_queue_; QHash copy_map_; QHash graph_map_; ViewerOutput* copied_viewer_node_; diff --git a/app/widget/menu/menushared.cpp b/app/widget/menu/menushared.cpp index 6332046ba..1ff70f954 100644 --- a/app/widget/menu/menushared.cpp +++ b/app/widget/menu/menushared.cpp @@ -293,7 +293,7 @@ void MenuShared::EnableDisableTriggered() void MenuShared::NestTriggered() { - qDebug() << "FIXME: Stub"; + PanelManager::instance()->MostRecentlyFocused()->NestSelectedClips(); } void MenuShared::DefaultTransitionTriggered() diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index f54792aef..e53d77f0d 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -539,12 +539,16 @@ void TimelineWidget::DecreaseTrackHeight() void TimelineWidget::InsertFootageAtPlayhead(const QVector& footage) { - import_tool_->PlaceAt(footage, GetTime(), true); + auto command = new MultiUndoCommand(); + import_tool_->PlaceAt(footage, GetTime(), true, command); + Core::instance()->undo_stack()->push(command); } void TimelineWidget::OverwriteFootageAtPlayhead(const QVector &footage) { - import_tool_->PlaceAt(footage, GetTime(), false); + auto command = new MultiUndoCommand(); + import_tool_->PlaceAt(footage, GetTime(), false, command); + Core::instance()->undo_stack()->push(command); } void TimelineWidget::ToggleLinksOnSelected() @@ -788,13 +792,14 @@ void TimelineWidget::RecordingCallback(const QString &filename, const TimeRange task.Start(); MultiUndoCommand *import_command = task.GetCommand(); - Core::instance()->undo_stack()->pushIfHasChildren(import_command); if (task.GetImportedFootage().empty()) { qCritical() << "Failed to import recorded audio file" << filename; } else { - import_tool_->PlaceAt({task.GetImportedFootage().front()}, time.in(), false, track.index()); + import_tool_->PlaceAt({task.GetImportedFootage().front()}, time.in(), false, import_command, track.index()); } + + Core::instance()->undo_stack()->pushIfHasChildren(import_command); } void TimelineWidget::EnableRecordingOverlay(const TimelineCoordinate &coord) @@ -839,6 +844,95 @@ void TimelineWidget::AddTentativeSubtitleTrack() } } +void TimelineWidget::NestSelectedClips() +{ + if (!GetConnectedNode()) { + return; + } + + QVector blocks = this->selected_blocks_; + if (blocks.empty()) { + return; + } + + QVector tracks(blocks.size()); + QVector times(blocks.size()); + QVector track_offset(Track::kCount, INT_MAX); + rational start_time = RATIONAL_MAX; + rational end_time = RATIONAL_MIN; + for (int i=0; itrack()->ToReference();; + tracks[i] = tf; + times[i] = b->range(); + + int &to = track_offset[tf.type()]; + to = std::min(to, tf.index()); + + start_time = std::min(start_time, b->in()); + end_time = std::max(end_time, b->out()); + } + + auto move_to_nest_command = new MultiUndoCommand(); + + // Remove blocks from this sequence + ReplaceBlocksWithGaps(blocks, false, move_to_nest_command); + + // Create new sequence + Project *project = this->GetConnectedNode()->project(); + Sequence *nest = Core::CreateNewSequenceForProject(tr("Nested Sequence %1"), project); + nest->SetVideoParams(GetConnectedNode()->GetVideoParams()); + nest->SetAudioParams(GetConnectedNode()->GetAudioParams()); + move_to_nest_command->add_child(new NodeAddCommand(project, nest)); + + // Add to same folder + move_to_nest_command->add_child(new FolderAddChild(this->GetConnectedNode()->folder(), nest)); + + // Place blocks in new sequence + for (int i=0; iadd_child(new TrackPlaceBlockCommand(nest->track_list(track.type()), + track.index() - track_offset.at(track.type()), + b, range.in() - start_time)); + } + + // Do this command now, because we later do checks and actions that rely on these having been done + move_to_nest_command->redo_now(); + + auto meta_command = new MultiUndoCommand(); + meta_command->add_child(move_to_nest_command); + + // Find first free track index + bool empty = false; + int index = -1; + while (!empty) { + index++; + empty = true; + for (int i=0; itrack_list(static_cast(i)); + if (index < list->GetTrackCount() && !list->GetTrackAt(index)->IsRangeFree(TimeRange(start_time, end_time))) { + empty = false; + break; + } + } + } + + // Place new sequence in this sequence + import_tool_->PlaceAt({nest}, start_time, false, meta_command, index); + + Core::instance()->undo_stack()->push(meta_command); +} + void TimelineWidget::ClearTentativeSubtitleTrack() { if (subtitle_show_command_) { diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index d952c5419..dd081af0f 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -112,6 +112,8 @@ public: void AddTentativeSubtitleTrack(); + void NestSelectedClips(); + /** * @brief Timelines should always be connected to sequences */ diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index 02dec714d..caa0037ed 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -183,7 +183,9 @@ void ImportTool::DragLeave(QDragLeaveEvent* event) void ImportTool::DragDrop(TimelineViewMouseEvent *event) { if (!dragged_footage_.isEmpty()) { - DropGhosts(event->GetModifiers() & Qt::ControlModifier); + auto command = new MultiUndoCommand(); + DropGhosts(event->GetModifiers() & Qt::ControlModifier, command); + Core::instance()->undo_stack()->pushIfHasChildren(command); event->accept(); } else { @@ -191,7 +193,7 @@ void ImportTool::DragDrop(TimelineViewMouseEvent *event) } } -void ImportTool::PlaceAt(const QVector &footage, const rational &start, bool insert, int track_offset) +void ImportTool::PlaceAt(const QVector &footage, const rational &start, bool insert, MultiUndoCommand *command, int track_offset) { DraggedFootageData refs; @@ -199,10 +201,10 @@ void ImportTool::PlaceAt(const QVector &footage, const rational refs.append({f, f->GetEnabledStreamsAsReferences()}); } - PlaceAt(refs, start, insert, track_offset); + PlaceAt(refs, start, insert, command, track_offset); } -void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &start, bool insert, int track_offset) +void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &start, bool insert, MultiUndoCommand *command, int track_offset) { dragged_footage_ = footage; @@ -211,7 +213,7 @@ void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &star } PrepGhosts(start, track_offset); - DropGhosts(insert); + DropGhosts(insert, command); } void ImportTool::FootageToGhosts(rational ghost_start, const DraggedFootageData &sorted, const rational& dest_tb, const int& track_start) @@ -292,9 +294,9 @@ void ImportTool::PrepGhosts(const rational& frame, const int& track_index) } } -void ImportTool::DropGhosts(bool insert) +void ImportTool::DropGhosts(bool insert, MultiUndoCommand *parent_command) { - MultiUndoCommand* command = new MultiUndoCommand(); + auto command = new MultiUndoCommand(); if (MultiUndoCommand *c = parent()->TakeSubtitleSectionCommand()) { command->add_child(c); @@ -500,7 +502,10 @@ void ImportTool::DropGhosts(bool insert) command->add_child(new OpenSequenceCommand(sequence)); } - Core::instance()->undo_stack()->pushIfHasChildren(command); + // Do command now because RequestInvalidatedFromConnected relies on track type, which will be + // "none" before this command is done because it won't be connected to any track + command->redo_now(); + parent_command->add_child(command); while (!imported_clips.empty()) { imported_clips.front()->RequestInvalidatedFromConnected(); diff --git a/app/widget/timelinewidget/tool/import.h b/app/widget/timelinewidget/tool/import.h index 565d8f0ce..765fd9d04 100644 --- a/app/widget/timelinewidget/tool/import.h +++ b/app/widget/timelinewidget/tool/import.h @@ -37,8 +37,8 @@ public: using DraggedFootageData = QVector > >; - void PlaceAt(const QVector &footage, const rational& start, bool insert, int track_offset = 0); - void PlaceAt(const DraggedFootageData &footage, const rational& start, bool insert, int track_offset = 0); + void PlaceAt(const QVector &footage, const rational& start, bool insert, MultiUndoCommand *command, int track_offset = 0); + void PlaceAt(const DraggedFootageData &footage, const rational& start, bool insert, MultiUndoCommand *command, int track_offset = 0); enum DropWithoutSequenceBehavior { kDWSAsk, @@ -52,7 +52,7 @@ private: void PrepGhosts(const rational &frame, const int &track_index); - void DropGhosts(bool insert); + void DropGhosts(bool insert, MultiUndoCommand *parent_command); TimelineViewGhostItem* CreateGhost(const TimeRange &range, const rational &media_in, const Track::Reference &track);