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:
@@ -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
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -36,7 +36,7 @@ public:
|
||||
virtual void mouse_release(TimelineViewMouseEvent *event) override;
|
||||
|
||||
private:
|
||||
QVector<Track::Reference> split_tracks_;
|
||||
QVector<TrackReference> split_tracks_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user