Files
test/source/blender/editors/space_sequencer/sequencer_edit.cc
Richard Antalik 68abed543b Refactor: Remove module prefix form symbols in sequnecer namespaces
Remove
SEQ_ prefix for blender::seq namespace and
ED_sequencer for blender::ed::vse namespace

Pull Request: https://projects.blender.org/blender/blender/pulls/135560
2025-03-06 13:04:39 +01:00

3568 lines
103 KiB
C++

/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup spseq
*/
#include "MEM_guardedalloc.h"
#include "BLI_fileops.h"
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BLI_path_utils.hh"
#include "BLI_string.h"
#include "BLI_timecode.h"
#include "BLI_utildefines.h"
#include "BLT_translation.hh"
#include "DNA_anim_types.h"
#include "DNA_scene_types.h"
#include "DNA_sound_types.h"
#include "BKE_context.hh"
#include "BKE_global.hh"
#include "BKE_library.hh"
#include "BKE_main.hh"
#include "BKE_report.hh"
#include "BKE_sound.h"
#include "SEQ_add.hh"
#include "SEQ_animation.hh"
#include "SEQ_channels.hh"
#include "SEQ_connect.hh"
#include "SEQ_edit.hh"
#include "SEQ_effects.hh"
#include "SEQ_iterator.hh"
#include "SEQ_prefetch.hh"
#include "SEQ_relations.hh"
#include "SEQ_render.hh"
#include "SEQ_select.hh"
#include "SEQ_sequencer.hh"
#include "SEQ_thumbnail_cache.hh"
#include "SEQ_time.hh"
#include "SEQ_transform.hh"
#include "SEQ_utils.hh"
#include "ANIM_action_legacy.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "RNA_define.hh"
#include "RNA_enum_types.hh"
#include "RNA_prototypes.hh"
/* For menu, popup, icons, etc. */
#include "ED_fileselect.hh"
#include "ED_numinput.hh"
#include "ED_scene.hh"
#include "ED_screen.hh"
#include "ED_sequencer.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "UI_view2d.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
/* Own include. */
#include "sequencer_intern.hh"
namespace blender::ed::vse {
/* -------------------------------------------------------------------- */
/** \name Public Context Checks
* \{ */
bool maskedit_mask_poll(bContext *C)
{
return maskedit_poll(C);
}
bool check_show_maskedit(SpaceSeq *sseq, Scene *scene)
{
if (sseq && sseq->mainb == SEQ_DRAW_IMG_IMBUF) {
return (seq::active_mask_get(scene) != nullptr);
}
return false;
}
bool maskedit_poll(bContext *C)
{
SpaceSeq *sseq = CTX_wm_space_seq(C);
if (sseq) {
Scene *scene = CTX_data_scene(C);
return check_show_maskedit(sseq, scene);
}
return false;
}
bool check_show_imbuf(SpaceSeq *sseq)
{
return (sseq->mainb == SEQ_DRAW_IMG_IMBUF) &&
ELEM(sseq->view, SEQ_VIEW_PREVIEW, SEQ_VIEW_SEQUENCE_PREVIEW);
}
bool check_show_strip(SpaceSeq *sseq)
{
return ELEM(sseq->view, SEQ_VIEW_SEQUENCE, SEQ_VIEW_SEQUENCE_PREVIEW);
}
static bool sequencer_fcurves_targets_color_strip(const FCurve *fcurve)
{
if (!BLI_str_startswith(fcurve->rna_path, "sequence_editor.strips_all[\"")) {
return false;
}
if (!BLI_str_endswith(fcurve->rna_path, "\"].color")) {
return false;
}
return true;
}
bool has_playback_animation(const SpaceSeq *sseq, const Scene *scene)
{
if (sseq->draw_flag & SEQ_DRAW_BACKDROP) {
return true;
}
if (!scene->adt) {
return false;
}
if (!scene->adt->action) {
return false;
}
for (FCurve *fcurve : blender::animrig::legacy::fcurves_for_assigned_action(scene->adt)) {
if (sequencer_fcurves_targets_color_strip(fcurve)) {
return true;
}
}
return false;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shared Poll Functions
* \{ */
bool sequencer_edit_poll(bContext *C)
{
return (seq::editing_get(CTX_data_scene(C)) != nullptr);
}
bool sequencer_edit_with_channel_region_poll(bContext *C)
{
if (!sequencer_edit_poll(C)) {
return false;
}
ARegion *region = CTX_wm_region(C);
if (!(region && (region->regiontype == RGN_TYPE_CHANNELS))) {
return false;
}
return true;
}
bool sequencer_editing_initialized_and_active(bContext *C)
{
return ED_operator_sequencer_active(C) && sequencer_edit_poll(C);
}
#if 0 /* UNUSED */
bool sequencer_strip_poll(bContext *C)
{
Editing *ed;
return (((ed = seq::editing_get(CTX_data_scene(C))) != nullptr) && (ed->act_seq != nullptr));
}
#endif
bool sequencer_strip_editable_poll(bContext *C)
{
Scene *scene = CTX_data_scene(C);
if (!ID_IS_EDITABLE(&scene->id)) {
return false;
}
Editing *ed = seq::editing_get(scene);
return (ed && (ed->act_seq != nullptr));
}
bool sequencer_strip_has_path_poll(bContext *C)
{
Editing *ed;
Strip *strip;
return (((ed = seq::editing_get(CTX_data_scene(C))) != nullptr) &&
((strip = ed->act_seq) != nullptr) && STRIP_HAS_PATH(strip));
}
bool sequencer_view_has_preview_poll(bContext *C)
{
SpaceSeq *sseq = CTX_wm_space_seq(C);
if (sseq == nullptr) {
return false;
}
if (seq::editing_get(CTX_data_scene(C)) == nullptr) {
return false;
}
if (!(ELEM(sseq->view, SEQ_VIEW_PREVIEW, SEQ_VIEW_SEQUENCE_PREVIEW) &&
(sseq->mainb == SEQ_DRAW_IMG_IMBUF)))
{
return false;
}
ARegion *region = CTX_wm_region(C);
if (!(region && region->regiontype == RGN_TYPE_PREVIEW)) {
return false;
}
return true;
}
bool sequencer_view_preview_only_poll(const bContext *C)
{
SpaceSeq *sseq = CTX_wm_space_seq(C);
if (sseq == nullptr) {
return false;
}
if (seq::editing_get(CTX_data_scene(C)) == nullptr) {
return false;
}
if (!(ELEM(sseq->view, SEQ_VIEW_PREVIEW) && (sseq->mainb == SEQ_DRAW_IMG_IMBUF))) {
return false;
}
ARegion *region = CTX_wm_region(C);
if (!(region && region->regiontype == RGN_TYPE_PREVIEW)) {
return false;
}
return true;
}
bool sequencer_view_strips_poll(bContext *C)
{
SpaceSeq *sseq = CTX_wm_space_seq(C);
if (sseq == nullptr) {
return false;
}
if (!check_show_strip(sseq)) {
return false;
}
ARegion *region = CTX_wm_region(C);
if (!(region && region->regiontype == RGN_TYPE_WINDOW)) {
return false;
}
return true;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Remove Gaps Operator
* \{ */
static int sequencer_gap_remove_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
const bool do_all = RNA_boolean_get(op->ptr, "all");
const Editing *ed = seq::editing_get(scene);
seq::edit_remove_gaps(scene, ed->seqbasep, scene->r.cfra, do_all);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_gap_remove(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Remove Gaps";
ot->idname = "SEQUENCER_OT_gap_remove";
ot->description =
"Remove gap at current frame to first strip at the right, independent of selection or "
"locked state of strips";
/* Api callbacks. */
// ot->invoke = sequencer_snap_invoke;
ot->exec = sequencer_gap_remove_exec;
ot->poll = sequencer_edit_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(ot->srna, "all", false, "All Gaps", "Do all gaps to right of current frame");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Insert Gaps Operator
* \{ */
static int sequencer_gap_insert_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
const int frames = RNA_int_get(op->ptr, "frames");
const Editing *ed = seq::editing_get(scene);
seq::transform_offset_after_frame(scene, ed->seqbasep, frames, scene->r.cfra);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_gap_insert(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Insert Gaps";
ot->idname = "SEQUENCER_OT_gap_insert";
ot->description =
"Insert gap at current frame to first strips at the right, independent of selection or "
"locked state of strips";
/* Api callbacks. */
// ot->invoke = sequencer_snap_invoke;
ot->exec = sequencer_gap_insert_exec;
ot->poll = sequencer_edit_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_int(ot->srna,
"frames",
10,
0,
INT_MAX,
"Frames",
"Frames to insert after current strip",
0,
1000);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Snap Strips to the Current Frame Operator
* \{ */
static int sequencer_snap_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
ListBase *channels = seq::channels_displayed_get(ed);
int snap_frame;
snap_frame = RNA_int_get(op->ptr, "frame");
/* Check meta-strips. */
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
if (strip->flag & SELECT && !seq::transform_is_locked(channels, strip) &&
seq::transform_sequence_can_be_translated(strip))
{
if ((strip->flag & (SEQ_LEFTSEL + SEQ_RIGHTSEL)) == 0) {
seq::transform_translate_sequence(
scene, strip, (snap_frame - strip->startofs) - strip->start);
}
else {
if (strip->flag & SEQ_LEFTSEL) {
seq::time_left_handle_frame_set(scene, strip, snap_frame);
}
else { /* SEQ_RIGHTSEL */
seq::time_right_handle_frame_set(scene, strip, snap_frame);
}
}
seq::relations_invalidate_cache_composite(scene, strip);
}
}
/* Test for effects and overlap. */
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
if (strip->flag & SELECT && !seq::transform_is_locked(channels, strip)) {
strip->flag &= ~SEQ_OVERLAP;
if (seq::transform_test_overlap(scene, ed->seqbasep, strip)) {
seq::transform_seqbase_shuffle(ed->seqbasep, strip, scene);
}
}
}
/* Recalculate bounds of effect strips, offsetting the keyframes if not snapping any handle. */
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
if (strip->type & STRIP_TYPE_EFFECT) {
const bool either_handle_selected = (strip->flag & (SEQ_LEFTSEL | SEQ_RIGHTSEL)) != 0;
if (strip->seq1 && (strip->seq1->flag & SELECT)) {
if (!either_handle_selected) {
seq::offset_animdata(
scene, strip, (snap_frame - seq::time_left_handle_frame_get(scene, strip)));
}
}
else if (strip->seq2 && (strip->seq2->flag & SELECT)) {
if (!either_handle_selected) {
seq::offset_animdata(
scene, strip, (snap_frame - seq::time_left_handle_frame_get(scene, strip)));
}
}
}
}
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
static int sequencer_snap_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
Scene *scene = CTX_data_scene(C);
int snap_frame;
snap_frame = scene->r.cfra;
RNA_int_set(op->ptr, "frame", snap_frame);
return sequencer_snap_exec(C, op);
}
void SEQUENCER_OT_snap(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Snap Strips to the Current Frame";
ot->idname = "SEQUENCER_OT_snap";
ot->description = "Frame where selected strips will be snapped";
/* Api callbacks. */
ot->invoke = sequencer_snap_invoke;
ot->exec = sequencer_snap_exec;
ot->poll = sequencer_edit_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_int(ot->srna,
"frame",
0,
INT_MIN,
INT_MAX,
"Frame",
"Frame where selected strips will be snapped",
INT_MIN,
INT_MAX);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Trim Strips Operator
* \{ */
struct SlipData {
float init_mouseloc[2];
int previous_offset;
float previous_subframe_offset;
float subframe_restore;
Strip **strip_array;
int num_seq;
bool slow;
int slow_offset; /* Offset at the point where offset was turned on. */
NumInput num_input;
};
static void slip_add_sequences(ListBase *seqbasep, Strip **strip_array)
{
int i = 0;
LISTBASE_FOREACH (Strip *, strip, seqbasep) {
if (!(strip->type & STRIP_TYPE_EFFECT) && (strip->flag & SELECT)) {
strip_array[i] = strip;
i++;
}
}
}
static int slip_count_sequences(ListBase *seqbasep)
{
int trimmed_sequences = 0;
LISTBASE_FOREACH (Strip *, strip, seqbasep) {
if (!(strip->type & STRIP_TYPE_EFFECT) && (strip->flag & SELECT)) {
trimmed_sequences++;
}
}
return trimmed_sequences;
}
static int sequencer_slip_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
SlipData *data;
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
float mouseloc[2];
int num_seq;
View2D *v2d = UI_view2d_fromcontext(C);
/* Count the amount of elements to trim. */
num_seq = slip_count_sequences(ed->seqbasep);
if (num_seq == 0) {
return OPERATOR_CANCELLED;
}
data = MEM_callocN<SlipData>("trimdata");
op->customdata = static_cast<void *>(data);
data->strip_array = MEM_calloc_arrayN<Strip *>(num_seq, "trimdata_strips");
data->num_seq = num_seq;
data->previous_offset = 0;
initNumInput(&data->num_input);
data->num_input.idx_max = 0;
data->num_input.unit_sys = USER_UNIT_NONE;
data->num_input.unit_type[0] = 0;
slip_add_sequences(ed->seqbasep, data->strip_array);
UI_view2d_region_to_view(v2d, event->mval[0], event->mval[1], &mouseloc[0], &mouseloc[1]);
copy_v2_v2(data->init_mouseloc, mouseloc);
data->slow = false;
WM_event_add_modal_handler(C, op);
/* Notify so we draw extensions immediately. */
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_RUNNING_MODAL;
}
static void sequencer_slip_strips(Scene *scene, SlipData *data, int delta, float subframe_delta)
{
for (int i = data->num_seq - 1; i >= 0; i--) {
Strip *strip = data->strip_array[i];
ListBase *channels = seq::channels_displayed_get(seq::editing_get(scene));
if (seq::transform_is_locked(channels, strip)) {
continue;
}
seq::time_slip_strip(scene, strip, delta, subframe_delta);
}
for (int i = data->num_seq - 1; i >= 0; i--) {
Strip *strip = data->strip_array[i];
seq::relations_invalidate_cache_preprocessed(scene, strip);
}
}
/* Make sure, that each strip contains at least 1 frame of content.
* Returns clamped offset relative to current strip positions. */
static int sequencer_slip_apply_limits(const Scene *scene, SlipData *data, int *offset)
{
int delta_offset = *offset - data->previous_offset;
for (int i = 0; i < data->num_seq; i++) {
Strip *strip = data->strip_array[i];
int strip_content_start = seq::time_start_frame_get(strip) + delta_offset;
int strip_content_end = strip_content_start + strip->len + strip->anim_startofs +
strip->anim_endofs;
int diff = 0;
if (strip_content_start >= seq::time_right_handle_frame_get(scene, strip)) {
diff = seq::time_right_handle_frame_get(scene, strip) - strip_content_start - 1;
}
if (strip_content_end <= seq::time_left_handle_frame_get(scene, strip)) {
diff = seq::time_left_handle_frame_get(scene, strip) - strip_content_end + 1;
}
*offset += diff;
delta_offset += diff;
}
data->previous_offset = *offset;
return delta_offset;
}
static int sequencer_slip_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
/* Count the amount of elements to trim. */
int num_seq = slip_count_sequences(ed->seqbasep);
if (num_seq == 0) {
return OPERATOR_CANCELLED;
}
SlipData *data = MEM_callocN<SlipData>("trimdata");
op->customdata = static_cast<void *>(data);
data->strip_array = MEM_calloc_arrayN<Strip *>(num_seq, "trimdata_strips");
data->num_seq = num_seq;
slip_add_sequences(ed->seqbasep, data->strip_array);
float offset_fl = RNA_float_get(op->ptr, "offset");
int offset = round_fl_to_int(offset_fl);
float subframe_delta = 0.0f;
if (std::trunc(offset_fl) != offset_fl) {
/* Only apply subframe offsets if the input is not an integer. */
subframe_delta = offset_fl - offset;
}
sequencer_slip_apply_limits(scene, data, &offset);
sequencer_slip_strips(scene, data, offset, subframe_delta);
MEM_freeN(data->strip_array);
MEM_freeN(data);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
return OPERATOR_FINISHED;
}
static void sequencer_slip_update_header(Scene *scene, ScrArea *area, SlipData *data, int offset)
{
if (area == nullptr) {
return;
}
char msg[UI_MAX_DRAW_STR];
if (hasNumInput(&data->num_input)) {
char num_str[NUM_STR_REP_LEN];
outputNumInput(&data->num_input, num_str, scene->unit);
SNPRINTF(msg, IFACE_("Slip offset: %s"), num_str);
}
else {
SNPRINTF(msg, IFACE_("Slip offset: %d"), offset);
}
ED_area_status_text(area, msg);
}
static void handle_number_input(
bContext *C, wmOperator *op, ScrArea *area, SlipData *data, Scene *scene)
{
float offset_fl;
applyNumInput(&data->num_input, &offset_fl);
int offset = round_fl_to_int(offset_fl);
const int delta_offset = sequencer_slip_apply_limits(scene, data, &offset);
sequencer_slip_update_header(scene, area, data, offset);
RNA_float_set(op->ptr, "offset", offset_fl);
float subframe_delta = 0.0f;
if (data->subframe_restore != 0.0f) {
/* Always remove the previous sub-frame adjustments we have potentially made with the mouse
* input when the user starts entering values by hand.
*/
subframe_delta = -data->subframe_restore;
data->subframe_restore = 0.0f;
}
if (std::trunc(offset_fl) != offset_fl) {
/* Only apply sub-frame offsets if the input is not an integer. */
subframe_delta = offset_fl - data->previous_subframe_offset - delta_offset;
data->subframe_restore += subframe_delta;
}
data->previous_subframe_offset = offset_fl;
sequencer_slip_strips(scene, data, delta_offset, subframe_delta);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
}
static int sequencer_slip_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
Scene *scene = CTX_data_scene(C);
SlipData *data = (SlipData *)op->customdata;
ScrArea *area = CTX_wm_area(C);
const bool has_numInput = hasNumInput(&data->num_input);
bool handled = true;
/* Modal numinput active, try to handle numeric inputs. */
if (event->val == KM_PRESS && has_numInput && handleNumInput(C, &data->num_input, event)) {
handle_number_input(C, op, area, data, scene);
return OPERATOR_RUNNING_MODAL;
}
switch (event->type) {
case MOUSEMOVE: {
if (!has_numInput) {
float mouseloc[2];
int offset;
float mouse_x;
View2D *v2d = UI_view2d_fromcontext(C);
if (data->slow) {
mouse_x = event->mval[0] - data->slow_offset;
mouse_x *= 0.1f;
mouse_x += data->slow_offset;
}
else {
mouse_x = event->mval[0];
}
/* Choose the side based on which side of the current frame the mouse is. */
UI_view2d_region_to_view(v2d, mouse_x, 0, &mouseloc[0], &mouseloc[1]);
float offset_fl = mouseloc[0] - data->init_mouseloc[0];
offset = offset_fl;
const int delta_offset = sequencer_slip_apply_limits(scene, data, &offset);
sequencer_slip_update_header(scene, area, data, offset);
if (!data->slow) {
RNA_float_set(op->ptr, "offset", offset);
}
float subframe_delta = 0.0f;
if (data->slow) {
RNA_float_set(op->ptr, "offset", offset_fl);
subframe_delta = offset_fl - data->previous_subframe_offset - delta_offset;
data->subframe_restore += subframe_delta;
}
else if (data->subframe_restore != 0.0f) {
/* If we exit slow mode, make sure we undo the fractional adjustments we have done. */
subframe_delta = -data->subframe_restore;
data->subframe_restore = 0.0f;
}
data->previous_subframe_offset = offset_fl;
sequencer_slip_strips(scene, data, delta_offset, subframe_delta);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
}
break;
}
case LEFTMOUSE:
case EVT_RETKEY:
case EVT_PADENTER:
case EVT_SPACEKEY: {
MEM_freeN(data->strip_array);
MEM_freeN(data);
op->customdata = nullptr;
if (area) {
ED_area_status_text(area, nullptr);
}
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
case EVT_ESCKEY:
case RIGHTMOUSE: {
int offset = data->previous_offset;
float subframe_delta = data->subframe_restore;
sequencer_slip_strips(scene, data, -offset, -subframe_delta);
MEM_freeN(data->strip_array);
MEM_freeN(data);
op->customdata = nullptr;
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
if (area) {
ED_area_status_text(area, nullptr);
}
return OPERATOR_CANCELLED;
}
case EVT_RIGHTSHIFTKEY:
case EVT_LEFTSHIFTKEY:
if (!has_numInput) {
if (event->val == KM_PRESS) {
data->slow = true;
data->slow_offset = event->mval[0];
}
else if (event->val == KM_RELEASE) {
data->slow = false;
}
}
break;
default:
handled = false;
break;
}
/* Modal numinput inactive, try to handle numeric inputs. */
if (!handled && event->val == KM_PRESS && handleNumInput(C, &data->num_input, event)) {
handle_number_input(C, op, area, data, scene);
}
return OPERATOR_RUNNING_MODAL;
}
void SEQUENCER_OT_slip(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Slip Strips";
ot->idname = "SEQUENCER_OT_slip";
ot->description = "Slip the contents of selected strips";
/* Api callbacks. */
ot->invoke = sequencer_slip_invoke;
ot->modal = sequencer_slip_modal;
ot->exec = sequencer_slip_exec;
ot->poll = sequencer_edit_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* Properties. */
PropertyRNA *prop;
prop = RNA_def_float(ot->srna,
"offset",
0,
-FLT_MAX,
FLT_MAX,
"Offset",
"Offset to the data of the strip",
-FLT_MAX,
FLT_MAX);
RNA_def_property_ui_range(prop, -FLT_MAX, FLT_MAX, 100, 0);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Mute Strips Operator
* \{ */
static int sequencer_mute_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
ListBase *channels = seq::channels_displayed_get(ed);
bool selected;
selected = !RNA_boolean_get(op->ptr, "unselected");
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
if (!seq::transform_is_locked(channels, strip)) {
if (selected) {
if (strip->flag & SELECT) {
strip->flag |= SEQ_MUTE;
seq::relations_invalidate_dependent(scene, strip);
}
}
else {
if ((strip->flag & SELECT) == 0) {
strip->flag |= SEQ_MUTE;
seq::relations_invalidate_dependent(scene, strip);
}
}
}
}
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_mute(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Mute Strips";
ot->idname = "SEQUENCER_OT_mute";
ot->description = "Mute (un)selected strips";
/* Api callbacks. */
ot->exec = sequencer_mute_exec;
ot->poll = sequencer_edit_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(
ot->srna, "unselected", false, "Unselected", "Mute unselected rather than selected strips");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Unmute Strips Operator
* \{ */
static int sequencer_unmute_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
ListBase *channels = seq::channels_displayed_get(ed);
bool selected;
selected = !RNA_boolean_get(op->ptr, "unselected");
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
if (!seq::transform_is_locked(channels, strip)) {
if (selected) {
if (strip->flag & SELECT) {
strip->flag &= ~SEQ_MUTE;
seq::relations_invalidate_dependent(scene, strip);
}
}
else {
if ((strip->flag & SELECT) == 0) {
strip->flag &= ~SEQ_MUTE;
seq::relations_invalidate_dependent(scene, strip);
}
}
}
}
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_unmute(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Unmute Strips";
ot->idname = "SEQUENCER_OT_unmute";
ot->description = "Unmute (un)selected strips";
/* Api callbacks. */
ot->exec = sequencer_unmute_exec;
ot->poll = sequencer_edit_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(ot->srna,
"unselected",
false,
"Unselected",
"Unmute unselected rather than selected strips");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Lock Strips Operator
* \{ */
static int sequencer_lock_exec(bContext *C, wmOperator * /*op*/)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
if (strip->flag & SELECT) {
strip->flag |= SEQ_LOCK;
}
}
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_lock(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Lock Strips";
ot->idname = "SEQUENCER_OT_lock";
ot->description = "Lock strips so they can't be transformed";
/* Api callbacks. */
ot->exec = sequencer_lock_exec;
ot->poll = sequencer_edit_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Unlock Strips Operator
* \{ */
static int sequencer_unlock_exec(bContext *C, wmOperator * /*op*/)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
if (strip->flag & SELECT) {
strip->flag &= ~SEQ_LOCK;
}
}
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_unlock(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Unlock Strips";
ot->idname = "SEQUENCER_OT_unlock";
ot->description = "Unlock strips so they can be transformed";
/* Api callbacks. */
ot->exec = sequencer_unlock_exec;
ot->poll = sequencer_edit_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Connect Strips Operator
* \{ */
static int sequencer_connect_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
ListBase *active_seqbase = seq::active_seqbase_get(ed);
blender::VectorSet<Strip *> selected = seq::query_selected_strips(active_seqbase);
if (selected.is_empty()) {
return OPERATOR_CANCELLED;
}
const bool toggle = RNA_boolean_get(op->ptr, "toggle");
if (toggle && seq::are_strips_connected_together(selected)) {
seq::disconnect(selected);
}
else {
seq::connect(selected);
}
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_connect(wmOperatorType *ot)
{
ot->name = "Connect Strips";
ot->idname = "SEQUENCER_OT_connect";
ot->description = "Link selected strips together for simplified group selection";
ot->exec = sequencer_connect_exec;
ot->poll = sequencer_edit_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(ot->srna, "toggle", true, "Toggle", "Toggle strip connections");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Disconnect Strips Operator
* \{ */
static int sequencer_disconnect_exec(bContext *C, wmOperator * /*op*/)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
ListBase *active_seqbase = seq::active_seqbase_get(ed);
blender::VectorSet<Strip *> selected = seq::query_selected_strips(active_seqbase);
if (seq::disconnect(selected)) {
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
return OPERATOR_CANCELLED;
}
void SEQUENCER_OT_disconnect(wmOperatorType *ot)
{
ot->name = "Disconnect Strips";
ot->idname = "SEQUENCER_OT_disconnect";
ot->description = "Unlink selected strips so that they can be selected individually";
ot->exec = sequencer_disconnect_exec;
ot->poll = sequencer_edit_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Reload Strips Operator
* \{ */
static int sequencer_reload_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
const bool adjust_length = RNA_boolean_get(op->ptr, "adjust_length");
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
if (strip->flag & SELECT) {
seq::add_reload_new_file(bmain, scene, strip, !adjust_length);
blender::seq::thumbnail_cache_invalidate_strip(scene, strip);
if (adjust_length) {
if (seq::transform_test_overlap(scene, ed->seqbasep, strip)) {
seq::transform_seqbase_shuffle(ed->seqbasep, strip, scene);
}
}
}
}
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_reload(wmOperatorType *ot)
{
PropertyRNA *prop;
/* Identifiers. */
ot->name = "Reload Strips";
ot->idname = "SEQUENCER_OT_reload";
ot->description = "Reload strips in the sequencer";
/* Api callbacks. */
ot->exec = sequencer_reload_exec;
ot->poll = sequencer_edit_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER; /* No undo, the data changed is stored outside 'main'. */
prop = RNA_def_boolean(ot->srna,
"adjust_length",
false,
"Adjust Length",
"Adjust length of strips to their data length");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Refresh Sequencer Operator
* \{ */
static bool sequencer_refresh_all_poll(bContext *C)
{
if (G.is_rendering) {
return false;
}
return sequencer_edit_poll(C);
}
static int sequencer_refresh_all_exec(bContext *C, wmOperator * /*op*/)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
seq::relations_free_imbuf(scene, &ed->seqbase, false);
blender::seq::media_presence_free(scene);
blender::seq::thumbnail_cache_clear(scene);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_refresh_all(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Refresh Sequencer";
ot->idname = "SEQUENCER_OT_refresh_all";
ot->description = "Refresh the sequencer editor";
/* Api callbacks. */
ot->exec = sequencer_refresh_all_exec;
ot->poll = sequencer_refresh_all_poll;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Reassign Inputs Operator
* \{ */
bool strip_effect_get_new_inputs(Scene *scene,
bool ignore_active,
int num_inputs,
Strip **r_seq1,
Strip **r_seq2,
const char **r_error_str)
{
Editing *ed = seq::editing_get(scene);
Strip *seq1 = nullptr, *seq2 = nullptr;
*r_error_str = nullptr;
if (num_inputs == 0) {
*r_seq1 = *r_seq2 = nullptr;
return true;
}
blender::VectorSet<Strip *> new_inputs = seq::query_selected_strips(ed->seqbasep);
// Ignore sound strips for now (avoids unnecessary errors when connected strips are
// selected together, and the intent to operate on strips with video content is clear).
new_inputs.remove_if([&](Strip *strip) { return strip->type == STRIP_TYPE_SOUND_RAM; });
if (ignore_active) {
// If `ignore_active` is true, this function is being called from the reassign inputs
// operator, meaning the active strip must be the effect strip to reassign.
Strip *active_strip = seq::select_active_get(scene);
new_inputs.remove_if([&](Strip *strip) { return strip == active_strip; });
}
if (new_inputs.size() > 2) {
*r_error_str = N_("Cannot apply effect to more than 2 sequence strips with video content");
return false;
}
if (num_inputs == 2) {
if (new_inputs.size() != 2) {
*r_error_str = N_("Exactly 2 selected sequence strips with video content are needed");
return false;
}
seq1 = new_inputs[0];
seq2 = new_inputs[1];
}
else if (num_inputs == 1) {
if (new_inputs.size() != 1) {
*r_error_str = N_("Exactly one selected sequence strip with video content is needed");
return false;
}
seq1 = new_inputs[0];
}
*r_seq1 = seq1;
*r_seq2 = seq2;
return true;
}
static int sequencer_reassign_inputs_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Strip *seq1, *seq2;
Strip *active_strip = seq::select_active_get(scene);
const char *error_msg;
const int num_inputs = seq::effect_get_num_inputs(active_strip->type);
if (num_inputs == 0) {
BKE_report(op->reports, RPT_ERROR, "Cannot reassign inputs: strip has no inputs");
return OPERATOR_CANCELLED;
}
if (!strip_effect_get_new_inputs(scene, true, num_inputs, &seq1, &seq2, &error_msg)) {
BKE_report(op->reports, RPT_ERROR, error_msg);
return OPERATOR_CANCELLED;
}
/* Check if reassigning would create recursivity. */
if (seq::relations_render_loop_check(seq1, active_strip) ||
seq::relations_render_loop_check(seq2, active_strip))
{
BKE_report(op->reports, RPT_ERROR, "Cannot reassign inputs: recursion detected");
return OPERATOR_CANCELLED;
}
active_strip->seq1 = seq1;
active_strip->seq2 = seq2;
int old_start = active_strip->start;
/* Force time position update for reassigned effects.
* TODO(Richard): This is because internally startdisp is still used, due to poor performance of
* mapping effect range to inputs. This mapping could be cached though. */
seq::strip_lookup_invalidate(scene->ed);
seq::time_left_handle_frame_set(scene, seq1, seq::time_left_handle_frame_get(scene, seq1));
seq::relations_invalidate_cache_preprocessed(scene, active_strip);
seq::offset_animdata(scene, active_strip, (active_strip->start - old_start));
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
static bool sequencer_effect_poll(bContext *C)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
if (ed) {
Strip *active_strip = seq::select_active_get(scene);
if (active_strip && (active_strip->type & STRIP_TYPE_EFFECT)) {
return true;
}
}
return false;
}
void SEQUENCER_OT_reassign_inputs(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Reassign Inputs";
ot->idname = "SEQUENCER_OT_reassign_inputs";
ot->description = "Reassign the inputs for the effect strip";
/* Api callbacks. */
ot->exec = sequencer_reassign_inputs_exec;
ot->poll = sequencer_effect_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Swap Inputs Operator
* \{ */
static int sequencer_swap_inputs_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Strip *active_strip = seq::select_active_get(scene);
if (active_strip->seq1 == nullptr || active_strip->seq2 == nullptr) {
BKE_report(op->reports, RPT_ERROR, "No valid inputs to swap");
return OPERATOR_CANCELLED;
}
Strip *strip = active_strip->seq1;
active_strip->seq1 = active_strip->seq2;
active_strip->seq2 = strip;
seq::relations_invalidate_cache_preprocessed(scene, active_strip);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_swap_inputs(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Swap Inputs";
ot->idname = "SEQUENCER_OT_swap_inputs";
ot->description = "Swap the two inputs of the effect strip";
/* Api callbacks. */
ot->exec = sequencer_swap_inputs_exec;
ot->poll = sequencer_effect_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Split Strips Operator
* \{ */
static int mouse_frame_side(View2D *v2d, short mouse_x, int frame)
{
int mval[2];
float mouseloc[2];
mval[0] = mouse_x;
mval[1] = 0;
/* Choose the side based on which side of the current frame the mouse is on. */
UI_view2d_region_to_view(v2d, mval[0], mval[1], &mouseloc[0], &mouseloc[1]);
return mouseloc[0] > frame ? seq::SIDE_RIGHT : seq::SIDE_LEFT;
}
static const EnumPropertyItem prop_split_types[] = {
{seq::SPLIT_SOFT, "SOFT", 0, "Soft", ""},
{seq::SPLIT_HARD, "HARD", 0, "Hard", ""},
{0, nullptr, 0, nullptr, nullptr},
};
const EnumPropertyItem prop_side_types[] = {
{seq::SIDE_MOUSE, "MOUSE", 0, "Mouse Position", ""},
{seq::SIDE_LEFT, "LEFT", 0, "Left", ""},
{seq::SIDE_RIGHT, "RIGHT", 0, "Right", ""},
{seq::SIDE_BOTH, "BOTH", 0, "Both", ""},
{seq::SIDE_NO_CHANGE, "NO_CHANGE", 0, "No Change", ""},
{0, nullptr, 0, nullptr, nullptr},
};
/* Get the splitting side for the Split Strips's operator exec() callback. */
static int sequence_split_side_for_exec_get(wmOperator *op)
{
const int split_side = RNA_enum_get(op->ptr, "side");
/* The mouse position can not be resolved from the exec() as the mouse coordinate is not
* accessible. So fall-back to the RIGHT side instead.
*
* The SEQ_SIDE_MOUSE is used by the Strip menu, together with the EXEC_DEFAULT operator
* context in order to have properly resolved shortcut in the menu. */
if (split_side == seq::SIDE_MOUSE) {
return seq::SIDE_RIGHT;
}
return split_side;
}
static int sequencer_split_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
bool changed = false;
bool strip_selected = false;
const bool use_cursor_position = RNA_boolean_get(op->ptr, "use_cursor_position");
const int split_frame = RNA_struct_property_is_set(op->ptr, "frame") ?
RNA_int_get(op->ptr, "frame") :
scene->r.cfra;
const int split_channel = RNA_int_get(op->ptr, "channel");
const seq::eSplitMethod method = seq::eSplitMethod(RNA_enum_get(op->ptr, "type"));
const int split_side = sequence_split_side_for_exec_get(op);
const bool ignore_selection = RNA_boolean_get(op->ptr, "ignore_selection");
seq::prefetch_stop(scene);
LISTBASE_FOREACH_BACKWARD (Strip *, strip, ed->seqbasep) {
if (use_cursor_position && strip->machine != split_channel) {
continue;
}
if (ignore_selection || strip->flag & SELECT) {
const char *error_msg = nullptr;
if (seq::edit_strip_split(
bmain, scene, ed->seqbasep, strip, split_frame, method, &error_msg) != nullptr)
{
changed = true;
}
if (error_msg != nullptr) {
BKE_report(op->reports, RPT_ERROR, error_msg);
}
}
}
if (changed) { /* Got new strips? */
if (ignore_selection) {
if (use_cursor_position) {
LISTBASE_FOREACH (Strip *, strip, seq::active_seqbase_get(ed)) {
if (seq::time_right_handle_frame_get(scene, strip) == split_frame &&
strip->machine == split_channel)
{
strip_selected = strip->flag & STRIP_ALLSEL;
}
}
if (!strip_selected) {
LISTBASE_FOREACH (Strip *, strip, seq::active_seqbase_get(ed)) {
if (seq::time_left_handle_frame_get(scene, strip) == split_frame &&
strip->machine == split_channel)
{
strip->flag &= ~STRIP_ALLSEL;
}
}
}
}
}
else {
if (split_side != seq::SIDE_BOTH) {
LISTBASE_FOREACH (Strip *, strip, seq::active_seqbase_get(ed)) {
if (split_side == seq::SIDE_LEFT) {
if (seq::time_left_handle_frame_get(scene, strip) >= split_frame) {
strip->flag &= ~STRIP_ALLSEL;
}
}
else {
if (seq::time_right_handle_frame_get(scene, strip) <= split_frame) {
strip->flag &= ~STRIP_ALLSEL;
}
}
}
}
}
}
if (changed) {
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
/* Passthrough to selection if used as tool. */
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
}
static int sequencer_split_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
Scene *scene = CTX_data_scene(C);
View2D *v2d = UI_view2d_fromcontext(C);
int split_side = RNA_enum_get(op->ptr, "side");
int split_frame = scene->r.cfra;
if (split_side == seq::SIDE_MOUSE) {
if (ED_operator_sequencer_active(C) && v2d) {
split_side = mouse_frame_side(v2d, event->mval[0], split_frame);
}
else {
split_side = seq::SIDE_BOTH;
}
}
float mouseloc[2];
if (v2d) {
UI_view2d_region_to_view(v2d, event->mval[0], event->mval[1], &mouseloc[0], &mouseloc[1]);
if (RNA_boolean_get(op->ptr, "use_cursor_position")) {
split_frame = mouseloc[0];
}
RNA_int_set(op->ptr, "channel", mouseloc[1]);
}
RNA_int_set(op->ptr, "frame", split_frame);
RNA_enum_set(op->ptr, "side", split_side);
// RNA_enum_set(op->ptr, "type", split_hard);
return sequencer_split_exec(C, op);
}
static void sequencer_split_ui(bContext * /*C*/, wmOperator *op)
{
uiLayout *layout = op->layout;
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
uiLayout *row = uiLayoutRow(layout, false);
uiItemR(row, op->ptr, "type", UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
uiItemR(layout, op->ptr, "frame", UI_ITEM_NONE, std::nullopt, ICON_NONE);
uiItemR(layout, op->ptr, "side", UI_ITEM_NONE, std::nullopt, ICON_NONE);
uiItemS(layout);
uiItemR(layout, op->ptr, "use_cursor_position", UI_ITEM_NONE, std::nullopt, ICON_NONE);
if (RNA_boolean_get(op->ptr, "use_cursor_position")) {
uiItemR(layout, op->ptr, "channel", UI_ITEM_NONE, std::nullopt, ICON_NONE);
}
}
void SEQUENCER_OT_split(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Split Strips";
ot->idname = "SEQUENCER_OT_split";
ot->description = "Split the selected strips in two";
/* Api callbacks. */
ot->invoke = sequencer_split_invoke;
ot->exec = sequencer_split_exec;
ot->poll = sequencer_edit_poll;
ot->ui = sequencer_split_ui;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
PropertyRNA *prop;
RNA_def_int(ot->srna,
"frame",
0,
INT_MIN,
INT_MAX,
"Frame",
"Frame where selected strips will be split",
INT_MIN,
INT_MAX);
RNA_def_int(ot->srna,
"channel",
0,
INT_MIN,
INT_MAX,
"Channel",
"Channel in which strip will be cut",
INT_MIN,
INT_MAX);
RNA_def_enum(ot->srna,
"type",
prop_split_types,
seq::SPLIT_SOFT,
"Type",
"The type of split operation to perform on strips");
RNA_def_boolean(ot->srna,
"use_cursor_position",
false,
"Use Cursor Position",
"Split at position of the cursor instead of current frame");
prop = RNA_def_enum(ot->srna,
"side",
prop_side_types,
seq::SIDE_MOUSE,
"Side",
"The side that remains selected after splitting");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(
ot->srna,
"ignore_selection",
false,
"Ignore Selection",
"Make cut even if strip is not selected preserving selection state after cut");
RNA_def_property_flag(prop, PROP_HIDDEN);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Duplicate Strips Operator
* \{ */
static int sequencer_add_duplicate_exec(bContext *C, wmOperator * /*op*/)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
ARegion *region = CTX_wm_region(C);
if (ed == nullptr) {
return OPERATOR_CANCELLED;
}
Strip *active_seq = seq::select_active_get(scene);
ListBase duplicated_strips = {nullptr, nullptr};
seq::sequence_base_dupli_recursive(scene, scene, &duplicated_strips, ed->seqbasep, 0, 0);
deselect_all_strips(scene);
if (duplicated_strips.first == nullptr) {
return OPERATOR_CANCELLED;
}
/* Duplicate animation.
* First backup original curves from scene and duplicate strip curves from backup into scene.
* This way, when pasted strips are renamed, curves are renamed with them. Finally, restore
* original curves from backup.
*/
seq::AnimationBackup animation_backup = {{nullptr}};
seq::animation_backup_original(scene, &animation_backup);
ListBase *seqbase = seq::active_seqbase_get(seq::editing_get(scene));
Strip *strip_last = static_cast<Strip *>(seqbase->last);
/* Rely on the `duplicated_strips` list being added at the end.
* Their UIDs has been re-generated by the #SEQ_sequence_base_dupli_recursive(). */
BLI_movelisttolist(ed->seqbasep, &duplicated_strips);
/* Handle duplicated strips: set active, select, ensure unique name and duplicate animation
* data. */
for (Strip *strip = strip_last->next; strip; strip = strip->next) {
if (active_seq != nullptr && STREQ(strip->name, active_seq->name)) {
seq::select_active_set(scene, strip);
}
strip->flag &= ~(SEQ_LEFTSEL + SEQ_RIGHTSEL + SEQ_LOCK);
strip->flag |= SEQ_IGNORE_CHANNEL_LOCK;
seq::animation_duplicate_backup_to_scene(scene, strip, &animation_backup);
seq::ensure_unique_name(strip, scene);
}
/* Special case for duplicating strips in preview: Do not duplicate sound strips and handle
* overlap, because strips won't be translated. */
if (region->regiontype == RGN_TYPE_PREVIEW && sequencer_view_preview_only_poll(C)) {
for (Strip *strip = strip_last->next; strip; strip = strip->next) {
if (strip->type == STRIP_TYPE_SOUND_RAM) {
seq::edit_flag_for_removal(scene, ed->seqbasep, strip);
}
}
seq::edit_remove_flagged_sequences(scene, ed->seqbasep);
for (Strip *strip = strip_last->next; strip; strip = strip->next) {
if (seq::transform_test_overlap(scene, ed->seqbasep, strip)) {
seq::transform_seqbase_shuffle(ed->seqbasep, strip, scene);
}
}
}
seq::animation_restore_original(scene, &animation_backup);
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
DEG_relations_tag_update(CTX_data_main(C));
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_duplicate(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Duplicate Strips";
ot->idname = "SEQUENCER_OT_duplicate";
ot->description = "Duplicate the selected strips";
/* Api callbacks. */
ot->exec = sequencer_add_duplicate_exec;
ot->poll = ED_operator_sequencer_active;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Erase Strips Operator
* \{ */
static void sequencer_delete_strip_data(bContext *C, Strip *strip)
{
if (strip->type != STRIP_TYPE_SCENE) {
return;
}
Main *bmain = CTX_data_main(C);
if (strip->scene) {
if (ED_scene_delete(C, bmain, strip->scene)) {
WM_event_add_notifier(C, NC_SCENE | NA_REMOVED, strip->scene);
}
}
}
static int sequencer_delete_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
ListBase *seqbasep = seq::active_seqbase_get(seq::editing_get(scene));
const bool delete_data = RNA_boolean_get(op->ptr, "delete_data");
if (sequencer_view_has_preview_poll(C) && !sequencer_view_preview_only_poll(C)) {
return OPERATOR_CANCELLED;
}
seq::prefetch_stop(scene);
for (Strip *strip : selected_strips_from_context(C)) {
seq::edit_flag_for_removal(scene, seqbasep, strip);
if (delete_data) {
sequencer_delete_strip_data(C, strip);
}
}
seq::edit_remove_flagged_sequences(scene, seqbasep);
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
if (scene->adt && scene->adt->action) {
DEG_id_tag_update(&scene->adt->action->id, ID_RECALC_ANIMATION_NO_FLUSH);
}
DEG_relations_tag_update(bmain);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
WM_event_add_notifier(C, NC_SCENE | ND_ANIMCHAN, scene);
return OPERATOR_FINISHED;
}
static int sequencer_delete_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
Scene *scene = CTX_data_scene(C);
ListBase *markers = &scene->markers;
if (!BLI_listbase_is_empty(markers)) {
ARegion *region = CTX_wm_region(C);
if (region && (region->regiontype == RGN_TYPE_WINDOW)) {
/* Bounding box of 30 pixels is used for markers shortcuts,
* prevent conflict with markers shortcuts here. */
if (event->mval[1] <= 30) {
return OPERATOR_PASS_THROUGH;
}
}
}
return sequencer_delete_exec(C, op);
}
void SEQUENCER_OT_delete(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Delete Strips";
ot->idname = "SEQUENCER_OT_delete";
ot->description = "Delete selected strips from the sequencer";
/* Api callbacks. */
ot->invoke = sequencer_delete_invoke;
ot->exec = sequencer_delete_exec;
ot->poll = sequencer_edit_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* Properties. */
ot->prop = RNA_def_boolean(ot->srna,
"delete_data",
false,
"Delete Data",
"After removing the Strip, delete the associated data also");
RNA_def_property_flag(ot->prop, PROP_SKIP_SAVE);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Clear Strip Offset Operator
* \{ */
static int sequencer_offset_clear_exec(bContext *C, wmOperator * /*op*/)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
Strip *strip;
ListBase *channels = seq::channels_displayed_get(seq::editing_get(scene));
/* For effects, try to find a replacement input. */
for (strip = static_cast<Strip *>(ed->seqbasep->first); strip;
strip = static_cast<Strip *>(strip->next))
{
if (seq::transform_is_locked(channels, strip)) {
continue;
}
if ((strip->type & STRIP_TYPE_EFFECT) == 0 && (strip->flag & SELECT)) {
strip->startofs = strip->endofs = 0;
}
}
/* Update lengths, etc. */
strip = static_cast<Strip *>(ed->seqbasep->first);
while (strip) {
seq::relations_invalidate_cache_preprocessed(scene, strip);
strip = strip->next;
}
for (strip = static_cast<Strip *>(ed->seqbasep->first); strip;
strip = static_cast<Strip *>(strip->next))
{
if ((strip->type & STRIP_TYPE_EFFECT) == 0 && (strip->flag & SELECT)) {
if (seq::transform_test_overlap(scene, ed->seqbasep, strip)) {
seq::transform_seqbase_shuffle(ed->seqbasep, strip, scene);
}
}
}
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_offset_clear(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Clear Strip Offset";
ot->idname = "SEQUENCER_OT_offset_clear";
ot->description = "Clear strip offsets from the start and end frames";
/* Api callbacks. */
ot->exec = sequencer_offset_clear_exec;
ot->poll = sequencer_edit_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Separate Images Operator
* \{ */
static int sequencer_separate_images_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
ListBase *seqbase = seq::active_seqbase_get(ed);
Strip *strip, *strip_new;
StripData *data_new;
StripElem *se, *se_new;
int start_ofs, timeline_frame, frame_end;
int step = RNA_int_get(op->ptr, "length");
strip = static_cast<Strip *>(seqbase->first); /* Poll checks this is valid. */
seq::prefetch_stop(scene);
while (strip) {
if ((strip->flag & SELECT) && (strip->type == STRIP_TYPE_IMAGE) && (strip->len > 1)) {
Strip *strip_next;
/* TODO: remove f-curve and assign to split image strips.
* The old animation system would remove the user of `strip->ipo`. */
start_ofs = timeline_frame = seq::time_left_handle_frame_get(scene, strip);
frame_end = seq::time_right_handle_frame_get(scene, strip);
while (timeline_frame < frame_end) {
/* New strip. */
se = seq::render_give_stripelem(scene, strip, timeline_frame);
strip_new = seq::sequence_dupli_recursive(
scene, scene, seqbase, strip, STRIP_DUPE_UNIQUE_NAME);
strip_new->start = start_ofs;
strip_new->type = STRIP_TYPE_IMAGE;
strip_new->len = 1;
strip_new->flag |= SEQ_SINGLE_FRAME_CONTENT;
strip_new->endofs = 1 - step;
/* New strip. */
data_new = strip_new->data;
data_new->us = 1;
/* New stripdata, only one element now. */
/* Note this assume all elements (images) have the same dimension,
* since we only copy the name here. */
se_new = static_cast<StripElem *>(MEM_reallocN(data_new->stripdata, sizeof(*se_new)));
STRNCPY(se_new->filename, se->filename);
data_new->stripdata = se_new;
if (step > 1) {
strip_new->flag &= ~SEQ_OVERLAP;
if (seq::transform_test_overlap(scene, seqbase, strip_new)) {
seq::transform_seqbase_shuffle(seqbase, strip_new, scene);
}
}
/* XXX, COPY FCURVES */
timeline_frame++;
start_ofs += step;
}
strip_next = static_cast<Strip *>(strip->next);
seq::edit_flag_for_removal(scene, seqbase, strip);
strip = strip_next;
}
else {
strip = strip->next;
}
}
seq::edit_remove_flagged_sequences(scene, seqbase);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
static int sequencer_separate_images_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
return WM_operator_props_popup_confirm_ex(
C, op, event, IFACE_("Separate Sequence Images"), IFACE_("Separate"));
}
void SEQUENCER_OT_images_separate(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Separate Images";
ot->idname = "SEQUENCER_OT_images_separate";
ot->description = "On image sequence strips, it returns a strip for each image";
/* Api callbacks. */
ot->exec = sequencer_separate_images_exec;
ot->invoke = sequencer_separate_images_invoke;
ot->poll = sequencer_edit_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_int(ot->srna, "length", 1, 1, INT_MAX, "Length", "Length of each frame", 1, 1000);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Toggle Meta Strip Operator
* \{ */
static int sequencer_meta_toggle_exec(bContext *C, wmOperator * /*op*/)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
Strip *active_strip = seq::select_active_get(scene);
seq::prefetch_stop(scene);
if (active_strip && active_strip->type == STRIP_TYPE_META && active_strip->flag & SELECT) {
/* Deselect active meta strip. */
seq::select_active_set(scene, nullptr);
seq::meta_stack_set(scene, active_strip);
}
else {
/* Exit meta-strip if possible. */
if (BLI_listbase_is_empty(&ed->metastack)) {
return OPERATOR_CANCELLED;
}
/* Display parent meta. */
Strip *meta_parent = seq::meta_stack_pop(ed);
seq::select_active_set(scene, meta_parent);
}
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_meta_toggle(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Toggle Meta Strip";
ot->idname = "SEQUENCER_OT_meta_toggle";
ot->description = "Toggle a meta-strip (to edit enclosed strips)";
/* Api callbacks. */
ot->exec = sequencer_meta_toggle_exec;
ot->poll = sequencer_edit_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Make Meta Strip Operator
* \{ */
static int sequencer_meta_make_exec(bContext *C, wmOperator * /*op*/)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
Strip *active_strip = seq::select_active_get(scene);
ListBase *active_seqbase = seq::active_seqbase_get(ed);
blender::VectorSet<Strip *> selected = seq::query_selected_strips(active_seqbase);
if (selected.is_empty()) {
return OPERATOR_CANCELLED;
}
seq::prefetch_stop(scene);
int channel_max = 1, channel_min = INT_MAX, meta_start_frame = MAXFRAME,
meta_end_frame = MINFRAME;
Strip *seqm = seq::sequence_alloc(active_seqbase, 1, 1, STRIP_TYPE_META);
/* Remove all selected from main list, and put in meta.
* Sequence is moved within the same edit, no need to re-generate the UID. */
blender::VectorSet<Strip *> strips_to_move;
strips_to_move.add_multiple(selected);
seq::iterator_set_expand(
scene, active_seqbase, strips_to_move, seq::query_strip_connected_and_effect_chain);
for (Strip *strip : strips_to_move) {
seq::relations_invalidate_cache_preprocessed(scene, strip);
BLI_remlink(active_seqbase, strip);
BLI_addtail(&seqm->seqbase, strip);
channel_max = max_ii(strip->machine, channel_max);
channel_min = min_ii(strip->machine, channel_min);
meta_start_frame = min_ii(seq::time_left_handle_frame_get(scene, strip), meta_start_frame);
meta_end_frame = max_ii(seq::time_right_handle_frame_get(scene, strip), meta_end_frame);
}
ListBase *channels_cur = seq::channels_displayed_get(ed);
ListBase *channels_meta = &seqm->channels;
for (int i = channel_min; i <= channel_max; i++) {
SeqTimelineChannel *channel_cur = seq::channel_get_by_index(channels_cur, i);
SeqTimelineChannel *channel_meta = seq::channel_get_by_index(channels_meta, i);
STRNCPY(channel_meta->name, channel_cur->name);
channel_meta->flag = channel_cur->flag;
}
seqm->machine = active_strip ? active_strip->machine : channel_max;
BLI_strncpy(seqm->name + 2, DATA_("MetaStrip"), sizeof(seqm->name) - 2);
seq::sequence_base_unique_name_recursive(scene, &ed->seqbase, seqm);
seqm->start = meta_start_frame;
seqm->len = meta_end_frame - meta_start_frame;
seq::select_active_set(scene, seqm);
if (seq::transform_test_overlap(scene, active_seqbase, seqm)) {
seq::transform_seqbase_shuffle(active_seqbase, seqm, scene);
}
seq::strip_lookup_invalidate(ed);
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_meta_make(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Make Meta Strip";
ot->idname = "SEQUENCER_OT_meta_make";
ot->description = "Group selected strips into a meta-strip";
/* Api callbacks. */
ot->exec = sequencer_meta_make_exec;
ot->poll = sequencer_edit_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name UnMeta Strip Operator
* \{ */
static int sequencer_meta_separate_exec(bContext *C, wmOperator * /*op*/)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
Strip *active_strip = seq::select_active_get(scene);
if (active_strip == nullptr || active_strip->type != STRIP_TYPE_META) {
return OPERATOR_CANCELLED;
}
seq::prefetch_stop(scene);
LISTBASE_FOREACH (Strip *, strip, &active_strip->seqbase) {
seq::relations_invalidate_cache_preprocessed(scene, strip);
}
/* Remove all selected from meta, and put in main list.
* Sequence is moved within the same edit, no need to re-generate the UID. */
BLI_movelisttolist(ed->seqbasep, &active_strip->seqbase);
BLI_listbase_clear(&active_strip->seqbase);
ListBase *active_seqbase = seq::active_seqbase_get(ed);
seq::edit_flag_for_removal(scene, active_seqbase, active_strip);
seq::edit_remove_flagged_sequences(scene, active_seqbase);
/* Test for effects and overlap. */
LISTBASE_FOREACH (Strip *, strip, active_seqbase) {
if (strip->flag & SELECT) {
strip->flag &= ~SEQ_OVERLAP;
if (seq::transform_test_overlap(scene, active_seqbase, strip)) {
seq::transform_seqbase_shuffle(active_seqbase, strip, scene);
}
}
}
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_meta_separate(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "UnMeta Strip";
ot->idname = "SEQUENCER_OT_meta_separate";
ot->description = "Put the contents of a meta-strip back in the sequencer";
/* Api callbacks. */
ot->exec = sequencer_meta_separate_exec;
ot->poll = sequencer_edit_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Jump to Strip Operator
* \{ */
static bool strip_jump_internal(Scene *scene,
const short side,
const bool do_skip_mute,
const bool do_center)
{
bool changed = false;
int timeline_frame = scene->r.cfra;
int next_frame = seq::time_find_next_prev_edit(
scene, timeline_frame, side, do_skip_mute, do_center, false);
if (next_frame != timeline_frame) {
scene->r.cfra = next_frame;
changed = true;
}
return changed;
}
static bool sequencer_strip_jump_poll(bContext *C)
{
/* Prevent changes during render. */
if (G.is_rendering) {
return false;
}
return sequencer_edit_poll(C);
}
static int sequencer_strip_jump_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
const bool next = RNA_boolean_get(op->ptr, "next");
const bool center = RNA_boolean_get(op->ptr, "center");
/* Currently do_skip_mute is always true. */
if (!strip_jump_internal(scene, next ? seq::SIDE_RIGHT : seq::SIDE_LEFT, true, center)) {
return OPERATOR_CANCELLED;
}
DEG_id_tag_update(&scene->id, ID_RECALC_FRAME_CHANGE);
WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_strip_jump(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Jump to Strip";
ot->idname = "SEQUENCER_OT_strip_jump";
ot->description = "Move frame to previous edit point";
/* Api callbacks. */
ot->exec = sequencer_strip_jump_exec;
ot->poll = sequencer_strip_jump_poll;
/* Flags. */
ot->flag = OPTYPE_UNDO;
/* Properties. */
RNA_def_boolean(ot->srna, "next", true, "Next Strip", "");
RNA_def_boolean(ot->srna, "center", true, "Use Strip Center", "");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Swap Strip Operator
* \{ */
static const EnumPropertyItem prop_side_lr_types[] = {
{seq::SIDE_LEFT, "LEFT", 0, "Left", ""},
{seq::SIDE_RIGHT, "RIGHT", 0, "Right", ""},
{0, nullptr, 0, nullptr, nullptr},
};
static void swap_sequence(Scene *scene, Strip *seqa, Strip *seqb)
{
int gap = seq::time_left_handle_frame_get(scene, seqb) -
seq::time_right_handle_frame_get(scene, seqa);
int strip_a_start;
int strip_b_start;
strip_b_start = (seqb->start - seq::time_left_handle_frame_get(scene, seqb)) +
seq::time_left_handle_frame_get(scene, seqa);
seq::transform_translate_sequence(scene, seqb, strip_b_start - seqb->start);
seq::relations_invalidate_cache_preprocessed(scene, seqb);
strip_a_start = (seqa->start - seq::time_left_handle_frame_get(scene, seqa)) +
seq::time_right_handle_frame_get(scene, seqb) + gap;
seq::transform_translate_sequence(scene, seqa, strip_a_start - seqa->start);
seq::relations_invalidate_cache_preprocessed(scene, seqa);
}
static Strip *find_next_prev_sequence(Scene *scene, Strip *test, int lr, int sel)
{
/* sel: 0==unselected, 1==selected, -1==don't care. */
Strip *strip, *best_strip = nullptr;
Editing *ed = seq::editing_get(scene);
int dist, best_dist;
best_dist = MAXFRAME * 2;
if (ed == nullptr) {
return nullptr;
}
strip = static_cast<Strip *>(ed->seqbasep->first);
while (strip) {
if ((strip != test) && (test->machine == strip->machine) &&
((sel == -1) || (sel == (strip->flag & SELECT))))
{
dist = MAXFRAME * 2;
switch (lr) {
case seq::SIDE_LEFT:
if (seq::time_right_handle_frame_get(scene, strip) <=
seq::time_left_handle_frame_get(scene, test))
{
dist = seq::time_right_handle_frame_get(scene, test) -
seq::time_left_handle_frame_get(scene, strip);
}
break;
case seq::SIDE_RIGHT:
if (seq::time_left_handle_frame_get(scene, strip) >=
seq::time_right_handle_frame_get(scene, test))
{
dist = seq::time_left_handle_frame_get(scene, strip) -
seq::time_right_handle_frame_get(scene, test);
}
break;
}
if (dist == 0) {
best_strip = strip;
break;
}
if (dist < best_dist) {
best_dist = dist;
best_strip = strip;
}
}
strip = static_cast<Strip *>(strip->next);
}
return best_strip; /* Can be nullptr. */
}
static bool strip_is_parent(const Strip *par, const Strip *strip)
{
return ((par->seq1 == strip) || (par->seq2 == strip));
}
static int sequencer_swap_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
Strip *active_seq = seq::select_active_get(scene);
ListBase *seqbase = seq::active_seqbase_get(ed);
Strip *strip;
int side = RNA_enum_get(op->ptr, "side");
if (active_seq == nullptr) {
return OPERATOR_CANCELLED;
}
strip = find_next_prev_sequence(scene, active_seq, side, -1);
if (strip) {
/* Disallow effect strips. */
if (seq::effect_get_num_inputs(strip->type) >= 1 &&
(strip->effectdata || strip->seq1 || strip->seq2))
{
return OPERATOR_CANCELLED;
}
if ((seq::effect_get_num_inputs(active_seq->type) >= 1) &&
(active_seq->effectdata || active_seq->seq1 || active_seq->seq2))
{
return OPERATOR_CANCELLED;
}
ListBase *channels = seq::channels_displayed_get(seq::editing_get(scene));
if (seq::transform_is_locked(channels, strip) ||
seq::transform_is_locked(channels, active_seq))
{
return OPERATOR_CANCELLED;
}
switch (side) {
case seq::SIDE_LEFT:
swap_sequence(scene, strip, active_seq);
break;
case seq::SIDE_RIGHT:
swap_sequence(scene, active_seq, strip);
break;
}
/* Do this in a new loop since both effects need to be calculated first. */
LISTBASE_FOREACH (Strip *, iseq, seqbase) {
if ((iseq->type & STRIP_TYPE_EFFECT) &&
(strip_is_parent(iseq, active_seq) || strip_is_parent(iseq, strip)))
{
/* This may now overlap. */
if (seq::transform_test_overlap(scene, seqbase, iseq)) {
seq::transform_seqbase_shuffle(seqbase, iseq, scene);
}
}
}
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
return OPERATOR_CANCELLED;
}
void SEQUENCER_OT_swap(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Swap Strip";
ot->idname = "SEQUENCER_OT_swap";
ot->description = "Swap active strip with strip to the right or left";
/* Api callbacks. */
ot->exec = sequencer_swap_exec;
ot->poll = sequencer_edit_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* Properties. */
RNA_def_enum(
ot->srna, "side", prop_side_lr_types, seq::SIDE_RIGHT, "Side", "Side of the strip to swap");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Set Render Size Operator
* \{ */
static int sequencer_rendersize_exec(bContext *C, wmOperator * /*op*/)
{
Scene *scene = CTX_data_scene(C);
Strip *active_seq = seq::select_active_get(scene);
StripElem *se = nullptr;
if (active_seq == nullptr || active_seq->data == nullptr) {
return OPERATOR_CANCELLED;
}
switch (active_seq->type) {
case STRIP_TYPE_IMAGE:
se = seq::render_give_stripelem(scene, active_seq, scene->r.cfra);
break;
case STRIP_TYPE_MOVIE:
se = active_seq->data->stripdata;
break;
default:
return OPERATOR_CANCELLED;
}
if (se == nullptr) {
return OPERATOR_CANCELLED;
}
/* Prevent setting the render size if sequence values aren't initialized. */
if (se->orig_width <= 0 || se->orig_height <= 0) {
return OPERATOR_CANCELLED;
}
scene->r.xsch = se->orig_width;
scene->r.ysch = se->orig_height;
active_seq->data->transform->scale_x = active_seq->data->transform->scale_y = 1.0f;
active_seq->data->transform->xofs = active_seq->data->transform->yofs = 0.0f;
seq::relations_invalidate_cache_preprocessed(scene, active_seq);
WM_event_add_notifier(C, NC_SCENE | ND_RENDER_OPTIONS, scene);
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_SEQUENCER, nullptr);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_rendersize(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Set Render Size";
ot->idname = "SEQUENCER_OT_rendersize";
ot->description = "Set render size and aspect from active sequence";
/* Api callbacks. */
ot->exec = sequencer_rendersize_exec;
ot->poll = sequencer_edit_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Copy Operator
* \{ */
void SEQUENCER_OT_copy(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Copy";
ot->idname = "SEQUENCER_OT_copy";
ot->description = "Copy the selected strips to the internal clipboard";
/* Api callbacks. */
ot->exec = sequencer_clipboard_copy_exec;
ot->poll = sequencer_edit_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Paste Operator
* \{ */
bool deselect_all_strips(Scene *scene)
{
Editing *ed = seq::editing_get(scene);
bool changed = false;
if (ed == nullptr) {
return changed;
}
LISTBASE_FOREACH (Strip *, strip, seq::active_seqbase_get(ed)) {
if (strip->flag & STRIP_ALLSEL) {
strip->flag &= ~STRIP_ALLSEL;
changed = true;
}
}
return changed;
}
void SEQUENCER_OT_paste(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Paste";
ot->idname = "SEQUENCER_OT_paste";
ot->description = "Paste strips from the internal clipboard";
/* Api callbacks. */
ot->exec = sequencer_clipboard_paste_exec;
ot->poll = ED_operator_sequencer_active;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* Properties. */
PropertyRNA *prop = RNA_def_boolean(
ot->srna,
"keep_offset",
false,
"Keep Offset",
"Keep strip offset relative to the current frame when pasting");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Sequencer Swap Data Operator
* \{ */
static int sequencer_swap_data_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Strip *strip_act;
Strip *strip_other;
const char *error_msg;
if (seq::select_active_get_pair(scene, &strip_act, &strip_other) == false) {
BKE_report(op->reports, RPT_ERROR, "Please select two strips");
return OPERATOR_CANCELLED;
}
if (seq::edit_sequence_swap(scene, strip_act, strip_other, &error_msg) == false) {
BKE_report(op->reports, RPT_ERROR, error_msg);
return OPERATOR_CANCELLED;
}
if (strip_act->scene_sound) {
BKE_sound_remove_scene_sound(scene, strip_act->scene_sound);
}
if (strip_other->scene_sound) {
BKE_sound_remove_scene_sound(scene, strip_other->scene_sound);
}
strip_act->scene_sound = nullptr;
strip_other->scene_sound = nullptr;
if (strip_act->sound) {
BKE_sound_add_scene_sound_defaults(scene, strip_act);
}
if (strip_other->sound) {
BKE_sound_add_scene_sound_defaults(scene, strip_other);
}
seq::relations_invalidate_cache_raw(scene, strip_act);
seq::relations_invalidate_cache_raw(scene, strip_other);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_swap_data(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Sequencer Swap Data";
ot->idname = "SEQUENCER_OT_swap_data";
ot->description = "Swap 2 sequencer strips";
/* Api callbacks. */
ot->exec = sequencer_swap_data_exec;
ot->poll = ED_operator_sequencer_active;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Change Effect Input Operator
* \{ */
static int sequencer_change_effect_input_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Strip *strip = seq::select_active_get(scene);
Strip **strip_1 = &strip->seq1, **strip_2 = &strip->seq2;
if (*strip_1 == nullptr || *strip_2 == nullptr) {
BKE_report(op->reports, RPT_ERROR, "One of the effect inputs is unset, cannot swap");
return OPERATOR_CANCELLED;
}
std::swap(*strip_1, *strip_2);
seq::relations_invalidate_cache_preprocessed(scene, strip);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_change_effect_input(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Change Effect Input";
ot->idname = "SEQUENCER_OT_change_effect_input";
/* Api callbacks. */
ot->exec = sequencer_change_effect_input_exec;
ot->poll = sequencer_effect_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Change Effect Type Operator
* \{ */
const EnumPropertyItem sequencer_prop_effect_types[] = {
{STRIP_TYPE_CROSS, "CROSS", 0, "Crossfade", "Crossfade effect strip type"},
{STRIP_TYPE_ADD, "ADD", 0, "Add", "Add effect strip type"},
{STRIP_TYPE_SUB, "SUBTRACT", 0, "Subtract", "Subtract effect strip type"},
{STRIP_TYPE_ALPHAOVER, "ALPHA_OVER", 0, "Alpha Over", "Alpha Over effect strip type"},
{STRIP_TYPE_ALPHAUNDER, "ALPHA_UNDER", 0, "Alpha Under", "Alpha Under effect strip type"},
{STRIP_TYPE_GAMCROSS, "GAMMA_CROSS", 0, "Gamma Cross", "Gamma Cross effect strip type"},
{STRIP_TYPE_MUL, "MULTIPLY", 0, "Multiply", "Multiply effect strip type"},
{STRIP_TYPE_WIPE, "WIPE", 0, "Wipe", "Wipe effect strip type"},
{STRIP_TYPE_GLOW, "GLOW", 0, "Glow", "Glow effect strip type"},
{STRIP_TYPE_TRANSFORM, "TRANSFORM", 0, "Transform", "Transform effect strip type"},
{STRIP_TYPE_COLOR, "COLOR", 0, "Color", "Color effect strip type"},
{STRIP_TYPE_SPEED, "SPEED", 0, "Speed", "Color effect strip type"},
{STRIP_TYPE_MULTICAM, "MULTICAM", 0, "Multicam Selector", ""},
{STRIP_TYPE_ADJUSTMENT, "ADJUSTMENT", 0, "Adjustment Layer", ""},
{STRIP_TYPE_GAUSSIAN_BLUR, "GAUSSIAN_BLUR", 0, "Gaussian Blur", ""},
{STRIP_TYPE_TEXT, "TEXT", 0, "Text", ""},
{STRIP_TYPE_COLORMIX, "COLORMIX", 0, "Color Mix", ""},
{0, nullptr, 0, nullptr, nullptr},
};
static int sequencer_change_effect_type_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Strip *strip = seq::select_active_get(scene);
const int new_type = RNA_enum_get(op->ptr, "type");
/* Free previous effect and init new effect. */
seq::EffectHandle sh;
if ((strip->type & STRIP_TYPE_EFFECT) == 0) {
return OPERATOR_CANCELLED;
}
/* Can someone explain the logic behind only allowing to increase this,
* copied from 2.4x - campbell */
if (seq::effect_get_num_inputs(strip->type) < seq::effect_get_num_inputs(new_type)) {
BKE_report(op->reports, RPT_ERROR, "New effect needs more input strips");
return OPERATOR_CANCELLED;
}
sh = seq::effect_handle_get(strip);
sh.free(strip, true);
strip->type = new_type;
sh = seq::effect_handle_get(strip);
sh.init(strip);
seq::relations_invalidate_cache_preprocessed(scene, strip);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_change_effect_type(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Change Effect Type";
ot->idname = "SEQUENCER_OT_change_effect_type";
/* Api callbacks. */
ot->exec = sequencer_change_effect_type_exec;
ot->poll = sequencer_effect_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
ot->prop = RNA_def_enum(ot->srna,
"type",
sequencer_prop_effect_types,
STRIP_TYPE_CROSS,
"Type",
"Sequencer effect type");
RNA_def_property_translation_context(ot->prop, BLT_I18NCONTEXT_ID_SEQUENCE);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Change Data/Files Operator
* \{ */
static int sequencer_change_path_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
Strip *strip = seq::select_active_get(scene);
const bool is_relative_path = RNA_boolean_get(op->ptr, "relative_path");
const bool use_placeholders = RNA_boolean_get(op->ptr, "use_placeholders");
int minext_frameme, numdigits;
if (strip->type == STRIP_TYPE_IMAGE) {
char directory[FILE_MAX];
int len;
StripElem *se;
/* Need to find min/max frame for placeholders. */
if (use_placeholders) {
len = sequencer_image_seq_get_minmax_frame(op, strip->sfra, &minext_frameme, &numdigits);
}
else {
len = RNA_property_collection_length(op->ptr, RNA_struct_find_property(op->ptr, "files"));
}
if (len == 0) {
return OPERATOR_CANCELLED;
}
RNA_string_get(op->ptr, "directory", directory);
if (is_relative_path) {
/* TODO(@ideasman42): shouldn't this already be relative from the filesel?
* (as the 'filepath' is) for now just make relative here,
* but look into changing after 2.60. */
BLI_path_rel(directory, BKE_main_blendfile_path(bmain));
}
STRNCPY(strip->data->dirpath, directory);
if (strip->data->stripdata) {
MEM_freeN(strip->data->stripdata);
}
strip->data->stripdata = se = MEM_calloc_arrayN<StripElem>(len, "stripelem");
if (use_placeholders) {
sequencer_image_seq_reserve_frames(op, se, len, minext_frameme, numdigits);
}
else {
RNA_BEGIN (op->ptr, itemptr, "files") {
char *filename = RNA_string_get_alloc(&itemptr, "name", nullptr, 0, nullptr);
STRNCPY(se->filename, filename);
MEM_freeN(filename);
se++;
}
RNA_END;
}
if (len == 1) {
strip->flag |= SEQ_SINGLE_FRAME_CONTENT;
}
else {
strip->flag &= ~SEQ_SINGLE_FRAME_CONTENT;
}
/* Reset these else we won't see all the images. */
strip->anim_startofs = strip->anim_endofs = 0;
/* Correct start/end frames so we don't move.
* Important not to set strip->len = len; allow the function to handle it. */
seq::add_reload_new_file(bmain, scene, strip, true);
}
else if (strip->type == STRIP_TYPE_SOUND_RAM) {
bSound *sound = strip->sound;
if (sound == nullptr) {
return OPERATOR_CANCELLED;
}
char filepath[FILE_MAX];
RNA_string_get(op->ptr, "filepath", filepath);
STRNCPY(sound->filepath, filepath);
BKE_sound_load(bmain, sound);
}
else {
/* Lame, set rna filepath. */
PropertyRNA *prop;
char filepath[FILE_MAX];
PointerRNA strip_ptr = RNA_pointer_create_discrete(&scene->id, &RNA_Strip, strip);
RNA_string_get(op->ptr, "filepath", filepath);
prop = RNA_struct_find_property(&strip_ptr, "filepath");
RNA_property_string_set(&strip_ptr, prop, filepath);
RNA_property_update(C, &strip_ptr, prop);
seq::relations_sequence_free_anim(strip);
}
seq::relations_invalidate_cache_raw(scene, strip);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
static int sequencer_change_path_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
Scene *scene = CTX_data_scene(C);
Strip *strip = seq::select_active_get(scene);
char filepath[FILE_MAX];
BLI_path_join(
filepath, sizeof(filepath), strip->data->dirpath, strip->data->stripdata->filename);
RNA_string_set(op->ptr, "directory", strip->data->dirpath);
RNA_string_set(op->ptr, "filepath", filepath);
/* Set default display depending on strip type. */
if (strip->type == STRIP_TYPE_IMAGE) {
RNA_boolean_set(op->ptr, "filter_movie", false);
}
else {
RNA_boolean_set(op->ptr, "filter_image", false);
}
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
void SEQUENCER_OT_change_path(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Change Data/Files";
ot->idname = "SEQUENCER_OT_change_path";
/* Api callbacks. */
ot->exec = sequencer_change_path_exec;
ot->invoke = sequencer_change_path_invoke;
ot->poll = sequencer_strip_has_path_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER,
FILE_SPECIAL,
FILE_OPENFILE,
WM_FILESEL_DIRECTORY | WM_FILESEL_RELPATH | WM_FILESEL_FILEPATH |
WM_FILESEL_FILES,
FILE_DEFAULTDISPLAY,
FILE_SORT_DEFAULT);
RNA_def_boolean(ot->srna,
"use_placeholders",
false,
"Use Placeholders",
"Use placeholders for missing frames of the strip");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Change Strip Scene Operator
* \{ */
static bool sequencer_strip_change_scene_poll(bContext *C)
{
Editing *ed = seq::editing_get(CTX_data_scene(C));
if (ed == nullptr) {
return false;
}
Strip *strip = ed->act_seq;
return ((strip != nullptr) && (strip->type == STRIP_TYPE_SCENE));
}
static int sequencer_change_scene_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
Scene *scene_seq = static_cast<Scene *>(
BLI_findlink(&bmain->scenes, RNA_enum_get(op->ptr, "scene")));
if (scene_seq == nullptr) {
BKE_report(op->reports, RPT_ERROR, "Scene not found");
return OPERATOR_CANCELLED;
}
/* Assign new scene. */
Strip *strip = seq::select_active_get(scene);
if (strip) {
strip->scene = scene_seq;
/* Do a refresh of the sequencer data. */
seq::relations_invalidate_cache_raw(scene, strip);
DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO | ID_RECALC_SEQUENCER_STRIPS);
DEG_relations_tag_update(bmain);
}
WM_event_add_notifier(C, NC_SCENE | ND_SCENEBROWSE, scene);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
static int sequencer_change_scene_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
if (!RNA_struct_property_is_set(op->ptr, "scene")) {
return WM_enum_search_invoke(C, op, event);
}
return sequencer_change_scene_exec(C, op);
}
void SEQUENCER_OT_change_scene(wmOperatorType *ot)
{
PropertyRNA *prop;
/* Identifiers. */
ot->name = "Change Scene";
ot->idname = "SEQUENCER_OT_change_scene";
ot->description = "Change Scene assigned to Strip";
/* Api callbacks. */
ot->exec = sequencer_change_scene_exec;
ot->invoke = sequencer_change_scene_invoke;
ot->poll = sequencer_strip_change_scene_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* Properties. */
prop = RNA_def_enum(ot->srna, "scene", rna_enum_dummy_NULL_items, 0, "Scene", "");
RNA_def_enum_funcs(prop, RNA_scene_without_active_itemf);
RNA_def_property_flag(prop, PROP_ENUM_NO_TRANSLATE);
ot->prop = prop;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Export Subtitles Operator
* \{ */
/** Comparison function suitable to be used with BLI_listbase_sort(). */
static int strip_cmp_time_startdisp_channel(void *thunk, const void *a, const void *b)
{
const Scene *scene = static_cast<Scene *>(thunk);
const Strip *strip_a = static_cast<const Strip *>(a);
const Strip *strip_b = static_cast<const Strip *>(b);
int strip_a_start = seq::time_left_handle_frame_get(scene, strip_a);
int strip_b_start = seq::time_left_handle_frame_get(scene, strip_b);
/* If strips have the same start frame favor the one with a higher channel. */
if (strip_a_start == strip_b_start) {
return strip_a->machine > strip_b->machine;
}
return (strip_a_start > strip_b_start);
}
static int sequencer_export_subtitles_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
ED_fileselect_ensure_default_filepath(C, op, ".srt");
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
struct Seq_get_text_cb_data {
ListBase *text_seq;
Scene *scene;
};
static bool strip_get_text_strip_cb(Strip *strip, void *user_data)
{
Seq_get_text_cb_data *cd = (Seq_get_text_cb_data *)user_data;
Editing *ed = seq::editing_get(cd->scene);
ListBase *channels = seq::channels_displayed_get(ed);
/* Only text strips that are not muted and don't end with negative frame. */
if ((strip->type == STRIP_TYPE_TEXT) && !seq::render_is_muted(channels, strip) &&
(seq::time_right_handle_frame_get(cd->scene, strip) > cd->scene->r.sfra))
{
BLI_addtail(cd->text_seq, MEM_dupallocN(strip));
}
return true;
}
static int sequencer_export_subtitles_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Strip *strip, *strip_next;
Editing *ed = seq::editing_get(scene);
ListBase text_seq = {nullptr};
int iter = 1; /* Sequence numbers in `.srt` files are 1-indexed. */
FILE *file;
char filepath[FILE_MAX];
if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) {
BKE_report(op->reports, RPT_ERROR, "No filepath given");
return OPERATOR_CANCELLED;
}
RNA_string_get(op->ptr, "filepath", filepath);
BLI_path_extension_ensure(filepath, sizeof(filepath), ".srt");
/* Avoid File write exceptions. */
if (!BLI_exists(filepath)) {
BLI_file_ensure_parent_dir_exists(filepath);
if (!BLI_file_touch(filepath)) {
BKE_report(op->reports, RPT_ERROR, "Can't create subtitle file");
return OPERATOR_CANCELLED;
}
}
else if (!BLI_file_is_writable(filepath)) {
BKE_report(op->reports, RPT_ERROR, "Can't overwrite export file");
return OPERATOR_CANCELLED;
}
if (ed != nullptr) {
Seq_get_text_cb_data cb_data = {&text_seq, scene};
seq::for_each_callback(&ed->seqbase, strip_get_text_strip_cb, &cb_data);
}
if (BLI_listbase_is_empty(&text_seq)) {
BKE_report(op->reports, RPT_ERROR, "No subtitles (text strips) to export");
return OPERATOR_CANCELLED;
}
BLI_listbase_sort_r(&text_seq, strip_cmp_time_startdisp_channel, scene);
/* Open and write file. */
file = BLI_fopen(filepath, "w");
for (strip = static_cast<Strip *>(text_seq.first); strip; strip = strip_next) {
TextVars *data = static_cast<TextVars *>(strip->effectdata);
char timecode_str_start[32];
char timecode_str_end[32];
/* Write time-code relative to start frame of scene. Don't allow negative time-codes. */
BLI_timecode_string_from_time(
timecode_str_start,
sizeof(timecode_str_start),
-2,
FRA2TIME(max_ii(seq::time_left_handle_frame_get(scene, strip) - scene->r.sfra, 0)),
FPS,
USER_TIMECODE_SUBRIP);
BLI_timecode_string_from_time(
timecode_str_end,
sizeof(timecode_str_end),
-2,
FRA2TIME(seq::time_right_handle_frame_get(scene, strip) - scene->r.sfra),
FPS,
USER_TIMECODE_SUBRIP);
fprintf(
file, "%d\n%s --> %s\n%s\n\n", iter++, timecode_str_start, timecode_str_end, data->text);
strip_next = static_cast<Strip *>(strip->next);
MEM_freeN(strip);
}
fclose(file);
return OPERATOR_FINISHED;
}
static bool sequencer_strip_is_text_poll(bContext *C)
{
Editing *ed;
Strip *strip;
return (((ed = seq::editing_get(CTX_data_scene(C))) != nullptr) &&
((strip = ed->act_seq) != nullptr) && (strip->type == STRIP_TYPE_TEXT));
}
void SEQUENCER_OT_export_subtitles(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Export Subtitles";
ot->idname = "SEQUENCER_OT_export_subtitles";
ot->description = "Export .srt file containing text strips";
/* Api callbacks. */
ot->exec = sequencer_export_subtitles_exec;
ot->invoke = sequencer_export_subtitles_invoke;
ot->poll = sequencer_strip_is_text_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER,
FILE_BLENDER,
FILE_SAVE,
WM_FILESEL_FILEPATH,
FILE_DEFAULTDISPLAY,
FILE_SORT_DEFAULT);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Set Range to Strips Operator
* \{ */
static int sequencer_set_range_to_strips_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
int sfra = MAXFRAME;
int efra = -MAXFRAME;
bool selected = false;
const bool preview = RNA_boolean_get(op->ptr, "preview");
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
if (strip->flag & SELECT) {
selected = true;
sfra = min_ii(sfra, seq::time_left_handle_frame_get(scene, strip));
/* Offset of -1 is needed because in the sequencer every frame has width.
* Range from 1 to 1 is drawn as range 1 to 2, because 1 frame long strip starts at frame 1
* and ends at frame 2. See #106480. */
efra = max_ii(efra, seq::time_right_handle_frame_get(scene, strip) - 1);
}
}
if (!selected) {
BKE_report(op->reports, RPT_WARNING, "Select one or more strips");
return OPERATOR_CANCELLED;
}
if (efra < 0) {
BKE_report(op->reports, RPT_ERROR, "Can't set a negative range");
return OPERATOR_CANCELLED;
}
if (preview) {
scene->r.flag |= SCER_PRV_RANGE;
scene->r.psfra = max_ii(0, sfra);
scene->r.pefra = efra;
}
else {
scene->r.flag &= ~SCER_PRV_RANGE;
scene->r.sfra = max_ii(0, sfra);
scene->r.efra = efra;
}
WM_event_add_notifier(C, NC_SCENE | ND_FRAME_RANGE, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_set_range_to_strips(wmOperatorType *ot)
{
PropertyRNA *prop;
/* Identifiers. */
ot->name = "Set Range to Strips";
ot->idname = "SEQUENCER_OT_set_range_to_strips";
ot->description = "Set the frame range to the selected strips start and end";
/* Api callbacks. */
ot->exec = sequencer_set_range_to_strips_exec;
ot->poll = sequencer_edit_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
prop = RNA_def_boolean(ot->srna, "preview", false, "Preview", "Set the preview range instead");
RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Clear Strip Transform Operator
* \{ */
enum {
STRIP_TRANSFORM_POSITION,
STRIP_TRANSFORM_SCALE,
STRIP_TRANSFORM_ROTATION,
STRIP_TRANSFORM_ALL,
};
static const EnumPropertyItem transform_reset_properties[] = {
{STRIP_TRANSFORM_POSITION, "POSITION", 0, "Position", "Reset strip transform location"},
{STRIP_TRANSFORM_SCALE, "SCALE", 0, "Scale", "Reset strip transform scale"},
{STRIP_TRANSFORM_ROTATION, "ROTATION", 0, "Rotation", "Reset strip transform rotation"},
{STRIP_TRANSFORM_ALL, "ALL", 0, "All", "Reset strip transform location, scale and rotation"},
{0, nullptr, 0, nullptr, nullptr},
};
static int sequencer_strip_transform_clear_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
const Editing *ed = seq::editing_get(scene);
const int property = RNA_enum_get(op->ptr, "property");
const bool use_autokeyframe = blender::animrig::is_autokey_on(scene);
const bool only_when_keyed = blender::animrig::is_keying_flag(scene,
AUTOKEY_FLAG_INSERTAVAILABLE);
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
if (strip->flag & SELECT && strip->type != STRIP_TYPE_SOUND_RAM) {
StripTransform *transform = strip->data->transform;
PropertyRNA *prop;
PointerRNA ptr = RNA_pointer_create_discrete(&scene->id, &RNA_StripTransform, transform);
switch (property) {
case STRIP_TRANSFORM_POSITION:
transform->xofs = 0;
transform->yofs = 0;
if (use_autokeyframe) {
prop = RNA_struct_find_property(&ptr, "offset_x");
blender::animrig::autokeyframe_property(
C, scene, &ptr, prop, -1, scene->r.cfra, only_when_keyed);
prop = RNA_struct_find_property(&ptr, "offset_y");
blender::animrig::autokeyframe_property(
C, scene, &ptr, prop, -1, scene->r.cfra, only_when_keyed);
}
break;
case STRIP_TRANSFORM_SCALE:
transform->scale_x = 1.0f;
transform->scale_y = 1.0f;
if (use_autokeyframe) {
prop = RNA_struct_find_property(&ptr, "scale_x");
blender::animrig::autokeyframe_property(
C, scene, &ptr, prop, -1, scene->r.cfra, only_when_keyed);
prop = RNA_struct_find_property(&ptr, "scale_y");
blender::animrig::autokeyframe_property(
C, scene, &ptr, prop, -1, scene->r.cfra, only_when_keyed);
}
break;
case STRIP_TRANSFORM_ROTATION:
transform->rotation = 0.0f;
if (use_autokeyframe) {
prop = RNA_struct_find_property(&ptr, "rotation");
blender::animrig::autokeyframe_property(
C, scene, &ptr, prop, -1, scene->r.cfra, only_when_keyed);
}
break;
case STRIP_TRANSFORM_ALL:
transform->xofs = 0;
transform->yofs = 0;
transform->scale_x = 1.0f;
transform->scale_y = 1.0f;
transform->rotation = 0.0f;
if (use_autokeyframe) {
prop = RNA_struct_find_property(&ptr, "offset_x");
blender::animrig::autokeyframe_property(
C, scene, &ptr, prop, -1, scene->r.cfra, only_when_keyed);
prop = RNA_struct_find_property(&ptr, "offset_y");
blender::animrig::autokeyframe_property(
C, scene, &ptr, prop, -1, scene->r.cfra, only_when_keyed);
prop = RNA_struct_find_property(&ptr, "scale_x");
blender::animrig::autokeyframe_property(
C, scene, &ptr, prop, -1, scene->r.cfra, only_when_keyed);
prop = RNA_struct_find_property(&ptr, "scale_y");
blender::animrig::autokeyframe_property(
C, scene, &ptr, prop, -1, scene->r.cfra, only_when_keyed);
prop = RNA_struct_find_property(&ptr, "rotation");
blender::animrig::autokeyframe_property(
C, scene, &ptr, prop, -1, scene->r.cfra, only_when_keyed);
}
break;
}
seq::relations_invalidate_cache_preprocessed(scene, strip);
}
}
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_strip_transform_clear(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Clear Strip Transform";
ot->idname = "SEQUENCER_OT_strip_transform_clear";
ot->description = "Reset image transformation to default value";
/* Api callbacks. */
ot->exec = sequencer_strip_transform_clear_exec;
ot->poll = sequencer_edit_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
ot->prop = RNA_def_enum(ot->srna,
"property",
transform_reset_properties,
STRIP_TRANSFORM_ALL,
"Property",
"Strip transform property to be reset");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Transform Set Fit Operator
* \{ */
static const EnumPropertyItem scale_fit_methods[] = {
{SEQ_SCALE_TO_FIT, "FIT", 0, "Scale to Fit", "Scale image so fits in preview"},
{SEQ_SCALE_TO_FILL, "FILL", 0, "Scale to Fill", "Scale image so it fills preview completely"},
{SEQ_STRETCH_TO_FILL, "STRETCH", 0, "Stretch to Fill", "Stretch image so it fills preview"},
{0, nullptr, 0, nullptr, nullptr},
};
static int sequencer_strip_transform_fit_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
const Editing *ed = seq::editing_get(scene);
const eSeqImageFitMethod fit_method = eSeqImageFitMethod(RNA_enum_get(op->ptr, "fit_method"));
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
if (strip->flag & SELECT && strip->type != STRIP_TYPE_SOUND_RAM) {
const int timeline_frame = scene->r.cfra;
StripElem *strip_elem = seq::render_give_stripelem(scene, strip, timeline_frame);
if (strip_elem == nullptr) {
continue;
}
seq::set_scale_to_fit(strip,
strip_elem->orig_width,
strip_elem->orig_height,
scene->r.xsch,
scene->r.ysch,
fit_method);
seq::relations_invalidate_cache_preprocessed(scene, strip);
}
}
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_strip_transform_fit(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Strip Transform Set Fit";
ot->idname = "SEQUENCER_OT_strip_transform_fit";
/* Api callbacks. */
ot->exec = sequencer_strip_transform_fit_exec;
ot->poll = sequencer_edit_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
ot->prop = RNA_def_enum(ot->srna,
"fit_method",
scale_fit_methods,
SEQ_SCALE_TO_FIT,
"Fit Method",
"Scale fit fit_method");
}
static int sequencer_strip_color_tag_set_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
const Editing *ed = seq::editing_get(scene);
const short color_tag = RNA_enum_get(op->ptr, "color");
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
if (strip->flag & SELECT) {
strip->color_tag = color_tag;
}
}
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
static bool sequencer_strip_color_tag_set_poll(bContext *C)
{
Scene *scene = CTX_data_scene(C);
if (scene == nullptr) {
return false;
}
Editing *ed = seq::editing_get(scene);
if (ed == nullptr) {
return false;
}
Strip *act_seq = ed->act_seq;
return act_seq != nullptr;
}
void SEQUENCER_OT_strip_color_tag_set(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Set Color Tag";
ot->idname = "SEQUENCER_OT_strip_color_tag_set";
ot->description = "Set a color tag for the selected strips";
/* Api callbacks. */
ot->exec = sequencer_strip_color_tag_set_exec;
ot->poll = sequencer_strip_color_tag_set_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_enum(ot->srna, "color", rna_enum_strip_color_items, STRIP_COLOR_NONE, "Color Tag", "");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Set 2D Cursor Operator
* \{ */
static int sequencer_set_2d_cursor_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
SpaceSeq *sseq = CTX_wm_space_seq(C);
float cursor_pixel[2];
RNA_float_get_array(op->ptr, "location", cursor_pixel);
blender::float2 cursor_region = seq::image_preview_unit_from_px(scene, cursor_pixel);
copy_v2_v2(sseq->cursor, cursor_region);
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_SEQUENCER, nullptr);
/* Use pass-through to allow click-drag to transform the cursor. */
return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;
}
static int sequencer_set_2d_cursor_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
ARegion *region = CTX_wm_region(C);
float cursor_pixel[2];
UI_view2d_region_to_view(
&region->v2d, event->mval[0], event->mval[1], &cursor_pixel[0], &cursor_pixel[1]);
RNA_float_set_array(op->ptr, "location", cursor_pixel);
return sequencer_set_2d_cursor_exec(C, op);
}
void SEQUENCER_OT_cursor_set(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Set 2D Cursor";
ot->description = "Set 2D cursor location";
ot->idname = "SEQUENCER_OT_cursor_set";
/* api callbacks */
ot->exec = sequencer_set_2d_cursor_exec;
ot->invoke = sequencer_set_2d_cursor_invoke;
ot->poll = sequencer_view_has_preview_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
RNA_def_float_vector(ot->srna,
"location",
2,
nullptr,
-FLT_MAX,
FLT_MAX,
"Location",
"Cursor location in normalized preview coordinates",
-10.0f,
10.0f);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Update scene strip frame range
* \{ */
static int sequencer_scene_frame_range_update_exec(bContext *C, wmOperator * /*op*/)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
Strip *strip = ed->act_seq;
const int old_start = seq::time_left_handle_frame_get(scene, strip);
const int old_end = seq::time_right_handle_frame_get(scene, strip);
Scene *target_scene = strip->scene;
strip->len = target_scene->r.efra - target_scene->r.sfra + 1;
seq::time_left_handle_frame_set(scene, strip, old_start);
seq::time_right_handle_frame_set(scene, strip, old_end);
seq::relations_invalidate_cache_raw(scene, strip);
DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO | ID_RECALC_SEQUENCER_STRIPS);
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_SEQUENCER, nullptr);
return OPERATOR_FINISHED;
}
static bool sequencer_scene_frame_range_update_poll(bContext *C)
{
Editing *ed = seq::editing_get(CTX_data_scene(C));
return (ed != nullptr && ed->act_seq != nullptr && (ed->act_seq->type & STRIP_TYPE_SCENE) != 0);
}
void SEQUENCER_OT_scene_frame_range_update(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Update Scene Frame Range";
ot->description = "Update frame range of scene strip";
ot->idname = "SEQUENCER_OT_scene_frame_range_update";
/* api callbacks */
ot->exec = sequencer_scene_frame_range_update_exec;
ot->poll = sequencer_scene_frame_range_update_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
} // namespace blender::ed::vse