engine: timeline panel core edit commands migrate to the facade (part 1)
- new batch primitives: split_clips (link-preserving, single undo command), delete_clips (gap replace + optional ripple with explicit region support), ripple_delete_range, marker_add_ex with color - razor/split-at-playhead, clip delete, ripple-to-point, track delete, and the non-dialog marker path now issue facade commands instead of the app's own undo command classes - batch operations deliberately produce one undo command per user action (deleting twenty clips is one entry, not twenty); selection and transition removal stay UI-side as documented leftovers
This commit is contained in:
@@ -30,6 +30,7 @@
|
||||
#include "common/current.h"
|
||||
#include "dialog/markerproperties/markerpropertiesdialog.h"
|
||||
#include "node/project/sequence/sequence.h"
|
||||
#include "oakengine/timeline.h"
|
||||
#include "timeline/timelineundoworkarea.h"
|
||||
#include "widget/timeruler/timeruler.h"
|
||||
|
||||
@@ -751,17 +752,35 @@ void TimeBasedWidget::set_marker()
|
||||
color, TimeRange(get_connected_node()->get_playhead(),
|
||||
get_connected_node()->get_playhead()));
|
||||
|
||||
bool edited_in_dialog = false;
|
||||
if (OAK_CONFIG("SetNameWithMarker").toBool()) {
|
||||
MarkerPropertiesDialog mpd({ marker }, timebase(), this);
|
||||
if (mpd.exec() != QDialog::Accepted) {
|
||||
delete marker;
|
||||
marker = nullptr;
|
||||
} else {
|
||||
edited_in_dialog = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (marker) {
|
||||
Core::instance()->undo_stack()->push(
|
||||
new MarkerAddCommand(markers, marker), tr("Added Marker"));
|
||||
if (edited_in_dialog) {
|
||||
// The dialog pushed undo commands referencing this exact
|
||||
// marker object, so it must be the one added to the list.
|
||||
Core::instance()->undo_stack()->push(
|
||||
new MarkerAddCommand(markers, marker), tr("Added Marker"));
|
||||
} else {
|
||||
// Pristine marker: add through the liboakengine C ABI
|
||||
// facade (one undoable command) and drop the temporary.
|
||||
oakengine_sequence_marker_add_ex(
|
||||
reinterpret_cast<OakEngineSequence *>(
|
||||
get_connected_node()),
|
||||
Timecode::time_to_timestamp(marker->time().in(),
|
||||
timebase(),
|
||||
Timecode::k_round),
|
||||
"", marker->color());
|
||||
delete marker;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
#include "node/nodeundo.h"
|
||||
#include "node/project/footage/footage.h"
|
||||
#include "node/project/serializer/serializer.h"
|
||||
#include "oakengine/timeline.h"
|
||||
#include "render/audiowaveformcache.h"
|
||||
#include "task/project/import/import.h"
|
||||
#include "timeline/timelineundogeneral.h"
|
||||
@@ -556,10 +557,20 @@ void TimelineWidget::split_at_playhead()
|
||||
}
|
||||
|
||||
if (!blocks_to_split.isEmpty()) {
|
||||
Core::instance()->undo_stack()->push(
|
||||
new BlockSplitPreservingLinksCommand(blocks_to_split,
|
||||
{ playhead_time }),
|
||||
tr("Split Clips At Playhead"));
|
||||
// Split through the liboakengine C ABI facade: one undoable,
|
||||
// link-preserving command with the same semantics as the old
|
||||
// app-side BlockSplitPreservingLinksCommand push.
|
||||
QVector<OakEngineClip *> clips;
|
||||
clips.reserve(blocks_to_split.size());
|
||||
foreach (Block *b, blocks_to_split) {
|
||||
clips.append(reinterpret_cast<OakEngineClip *>(
|
||||
static_cast<ClipBlock *>(b)));
|
||||
}
|
||||
oakengine_sequence_split_clips(
|
||||
reinterpret_cast<OakEngineSequence *>(sequence()), clips.data(),
|
||||
clips.size(),
|
||||
Timecode::time_to_timestamp(playhead_time, timebase(),
|
||||
Timecode::k_round));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -639,32 +650,47 @@ void TimelineWidget::DeleteSelected(bool ripple)
|
||||
command->add_child(trc);
|
||||
}
|
||||
|
||||
// Replace clips with gaps (effectively deleting them)
|
||||
replace_blocks_with_gaps(clips_to_delete, true, command, false);
|
||||
// Selection clearing and transition removal stay app-side (selection
|
||||
// state and transition commands have no facade equivalent); the clip
|
||||
// deletion core below goes through the facade and lands as one undoable
|
||||
// command right after this one, keeping the undo order intact.
|
||||
Core::instance()->undo_stack()->push(command, tr("Deleted Clips"));
|
||||
|
||||
// Insert ripple command now that it's all cleaned up gaps
|
||||
TimelineRippleDeleteGapsAtRegionsCommand *ripple_command = nullptr;
|
||||
Rational new_playhead = RATIONAL_MAX;
|
||||
if (ripple) {
|
||||
TimelineRippleDeleteGapsAtRegionsCommand::RangeList range_list;
|
||||
|
||||
foreach (Block *b, selected_list) {
|
||||
range_list.append({ b->track(), b->range() });
|
||||
new_playhead = qMin(new_playhead, b->in());
|
||||
}
|
||||
|
||||
ripple_command = new TimelineRippleDeleteGapsAtRegionsCommand(
|
||||
sequence(), range_list);
|
||||
command->add_child(ripple_command);
|
||||
// Delete the clips through the liboakengine C ABI facade (gap
|
||||
// replacement + graph removal, optionally rippling the selected ranges
|
||||
// closed), same semantics as the old in-command children.
|
||||
QVector<OakEngineClip *> facade_clips;
|
||||
facade_clips.reserve(clips_to_delete.size());
|
||||
foreach (Block *b, clips_to_delete) {
|
||||
facade_clips.append(
|
||||
reinterpret_cast<OakEngineClip *>(static_cast<ClipBlock *>(b)));
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->push(command, tr("Deleted Clips"));
|
||||
Rational new_playhead = RATIONAL_MAX;
|
||||
QVector<int64_t> ripple_ranges;
|
||||
if (ripple) {
|
||||
foreach (Block *b, selected_list) {
|
||||
ripple_ranges.append(int64_t(b->track()->type()));
|
||||
ripple_ranges.append(b->track()->index());
|
||||
ripple_ranges.append(Timecode::time_to_timestamp(
|
||||
b->in(), timebase(), Timecode::k_round));
|
||||
ripple_ranges.append(Timecode::time_to_timestamp(
|
||||
b->out(), timebase(), Timecode::k_round));
|
||||
new_playhead = qMin(new_playhead, b->in());
|
||||
}
|
||||
}
|
||||
|
||||
int rippled = 0;
|
||||
oakengine_sequence_delete_clips(
|
||||
reinterpret_cast<OakEngineSequence *>(sequence()),
|
||||
facade_clips.data(), facade_clips.size(), ripple ? 1 : 0,
|
||||
ripple ? ripple_ranges.constData() : nullptr,
|
||||
ripple ? ripple_ranges.size() / 4 : 0, &rippled);
|
||||
|
||||
// Ensures any current drag operations are cancelled
|
||||
clear_ghosts();
|
||||
|
||||
if (ripple_command && ripple_command->has_commands() &&
|
||||
new_playhead != RATIONAL_MAX) {
|
||||
if (ripple && rippled && new_playhead != RATIONAL_MAX) {
|
||||
get_connected_node()->set_playhead(new_playhead);
|
||||
}
|
||||
}
|
||||
@@ -2535,10 +2561,14 @@ void TimelineWidget::ripple_to(Timeline::MovementMode mode)
|
||||
Rational in_ripple = qMin(closest_point_to_playhead, playhead_time);
|
||||
Rational out_ripple = qMax(closest_point_to_playhead, playhead_time);
|
||||
|
||||
TimelineRippleRemoveAreaCommand *c =
|
||||
new TimelineRippleRemoveAreaCommand(sequence(), in_ripple, out_ripple);
|
||||
|
||||
Core::instance()->undo_stack()->push(c, tr("Rippled Clip(s) To Point"));
|
||||
// Ripple the region out through the liboakengine C ABI facade (one
|
||||
// undoable all-tracks ripple, same as the old
|
||||
// TimelineRippleRemoveAreaCommand push).
|
||||
oakengine_sequence_ripple_delete_range(
|
||||
reinterpret_cast<OakEngineSequence *>(sequence()),
|
||||
Timecode::time_to_timestamp(in_ripple, timebase(), Timecode::k_round),
|
||||
Timecode::time_to_timestamp(out_ripple, timebase(),
|
||||
Timecode::k_round));
|
||||
|
||||
// If we rippled, ump to where new cut is if applicable
|
||||
if (mode == Timeline::k_trim_in) {
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
|
||||
#include "razor.h"
|
||||
|
||||
#include "node/nodeundo.h"
|
||||
#include "timeline/timelineundosplit.h"
|
||||
#include "oakengine/timeline.h"
|
||||
#include "widget/timelinewidget/timelinewidget.h"
|
||||
|
||||
namespace olive
|
||||
@@ -94,10 +93,21 @@ void RazorTool::mouse_release(TimelineViewMouseEvent *event)
|
||||
split_tracks_.clear();
|
||||
|
||||
if (!blocks_to_split.isEmpty()) {
|
||||
Core::instance()->undo_stack()->push(
|
||||
new BlockSplitPreservingLinksCommand(blocks_to_split,
|
||||
{ split_time }),
|
||||
qApp->translate("RazorTool", "Split Clips"));
|
||||
// Split through the liboakengine C ABI facade: one undoable,
|
||||
// link-preserving command with the same semantics as the old
|
||||
// app-side BlockSplitPreservingLinksCommand push.
|
||||
QVector<OakEngineClip *> clips;
|
||||
clips.reserve(blocks_to_split.size());
|
||||
foreach (Block *b, blocks_to_split) {
|
||||
if (ClipBlock *clip = dynamic_cast<ClipBlock *>(b)) {
|
||||
clips.append(reinterpret_cast<OakEngineClip *>(clip));
|
||||
}
|
||||
}
|
||||
oakengine_sequence_split_clips(
|
||||
reinterpret_cast<OakEngineSequence *>(parent()->sequence()),
|
||||
clips.data(), clips.size(),
|
||||
Timecode::time_to_timestamp(split_time, parent()->timebase(),
|
||||
Timecode::k_round));
|
||||
}
|
||||
|
||||
dragging_ = false;
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <QtMath>
|
||||
|
||||
#include "core.h"
|
||||
#include "oakengine/timeline.h"
|
||||
#include "timeline/timelineundogeneral.h"
|
||||
#include "ui/icons/icons.h"
|
||||
#include "widget/menu/menu.h"
|
||||
@@ -159,9 +160,11 @@ void TrackViewItem::show_context_menu(const QPoint &p)
|
||||
void TrackViewItem::delete_track()
|
||||
{
|
||||
emit about_to_delete_track(track_);
|
||||
Core::instance()->undo_stack()->push(
|
||||
new TimelineRemoveTrackCommand(track_),
|
||||
tr("Deleted Track \"%1\"").arg(track_->get_label_or_name()));
|
||||
// Through the liboakengine C ABI facade (one undoable command, same as
|
||||
// the old TimelineRemoveTrackCommand push).
|
||||
oakengine_sequence_remove_track(
|
||||
reinterpret_cast<OakEngineSequence *>(track_->sequence()),
|
||||
int(track_->type()), track_->index());
|
||||
}
|
||||
|
||||
void TrackViewItem::delete_all_empty_tracks()
|
||||
|
||||
Reference in New Issue
Block a user