Files
test/source/blender/editors/space_sequencer/sequencer_retiming.cc
Campbell Barton ee4c45c612 Fix crash drawing in the sequencer when Scene::ed is null
Regression in [0], also order checks so creating a map of sequencer
strips is done after the flag check.

[0]: 86a0d0015a
2023-09-27 13:02:35 +10:00

887 lines
25 KiB
C++

/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup spseq
*/
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "BLI_set.hh"
#include "DNA_anim_types.h"
#include "DNA_scene_types.h"
#include "DNA_space_types.h"
#include "DNA_workspace_types.h"
#include "BKE_context.h"
#include "BKE_report.h"
#include "BKE_scene.h"
#include "ED_select_utils.hh"
#include "ED_sequencer.hh"
#include "SEQ_iterator.h"
#include "SEQ_relations.h"
#include "SEQ_retiming.hh"
#include "SEQ_select.h"
#include "SEQ_sequencer.h"
#include "SEQ_time.h"
#include "SEQ_transform.h"
#include "WM_api.hh"
#include "WM_toolsystem.h"
#include "RNA_define.hh"
#include "UI_interface.hh"
#include "UI_view2d.hh"
#include "DEG_depsgraph.hh"
/* Own include. */
#include "sequencer_intern.hh"
using blender::MutableSpan;
bool sequencer_retiming_mode_is_active(const bContext *C)
{
const Scene *scene = CTX_data_scene(C);
Editing *ed = SEQ_editing_get(scene);
if (ed == nullptr) {
return false;
}
return SEQ_retiming_selection_get(ed).size() > 0;
}
/*-------------------------------------------------------------------- */
/** \name Retiming Data Show
* \{ */
static void sequencer_retiming_data_show_selection(ListBase *seqbase)
{
LISTBASE_FOREACH (Sequence *, seq, seqbase) {
if ((seq->flag & SELECT) == 0) {
continue;
}
seq->flag |= SEQ_SHOW_RETIMING;
}
}
static void sequencer_retiming_data_hide_selection(ListBase *seqbase)
{
LISTBASE_FOREACH (Sequence *, seq, seqbase) {
if ((seq->flag & SELECT) == 0) {
continue;
}
seq->flag &= ~SEQ_SHOW_RETIMING;
}
}
static void sequencer_retiming_data_hide_all(ListBase *seqbase)
{
LISTBASE_FOREACH (Sequence *, seq, seqbase) {
seq->flag &= ~SEQ_SHOW_RETIMING;
}
}
static int sequencer_retiming_data_show_exec(bContext *C, wmOperator * /* op */)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = SEQ_editing_get(scene);
Sequence *seq_act = SEQ_select_active_get(scene);
if (seq_act == nullptr) {
return OPERATOR_CANCELLED;
}
if (sequencer_retiming_mode_is_active(C)) {
sequencer_retiming_data_hide_all(ed->seqbasep);
}
else if (SEQ_retiming_data_is_editable(seq_act)) {
sequencer_retiming_data_hide_selection(ed->seqbasep);
}
else {
sequencer_retiming_data_show_selection(ed->seqbasep);
}
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_retiming_show(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Retime Strips";
ot->description = "Show retiming keys in selected strips";
ot->idname = "SEQUENCER_OT_retiming_show";
/* api callbacks */
ot->exec = sequencer_retiming_data_show_exec;
ot->poll = sequencer_editing_initialized_and_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
static bool retiming_poll(bContext *C)
{
const Editing *ed = SEQ_editing_get(CTX_data_scene(C));
if (ed == nullptr) {
return false;
}
Sequence *seq = ed->act_seq;
if (seq == nullptr) {
return false;
}
if (!SEQ_retiming_is_allowed(seq)) {
CTX_wm_operator_poll_msg_set(C, "This strip type can not be retimed");
return false;
}
return true;
}
static void retiming_key_overlap(Scene *scene, Sequence *seq)
{
ListBase *seqbase = SEQ_active_seqbase_get(SEQ_editing_get(scene));
SeqCollection *strips = SEQ_collection_create(__func__);
SEQ_collection_append_strip(seq, strips);
SeqCollection *dependant = SEQ_collection_create(__func__);
SEQ_collection_expand(scene, seqbase, strips, SEQ_query_strip_effect_chain);
SEQ_collection_remove_strip(seq, dependant);
SEQ_transform_handle_overlap(scene, seqbase, strips, dependant, true);
SEQ_collection_free(strips);
SEQ_collection_free(dependant);
}
/*-------------------------------------------------------------------- */
/** \name Retiming Reset
* \{ */
static int sequencer_retiming_reset_exec(bContext *C, wmOperator * /*op*/)
{
Scene *scene = CTX_data_scene(C);
const Editing *ed = SEQ_editing_get(scene);
Sequence *seq = ed->act_seq;
SEQ_retiming_data_clear(seq);
retiming_key_overlap(scene, seq);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
void SEQUENCER_OT_retiming_reset(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Reset Retiming";
ot->description = "Reset strip retiming";
ot->idname = "SEQUENCER_OT_retiming_reset";
/* api callbacks */
ot->exec = sequencer_retiming_reset_exec;
ot->poll = retiming_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Retiming Add Key
* \{ */
static bool retiming_key_add_new_for_seq(bContext *C,
wmOperator *op,
Sequence *seq,
const int timeline_frame)
{
Scene *scene = CTX_data_scene(C);
SEQ_retiming_data_ensure(seq);
const int frame_index = BKE_scene_frame_get(scene) - SEQ_time_start_frame_get(seq);
const SeqRetimingKey *key = SEQ_retiming_find_segment_start_key(seq, frame_index);
if (SEQ_retiming_key_is_transition_start(key)) {
BKE_report(op->reports, RPT_WARNING, "Can not create key inside of speed transition");
return false;
}
const float end_frame = seq->start + SEQ_time_strip_length_get(scene, seq);
if (seq->start > timeline_frame || end_frame < timeline_frame) {
return false;
}
SEQ_retiming_add_key(scene, seq, timeline_frame);
return true;
}
static int retiming_key_add_from_selection(bContext *C,
wmOperator *op,
SeqCollection *strips,
const int timeline_frame)
{
bool inserted = false;
Sequence *seq;
SEQ_ITERATOR_FOREACH (seq, strips) {
inserted |= retiming_key_add_new_for_seq(C, op, seq, timeline_frame);
}
return inserted ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
static int retiming_key_add_to_editable_strips(bContext *C,
wmOperator *op,
const int timeline_frame)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = SEQ_editing_get(scene);
bool inserted = false;
blender::Map selection = SEQ_retiming_selection_get(ed);
if (selection.size() == 0) {
return OPERATOR_CANCELLED;
}
for (Sequence *seq : selection.values()) {
inserted |= retiming_key_add_new_for_seq(C, op, seq, timeline_frame);
}
return inserted ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
static int sequencer_retiming_key_add_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
float timeline_frame;
if (RNA_struct_property_is_set(op->ptr, "timeline_frame")) {
timeline_frame = RNA_int_get(op->ptr, "timeline_frame");
}
else {
timeline_frame = BKE_scene_frame_get(scene);
}
int ret_val;
SeqCollection *strips = selected_strips_from_context(C);
if (SEQ_collection_len(strips) != 0) {
ret_val = retiming_key_add_from_selection(C, op, strips, timeline_frame);
}
else {
ret_val = retiming_key_add_to_editable_strips(C, op, timeline_frame);
}
SEQ_collection_free(strips);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return ret_val;
}
void SEQUENCER_OT_retiming_key_add(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Add Retiming Key";
ot->description = "Add retiming Key";
ot->idname = "SEQUENCER_OT_retiming_key_add";
/* api callbacks */
ot->exec = sequencer_retiming_key_add_exec;
ot->poll = retiming_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_int(ot->srna,
"timeline_frame",
0,
0,
INT_MAX,
"Timeline Frame",
"Frame where key will be added",
0,
INT_MAX);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Retiming Add Freeze Frame
* \{ */
static bool freeze_frame_add_new_for_seq(const bContext *C,
const wmOperator *op,
Sequence *seq,
const int timeline_frame,
const int duration)
{
Scene *scene = CTX_data_scene(C);
SEQ_retiming_data_ensure(seq);
// ensure L+R key
SeqRetimingKey *key = SEQ_retiming_add_key(scene, seq, timeline_frame);
if (key == nullptr) {
key = SEQ_retiming_key_get_by_timeline_frame(scene, seq, timeline_frame);
}
if (SEQ_retiming_key_is_transition_start(key)) {
BKE_report(op->reports, RPT_WARNING, "Can not create key inside of speed transition");
return false;
}
if (key == nullptr) {
BKE_report(op->reports, RPT_WARNING, "Can not create freeze frame");
return false;
}
SeqRetimingKey *freeze = SEQ_retiming_add_freeze_frame(scene, seq, key, duration);
if (freeze == nullptr) {
BKE_report(op->reports, RPT_WARNING, "Can not create freeze frame");
return false;
}
SEQ_relations_invalidate_cache_raw(scene, seq);
return true;
}
static bool freeze_frame_add_from_strip_selection(bContext *C,
const wmOperator *op,
const int duration)
{
Scene *scene = CTX_data_scene(C);
SeqCollection *strips = selected_strips_from_context(C);
const int timeline_frame = BKE_scene_frame_get(scene);
bool success = false;
Sequence *seq;
SEQ_ITERATOR_FOREACH (seq, strips) {
success |= freeze_frame_add_new_for_seq(C, op, seq, timeline_frame, duration);
SEQ_relations_invalidate_cache_raw(scene, seq);
}
SEQ_collection_free(strips);
return success;
}
static bool freeze_frame_add_from_retiming_selection(const bContext *C,
const wmOperator *op,
const int duration)
{
Scene *scene = CTX_data_scene(C);
bool success = false;
for (auto item : SEQ_retiming_selection_get(SEQ_editing_get(scene)).items()) {
const int timeline_frame = SEQ_retiming_key_timeline_frame_get(scene, item.value, item.key);
success |= freeze_frame_add_new_for_seq(C, op, item.value, timeline_frame, duration);
SEQ_relations_invalidate_cache_raw(scene, item.value);
}
return success;
}
static int sequencer_retiming_freeze_frame_add_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
bool success = false;
const float fps = scene->r.frs_sec / scene->r.frs_sec_base;
int duration = 4 * fps;
if (RNA_property_is_set(op->ptr, RNA_struct_find_property(op->ptr, "duration"))) {
duration = RNA_int_get(op->ptr, "duration");
}
if (sequencer_retiming_mode_is_active(C)) {
success = freeze_frame_add_from_retiming_selection(C, op, duration);
}
else {
success = freeze_frame_add_from_strip_selection(C, op, duration);
}
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return success ? OPERATOR_FINISHED : OPERATOR_PASS_THROUGH;
}
void SEQUENCER_OT_retiming_freeze_frame_add(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Add Freeze Frame";
ot->description = "Add freeze frame";
ot->idname = "SEQUENCER_OT_retiming_freeze_frame_add";
/* api callbacks */
ot->exec = sequencer_retiming_freeze_frame_add_exec;
ot->poll = retiming_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
RNA_def_int(ot->srna,
"duration",
0,
0,
INT_MAX,
"Duration",
"Duration of freeze frame segment",
0,
INT_MAX);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Retiming Add Speed Transition
* \{ */
static bool transition_add_new_for_seq(const bContext *C,
const wmOperator *op,
Sequence *seq,
const int timeline_frame,
const int duration)
{
Scene *scene = CTX_data_scene(C);
// ensure L+R key
SeqRetimingKey *key = SEQ_retiming_add_key(scene, seq, timeline_frame);
if (key == nullptr) {
key = SEQ_retiming_key_get_by_timeline_frame(scene, seq, timeline_frame);
}
SeqRetimingKey *transition = SEQ_retiming_add_transition(scene, seq, key, duration);
if (transition == nullptr) {
BKE_report(op->reports, RPT_WARNING, "Can not create transition");
return false;
}
SEQ_relations_invalidate_cache_raw(scene, seq);
return true;
}
static bool transition_add_from_retiming_selection(const bContext *C,
const wmOperator *op,
const int duration)
{
Scene *scene = CTX_data_scene(C);
bool success = false;
for (auto item : SEQ_retiming_selection_get(SEQ_editing_get(scene)).items()) {
const int timeline_frame = SEQ_retiming_key_timeline_frame_get(scene, item.value, item.key);
success |= transition_add_new_for_seq(C, op, item.value, timeline_frame, duration);
}
return success;
}
static int sequencer_retiming_transition_add_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
bool success = false;
const float fps = scene->r.frs_sec / scene->r.frs_sec_base;
int duration = 4 * fps;
if (RNA_property_is_set(op->ptr, RNA_struct_find_property(op->ptr, "duration"))) {
duration = RNA_int_get(op->ptr, "duration");
}
if (sequencer_retiming_mode_is_active(C)) {
success = transition_add_from_retiming_selection(C, op, duration);
}
else {
BKE_report(op->reports, RPT_WARNING, "Retiming key must be selected");
return false;
}
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return success ? OPERATOR_FINISHED : OPERATOR_PASS_THROUGH;
}
void SEQUENCER_OT_retiming_transition_add(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Add Speed Transition";
ot->description = "Add smooth transition between 2 retimed segments";
ot->idname = "SEQUENCER_OT_retiming_transition_add";
/* api callbacks */
ot->exec = sequencer_retiming_transition_add_exec;
ot->poll = retiming_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
RNA_def_int(ot->srna,
"duration",
0,
0,
INT_MAX,
"Duration",
"Duration of freeze frame segment",
0,
INT_MAX);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Retiming Set Segment Speed
* \{ */
static SeqRetimingKey *ensure_left_and_right_keys(const bContext *C, Sequence *seq)
{
Scene *scene = CTX_data_scene(C);
SEQ_retiming_add_key(scene, seq, left_fake_key_frame_get(C, seq));
return SEQ_retiming_add_key(scene, seq, right_fake_key_frame_get(C, seq));
}
static int strip_speed_set_exec(bContext *C, const wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
SeqCollection *strips = selected_strips_from_context(C);
Sequence *seq;
SEQ_ITERATOR_FOREACH (seq, strips) {
SEQ_retiming_data_ensure(seq);
SeqRetimingKey *key = ensure_left_and_right_keys(C, seq);
if (key == nullptr) {
continue;
}
/* TODO: it would be nice to multiply speed with complex retiming by a factor. */
SEQ_retiming_key_speed_set(scene, seq, key, RNA_float_get(op->ptr, "speed"));
SEQ_relations_invalidate_cache_raw(scene, seq);
}
SEQ_collection_free(strips);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
static int segment_speed_set_exec(const bContext *C,
const wmOperator *op,
blender::Map<SeqRetimingKey *, Sequence *> selection)
{
Scene *scene = CTX_data_scene(C);
for (auto item : selection.items()) {
SEQ_retiming_key_speed_set(scene, item.value, item.key, RNA_float_get(op->ptr, "speed"));
SEQ_relations_invalidate_cache_raw(scene, item.value);
}
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
static int sequencer_retiming_segment_speed_set_exec(bContext *C, wmOperator *op)
{
const Scene *scene = CTX_data_scene(C);
/* Strip mode. */
if (!sequencer_retiming_mode_is_active(C)) {
return strip_speed_set_exec(C, op);
}
blender::Map selection = SEQ_retiming_selection_get(SEQ_editing_get(scene));
/* Retiming mode. */
if (selection.size() > 0) {
return segment_speed_set_exec(C, op, selection);
}
BKE_report(op->reports, RPT_ERROR, "No keys or strips selected");
return OPERATOR_CANCELLED;
}
static int sequencer_retiming_segment_speed_set_invoke(bContext *C,
wmOperator *op,
const wmEvent *event)
{
if (!RNA_struct_property_is_set(op->ptr, "speed")) {
return WM_operator_props_popup(C, op, event);
}
return sequencer_retiming_segment_speed_set_exec(C, op);
}
void SEQUENCER_OT_retiming_segment_speed_set(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Set Speed";
ot->description = "Set speed of retimed segment";
ot->idname = "SEQUENCER_OT_retiming_segment_speed_set";
/* api callbacks */
ot->invoke = sequencer_retiming_segment_speed_set_invoke;
ot->exec = sequencer_retiming_segment_speed_set_exec;
ot->poll = retiming_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
RNA_def_float(ot->srna,
"speed",
100.0f,
0.001f,
FLT_MAX,
"Speed",
"New speed of retimed segment",
0.1f,
FLT_MAX);
}
/** \} */
static bool select_key(const Editing *ed,
SeqRetimingKey *key,
const bool toggle,
const bool deselect_all)
{
bool changed = false;
if (deselect_all) {
changed = SEQ_retiming_selection_clear(ed);
}
if (key == nullptr) {
return changed;
}
if (toggle && SEQ_retiming_selection_contains(ed, key)) {
SEQ_retiming_selection_remove(key);
}
else {
SEQ_retiming_selection_append(key);
}
return true;
}
int sequencer_retiming_key_select_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Editing *ed = SEQ_editing_get(scene);
const int mval[2] = {RNA_int_get(op->ptr, "mouse_x"), RNA_int_get(op->ptr, "mouse_y")};
int hand;
Sequence *seq_key_owner = nullptr;
SeqRetimingKey *key = retiming_mousover_key_get(C, mval, &seq_key_owner);
/* Try to realize "fake" key, since it is clicked on. */
if (key == nullptr && seq_key_owner != nullptr) {
key = try_to_realize_virtual_key(C, seq_key_owner, mval);
}
const bool deselect_all = RNA_boolean_get(op->ptr, "deselect_all");
const bool wait_to_deselect_others = RNA_boolean_get(op->ptr, "wait_to_deselect_others");
const bool toggle = RNA_boolean_get(op->ptr, "toggle");
/* Click on unselected key. */
if (key != nullptr && !SEQ_retiming_selection_contains(ed, key) && !toggle) {
select_key(ed, key, false, deselect_all);
}
/* Clicked on any key, waiting to click release. */
if (key != nullptr && wait_to_deselect_others && !toggle) {
return OPERATOR_RUNNING_MODAL;
}
/* Click on strip, do strip selection. */
const Sequence *seq_click_exact = find_nearest_seq(scene, UI_view2d_fromcontext(C), &hand, mval);
if (seq_click_exact != nullptr && key == nullptr) {
SEQ_retiming_selection_clear(ed);
return sequencer_select_exec(C, op);
}
/* Selection after click is released. */
const bool changed = select_key(ed, key, toggle, deselect_all);
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return changed ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
static void realize_fake_keys_in_rect(bContext *C, Sequence *seq, rctf &rectf)
{
const Scene *scene = CTX_data_scene(C);
const int content_start = SEQ_time_start_frame_get(seq);
const int left_key_frame = max_ii(content_start, SEQ_time_left_handle_frame_get(scene, seq));
const int content_end = SEQ_time_content_end_frame_get(scene, seq);
const int right_key_frame = min_ii(content_end, SEQ_time_right_handle_frame_get(scene, seq));
/* Realize "fake" keys. */
if (left_key_frame > rectf.xmin && left_key_frame < rectf.xmax) {
SEQ_retiming_add_key(scene, seq, left_key_frame);
}
if (right_key_frame > rectf.xmin && right_key_frame < rectf.xmax) {
SEQ_retiming_add_key(scene, seq, right_key_frame);
}
}
int sequencer_retiming_box_select_exec(bContext *C, wmOperator *op)
{
const Scene *scene = CTX_data_scene(C);
const View2D *v2d = UI_view2d_fromcontext(C);
Editing *ed = SEQ_editing_get(scene);
if (ed == nullptr) {
return OPERATOR_CANCELLED;
}
const eSelectOp sel_op = eSelectOp(RNA_enum_get(op->ptr, "mode"));
bool changed = false;
if (SEL_OP_USE_PRE_DESELECT(sel_op)) {
changed |= SEQ_retiming_selection_clear(ed);
}
rctf rectf;
WM_operator_properties_border_to_rctf(op, &rectf);
UI_view2d_region_to_view_rctf(v2d, &rectf, &rectf);
blender::Set<SeqRetimingKey *> and_keys;
for (Sequence *seq : sequencer_visible_strips_get(C)) {
if (seq->machine < rectf.ymin || seq->machine > rectf.ymax) {
continue;
}
if (!SEQ_retiming_data_is_editable(seq)) {
continue;
}
realize_fake_keys_in_rect(C, seq, rectf);
for (SeqRetimingKey &key : SEQ_retiming_keys_get(seq)) {
const int key_frame = SEQ_retiming_key_timeline_frame_get(scene, seq, &key);
const int strip_start = SEQ_time_left_handle_frame_get(scene, seq);
const int strip_end = SEQ_time_right_handle_frame_get(scene, seq);
if (key_frame < strip_start || key_frame > strip_end) {
continue;
}
if (key_frame > rectf.xmax || key_frame < rectf.xmin) {
continue;
}
switch (sel_op) {
case SEL_OP_ADD:
case SEL_OP_SET: {
SEQ_retiming_selection_append(&key);
break;
}
case SEL_OP_SUB: {
SEQ_retiming_selection_remove(&key);
break;
}
case SEL_OP_XOR: { /* Toggle */
if (SEQ_retiming_selection_contains(ed, &key)) {
SEQ_retiming_selection_remove(&key);
}
else {
SEQ_retiming_selection_append(&key);
}
break;
case SEL_OP_AND: {
if (SEQ_retiming_selection_contains(ed, &key)) {
and_keys.add(&key);
}
break;
}
}
}
changed = true;
}
}
if (and_keys.size() > 0) {
SEQ_retiming_selection_clear(ed);
for (auto key : and_keys) {
SEQ_retiming_selection_append(key);
}
}
return changed ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
int sequencer_retiming_select_all_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
int action = RNA_enum_get(op->ptr, "action");
SeqCollection *strips = all_strips_from_context(C);
Sequence *seq;
if (action == SEL_TOGGLE) {
action = SEL_SELECT;
SEQ_ITERATOR_FOREACH (seq, strips) {
if (!SEQ_retiming_data_is_editable(seq)) {
continue;
}
for (SeqRetimingKey &key : SEQ_retiming_keys_get(seq)) {
if (key.flag & SEQ_KEY_SELECTED) {
action = SEL_DESELECT;
break;
}
}
}
}
if (action == SEL_DESELECT) {
SEQ_retiming_selection_clear(SEQ_editing_get(scene));
}
SEQ_ITERATOR_FOREACH (seq, strips) {
if (!SEQ_retiming_data_is_editable(seq)) {
continue;
}
for (SeqRetimingKey &key : SEQ_retiming_keys_get(seq)) {
switch (action) {
case SEL_SELECT:
key.flag |= SEQ_KEY_SELECTED;
break;
case SEL_INVERT:
if (key.flag & SEQ_KEY_SELECTED) {
key.flag &= ~SEQ_KEY_SELECTED;
}
else {
key.flag |= SEQ_KEY_SELECTED;
}
break;
}
}
}
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
int sequencer_retiming_key_remove_exec(bContext *C, wmOperator * /* op */)
{
Scene *scene = CTX_data_scene(C);
blender::Vector<Sequence *> strips_to_handle;
blender::Vector<SeqRetimingKey *> keys_to_delete;
blender::Map selection = SEQ_retiming_selection_get(SEQ_editing_get(scene));
for (auto item : selection.items()) {
/* First and last key can not be removed. */
if (item.key->strip_frame_index == 0 || SEQ_retiming_is_last_key(item.value, item.key)) {
continue;
}
strips_to_handle.append_non_duplicates(item.value);
keys_to_delete.append(item.key);
}
for (Sequence *seq : strips_to_handle) {
SEQ_retiming_remove_multiple_keys(seq, keys_to_delete);
SEQ_relations_invalidate_cache_raw(scene, seq);
}
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}