Files
test2/source/blender/editors/space_sequencer/sequencer_edit.cc

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

3568 lines
103 KiB
C++
Raw Normal View History

/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup spseq
2011-02-27 20:29:51 +00:00
*/
#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"
VSE: ability to connect and disconnect strips in the VSE. Adds the ability to connect and disconnect strips in the VSE. - Connected strips have an icon indicating their status, and attempting to select one connected strip selects all other connected strips in that chain. - If the user attempts to connect a strip that is already connected to other strips, that strip will disconnect itself from others before connecting to new strips. - Preview selection also works in bulk if multiple video strips are connected together in the timeline. - When adding new strips from the Add menu or the File Browser, strips from the same file are connected by default. There's an option to turn this off in Editing > Video Sequencer user preferences. - It is possible to individually tweak strips/handles and ignore connections with Alt+Click. - This shortcut overrides the old keymap item for "Linked Handle" selection. The property still exists if people want to use that shortcut for its old purpose. - To make sure that connections remain valid even after duplication, I've added a condition to `seq_new_fix_links_recursive` that also updates connections using the `seq->tmp` var. (A note -- I've updated the comment for this field in `DNA_sequence_types.h` because the var is only used for duplication now. It was once present in `select_more_less_seq__internal` to be used for linked selection but is gone now). - There are also functions to cut one-way links and make sure that all strips are bidirectionally connected after duplicating. Pull Request: https://projects.blender.org/blender/blender/pulls/124333
2024-08-22 14:54:42 +02:00
#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) &&
2022-10-07 22:52:53 +11:00
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)
{
2012-03-29 22:26:11 +00:00
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
ListBase *channels = seq::channels_displayed_get(ed);
int snap_frame;
2012-03-29 22:26:11 +00:00
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);
2012-03-29 22:26:11 +00:00
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. */
2012-03-29 22:26:11 +00:00
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 {
2014-10-21 19:02:05 +02:00
float init_mouseloc[2];
int previous_offset;
float previous_subframe_offset;
float subframe_restore;
Strip **strip_array;
2014-10-21 19:02:05 +02:00
int num_seq;
bool slow;
int slow_offset; /* Offset at the point where offset was turned on. */
2015-02-06 16:38:32 +01:00
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)
2014-10-21 19:02:05 +02:00
{
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];
2020-09-09 18:41:07 +02:00
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;
2015-02-06 16:38:32 +01:00
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)
2015-02-06 16:38:32 +01:00
{
if (area == nullptr) {
return;
}
2015-02-06 16:38:32 +01:00
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);
2015-02-06 16:38:32 +01:00
}
ED_area_status_text(area, msg);
2015-02-06 16:38:32 +01:00
}
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) {
2024-07-13 16:56:57 +10:00
/* 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) {
2024-07-13 16:56:57 +10:00
/* 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);
2015-02-06 16:38:32 +01:00
const bool has_numInput = hasNumInput(&data->num_input);
bool handled = true;
/* Modal numinput active, try to handle numeric inputs. */
2015-02-06 16:38:32 +01:00
if (event->val == KM_PRESS && has_numInput && handleNumInput(C, &data->num_input, event)) {
handle_number_input(C, op, area, data, scene);
2015-02-06 16:38:32 +01:00
return OPERATOR_RUNNING_MODAL;
}
switch (event->type) {
case MOUSEMOVE: {
2015-02-06 16:38:32 +01:00
if (!has_numInput) {
float mouseloc[2];
int offset;
float mouse_x;
2015-02-06 16:38:32 +01:00
View2D *v2d = UI_view2d_fromcontext(C);
2015-02-06 16:38:32 +01:00
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. */
2015-02-06 16:38:32 +01:00
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:
2015-02-06 16:38:32 +01:00
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;
}
2015-02-06 16:38:32 +01:00
}
break;
default:
2015-02-06 16:38:32 +01:00
handled = false;
break;
}
/* Modal numinput inactive, try to handle numeric inputs. */
2015-02-06 16:38:32 +01:00
if (!handled && event->val == KM_PRESS && handleNumInput(C, &data->num_input, event)) {
handle_number_input(C, op, area, data, scene);
2015-02-06 16:38:32 +01:00
}
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)
{
2012-03-29 22:26:11 +00:00
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
ListBase *channels = seq::channels_displayed_get(ed);
bool selected;
2012-03-29 22:26:11 +00:00
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);
2012-03-29 22:26:11 +00:00
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. */
2012-03-29 22:26:11 +00:00
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)
{
2012-03-29 22:26:11 +00:00
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
ListBase *channels = seq::channels_displayed_get(ed);
bool selected;
2012-03-29 22:26:11 +00:00
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);
2012-03-29 22:26:11 +00:00
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. */
2012-03-29 22:26:11 +00:00
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*/)
{
2012-03-29 22:26:11 +00:00
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;
}
}
2012-03-29 22:26:11 +00:00
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. */
2012-03-29 22:26:11 +00:00
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Unlock Strips Operator
* \{ */
static int sequencer_unlock_exec(bContext *C, wmOperator * /*op*/)
{
2012-03-29 22:26:11 +00:00
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;
}
}
2012-03-29 22:26:11 +00:00
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. */
2012-03-29 22:26:11 +00:00
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
VSE: ability to connect and disconnect strips in the VSE. Adds the ability to connect and disconnect strips in the VSE. - Connected strips have an icon indicating their status, and attempting to select one connected strip selects all other connected strips in that chain. - If the user attempts to connect a strip that is already connected to other strips, that strip will disconnect itself from others before connecting to new strips. - Preview selection also works in bulk if multiple video strips are connected together in the timeline. - When adding new strips from the Add menu or the File Browser, strips from the same file are connected by default. There's an option to turn this off in Editing > Video Sequencer user preferences. - It is possible to individually tweak strips/handles and ignore connections with Alt+Click. - This shortcut overrides the old keymap item for "Linked Handle" selection. The property still exists if people want to use that shortcut for its old purpose. - To make sure that connections remain valid even after duplication, I've added a condition to `seq_new_fix_links_recursive` that also updates connections using the `seq->tmp` var. (A note -- I've updated the comment for this field in `DNA_sequence_types.h` because the var is only used for duplication now. It was once present in `select_more_less_seq__internal` to be used for linked selection but is gone now). - There are also functions to cut one-way links and make sure that all strips are bidirectionally connected after duplicating. Pull Request: https://projects.blender.org/blender/blender/pulls/124333
2024-08-22 14:54:42 +02:00
/* -------------------------------------------------------------------- */
/** \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);
VSE: ability to connect and disconnect strips in the VSE. Adds the ability to connect and disconnect strips in the VSE. - Connected strips have an icon indicating their status, and attempting to select one connected strip selects all other connected strips in that chain. - If the user attempts to connect a strip that is already connected to other strips, that strip will disconnect itself from others before connecting to new strips. - Preview selection also works in bulk if multiple video strips are connected together in the timeline. - When adding new strips from the Add menu or the File Browser, strips from the same file are connected by default. There's an option to turn this off in Editing > Video Sequencer user preferences. - It is possible to individually tweak strips/handles and ignore connections with Alt+Click. - This shortcut overrides the old keymap item for "Linked Handle" selection. The property still exists if people want to use that shortcut for its old purpose. - To make sure that connections remain valid even after duplication, I've added a condition to `seq_new_fix_links_recursive` that also updates connections using the `seq->tmp` var. (A note -- I've updated the comment for this field in `DNA_sequence_types.h` because the var is only used for duplication now. It was once present in `select_more_less_seq__internal` to be used for linked selection but is gone now). - There are also functions to cut one-way links and make sure that all strips are bidirectionally connected after duplicating. Pull Request: https://projects.blender.org/blender/blender/pulls/124333
2024-08-22 14:54:42 +02:00
blender::VectorSet<Strip *> selected = seq::query_selected_strips(active_seqbase);
VSE: ability to connect and disconnect strips in the VSE. Adds the ability to connect and disconnect strips in the VSE. - Connected strips have an icon indicating their status, and attempting to select one connected strip selects all other connected strips in that chain. - If the user attempts to connect a strip that is already connected to other strips, that strip will disconnect itself from others before connecting to new strips. - Preview selection also works in bulk if multiple video strips are connected together in the timeline. - When adding new strips from the Add menu or the File Browser, strips from the same file are connected by default. There's an option to turn this off in Editing > Video Sequencer user preferences. - It is possible to individually tweak strips/handles and ignore connections with Alt+Click. - This shortcut overrides the old keymap item for "Linked Handle" selection. The property still exists if people want to use that shortcut for its old purpose. - To make sure that connections remain valid even after duplication, I've added a condition to `seq_new_fix_links_recursive` that also updates connections using the `seq->tmp` var. (A note -- I've updated the comment for this field in `DNA_sequence_types.h` because the var is only used for duplication now. It was once present in `select_more_less_seq__internal` to be used for linked selection but is gone now). - There are also functions to cut one-way links and make sure that all strips are bidirectionally connected after duplicating. Pull Request: https://projects.blender.org/blender/blender/pulls/124333
2024-08-22 14:54:42 +02:00
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);
VSE: ability to connect and disconnect strips in the VSE. Adds the ability to connect and disconnect strips in the VSE. - Connected strips have an icon indicating their status, and attempting to select one connected strip selects all other connected strips in that chain. - If the user attempts to connect a strip that is already connected to other strips, that strip will disconnect itself from others before connecting to new strips. - Preview selection also works in bulk if multiple video strips are connected together in the timeline. - When adding new strips from the Add menu or the File Browser, strips from the same file are connected by default. There's an option to turn this off in Editing > Video Sequencer user preferences. - It is possible to individually tweak strips/handles and ignore connections with Alt+Click. - This shortcut overrides the old keymap item for "Linked Handle" selection. The property still exists if people want to use that shortcut for its old purpose. - To make sure that connections remain valid even after duplication, I've added a condition to `seq_new_fix_links_recursive` that also updates connections using the `seq->tmp` var. (A note -- I've updated the comment for this field in `DNA_sequence_types.h` because the var is only used for duplication now. It was once present in `select_more_less_seq__internal` to be used for linked selection but is gone now). - There are also functions to cut one-way links and make sure that all strips are bidirectionally connected after duplicating. Pull Request: https://projects.blender.org/blender/blender/pulls/124333
2024-08-22 14:54:42 +02:00
}
else {
seq::connect(selected);
VSE: ability to connect and disconnect strips in the VSE. Adds the ability to connect and disconnect strips in the VSE. - Connected strips have an icon indicating their status, and attempting to select one connected strip selects all other connected strips in that chain. - If the user attempts to connect a strip that is already connected to other strips, that strip will disconnect itself from others before connecting to new strips. - Preview selection also works in bulk if multiple video strips are connected together in the timeline. - When adding new strips from the Add menu or the File Browser, strips from the same file are connected by default. There's an option to turn this off in Editing > Video Sequencer user preferences. - It is possible to individually tweak strips/handles and ignore connections with Alt+Click. - This shortcut overrides the old keymap item for "Linked Handle" selection. The property still exists if people want to use that shortcut for its old purpose. - To make sure that connections remain valid even after duplication, I've added a condition to `seq_new_fix_links_recursive` that also updates connections using the `seq->tmp` var. (A note -- I've updated the comment for this field in `DNA_sequence_types.h` because the var is only used for duplication now. It was once present in `select_more_less_seq__internal` to be used for linked selection but is gone now). - There are also functions to cut one-way links and make sure that all strips are bidirectionally connected after duplicating. Pull Request: https://projects.blender.org/blender/blender/pulls/124333
2024-08-22 14:54:42 +02:00
}
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);
VSE: ability to connect and disconnect strips in the VSE. Adds the ability to connect and disconnect strips in the VSE. - Connected strips have an icon indicating their status, and attempting to select one connected strip selects all other connected strips in that chain. - If the user attempts to connect a strip that is already connected to other strips, that strip will disconnect itself from others before connecting to new strips. - Preview selection also works in bulk if multiple video strips are connected together in the timeline. - When adding new strips from the Add menu or the File Browser, strips from the same file are connected by default. There's an option to turn this off in Editing > Video Sequencer user preferences. - It is possible to individually tweak strips/handles and ignore connections with Alt+Click. - This shortcut overrides the old keymap item for "Linked Handle" selection. The property still exists if people want to use that shortcut for its old purpose. - To make sure that connections remain valid even after duplication, I've added a condition to `seq_new_fix_links_recursive` that also updates connections using the `seq->tmp` var. (A note -- I've updated the comment for this field in `DNA_sequence_types.h` because the var is only used for duplication now. It was once present in `select_more_less_seq__internal` to be used for linked selection but is gone now). - There are also functions to cut one-way links and make sure that all strips are bidirectionally connected after duplicating. Pull Request: https://projects.blender.org/blender/blender/pulls/124333
2024-08-22 14:54:42 +02:00
blender::VectorSet<Strip *> selected = seq::query_selected_strips(active_seqbase);
VSE: ability to connect and disconnect strips in the VSE. Adds the ability to connect and disconnect strips in the VSE. - Connected strips have an icon indicating their status, and attempting to select one connected strip selects all other connected strips in that chain. - If the user attempts to connect a strip that is already connected to other strips, that strip will disconnect itself from others before connecting to new strips. - Preview selection also works in bulk if multiple video strips are connected together in the timeline. - When adding new strips from the Add menu or the File Browser, strips from the same file are connected by default. There's an option to turn this off in Editing > Video Sequencer user preferences. - It is possible to individually tweak strips/handles and ignore connections with Alt+Click. - This shortcut overrides the old keymap item for "Linked Handle" selection. The property still exists if people want to use that shortcut for its old purpose. - To make sure that connections remain valid even after duplication, I've added a condition to `seq_new_fix_links_recursive` that also updates connections using the `seq->tmp` var. (A note -- I've updated the comment for this field in `DNA_sequence_types.h` because the var is only used for duplication now. It was once present in `select_more_less_seq__internal` to be used for linked selection but is gone now). - There are also functions to cut one-way links and make sure that all strips are bidirectionally connected after duplicating. Pull Request: https://projects.blender.org/blender/blender/pulls/124333
2024-08-22 14:54:42 +02:00
if (seq::disconnect(selected)) {
VSE: ability to connect and disconnect strips in the VSE. Adds the ability to connect and disconnect strips in the VSE. - Connected strips have an icon indicating their status, and attempting to select one connected strip selects all other connected strips in that chain. - If the user attempts to connect a strip that is already connected to other strips, that strip will disconnect itself from others before connecting to new strips. - Preview selection also works in bulk if multiple video strips are connected together in the timeline. - When adding new strips from the Add menu or the File Browser, strips from the same file are connected by default. There's an option to turn this off in Editing > Video Sequencer user preferences. - It is possible to individually tweak strips/handles and ignore connections with Alt+Click. - This shortcut overrides the old keymap item for "Linked Handle" selection. The property still exists if people want to use that shortcut for its old purpose. - To make sure that connections remain valid even after duplication, I've added a condition to `seq_new_fix_links_recursive` that also updates connections using the `seq->tmp` var. (A note -- I've updated the comment for this field in `DNA_sequence_types.h` because the var is only used for duplication now. It was once present in `select_more_less_seq__internal` to be used for linked selection but is gone now). - There are also functions to cut one-way links and make sure that all strips are bidirectionally connected after duplicating. Pull Request: https://projects.blender.org/blender/blender/pulls/124333
2024-08-22 14:54:42 +02:00
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
return OPERATOR_CANCELLED;
VSE: ability to connect and disconnect strips in the VSE. Adds the ability to connect and disconnect strips in the VSE. - Connected strips have an icon indicating their status, and attempting to select one connected strip selects all other connected strips in that chain. - If the user attempts to connect a strip that is already connected to other strips, that strip will disconnect itself from others before connecting to new strips. - Preview selection also works in bulk if multiple video strips are connected together in the timeline. - When adding new strips from the Add menu or the File Browser, strips from the same file are connected by default. There's an option to turn this off in Editing > Video Sequencer user preferences. - It is possible to individually tweak strips/handles and ignore connections with Alt+Click. - This shortcut overrides the old keymap item for "Linked Handle" selection. The property still exists if people want to use that shortcut for its old purpose. - To make sure that connections remain valid even after duplication, I've added a condition to `seq_new_fix_links_recursive` that also updates connections using the `seq->tmp` var. (A note -- I've updated the comment for this field in `DNA_sequence_types.h` because the var is only used for duplication now. It was once present in `select_more_less_seq__internal` to be used for linked selection but is gone now). - There are also functions to cut one-way links and make sure that all strips are bidirectionally connected after duplicating. Pull Request: https://projects.blender.org/blender/blender/pulls/124333
2024-08-22 14:54:42 +02:00
}
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);
2012-03-29 22:26:11 +00:00
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
2014-02-03 18:55:59 +11:00
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);
}
}
}
}
2012-03-29 22:26:11 +00:00
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
* \{ */
2018-07-02 11:47:00 +02:00
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*/)
{
2012-03-29 22:26:11 +00:00
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);
2012-03-29 22:26:11 +00:00
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;
}
2010-07-03 22:25:22 +00:00
static int sequencer_reassign_inputs_exec(bContext *C, wmOperator *op)
{
2012-03-29 22:26:11 +00:00
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);
2010-07-03 22:25:22 +00:00
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)) {
2010-07-03 22:25:22 +00:00
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");
2010-07-03 22:25:22 +00:00
return OPERATOR_CANCELLED;
}
active_strip->seq1 = seq1;
active_strip->seq2 = seq2;
2010-07-03 22:25:22 +00:00
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));
2010-07-03 22:25:22 +00:00
2012-03-29 22:26:11 +00:00
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
2010-07-03 22:25:22 +00:00
return OPERATOR_FINISHED;
}
2018-07-02 11:47:00 +02:00
static bool sequencer_effect_poll(bContext *C)
2010-07-03 22:25:22 +00:00
{
2012-03-29 22:26:11 +00:00
Scene *scene = CTX_data_scene(C);
Editing *ed = seq::editing_get(scene);
2010-07-03 22:25:22 +00:00
if (ed) {
Strip *active_strip = seq::select_active_get(scene);
if (active_strip && (active_strip->type & STRIP_TYPE_EFFECT)) {
return true;
2010-07-03 22:25:22 +00:00
}
}
return false;
2010-07-03 22:25:22 +00:00
}
void SEQUENCER_OT_reassign_inputs(wmOperatorType *ot)
2010-07-03 22:25:22 +00:00
{
/* Identifiers. */
ot->name = "Reassign Inputs";
ot->idname = "SEQUENCER_OT_reassign_inputs";
ot->description = "Reassign the inputs for the effect strip";
2010-07-03 22:25:22 +00:00
/* Api callbacks. */
ot->exec = sequencer_reassign_inputs_exec;
ot->poll = sequencer_effect_poll;
2010-07-03 22:25:22 +00:00
/* Flags. */
2012-03-29 22:26:11 +00:00
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2010-07-03 22:25:22 +00:00
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Swap Inputs Operator
* \{ */
static int sequencer_swap_inputs_exec(bContext *C, wmOperator *op)
{
2012-03-29 22:26:11 +00:00
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);
2012-03-29 22:26:11 +00:00
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. */
2012-03-29 22:26:11 +00:00
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);
2012-03-29 22:26:11 +00:00
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) {
2012-03-29 22:26:11 +00:00
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);
2012-03-29 22:26:11 +00:00
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. */
2012-03-29 22:26:11 +00:00
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",
UI: fix and improve a few messages - "Rename Channels": only one channel can be renamed at a time, use singular. - "Copy Markers to Scene": rephrase erroneous operator description. - "Axis tag names with": grammar. - "Close or open the selected stroke adding an edge from last to first point": "segment" is preferred to "edge" elsewhere in the context of curves or Grease Pencil. - "Number of subdivisions [points] by edge[s]": replace "by edge" with "per segment" for the same reason. - "Compatibility mode for SL, OpenSim...": expand to explain that SL and OpenSim are respectively Second Life and OpenSimulator. - "W/m^2" -> "W/m²". This symbol is widely supported and should be preferred for exponentiation outside of code. - "Effect on tracks which are tracked less than specified amount of frames", "Effect on tracks which have a larger reprojection error": Use "Affect" and "number" instead of "amount". - "Hull curve" -> "Envelope". This is a calque from German Hüllkurve meaning envelope. - "Frquency Cutoff" -> "Frequency Cutoff" (typo) - "Check if Select Left or Right": rephrase to "Based on Mouse Position" as it better explains the action of the operator. - "Make cut event if strip is not selected ..." -> even (typo) - "Shear selected items along the horizontal screen axis": Rephrase as this transform operator can act in many different axes. - Tonemapping compositing node: the two algorithms "R/D Photoreceptor" and "Rh Simple" only had names, but no description. Add ones explaining at least the basic principle and where the names come from. - In the "Matte dilate/erode side" description for the Keying node's Dilate/Erode socket, "side" was likely a typo for "size". Reformulate the description to make it clearer, inspired by similar ones. - "Width of the blur edge" -> "Width of the blur for the transition"; "Edge angle" -> "Angle of the transition"; "Wipe direction" -> "Whether to fade in or out": Better explains the sequencer wipe transition (inspired by the manual). - OSL shaders now supported on some GPU backends. - "Add a new repeat input and output nodes " -> "Add new" (typo). Pull Request: https://projects.blender.org/blender/blender/pulls/110321
2023-07-24 21:22:07 +02:00
"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*/)
{
2012-03-29 22:26:11 +00:00
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);
2023-12-10 16:33:54 +11:00
/* 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. */
2012-03-29 22:26:11 +00:00
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);
2012-03-29 22:26:11 +00:00
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);
2012-03-29 22:26:11 +00:00
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. */
2012-03-29 22:26:11 +00:00
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*/)
{
2012-03-29 22:26:11 +00:00
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);
}
}
}
2012-03-29 22:26:11 +00:00
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. */
2012-03-29 22:26:11 +00:00
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Separate Images Operator
* \{ */
static int sequencer_separate_images_exec(bContext *C, wmOperator *op)
{
2012-03-29 22:26:11 +00:00
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;
2012-03-29 22:26:11 +00:00
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);
2012-03-29 22:26:11 +00:00
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"));
}
2009-04-12 20:32:42 +00:00
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. */
2012-03-29 22:26:11 +00:00
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2009-10-19 21:34:38 +00:00
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*/)
{
2012-03-29 22:26:11 +00:00
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);
2012-03-29 22:26:11 +00:00
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. */
2012-03-29 22:26:11 +00:00
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Make Meta Strip Operator
* \{ */
static int sequencer_meta_make_exec(bContext *C, wmOperator * /*op*/)
{
2012-03-29 22:26:11 +00:00
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);
2012-03-29 22:26:11 +00:00
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. */
2012-03-29 22:26:11 +00:00
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name UnMeta Strip Operator
* \{ */
static int sequencer_meta_separate_exec(bContext *C, wmOperator * /*op*/)
{
2012-03-29 22:26:11 +00:00
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);
2012-03-29 22:26:11 +00:00
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. */
2012-03-29 22:26:11 +00:00
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Jump to Strip Operator
* \{ */
static bool strip_jump_internal(Scene *scene,
const short side,
2014-02-03 18:55:59 +11:00
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;
}
2018-07-02 11:47:00 +02:00
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)
{
2012-03-29 22:26:11 +00:00
Scene *scene = CTX_data_scene(C);
2014-02-03 18:55:59 +11:00
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);
2012-03-29 22:26:11 +00:00
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)
{
2012-03-29 22:26:11 +00:00
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;
2012-03-29 22:26:11 +00:00
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);
}
}
}
2012-03-29 22:26:11 +00:00
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. */
2012-03-29 22:26:11 +00:00
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*/)
{
2012-03-29 22:26:11 +00:00
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. */
2012-03-29 22:26:11 +00:00
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. */
2012-03-29 22:26:11 +00:00
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)
{
2012-03-29 22:26:11 +00:00
Scene *scene = CTX_data_scene(C);
Strip *strip_act;
Strip *strip_other;
2011-05-28 09:59:34 +00:00
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) {
2011-05-28 09:59:34 +00:00
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);
}
2010-07-04 10:22:31 +00:00
seq::relations_invalidate_cache_raw(scene, strip_act);
seq::relations_invalidate_cache_raw(scene, strip_other);
2012-03-29 22:26:11 +00:00
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. */
2012-03-29 22:26:11 +00:00
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Change Effect Input Operator
* \{ */
static int sequencer_change_effect_input_exec(bContext *C, wmOperator *op)
{
2012-03-29 22:26:11 +00:00
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);
2012-03-29 22:26:11 +00:00
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. */
2012-03-29 22:26:11 +00:00
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)
{
2012-03-29 22:26:11 +00:00
Scene *scene = CTX_data_scene(C);
Strip *strip = seq::select_active_get(scene);
2012-03-29 22:26:11 +00:00
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);
2012-03-29 22:26:11 +00:00
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. */
2012-03-29 22:26:11 +00:00
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)
{
2012-03-29 22:26:11 +00:00
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
Strip *strip = seq::select_active_get(scene);
2014-04-11 11:25:41 +10:00
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);
2023-05-09 12:50:37 +10:00
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);
2012-03-29 22:26:11 +00:00
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*/)
{
2012-03-29 22:26:11 +00:00
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. */
2012-03-29 22:26:11 +00:00
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2016-02-07 22:56:20 +11:00
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
* \{ */
2022-05-17 10:01:30 +10:00
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);
2023-08-03 19:14:53 +10:00
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];
2021-07-02 12:11:54 +10:00
/* Write time-code relative to start frame of scene. Don't allow negative time-codes. */
VSE: Make time operations self-contained This patch makes it possible to manipulate strips without need to use update functions to recalculate effect and meta strips. Prior to this change function `SEQ_time_update_sequence` had to be used to update mainly effects and meta strips. This was implemented in a way that relied on sorted list of strips, which can't always be done and in rare cases this approach failed. In case of meta strips, `seqbase` had to be passed and compared with "active" one to determine whether meta strip should be updated or not. This is especially weak system that is prone to bugs when functions are used by python API functions. Finally, other strip types had startdisp` and `enddisp` fields updated by this function and a lot of code relied on these fields even if strip start, length and offsets are available. This is completely unnecessary. Implemented changes: All effects and meta strips are updated when strip handles are moved or strip is translated, without need to call any update function. Function `SEQ_time_update_sequence` has been split to `SEQ_time_update_meta_strip_range` and `seq_time_update_effects_strip_range`. These functions should be only used within sequencer module code. Meta update is used for versioning, which is only reason for it not being declared internally. Sequence fields `startdisp` and `enddisp` are now only used for effects to store strip start and end points. These fields should be used only internally within sequencer module code. Use function `SEQ_time_*_handle_frame_get` to get strip start and end points. To update effects and meta strips with reasonable performance, cache for "parent" meta strip and attached effects is added to `SequenceLookup` cache, so it shares invalidation mechanisms. All caches are populated during single iteration. There should be no functional changes. Differential Revision: https://developer.blender.org/D14990
2022-06-02 01:39:40 +02:00
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),
VSE: Make time operations self-contained This patch makes it possible to manipulate strips without need to use update functions to recalculate effect and meta strips. Prior to this change function `SEQ_time_update_sequence` had to be used to update mainly effects and meta strips. This was implemented in a way that relied on sorted list of strips, which can't always be done and in rare cases this approach failed. In case of meta strips, `seqbase` had to be passed and compared with "active" one to determine whether meta strip should be updated or not. This is especially weak system that is prone to bugs when functions are used by python API functions. Finally, other strip types had startdisp` and `enddisp` fields updated by this function and a lot of code relied on these fields even if strip start, length and offsets are available. This is completely unnecessary. Implemented changes: All effects and meta strips are updated when strip handles are moved or strip is translated, without need to call any update function. Function `SEQ_time_update_sequence` has been split to `SEQ_time_update_meta_strip_range` and `seq_time_update_effects_strip_range`. These functions should be only used within sequencer module code. Meta update is used for versioning, which is only reason for it not being declared internally. Sequence fields `startdisp` and `enddisp` are now only used for effects to store strip start and end points. These fields should be used only internally within sequencer module code. Use function `SEQ_time_*_handle_frame_get` to get strip start and end points. To update effects and meta strips with reasonable performance, cache for "parent" meta strip and attached effects is added to `SequenceLookup` cache, so it shares invalidation mechanisms. All caches are populated during single iteration. There should be no functional changes. Differential Revision: https://developer.blender.org/D14990
2022-06-02 01:39:40 +02:00
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;
}
2018-07-02 11:47:00 +02:00
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;
2016-02-07 22:56:20 +11:00
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));
2023-09-08 16:58:00 +10:00
/* 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