R8: finish app/ pure C ABI migration (P3-P9) and make OTIO required

- app/ no longer includes engine C++ headers nor holds engine C++ types:
  engine access goes through the oakengine C ABI plus C++ wrappers
  (oakutil/oaknode.h, oakutil/oakvideo.h) and app-local mirror types
  (tooltypes, trackreferencehandle, timelinecommonapp, keyframetypes,
  subtitleapp, serializedlayoutinfoapp, nodevaluehandle, sliderdisplaytypeapp)
- engine: new C ABI functions for block/track/clip/transition navigation
  and predicates, links, caches, waveform/playback, disk folder,
  sequence_track_list, node_free, footage_is_valid, block_get_track,
  get_brush; loadotio/saveotio ported to the current engine API
- OTIO is now a required dependency: CI and CD build it on every
  platform, FindOpenTimelineIO fixed for OTIO 0.16/0.19 (the old deps
  include requirement silently disabled OTIO everywhere), runtime
  libraries are bundled into packages and copied next to macOS binaries
  (oak_copy_otio_runtime)
- fix ProjectViewModel drag&drop mime read/write size mismatch (segfault)
- unify color label naming (k_olive -> "Oak") in the app-side mirror
- docs: OTIO required, FFmpeg minimum corrected to 6.0 (en/zh)
- gtest suite: 1925 passed, 0 failed
This commit is contained in:
2026-07-31 22:46:52 +08:00
parent 18aed979a2
commit 66d761b4b7
285 changed files with 13261 additions and 5895 deletions
+71 -43
View File
@@ -23,60 +23,87 @@
#include <cstring>
#include "node/block/clip/clip.h"
#include <olive/core/core.h>
#include "oakengine/node.h"
#include "oakengine/timeline.h"
namespace olive
{
class FrameHashCache;
class AudioWaveformCache;
using olive::core::Rational;
using olive::core::TimeRange;
/**
* @brief Facade accessors for ClipBlock pointers held by the timeline UI.
* @brief Facade accessors for clip blocks held by the timeline UI.
*
* ClipBlock's header-inline convenience accessors (speed()/loop_mode()/
* The engine's header-inline clip convenience accessors (speed()/loop_mode()/
* thumbnails()/waveform()/connected_video_cache()/...) reference the
* input-id statics (k_speed_input/k_buffer_in/...), which are engine
* symbols the app must no longer pull across the liboakengine boundary.
* These helpers route the same queries through the C ABI instead (same
* pattern as app/widget/keyframeview/keyframehandle.h). The ClipBlock*
* itself stays an opaque identity pointer.
* pattern as app/widget/keyframeview/keyframehandle.h). Clips are passed
* as OakEngineBlock* handles; the engine cache types
* (FrameHashCache/AudioWaveformCache) are forward-declared here and only
* dereferenced by callers that still see the engine class definition.
*/
inline OakEngineClip *cliphandle(ClipBlock *clip)
inline OakEngineClip *cliphandle(OakEngineBlock *clip)
{
return reinterpret_cast<OakEngineClip *>(clip);
}
/** @brief The node feeding the clip's buffer input (ClipBlock's inline
/** @brief The node feeding the clip's buffer input (the inline
* get_connected_output(k_buffer_in) uses; borrowed, may be null). */
inline Node *clip_connected_node(ClipBlock *clip)
inline OakEngineNode *clip_connected_node(OakEngineBlock *clip)
{
return reinterpret_cast<Node *>(
oakengine_node_input_get_connected_node(
reinterpret_cast<OakEngineNode *>(clip),
oakengine_clip_buffer_input_id(), -1));
return oakengine_node_input_get_connected_node(
reinterpret_cast<OakEngineNode *>(clip),
oakengine_clip_buffer_input_id(), -1);
}
inline FrameHashCache *clip_thumbnails(ClipBlock *clip)
inline FrameHashCache *clip_thumbnails(OakEngineBlock *clip)
{
Node *n = clip_connected_node(clip);
return n ? n->thumbnail_cache() : nullptr;
OakEngineNode *n = clip_connected_node(clip);
return n ? reinterpret_cast<FrameHashCache *>(
oakengine_node_get_thumbnail_cache(n)) :
nullptr;
}
inline AudioWaveformCache *clip_waveform(ClipBlock *clip)
inline AudioWaveformCache *clip_waveform(OakEngineBlock *clip)
{
Node *n = clip_connected_node(clip);
return n ? n->waveform_cache() : nullptr;
OakEngineNode *n = clip_connected_node(clip);
return n ? reinterpret_cast<AudioWaveformCache *>(
oakengine_node_get_waveform_cache(n)) :
nullptr;
}
inline FrameHashCache *clip_connected_video_cache(ClipBlock *clip)
inline FrameHashCache *clip_connected_video_cache(OakEngineBlock *clip)
{
Node *n = clip_connected_node(clip);
return n ? n->video_frame_cache() : nullptr;
OakEngineNode *n = clip_connected_node(clip);
return n ? reinterpret_cast<FrameHashCache *>(
oakengine_node_get_video_frame_cache(n)) :
nullptr;
}
/** @brief ClipBlock::speed() through the facade input getter. */
inline double clip_speed(ClipBlock *clip)
/**
* @brief Owning track handle of a block of any kind (the block's track).
*
* Wraps the generic oakengine_block_get_track(); the timeline family's
* OakEngineTrack* is reinterpreted to the node family's OakEngineNode*
* (same underlying track object; the track accessors in node.h take the
* latter). NULL when the block is not on a track.
*/
inline OakEngineNode *block_track_handle(OakEngineBlock *block)
{
return reinterpret_cast<OakEngineNode *>(oakengine_block_get_track(block));
}
/** @brief The clip's speed through the facade input getter. */
inline double clip_speed(OakEngineBlock *clip)
{
oak_node_value v;
memset(&v, 0, sizeof(v));
@@ -88,8 +115,8 @@ inline double clip_speed(ClipBlock *clip)
return v.f[0];
}
/** @brief ClipBlock::loop_mode() value (an olive::LoopMode int). */
inline int clip_loop_mode(ClipBlock *clip)
/** @brief The clip's loop mode value (an OAKENGINE_LOOP_MODE_* int). */
inline int clip_loop_mode(OakEngineBlock *clip)
{
oak_node_value v;
memset(&v, 0, sizeof(v));
@@ -101,8 +128,8 @@ inline int clip_loop_mode(ClipBlock *clip)
return int(v.num);
}
/** @brief ClipBlock::is_reversed() through the facade input getter. */
inline bool clip_is_reversed(ClipBlock *clip)
/** @brief The clip's reverse flag through the facade input getter. */
inline bool clip_is_reversed(OakEngineBlock *clip)
{
oak_node_value v;
memset(&v, 0, sizeof(v));
@@ -112,8 +139,8 @@ inline bool clip_is_reversed(ClipBlock *clip)
v.num != 0;
}
/** @brief ClipBlock::maintain_audio_pitch() through the facade. */
inline bool clip_maintain_audio_pitch(ClipBlock *clip)
/** @brief The clip's maintain-audio-pitch flag through the facade. */
inline bool clip_maintain_audio_pitch(OakEngineBlock *clip)
{
oak_node_value v;
memset(&v, 0, sizeof(v));
@@ -124,14 +151,14 @@ inline bool clip_maintain_audio_pitch(ClipBlock *clip)
v.num != 0;
}
/** @brief Create a new empty ClipBlock through the C ABI. */
inline ClipBlock *clip_create_empty(const char *label = nullptr)
/** @brief Create a new empty clip block through the C ABI. */
inline OakEngineBlock *clip_create_empty(const char *label = nullptr)
{
return reinterpret_cast<ClipBlock *>(oakengine_clip_create_empty(label));
return reinterpret_cast<OakEngineBlock *>(oakengine_clip_create_empty(label));
}
/** @brief ClipBlock::media_in() through the facade. */
inline Rational clip_media_in(ClipBlock *clip)
/** @brief The clip's media in-point through the facade. */
inline Rational clip_media_in(OakEngineBlock *clip)
{
int64_t num = 0, den = 1;
if (oakengine_clip_get_media_in_rational(cliphandle(clip), &num, &den) ==
@@ -141,23 +168,23 @@ inline Rational clip_media_in(ClipBlock *clip)
return Rational(0, 1);
}
/** @brief ClipBlock::media_range() through the facade. */
inline TimeRange clip_media_range(ClipBlock *clip)
/** @brief The clip's media range through the facade. */
inline TimeRange clip_media_range(OakEngineBlock *clip)
{
int64_t in_num = 0, in_den = 1, out_num = 0, out_den = 1;
if (oakengine_clip_get_media_range_rational(
cliphandle(clip), &in_num, &in_den, &out_num, &out_den) ==
OAKENGINE_OK) {
return TimeRange(Rational(static_cast<int>(in_num),
static_cast<int>(in_den)),
static_cast<int>(in_den)),
Rational(static_cast<int>(out_num),
static_cast<int>(out_den)));
static_cast<int>(out_den)));
}
return TimeRange(0, 0);
}
/** @brief Set the clip's media in-point directly (rational seconds). */
inline void clip_set_media_in(ClipBlock *clip, const Rational &media_in,
inline void clip_set_media_in(OakEngineBlock *clip, const Rational &media_in,
bool undoable = false)
{
if (!clip) {
@@ -169,8 +196,8 @@ inline void clip_set_media_in(ClipBlock *clip, const Rational &media_in,
undoable ? 1 : 0);
}
/** @brief ClipBlock::is_autocaching() through the facade. */
inline bool clip_is_autocaching(ClipBlock *clip)
/** @brief The clip's auto-cache flag through the facade. */
inline bool clip_is_autocaching(OakEngineBlock *clip)
{
oak_node_value v;
memset(&v, 0, sizeof(v));
@@ -181,9 +208,10 @@ inline bool clip_is_autocaching(ClipBlock *clip)
v.num != 0;
}
/** @brief ClipBlock::request_invalidated_from_connected() through the facade. */
/** @brief Request invalidation from the clip's connected node, through the
* facade. */
inline void clip_request_invalidate_connected(
ClipBlock *clip, bool force_all = false,
OakEngineBlock *clip, bool force_all = false,
const TimeRange &intersect = TimeRange())
{
if (!clip) {
File diff suppressed because it is too large Load Diff
+50 -42
View File
@@ -29,13 +29,12 @@
#include <QWidget>
#include "core.h"
#include "node/block/transition/transition.h"
#include "node/output/viewer/viewer.h"
#include "oakengine/events.h"
#include "oakengine/node.h"
#include "oakengine/serializer.h"
#include "oakengine/timeline.h"
#include "oakengine/undo.h"
#include "timeline/timelinecommon.h"
#include "timeline/timelinecommonapp.h"
#include "timelineandtrackview.h"
#include "widget/slider/rationalslider.h"
#include "widget/timebased/timebasedwidget.h"
@@ -127,7 +126,7 @@ public:
void show_proxy_dialog_for_selected_clips();
void recording_callback(const QString &filename, const TimeRange &time,
const Track::Reference &track);
const TrackReference &track);
void enable_recording_overlay(const TimelineCoordinate &coord);
@@ -140,12 +139,21 @@ public:
/**
* @brief Timelines should always be connected to sequences
*/
Sequence *sequence() const
OakEngineSequence *sequence() const
{
return static_cast<Sequence *>(get_connected_node());
// R8: the type check goes through the C ABI facade predicate; the
// Sequence handle is shared with the node handle, so no engine C++
// definition is needed here (replaces the old static_cast, which
// required the complete engine type).
auto *connected = get_connected_node();
return (connected &&
oakengine_node_is_sequence(
reinterpret_cast<OakEngineNode *>(connected)))
? reinterpret_cast<OakEngineSequence *>(connected)
: nullptr;
}
const QVector<Block *> &get_selected_blocks() const
const QVector<OakEngineBlock *> &get_selected_blocks() const
{
return selected_blocks_;
}
@@ -154,7 +162,7 @@ public:
void restore_splitter_state(const QByteArray &state);
static void replace_blocks_with_gaps(const QVector<Block *> &blocks,
static void replace_blocks_with_gaps(const QVector<OakEngineBlock *> &blocks,
bool remove_from_graph,
void *command,
bool handle_transitions = true);
@@ -165,13 +173,13 @@ public:
* Requires a float-based scene position. If you have a screen position, use GetScenePos() first to convert it to a
* scene position
*/
Block *get_item_at_scene_pos(const TimelineCoordinate &coord);
OakEngineBlock *get_item_at_scene_pos(const TimelineCoordinate &coord);
void add_selection(const TimeRange &time, const Track::Reference &track);
void add_selection(Block *item);
void add_selection(const TimeRange &time, const TrackReference &track);
void add_selection(OakEngineBlock *item);
void remove_selection(const TimeRange &time, const Track::Reference &track);
void remove_selection(Block *item);
void remove_selection(const TimeRange &time, const TrackReference &track);
void remove_selection(OakEngineBlock *item);
const TimelineWidgetSelections &get_selections() const
{
@@ -181,10 +189,10 @@ public:
void set_selections(const TimelineWidgetSelections &s,
bool process_block_changes);
Track *get_track_from_reference(const Track::Reference &ref) const;
OakEngineTrack *get_track_from_reference(const TrackReference &ref) const;
void set_view_beam_cursor(const TimelineCoordinate &coord);
void set_view_transition_overlay(ClipBlock *out, ClipBlock *in);
void set_view_transition_overlay(OakEngineClip *out, OakEngineClip *in);
const QVector<TimelineViewGhostItem *> &get_ghost_items() const
{
@@ -198,8 +206,8 @@ public:
void move_rubber_band_select(bool enable_selecting, bool select_links);
void end_rubber_band_select();
int get_track_y(const Track::Reference &ref);
int get_track_height(const Track::Reference &ref);
int get_track_y(const TrackReference &ref);
int get_track_height(const TrackReference &ref);
void add_ghost(TimelineViewGhostItem *ghost);
@@ -210,18 +218,18 @@ public:
return !ghost_items_.isEmpty();
}
bool is_block_selected(Block *b) const
bool is_block_selected(OakEngineBlock *b) const
{
return selected_blocks_.contains(b);
}
void set_block_links_selected(ClipBlock *block, bool selected);
void set_block_links_selected(OakEngineClip *block, bool selected);
void queue_scroll(int value);
TimelineView *get_first_timeline_view();
Rational get_timebase_for_track_type(Track::Type type);
Rational get_timebase_for_track_type(TrackReference::Type type);
const QRect &get_rubber_band_geometry() const;
@@ -241,13 +249,13 @@ public:
* this is preferable and should only be set to FALSE if the list is guaranteed not to contain
* already selected blocks (and therefore filtering can be skipped to save time).
*/
void signal_selected_blocks(QVector<Block *> selected_blocks,
void signal_selected_blocks(QVector<OakEngineBlock *> selected_blocks,
bool filter = true);
/**
* @brief Track blocks that have been newly deselected
*/
void signal_deselected_blocks(const QVector<Block *> &deselected_blocks);
void signal_deselected_blocks(const QVector<OakEngineBlock *> &deselected_blocks);
/**
* @brief Convenience function to deselect all blocks and signal them
@@ -285,7 +293,7 @@ signals:
void block_selection_changed(const QVector<OakEngineBlock *> &selected_blocks);
void request_capture_start(const TimeRange &time,
const Track::Reference &track);
const TrackReference &track);
void reveal_viewer_in_footage_viewer(OakEngineNode *r, const TimeRange &range);
void reveal_viewer_in_project(OakEngineNode *r);
@@ -297,10 +305,10 @@ protected:
virtual void TimebaseChangedEvent(const Rational &) override;
virtual void ScaleChangedEvent(const double &) override;
virtual void ConnectNodeEvent(ViewerOutput *n) override;
virtual void DisconnectNodeEvent(ViewerOutput *n) override;
virtual void ConnectNodeEvent(OakEngineNode *n) override;
virtual void DisconnectNodeEvent(OakEngineNode *n) override;
virtual const QVector<Block *> *get_snap_blocks() const override
virtual const QVector<OakEngineBlock *> *get_snap_blocks() const override
{
return &added_blocks_;
}
@@ -309,14 +317,14 @@ protected slots:
virtual void SendCatchUpScrollEvent() override;
private:
QVector<Timeline::EditToInfo> get_edit_to_info(const Rational &playhead_time,
Timeline::MovementMode mode);
QVector<TimelineApp::EditToInfo> get_edit_to_info(const Rational &playhead_time,
TimelineApp::MovementMode mode);
void ripple_to(Timeline::MovementMode mode);
void ripple_to(TimelineApp::MovementMode mode);
void edit_to(Timeline::MovementMode mode);
void edit_to(TimelineApp::MovementMode mode);
void update_viewports(const Track::Type &type = Track::k_none);
void update_viewports(const TrackReference::Type &type = TrackReference::k_none);
bool paste_internal(bool insert);
@@ -324,13 +332,13 @@ private:
TimelineAndTrackView *add_timeline_and_track_view(Qt::Alignment alignment);
QHash<Node *, Node *>
QHash<OakEngineNode *, OakEngineNode *>
generate_existing_paste_map(void *clipboard);
QRubberBand rubberband_;
QVector<QPointF> rubberband_scene_pos_;
TimelineWidgetSelections rubberband_old_selections_;
QVector<Block *> rubberband_now_selected_;
QVector<OakEngineBlock *> rubberband_now_selected_;
bool rubberband_enable_selecting_;
bool rubberband_select_links_;
@@ -350,11 +358,11 @@ private:
RationalSlider *timecode_label_;
QVector<Block *> selected_blocks_;
QVector<OakEngineBlock *> selected_blocks_;
QVector<Block *> added_blocks_;
QVector<OakEngineBlock *> added_blocks_;
QHash<Block *, QVector<int64_t>> block_subscriptions_;
QHash<OakEngineBlock *, QVector<int64_t>> block_subscriptions_;
int deferred_scroll_value_;
@@ -424,7 +432,7 @@ private slots:
void view_drag_left(QDragLeaveEvent *event);
void view_drag_dropped(TimelineViewMouseEvent *event);
void track_updated(Track::Type type);
void track_updated(TrackReference::Type type);
void block_updated(OakEngineBlock *block = nullptr);
@@ -468,13 +476,13 @@ private slots:
void force_update_rubber_band();
private:
void add_block(Block *block);
void remove_block(Block *blocks);
void add_block(OakEngineBlock *block);
void remove_block(OakEngineBlock *blocks);
void add_track(Track *track);
void remove_track(Track *track);
void add_track(OakEngineTrack *track);
void remove_track(OakEngineTrack *track);
void track_index_changed(Track *track, int old, int now);
void track_index_changed(OakEngineTrack *track, int old, int now);
void track_about_to_be_deleted(OakEngineTrack *track);
};
@@ -31,7 +31,7 @@ void TimelineWidgetSelections::shift_time(const Rational &diff)
}
}
void TimelineWidgetSelections::shift_tracks(Track::Type type, int diff)
void TimelineWidgetSelections::shift_tracks(TrackReference::Type type, int diff)
{
TimelineWidgetSelections cached_selections;
@@ -51,7 +51,7 @@ void TimelineWidgetSelections::shift_tracks(Track::Type type, int diff)
// Then re-insert them with the diff applied
for (auto it = cached_selections.cbegin(); it != cached_selections.cend();
it++) {
Track::Reference ref(it.key().type(), it.key().index() + diff);
TrackReference ref(it.key().type(), it.key().index() + diff);
this->insert(ref, it.value());
}
@@ -75,7 +75,7 @@ void TimelineWidgetSelections::subtract(
const TimelineWidgetSelections &selections)
{
for (auto it = selections.cbegin(); it != selections.cend(); it++) {
const Track::Reference &track = it.key();
const TrackReference &track = it.key();
const TimeRangeList &their_list = it.value();
if (this->contains(track)) {
@@ -24,18 +24,22 @@
#include <QHash>
#include "node/output/track/track.h"
#include <olive/core/util/timerange.h>
#include "common/trackreferencehandle.h"
namespace olive
{
class TimelineWidgetSelections : public QHash<Track::Reference, TimeRangeList> {
using namespace core;
class TimelineWidgetSelections : public QHash<TrackReference, TimeRangeList> {
public:
TimelineWidgetSelections() = default;
void shift_time(const Rational &diff);
void shift_tracks(Track::Type type, int diff);
void shift_tracks(TrackReference::Type type, int diff);
void trim_in(const Rational &diff);
@@ -23,8 +23,8 @@
#include <cmath>
#include "node/block/clip/clip.h"
#include "render/audiowaveformcache.h"
#include "olive/core/util/timecodefunctions.h"
#include "oakengine/viewer.h"
#include "widget/timelinewidget/cliphandle.h"
namespace olive
@@ -33,10 +33,81 @@ namespace olive
namespace timeline_waveform_sync
{
bool get_waveform_sync_clip(Block *block, WaveformSyncClip *out)
namespace
{
ClipBlock *clip = dynamic_cast<ClipBlock *>(block);
if (!clip || !clip_waveform(clip)) {
/**
* @brief Validated ranges of a waveform cache as a TimeRangeList.
*
* WRAPPER-GAP: the C ABI has no waveform-specific validated-ranges accessor;
* oakengine_playback_cache_valid_ranges() is reused instead. That function
* reinterprets the handle as PlaybackCache, which is sound here because
* AudioWaveformCache derives (single inheritance) from PlaybackCache.
*/
TimeRangeList waveform_validated_ranges(const void *waveform)
{
TimeRangeList list;
QVector<int64_t> quads(4 * 64);
int count;
while ((count = oakengine_playback_cache_valid_ranges(
static_cast<OakEnginePlaybackCache *>(
const_cast<void *>(waveform)),
quads.data(), quads.size() / 4)) == quads.size() / 4) {
quads.resize(quads.size() * 2);
}
for (int i = 0; i < count; i++) {
list.insert(TimeRange(Rational(static_cast<int>(quads.at(i * 4 + 0)),
static_cast<int>(quads.at(i * 4 + 1))),
Rational(static_cast<int>(quads.at(i * 4 + 2)),
static_cast<int>(quads.at(i * 4 + 3)))));
}
return list;
}
/**
* @brief Peak over [t, t + length) across all channels
* (AudioWaveformCache::get_summary_from_time() equivalent). 0 when the
* summary is unavailable.
*/
double waveform_window_peak(const void *waveform, const Rational &t,
const Rational &length, int sample_rate)
{
// The summary C ABI works in sample frames at the cache's sample rate
const Rational sample_tb(1, sample_rate);
const int64_t start_ts = core::Timecode::time_to_timestamp(
t, sample_tb, core::Timecode::k_round);
const int64_t end_ts = core::Timecode::time_to_timestamp(
t + length, sample_tb, core::Timecode::k_round);
double min_vals[64], max_vals[64];
int channels = 0;
if (oakengine_waveform_cache_get_summary(waveform, start_ts, end_ts,
min_vals, max_vals, 64,
&channels) != OAKENGINE_OK) {
return 0.0;
}
double peak = 0.0;
for (int i = 0; i < channels; i++) {
const double channel_peak =
std::max(std::abs(min_vals[i]), std::abs(max_vals[i]));
peak = std::max(peak, channel_peak);
}
return peak;
}
} // namespace
bool get_waveform_sync_clip(OakEngineBlock *block, WaveformSyncClip *out)
{
if (!block ||
!oakengine_node_is_clip(reinterpret_cast<OakEngineNode *>(block))) {
return false;
}
OakEngineBlock *clip = block;
const void *waveform = clip_waveform(clip);
if (!waveform) {
return false;
}
@@ -45,8 +116,8 @@ bool get_waveform_sync_clip(Block *block, WaveformSyncClip *out)
return false;
}
const AudioWaveformCache *waveform = clip_waveform(clip);
if (waveform->get_parameters().sample_rate() <= 0) {
const int sample_rate = oakengine_waveform_cache_sample_rate(waveform);
if (sample_rate <= 0) {
return false;
}
@@ -55,7 +126,7 @@ bool get_waveform_sync_clip(Block *block, WaveformSyncClip *out)
// validated makes the menu item stay disabled for long clips and gives
// the appearance that "nothing happens" when the user tries to sync.
const TimeRangeList validated_ranges =
waveform->get_validated_ranges().intersects(media_range);
waveform_validated_ranges(waveform).intersects(media_range);
if (validated_ranges.isEmpty()) {
return false;
}
@@ -63,15 +134,15 @@ bool get_waveform_sync_clip(Block *block, WaveformSyncClip *out)
out->clip = clip;
out->waveform = waveform;
out->media_range = media_range;
out->sample_rate = waveform->get_parameters().sample_rate();
out->sample_rate = sample_rate;
return true;
}
QVector<WaveformSyncClip>
get_selected_waveform_sync_clips(const QVector<Block *> &blocks)
get_selected_waveform_sync_clips(const QVector<OakEngineBlock *> &blocks)
{
QVector<WaveformSyncClip> clips;
for (Block *block : blocks) {
for (OakEngineBlock *block : blocks) {
WaveformSyncClip sync_clip;
if (get_waveform_sync_clip(block, &sync_clip)) {
clips.append(sync_clip);
@@ -103,7 +174,7 @@ QVector<double> extract_waveform_cache_envelope(const WaveformSyncClip &clip,
// absolute timeline, while the validity mask lets the correlation skip
// those placeholders entirely.
const TimeRangeList validated_ranges =
clip.waveform->get_validated_ranges().intersects(clip.media_range);
waveform_validated_ranges(clip.waveform).intersects(clip.media_range);
for (Rational t = clip.media_range.in(); t < clip.media_range.out();
t += window_time) {
@@ -114,16 +185,7 @@ QVector<double> extract_waveform_cache_envelope(const WaveformSyncClip &clip,
double peak = 0.0;
if (window_valid) {
const AudioVisualWaveform::Sample summary =
clip.waveform->get_summary_from_time(t, length);
for (const AudioVisualWaveform::SamplePerChannel &channel :
summary) {
const double channel_peak =
std::max(std::abs(static_cast<double>(channel.min)),
std::abs(static_cast<double>(channel.max)));
peak = std::max(peak, channel_peak);
}
peak = waveform_window_peak(clip.waveform, t, length, sample_rate);
}
envelope.append(peak);
@@ -24,22 +24,25 @@
#include <QVector>
#include "node/block/block.h"
#include "olive/core/util/rational.h"
#include "olive/core/util/timerange.h"
#include "oakengine/timeline.h"
namespace olive
{
class AudioWaveformCache;
class ClipBlock;
// Same namespace bridge the engine headers used to provide (unqualified
// Rational/TimeRange/TimeRangeList inside namespace olive).
using namespace core;
/**
* @brief Data required to synchronize a clip using its cached audio waveform.
*/
struct WaveformSyncClip {
ClipBlock *clip = nullptr;
const AudioWaveformCache *waveform = nullptr;
OakEngineBlock *clip = nullptr;
/// Opaque engine AudioWaveformCache handle, only ever passed to the
/// oakengine_waveform_cache_* C ABI (never dereferenced).
const void *waveform = nullptr;
TimeRange media_range;
int sample_rate = 0;
};
@@ -59,13 +62,13 @@ namespace timeline_waveform_sync
* has been validated in the waveform cache. Previously the whole range had to
* be validated, which made the context-menu action unavailable for long clips.
*/
bool get_waveform_sync_clip(Block *block, WaveformSyncClip *out);
bool get_waveform_sync_clip(OakEngineBlock *block, WaveformSyncClip *out);
/**
* @brief Return all selected blocks that can be synchronized by waveform.
*/
QVector<WaveformSyncClip>
get_selected_waveform_sync_clips(const QVector<Block *> &blocks);
get_selected_waveform_sync_clips(const QVector<OakEngineBlock *> &blocks);
/**
* @brief Extract a peak envelope from the validated regions of a waveform cache.
+38 -31
View File
@@ -21,17 +21,12 @@
#include "add.h"
#include "core.h"
#include "node/block/subtitle/subtitle.h"
#include "node/factory.h"
#include "node/generator/shape/shapenode.h"
#include "node/generator/solid/solid.h"
#include "node/generator/text/textv3.h"
#include "oakengine/node.h"
#include "oakengine/node.h"
#include "oakengine/timeline.h"
#include "oakengine/undo.h"
#include "timeline/timelineundopointer.h"
#include "widget/timelinewidget/cliphandle.h"
#include "widget/timelinewidget/timelinewidget.h"
#include "widget/timelinewidget/trackhandle.h"
#include "widget/viewer/vieweroutpututils.h"
namespace olive
@@ -45,28 +40,28 @@ AddTool::AddTool(TimelineWidget *parent)
void AddTool::mouse_press(TimelineViewMouseEvent *event)
{
const Track::Reference &track = event->get_track();
const TrackReference &track = event->get_track();
// Check if track is locked
Track *t = parent()->get_track_from_reference(track);
if (t && t->is_locked()) {
OakEngineTrack *t = parent()->get_track_from_reference(track);
if (track_is_locked(t)) {
return;
}
Track::Type add_type = Track::k_none;
TrackReference::Type add_type = TrackReference::k_none;
switch (Core::instance()->get_selected_addable_object()) {
case Tool::k_addable_bars:
case Tool::k_addable_solid:
case Tool::k_addable_title:
case Tool::k_addable_shape:
add_type = Track::k_video;
add_type = TrackReference::k_video;
break;
case Tool::k_addable_tone:
add_type = Track::k_audio;
add_type = TrackReference::k_audio;
break;
case Tool::k_addable_subtitle:
add_type = Track::k_subtitle;
add_type = TrackReference::k_subtitle;
break;
case Tool::k_addable_empty:
// Leave as "none", which means this block can be placed on any track
@@ -76,7 +71,7 @@ void AddTool::mouse_press(TimelineViewMouseEvent *event)
return;
}
if (add_type == Track::k_none || add_type == track.type()) {
if (add_type == TrackReference::k_none || add_type == track.type()) {
drag_start_point_ =
validated_coordinate(event->get_coordinates(true)).get_frame();
@@ -111,12 +106,12 @@ void AddTool::mouse_release(TimelineViewMouseEvent *event)
oakengine_undo_command_multi_add_child(command, subtitle_section_command);
}
Sequence *s = parent()->sequence();
OakEngineSequence *s = sequence();
QRectF r;
if (Core::instance()->get_selected_addable_object() ==
Tool::k_addable_title) {
VideoParams svp = viewer_output_video_params(s);
oak::VideoParams svp = viewer_output_video_params(s);
r = QRectF(0, 0, svp.width(), svp.height());
r.adjust(svp.width() / 10, svp.height() / 10, -svp.width() / 10,
-svp.height() / 10);
@@ -136,44 +131,56 @@ void AddTool::mouse_release(TimelineViewMouseEvent *event)
}
}
Node *AddTool::create_addable_clip(void *command, Sequence *sequence,
const Track::Reference &track,
OakEngineNode *AddTool::create_addable_clip(void *command, OakEngineSequence *sequence,
const TrackReference &track,
const Rational &in, const Rational &length,
const QRectF &rect)
{
ClipBlock *clip;
OakEngineBlock *clip;
if (Core::instance()->get_selected_addable_object() ==
Tool::k_addable_subtitle) {
clip = reinterpret_cast<SubtitleBlock*>(oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.subtitle"));
clip = reinterpret_cast<OakEngineBlock*>(oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.subtitle"));
} else {
clip = clip_create_empty(olive::Tool::get_addable_object_name(
Core::instance()->get_selected_addable_object()).toUtf8().constData());
}
clip->set_length_and_media_out(length);
Project *graph = sequence->parent();
OakEngineProject *graph = oakengine_node_get_project(
reinterpret_cast<OakEngineNode *>(sequence));
oakengine_undo_command_multi_add_child(command,
oakengine_node_add_to_project_command(
reinterpret_cast<OakEngineProject *>(graph),
graph,
reinterpret_cast<OakEngineNode *>(clip)));
oakengine_undo_command_multi_add_child(command, oakengine_node_set_position_command(reinterpret_cast<void *>(clip), reinterpret_cast<void *>(clip), 0, 0, 0));
oakengine_undo_command_multi_add_child(command, oakengine_track_place_block_command(reinterpret_cast<void *>(sequence->track_list(track.type())), track.index(), reinterpret_cast<void *>(clip), core::Timecode::time_to_timestamp(in, sequence_timebase(sequence))));
// Set the clip's length before placement through a trim command child
// (children redo in order): oakengine_block_set_length_and_media_out()
// requires the block to already be on a track (OAKENGINE_E_STATE), and
// pre-placement there are no adjacent blocks, so a trim-out command
// reduces to Block::set_length_and_media_out().
oakengine_undo_command_multi_add_child(command,
oakengine_block_trim_command(
reinterpret_cast<void *>(oakengine_sequence_track_at(
sequence,
track.type(), track.index())),
reinterpret_cast<void *>(clip),
length.numerator(), length.denominator(),
OAKENGINE_MOVEMENT_MODE_TRIM_OUT, 0));
oakengine_undo_command_multi_add_child(command, oakengine_track_place_block_command(reinterpret_cast<void *>(oakengine_sequence_track_list(sequence, track.type())), track.index(), reinterpret_cast<void *>(clip), core::Timecode::time_to_timestamp(in, sequence_timebase(sequence))));
Node *node_to_add = nullptr;
OakEngineNode *node_to_add = nullptr;
switch (Core::instance()->get_selected_addable_object()) {
case Tool::k_addable_empty:
// Empty, nothing to be done
break;
case Tool::k_addable_solid:
node_to_add = reinterpret_cast<SolidGenerator*>(oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.solidgenerator"));
node_to_add = oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.solidgenerator");
break;
case Tool::k_addable_shape:
node_to_add = reinterpret_cast<ShapeNode*>(oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.shape"));
node_to_add = oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.shape");
break;
case Tool::k_addable_title:
node_to_add = reinterpret_cast<TextGeneratorV3*>(oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.text3"));
node_to_add = oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.text3");
break;
case Tool::k_addable_bars:
case Tool::k_addable_tone:
@@ -205,7 +212,7 @@ Node *AddTool::create_addable_clip(void *command, Sequence *sequence,
oakengine_undo_command_multi_add_child(command, oakengine_node_set_position_command(reinterpret_cast<void *>(node_to_add), reinterpret_cast<void *>(clip), extra_node_offset.x(), extra_node_offset.y(), 0));
if (!rect.isNull()) {
const VideoParams vp = viewer_output_video_params(sequence);
const oak::VideoParams vp = viewer_output_video_params(sequence);
oak_video_params pod = {};
pod.width = vp.width();
pod.height = vp.height();
+3 -3
View File
@@ -35,9 +35,9 @@ public:
virtual void mouse_move(TimelineViewMouseEvent *event) override;
virtual void mouse_release(TimelineViewMouseEvent *event) override;
static Node *create_addable_clip(void *command,
Sequence *sequence,
const Track::Reference &track,
static OakEngineNode *create_addable_clip(void *command,
OakEngineSequence *sequence,
const TrackReference &track,
const Rational &in, const Rational &length,
const QRectF &rect = QRectF());
+4 -2
View File
@@ -21,6 +21,7 @@
#include "edit.h"
#include "widget/timelinewidget/timelinewidget.h"
#include "widget/timelinewidget/trackhandle.h"
namespace olive
{
@@ -84,9 +85,10 @@ void EditTool::mouse_release(TimelineViewMouseEvent *event)
void EditTool::mouse_double_click(TimelineViewMouseEvent *event)
{
Block *item = parent()->get_item_at_scene_pos(event->get_coordinates());
OakEngineBlock *item =
parent()->get_item_at_scene_pos(event->get_coordinates());
if (item && !item->track()->is_locked()) {
if (item && !track_is_locked(oakengine_block_get_track(item))) {
parent()->add_selection(item);
}
}
+112 -71
View File
@@ -27,21 +27,16 @@
#include <QToolTip>
#include "common/configwrapper.h"
#include "common/subtitleapp.h"
#include "oakutil/oaknode.h"
#include "oakutil/qtutils.h"
#include "core.h"
#include "dialog/sequence/sequence.h"
#include "node/audio/volume/volume.h"
#include "node/block/subtitle/subtitle.h"
#include "node/distort/transform/transformdistortnode.h"
#include "node/generator/matrix/matrix.h"
#include "node/math/math/math.h"
#include "node/project/sequence/sequence.h"
#include "oakengine/node.h"
#include "oakengine/timeline.h"
#include "oakengine/undo.h"
#include "oakengine/viewer.h"
#include "oakengine/project.h"
#include "timeline/timelineundopointer.h"
#include "widget/timelinewidget/cliphandle.h"
#include "window/mainwindow/mainwindow.h"
#include "window/mainwindow/mainwindowundo.h"
@@ -73,7 +68,7 @@ void ImportTool::drag_enter(TimelineViewMouseEvent *event)
// Variables to deserialize into
quintptr item_ptr;
QVector<Track::Reference> enabled_streams;
QVector<TrackReference> enabled_streams;
// Set drag start position
drag_start_ = event->get_coordinates();
@@ -84,14 +79,16 @@ void ImportTool::drag_enter(TimelineViewMouseEvent *event)
stream >> enabled_streams >> item_ptr;
// Get Item object
Node *item = reinterpret_cast<Node *>(item_ptr);
OakEngineNode *item = reinterpret_cast<OakEngineNode *>(item_ptr);
// Check if Item is Footage
ViewerOutput *f = dynamic_cast<ViewerOutput *>(item);
if (f && f->get_total_stream_count()) {
// Check if Item is a viewer (Footage or Sequence) with streams
if (oakengine_node_is_viewer_output(item) &&
oakengine_viewer_get_video_stream_count(item) +
oakengine_viewer_get_audio_stream_count(item) +
oakengine_viewer_get_subtitle_stream_count(item) >
0) {
// If the Item is Footage, we can create a Ghost from it
dragged_footage_.append({ f, enabled_streams });
dragged_footage_.append({ item, enabled_streams });
}
}
@@ -207,15 +204,26 @@ void ImportTool::drag_drop(TimelineViewMouseEvent *event)
}
}
void ImportTool::place_at(const QVector<ViewerOutput *> &footage,
void ImportTool::place_at(const QVector<OakEngineNode *> &footage,
const Rational &start, bool insert,
void *command, int track_offset,
bool jump_to_end)
{
DraggedFootageData refs;
foreach (ViewerOutput *f, footage) {
refs.append({ f, f->get_enabled_streams_as_references() });
foreach (OakEngineNode *f, footage) {
// ViewerOutput::get_enabled_streams_as_references() via the oak::
// wrapper (C ABI): (track_type, index) pairs with ordinals matching
// TrackReference::Type (see common/trackreferencehandle.h)
QVector<TrackReference> enabled_streams;
const QVector<QPair<int, int>> streams =
oak::Node(f).enabled_streams();
enabled_streams.reserve(streams.size());
for (const QPair<int, int> &s : streams) {
enabled_streams.append(TrackReference(
static_cast<TrackReference::Type>(s.first), s.second));
}
refs.append({ f, enabled_streams });
}
place_at(refs, start, insert, command, track_offset, jump_to_end);
@@ -256,27 +264,39 @@ void ImportTool::footage_to_ghosts(Rational ghost_start,
const int &track_start)
{
for (auto it = sorted.cbegin(); it != sorted.cend(); it++) {
ViewerOutput *footage = it->first;
OakEngineNode *footage = it->first;
if (footage == sequence() ||
(sequence() && footage->inputs_from(sequence(), true))) {
if (footage ==
reinterpret_cast<OakEngineNode *>(sequence()) ||
(sequence() &&
oakengine_node_inputs_from(
footage,
reinterpret_cast<OakEngineNode *>(sequence()), 1))) {
// Prevent cyclical dependency
continue;
}
// Each stream is offset by one track per track "type", we keep track of them in this vector
QVector<int> track_offsets(Track::k_count);
QVector<int> track_offsets(TrackReference::k_count);
track_offsets.fill(track_start);
Rational footage_duration;
Rational ghost_in;
TimelineWorkArea *wk = footage->get_work_area();
if (wk->enabled()) {
footage_duration = wk->length();
ghost_in = wk->in();
oakengine_viewer_workarea wk;
oakengine_viewer_get_workarea(
footage, &wk);
if (wk.enabled) {
footage_duration =
Rational(int(wk.out_num), int(wk.out_den)) -
Rational(int(wk.in_num), int(wk.in_den));
ghost_in = Rational(int(wk.in_num), int(wk.in_den));
} else {
footage_duration = footage->get_length();
int64_t len_num = 0, len_den = 1;
oakengine_viewer_get_length(
footage, &len_num,
&len_den);
footage_duration = Rational(int(len_num), int(len_den));
if (footage_duration.isNull()) {
// Fallback to still length if legngth was 0
@@ -293,12 +313,13 @@ void ImportTool::footage_to_ghosts(Rational ghost_start,
}
// Create ghosts
foreach (const Track::Reference &ref, it->second) {
Track::Type track_type = ref.type();
Track::Reference dest_track(track_type,
track_offsets.at(track_type));
foreach (const TrackReference &ref, it->second) {
TrackReference::Type track_type = ref.type();
TrackReference dest_track(track_type,
track_offsets.at(track_type));
if (track_type == Track::k_video || track_type == Track::k_audio) {
if (track_type == TrackReference::k_video ||
track_type == TrackReference::k_audio) {
auto ghost = create_ghost(
TimeRange(ghost_start, ghost_start + footage_duration),
ghost_in, dest_track);
@@ -310,15 +331,17 @@ void ImportTool::footage_to_ghosts(Rational ghost_start,
ref.to_string() };
ghost->set_data(TimelineViewGhostItem::k_attached_footage,
QVariant::fromValue(af));
} else if (track_type == Track::k_subtitle) {
} else if (track_type == TrackReference::k_subtitle) {
int sub_count = oakengine_viewer_get_subtitle_count(
reinterpret_cast<const OakEngineNode *>(footage),
footage,
ref.index());
for (int si = 0; si < sub_count; si++) {
const Subtitle *sub = static_cast<const Subtitle *>(
// Points at an engine Subtitle; read through the
// layout-identical app mirror (common/subtitleapp.h)
const SubtitleApp *sub = static_cast<const SubtitleApp *>(
oakengine_viewer_get_subtitle_at(
reinterpret_cast<const OakEngineNode *>(footage),
footage,
ref.index(), si));
auto ghost =
create_ghost(sub->time() + ghost_start, 0, dest_track);
@@ -354,12 +377,13 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
oakengine_undo_command_multi_add_child(command, c);
}
Project *dst_graph = nullptr;
Sequence *sequence = this->sequence();
OakEngineProject *dst_graph = nullptr;
OakEngineSequence *sequence = this->sequence();
bool open_sequence = false;
if (sequence) {
dst_graph = sequence->parent();
dst_graph = oakengine_node_parent(
reinterpret_cast<OakEngineNode *>(sequence));
} else {
// There's no active timeline here, ask the user what to do
@@ -412,9 +436,9 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
OakEngineProject *active_project = Core::instance()->get_active_project();
if (active_project) {
Sequence *new_sequence = reinterpret_cast<Sequence *>(
OakEngineSequence *new_sequence =
Core::instance()->create_new_sequence_for_project(
active_project));
active_project);
oakengine_viewer_set_default_parameters(
reinterpret_cast<OakEngineNode *>(new_sequence));
@@ -423,7 +447,7 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
// Even if the user selected manual, set from footage anyway so the user has a useful
// starting point
QVector<ViewerOutput *> footage_only;
QVector<OakEngineNode *> footage_only;
for (auto it = dragged_footage_.cbegin();
it != dragged_footage_.cend(); it++) {
@@ -434,8 +458,7 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
QVector<OakEngineNode *> _footage_nodes;
for (auto *f : footage_only) {
_footage_nodes.append(
reinterpret_cast<OakEngineNode *>(f));
_footage_nodes.append(f);
}
oakengine_viewer_set_parameters_from_footage(
reinterpret_cast<OakEngineNode *>(new_sequence),
@@ -443,7 +466,7 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
// If the user selected manual, show them a dialog with parameters
if (behavior == k_dws_manual) {
SequenceDialog sd(new_sequence, SequenceDialog::k_new,
SequenceDialog sd(reinterpret_cast<OakEngineNode *>(new_sequence), SequenceDialog::k_new,
parent());
sd.set_undoable(false);
@@ -453,11 +476,11 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
}
if (sequence_is_valid) {
dst_graph = reinterpret_cast<Project *>(Core::instance()->get_active_project());
dst_graph = Core::instance()->get_active_project();
oakengine_undo_command_multi_add_child(command,
oakengine_node_add_to_project_command(
reinterpret_cast<OakEngineProject *>(dst_graph),
dst_graph,
reinterpret_cast<OakEngineNode *>(new_sequence)));
oakengine_folder_add_child(
reinterpret_cast<OakEngineNode *>(
@@ -465,7 +488,7 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
reinterpret_cast<OakEngineNode *>(new_sequence));
oakengine_undo_command_multi_add_child(command, oakengine_node_set_position_command(reinterpret_cast<void *>(new_sequence), reinterpret_cast<void *>(new_sequence), 0, 0, 0));
oakengine_sequence_add_default_nodes(
reinterpret_cast<OakEngineSequence *>(new_sequence));
new_sequence);
footage_to_ghosts(0, dragged_footage_,
viewer_output_video_params(new_sequence).time_base(),
@@ -483,16 +506,17 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
} else {
// If the sequence is valid, ownership is passed to AddItemCommand.
// Otherwise, we're responsible for deleting it.
delete new_sequence;
oakengine_node_free(
reinterpret_cast<OakEngineNode *>(new_sequence));
}
}
}
}
std::list<ClipBlock *> imported_clips;
std::list<OakEngineBlock *> imported_clips;
if (dst_graph) {
QVector<Block *> block_items(parent()->get_ghost_items().size());
QVector<OakEngineBlock *> block_items(parent()->get_ghost_items().size());
// Check if we're inserting (only valid if we're not creating this sequence ourselves)
if (insert && !open_sequence) {
@@ -501,15 +525,16 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
for (int i = 0; i < parent()->get_ghost_items().size(); i++) {
TimelineViewGhostItem *ghost = parent()->get_ghost_items().at(i);
Block *block = nullptr;
OakEngineBlock *block = nullptr;
Track::Type track_type = ghost->get_adjusted_track().type();
if (track_type == Track::k_video || track_type == Track::k_audio) {
TrackReference::Type track_type = ghost->get_adjusted_track().type();
if (track_type == TrackReference::k_video ||
track_type == TrackReference::k_audio) {
TimelineViewGhostItem::AttachedFootage footage_stream =
ghost->get_data(TimelineViewGhostItem::k_attached_footage)
.value<TimelineViewGhostItem::AttachedFootage>();
ClipBlock *clip = clip_create_empty();
OakEngineBlock *clip = clip_create_empty();
block = clip;
clip_set_media_in(clip, ghost->get_media_in());
oakengine_undo_command_multi_add_child(command,
@@ -528,10 +553,10 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
dep_pos++;
switch (
Track::Reference::type_from_string(footage_stream.output)) {
case Track::k_video: {
TransformDistortNode *transform =
reinterpret_cast<TransformDistortNode*>(oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.transformdistort"));
TrackReference::type_from_string(footage_stream.output)) {
case TrackReference::k_video: {
OakEngineNode *transform =
oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.transformdistort");
oakengine_undo_command_multi_add_child(command,
oakengine_node_add_to_project_command(
reinterpret_cast<OakEngineProject *>(dst_graph),
@@ -548,7 +573,7 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
oakengine_undo_command_multi_add_child(
command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(footage_stream.footage),
footage_stream.footage,
reinterpret_cast<OakEngineNode *>(transform),
QLatin1String(oakengine_transform_texture_input_id()).toUtf8().constData(),
-1));
@@ -562,8 +587,8 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
oakengine_undo_command_multi_add_child(command, oakengine_node_set_position_command(reinterpret_cast<void *>(transform), reinterpret_cast<void *>(clip), dep_pos, 0, 0));
break;
}
case Track::k_audio: {
VolumeNode *volume_node = reinterpret_cast<VolumeNode*>(oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.volume"));
case TrackReference::k_audio: {
OakEngineNode *volume_node = oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.volume");
oakengine_undo_command_multi_add_child(command,
oakengine_node_add_to_project_command(
reinterpret_cast<OakEngineProject *>(dst_graph),
@@ -580,7 +605,7 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
oakengine_undo_command_multi_add_child(
command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(footage_stream.footage),
footage_stream.footage,
reinterpret_cast<OakEngineNode *>(volume_node),
QLatin1String(oakengine_volume_samples_input_id()).toUtf8().constData(),
-1));
@@ -615,11 +640,11 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
}
imported_clips.push_back(clip);
} else if (track_type == Track::k_subtitle) {
Subtitle src =
} else if (track_type == TrackReference::k_subtitle) {
SubtitleApp src =
ghost->get_data(TimelineViewGhostItem::k_attached_footage)
.value<Subtitle>();
SubtitleBlock *sub = reinterpret_cast<SubtitleBlock *>(
.value<SubtitleApp>();
OakEngineBlock *sub = reinterpret_cast<OakEngineBlock *>(
oakengine_node_factory_create_from_id(
"org.olivevideoeditor.Olive.subtitle"));
oakengine_subtitle_set_text(
@@ -634,16 +659,32 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
oakengine_undo_command_multi_add_child(command, oakengine_node_set_position_command(reinterpret_cast<void *>(sub), reinterpret_cast<void *>(sub), 0, 0, 0));
}
block->set_length_and_media_out(ghost->get_length());
// Set the block's length before placement through a trim command
// child (children redo in order, so the length is set before
// the block is placed): oakengine_block_set_length_and_media_out()
// itself requires the block to already be on a track
// (OAKENGINE_E_STATE), and pre-placement there are no adjacent
// blocks, so a trim-out command reduces to
// Block::set_length_and_media_out().
oakengine_undo_command_multi_add_child(command,
oakengine_block_trim_command(
reinterpret_cast<void *>(oakengine_sequence_track_at(
sequence,
ghost->get_adjusted_track().type(),
ghost->get_adjusted_track().index())),
reinterpret_cast<void *>(block),
ghost->get_length().numerator(),
ghost->get_length().denominator(),
OAKENGINE_MOVEMENT_MODE_TRIM_OUT, 0));
oakengine_undo_command_multi_add_child(command, oakengine_track_place_block_command(reinterpret_cast<void *>(sequence->track_list(ghost->get_adjusted_track().type())), ghost->get_adjusted_track().index(), reinterpret_cast<void *>(block), core::Timecode::time_to_timestamp(ghost->get_adjusted_in(), parent()->timebase())));
oakengine_undo_command_multi_add_child(command, oakengine_track_place_block_command(reinterpret_cast<void *>(oakengine_sequence_track_list(sequence, static_cast<int>(ghost->get_adjusted_track().type()))), ghost->get_adjusted_track().index(), reinterpret_cast<void *>(block), core::Timecode::time_to_timestamp(ghost->get_adjusted_in(), parent()->timebase())));
block_items.replace(i, block);
}
}
if (open_sequence) {
oakengine_undo_command_multi_add_child(command, make_open_sequence_command(sequence));
oakengine_undo_command_multi_add_child(command, make_open_sequence_command(reinterpret_cast<OakEngineNode *>(sequence)));
}
// Do command now because RequestInvalidatedFromConnected relies on track type, which will be
@@ -662,7 +703,7 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
TimelineViewGhostItem *ImportTool::create_ghost(const TimeRange &range,
const Rational &media_in,
const Track::Reference &track)
const TrackReference &track)
{
TimelineViewGhostItem *ghost = new TimelineViewGhostItem();
@@ -674,7 +715,7 @@ TimelineViewGhostItem *ImportTool::create_ghost(const TimeRange &range,
snap_points_.push_back(ghost->get_in());
snap_points_.push_back(ghost->get_out());
ghost->set_mode(Timeline::k_move);
ghost->set_mode(TimelineApp::k_move);
parent()->add_ghost(ghost);
+3 -3
View File
@@ -38,9 +38,9 @@ public:
virtual void drag_drop(TimelineViewMouseEvent *event) override;
using DraggedFootageData =
QVector<QPair<ViewerOutput *, QVector<Track::Reference>>>;
QVector<QPair<OakEngineNode *, QVector<TrackReference>>>;
void place_at(const QVector<ViewerOutput *> &footage, const Rational &start,
void place_at(const QVector<OakEngineNode *> &footage, const Rational &start,
bool insert, void *command, int track_offset = 0,
bool jump_to_end = false);
void place_at(const DraggedFootageData &footage, const Rational &start,
@@ -65,7 +65,7 @@ private:
TimelineViewGhostItem *create_ghost(const TimeRange &range,
const Rational &media_in,
const Track::Reference &track);
const TrackReference &track);
DraggedFootageData dragged_footage_;
+292 -207
View File
@@ -28,18 +28,72 @@
#include "oakutil/range.h"
#include "common/configwrapper.h"
#include "core.h"
#include "node/block/gap/gap.h"
#include "node/block/transition/transition.h"
#include "oakengine/node.h"
#include "oakengine/undo.h"
#include "oakengine/timeline.h"
#include "pointer.h"
#include "timeline/timelineundopointer.h"
#include "widget/timeruler/timeruler.h"
#include "widget/timelinewidget/trackhandle.h"
namespace olive
{
namespace
{
/// Block::in() as rational seconds.
Rational block_in_rational(const OakEngineBlock *block)
{
int num = 0, den = 1;
oakengine_block_get_in_rational(
reinterpret_cast<const OakEngineNode *>(block), &num, &den);
return Rational(num, den);
}
/// Block::out() as rational seconds.
Rational block_out_rational(const OakEngineBlock *block)
{
int num = 0, den = 1;
oakengine_block_get_out_rational(
reinterpret_cast<const OakEngineNode *>(block), &num, &den);
return Rational(num, den);
}
/**
* @brief ClipBlock::block_links() through the engine C ABI
* (oakengine_block_link_count/at: Node::links() filtered to blocks, same
* content and ordering for a ClipBlock).
*/
QVector<OakEngineBlock *> block_links_of(OakEngineBlock *block)
{
QVector<OakEngineBlock *> links;
const int n = oakengine_block_link_count(block);
links.reserve(n);
for (int i = 0; i < n; i++) {
links.append(oakengine_block_link_at(block, i));
}
return links;
}
/**
* @brief Track::to_reference() facade: the app TrackReference mirror of an
* engine Track, through the C ABI (same pattern as
* ghost_block_track_reference()). Type ordinals are pinned to the engine
* Track::Type ordinals by the static_asserts in trackreferencehandle.h.
*/
TrackReference track_reference_of(OakEngineTrack *track)
{
if (!track) {
return TrackReference();
}
auto *h = reinterpret_cast<OakEngineNode *>(track);
return TrackReference(
static_cast<TrackReference::Type>(oakengine_track_get_type(h)),
oakengine_track_get_index(h));
}
} // namespace
PointerTool::PointerTool(TimelineWidget *parent)
: TimelineTool(parent)
, movement_allowed_(true)
@@ -53,17 +107,20 @@ PointerTool::PointerTool(TimelineWidget *parent)
void PointerTool::mouse_press(TimelineViewMouseEvent *event)
{
const Track::Reference &track_ref = event->get_track();
const TrackReference &track_ref = event->get_track();
// Determine if item clicked on is selectable
clicked_item_ = parent()->get_item_at_scene_pos(event->get_coordinates());
ClipBlock *clip_clicked_item = dynamic_cast<ClipBlock *>(clicked_item_);
OakEngineClip *clip_clicked_item =
oakengine_node_is_clip(reinterpret_cast<OakEngineNode *>(clicked_item_)) ?
reinterpret_cast<OakEngineClip *>(clicked_item_) :
nullptr;
can_rubberband_select_ = false;
bool selectable_item =
(clicked_item_ &&
!parent()->get_track_from_reference(track_ref)->is_locked());
!track_is_locked(parent()->get_track_from_reference(track_ref)));
if (selectable_item) {
// Cache the clip's type for use later
@@ -79,15 +136,16 @@ void PointerTool::mouse_press(TimelineViewMouseEvent *event)
// If we're not in a trim mode, we must be in a move mode (provided the tool allows movement and
// the block is not a gap)
if (drag_movement_mode_ == Timeline::k_none && movement_allowed_ &&
!dynamic_cast<GapBlock *>(clicked_item_)) {
drag_movement_mode_ = Timeline::k_move;
if (drag_movement_mode_ == TimelineApp::k_none && movement_allowed_ &&
!oakengine_block_is_gap(
reinterpret_cast<OakEngineBlock *>(clicked_item_))) {
drag_movement_mode_ = TimelineApp::k_move;
}
// If this item is already selected, no further selection needs to be made
if (parent()->is_block_selected(clicked_item_)) {
// Collect item deselections
QVector<Block *> deselected_blocks;
QVector<OakEngineBlock *> deselected_blocks;
// If shift is held, deselect it
if (event->get_modifiers() & Qt::ShiftModifier) {
@@ -98,7 +156,7 @@ void PointerTool::mouse_press(TimelineViewMouseEvent *event)
if (clip_clicked_item &&
!(event->get_modifiers() & Qt::AltModifier)) {
parent()->set_block_links_selected(clip_clicked_item, false);
deselected_blocks.append(clip_clicked_item->block_links());
deselected_blocks.append(block_links_of(reinterpret_cast<OakEngineBlock *>(clip_clicked_item)));
}
}
@@ -115,7 +173,7 @@ void PointerTool::mouse_press(TimelineViewMouseEvent *event)
if (selectable_item) {
// Collect item selections
QVector<Block *> selected_blocks;
QVector<OakEngineBlock *> selected_blocks;
// Select this item
parent()->add_selection(clicked_item_);
@@ -124,7 +182,7 @@ void PointerTool::mouse_press(TimelineViewMouseEvent *event)
// If not holding alt, select all links as well
if (clip_clicked_item && !(event->get_modifiers() & Qt::AltModifier)) {
parent()->set_block_links_selected(clip_clicked_item, true);
selected_blocks.append(clip_clicked_item->block_links());
selected_blocks.append(block_links_of(reinterpret_cast<OakEngineBlock *>(clip_clicked_item)));
}
parent()->signal_selected_blocks(selected_blocks);
@@ -136,7 +194,7 @@ void PointerTool::mouse_press(TimelineViewMouseEvent *event)
&&
(!selectable_item ||
drag_movement_mode_ ==
Timeline::
TimelineApp::
k_none)); // And if no item was selected OR the item isn't draggable
if (can_rubberband_select_) {
@@ -176,7 +234,7 @@ void PointerTool::mouse_move(TimelineViewMouseEvent *event)
snap_points_.clear();
// If we're performing an action, we can initiate ghosts
if (drag_movement_mode_ != Timeline::k_none) {
if (drag_movement_mode_ != TimelineApp::k_none) {
initiate_drag(clicked_item_, drag_movement_mode_,
event->get_modifiers());
}
@@ -219,15 +277,15 @@ void PointerTool::hover_move(TimelineViewMouseEvent *event)
{
if (trimming_allowed_) {
// No dragging, but we still want to process cursors
Block *block_at_cursor =
OakEngineBlock *block_at_cursor =
parent()->get_item_at_scene_pos(event->get_coordinates());
if (block_at_cursor) {
switch (is_cursor_in_trim_handle(block_at_cursor, event->get_scene_x())) {
case Timeline::k_trim_in:
case TimelineApp::k_trim_in:
parent()->setCursor(Qt::SizeHorCursor);
break;
case Timeline::k_trim_out:
case TimelineApp::k_trim_out:
parent()->setCursor(Qt::SizeHorCursor);
break;
default:
@@ -247,20 +305,20 @@ void set_ghost_to_slide_mode(TimelineViewGhostItem *g)
g->set_data(TimelineViewGhostItem::k_ghost_is_sliding, true);
}
void PointerTool::initiate_drag_internal(Block *clicked_item,
Timeline::MovementMode trim_mode,
void PointerTool::initiate_drag_internal(OakEngineBlock *clicked_item,
TimelineApp::MovementMode trim_mode,
Qt::KeyboardModifiers modifiers,
bool dont_roll_trims,
bool allow_nongap_rolling,
bool slide_instead_of_moving)
{
// Get list of selected blocks
QVector<Block *> clips = parent()->get_selected_blocks();
QVector<OakEngineBlock *> clips = parent()->get_selected_blocks();
if (trim_mode == Timeline::k_move) {
if (trim_mode == TimelineApp::k_move) {
// Gaps are not allowed to move, and since we only allow moving one block type at a time,
// dragging a gap is a no-op
if (dynamic_cast<GapBlock *>(clicked_item)) {
if (oakengine_block_is_gap(clicked_item)) {
return;
}
@@ -269,18 +327,23 @@ void PointerTool::initiate_drag_internal(Block *clicked_item,
if (!slide_instead_of_moving) {
// If the user tries to move a transition without moving the clip it belongs to, we turn
// this into a slide
foreach (Block *block, clips) {
if (TransitionBlock *transit =
dynamic_cast<TransitionBlock *>(block)) {
if (!can_transition_move(transit, clips)) {
foreach (OakEngineBlock *block, clips) {
if (oakengine_node_is_transition(
reinterpret_cast<OakEngineNode *>(block))) {
if (!can_transition_move(block, clips)) {
slide_instead_of_moving = true;
break;
}
} else if (ClipBlock *clip = dynamic_cast<ClipBlock *>(block)) {
if ((clip->in_transition() &&
!can_transition_move(clip->in_transition(), clips)) ||
(clip->out_transition() &&
!can_transition_move(clip->out_transition(), clips))) {
} else if (oakengine_node_is_clip(
reinterpret_cast<OakEngineNode *>(block))) {
OakEngineBlock *in_transit =
oakengine_clip_in_transition(block);
OakEngineBlock *out_transit =
oakengine_clip_out_transition(block);
if ((in_transit &&
!can_transition_move(in_transit, clips)) ||
(out_transit &&
!can_transition_move(out_transit, clips))) {
slide_instead_of_moving = true;
break;
}
@@ -298,117 +361,129 @@ void PointerTool::initiate_drag_internal(Block *clicked_item,
// For slides to be legal, we make all blocks "contiguous". This means that only one series
// of blocks can move at a time and prevents.
QHash<Track *, Block *> earliest_block_on_track;
QHash<Track *, Block *> latest_block_on_track;
QHash<OakEngineTrack *, OakEngineBlock *> earliest_block_on_track;
QHash<OakEngineTrack *, OakEngineBlock *> latest_block_on_track;
foreach (Block *this_block, clips) {
Block *current_earliest =
earliest_block_on_track.value(this_block->track(), nullptr);
foreach (OakEngineBlock *this_block, clips) {
OakEngineTrack *this_track =
oakengine_block_get_track(this_block);
OakEngineBlock *current_earliest =
earliest_block_on_track.value(this_track, nullptr);
if (!current_earliest ||
this_block->in() < current_earliest->in()) {
earliest_block_on_track.insert(this_block->track(),
this_block);
block_in_rational(this_block) <
block_in_rational(current_earliest)) {
earliest_block_on_track.insert(this_track, this_block);
}
Block *current_latest =
latest_block_on_track.value(this_block->track(), nullptr);
OakEngineBlock *current_latest =
latest_block_on_track.value(this_track, nullptr);
if (!current_latest ||
this_block->out() > current_earliest->out()) {
latest_block_on_track.insert(this_block->track(),
this_block);
block_out_rational(this_block) >
block_out_rational(current_earliest)) {
latest_block_on_track.insert(this_track, this_block);
}
}
for (auto i = earliest_block_on_track.constBegin();
i != earliest_block_on_track.constEnd(); i++) {
// Make a contiguous stream
Track *track = i.key();
Block *earliest = i.value();
Block *latest = latest_block_on_track.value(i.key());
OakEngineTrack *track = i.key();
OakEngineBlock *earliest = i.value();
OakEngineBlock *latest = latest_block_on_track.value(i.key());
OakEngineBlock *earliest_previous =
oakengine_block_prev(earliest);
OakEngineBlock *latest_next = oakengine_block_next(latest);
// First we add the block that's out trimming, the one prior to the earliest
{
TimelineViewGhostItem *earliest_ghost;
bool slide_with_earliest_previous = true;
if (sliding_due_to_transition && earliest->previous()) {
if (TransitionBlock *transit =
dynamic_cast<TransitionBlock *>(earliest)) {
if (earliest->previous() !=
transit->connected_out_block()) {
if (sliding_due_to_transition && earliest_previous) {
if (oakengine_node_is_transition(
reinterpret_cast<OakEngineNode *>(earliest))) {
if (earliest_previous !=
oakengine_transition_connected_out_block(
earliest)) {
slide_with_earliest_previous = false;
}
} else if (ClipBlock *clip =
dynamic_cast<ClipBlock *>(earliest)) {
if (earliest->previous() != clip->in_transition()) {
} else if (oakengine_node_is_clip(
reinterpret_cast<OakEngineNode *>(
earliest))) {
if (earliest_previous !=
oakengine_clip_in_transition(earliest)) {
slide_with_earliest_previous = false;
}
}
}
if (earliest->previous() && slide_with_earliest_previous) {
earliest_ghost = add_ghost_from_block(earliest->previous(),
Timeline::k_trim_out);
if (earliest_previous && slide_with_earliest_previous) {
earliest_ghost = add_ghost_from_block(earliest_previous,
TimelineApp::k_trim_out);
} else {
earliest_ghost = add_ghost_from_null(earliest->in(),
earliest->in(),
track->to_reference(),
Timeline::k_trim_out);
earliest_ghost = add_ghost_from_null(block_in_rational(earliest),
block_in_rational(earliest),
track_reference_of(track),
TimelineApp::k_trim_out);
}
set_ghost_to_slide_mode(earliest_ghost);
}
// Then we add the block that's in trimming, the one after the latest
if (latest->next()) {
if (latest_next) {
TimelineViewGhostItem *latest_ghost;
bool slide_with_latest_next = true;
if (sliding_due_to_transition) {
if (TransitionBlock *transit =
dynamic_cast<TransitionBlock *>(latest)) {
if (latest->next() !=
transit->connected_in_block()) {
if (oakengine_node_is_transition(
reinterpret_cast<OakEngineNode *>(latest))) {
if (latest_next !=
oakengine_transition_connected_in_block(
latest)) {
slide_with_latest_next = false;
}
} else if (ClipBlock *clip =
dynamic_cast<ClipBlock *>(latest)) {
if (latest->next() != clip->out_transition()) {
} else if (oakengine_node_is_clip(
reinterpret_cast<OakEngineNode *>(
latest))) {
if (latest_next !=
oakengine_clip_out_transition(latest)) {
slide_with_latest_next = false;
}
}
}
if (slide_with_latest_next) {
latest_ghost = add_ghost_from_block(latest->next(),
Timeline::k_trim_in);
latest_ghost = add_ghost_from_block(latest_next,
TimelineApp::k_trim_in);
} else {
latest_ghost = add_ghost_from_null(latest->out(),
latest->out(),
track->to_reference(),
Timeline::k_trim_in);
latest_ghost = add_ghost_from_null(block_out_rational(latest),
block_out_rational(latest),
track_reference_of(track),
TimelineApp::k_trim_in);
}
set_ghost_to_slide_mode(latest_ghost);
}
// Finally, we add all of the moving blocks in between
Block *b = nullptr;
OakEngineBlock *b = nullptr;
do {
// On first run-through, set to earliest only. From then on, set to the next of the last
// in the loop.
if (b) {
b = b->next();
b = oakengine_block_next(b);
} else {
b = earliest;
}
TimelineViewGhostItem *between_ghost =
add_ghost_from_block(b, Timeline::k_move);
add_ghost_from_block(b, TimelineApp::k_move);
set_ghost_to_slide_mode(between_ghost);
} while (b != latest);
}
} else {
// Prepare for a standard pointer move by creating ghosts for them and any related blocks
foreach (Block *block, clips) {
if (dynamic_cast<GapBlock *>(block)) {
foreach (OakEngineBlock *block, clips) {
if (oakengine_block_is_gap(block)) {
continue;
}
@@ -416,14 +491,15 @@ void PointerTool::initiate_drag_internal(Block *clicked_item,
auto ghost = add_ghost_from_block(block, trim_mode, true);
Q_UNUSED(ghost)
if (ClipBlock *clip = dynamic_cast<ClipBlock *>(block)) {
if (clip->out_transition()) {
add_ghost_from_block(clip->out_transition(), trim_mode,
true);
if (oakengine_node_is_clip(
reinterpret_cast<OakEngineNode *>(block))) {
if (OakEngineBlock *out_transit =
oakengine_clip_out_transition(block)) {
add_ghost_from_block(out_transit, trim_mode, true);
}
if (clip->in_transition()) {
add_ghost_from_block(clip->in_transition(), trim_mode,
true);
if (OakEngineBlock *in_transit =
oakengine_clip_in_transition(block)) {
add_ghost_from_block(in_transit, trim_mode, true);
}
}
}
@@ -437,7 +513,7 @@ void PointerTool::initiate_drag_internal(Block *clicked_item,
is_clip_trimmable(clicked_item, clips, trim_mode);
// Create ghosts for trimming
for (Block *clip_item : clips) {
for (OakEngineBlock *clip_item : clips) {
if (clip_item != clicked_item &&
(!multitrim_enabled ||
!is_clip_trimmable(clip_item, clips, trim_mode))) {
@@ -446,7 +522,7 @@ void PointerTool::initiate_drag_internal(Block *clicked_item,
continue;
}
Block *block = clip_item;
OakEngineBlock *block = clip_item;
// Create ghost for this block
TimelineViewGhostItem *ghost = add_ghost_from_block(block, trim_mode);
@@ -455,22 +531,21 @@ void PointerTool::initiate_drag_internal(Block *clicked_item,
// transition than a trim/roll
bool treat_trim_as_slide = false;
ClipBlock *cb = dynamic_cast<ClipBlock *>(block);
if (cb) {
if (oakengine_node_is_clip(reinterpret_cast<OakEngineNode *>(block))) {
// See if this clip has a transition attached, and move it with the trim if so
TransitionBlock *connected_transition;
OakEngineBlock *connected_transition;
// Get appropriate transition for the side of the clip
if (trim_mode == Timeline::k_trim_in) {
connected_transition = cb->in_transition();
if (trim_mode == TimelineApp::k_trim_in) {
connected_transition = oakengine_clip_in_transition(block);
} else {
connected_transition = cb->out_transition();
connected_transition = oakengine_clip_out_transition(block);
}
if (connected_transition) {
// We found a transition, we'll make this a "slide" action
TimelineViewGhostItem *transition_ghost = add_ghost_from_block(
connected_transition, Timeline::k_move);
connected_transition, TimelineApp::k_move);
// This will in effect be a slide with the transition moving between two other blocks
set_ghost_to_slide_mode(ghost);
@@ -485,29 +560,32 @@ void PointerTool::initiate_drag_internal(Block *clicked_item,
// Standard pointer trimming in reality is a "roll" edit with an adjacent gap (one that may
// or may not exist already)
if (!dont_roll_trims) {
Block *adjacent = nullptr;
OakEngineBlock *adjacent = nullptr;
// Determine which block is adjacent
if (trim_mode == Timeline::k_trim_in) {
adjacent = block->previous();
if (trim_mode == TimelineApp::k_trim_in) {
adjacent = oakengine_block_prev(block);
} else {
adjacent = block->next();
adjacent = oakengine_block_next(block);
}
// See if we can roll the adjacent or if we'll need to create our own gap
if (!dynamic_cast<GapBlock *>(block) && !allow_nongap_rolling &&
adjacent && !dynamic_cast<GapBlock *>(adjacent) &&
!(dynamic_cast<TransitionBlock *>(block) &&
((trim_mode == Timeline::k_trim_in &&
static_cast<TransitionBlock *>(block)
->connected_out_block() == adjacent) ||
(trim_mode == Timeline::k_trim_out &&
static_cast<TransitionBlock *>(block)
->connected_in_block() == adjacent)))) {
bool block_is_transition = oakengine_node_is_transition(
reinterpret_cast<OakEngineNode *>(block));
if (!oakengine_block_is_gap(block) &&
!allow_nongap_rolling && adjacent &&
!oakengine_block_is_gap(adjacent) &&
!(block_is_transition &&
((trim_mode == TimelineApp::k_trim_in &&
oakengine_transition_connected_out_block(block) ==
adjacent) ||
(trim_mode == TimelineApp::k_trim_out &&
oakengine_transition_connected_in_block(block) ==
adjacent)))) {
adjacent = nullptr;
}
Timeline::MovementMode flipped_mode = flip_trim_mode(trim_mode);
TimelineApp::MovementMode flipped_mode = flip_trim_mode(trim_mode);
QVector<TimelineViewGhostItem *> adjacent_ghosts;
if (adjacent) {
@@ -518,23 +596,26 @@ void PointerTool::initiate_drag_internal(Block *clicked_item,
// FIXME: The check for `clips.size() == 1` may not be necessary, but I don't know yet.
// I'm only including it to prevent any potentially unintended behavior.
if (clips.size() == 1 && !(modifiers & Qt::AltModifier)) {
if (ClipBlock *adjacent_clip =
dynamic_cast<ClipBlock *>(adjacent)) {
for (Block *adjacent_link :
adjacent_clip->block_links()) {
if (oakengine_node_is_clip(
reinterpret_cast<OakEngineNode *>(adjacent))) {
for (OakEngineBlock *adjacent_link :
block_links_of(adjacent)) {
adjacent_ghosts.append(add_ghost_from_block(
adjacent_link, flipped_mode));
}
}
}
} else if (trim_mode == Timeline::k_trim_in || block->next()) {
Rational null_ghost_pos = (trim_mode == Timeline::k_trim_in) ?
block->in() :
block->out();
} else if (trim_mode == TimelineApp::k_trim_in ||
oakengine_block_next(block)) {
Rational null_ghost_pos = (trim_mode == TimelineApp::k_trim_in) ?
block_in_rational(block) :
block_out_rational(block);
adjacent_ghosts.append(add_ghost_from_null(
null_ghost_pos, null_ghost_pos,
clip_item->track()->to_reference(), flipped_mode));
track_reference_of(
oakengine_block_get_track(clip_item)),
flipped_mode));
}
// If we have an adjacent block (for any reason), this is a roll edit and the adjacent is
@@ -547,7 +628,7 @@ void PointerTool::initiate_drag_internal(Block *clicked_item,
if (treat_trim_as_slide) {
// We're sliding a transition rather than a pure trim/roll
set_ghost_to_slide_mode(adjacent_ghost);
} else if (dynamic_cast<GapBlock *>(block)) {
} else if (oakengine_block_is_gap(block)) {
ghost->set_data(
TimelineViewGhostItem::k_trim_should_be_ignored,
true);
@@ -563,11 +644,11 @@ void PointerTool::initiate_drag_internal(Block *clicked_item,
}
}
bool PointerTool::can_transition_move(TransitionBlock *transit,
const QVector<Block *> &clips)
bool PointerTool::can_transition_move(OakEngineBlock *transit,
const QVector<OakEngineBlock *> &clips)
{
Block *out = transit->connected_out_block();
Block *in = transit->connected_in_block();
OakEngineBlock *out = oakengine_transition_connected_out_block(transit);
OakEngineBlock *in = oakengine_transition_connected_in_block(transit);
if ((out && !clips.contains(out)) || (in && !clips.contains(in))) {
return false;
@@ -619,16 +700,16 @@ void PointerTool::process_drag(const TimelineCoordinate &mouse_pos)
// Perform movement
foreach (TimelineViewGhostItem *ghost, parent()->get_ghost_items()) {
switch (ghost->get_mode()) {
case Timeline::k_none:
case TimelineApp::k_none:
break;
case Timeline::k_trim_in:
case TimelineApp::k_trim_in:
ghost->set_in_adjustment(time_movement);
ghost->set_media_in_adjustment(time_movement);
break;
case Timeline::k_trim_out:
case TimelineApp::k_trim_out:
ghost->set_out_adjustment(time_movement);
break;
case Timeline::k_move: {
case TimelineApp::k_move: {
ghost->set_in_adjustment(time_movement);
ghost->set_out_adjustment(time_movement);
@@ -656,7 +737,7 @@ void PointerTool::process_drag(const TimelineCoordinate &mouse_pos)
struct GhostBlockPair {
TimelineViewGhostItem *ghost;
Block *block;
OakEngineBlock *block;
};
void PointerTool::finish_drag(TimelineViewMouseEvent *event)
@@ -668,14 +749,14 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
// Sort ghosts depending on which ones are trimming, which are moving, and which are sliding
foreach (TimelineViewGhostItem *ghost, parent()->get_ghost_items()) {
if (ghost->has_been_adjusted()) {
Block *b = QtUtils::value_to_ptr<Block>(
OakEngineBlock *b = QtUtils::value_to_ptr<OakEngineBlock>(
ghost->get_data(TimelineViewGhostItem::k_attached_block));
if (ghost->get_data(TimelineViewGhostItem::k_ghost_is_sliding).toBool()) {
blocks_sliding.append({ ghost, b });
} else if (ghost->get_mode() == Timeline::k_move) {
} else if (ghost->get_mode() == TimelineApp::k_move) {
blocks_moving.append({ ghost, b });
} else if (Timeline::is_a_trim_mode(ghost->get_mode())) {
} else if (TimelineApp::is_a_trim_mode(ghost->get_mode())) {
blocks_trimming.append({ ghost, b });
}
}
@@ -700,7 +781,8 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
command,
oakengine_block_trim_command(
reinterpret_cast<void *>(
parent()->get_track_from_reference(ghost->get_adjusted_track())),
parent()->get_track_from_reference(
ghost->get_adjusted_track())),
reinterpret_cast<void *>(p.block),
ghost->get_adjusted_length().numerator(),
ghost->get_adjusted_length().denominator(),
@@ -717,7 +799,7 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
TimelineWidgetSelections new_sel = parent()->get_selections();
TimelineViewGhostItem *reference_ghost =
blocks_trimming.first().ghost;
if (reference_ghost->get_mode() == Timeline::k_trim_in) {
if (reference_ghost->get_mode() == TimelineApp::k_trim_in) {
new_sel.trim_in(reference_ghost->get_in_adjustment());
} else {
new_sel.trim_out(reference_ghost->get_out_adjustment());
@@ -733,7 +815,7 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
// If we're not duplicating, "remove" the clips and replace them with gaps
if (!duplicate_clips) {
QVector<Block *> blocks_to_delete(blocks_moving.size());
QVector<OakEngineBlock *> blocks_to_delete(blocks_moving.size());
for (int i = 0; i < blocks_moving.size(); i++) {
blocks_to_delete[i] = blocks_moving.at(i).block;
@@ -748,39 +830,39 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
insert_gaps_at_ghost_destination(command);
}
QMap<Node *, Node *> relinks;
QMap<OakEngineBlock *, OakEngineBlock *> relinks;
// Now we can re-add each clip
foreach (const GhostBlockPair &p, blocks_moving) {
Block *block = p.block;
OakEngineBlock *block = p.block;
if (duplicate_clips) {
// Duplicate rather than move
// Place the copy instead of the original block
Block *new_block =
reinterpret_cast<Block *>(oakengine_node_copy_in_graph(
OakEngineBlock *new_block =
reinterpret_cast<OakEngineBlock *>(oakengine_node_copy_in_graph(
reinterpret_cast<OakEngineNode*>(block), command));
relinks.insert(block, new_block);
block = new_block;
if (ClipBlock *new_clip = dynamic_cast<ClipBlock *>(block)) {
if (oakengine_node_is_clip(
reinterpret_cast<OakEngineNode *>(block))) {
oakengine_clip_add_cache_passthrough(
reinterpret_cast<OakEngineClip *>(new_clip),
reinterpret_cast<OakEngineClip *>(block),
reinterpret_cast<OakEngineClip *>(p.block));
}
}
const Track::Reference &track_ref = p.ghost->get_adjusted_track();
oakengine_undo_command_multi_add_child(command, oakengine_track_place_block_command(reinterpret_cast<void *>(sequence()->track_list(track_ref.type())), track_ref.index(), reinterpret_cast<void *>(block), core::Timecode::time_to_timestamp(p.ghost->get_adjusted_in(), parent()->timebase())));
const TrackReference track_ref = p.ghost->get_adjusted_track();
oakengine_undo_command_multi_add_child(command, oakengine_track_place_block_command(reinterpret_cast<void *>(oakengine_sequence_track_list(sequence(), track_ref.type())), track_ref.index(), reinterpret_cast<void *>(block), core::Timecode::time_to_timestamp(p.ghost->get_adjusted_in(), parent()->timebase())));
}
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);
// Re-connect links on duplicate clips (block links, same
// content as Node::links() for a ClipBlock)
for (OakEngineBlock *link : block_links_of(it.key())) {
OakEngineBlock *copy_link = relinks.value(link);
if (copy_link) {
oakengine_undo_command_multi_add_child(command, (void *)(oakengine_node_link_command(
reinterpret_cast<OakEngineNode*>(it.value()),
@@ -789,23 +871,21 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
}
// Re-connect transitions where applicable
if (ClipBlock *og_clip = dynamic_cast<ClipBlock *>(it.key())) {
ClipBlock *cp_clip = static_cast<ClipBlock *>(it.value());
TransitionBlock *og_in_transition =
og_clip->in_transition();
TransitionBlock *og_out_transition =
og_clip->out_transition();
if (oakengine_node_is_clip(
reinterpret_cast<OakEngineNode *>(it.key()))) {
OakEngineBlock *og_in_transition =
oakengine_clip_in_transition(it.key());
OakEngineBlock *og_out_transition =
oakengine_clip_out_transition(it.key());
if (og_in_transition &&
relinks.contains(og_in_transition)) {
TransitionBlock *cp_in_transition =
static_cast<TransitionBlock *>(
relinks.value(og_in_transition));
OakEngineBlock *cp_in_transition =
relinks.value(og_in_transition);
oakengine_undo_command_multi_add_child(
command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(cp_clip),
reinterpret_cast<OakEngineNode *>(it.value()),
reinterpret_cast<OakEngineNode *>(cp_in_transition),
QLatin1String(oakengine_transition_in_block_input_id()).toUtf8().constData(),
-1));
@@ -813,13 +893,12 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
if (og_out_transition &&
relinks.contains(og_out_transition)) {
TransitionBlock *cp_out_transition =
static_cast<TransitionBlock *>(
relinks.value(og_out_transition));
OakEngineBlock *cp_out_transition =
relinks.value(og_out_transition);
oakengine_undo_command_multi_add_child(
command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(cp_clip),
reinterpret_cast<OakEngineNode *>(it.value()),
reinterpret_cast<OakEngineNode *>(cp_out_transition),
QLatin1String(oakengine_transition_out_block_input_id()).toUtf8().constData(),
-1));
@@ -840,26 +919,27 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
// Assume that the blocks are contiguous per track as set up in InitiateGhostsInternal()
// All we need to do is sort them by track and order them
QHash<Track::Reference, QList<Block *>> slide_info;
QHash<Track::Reference, Block *> in_adjacents;
QHash<Track::Reference, Block *> out_adjacents;
QHash<TrackReference, QList<OakEngineBlock *>> slide_info;
QHash<TrackReference, OakEngineBlock *> in_adjacents;
QHash<TrackReference, OakEngineBlock *> out_adjacents;
Rational movement;
foreach (const GhostBlockPair &p, blocks_sliding) {
const Track::Reference &track = p.ghost->get_track();
const TrackReference &track = p.ghost->get_track();
switch (p.ghost->get_mode()) {
case Timeline::k_none:
case TimelineApp::k_none:
break;
case Timeline::k_move: {
case TimelineApp::k_move: {
// These all should have moved uniformly, so as long as this is set, it should be fine
movement = p.ghost->get_in_adjustment();
QList<Block *> &blocks_on_this_track = slide_info[track];
QList<OakEngineBlock *> &blocks_on_this_track = slide_info[track];
bool inserted = false;
for (int i = 0; i < blocks_on_this_track.size(); i++) {
if (blocks_on_this_track.at(i)->in() > p.block->in()) {
if (block_in_rational(blocks_on_this_track.at(i)) >
block_in_rational(p.block)) {
blocks_on_this_track.insert(i, p.block);
inserted = true;
break;
@@ -871,10 +951,10 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
}
break;
}
case Timeline::k_trim_in:
case TimelineApp::k_trim_in:
out_adjacents.insert(track, p.block);
break;
case Timeline::k_trim_out:
case TimelineApp::k_trim_out:
in_adjacents.insert(track, p.block);
break;
}
@@ -883,16 +963,17 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
if (!movement.isNull()) {
for (auto i = slide_info.constBegin(); i != slide_info.constEnd();
i++) {
const QList<Block *> &moving_blocks = i.value();
const QList<OakEngineBlock *> &moving_blocks = i.value();
QVector<void *> slide_blocks;
slide_blocks.reserve(moving_blocks.size());
for (Block *b : moving_blocks) {
for (OakEngineBlock *b : moving_blocks) {
slide_blocks.append(reinterpret_cast<void *>(b));
}
oakengine_undo_command_multi_add_child(
command,
oakengine_track_slide_command(
reinterpret_cast<void *>(parent()->get_track_from_reference(i.key())),
reinterpret_cast<void *>(
parent()->get_track_from_reference(i.key())),
slide_blocks.constData(), slide_blocks.size(),
reinterpret_cast<void *>(in_adjacents.value(i.key())),
reinterpret_cast<void *>(out_adjacents.value(i.key())),
@@ -910,42 +991,42 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
command, qApp->translate("PointerTool", "Moved Clips").toUtf8().constData());
}
Timeline::MovementMode PointerTool::is_cursor_in_trim_handle(Block *block,
TimelineApp::MovementMode PointerTool::is_cursor_in_trim_handle(OakEngineBlock *block,
qreal cursor_x)
{
const double k_trim_handle =
QtUtils::q_font_metrics_width(parent()->fontMetrics(), "H");
double block_left = parent()->time_to_scene(block->in());
double block_right = parent()->time_to_scene(block->out());
double block_left = parent()->time_to_scene(block_in_rational(block));
double block_right = parent()->time_to_scene(block_out_rational(block));
double block_width = block_right - block_left;
// Block is too narrow, no trimming allowed
if (block_width <= k_trim_handle * 2) {
return Timeline::k_none;
return TimelineApp::k_none;
}
if (trimming_allowed_ && cursor_x <= block_left + k_trim_handle) {
return Timeline::k_trim_in;
return TimelineApp::k_trim_in;
} else if (trimming_allowed_ && cursor_x >= block_right - k_trim_handle) {
return Timeline::k_trim_out;
return TimelineApp::k_trim_out;
} else {
return Timeline::k_none;
return TimelineApp::k_none;
}
}
void PointerTool::initiate_drag(Block *clicked_item,
Timeline::MovementMode trim_mode,
void PointerTool::initiate_drag(OakEngineBlock *clicked_item,
TimelineApp::MovementMode trim_mode,
Qt::KeyboardModifiers modifiers)
{
initiate_drag_internal(clicked_item, trim_mode, modifiers, false, false,
false);
}
TimelineViewGhostItem *PointerTool::get_existing_ghost_from_block(Block *block)
TimelineViewGhostItem *PointerTool::get_existing_ghost_from_block(OakEngineBlock *block)
{
foreach (TimelineViewGhostItem *ghost, parent()->get_ghost_items()) {
if (QtUtils::value_to_ptr<Block>(ghost->get_data(
if (QtUtils::value_to_ptr<OakEngineBlock>(ghost->get_data(
TimelineViewGhostItem::k_attached_block)) == block) {
return ghost;
}
@@ -957,12 +1038,12 @@ TimelineViewGhostItem *PointerTool::get_existing_ghost_from_block(Block *block)
//#define HIDE_GAP_GHOSTS
TimelineViewGhostItem *
PointerTool::add_ghost_from_block(Block *block, Timeline::MovementMode mode,
PointerTool::add_ghost_from_block(OakEngineBlock *block, TimelineApp::MovementMode mode,
bool check_if_exists)
{
// Ignore null blocks or blocks that aren't attached to a track because there's nothing we can
// do with either of those
if (!block || !block->track()) {
if (!block || !oakengine_block_get_track(block)) {
return nullptr;
}
@@ -979,8 +1060,8 @@ PointerTool::add_ghost_from_block(Block *block, Timeline::MovementMode mode,
ghost = TimelineViewGhostItem::from_block(block);
#ifdef HIDE_GAP_GHOSTS
if (block->type() == Block::kGap) {
ghost->SetInvisible(true);
if (oakengine_block_is_gap(block)) {
ghost->set_invisible(true);
}
#endif
@@ -991,8 +1072,8 @@ PointerTool::add_ghost_from_block(Block *block, Timeline::MovementMode mode,
TimelineViewGhostItem *
PointerTool::add_ghost_from_null(const Rational &in, const Rational &out,
const Track::Reference &track,
Timeline::MovementMode mode)
const TrackReference &track,
TimelineApp::MovementMode mode)
{
TimelineViewGhostItem *ghost = new TimelineViewGhostItem();
@@ -1010,20 +1091,20 @@ PointerTool::add_ghost_from_null(const Rational &in, const Rational &out,
}
void PointerTool::add_ghost_internal(TimelineViewGhostItem *ghost,
Timeline::MovementMode mode)
TimelineApp::MovementMode mode)
{
ghost->set_mode(mode);
// Prepare snap points (optimizes snapping for later)
switch (mode) {
case Timeline::k_move:
case TimelineApp::k_move:
snap_points_.push_back(ghost->get_in());
snap_points_.push_back(ghost->get_out());
break;
case Timeline::k_trim_in:
case TimelineApp::k_trim_in:
snap_points_.push_back(ghost->get_in());
break;
case Timeline::k_trim_out:
case TimelineApp::k_trim_out:
snap_points_.push_back(ghost->get_out());
break;
default:
@@ -1033,13 +1114,17 @@ void PointerTool::add_ghost_internal(TimelineViewGhostItem *ghost,
parent()->add_ghost(ghost);
}
bool PointerTool::is_clip_trimmable(Block *clip, const QVector<Block *> &items,
const Timeline::MovementMode &mode)
bool PointerTool::is_clip_trimmable(OakEngineBlock *clip, const QVector<OakEngineBlock *> &items,
const TimelineApp::MovementMode &mode)
{
foreach (Block *compare, items) {
if (clip->track() == compare->track() && clip != compare &&
((compare->in() < clip->in() && mode == Timeline::k_trim_in) ||
(compare->out() > clip->out() && mode == Timeline::k_trim_out))) {
foreach (OakEngineBlock *compare, items) {
if (oakengine_block_get_track(clip) ==
oakengine_block_get_track(compare) &&
clip != compare &&
((block_in_rational(compare) < block_in_rational(clip) &&
mode == TimelineApp::k_trim_in) ||
(block_out_rational(compare) > block_out_rational(clip) &&
mode == TimelineApp::k_trim_out))) {
return false;
}
}
@@ -1052,7 +1137,7 @@ Rational PointerTool::validate_in_trimming(Rational movement)
bool first_ghost = true;
foreach (TimelineViewGhostItem *ghost, parent()->get_ghost_items()) {
if (ghost->get_mode() != Timeline::k_trim_in) {
if (ghost->get_mode() != TimelineApp::k_trim_in) {
continue;
}
@@ -1090,7 +1175,7 @@ Rational PointerTool::validate_out_trimming(Rational movement)
bool first_ghost = true;
foreach (TimelineViewGhostItem *ghost, parent()->get_ghost_items()) {
if (ghost->get_mode() != Timeline::k_trim_out) {
if (ghost->get_mode() != TimelineApp::k_trim_out) {
continue;
}
+21 -21
View File
@@ -40,20 +40,20 @@ public:
protected:
virtual void finish_drag(TimelineViewMouseEvent *event);
virtual void initiate_drag(Block *clicked_item,
Timeline::MovementMode trim_mode,
virtual void initiate_drag(OakEngineBlock *clicked_item,
TimelineApp::MovementMode trim_mode,
Qt::KeyboardModifiers modifiers);
TimelineViewGhostItem *get_existing_ghost_from_block(Block *block);
TimelineViewGhostItem *get_existing_ghost_from_block(OakEngineBlock *block);
TimelineViewGhostItem *add_ghost_from_block(Block *block,
Timeline::MovementMode mode,
TimelineViewGhostItem *add_ghost_from_block(OakEngineBlock *block,
TimelineApp::MovementMode mode,
bool check_if_exists = false);
TimelineViewGhostItem *add_ghost_from_null(const Rational &in,
const Rational &out,
const Track::Reference &track,
Timeline::MovementMode mode);
const TrackReference &track,
TimelineApp::MovementMode mode);
/**
* @brief Validates Ghosts that are getting their in points trimmed
@@ -73,23 +73,23 @@ protected:
virtual void process_drag(const TimelineCoordinate &mouse_pos);
void initiate_drag_internal(Block *clicked_item,
Timeline::MovementMode trim_mode,
void initiate_drag_internal(OakEngineBlock *clicked_item,
TimelineApp::MovementMode trim_mode,
Qt::KeyboardModifiers modifiers,
bool dont_roll_trims, bool allow_nongap_rolling,
bool slide_instead_of_moving);
const Timeline::MovementMode &drag_movement_mode() const
const TimelineApp::MovementMode &drag_movement_mode() const
{
return drag_movement_mode_;
}
void set_drag_movement_mode(const Timeline::MovementMode &d)
void set_drag_movement_mode(const TimelineApp::MovementMode &d)
{
drag_movement_mode_ = d;
}
static bool can_transition_move(TransitionBlock *transit,
const QVector<Block *> &clips);
static bool can_transition_move(OakEngineBlock *transit,
const QVector<OakEngineBlock *> &clips);
void set_movement_allowed(bool e)
{
@@ -111,19 +111,19 @@ protected:
gap_trimming_allowed_ = e;
}
void set_clicked_item(Block *b)
void set_clicked_item(OakEngineBlock *b)
{
clicked_item_ = b;
}
private:
Timeline::MovementMode is_cursor_in_trim_handle(Block *block, qreal cursor_x);
TimelineApp::MovementMode is_cursor_in_trim_handle(OakEngineBlock *block, qreal cursor_x);
void add_ghost_internal(TimelineViewGhostItem *ghost,
Timeline::MovementMode mode);
TimelineApp::MovementMode mode);
bool is_clip_trimmable(Block *clip, const QVector<Block *> &items,
const Timeline::MovementMode &mode);
bool is_clip_trimmable(OakEngineBlock *clip, const QVector<OakEngineBlock *> &items,
const TimelineApp::MovementMode &mode);
void process_ghosts_for_sliding();
@@ -136,10 +136,10 @@ private:
bool can_rubberband_select_;
bool rubberband_selecting_;
Track::Type drag_track_type_;
Timeline::MovementMode drag_movement_mode_;
TrackReference::Type drag_track_type_;
TimelineApp::MovementMode drag_movement_mode_;
Block *clicked_item_;
OakEngineBlock *clicked_item_;
QPoint drag_global_start_;
};
+35 -20
View File
@@ -21,8 +21,10 @@
#include "razor.h"
#include "oakengine/node.h"
#include "oakengine/timeline.h"
#include "widget/timelinewidget/timelinewidget.h"
#include "widget/timelinewidget/trackhandle.h"
namespace olive
{
@@ -47,7 +49,7 @@ void RazorTool::mouse_move(TimelineViewMouseEvent *event)
}
// Split at the current cursor track
Track::Reference split_track = event->get_track();
TrackReference split_track = event->get_track();
if (!split_tracks_.contains(split_track)) {
split_tracks_.append(split_track);
@@ -61,29 +63,42 @@ void RazorTool::mouse_release(TimelineViewMouseEvent *event)
// Always split at the same time
Rational split_time = drag_start_.get_frame();
QVector<Block *> blocks_to_split;
QVector<OakEngineBlock *> blocks_to_split;
foreach (const Track::Reference &track_ref, split_tracks_) {
Track *track = parent()->get_track_from_reference(track_ref);
foreach (const TrackReference &track_ref, split_tracks_) {
OakEngineTrack *track = parent()->get_track_from_reference(track_ref);
if (track == nullptr || track->is_locked()) {
if (track == nullptr || track_is_locked(track)) {
continue;
}
Block *block_at_time = track->nearest_block_before(split_time);
OakEngineBlock *block_at_time = oakengine_track_nearest_block_before(
track,
Timecode::time_to_timestamp(split_time, parent()->timebase(),
Timecode::k_round));
// Ensure there's a valid block here
ClipBlock *clip_at_time;
if (block_at_time && block_at_time->out() != split_time &&
(clip_at_time = dynamic_cast<ClipBlock *>(block_at_time)) &&
!blocks_to_split.contains(block_at_time)) {
blocks_to_split.append(block_at_time);
if (block_at_time &&
oakengine_node_is_clip(
reinterpret_cast<OakEngineNode *>(block_at_time))) {
int out_num = 0, out_den = 1;
oakengine_block_get_out_rational(
reinterpret_cast<const OakEngineNode *>(block_at_time),
&out_num, &out_den);
if (Rational(out_num, out_den) != split_time &&
!blocks_to_split.contains(block_at_time)) {
blocks_to_split.append(block_at_time);
// Add links if no alt is held
if (!(event->get_modifiers() & Qt::AltModifier)) {
foreach (Block *link, clip_at_time->block_links()) {
if (!blocks_to_split.contains(link)) {
blocks_to_split.append(link);
// Add links if no alt is held
if (!(event->get_modifiers() & Qt::AltModifier)) {
const int link_count =
oakengine_block_link_count(block_at_time);
for (int i = 0; i < link_count; i++) {
OakEngineBlock *link =
oakengine_block_link_at(block_at_time, i);
if (!blocks_to_split.contains(link)) {
blocks_to_split.append(link);
}
}
}
}
@@ -98,13 +113,13 @@ void RazorTool::mouse_release(TimelineViewMouseEvent *event)
// 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));
foreach (OakEngineBlock *b, blocks_to_split) {
if (oakengine_node_is_clip(reinterpret_cast<OakEngineNode *>(b))) {
clips.append(reinterpret_cast<OakEngineClip *>(b));
}
}
oakengine_sequence_split_clips(
reinterpret_cast<OakEngineSequence *>(parent()->sequence()),
parent()->sequence(),
clips.data(), clips.size(),
Timecode::time_to_timestamp(split_time, parent()->timebase(),
Timecode::k_round));
+1 -1
View File
@@ -36,7 +36,7 @@ public:
virtual void mouse_release(TimelineViewMouseEvent *event) override;
private:
QVector<Track::Reference> split_tracks_;
QVector<TrackReference> split_tracks_;
};
}
+8 -4
View File
@@ -19,6 +19,7 @@
#include "record.h"
#include "widget/timelinewidget/timelinewidget.h"
#include "widget/timelinewidget/trackhandle.h"
namespace olive
{
@@ -31,15 +32,18 @@ RecordTool::RecordTool(TimelineWidget *parent)
void RecordTool::mouse_press(TimelineViewMouseEvent *event)
{
const Track::Reference &track = event->get_track();
const TrackReference &track = event->get_track();
// Check if track is locked
Track *t = parent()->get_track_from_reference(track);
if (t && t->is_locked()) {
// TimelineWidget::get_track_from_reference() takes the app
// TrackReference mirror (ordinals static_assert-pinned to the engine
// Track::Type values, see common/trackreferencehandle.h).
OakEngineTrack *t = parent()->get_track_from_reference(track);
if (track_is_locked(t)) {
return;
}
if (t && t->type() != Track::k_audio) {
if (t && oakengine_track_type(t) != TrackReference::k_audio) {
// We only support audio tracks here
return;
}
+68 -40
View File
@@ -21,11 +21,11 @@
#include "widget/timelinewidget/timelinewidget.h"
#include "node/block/gap/gap.h"
#include "oakengine/node.h"
#include "oakengine/timeline.h"
#include "oakengine/undo.h"
#include "timeline/timelineundoripple.h"
#include "ripple.h"
#include "widget/timelinewidget/trackhandle.h"
namespace olive
{
@@ -37,8 +37,8 @@ RippleTool::RippleTool(TimelineWidget *parent)
set_gap_trimming_allowed(true);
}
void RippleTool::initiate_drag(Block *clicked_item,
Timeline::MovementMode trim_mode,
void RippleTool::initiate_drag(OakEngineBlock *clicked_item,
TimelineApp::MovementMode trim_mode,
Qt::KeyboardModifiers modifiers)
{
initiate_drag_internal(clicked_item, trim_mode, modifiers, true, true, false);
@@ -53,7 +53,7 @@ void RippleTool::initiate_drag(Block *clicked_item,
foreach (TimelineViewGhostItem *ghost, parent()->get_ghost_items()) {
Rational ghost_ripple_point;
if (trim_mode == Timeline::k_trim_in) {
if (trim_mode == TimelineApp::k_trim_in) {
ghost_ripple_point = ghost->get_in();
} else {
ghost_ripple_point = ghost->get_out();
@@ -63,33 +63,52 @@ void RippleTool::initiate_drag(Block *clicked_item,
}
// For each track that does NOT have a ghost, we need to make one for Gaps
foreach (Track *track, sequence()->get_tracks()) {
if (track->is_locked()) {
continue;
}
// (engine C ABI: per-type count + indexed access; type ordinals match
// TrackReference::Type/OAKENGINE_TRACK_TYPE_*)
auto *seq_handle = sequence();
int track_counts[3] = { 0, 0, 0 };
oakengine_sequence_track_count(seq_handle, &track_counts[0],
&track_counts[1], &track_counts[2]);
for (int track_type = 0; track_type < 3; track_type++) {
for (int track_index = 0; track_index < track_counts[track_type];
track_index++) {
OakEngineTrack *track =
oakengine_sequence_track_at(seq_handle, track_type,
track_index);
if (track_is_locked(track)) {
continue;
}
// Determine if we've already created a ghost on this track
bool ghost_on_this_track_exists = false;
// Determine if we've already created a ghost on this track
bool ghost_on_this_track_exists = false;
foreach (TimelineViewGhostItem *ghost, parent()->get_ghost_items()) {
if (parent()->get_track_from_reference(ghost->get_track()) == track) {
ghost_on_this_track_exists = true;
break;
foreach (TimelineViewGhostItem *ghost, parent()->get_ghost_items()) {
if (parent()->get_track_from_reference(ghost->get_track()) ==
track) {
ghost_on_this_track_exists = true;
break;
}
}
}
// If there's no ghost on this track, create one
if (!ghost_on_this_track_exists) {
// Find the block that starts just after or at the ripple point
Block *block_after_ripple =
track->nearest_block_after_or_at(earliest_ripple);
OakEngineBlock *block_after_ripple =
oakengine_track_nearest_block_after_or_at(
track,
core::Timecode::time_to_timestamp(earliest_ripple,
parent()->timebase()));
// Exception for out-transitions, do not create a gap between them
if (block_after_ripple) {
if (ClipBlock *prev_clip = dynamic_cast<ClipBlock *>(
block_after_ripple->previous())) {
if (prev_clip->out_transition() == block_after_ripple) {
block_after_ripple = block_after_ripple->next();
OakEngineBlock *prev_block =
oakengine_block_prev(block_after_ripple);
if (oakengine_node_is_clip(
reinterpret_cast<OakEngineNode *>(prev_block))) {
if (oakengine_clip_out_transition(prev_block) ==
block_after_ripple) {
block_after_ripple =
oakengine_block_next(block_after_ripple);
}
}
}
@@ -98,22 +117,30 @@ void RippleTool::initiate_drag(Block *clicked_item,
if (block_after_ripple) {
TimelineViewGhostItem *ghost;
if (dynamic_cast<GapBlock *>(block_after_ripple)) {
if (oakengine_block_is_gap(block_after_ripple)) {
// If this Block is already a Gap, ghost it now
ghost = add_ghost_from_block(block_after_ripple, trim_mode);
} else {
// Well we need to ripple SOMETHING, it'll either be the previous block if it's a gap
// or we'll have to create a new gap ourselves
Block *previous = block_after_ripple->previous();
OakEngineBlock *previous =
oakengine_block_prev(block_after_ripple);
if (dynamic_cast<GapBlock *>(previous)) {
if (oakengine_block_is_gap(previous)) {
// Previous is a gap, that'll make a fine substitute
ghost = add_ghost_from_block(previous, trim_mode);
} else {
// Previous is not a gap, we'll have to insert one there ourselves
ghost = add_ghost_from_null(block_after_ripple->in(),
block_after_ripple->in(),
track->to_reference(),
int in_num = 0, in_den = 1;
oakengine_block_get_in_rational(
reinterpret_cast<const OakEngineNode *>(
block_after_ripple),
&in_num, &in_den);
Rational ripple_in(in_num, in_den);
ghost = add_ghost_from_null(ripple_in,
ripple_in,
ghost_block_track_reference(
block_after_ripple),
trim_mode);
ghost->set_data(TimelineViewGhostItem::k_reference_block,
QtUtils::ptr_to_value(block_after_ripple));
@@ -123,44 +150,45 @@ void RippleTool::initiate_drag(Block *clicked_item,
}
}
}
}
void RippleTool::finish_drag(TimelineViewMouseEvent *event)
{
Q_UNUSED(event)
if (parent()->has_ghosts()) {
QVector<QVector<oakengine_ripple_info>> info_list(Track::k_count);
QVector<QVector<oakengine_ripple_info>> info_list(TrackReference::k_count);
foreach (TimelineViewGhostItem *ghost, parent()->get_ghost_items()) {
if (!ghost->has_been_adjusted()) {
continue;
}
Track *track = parent()->get_track_from_reference(ghost->get_track());
OakEngineTrack *track =
parent()->get_track_from_reference(ghost->get_track());
oakengine_ripple_info info;
Block *b = QtUtils::value_to_ptr<Block>(
OakEngineBlock *b = QtUtils::value_to_ptr<OakEngineBlock>(
ghost->get_data(TimelineViewGhostItem::k_attached_block));
if (b) {
info.block = reinterpret_cast<OakEngineBlock *>(b);
info.block = b;
info.append_gap = 0;
} else {
info.block = reinterpret_cast<OakEngineBlock *>(
QtUtils::value_to_ptr<Block>(
ghost->get_data(TimelineViewGhostItem::k_reference_block)));
info.block = QtUtils::value_to_ptr<OakEngineBlock>(
ghost->get_data(TimelineViewGhostItem::k_reference_block));
info.append_gap = 1;
}
info.track = reinterpret_cast<OakEngineTrack *>(track);
info.track = track;
info_list[track->type()].append(info);
info_list[oakengine_track_type(track)].append(info);
}
void *command = oakengine_undo_command_create_multi();
Rational movement;
if (drag_movement_mode() == Timeline::k_trim_out) {
if (drag_movement_mode() == TimelineApp::k_trim_out) {
movement = parent()->get_ghost_items().first()->get_out_adjustment();
} else {
movement = parent()->get_ghost_items().first()->get_in_adjustment();
@@ -171,7 +199,7 @@ void RippleTool::finish_drag(TimelineViewMouseEvent *event)
oakengine_undo_command_multi_add_child(
command,
oakengine_sequence_ripple_tracks_command(
reinterpret_cast<OakEngineSequence *>(sequence()), i,
sequence(), i,
info_list.at(i).constData(), info_list.at(i).size(),
movement.numerator(), movement.denominator(),
drag_movement_mode()));
@@ -182,7 +210,7 @@ void RippleTool::finish_drag(TimelineViewMouseEvent *event)
TimelineWidgetSelections new_sel = parent()->get_selections();
TimelineViewGhostItem *reference_ghost =
parent()->get_ghost_items().first();
if (drag_movement_mode() == Timeline::k_trim_in) {
if (drag_movement_mode() == TimelineApp::k_trim_in) {
new_sel.trim_out(-reference_ghost->get_in_adjustment());
} else {
new_sel.trim_out(reference_ghost->get_out_adjustment());
+2 -2
View File
@@ -34,8 +34,8 @@ public:
protected:
virtual void finish_drag(TimelineViewMouseEvent *event) override;
virtual void initiate_drag(Block *clicked_item,
Timeline::MovementMode trim_mode,
virtual void initiate_drag(OakEngineBlock *clicked_item,
TimelineApp::MovementMode trim_mode,
Qt::KeyboardModifiers modifiers) override;
};
+2 -3
View File
@@ -21,7 +21,6 @@
#include "widget/timelinewidget/timelinewidget.h"
#include "node/block/gap/gap.h"
#include "rolling.h"
namespace olive
@@ -34,8 +33,8 @@ RollingTool::RollingTool(TimelineWidget *parent)
set_gap_trimming_allowed(true);
}
void RollingTool::initiate_drag(Block *clicked_item,
Timeline::MovementMode trim_mode,
void RollingTool::initiate_drag(OakEngineBlock *clicked_item,
TimelineApp::MovementMode trim_mode,
Qt::KeyboardModifiers modifiers)
{
initiate_drag_internal(clicked_item, trim_mode, modifiers, false, true,
+2 -2
View File
@@ -32,8 +32,8 @@ public:
RollingTool(TimelineWidget *parent);
protected:
virtual void initiate_drag(Block *clicked_item,
Timeline::MovementMode trim_mode,
virtual void initiate_drag(OakEngineBlock *clicked_item,
TimelineApp::MovementMode trim_mode,
Qt::KeyboardModifiers modifiers) override;
};
+2 -3
View File
@@ -21,7 +21,6 @@
#include "widget/timelinewidget/timelinewidget.h"
#include "node/block/gap/gap.h"
#include "slide.h"
namespace olive
@@ -35,8 +34,8 @@ SlideTool::SlideTool(TimelineWidget *parent)
set_gap_trimming_allowed(true);
}
void SlideTool::initiate_drag(Block *clicked_item,
Timeline::MovementMode trim_mode,
void SlideTool::initiate_drag(OakEngineBlock *clicked_item,
TimelineApp::MovementMode trim_mode,
Qt::KeyboardModifiers modifiers)
{
initiate_drag_internal(clicked_item, trim_mode, modifiers, false, true, true);
+2 -2
View File
@@ -32,8 +32,8 @@ public:
SlideTool(TimelineWidget *parent);
protected:
virtual void initiate_drag(Block *clicked_item,
Timeline::MovementMode trim_mode,
virtual void initiate_drag(OakEngineBlock *clicked_item,
TimelineApp::MovementMode trim_mode,
Qt::KeyboardModifiers modifiers) override;
};
+3 -5
View File
@@ -24,7 +24,6 @@
#include <QToolTip>
#include "common/configwrapper.h"
#include "timeline/timelineundogeneral.h"
#include "widget/timelinewidget/timelinewidget.h"
#include "oakengine/timeline.h"
@@ -76,12 +75,11 @@ void SlipTool::finish_drag(TimelineViewMouseEvent *event)
// Find earliest point to ripple around
foreach (TimelineViewGhostItem *ghost, parent()->get_ghost_items()) {
Block *b = QtUtils::value_to_ptr<Block>(
OakEngineBlock *b = QtUtils::value_to_ptr<OakEngineBlock>(
ghost->get_data(TimelineViewGhostItem::k_attached_block));
ClipBlock *cb = dynamic_cast<ClipBlock *>(b);
if (cb) {
oakengine_undo_command_multi_add_child(command, oakengine_block_set_media_in_command(reinterpret_cast<void *>(cb), ghost->get_adjusted_media_in().numerator(), ghost->get_adjusted_media_in().denominator()));
if (oakengine_node_is_clip(reinterpret_cast<OakEngineNode *>(b))) {
oakengine_undo_command_multi_add_child(command, oakengine_block_set_media_in_command(reinterpret_cast<void *>(b), ghost->get_adjusted_media_in().numerator(), ghost->get_adjusted_media_in().denominator()));
}
}
+9 -11
View File
@@ -21,8 +21,6 @@
#include "widget/timelinewidget/timelinewidget.h"
#include "node/block/transition/transition.h"
namespace olive
{
@@ -43,20 +41,20 @@ TimelineWidget *TimelineTool::parent()
return parent_;
}
Sequence *TimelineTool::sequence()
OakEngineSequence *TimelineTool::sequence()
{
return parent_->sequence();
}
Timeline::MovementMode
TimelineTool::flip_trim_mode(const Timeline::MovementMode &trim_mode)
TimelineApp::MovementMode
TimelineTool::flip_trim_mode(const TimelineApp::MovementMode &trim_mode)
{
if (trim_mode == Timeline::k_trim_in) {
return Timeline::k_trim_out;
if (trim_mode == TimelineApp::k_trim_in) {
return TimelineApp::k_trim_out;
}
if (trim_mode == Timeline::k_trim_out) {
return Timeline::k_trim_in;
if (trim_mode == TimelineApp::k_trim_out) {
return TimelineApp::k_trim_in;
}
return trim_mode;
@@ -82,7 +80,7 @@ Rational TimelineTool::validate_time_movement(Rational movement)
bool first_ghost = true;
foreach (TimelineViewGhostItem *ghost, parent()->get_ghost_items()) {
if (ghost->get_mode() != Timeline::k_move) {
if (ghost->get_mode() != TimelineApp::k_move) {
continue;
}
@@ -106,7 +104,7 @@ int TimelineTool::validate_track_movement(
int movement, const QVector<TimelineViewGhostItem *> &ghosts)
{
foreach (TimelineViewGhostItem *ghost, ghosts) {
if (ghost->get_mode() != Timeline::k_move) {
if (ghost->get_mode() != TimelineApp::k_move) {
continue;
}
+3 -3
View File
@@ -69,10 +69,10 @@ public:
TimelineWidget *parent();
Sequence *sequence();
OakEngineSequence *sequence();
static Timeline::MovementMode
flip_trim_mode(const Timeline::MovementMode &trim_mode);
static TimelineApp::MovementMode
flip_trim_mode(const TimelineApp::MovementMode &trim_mode);
static Rational snap_movement_to_timebase(const Rational &start,
Rational movement,
+38 -18
View File
@@ -21,8 +21,7 @@
#include "trackselect.h"
#include "node/block/gap/gap.h"
#include "node/output/track/track.h"
#include "oakengine/timeline.h"
#include "widget/timelinewidget/timelinewidget.h"
namespace olive
@@ -35,56 +34,77 @@ TrackSelectTool::TrackSelectTool(TimelineWidget *parent)
void TrackSelectTool::mouse_press(TimelineViewMouseEvent *event)
{
QVector<Block *> blocks;
QVector<OakEngineBlock *> blocks;
bool forward = !(event->get_modifiers() & Qt::ControlModifier);
parent()->deselect_all();
if (event->get_modifiers() & Qt::ShiftModifier) {
// Track only
Track *track = parent()->get_track_from_reference(event->get_track());
OakEngineTrack *track =
parent()->get_track_from_reference(event->get_track());
if (track) {
select_blocks_on_track(track, event, &blocks, forward);
}
} else {
// All tracks
foreach (Track *track, parent()->sequence()->get_tracks()) {
select_blocks_on_track(track, event, &blocks, forward);
// All tracks (engine C ABI: per-type count + indexed access; type
// ordinals match TrackReference::Type/OAKENGINE_TRACK_TYPE_*)
auto *seq_handle = parent()->sequence();
int counts[3] = { 0, 0, 0 };
oakengine_sequence_track_count(seq_handle, &counts[0], &counts[1],
&counts[2]);
for (int type = 0; type < 3; type++) {
for (int i = 0; i < counts[type]; i++) {
OakEngineTrack *track =
oakengine_sequence_track_at(seq_handle, type, i);
select_blocks_on_track(track, event, &blocks, forward);
}
}
}
if (!blocks.isEmpty()) {
parent()->signal_selected_blocks(blocks);
set_drag_movement_mode(Timeline::k_move);
set_drag_movement_mode(TimelineApp::k_move);
set_clicked_item(blocks.first());
drag_start_ = event->get_coordinates();
} else {
set_drag_movement_mode(Timeline::k_none);
set_drag_movement_mode(TimelineApp::k_none);
}
}
void TrackSelectTool::select_blocks_on_track(Track *track,
void TrackSelectTool::select_blocks_on_track(OakEngineTrack *track,
TimelineViewMouseEvent *event,
QVector<Block *> *blocks,
QVector<OakEngineBlock *> *blocks,
bool forward)
{
Block *b = track->nearest_block_before_or_at(event->get_frame());
OakEngineBlock *b =
oakengine_track_nearest_block_before_or_at(
track,
core::Timecode::time_to_timestamp(event->get_frame(),
parent()->timebase()));
if (!b && !track->blocks().isEmpty() && !forward) {
if (!b && oakengine_track_block_count(track) > 0 && !forward) {
// Fallback to first or last block in track
b = track->blocks().last();
b = oakengine_track_block_at(track,
oakengine_track_block_count(track) - 1);
}
while (b) {
if (!dynamic_cast<GapBlock *>(b)) {
if (!oakengine_block_is_gap(b)) {
if (!blocks->contains(b)) {
parent()->add_selection(b);
blocks->append(b);
}
if (!(event->get_modifiers() & Qt::AltModifier)) {
if (ClipBlock *clip = dynamic_cast<ClipBlock *>(b)) {
foreach (Block *link, clip->block_links()) {
if (oakengine_node_is_clip(
reinterpret_cast<OakEngineNode *>(b))) {
// ClipBlock::block_links() via the C ABI (linked blocks)
const int link_count =
oakengine_block_link_count(b);
for (int i = 0; i < link_count; i++) {
OakEngineBlock *link =
oakengine_block_link_at(b, i);
if (!blocks->contains(link)) {
parent()->add_selection(link);
blocks->append(link);
@@ -94,7 +114,7 @@ void TrackSelectTool::select_blocks_on_track(Track *track,
}
}
b = forward ? b->next() : b->previous();
b = forward ? oakengine_block_next(b) : oakengine_block_prev(b);
}
}
+2 -2
View File
@@ -34,8 +34,8 @@ public:
virtual void mouse_press(TimelineViewMouseEvent *event) override;
private:
void select_blocks_on_track(Track *track, TimelineViewMouseEvent *event,
QVector<Block *> *blocks, bool forward);
void select_blocks_on_track(OakEngineTrack *track, TimelineViewMouseEvent *event,
QVector<OakEngineBlock *> *blocks, bool forward);
};
}
+119 -57
View File
@@ -21,17 +21,47 @@
#include "widget/timelinewidget/timelinewidget.h"
#include "node/block/transition/crossdissolve/crossdissolvetransition.h"
#include "node/block/transition/transition.h"
#include "oakengine/node.h"
#include "oakengine/undo.h"
#include "oakengine/timeline.h"
#include "timeline/timelineundopointer.h"
#include "transition.h"
#include "widget/timelinewidget/trackhandle.h"
namespace olive
{
namespace
{
/// Block::in() as rational seconds.
Rational ghost_block_in(const OakEngineBlock *block)
{
int num = 0, den = 1;
oakengine_block_get_in_rational(
reinterpret_cast<const OakEngineNode *>(block), &num, &den);
return Rational(num, den);
}
/// Block::out() as rational seconds.
Rational ghost_block_out(const OakEngineBlock *block)
{
int num = 0, den = 1;
oakengine_block_get_out_rational(
reinterpret_cast<const OakEngineNode *>(block), &num, &den);
return Rational(num, den);
}
/// Block::length() as rational seconds.
Rational ghost_block_length(const OakEngineBlock *block)
{
int num = 0, den = 1;
oakengine_block_get_length_rational(
reinterpret_cast<const OakEngineNode *>(block), &num, &den);
return Rational(num, den);
}
} // namespace
TransitionTool::TransitionTool(TimelineWidget *parent)
: AddTool(parent)
{
@@ -39,15 +69,15 @@ TransitionTool::TransitionTool(TimelineWidget *parent)
void TransitionTool::hover_move(TimelineViewMouseEvent *event)
{
ClipBlock *primary = nullptr;
ClipBlock *secondary = nullptr;
Timeline::MovementMode trim_mode = Timeline::k_none;
OakEngineClip *primary = nullptr;
OakEngineClip *secondary = nullptr;
TimelineApp::MovementMode trim_mode = TimelineApp::k_none;
Rational transition_start_point;
get_blocks_at_coord(event->get_coordinates(), &primary, &secondary, &trim_mode,
&transition_start_point);
if (trim_mode == Timeline::k_trim_in) {
if (trim_mode == TimelineApp::k_trim_in) {
std::swap(primary, secondary);
}
@@ -56,8 +86,8 @@ void TransitionTool::hover_move(TimelineViewMouseEvent *event)
void TransitionTool::mouse_press(TimelineViewMouseEvent *event)
{
ClipBlock *primary, *secondary;
Timeline::MovementMode trim_mode;
OakEngineClip *primary, *secondary;
TimelineApp::MovementMode trim_mode;
Rational transition_start_point;
if (!get_blocks_at_coord(event->get_coordinates(), &primary, &secondary,
&trim_mode, &transition_start_point)) {
@@ -97,51 +127,68 @@ void TransitionTool::mouse_move(TimelineViewMouseEvent *event)
void TransitionTool::mouse_release(TimelineViewMouseEvent *event)
{
const Track::Reference &track = ghost_->get_track();
const TrackReference &track = ghost_->get_track();
if (ghost_) {
if (!ghost_->get_adjusted_length().isNull()) {
TransitionBlock *transition;
OakEngineNode *transition;
if (Core::instance()->get_selected_transition().isEmpty()) {
// Fallback if the user hasn't selected one yet
transition = reinterpret_cast<CrossDissolveTransition*>(oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.crossdissolve"));
transition = oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.crossdissolve");
} else {
transition =
reinterpret_cast<TransitionBlock *>(oakengine_node_factory_create_from_id(
Core::instance()->get_selected_transition().toUtf8().constData()));
oakengine_node_factory_create_from_id(
Core::instance()->get_selected_transition().toUtf8().constData());
}
// Set transition length
// Set transition length: routed through a trim command child
// added before the placement child below (children redo in
// order, so the length is set before the block is placed).
// oakengine_block_set_length_and_media_out() itself cannot be
// used here -- it requires the block to already be on a track
// (OAKENGINE_E_STATE). Before placement the block has no
// adjacent blocks, so a trim-out command reduces to
// Block::set_length_and_media_out().
Rational len = ghost_->get_adjusted_length();
transition->set_length_and_media_out(len);
void *command = oakengine_undo_command_create_multi();
auto *seq_handle = sequence();
// Place transition in place
oakengine_undo_command_multi_add_child(command,
oakengine_node_add_to_project_command(
reinterpret_cast<OakEngineProject *>(parent()->get_connected_node()->parent()),
reinterpret_cast<OakEngineNode *>(transition)));
oakengine_node_parent(reinterpret_cast<OakEngineNode *>(
parent()->get_connected_node())),
transition));
oakengine_undo_command_multi_add_child(command, oakengine_node_set_position_command(reinterpret_cast<void *>(transition), reinterpret_cast<void *>(transition), 0, 0, 0));
oakengine_undo_command_multi_add_child(command, oakengine_track_place_block_command(reinterpret_cast<void *>(sequence()->track_list(track.type())), track.index(), reinterpret_cast<void *>(transition), core::Timecode::time_to_timestamp(ghost_->get_adjusted_in(), parent()->timebase())));
oakengine_undo_command_multi_add_child(command,
oakengine_block_trim_command(
reinterpret_cast<void *>(oakengine_sequence_track_at(
seq_handle, track.type(), track.index())),
reinterpret_cast<void *>(transition),
len.numerator(), len.denominator(),
OAKENGINE_MOVEMENT_MODE_TRIM_OUT, 0));
oakengine_undo_command_multi_add_child(command, oakengine_track_place_block_command(reinterpret_cast<void *>(oakengine_sequence_track_list(seq_handle, track.type())), track.index(), reinterpret_cast<void *>(transition), core::Timecode::time_to_timestamp(ghost_->get_adjusted_in(), parent()->timebase())));
if (dual_transition_) {
// Block mouse is hovering over
Block *active_block = QtUtils::value_to_ptr<Block>(
OakEngineBlock *active_block = QtUtils::value_to_ptr<OakEngineBlock>(
ghost_->get_data(TimelineViewGhostItem::k_attached_block));
// Block mouse is next to
Block *friend_block = QtUtils::value_to_ptr<Block>(
OakEngineBlock *friend_block = QtUtils::value_to_ptr<OakEngineBlock>(
ghost_->get_data(TimelineViewGhostItem::k_reference_block));
// Use ghost mode to determine which block is which
Block *out_block = (ghost_->get_mode() == Timeline::k_trim_in) ?
OakEngineBlock *out_block = (ghost_->get_mode() == TimelineApp::k_trim_in) ?
friend_block :
active_block;
Block *in_block = (ghost_->get_mode() == Timeline::k_trim_in) ?
OakEngineBlock *in_block = (ghost_->get_mode() == TimelineApp::k_trim_in) ?
active_block :
friend_block;
@@ -150,7 +197,7 @@ void TransitionTool::mouse_release(TimelineViewMouseEvent *event)
command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(out_block),
reinterpret_cast<OakEngineNode *>(transition),
transition,
QLatin1String(oakengine_transition_out_block_input_id()).toUtf8().constData(),
-1));
@@ -158,18 +205,18 @@ void TransitionTool::mouse_release(TimelineViewMouseEvent *event)
command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(in_block),
reinterpret_cast<OakEngineNode *>(transition),
transition,
QLatin1String(oakengine_transition_in_block_input_id()).toUtf8().constData(),
-1));
oakengine_undo_command_multi_add_child(command, oakengine_node_set_position_command(reinterpret_cast<void *>(out_block), reinterpret_cast<void *>(transition), -1, -0.5, 0));
oakengine_undo_command_multi_add_child(command, oakengine_node_set_position_command(reinterpret_cast<void *>(in_block), reinterpret_cast<void *>(transition), -1, 0.5, 0));
} else {
Block *block_to_transition = QtUtils::value_to_ptr<Block>(
OakEngineBlock *block_to_transition = QtUtils::value_to_ptr<OakEngineBlock>(
ghost_->get_data(TimelineViewGhostItem::k_attached_block));
QString transition_input_to_connect;
if (ghost_->get_mode() == Timeline::k_trim_in) {
if (ghost_->get_mode() == TimelineApp::k_trim_in) {
transition_input_to_connect =
QLatin1String(oakengine_transition_in_block_input_id());
} else {
@@ -182,7 +229,7 @@ void TransitionTool::mouse_release(TimelineViewMouseEvent *event)
command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(block_to_transition),
reinterpret_cast<OakEngineNode *>(transition),
transition,
transition_input_to_connect.toUtf8().constData(),
-1));
@@ -203,68 +250,83 @@ void TransitionTool::mouse_release(TimelineViewMouseEvent *event)
}
bool TransitionTool::get_blocks_at_coord(const TimelineCoordinate &coord,
ClipBlock **primary,
ClipBlock **secondary,
Timeline::MovementMode *ptrim_mode,
OakEngineClip **primary,
OakEngineClip **secondary,
TimelineApp::MovementMode *ptrim_mode,
Rational *start_point)
{
const Track::Reference &track = coord.get_track();
Track *t = parent()->get_track_from_reference(track);
const TrackReference &coord_track = coord.get_track();
OakEngineTrack *t = parent()->get_track_from_reference(coord_track);
Rational cursor_frame = coord.get_frame();
if (!t || t->is_locked()) {
if (!t || track_is_locked(t)) {
return false;
}
Block *block_at_time = t->nearest_block_before_or_at(coord.get_frame());
if (!dynamic_cast<ClipBlock *>(block_at_time)) {
OakEngineBlock *block_at_time =
oakengine_track_nearest_block_before_or_at(
t,
core::Timecode::time_to_timestamp(coord.get_frame(),
parent()->timebase()));
if (!oakengine_node_is_clip(reinterpret_cast<OakEngineNode *>(block_at_time))) {
return false;
}
// Determine which side of the clip the transition belongs to
Rational transition_start_point;
Timeline::MovementMode trim_mode;
Rational tenth_point = block_at_time->length() / 10;
Block *other_block = nullptr;
if (cursor_frame < (block_at_time->in() + block_at_time->length() / 2)) {
if (static_cast<ClipBlock *>(block_at_time)->in_transition()) {
TimelineApp::MovementMode trim_mode;
Rational tenth_point = ghost_block_length(block_at_time) / 10;
OakEngineBlock *other_block = nullptr;
if (cursor_frame <
(ghost_block_in(block_at_time) +
ghost_block_length(block_at_time) / 2)) {
if (oakengine_clip_in_transition(block_at_time)) {
// This clip already has a transition here
return false;
}
ClipBlock *adjacent =
dynamic_cast<ClipBlock *>(block_at_time->previous());
OakEngineBlock *previous = oakengine_block_prev(block_at_time);
OakEngineClip *adjacent =
oakengine_node_is_clip(reinterpret_cast<OakEngineNode *>(previous)) ?
reinterpret_cast<OakEngineClip *>(previous) :
nullptr;
if (adjacent) {
tenth_point = std::min(tenth_point, adjacent->length() / 10);
tenth_point = std::min(tenth_point, ghost_block_length(reinterpret_cast<const OakEngineBlock *>(adjacent)) / 10);
}
transition_start_point = block_at_time->in();
trim_mode = Timeline::k_trim_in;
transition_start_point = ghost_block_in(block_at_time);
trim_mode = TimelineApp::k_trim_in;
if (cursor_frame < (block_at_time->in() + tenth_point) && adjacent) {
other_block = adjacent;
if (cursor_frame < (ghost_block_in(block_at_time) + tenth_point) &&
adjacent) {
other_block = reinterpret_cast<OakEngineBlock *>(adjacent);
}
} else {
if (static_cast<ClipBlock *>(block_at_time)->out_transition()) {
if (oakengine_clip_out_transition(block_at_time)) {
// This clip already has a transition here
return false;
}
ClipBlock *adjacent = dynamic_cast<ClipBlock *>(block_at_time->next());
OakEngineBlock *next = oakengine_block_next(block_at_time);
OakEngineClip *adjacent =
oakengine_node_is_clip(reinterpret_cast<OakEngineNode *>(next)) ?
reinterpret_cast<OakEngineClip *>(next) :
nullptr;
if (adjacent) {
tenth_point = std::min(tenth_point, adjacent->length() / 10);
tenth_point = std::min(tenth_point, ghost_block_length(reinterpret_cast<const OakEngineBlock *>(adjacent)) / 10);
}
transition_start_point = block_at_time->out();
trim_mode = Timeline::k_trim_out;
transition_start_point = ghost_block_out(block_at_time);
trim_mode = TimelineApp::k_trim_out;
if (cursor_frame > block_at_time->out() - tenth_point && adjacent) {
other_block = block_at_time->next();
if (cursor_frame > ghost_block_out(block_at_time) - tenth_point &&
adjacent) {
other_block = next;
}
}
*primary = static_cast<ClipBlock *>(block_at_time);
*secondary = static_cast<ClipBlock *>(other_block);
*primary = reinterpret_cast<OakEngineClip *>(block_at_time);
*secondary = reinterpret_cast<OakEngineClip *>(other_block);
*ptrim_mode = trim_mode;
*start_point = transition_start_point;
+3 -3
View File
@@ -38,9 +38,9 @@ public:
virtual void mouse_release(TimelineViewMouseEvent *event) override;
private:
bool get_blocks_at_coord(const TimelineCoordinate &coord, ClipBlock **primary,
ClipBlock **secondary,
Timeline::MovementMode *trim_mode,
bool get_blocks_at_coord(const TimelineCoordinate &coord, OakEngineClip **primary,
OakEngineClip **secondary,
TimelineApp::MovementMode *trim_mode,
Rational *start_point);
bool dual_transition_;
+25 -17
View File
@@ -21,52 +21,60 @@
#ifndef OAK_TRACKHANDLE_H
#define OAK_TRACKHANDLE_H
#include "node/output/track/track.h"
#include "oakengine/node.h"
#include "oakengine/timeline.h"
namespace olive
{
/**
* @brief Facade accessors for Track pointers held by the timeline tools.
* @brief Facade accessors for track handles held by the timeline tools.
*
* Track::is_locked()/is_muted()/type() are out-of-line engine symbols; the
* timeline code keeps Track* as opaque identity pointers and routes the
* queries through the liboakengine C ABI instead (same pattern as
* app/widget/keyframeview/keyframehandle.h). Track::sequence()/index() are
* header-inline and used directly.
* The engine's track accessors (is_locked()/is_muted()/sequence()/index())
* are engine symbols; the timeline code keeps tracks as opaque
* OakEngineTrack* handles and routes the queries through the liboakengine
* C ABI instead (same pattern as app/widget/keyframeview/keyframehandle.h).
*/
inline OakEngineTrack *trackhandle(Track *track)
inline OakEngineTrack *trackhandle(OakEngineTrack *track)
{
return reinterpret_cast<OakEngineTrack *>(track);
return track;
}
inline OakEngineSequence *track_sequence_handle(Track *track)
inline OakEngineSequence *track_sequence_handle(OakEngineTrack *track)
{
return reinterpret_cast<OakEngineSequence *>(track ? track->sequence() :
nullptr);
return track ? reinterpret_cast<OakEngineSequence *>(
oakengine_track_get_sequence(
reinterpret_cast<OakEngineNode *>(track))) :
nullptr;
}
inline int track_type_of(Track *track)
inline int track_type_of(OakEngineTrack *track)
{
return oakengine_track_type(trackhandle(track));
}
inline bool track_is_locked(Track *track)
inline int track_index_of(OakEngineTrack *track)
{
return track ? oakengine_track_get_index(
reinterpret_cast<OakEngineNode *>(track)) :
-1;
}
inline bool track_is_locked(OakEngineTrack *track)
{
return track &&
oakengine_track_is_locked(track_sequence_handle(track),
track_type_of(track),
track->index()) != 0;
track_index_of(track)) != 0;
}
inline bool track_is_muted(Track *track)
inline bool track_is_muted(OakEngineTrack *track)
{
return track &&
oakengine_track_is_muted(track_sequence_handle(track),
track_type_of(track),
track->index()) != 0;
track_index_of(track)) != 0;
}
} // namespace olive
@@ -29,14 +29,35 @@
#include "trackviewitem.h"
#include "oakengine/node.h"
#include "oakengine/timeline.h"
namespace olive
{
namespace
{
/// Number of tracks of `track_type` (OAKENGINE_TRACK_TYPE_*) in `sequence`.
int track_count_for(OakEngineSequence *sequence, int track_type)
{
int video = 0, audio = 0, subtitle = 0;
oakengine_sequence_track_count(sequence, &video, &audio, &subtitle);
switch (track_type) {
case OAKENGINE_TRACK_TYPE_VIDEO:
return video;
case OAKENGINE_TRACK_TYPE_AUDIO:
return audio;
case OAKENGINE_TRACK_TYPE_SUBTITLE:
return subtitle;
}
return 0;
}
} // namespace
TrackView::TrackView(Qt::Alignment vertical_alignment, QWidget *parent)
: QScrollArea(parent)
, list_(nullptr)
, sequence_(nullptr)
, track_type_(OAKENGINE_TRACK_TYPE_VIDEO)
, alignment_(vertical_alignment)
{
setAlignment(Qt::AlignLeft | alignment_);
@@ -71,27 +92,30 @@ TrackView::TrackView(Qt::Alignment vertical_alignment, QWidget *parent)
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
}
void TrackView::connect_track_list(TrackList *list)
void TrackView::connect_track_list(OakEngineSequence *sequence, int track_type)
{
if (list_ != nullptr) {
if (sequence_ != nullptr) {
// Remove tracks
for (int i = 0; i < list_->get_track_count(); i++) {
const int count = track_count_for(sequence_, track_type_);
for (int i = 0; i < count; i++) {
splitter_->remove(0);
}
}
list_ = list;
sequence_ = sequence;
track_type_ = track_type;
if (list_ != nullptr) {
foreach (Track *track, list_->get_tracks()) {
insert_track(track);
if (sequence_ != nullptr) {
const int count = track_count_for(sequence_, track_type_);
for (int i = 0; i < count; i++) {
insert_track(oakengine_sequence_track_at(sequence_, track_type_, i));
}
}
}
void TrackView::disconnect_track_list()
{
connect_track_list(nullptr);
connect_track_list(nullptr, track_type_);
}
void TrackView::resizeEvent(QResizeEvent *e)
@@ -116,26 +140,32 @@ void TrackView::scrollbar_range_changed(int, int max)
void TrackView::track_height_changed(int index, int height)
{
Track *track = list_->get_track_at(index);
oakengine_track_set_height(
reinterpret_cast<OakEngineSequence *>(list_->parent()),
track->type(), track->index(),
sequence_, track_type_, index,
oakengine_track_height_pixels_to_internal(height));
}
void TrackView::insert_track(Track *track)
void TrackView::insert_track(OakEngineTrack *track)
{
TrackViewItem *tvi = new TrackViewItem(track);
connect(tvi, &TrackViewItem::about_to_delete_track, this,
&TrackView::about_to_delete_track);
splitter_->insert(track->index(), track->get_track_height_in_pixels(), tvi);
const int index = oakengine_track_get_index(
reinterpret_cast<OakEngineNode *>(track));
double internal_height = 0.0;
oakengine_track_get_height(sequence_, track_type_, index,
&internal_height);
splitter_->insert(
index, oakengine_track_height_internal_to_pixels(internal_height),
tvi);
}
void TrackView::remove_track(Track *track)
void TrackView::remove_track(OakEngineTrack *track)
{
splitter_->remove(track->index());
splitter_->remove(oakengine_track_get_index(
reinterpret_cast<OakEngineNode *>(track)));
}
}
@@ -25,7 +25,6 @@
#include <QScrollArea>
#include <QSplitter>
#include "node/output/track/tracklist.h"
#include "oakengine/timeline.h"
#include "trackviewitem.h"
#include "trackviewsplitter.h"
@@ -39,12 +38,15 @@ public:
TrackView(Qt::Alignment vertical_alignment = Qt::AlignTop,
QWidget *parent = nullptr);
void connect_track_list(TrackList *list);
/// Bind to the track list of `sequence` for `track_type`
/// (OAKENGINE_TRACK_TYPE_*). The view holds the (sequence, type) pair
/// and resolves tracks through the C ABI (no engine track-list object
/// crosses the boundary).
void connect_track_list(OakEngineSequence *sequence, int track_type);
void disconnect_track_list();
void insert_track(Track *track);
void remove_track(Track *track);
void insert_track(OakEngineTrack *track);
void remove_track(OakEngineTrack *track);
signals:
void about_to_delete_track(OakEngineTrack *track);
@@ -52,7 +54,9 @@ protected:
virtual void resizeEvent(QResizeEvent *e) override;
private:
TrackList *list_;
OakEngineSequence *sequence_;
int track_type_;
TrackViewSplitter *splitter_;
@@ -28,16 +28,28 @@
#include <QPainter>
#include <QtMath>
#include "node/project/sequence/sequence.h"
#include "oakengine/node.h"
#include "oakengine/timeline.h"
#include "ui/icons/icons.h"
#include "widget/menu/menu.h"
#include "widget/timelinewidget/trackhandle.h"
namespace olive
{
TrackViewItem::TrackViewItem(Track *track, QWidget *parent)
namespace
{
// The sequence that owns `track`, through the C ABI.
OakEngineSequence *track_owner_sequence(OakEngineTrack *track)
{
return reinterpret_cast<OakEngineSequence *>(oakengine_track_get_sequence(
reinterpret_cast<OakEngineNode *>(track)));
}
} // namespace
TrackViewItem::TrackViewItem(OakEngineTrack *track, QWidget *parent)
: QWidget(parent)
, track_(track)
{
@@ -51,8 +63,7 @@ TrackViewItem::TrackViewItem(Track *track, QWidget *parent)
label_ = new ClickableLabel();
connect(label_, &ClickableLabel::mouse_double_clicked, this,
&TrackViewItem::label_clicked);
bridge_.subscribe(reinterpret_cast<OakEngineTrack *>(track_),
OAKENGINE_EVENT_TRACK_INDEX_CHANGED);
bridge_.subscribe(track_, OAKENGINE_EVENT_TRACK_INDEX_CHANGED);
connect(&bridge_, &EngineEventBridge::track_index_changed, this,
&TrackViewItem::update_label);
update_label();
@@ -67,15 +78,15 @@ TrackViewItem::TrackViewItem(Track *track, QWidget *parent)
mute_button_ = create_msl_button(Qt::red);
mute_button_->setChecked(oakengine_track_is_muted(
reinterpret_cast<OakEngineSequence *>(track->sequence()),
track->type(), track->index()));
track_owner_sequence(track),
track_type_of(track), track_index_of(track)));
update_mute_button(oakengine_track_is_muted(
reinterpret_cast<OakEngineSequence *>(track->sequence()),
track->type(), track->index()));
track_owner_sequence(track),
track_type_of(track), track_index_of(track)));
connect(mute_button_, &QPushButton::toggled, this, [this](bool checked) {
oakengine_track_set_muted(
reinterpret_cast<OakEngineSequence *>(track_->sequence()),
track_->type(), track_->index(), checked);
track_owner_sequence(track_),
track_type_of(track_), track_index_of(track_), checked);
});
connect(mute_button_, &QPushButton::toggled, this,
&TrackViewItem::update_mute_button);
@@ -86,15 +97,15 @@ TrackViewItem::TrackViewItem(Track *track, QWidget *parent)
lock_button_ = create_msl_button(Qt::gray);
lock_button_->setChecked(oakengine_track_is_locked(
reinterpret_cast<OakEngineSequence *>(track->sequence()),
track->type(), track->index()));
track_owner_sequence(track),
track_type_of(track), track_index_of(track)));
update_lock_button(oakengine_track_is_locked(
reinterpret_cast<OakEngineSequence *>(track->sequence()),
track->type(), track->index()));
track_owner_sequence(track),
track_type_of(track), track_index_of(track)));
connect(lock_button_, &QPushButton::toggled, this, [this](bool checked) {
oakengine_track_set_locked(
reinterpret_cast<OakEngineSequence *>(track_->sequence()),
track_->type(), track_->index(), checked);
track_owner_sequence(track_),
track_type_of(track_), track_index_of(track_), checked);
});
connect(lock_button_, &QPushButton::toggled, this,
&TrackViewItem::update_lock_button);
@@ -103,8 +114,7 @@ TrackViewItem::TrackViewItem(Track *track, QWidget *parent)
setMinimumHeight(mute_button_->height());
setContextMenuPolicy(Qt::CustomContextMenu);
bridge_.subscribe(reinterpret_cast<OakEngineTrack *>(track),
OAKENGINE_EVENT_TRACK_MUTED_CHANGED);
bridge_.subscribe(track, OAKENGINE_EVENT_TRACK_MUTED_CHANGED);
connect(&bridge_, &EngineEventBridge::track_muted_changed, mute_button_,
[this](OakEngineTrack *, bool muted) {
mute_button_->setChecked(muted);
@@ -165,15 +175,15 @@ void TrackViewItem::update_label()
// type+number prefix (V1/V2 for video, A1/A2 for audio, S1 for subtitle)
// followed by the track's custom label or default name.
QString prefix;
switch (track_->type()) {
case Track::k_video:
prefix = QStringLiteral("V%1").arg(track_->index() + 1);
switch (track_type_of(track_)) {
case OAKENGINE_TRACK_TYPE_VIDEO:
prefix = QStringLiteral("V%1").arg(track_index_of(track_) + 1);
break;
case Track::k_audio:
prefix = QStringLiteral("A%1").arg(track_->index() + 1);
case OAKENGINE_TRACK_TYPE_AUDIO:
prefix = QStringLiteral("A%1").arg(track_index_of(track_) + 1);
break;
case Track::k_subtitle:
prefix = QStringLiteral("S%1").arg(track_->index() + 1);
case OAKENGINE_TRACK_TYPE_SUBTITLE:
prefix = QStringLiteral("S%1").arg(track_index_of(track_) + 1);
break;
}
@@ -214,28 +224,48 @@ void TrackViewItem::show_context_menu(const QPoint &p)
void TrackViewItem::delete_track()
{
emit about_to_delete_track(reinterpret_cast<OakEngineTrack *>(track_));
emit about_to_delete_track(track_);
// 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());
track_owner_sequence(track_),
int(track_type_of(track_)), track_index_of(track_));
}
void TrackViewItem::delete_all_empty_tracks()
{
Sequence *sequence = track_->sequence();
QVector<Track *> tracks_to_remove;
OakEngineSequence *sequence = track_owner_sequence(track_);
QStringList track_names_to_remove;
foreach (Track *t, sequence->get_tracks()) {
if (t->blocks().isEmpty()) {
tracks_to_remove.append(t);
track_names_to_remove.append(t->get_label_or_name());
// Iterate all track lists (video, audio, subtitle), mirroring
// Sequence::get_tracks(); the per-type counts line up with the
// OAKENGINE_TRACK_TYPE_* ordinals (0..2).
int track_counts[3] = { 0, 0, 0 };
oakengine_sequence_track_count(sequence, &track_counts[0],
&track_counts[1], &track_counts[2]);
for (int type = 0; type < 3; type++) {
for (int ti = 0; ti < track_counts[type]; ti++) {
OakEngineTrack *t = oakengine_sequence_track_at(sequence, type, ti);
if (t && oakengine_track_block_count(t) == 0) {
// WRAPPER-GAP: oakengine_node_get_label_or_name -- emulate
// inline (the label, falling back to the name).
char buf[256];
buf[0] = '\0';
oakengine_node_get_label(
reinterpret_cast<OakEngineNode *>(t), buf, sizeof(buf));
QString name = QString::fromUtf8(buf);
if (name.isEmpty()) {
oakengine_node_get_name(
reinterpret_cast<OakEngineNode *>(t), buf,
sizeof(buf));
name = QString::fromUtf8(buf);
}
track_names_to_remove.append(name);
}
}
}
if (tracks_to_remove.isEmpty()) {
if (track_names_to_remove.isEmpty()) {
QMessageBox::information(this, tr("Delete All Empty"),
tr("No tracks are currently empty"));
} else {
@@ -246,8 +276,7 @@ void TrackViewItem::delete_all_empty_tracks()
QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
// Batch removal through the liboakengine C ABI facade (one
// undoable command, same as the old per-track children).
oakengine_sequence_delete_empty_tracks(
reinterpret_cast<OakEngineSequence *>(sequence), -1);
oakengine_sequence_delete_empty_tracks(sequence, -1);
}
}
}
@@ -27,7 +27,6 @@
#include <QWidget>
#include "engineeventbridge.h"
#include "node/output/track/track.h"
#include "oakengine/timeline.h"
#include "widget/clickablelabel/clickablelabel.h"
#include "widget/focusablelineedit/focusablelineedit.h"
@@ -39,7 +38,7 @@ namespace olive
class TrackViewItem : public QWidget {
Q_OBJECT
public:
TrackViewItem(Track *track, QWidget *parent = nullptr);
TrackViewItem(OakEngineTrack *track, QWidget *parent = nullptr);
signals:
void about_to_delete_track(OakEngineTrack *track);
@@ -56,7 +55,7 @@ private:
QPushButton *solo_button_;
QPushButton *lock_button_;
Track *track_;
OakEngineTrack *track_;
EngineEventBridge bridge_;
@@ -24,7 +24,6 @@
#include <QDebug>
#include <QPainter>
#include "node/output/track/track.h"
#include "oakengine/timeline.h"
namespace olive
+393 -152
View File
@@ -21,6 +21,7 @@
#include "timelineview.h"
#include <QByteArray>
#include <QDebug>
#include <QMimeData>
#include <QMouseEvent>
@@ -31,13 +32,14 @@
#include "common/configwrapper.h"
#include "oakutil/qtutils.h"
#include "../../timeruler/markerpainting.h"
#include "node/project/footage/footage.h"
#include "oakengine/preview.h"
#include "oakengine/timeline.h"
#include "oakengine/viewer.h"
#include "panel/panelmanager.h"
#include "panel/timeline/timeline.h"
#include "widget/timelinewidget/cliphandle.h"
#include "ui/colorcoding.h"
#include "widget/timelinewidget/trackhandle.h"
#include "common/colorcodingapp.h"
#include "widget/timelinewidget/timelinewidget.h"
#include "widget/viewer/vieweroutpututils.h"
@@ -46,12 +48,203 @@ namespace olive
#define super TimeBasedView
namespace
{
/// Number of tracks of `type` in `seq` (TrackList::get_track_count()).
int track_count_of(OakEngineSequence *seq, int type)
{
if (!seq || type < 0 || type >= 3) {
return 0;
}
int counts[3] = { 0, 0, 0 };
oakengine_sequence_track_count(seq, &counts[0], &counts[1], &counts[2]);
return counts[type];
}
/// Borrowed track at (type, index) as an opaque track handle.
OakEngineTrack *track_at(OakEngineSequence *seq, int type, int index)
{
return oakengine_sequence_track_at(seq, type, index);
}
/// All tracks of `type` through the C ABI (count + indexed access).
QVector<OakEngineTrack *> track_list_tracks(OakEngineSequence *seq, int type)
{
QVector<OakEngineTrack *> tracks;
const int n = track_count_of(seq, type);
tracks.reserve(n);
for (int i = 0; i < n; i++) {
if (OakEngineTrack *t = track_at(seq, type, i)) {
tracks.append(t);
}
}
return tracks;
}
/// Track::get_track_height_in_pixels() through the C ABI.
int track_height_in_pixels(OakEngineSequence *seq, int type, int index)
{
double h = 0;
if (!seq ||
oakengine_track_get_height(seq, type, index, &h) != OAKENGINE_OK) {
return oakengine_track_default_height_in_pixels();
}
return oakengine_track_height_internal_to_pixels(h);
}
/// The track's blocks through the C ABI (count + indexed access).
QVector<OakEngineBlock *> track_all_blocks(OakEngineTrack *track)
{
QVector<OakEngineBlock *> blocks;
OakEngineTrack *h = trackhandle(track);
const int n = oakengine_track_block_count(h);
blocks.reserve(n);
for (int i = 0; i < n; i++) {
if (OakEngineBlock *b = oakengine_track_block_at(h, i)) {
blocks.append(b);
}
}
return blocks;
}
/// Clip-style predicate for block handles (replaces a dynamic_cast to the
/// engine clip class now that its definition is no longer visible here).
OakEngineBlock *block_as_clip(OakEngineBlock *block)
{
return (block &&
oakengine_node_is_clip(reinterpret_cast<OakEngineNode *>(block)))
? block
: nullptr;
}
/// The block's in-point as rational seconds.
Rational block_time_in(OakEngineBlock *block)
{
int num = 0, den = 1;
oakengine_block_get_in_rational(
reinterpret_cast<const OakEngineNode *>(block), &num, &den);
return Rational(num, den);
}
/// The block's out-point as rational seconds.
Rational block_time_out(OakEngineBlock *block)
{
int num = 0, den = 1;
oakengine_block_get_out_rational(
reinterpret_cast<const OakEngineNode *>(block), &num, &den);
return Rational(num, den);
}
/// The block's length as rational seconds.
Rational block_length(OakEngineBlock *block)
{
int num = 0, den = 1;
oakengine_block_get_length_rational(
reinterpret_cast<const OakEngineNode *>(block), &num, &den);
return Rational(num, den);
}
/// Next block on the track (borrowed handle, may be null).
OakEngineBlock *block_next(OakEngineBlock *block)
{
return oakengine_block_next(block);
}
/// The block's enabled flag through the C ABI.
bool block_is_enabled(OakEngineBlock *block)
{
return oakengine_block_is_enabled(block) != 0;
}
/// The block's effective color through the C ABI (color label -> app
/// ColorCoding).
core::Color block_color(OakEngineBlock *block)
{
return AppColorCoding::get_color(oakengine_node_get_effective_color_label(
reinterpret_cast<const OakEngineNode *>(block)));
}
/// Brush for a block, app-side (gradient or flat, mirroring the engine
/// version).
QBrush block_brush(OakEngineBlock *block, qreal top, qreal bottom)
{
const QColor c = QtUtils::to_q_color(block_color(block));
if (OAK_CONFIG("UseGradients").toBool()) {
QLinearGradient grad;
grad.setStart(0, top);
grad.setFinalStop(0, bottom);
grad.setColorAt(0.0, c.lighter());
grad.setColorAt(1.0, c);
return grad;
}
return c;
}
/// The block's link presence through the C ABI.
bool block_has_links(OakEngineBlock *block)
{
return oakengine_block_link_count(block) > 0;
}
/// The block's label-or-name through the C ABI.
QString block_label_or_name(OakEngineBlock *block)
{
char buf[1024];
buf[0] = '\0';
oakengine_node_get_label_and_name(
reinterpret_cast<const OakEngineNode *>(block), buf, sizeof(buf));
return QString::fromUtf8(buf);
}
/// The clip's connected viewer as an OakEngineNode handle (borrowed).
OakEngineNode *clip_connected_viewer(OakEngineBlock *clip)
{
return oakengine_clip_get_connected_viewer(clip);
}
/// The clip's track type through the C ABI (track of the clip).
int clip_track_type(OakEngineBlock *clip)
{
return oakengine_track_type(reinterpret_cast<OakEngineTrack *>(
oakengine_clip_get_track(reinterpret_cast<OakEngineNode *>(clip))));
}
/// The viewer's length as rational seconds.
Rational viewer_length(OakEngineNode *viewer)
{
int64_t num = 0, den = 1;
oakengine_viewer_get_length(viewer, &num, &den);
return Rational(int(num), int(den));
}
/// Marker time range as rational seconds (oakengine_marker_get_time).
TimeRange marker_time_range(const OakEngineMarker *marker)
{
int64_t in_num = 0, in_den = 1, out_num = 0, out_den = 1;
oakengine_marker_get_time(marker, &in_num, &in_den, &out_num, &out_den);
return TimeRange(Rational(int(in_num), int(in_den)),
Rational(int(out_num), int(out_den)));
}
/// Marker name through the C ABI.
QString marker_name_of(const OakEngineMarker *marker)
{
const int size = oakengine_marker_get_name(marker, nullptr, 0);
QByteArray buf(size + 1, '\0');
oakengine_marker_get_name(marker, buf.data(), int(buf.size()));
return QString::fromUtf8(buf.constData());
}
} // namespace
TimelineView::TimelineView(Qt::Alignment vertical_alignment, QWidget *parent)
: super(parent)
, selections_(nullptr)
, ghosts_(nullptr)
, show_beam_cursor_(false)
, connected_track_list_(nullptr)
, connected_sequence_(nullptr)
, connected_track_type_(-1)
, transition_overlay_out_(nullptr)
, transition_overlay_in_(nullptr)
{
@@ -74,10 +267,10 @@ void TimelineView::mousePressEvent(QMouseEvent *event)
for (auto it = clip_marker_rects_.cbegin(); it != clip_marker_rects_.cend();
it++) {
if (it.value().contains(scene_pos)) {
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(get_viewer_node()),
it.key()->time().in().numerator(),
it.key()->time().in().denominator());
const Rational marker_in = marker_time_range(it.key()).in();
oakengine_viewer_set_playhead(get_viewer_node(),
marker_in.numerator(),
marker_in.denominator());
break;
}
}
@@ -116,19 +309,19 @@ void TimelineView::mouseMoveEvent(QMouseEvent *event)
}
if (event->buttons() == Qt::NoButton) {
Block *b = get_item_at_scene_pos(timeline_event.get_frame(),
timeline_event.get_track().index());
OakEngineBlock *b = get_item_at_scene_pos(
timeline_event.get_frame(), timeline_event.get_track().index());
if (b) {
setToolTip(
tr("In: %1\nOut: %2\nDuration: %3")
.arg(QString::fromStdString(Timecode::time_to_timecode(
b->in(), timebase(),
block_time_in(b), timebase(),
Core::instance()->get_timecode_display())),
QString::fromStdString(Timecode::time_to_timecode(
b->out(), timebase(),
block_time_out(b), timebase(),
Core::instance()->get_timecode_display())),
QString::fromStdString(Timecode::time_to_timecode(
b->length(), timebase(),
block_length(b), timebase(),
Core::instance()->get_timecode_display()))));
} else {
setToolTip(QString());
@@ -202,7 +395,7 @@ void TimelineView::dropEvent(QDropEvent *event)
void TimelineView::drawBackground(QPainter *painter, const QRectF &rect)
{
if (!connected_track_list_) {
if (!connected_sequence_) {
return;
}
@@ -210,8 +403,11 @@ void TimelineView::drawBackground(QPainter *painter, const QRectF &rect)
int line_y = 0;
foreach (Track *track, connected_track_list_->get_tracks()) {
line_y += track->get_track_height_in_pixels();
const int track_count = track_count_of(connected_sequence_,
connected_track_type_);
for (int i = 0; i < track_count; i++) {
line_y += track_height_in_pixels(connected_sequence_,
connected_track_type_, i);
// One px gap between tracks
line_y++;
@@ -231,7 +427,7 @@ void TimelineView::drawBackground(QPainter *painter, const QRectF &rect)
void TimelineView::drawForeground(QPainter *painter, const QRectF &rect)
{
if (!connected_track_list_) {
if (!connected_sequence_) {
return;
}
@@ -244,7 +440,10 @@ void TimelineView::drawForeground(QPainter *painter, const QRectF &rect)
painter->setBrush(QColor(0, 0, 0, 64));
for (auto it = selections_->cbegin(); it != selections_->cend(); it++) {
if (it.key().type() == connected_track_list_->type()) {
// TrackReference mirror ordinals == engine Track::Type ordinals
// (static_assert-pinned in trackreferencehandle.h)
if (it.key().type() ==
static_cast<TrackReference::Type>(connected_track_type_)) {
int track_index = it.key().index();
foreach (const TimeRange &range, it.value()) {
@@ -263,11 +462,12 @@ void TimelineView::drawForeground(QPainter *painter, const QRectF &rect)
// Draw ghosts
if (ghosts_ && !ghosts_->isEmpty()) {
foreach (TimelineViewGhostItem *ghost, (*ghosts_)) {
if (ghost->get_track().type() == connected_track_list_->type() &&
if (ghost->get_track().type() ==
static_cast<TrackReference::Type>(connected_track_type_) &&
!ghost->is_invisible()) {
int track_index = ghost->get_adjusted_track().index();
Block *attached = QtUtils::value_to_ptr<Block>(
OakEngineBlock *attached = QtUtils::value_to_ptr<OakEngineBlock>(
ghost->get_data(TimelineViewGhostItem::k_attached_block));
if (attached &&
@@ -303,7 +503,8 @@ void TimelineView::drawForeground(QPainter *painter, const QRectF &rect)
// Draw beam cursor
if (show_beam_cursor_ &&
cursor_coord_.get_track().type() == connected_track_list_->type()) {
cursor_coord_.get_track().type() ==
static_cast<TrackReference::Type>(connected_track_type_)) {
painter->setPen(Qt::gray);
double cursor_x = time_to_scene(cursor_coord_.get_frame());
@@ -316,13 +517,17 @@ void TimelineView::drawForeground(QPainter *painter, const QRectF &rect)
// Draw recording overlay
if (recording_overlay_ &&
recording_coord_.get_track().type() == connected_track_list_->type()) {
recording_coord_.get_track().type() ==
static_cast<TrackReference::Type>(connected_track_type_)) {
painter->setPen(QPen(Qt::red, 2));
painter->setBrush(QColor(255, 128, 128));
int x = time_to_scene(recording_coord_.get_frame());
int64_t ph_num = 0, ph_den = 1;
oakengine_viewer_get_playhead(get_viewer_node(), &ph_num,
&ph_den);
painter->drawRect(x, get_track_y(recording_coord_.get_track().index()),
time_to_scene(get_viewer_node()->get_playhead()) - x,
time_to_scene(Rational(int(ph_num), int(ph_den))) - x,
get_track_height(recording_coord_.get_track().index()));
}
@@ -370,13 +575,9 @@ void TimelineView::SceneRectUpdateEvent(QRectF &rect)
}
}
Track::Type TimelineView::connected_track_type()
TrackReference::Type TimelineView::connected_track_type() const
{
if (connected_track_list_) {
return connected_track_list_->type();
}
return Track::k_none;
return static_cast<TrackReference::Type>(connected_track_type_);
}
TimelineCoordinate TimelineView::screen_to_coordinate(const QPoint &pt)
@@ -387,8 +588,8 @@ TimelineCoordinate TimelineView::screen_to_coordinate(const QPoint &pt)
TimelineCoordinate TimelineView::scene_to_coordinate(const QPointF &pt)
{
return TimelineCoordinate(scene_to_time(pt.x()),
Track::Reference(connected_track_type(),
scene_to_track(pt.y())));
TrackReference(connected_track_type(),
scene_to_track(pt.y())));
}
TimelineViewMouseEvent TimelineView::CreateMouseEvent(QMouseEvent *event)
@@ -403,8 +604,8 @@ TimelineView::CreateMouseEvent(const QPoint &pos, Qt::MouseButton button,
QPointF scene_pt = mapToScene(pos);
return TimelineViewMouseEvent(scene_pt, pos, get_scale(), timebase(),
Track::Reference(connected_track_type(),
scene_to_track(scene_pt.y())),
TrackReference(connected_track_type(),
scene_to_track(scene_pt.y())),
button, modifiers);
}
@@ -412,50 +613,56 @@ void TimelineView::draw_blocks(QPainter *painter, bool foreground)
{
Rational start_time = scene_to_time(get_timeline_left_bound());
Rational end_time = scene_to_time(get_timeline_right_bound());
const int64_t start_ts = core::Timecode::time_to_timestamp(
start_time, sequence_timebase(connected_sequence_));
foreach (Track *track, connected_track_list_->get_tracks()) {
foreach (OakEngineTrack *track,
track_list_tracks(connected_sequence_, connected_track_type_)) {
// Get first visible block in this track
Block *block = track->nearest_block_before_or_at(start_time);
OakEngineBlock *block = oakengine_track_nearest_block_before_or_at(
trackhandle(track), start_ts);
qreal track_top = get_track_y(track->index());
qreal track_height = get_track_height(track->index());
const int track_index = track_index_of(track);
qreal track_top = get_track_y(track_index);
qreal track_height = get_track_height(track_index);
while (block) {
draw_block(painter, foreground, block, track_top, track_height);
if (block->out() >= end_time) {
if (block_time_out(block) >= end_time) {
// Rest of the clips are offscreen, can break loop now
break;
}
block = block->next();
block = block_next(block);
}
}
}
void TimelineView::draw_block(QPainter *painter, bool foreground, Block *block,
qreal block_top, qreal block_height)
void TimelineView::draw_block(QPainter *painter, bool foreground,
OakEngineBlock *block, qreal block_top,
qreal block_height)
{
Rational media_in = 0;
if (ClipBlock *cb = dynamic_cast<ClipBlock *>(block)) {
if (OakEngineBlock *cb = block_as_clip(block)) {
int64_t in_num, in_den;
if (oakengine_clip_get_media_range_rational(
reinterpret_cast<OakEngineClip *>(cb), &in_num, &in_den,
cliphandle(cb), &in_num, &in_den,
nullptr, nullptr) == OAKENGINE_OK) {
media_in = Rational(in_num, in_den);
}
}
draw_block(painter, foreground, block, block_top, block_height,
block->in(), block->out(), media_in);
block_time_in(block), block_time_out(block), media_in);
}
void TimelineView::draw_block(QPainter *painter, bool foreground, Block *block,
qreal block_top, qreal block_height,
const Rational &in, const Rational &out,
const Rational &media_in)
void TimelineView::draw_block(QPainter *painter, bool foreground,
OakEngineBlock *block, qreal block_top,
qreal block_height, const Rational &in,
const Rational &out, const Rational &media_in)
{
if (dynamic_cast<ClipBlock *>(block) ||
dynamic_cast<TransitionBlock *>(block)) {
if (block_as_clip(block) ||
oakengine_node_is_transition(reinterpret_cast<OakEngineNode *>(block))) {
qreal block_in = time_to_scene(in);
qreal block_left = qMax(get_timeline_left_bound(), block_in);
@@ -463,8 +670,8 @@ void TimelineView::draw_block(QPainter *painter, bool foreground, Block *block,
QRectF r(block_left, block_top, block_right - block_left, block_height);
QColor shadow_color = block->is_enabled() ?
QtUtils::to_q_color(block->color()).darker() :
QColor shadow_color = block_is_enabled(block) ?
QtUtils::to_q_color(block_color(block)).darker() :
QColor(Qt::darkGray).darker();
const qreal minimum_rect_width = 2;
@@ -490,18 +697,18 @@ void TimelineView::draw_block(QPainter *painter, bool foreground, Block *block,
painter->setBrush(Qt::NoBrush);
if (r.width() > minimum_detail_width) {
QString using_label = block->get_label_or_name();
QString using_label = block_label_or_name(block);
QRectF text_rect = r.adjusted(text_padding, text_padding,
-text_padding, -text_padding);
painter->setPen(
block->is_enabled() ?
ColorCoding::get_ui_selector_color(block->color()) :
block_is_enabled(block) ?
AppColorCoding::get_ui_selector_color(block_color(block)) :
Qt::lightGray);
painter->drawText(text_rect, Qt::AlignLeft | Qt::AlignTop,
using_label);
if (block->has_links()) {
if (block_has_links(block)) {
int text_width =
qMin(qRound(text_rect.width()),
QtUtils::q_font_metrics_width(fm, using_label));
@@ -530,19 +737,19 @@ void TimelineView::draw_block(QPainter *painter, bool foreground, Block *block,
} else {
painter->setPen(Qt::NoPen);
painter->setBrush(
block->is_enabled() ?
block->brush(block_top, block_top + block_height) :
block_is_enabled(block) ?
block_brush(block, block_top, block_top + block_height) :
Qt::gray);
painter->drawRect(r);
if (r.width() > minimum_detail_width) {
if (ClipBlock *clip = dynamic_cast<ClipBlock *>(block)) {
if (OakEngineBlock *clip = block_as_clip(block)) {
QRect preview_rect = r.toRect();
// Draw clip thumbnails
if (clip->get_track_type() == Track::k_video &&
if (clip_track_type(clip) == TrackReference::k_video &&
OAK_CONFIG("TimelineThumbnailMode").toInt() !=
Timeline::k_thumbnail_off) {
TimelineApp::k_thumbnail_off) {
// Start thumbnails underneath clip name
preview_rect.adjust(0, text_total_height, 0, 0);
@@ -555,8 +762,10 @@ void TimelineView::draw_block(QPainter *painter, bool foreground, Block *block,
painter->setClipRect(preview_rect);
if (OAK_CONFIG("TimelineThumbnailMode") ==
Timeline::k_thumbnail_on) {
Sequence *s = clip->track()->sequence();
TimelineApp::k_thumbnail_on) {
OakEngineNode *s = oakengine_track_get_sequence(
oakengine_clip_get_track(
reinterpret_cast<OakEngineNode *>(clip)));
int width = viewer_output_video_params(s).width();
int height =
viewer_output_video_params(s).height();
@@ -583,7 +792,7 @@ void TimelineView::draw_block(QPainter *painter, bool foreground, Block *block,
scene_to_time(
i - block_in, get_scale(),
viewer_output_video_params(
connected_track_list_->parent())
connected_sequence_)
.frame_rate_as_time_base()) +
media_in;
draw_thumbnail(painter, thumbs,
@@ -609,16 +818,16 @@ void TimelineView::draw_block(QPainter *painter, bool foreground, Block *block,
}
// Draw waveform
if (clip->get_track_type() == Track::k_audio &&
if (clip_track_type(clip) == TrackReference::k_audio &&
OAK_CONFIG("TimelineWaveformMode").toInt() ==
Timeline::k_waveforms_enabled) {
TimelineApp::k_waveforms_enabled) {
if (const AudioWaveformCache *wave =
clip_waveform(clip)) {
Rational waveform_start =
scene_to_time(
block_left - block_in, get_scale(),
viewer_output_audio_params(
connected_track_list_->parent())
connected_sequence_)
.sample_rate_as_time_base()) +
media_in;
painter->setPen(shadow_color);
@@ -629,16 +838,21 @@ void TimelineView::draw_block(QPainter *painter, bool foreground, Block *block,
}
// Draw zebra stripes and markers
if (clip->connected_viewer()) {
if (!clip->connected_viewer()->get_length().isNull()) {
OakEngineNode *connected_viewer =
clip_connected_viewer(clip);
if (connected_viewer) {
const Rational connected_length =
viewer_length(connected_viewer);
if (!connected_length.isNull()) {
painter->setPen(shadow_color);
if (clip_media_in(clip) < 0) {
qreal zebra_right = time_to_scene(
clip->in() - clip_media_in(clip));
block_time_in(block) -
clip_media_in(clip));
switch (static_cast<LoopMode>(clip_loop_mode(clip))) {
case LoopMode::k_loop_mode_off:
switch (clip_loop_mode(clip)) {
case OAKENGINE_LOOP_MODE_OFF:
// Draw stripes for sections of clip < 0
if (zebra_right >
get_timeline_left_bound()) {
@@ -649,18 +863,17 @@ void TimelineView::draw_block(QPainter *painter, bool foreground, Block *block,
block_height));
}
break;
case LoopMode::k_loop_mode_loop:
case OAKENGINE_LOOP_MODE_LOOP:
for (qreal i = zebra_right;
i > block_left;
i -= time_to_scene(
clip->connected_viewer()
->get_length())) {
connected_length)) {
painter->drawLine(i, block_top, i,
block_top +
block_height);
}
break;
case LoopMode::k_loop_mode_clamp:
case OAKENGINE_LOOP_MODE_CLAMP:
painter->drawLine(
zebra_right, block_top, zebra_right,
block_top + block_height);
@@ -668,14 +881,15 @@ void TimelineView::draw_block(QPainter *painter, bool foreground, Block *block,
}
}
if (clip->length() + clip_media_in(clip) >
clip->connected_viewer()->get_length()) {
if (block_length(block) + clip_media_in(clip) >
connected_length) {
qreal zebra_left = time_to_scene(
clip->out() -
(clip_media_in(clip) + clip->length() -
clip->connected_viewer()->get_length()));
switch (static_cast<LoopMode>(clip_loop_mode(clip))) {
case LoopMode::k_loop_mode_off:
block_time_out(block) -
(clip_media_in(clip) +
block_length(block) -
connected_length));
switch (clip_loop_mode(clip)) {
case OAKENGINE_LOOP_MODE_OFF:
// Draw stripes for sections for clip > clip length
if (zebra_left <
get_timeline_right_bound()) {
@@ -686,18 +900,17 @@ void TimelineView::draw_block(QPainter *painter, bool foreground, Block *block,
block_height));
}
break;
case LoopMode::k_loop_mode_loop:
case OAKENGINE_LOOP_MODE_LOOP:
for (qreal i = zebra_left;
i < block_right;
i += time_to_scene(
clip->connected_viewer()
->get_length())) {
connected_length)) {
painter->drawLine(i, block_top, i,
block_top +
block_height);
}
break;
case LoopMode::k_loop_mode_clamp:
case OAKENGINE_LOOP_MODE_CLAMP:
painter->drawLine(
zebra_left, block_top, zebra_left,
block_top + block_height);
@@ -706,32 +919,42 @@ void TimelineView::draw_block(QPainter *painter, bool foreground, Block *block,
}
}
TimelineMarkerList *marker_list =
clip->connected_viewer()->get_markers();
if (!marker_list->empty()) {
OakEngineMarkerList *marker_list =
oakengine_viewer_get_marker_list(
connected_viewer);
const int marker_count =
oakengine_marker_list_count(marker_list);
if (marker_count > 0) {
clip_marker_rects_.clear();
for (auto it = marker_list->cbegin();
it != marker_list->cend(); it++) {
TimelineMarker *marker = *it;
for (int mi = 0; mi < marker_count; mi++) {
OakEngineMarker *marker =
oakengine_marker_list_at(marker_list,
mi);
const TimeRange marker_range =
marker_time_range(marker);
// Make sure marker is within In/Out points of the clip
if (marker->time().in() >=
if (marker_range.in() >=
clip_media_in(clip) &&
marker->time().out() <=
clip_media_in(clip) + clip->length()) {
marker_range.out() <=
clip_media_in(clip) +
block_length(block)) {
QPoint marker_pt(
time_to_scene(clip->in() -
clip_media_in(clip) +
marker->time().in()),
time_to_scene(
block_time_in(block) -
clip_media_in(clip) +
marker_range.in()),
block_top + block_height);
painter->setClipRect(r);
QRect marker_rect =
MarkerPainting::draw(
painter, marker_pt, -1,
get_scale(), false,
marker->name(), marker->color(),
marker->time().in(),
marker->time().out());
marker_name_of(marker),
oakengine_marker_get_color(
marker),
marker_range.in(),
marker_range.out());
clip_marker_rects_.insert(marker,
marker_rect);
painter->setClipping(false);
@@ -757,15 +980,16 @@ void TimelineView::draw_block(QPainter *painter, bool foreground, Block *block,
}
// For transitions, show lines representing a transition
if (TransitionBlock *transition =
dynamic_cast<TransitionBlock *>(block)) {
if (oakengine_node_is_transition(
reinterpret_cast<OakEngineNode *>(block))) {
QVector<QLineF> lines;
OakEngineBlock *tb = block;
if (transition->connected_in_block()) {
if (oakengine_transition_connected_in_block(tb)) {
lines.append(QLineF(r.bottomLeft(), r.topRight()));
}
if (transition->connected_out_block()) {
if (oakengine_transition_connected_out_block(tb)) {
lines.append(QLineF(r.topLeft(), r.bottomRight()));
}
@@ -773,27 +997,26 @@ void TimelineView::draw_block(QPainter *painter, bool foreground, Block *block,
painter->drawLines(lines);
}
if (transition_overlay_out_ == block ||
transition_overlay_in_ == block) {
OakEngineBlock *overlay_out = transition_overlay_out_;
OakEngineBlock *overlay_in = transition_overlay_in_;
if (overlay_out == block || overlay_in == block) {
QRectF transition_overlay_rect = r;
qreal transition_overlay_width =
time_to_scene(block->length()) * 0.5;
if (transition_overlay_out_ && transition_overlay_in_) {
time_to_scene(block_length(block)) * 0.5;
if (overlay_out && overlay_in) {
// This is a dual transition, use the smallest width
Block *other_block =
(transition_overlay_out_ == block) ?
transition_overlay_in_ :
transition_overlay_out_;
OakEngineBlock *other_block =
(overlay_out == block) ? overlay_in : overlay_out;
qreal other_width =
time_to_scene(other_block->length()) * 0.5;
time_to_scene(block_length(other_block)) * 0.5;
transition_overlay_width =
qMin(transition_overlay_width, other_width);
}
if (transition_overlay_out_ == block) {
if (overlay_out == block) {
transition_overlay_rect.setLeft(
transition_overlay_rect.right() -
transition_overlay_width);
@@ -838,11 +1061,13 @@ void TimelineView::draw_zebra_stripes(QPainter *painter, const QRectF &r)
int TimelineView::get_height_of_all_tracks() const
{
if (connected_track_list_) {
if (connected_sequence_) {
const int count =
track_count_of(connected_sequence_, connected_track_type_);
if (alignment() & Qt::AlignTop) {
return get_track_y(connected_track_list_->get_track_count());
return get_track_y(count);
} else {
return get_track_y(connected_track_list_->get_track_count() - 1);
return get_track_y(count - 1);
}
} else {
return 0;
@@ -880,7 +1105,8 @@ void TimelineView::draw_thumbnail(QPainter *painter,
int TimelineView::get_track_y(int track_index) const
{
if (!connected_track_list_ || !connected_track_list_->get_track_count()) {
if (!connected_sequence_ ||
!track_count_of(connected_sequence_, connected_track_type_)) {
return 0;
}
@@ -906,26 +1132,27 @@ int TimelineView::get_track_y(int track_index) const
int TimelineView::get_track_height(int track_index) const
{
if (!connected_track_list_ || connected_track_list_->get_track_count() == 0) {
const int count = track_count_of(connected_sequence_, connected_track_type_);
if (!connected_sequence_ || count == 0) {
// Handle null or empty track list
return oakengine_track_default_height_in_pixels();
}
if (track_index >= connected_track_list_->get_track_count()) {
if (track_index >= count) {
// Handle new track at the end of the list
return connected_track_list_
->get_track_at(connected_track_list_->get_track_count() - 1)
->get_track_height_in_pixels();
return track_height_in_pixels(connected_sequence_,
connected_track_type_, count - 1);
}
if (track_index < 0) {
// Handle new track at the beginning of the list
return connected_track_list_->get_track_at(0)->get_track_height_in_pixels();
return track_height_in_pixels(connected_sequence_,
connected_track_type_, 0);
}
// Track definitely exists, return its actual height
return connected_track_list_->get_track_at(track_index)
->get_track_height_in_pixels();
// The track definitely exists, return its actual height
return track_height_in_pixels(connected_sequence_, connected_track_type_,
track_index);
}
QPoint TimelineView::get_scroll_coordinates() const
@@ -939,20 +1166,24 @@ void TimelineView::set_scroll_coordinates(const QPoint &pt)
verticalScrollBar()->setValue(pt.y());
}
void TimelineView::connect_track_list(TrackList *list)
void TimelineView::connect_track_list(OakEngineSequence *sequence,
int track_type)
{
connected_track_list_ = list;
connected_sequence_ = sequence;
connected_track_type_ = track_type;
}
void TimelineView::set_beam_cursor(const TimelineCoordinate &coord)
{
if (!connected_track_list_) {
if (!connected_sequence_) {
return;
}
bool update_required =
coord.get_track().type() == connected_track_list_->type() ||
cursor_coord_.get_track().type() == connected_track_list_->type();
coord.get_track().type() ==
static_cast<TrackReference::Type>(connected_track_type_) ||
cursor_coord_.get_track().type() ==
static_cast<TrackReference::Type>(connected_track_type_);
show_beam_cursor_ = true;
cursor_coord_ = coord;
@@ -962,18 +1193,19 @@ void TimelineView::set_beam_cursor(const TimelineCoordinate &coord)
}
}
void TimelineView::set_transition_overlay(ClipBlock *out, ClipBlock *in)
void TimelineView::set_transition_overlay(OakEngineBlock *out,
OakEngineBlock *in)
{
if (transition_overlay_out_ != out || transition_overlay_in_ != in) {
Track::Type type = Track::k_none;
int type = -1;
if (out) {
type = out->track()->type();
type = clip_track_type(out);
} else if (in) {
type = in->track()->type();
type = clip_track_type(in);
}
if (type == this->connected_track_list_->type()) {
if (type == connected_track_type_) {
transition_overlay_out_ = out;
transition_overlay_in_ = in;
} else {
@@ -1015,15 +1247,16 @@ int TimelineView::scene_to_track(double y)
return track;
}
Block *TimelineView::get_item_at_scene_pos(const Rational &time,
int track_index) const
OakEngineBlock *TimelineView::get_item_at_scene_pos(const Rational &time,
int track_index) const
{
if (connected_track_list_) {
Track *track = connected_track_list_->get_track_at(track_index);
if (connected_sequence_) {
OakEngineTrack *track = track_at(connected_sequence_,
connected_track_type_, track_index);
if (track) {
foreach (Block *b, track->blocks()) {
if (b->in() <= time && b->out() > time) {
foreach (OakEngineBlock *b, track_all_blocks(track)) {
if (block_time_in(b) <= time && block_time_out(b) > time) {
return b;
}
}
@@ -1033,25 +1266,33 @@ Block *TimelineView::get_item_at_scene_pos(const Rational &time,
return nullptr;
}
QVector<Block *> TimelineView::get_items_at_scene_rect(const QRectF &rect) const
QVector<OakEngineBlock *> TimelineView::get_items_at_scene_rect(
const QRectF &rect) const
{
QVector<Block *> list;
QVector<OakEngineBlock *> list;
if (connected_track_list_) {
if (connected_sequence_) {
Rational start = this->scene_to_time(rect.left());
Rational end = this->scene_to_time(rect.right());
const int64_t start_ts = core::Timecode::time_to_timestamp(
start, sequence_timebase(connected_sequence_));
for (int i = 0; i < connected_track_list_->get_track_count(); i++) {
Track *track = connected_track_list_->get_track_at(i);
const int count =
track_count_of(connected_sequence_, connected_track_type_);
for (int i = 0; i < count; i++) {
OakEngineTrack *track = track_at(connected_sequence_,
connected_track_type_, i);
int track_top = get_track_y(i);
int track_bottom = track_top + get_track_height(i);
if (track) {
if (!(track_bottom < rect.top() || track_top > rect.bottom())) {
Block *b = track->nearest_block_before_or_at(start);
while (b && b->in() < end) {
OakEngineBlock *b =
oakengine_track_nearest_block_before_or_at(
trackhandle(track), start_ts);
while (b && block_time_in(b) < end) {
list.append(b);
b = b->next();
b = block_next(b);
}
}
}
+25 -18
View File
@@ -29,7 +29,6 @@
#include <QDropEvent>
#include "engineeventbridge.h"
#include "node/block/clip/clip.h"
#include "timelineviewmouseevent.h"
#include "timelineviewghostitem.h"
#include "widget/timebased/timebasedview.h"
@@ -37,10 +36,14 @@
namespace olive
{
// Engine cache type used here only as an opaque pointer (C ABI interop);
// the forward declaration replaces the old engine clip.h include.
class FrameHashCache;
/**
* @brief A widget for viewing and interacting Sequences
*
* This widget primarily exposes users to viewing and modifying Block nodes, usually through a TimelineOutput node.
* This widget primarily exposes users to viewing and modifying timeline blocks, usually through a timeline output node.
*/
class TimelineView : public TimeBasedView {
Q_OBJECT
@@ -54,16 +57,16 @@ public:
QPoint get_scroll_coordinates() const;
void set_scroll_coordinates(const QPoint &pt);
void connect_track_list(TrackList *list);
void connect_track_list(OakEngineSequence *sequence, int track_type);
void track_list_changed();
void set_beam_cursor(const TimelineCoordinate &coord);
void set_transition_overlay(ClipBlock *out, ClipBlock *in);
void set_transition_overlay(OakEngineBlock *out, OakEngineBlock *in);
void enable_recording_overlay(const TimelineCoordinate &coord);
void disable_recording_overlay();
void set_selection_list(QHash<Track::Reference, TimeRangeList> *s)
void set_selection_list(QHash<TrackReference, TimeRangeList> *s)
{
selections_ = s;
}
@@ -75,9 +78,10 @@ public:
int scene_to_track(double y);
Block *get_item_at_scene_pos(const Rational &time, int track_index) const;
OakEngineBlock *get_item_at_scene_pos(const Rational &time,
int track_index) const;
QVector<Block *> get_items_at_scene_rect(const QRectF &rect) const;
QVector<OakEngineBlock *> get_items_at_scene_rect(const QRectF &rect) const;
signals:
void mouse_pressed(TimelineViewMouseEvent *event);
@@ -109,7 +113,7 @@ protected:
virtual void SceneRectUpdateEvent(QRectF &rect) override;
private:
Track::Type connected_track_type();
TrackReference::Type connected_track_type() const;
TimelineCoordinate screen_to_coordinate(const QPoint &pt);
TimelineCoordinate scene_to_coordinate(const QPointF &pt);
@@ -121,11 +125,11 @@ private:
void draw_blocks(QPainter *painter, bool foreground);
void draw_block(QPainter *painter, bool foreground, Block *block, qreal top,
qreal height, const Rational &in, const Rational &out,
const Rational &media_in);
void draw_block(QPainter *painter, bool foreground, Block *block, qreal top,
qreal height);
void draw_block(QPainter *painter, bool foreground, OakEngineBlock *block,
qreal top, qreal height, const Rational &in,
const Rational &out, const Rational &media_in);
void draw_block(QPainter *painter, bool foreground, OakEngineBlock *block,
qreal top, qreal height);
void draw_zebra_stripes(QPainter *painter, const QRectF &r);
@@ -141,7 +145,7 @@ private:
const Rational &time, int x, const QRect &preview_rect,
QRect *thumb_rect) const;
QHash<Track::Reference, TimeRangeList> *selections_;
QHash<TrackReference, TimeRangeList> *selections_;
QVector<TimelineViewGhostItem *> *ghosts_;
@@ -149,12 +153,15 @@ private:
TimelineCoordinate cursor_coord_;
TrackList *connected_track_list_;
// Connected track list as (sequence, type); tracks are enumerated
// through the C ABI (same model as TrackView::connect_track_list).
OakEngineSequence *connected_sequence_;
int connected_track_type_;
ClipBlock *transition_overlay_out_;
ClipBlock *transition_overlay_in_;
OakEngineBlock *transition_overlay_out_;
OakEngineBlock *transition_overlay_in_;
QMap<TimelineMarker *, QRectF> clip_marker_rects_;
QMap<OakEngineMarker *, QRectF> clip_marker_rects_;
bool recording_overlay_;
TimelineCoordinate recording_coord_;
@@ -24,15 +24,37 @@
#include <QVariant>
#include "node/block/clip/clip.h"
#include "node/block/transition/transition.h"
#include "node/output/track/track.h"
#include "node/project/footage/footage.h"
#include "timeline/timelinecommon.h"
#include <olive/core/core.h>
#include "common/trackreferencehandle.h"
#include "oakengine/node.h"
#include "oakengine/timeline.h"
#include "oakutil/qtutils.h"
#include "timeline/timelinecommonapp.h"
#include "widget/timelinewidget/cliphandle.h"
namespace olive
{
using olive::core::Rational;
/**
* @brief TrackReference of the track owning `block`
* (the owning track as a reference facade). Type ordinals are the
* TrackReference mirror values, pinned to the engine track-type ordinals
* by the static_asserts in trackreferencehandle.h.
*/
inline TrackReference ghost_block_track_reference(OakEngineBlock *block)
{
OakEngineNode *track = block_track_handle(block);
if (!track) {
return TrackReference();
}
return TrackReference(
static_cast<TrackReference::Type>(oakengine_track_get_type(track)),
oakengine_track_get_index(track));
}
/**
* @brief A graphical representation of changes the user is making before they apply it
*/
@@ -48,34 +70,44 @@ public:
};
struct AttachedFootage {
ViewerOutput *footage;
OakEngineNode *footage;
QString output;
};
TimelineViewGhostItem()
: track_adj_(0)
, mode_(Timeline::k_none)
, mode_(TimelineApp::k_none)
, can_have_zero_length_(true)
, can_move_tracks_(true)
, invisible_(false)
{
}
static TimelineViewGhostItem *from_block(Block *block)
static TimelineViewGhostItem *from_block(OakEngineBlock *block)
{
TimelineViewGhostItem *ghost = new TimelineViewGhostItem();
ghost->set_in(block->in());
ghost->set_out(block->out());
if (dynamic_cast<ClipBlock *>(block)) {
ghost->set_media_in(clip_media_in(static_cast<ClipBlock *>(block)));
// All engine queries go through the C ABI: oakengine_node_is_clip()/
// oakengine_node_is_transition() replace dynamic_cast (the block
// classes are abstract and carry no own type id), the range comes
// from the block facade as rational seconds.
OakEngineNode *block_node = reinterpret_cast<OakEngineNode *>(block);
int in_num = 0, in_den = 1, out_num = 0, out_den = 1;
oakengine_block_get_in_rational(block_node, &in_num, &in_den);
oakengine_block_get_out_rational(block_node, &out_num, &out_den);
ghost->set_in(Rational(in_num, in_den));
ghost->set_out(Rational(out_num, out_den));
if (oakengine_node_is_clip(block_node)) {
ghost->set_media_in(clip_media_in(block));
}
ghost->set_track(block->track()->to_reference());
ghost->set_track(ghost_block_track_reference(block));
ghost->set_data(k_attached_block, QtUtils::ptr_to_value(block));
if (dynamic_cast<ClipBlock *>(block)) {
if (oakengine_node_is_clip(block_node)) {
ghost->can_have_zero_length_ = false;
} else if (dynamic_cast<TransitionBlock *>(block)) {
} else if (oakengine_node_is_transition(block_node)) {
ghost->can_have_zero_length_ = false;
}
@@ -192,17 +224,17 @@ public:
return media_in_ + media_in_adj_;
}
Track::Reference get_adjusted_track() const
TrackReference get_adjusted_track() const
{
return Track::Reference(track_.type(), track_.index() + track_adj_);
return TrackReference(track_.type(), track_.index() + track_adj_);
}
const Timeline::MovementMode &get_mode() const
const TimelineApp::MovementMode &get_mode() const
{
return mode_;
}
void set_mode(const Timeline::MovementMode &mode)
void set_mode(const TimelineApp::MovementMode &mode)
{
mode_ = mode;
}
@@ -223,12 +255,12 @@ public:
data_.insert(key, value);
}
const Track::Reference &get_track() const
const TrackReference &get_track() const
{
return track_;
}
void set_track(const Track::Reference &track)
void set_track(const TrackReference &track)
{
track_ = track;
}
@@ -255,12 +287,12 @@ private:
int track_adj_;
Timeline::MovementMode mode_;
TimelineApp::MovementMode mode_;
bool can_have_zero_length_;
bool can_move_tracks_;
Track::Reference track_;
TrackReference track_;
QHash<int, QVariant> data_;
@@ -38,7 +38,7 @@ public:
TimelineViewMouseEvent(
const QPointF &scene_pos, const QPoint &screen_pos,
const double &scale_x, const Rational &timebase,
const Track::Reference &track, const Qt::MouseButton &button,
const TrackReference &track, const Qt::MouseButton &button,
const Qt::KeyboardModifiers &modifiers = Qt::NoModifier)
: scene_pos_(scene_pos)
, screen_pos_(screen_pos)
@@ -78,7 +78,7 @@ public:
round);
}
const Track::Reference &get_track() const
const TrackReference &get_track() const
{
return track_;
}
@@ -144,7 +144,7 @@ private:
double scale_x_;
Rational timebase_;
Track::Reference track_;
TrackReference track_;
Qt::MouseButton button_;