2178 lines
63 KiB
C++
2178 lines
63 KiB
C++
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup spseq
|
|
*/
|
|
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_blenlib.h"
|
|
#include "BLI_ghash.h"
|
|
#include "BLI_math_geom.h"
|
|
#include "BLI_math_vector.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "DNA_scene_types.h"
|
|
|
|
#include "BKE_context.hh"
|
|
#include "BKE_report.hh"
|
|
|
|
#include "WM_api.hh"
|
|
#include "WM_types.hh"
|
|
|
|
#include "RNA_define.hh"
|
|
|
|
#include "SEQ_channels.hh"
|
|
#include "SEQ_iterator.hh"
|
|
#include "SEQ_relations.hh"
|
|
#include "SEQ_retiming.hh"
|
|
#include "SEQ_select.hh"
|
|
#include "SEQ_sequencer.hh"
|
|
#include "SEQ_time.hh"
|
|
#include "SEQ_transform.hh"
|
|
|
|
/* For menu, popup, icons, etc. */
|
|
|
|
#include "ED_outliner.hh"
|
|
#include "ED_screen.hh"
|
|
#include "ED_select_utils.hh"
|
|
#include "ED_sequencer.hh"
|
|
|
|
#include "UI_view2d.hh"
|
|
|
|
/* Own include. */
|
|
#include "sequencer_intern.hh"
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Selection Utilities
|
|
* \{ */
|
|
|
|
blender::VectorSet<Sequence *> all_strips_from_context(bContext *C)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = SEQ_editing_get(scene);
|
|
ListBase *seqbase = SEQ_active_seqbase_get(ed);
|
|
ListBase *channels = SEQ_channels_displayed_get(ed);
|
|
|
|
const bool is_preview = sequencer_view_has_preview_poll(C);
|
|
if (is_preview) {
|
|
return SEQ_query_rendered_strips(scene, channels, seqbase, scene->r.cfra, 0);
|
|
}
|
|
|
|
return SEQ_query_all_strips(seqbase);
|
|
}
|
|
|
|
blender::VectorSet<Sequence *> selected_strips_from_context(bContext *C)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = SEQ_editing_get(scene);
|
|
ListBase *seqbase = SEQ_active_seqbase_get(ed);
|
|
ListBase *channels = SEQ_channels_displayed_get(ed);
|
|
|
|
const bool is_preview = sequencer_view_has_preview_poll(C);
|
|
|
|
if (is_preview) {
|
|
blender::VectorSet strips = SEQ_query_rendered_strips(
|
|
scene, channels, seqbase, scene->r.cfra, 0);
|
|
strips.remove_if([&](Sequence *seq) { return (seq->flag & SELECT) == 0; });
|
|
return strips;
|
|
}
|
|
|
|
return SEQ_query_selected_strips(seqbase);
|
|
}
|
|
|
|
static void select_surrounding_handles(Scene *scene, Sequence *test) /* XXX BRING BACK */
|
|
{
|
|
Sequence *neighbor;
|
|
|
|
neighbor = find_neighboring_sequence(scene, test, SEQ_SIDE_LEFT, -1);
|
|
if (neighbor) {
|
|
/* Only select neighbor handle if matching handle from test seq is also selected,
|
|
* or if neighbor was not selected at all up till now.
|
|
* Otherwise, we get odd mismatch when shift-alt-rmb selecting neighbor strips... */
|
|
if (!(neighbor->flag & SELECT) || (test->flag & SEQ_LEFTSEL)) {
|
|
neighbor->flag |= SEQ_RIGHTSEL;
|
|
}
|
|
neighbor->flag |= SELECT;
|
|
recurs_sel_seq(neighbor);
|
|
}
|
|
neighbor = find_neighboring_sequence(scene, test, SEQ_SIDE_RIGHT, -1);
|
|
if (neighbor) {
|
|
if (!(neighbor->flag & SELECT) || (test->flag & SEQ_RIGHTSEL)) { /* See comment above. */
|
|
neighbor->flag |= SEQ_LEFTSEL;
|
|
}
|
|
neighbor->flag |= SELECT;
|
|
recurs_sel_seq(neighbor);
|
|
}
|
|
}
|
|
|
|
/* Used for mouse selection in SEQUENCER_OT_select. */
|
|
static void select_active_side(
|
|
const Scene *scene, ListBase *seqbase, int sel_side, int channel, int frame)
|
|
{
|
|
|
|
LISTBASE_FOREACH (Sequence *, seq, seqbase) {
|
|
if (channel == seq->machine) {
|
|
switch (sel_side) {
|
|
case SEQ_SIDE_LEFT:
|
|
if (frame > SEQ_time_left_handle_frame_get(scene, seq)) {
|
|
seq->flag &= ~(SEQ_RIGHTSEL | SEQ_LEFTSEL);
|
|
seq->flag |= SELECT;
|
|
}
|
|
break;
|
|
case SEQ_SIDE_RIGHT:
|
|
if (frame < SEQ_time_left_handle_frame_get(scene, seq)) {
|
|
seq->flag &= ~(SEQ_RIGHTSEL | SEQ_LEFTSEL);
|
|
seq->flag |= SELECT;
|
|
}
|
|
break;
|
|
case SEQ_SIDE_BOTH:
|
|
seq->flag &= ~(SEQ_RIGHTSEL | SEQ_LEFTSEL);
|
|
seq->flag |= SELECT;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Used for mouse selection in SEQUENCER_OT_select_side. */
|
|
static void select_active_side_range(const Scene *scene,
|
|
ListBase *seqbase,
|
|
const int sel_side,
|
|
const int frame_ranges[MAXSEQ],
|
|
const int frame_ignore)
|
|
{
|
|
LISTBASE_FOREACH (Sequence *, seq, seqbase) {
|
|
if (seq->machine < MAXSEQ) {
|
|
const int frame = frame_ranges[seq->machine];
|
|
if (frame == frame_ignore) {
|
|
continue;
|
|
}
|
|
switch (sel_side) {
|
|
case SEQ_SIDE_LEFT:
|
|
if (frame > SEQ_time_left_handle_frame_get(scene, seq)) {
|
|
seq->flag &= ~(SEQ_RIGHTSEL | SEQ_LEFTSEL);
|
|
seq->flag |= SELECT;
|
|
}
|
|
break;
|
|
case SEQ_SIDE_RIGHT:
|
|
if (frame < SEQ_time_left_handle_frame_get(scene, seq)) {
|
|
seq->flag &= ~(SEQ_RIGHTSEL | SEQ_LEFTSEL);
|
|
seq->flag |= SELECT;
|
|
}
|
|
break;
|
|
case SEQ_SIDE_BOTH:
|
|
seq->flag &= ~(SEQ_RIGHTSEL | SEQ_LEFTSEL);
|
|
seq->flag |= SELECT;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Used for mouse selection in SEQUENCER_OT_select */
|
|
static void select_linked_time(const Scene *scene, ListBase *seqbase, Sequence *seq_link)
|
|
{
|
|
LISTBASE_FOREACH (Sequence *, seq, seqbase) {
|
|
if (seq_link->machine != seq->machine) {
|
|
int left_match = (SEQ_time_left_handle_frame_get(scene, seq) == seq_link->startdisp) ? 1 : 0;
|
|
int right_match = (SEQ_time_right_handle_frame_get(scene, seq) == seq_link->enddisp) ? 1 : 0;
|
|
|
|
if (left_match && right_match) {
|
|
/* Direct match, copy the selection settings. */
|
|
seq->flag &= ~(SELECT | SEQ_LEFTSEL | SEQ_RIGHTSEL);
|
|
seq->flag |= seq_link->flag & (SELECT | SEQ_LEFTSEL | SEQ_RIGHTSEL);
|
|
|
|
recurs_sel_seq(seq);
|
|
}
|
|
else if (seq_link->flag & SELECT && (left_match || right_match)) {
|
|
|
|
/* Clear for reselection. */
|
|
seq->flag &= ~(SEQ_LEFTSEL | SEQ_RIGHTSEL);
|
|
|
|
if (left_match && seq_link->flag & SEQ_LEFTSEL) {
|
|
seq->flag |= SELECT | SEQ_LEFTSEL;
|
|
}
|
|
|
|
if (right_match && seq_link->flag & SEQ_RIGHTSEL) {
|
|
seq->flag |= SELECT | SEQ_RIGHTSEL;
|
|
}
|
|
|
|
recurs_sel_seq(seq);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if 0 /* BRING BACK */
|
|
void select_surround_from_last(Scene *scene)
|
|
{
|
|
Sequence *seq = get_last_seq(scene);
|
|
|
|
if (seq == nullptr) {
|
|
return;
|
|
}
|
|
|
|
select_surrounding_handles(scene, seq);
|
|
}
|
|
#endif
|
|
|
|
void ED_sequencer_select_sequence_single(Scene *scene, Sequence *seq, bool deselect_all)
|
|
{
|
|
Editing *ed = SEQ_editing_get(scene);
|
|
|
|
if (deselect_all) {
|
|
ED_sequencer_deselect_all(scene);
|
|
}
|
|
|
|
SEQ_select_active_set(scene, seq);
|
|
|
|
if (ELEM(seq->type, SEQ_TYPE_IMAGE, SEQ_TYPE_MOVIE)) {
|
|
if (seq->strip) {
|
|
BLI_strncpy(ed->act_imagedir, seq->strip->dirpath, FILE_MAXDIR);
|
|
}
|
|
}
|
|
else if (seq->type == SEQ_TYPE_SOUND_RAM) {
|
|
if (seq->strip) {
|
|
BLI_strncpy(ed->act_sounddir, seq->strip->dirpath, FILE_MAXDIR);
|
|
}
|
|
}
|
|
seq->flag |= SELECT;
|
|
recurs_sel_seq(seq);
|
|
}
|
|
|
|
void seq_rectf(const Scene *scene, Sequence *seq, rctf *rect)
|
|
{
|
|
rect->xmin = SEQ_time_left_handle_frame_get(scene, seq);
|
|
rect->xmax = SEQ_time_right_handle_frame_get(scene, seq);
|
|
rect->ymin = seq->machine + SEQ_STRIP_OFSBOTTOM;
|
|
rect->ymax = seq->machine + SEQ_STRIP_OFSTOP;
|
|
}
|
|
|
|
Sequence *find_neighboring_sequence(Scene *scene, Sequence *test, int lr, int sel)
|
|
{
|
|
/* sel: 0==unselected, 1==selected, -1==don't care. */
|
|
Editing *ed = SEQ_editing_get(scene);
|
|
|
|
if (ed == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (sel > 0) {
|
|
sel = SELECT;
|
|
}
|
|
LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) {
|
|
if ((seq != test) && (test->machine == seq->machine) &&
|
|
((sel == -1) || (sel && (seq->flag & SELECT)) || (sel == 0 && (seq->flag & SELECT) == 0)))
|
|
{
|
|
switch (lr) {
|
|
case SEQ_SIDE_LEFT:
|
|
if (SEQ_time_left_handle_frame_get(scene, test) ==
|
|
SEQ_time_right_handle_frame_get(scene, seq))
|
|
{
|
|
return seq;
|
|
}
|
|
break;
|
|
case SEQ_SIDE_RIGHT:
|
|
if (SEQ_time_right_handle_frame_get(scene, test) ==
|
|
SEQ_time_left_handle_frame_get(scene, seq))
|
|
{
|
|
return seq;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Sequence *find_nearest_seq(const Scene *scene, const View2D *v2d, const int mval[2], int *r_hand)
|
|
{
|
|
Sequence *seq;
|
|
Editing *ed = SEQ_editing_get(scene);
|
|
float x, y;
|
|
float pixelx;
|
|
float handsize;
|
|
float displen;
|
|
*r_hand = SEQ_SIDE_NONE;
|
|
|
|
if (ed == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
pixelx = BLI_rctf_size_x(&v2d->cur) / BLI_rcti_size_x(&v2d->mask);
|
|
|
|
UI_view2d_region_to_view(v2d, mval[0], mval[1], &x, &y);
|
|
|
|
seq = static_cast<Sequence *>(ed->seqbasep->first);
|
|
|
|
while (seq) {
|
|
if (seq->machine == int(y)) {
|
|
/* Check for both normal strips, and strips that have been flipped horizontally. */
|
|
if (((SEQ_time_left_handle_frame_get(scene, seq) <
|
|
SEQ_time_right_handle_frame_get(scene, seq)) &&
|
|
(SEQ_time_left_handle_frame_get(scene, seq) <= x &&
|
|
SEQ_time_right_handle_frame_get(scene, seq) >= x)) ||
|
|
((SEQ_time_left_handle_frame_get(scene, seq) >
|
|
SEQ_time_right_handle_frame_get(scene, seq)) &&
|
|
(SEQ_time_left_handle_frame_get(scene, seq) >= x &&
|
|
SEQ_time_right_handle_frame_get(scene, seq) <= x)))
|
|
{
|
|
if (SEQ_transform_sequence_can_be_translated(seq)) {
|
|
|
|
/* Clamp handles to defined size in pixel space. */
|
|
handsize = 2.0f * sequence_handle_size_get_clamped(scene, seq, pixelx);
|
|
displen = float(abs(SEQ_time_left_handle_frame_get(scene, seq) -
|
|
SEQ_time_right_handle_frame_get(scene, seq)));
|
|
|
|
/* Don't even try to grab the handles of small strips. */
|
|
if (displen / pixelx > 16) {
|
|
|
|
/* Set the max value to handle to 1/3 of the total len when its
|
|
* less than 28. This is important because otherwise selecting
|
|
* handles happens even when you click in the middle. */
|
|
if ((displen / 3) < 30 * pixelx) {
|
|
handsize = displen / 3;
|
|
}
|
|
else {
|
|
CLAMP(handsize, 7 * pixelx, 30 * pixelx);
|
|
}
|
|
|
|
if (handsize + SEQ_time_left_handle_frame_get(scene, seq) >= x) {
|
|
*r_hand = SEQ_SIDE_LEFT;
|
|
}
|
|
else if (-handsize + SEQ_time_right_handle_frame_get(scene, seq) <= x) {
|
|
*r_hand = SEQ_SIDE_RIGHT;
|
|
}
|
|
}
|
|
}
|
|
return seq;
|
|
}
|
|
}
|
|
seq = static_cast<Sequence *>(seq->next);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
#if 0
|
|
static void select_neighbor_from_last(Scene *scene, int lr)
|
|
{
|
|
Sequence *seq = SEQ_select_active_get(scene);
|
|
Sequence *neighbor;
|
|
bool changed = false;
|
|
if (seq) {
|
|
neighbor = find_neighboring_sequence(scene, seq, lr, -1);
|
|
if (neighbor) {
|
|
switch (lr) {
|
|
case SEQ_SIDE_LEFT:
|
|
neighbor->flag |= SELECT;
|
|
recurs_sel_seq(neighbor);
|
|
neighbor->flag |= SEQ_RIGHTSEL;
|
|
seq->flag |= SEQ_LEFTSEL;
|
|
break;
|
|
case SEQ_SIDE_RIGHT:
|
|
neighbor->flag |= SELECT;
|
|
recurs_sel_seq(neighbor);
|
|
neighbor->flag |= SEQ_LEFTSEL;
|
|
seq->flag |= SEQ_RIGHTSEL;
|
|
break;
|
|
}
|
|
seq->flag |= SELECT;
|
|
changed = true;
|
|
}
|
|
}
|
|
if (changed) {
|
|
/* Pass. */
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void recurs_sel_seq(Sequence *seq_meta)
|
|
{
|
|
Sequence *seq;
|
|
seq = static_cast<Sequence *>(seq_meta->seqbase.first);
|
|
|
|
while (seq) {
|
|
|
|
if (seq_meta->flag & (SEQ_LEFTSEL + SEQ_RIGHTSEL)) {
|
|
seq->flag &= ~SEQ_ALLSEL;
|
|
}
|
|
else if (seq_meta->flag & SELECT) {
|
|
seq->flag |= SELECT;
|
|
}
|
|
else {
|
|
seq->flag &= ~SEQ_ALLSEL;
|
|
}
|
|
|
|
if (seq->seqbase.first) {
|
|
recurs_sel_seq(seq);
|
|
}
|
|
|
|
seq = static_cast<Sequence *>(seq->next);
|
|
}
|
|
}
|
|
|
|
static bool seq_point_image_isect(const Scene *scene, const Sequence *seq, float point[2])
|
|
{
|
|
float seq_image_quad[4][2];
|
|
SEQ_image_transform_final_quad_get(scene, seq, seq_image_quad);
|
|
return isect_point_quad_v2(
|
|
point, seq_image_quad[0], seq_image_quad[1], seq_image_quad[2], seq_image_quad[3]);
|
|
}
|
|
|
|
static void sequencer_select_do_updates(bContext *C, Scene *scene)
|
|
{
|
|
ED_outliner_select_sync_from_sequence_tag(C);
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name (De)select All Operator
|
|
* \{ */
|
|
|
|
static int sequencer_de_select_all_exec(bContext *C, wmOperator *op)
|
|
{
|
|
int action = RNA_enum_get(op->ptr, "action");
|
|
Scene *scene = CTX_data_scene(C);
|
|
|
|
if (sequencer_view_has_preview_poll(C) && !sequencer_view_preview_only_poll(C)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (sequencer_retiming_mode_is_active(C) && retiming_keys_are_visible(C)) {
|
|
return sequencer_retiming_select_all_exec(C, op);
|
|
}
|
|
|
|
blender::VectorSet strips = all_strips_from_context(C);
|
|
|
|
if (action == SEL_TOGGLE) {
|
|
action = SEL_SELECT;
|
|
for (Sequence *seq : strips) {
|
|
if (seq->flag & SEQ_ALLSEL) {
|
|
action = SEL_DESELECT;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (Sequence *seq : strips) {
|
|
switch (action) {
|
|
case SEL_SELECT:
|
|
seq->flag &= ~(SEQ_LEFTSEL + SEQ_RIGHTSEL);
|
|
seq->flag |= SELECT;
|
|
break;
|
|
case SEL_DESELECT:
|
|
seq->flag &= ~SEQ_ALLSEL;
|
|
break;
|
|
case SEL_INVERT:
|
|
if (seq->flag & SEQ_ALLSEL) {
|
|
seq->flag &= ~SEQ_ALLSEL;
|
|
}
|
|
else {
|
|
seq->flag &= ~(SEQ_LEFTSEL + SEQ_RIGHTSEL);
|
|
seq->flag |= SELECT;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
ED_outliner_select_sync_from_sequence_tag(C);
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_select_all(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "(De)select All";
|
|
ot->idname = "SEQUENCER_OT_select_all";
|
|
ot->description = "Select or deselect all strips";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_de_select_all_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_UNDO;
|
|
|
|
WM_operator_properties_select_all(ot);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Select Inverse Operator
|
|
* \{ */
|
|
|
|
static int sequencer_select_inverse_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
|
|
if (sequencer_view_has_preview_poll(C) && !sequencer_view_preview_only_poll(C)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
blender::VectorSet strips = all_strips_from_context(C);
|
|
|
|
for (Sequence *seq : strips) {
|
|
if (seq->flag & SELECT) {
|
|
seq->flag &= ~SEQ_ALLSEL;
|
|
}
|
|
else {
|
|
seq->flag &= ~(SEQ_LEFTSEL + SEQ_RIGHTSEL);
|
|
seq->flag |= SELECT;
|
|
}
|
|
}
|
|
|
|
ED_outliner_select_sync_from_sequence_tag(C);
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_select_inverse(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Select Inverse";
|
|
ot->idname = "SEQUENCER_OT_select_inverse";
|
|
ot->description = "Select unselected strips";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_select_inverse_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Select Operator
|
|
* \{ */
|
|
|
|
static void sequencer_select_set_active(Scene *scene, Sequence *seq)
|
|
{
|
|
Editing *ed = SEQ_editing_get(scene);
|
|
|
|
SEQ_select_active_set(scene, seq);
|
|
|
|
if (ELEM(seq->type, SEQ_TYPE_IMAGE, SEQ_TYPE_MOVIE)) {
|
|
if (seq->strip) {
|
|
BLI_strncpy(ed->act_imagedir, seq->strip->dirpath, FILE_MAXDIR);
|
|
}
|
|
}
|
|
else if (seq->type == SEQ_TYPE_SOUND_RAM) {
|
|
if (seq->strip) {
|
|
BLI_strncpy(ed->act_sounddir, seq->strip->dirpath, FILE_MAXDIR);
|
|
}
|
|
}
|
|
recurs_sel_seq(seq);
|
|
}
|
|
|
|
static void sequencer_select_side_of_frame(const bContext *C,
|
|
const View2D *v2d,
|
|
const int mval[2],
|
|
Scene *scene)
|
|
{
|
|
Editing *ed = SEQ_editing_get(scene);
|
|
|
|
const float x = UI_view2d_region_to_view_x(v2d, mval[0]);
|
|
LISTBASE_FOREACH (Sequence *, seq_iter, SEQ_active_seqbase_get(ed)) {
|
|
if (((x < scene->r.cfra) &&
|
|
(SEQ_time_right_handle_frame_get(scene, seq_iter) <= scene->r.cfra)) ||
|
|
((x >= scene->r.cfra) &&
|
|
(SEQ_time_left_handle_frame_get(scene, seq_iter) >= scene->r.cfra)))
|
|
{
|
|
/* Select left or right. */
|
|
seq_iter->flag |= SELECT;
|
|
recurs_sel_seq(seq_iter);
|
|
}
|
|
}
|
|
|
|
{
|
|
SpaceSeq *sseq = CTX_wm_space_seq(C);
|
|
if (sseq && sseq->flag & SEQ_MARKER_TRANS) {
|
|
|
|
LISTBASE_FOREACH (TimeMarker *, tmarker, &scene->markers) {
|
|
if (((x < scene->r.cfra) && (tmarker->frame <= scene->r.cfra)) ||
|
|
((x >= scene->r.cfra) && (tmarker->frame >= scene->r.cfra)))
|
|
{
|
|
tmarker->flag |= SELECT;
|
|
}
|
|
else {
|
|
tmarker->flag &= ~SELECT;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void sequencer_select_linked_handle(const bContext *C,
|
|
Sequence *seq,
|
|
const int handle_clicked)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = SEQ_editing_get(scene);
|
|
if (!ELEM(handle_clicked, SEQ_SIDE_LEFT, SEQ_SIDE_RIGHT)) {
|
|
/* First click selects the strip and its adjacent handles (if valid).
|
|
* Second click selects the strip,
|
|
* both of its handles and its adjacent handles (if valid). */
|
|
const bool is_striponly_selected = ((seq->flag & SEQ_ALLSEL) == SELECT);
|
|
seq->flag &= ~SEQ_ALLSEL;
|
|
seq->flag |= is_striponly_selected ? SEQ_ALLSEL : SELECT;
|
|
select_surrounding_handles(scene, seq);
|
|
}
|
|
else {
|
|
/* Always select the strip under the cursor. */
|
|
seq->flag |= SELECT;
|
|
|
|
/* First click selects adjacent handles on that side.
|
|
* Second click selects all strips in that direction.
|
|
* If there are no adjacent strips, it just selects all in that direction.
|
|
*/
|
|
int sel_side = handle_clicked;
|
|
Sequence *neighbor = find_neighboring_sequence(scene, seq, sel_side, -1);
|
|
if (neighbor) {
|
|
switch (sel_side) {
|
|
case SEQ_SIDE_LEFT:
|
|
if ((seq->flag & SEQ_LEFTSEL) && (neighbor->flag & SEQ_RIGHTSEL)) {
|
|
seq->flag |= SELECT;
|
|
select_active_side(scene,
|
|
ed->seqbasep,
|
|
SEQ_SIDE_LEFT,
|
|
seq->machine,
|
|
SEQ_time_left_handle_frame_get(scene, seq));
|
|
}
|
|
else {
|
|
seq->flag |= SELECT;
|
|
neighbor->flag |= SELECT;
|
|
recurs_sel_seq(neighbor);
|
|
neighbor->flag |= SEQ_RIGHTSEL;
|
|
seq->flag |= SEQ_LEFTSEL;
|
|
}
|
|
break;
|
|
case SEQ_SIDE_RIGHT:
|
|
if ((seq->flag & SEQ_RIGHTSEL) && (neighbor->flag & SEQ_LEFTSEL)) {
|
|
seq->flag |= SELECT;
|
|
select_active_side(scene,
|
|
ed->seqbasep,
|
|
SEQ_SIDE_RIGHT,
|
|
seq->machine,
|
|
SEQ_time_left_handle_frame_get(scene, seq));
|
|
}
|
|
else {
|
|
seq->flag |= SELECT;
|
|
neighbor->flag |= SELECT;
|
|
recurs_sel_seq(neighbor);
|
|
neighbor->flag |= SEQ_LEFTSEL;
|
|
seq->flag |= SEQ_RIGHTSEL;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
|
|
select_active_side(
|
|
scene, ed->seqbasep, sel_side, seq->machine, SEQ_time_left_handle_frame_get(scene, seq));
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Collect sequencer that are candidates for being selected. */
|
|
struct SeqSelect_Link {
|
|
SeqSelect_Link *next, *prev;
|
|
Sequence *seq;
|
|
/** Only use for center selection. */
|
|
float center_dist_sq;
|
|
};
|
|
|
|
static int seq_sort_for_depth_select(const void *a, const void *b)
|
|
{
|
|
const SeqSelect_Link *slink_a = static_cast<const SeqSelect_Link *>(a);
|
|
const SeqSelect_Link *slink_b = static_cast<const SeqSelect_Link *>(b);
|
|
|
|
/* Exactly overlapping strips, sort by machine (so the top-most is first). */
|
|
if (slink_a->seq->machine < slink_b->seq->machine) {
|
|
return 1;
|
|
}
|
|
if (slink_a->seq->machine > slink_b->seq->machine) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int seq_sort_for_center_select(const void *a, const void *b)
|
|
{
|
|
const SeqSelect_Link *slink_a = static_cast<const SeqSelect_Link *>(a);
|
|
const SeqSelect_Link *slink_b = static_cast<const SeqSelect_Link *>(b);
|
|
if (slink_a->center_dist_sq > slink_b->center_dist_sq) {
|
|
return 1;
|
|
}
|
|
if (slink_a->center_dist_sq < slink_b->center_dist_sq) {
|
|
return -1;
|
|
}
|
|
|
|
/* Exactly overlapping strips, use depth. */
|
|
return seq_sort_for_depth_select(a, b);
|
|
}
|
|
|
|
/**
|
|
* Check if click happened on image which belongs to strip.
|
|
* If multiple strips are found, loop through them in order
|
|
* (depth (top-most first) or closest to mouse when `center` is true).
|
|
*/
|
|
static Sequence *seq_select_seq_from_preview(
|
|
const bContext *C, const int mval[2], const bool toggle, const bool extend, const bool center)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = SEQ_editing_get(scene);
|
|
ListBase *seqbase = SEQ_active_seqbase_get(ed);
|
|
ListBase *channels = SEQ_channels_displayed_get(ed);
|
|
SpaceSeq *sseq = CTX_wm_space_seq(C);
|
|
View2D *v2d = UI_view2d_fromcontext(C);
|
|
|
|
float mouseco_view[2];
|
|
UI_view2d_region_to_view(v2d, mval[0], mval[1], &mouseco_view[0], &mouseco_view[1]);
|
|
|
|
/* Always update the coordinates (check extended after). */
|
|
const bool use_cycle = (!WM_cursor_test_motion_and_update(mval) || extend || toggle);
|
|
|
|
/* Allow strips this far from the closest center to be included.
|
|
* This allows cycling over center points which are near enough
|
|
* to overlapping from the users perspective. */
|
|
const float center_dist_sq_max = square_f(75.0f * U.pixelsize);
|
|
const float center_scale_px[2] = {
|
|
UI_view2d_scale_get_x(v2d),
|
|
UI_view2d_scale_get_y(v2d),
|
|
};
|
|
|
|
blender::VectorSet strips = SEQ_query_rendered_strips(
|
|
scene, channels, seqbase, scene->r.cfra, sseq->chanshown);
|
|
|
|
SeqSelect_Link *slink_active = nullptr;
|
|
Sequence *seq_active = SEQ_select_active_get(scene);
|
|
ListBase strips_ordered = {nullptr};
|
|
for (Sequence *seq : strips) {
|
|
bool isect = false;
|
|
float center_dist_sq_test = 0.0f;
|
|
if (center) {
|
|
/* Detect overlapping center points (scaled by the zoom level). */
|
|
float co[2];
|
|
SEQ_image_transform_origin_offset_pixelspace_get(scene, seq, co);
|
|
sub_v2_v2(co, mouseco_view);
|
|
mul_v2_v2(co, center_scale_px);
|
|
center_dist_sq_test = len_squared_v2(co);
|
|
isect = center_dist_sq_test <= center_dist_sq_max;
|
|
if (isect) {
|
|
/* Use an active strip penalty for "center" selection when cycle is enabled. */
|
|
if (use_cycle && (seq == seq_active) && (seq_active->flag & SELECT)) {
|
|
center_dist_sq_test = square_f(sqrtf(center_dist_sq_test) + (3.0f * U.pixelsize));
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
isect = seq_point_image_isect(scene, seq, mouseco_view);
|
|
}
|
|
|
|
if (isect) {
|
|
SeqSelect_Link *slink = MEM_cnew<SeqSelect_Link>(__func__);
|
|
slink->seq = seq;
|
|
slink->center_dist_sq = center_dist_sq_test;
|
|
BLI_addtail(&strips_ordered, slink);
|
|
|
|
if (seq == seq_active) {
|
|
slink_active = slink;
|
|
}
|
|
}
|
|
}
|
|
|
|
BLI_listbase_sort(&strips_ordered,
|
|
center ? seq_sort_for_center_select : seq_sort_for_depth_select);
|
|
|
|
SeqSelect_Link *slink_select = static_cast<SeqSelect_Link *>(strips_ordered.first);
|
|
Sequence *seq_select = nullptr;
|
|
if (slink_select != nullptr) {
|
|
/* Only use special behavior for the active strip when it's selected. */
|
|
if ((center == false) && slink_active && (seq_active->flag & SELECT)) {
|
|
if (use_cycle) {
|
|
if (slink_active->next) {
|
|
slink_select = slink_active->next;
|
|
}
|
|
}
|
|
else {
|
|
/* Match object selection behavior: keep the current active item unless cycle is enabled.
|
|
* Clicking again in the same location will cycle away from the active object. */
|
|
slink_select = slink_active;
|
|
}
|
|
}
|
|
seq_select = slink_select->seq;
|
|
}
|
|
|
|
BLI_freelistN(&strips_ordered);
|
|
|
|
return seq_select;
|
|
}
|
|
|
|
static bool element_already_selected(const Sequence *seq, const int handle_clicked)
|
|
{
|
|
const bool handle_already_selected = ((handle_clicked == SEQ_SIDE_LEFT) &&
|
|
(seq->flag & SEQ_LEFTSEL)) ||
|
|
((handle_clicked == SEQ_SIDE_RIGHT) &&
|
|
(seq->flag & SEQ_RIGHTSEL));
|
|
return ((seq->flag & SELECT) && handle_clicked == SEQ_SIDE_NONE) || handle_already_selected;
|
|
}
|
|
|
|
static void sequencer_select_strip_impl(const Editing *ed,
|
|
Sequence *seq,
|
|
const int handle_clicked,
|
|
const bool extend,
|
|
const bool deselect,
|
|
const bool toggle)
|
|
{
|
|
const bool is_active = (ed->act_seq == seq);
|
|
|
|
/* Exception for active strip handles. */
|
|
if ((handle_clicked != SEQ_SIDE_NONE) && (seq->flag & SELECT) && is_active && toggle) {
|
|
switch (handle_clicked) {
|
|
case SEQ_SIDE_LEFT:
|
|
seq->flag ^= SEQ_LEFTSEL;
|
|
break;
|
|
case SEQ_SIDE_RIGHT:
|
|
seq->flag ^= SEQ_RIGHTSEL;
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Select strip. */
|
|
/* Match object selection behavior. */
|
|
int action = -1;
|
|
if (extend) {
|
|
action = 1;
|
|
}
|
|
else if (deselect) {
|
|
action = 0;
|
|
}
|
|
else {
|
|
if (!((seq->flag & SELECT) && is_active)) {
|
|
action = 1;
|
|
}
|
|
else if (toggle) {
|
|
action = 0;
|
|
}
|
|
}
|
|
|
|
if (action == 1) {
|
|
seq->flag |= SELECT;
|
|
if (handle_clicked == SEQ_SIDE_LEFT) {
|
|
seq->flag |= SEQ_LEFTSEL;
|
|
}
|
|
if (handle_clicked == SEQ_SIDE_RIGHT) {
|
|
seq->flag |= SEQ_RIGHTSEL;
|
|
}
|
|
}
|
|
else if (action == 0) {
|
|
seq->flag &= ~SEQ_ALLSEL;
|
|
}
|
|
}
|
|
|
|
static bool use_retiming_mode(const bContext *C, const Sequence *seq_key_test)
|
|
{
|
|
return seq_key_test && SEQ_retiming_data_is_editable(seq_key_test) &&
|
|
!sequencer_retiming_mode_is_active(C) && retiming_keys_are_visible(C);
|
|
}
|
|
|
|
int sequencer_select_exec(bContext *C, wmOperator *op)
|
|
{
|
|
View2D *v2d = UI_view2d_fromcontext(C);
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = SEQ_editing_get(scene);
|
|
ARegion *region = CTX_wm_region(C);
|
|
|
|
if (ed == nullptr) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (region->regiontype == RGN_TYPE_PREVIEW) {
|
|
if (!sequencer_view_preview_only_poll(C)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
const SpaceSeq *sseq = CTX_wm_space_seq(C);
|
|
if (sseq->mainb != SEQ_DRAW_IMG_IMBUF) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
}
|
|
|
|
if (sequencer_retiming_mode_is_active(C) && retiming_keys_are_visible(C)) {
|
|
return sequencer_retiming_key_select_exec(C, op);
|
|
}
|
|
|
|
bool extend = RNA_boolean_get(op->ptr, "extend");
|
|
bool deselect = RNA_boolean_get(op->ptr, "deselect");
|
|
bool deselect_all = RNA_boolean_get(op->ptr, "deselect_all");
|
|
bool toggle = RNA_boolean_get(op->ptr, "toggle");
|
|
bool center = RNA_boolean_get(op->ptr, "center");
|
|
|
|
int mval[2];
|
|
mval[0] = RNA_int_get(op->ptr, "mouse_x");
|
|
mval[1] = RNA_int_get(op->ptr, "mouse_y");
|
|
|
|
int handle_clicked = SEQ_SIDE_NONE;
|
|
Sequence *seq = nullptr;
|
|
if (region->regiontype == RGN_TYPE_PREVIEW) {
|
|
seq = seq_select_seq_from_preview(C, mval, toggle, extend, center);
|
|
}
|
|
else {
|
|
seq = find_nearest_seq(scene, v2d, mval, &handle_clicked);
|
|
}
|
|
|
|
Sequence *seq_key_test = nullptr;
|
|
SeqRetimingKey *key = retiming_mousover_key_get(C, mval, &seq_key_test);
|
|
|
|
/* NOTE: `side_of_frame` and `linked_time` functionality is designed to be shared on one keymap,
|
|
* therefore both properties can be true at the same time. */
|
|
if (seq && RNA_boolean_get(op->ptr, "linked_time")) {
|
|
if (use_retiming_mode(C, seq_key_test)) {
|
|
return sequencer_retiming_select_linked_time(C, op);
|
|
}
|
|
else {
|
|
if (!extend && !toggle) {
|
|
ED_sequencer_deselect_all(scene);
|
|
}
|
|
sequencer_select_strip_impl(ed, seq, handle_clicked, extend, deselect, toggle);
|
|
select_linked_time(scene, ed->seqbasep, seq);
|
|
sequencer_select_do_updates(C, scene);
|
|
sequencer_select_set_active(scene, seq);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
}
|
|
|
|
/* Select left, right or overlapping the current frame. */
|
|
if (RNA_boolean_get(op->ptr, "side_of_frame")) {
|
|
if (!extend && !toggle) {
|
|
ED_sequencer_deselect_all(scene);
|
|
}
|
|
sequencer_select_side_of_frame(C, v2d, mval, scene);
|
|
sequencer_select_do_updates(C, scene);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
/* On Alt selection, select the strip and bordering handles. */
|
|
if (seq && RNA_boolean_get(op->ptr, "linked_handle")) {
|
|
if (!extend && !toggle) {
|
|
ED_sequencer_deselect_all(scene);
|
|
}
|
|
sequencer_select_linked_handle(C, seq, handle_clicked);
|
|
sequencer_select_do_updates(C, scene);
|
|
sequencer_select_set_active(scene, seq);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
const bool wait_to_deselect_others = RNA_boolean_get(op->ptr, "wait_to_deselect_others");
|
|
|
|
/* Clicking on already selected element falls on modal operation.
|
|
* All strips are deselected on mouse button release unless extend mode is used. */
|
|
if (seq && element_already_selected(seq, handle_clicked) && wait_to_deselect_others && !toggle) {
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
if (use_retiming_mode(C, seq_key_test)) {
|
|
|
|
/* Realize "fake" key, if it is clicked on. */
|
|
if (key == nullptr && seq_key_test != nullptr) {
|
|
key = try_to_realize_virtual_key(C, seq_key_test, mval);
|
|
}
|
|
|
|
bool retiming_key_clicked = (key != nullptr);
|
|
|
|
if (seq_key_test && retiming_key_clicked) {
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
ED_sequencer_deselect_all(scene);
|
|
SEQ_retiming_selection_clear(ed);
|
|
SEQ_retiming_selection_append(key);
|
|
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
|
|
}
|
|
}
|
|
|
|
bool changed = false;
|
|
|
|
/* Deselect everything */
|
|
if (deselect_all || (seq && (extend == false && deselect == false && toggle == false))) {
|
|
changed |= ED_sequencer_deselect_all(scene);
|
|
}
|
|
|
|
/* Nothing to select, but strips could be deselected. */
|
|
if (!seq) {
|
|
if (changed) {
|
|
sequencer_select_do_updates(C, scene);
|
|
}
|
|
return changed ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* Do actual selection. */
|
|
sequencer_select_strip_impl(ed, seq, handle_clicked, extend, deselect, toggle);
|
|
|
|
sequencer_select_do_updates(C, scene);
|
|
sequencer_select_set_active(scene, seq);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static int sequencer_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
const int retval = WM_generic_select_invoke(C, op, event);
|
|
ARegion *region = CTX_wm_region(C);
|
|
if (region && (region->regiontype == RGN_TYPE_PREVIEW)) {
|
|
return WM_operator_flag_only_pass_through_on_press(retval, event);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
void SEQUENCER_OT_select(wmOperatorType *ot)
|
|
{
|
|
PropertyRNA *prop;
|
|
|
|
/* Identifiers. */
|
|
ot->name = "Select";
|
|
ot->idname = "SEQUENCER_OT_select";
|
|
ot->description = "Select a strip (last selected becomes the \"active strip\")";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_select_exec;
|
|
ot->invoke = sequencer_select_invoke;
|
|
ot->modal = WM_generic_select_modal;
|
|
ot->poll = ED_operator_sequencer_active;
|
|
ot->get_name = ED_select_pick_get_name;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_UNDO;
|
|
|
|
/* Properties. */
|
|
WM_operator_properties_generic_select(ot);
|
|
|
|
WM_operator_properties_mouse_select(ot);
|
|
|
|
prop = RNA_def_boolean(
|
|
ot->srna,
|
|
"center",
|
|
false,
|
|
"Center",
|
|
"Use the object center when selecting, in edit mode used to extend object selection");
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
|
|
|
prop = RNA_def_boolean(ot->srna,
|
|
"linked_handle",
|
|
false,
|
|
"Linked Handle",
|
|
"Select handles next to the active strip");
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
|
|
|
prop = RNA_def_boolean(
|
|
ot->srna, "linked_time", false, "Linked Time", "Select other strips at the same time");
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
|
|
|
prop = RNA_def_boolean(
|
|
ot->srna,
|
|
"side_of_frame",
|
|
false,
|
|
"Side of Frame",
|
|
"Select all strips on same side of the current frame as the mouse cursor");
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Select More Operator
|
|
* \{ */
|
|
|
|
/* Run recursively to select linked. */
|
|
static bool select_linked_internal(Scene *scene)
|
|
{
|
|
Editing *ed = SEQ_editing_get(scene);
|
|
|
|
if (ed == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
bool changed = false;
|
|
|
|
LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) {
|
|
if ((seq->flag & SELECT) == 0) {
|
|
continue;
|
|
}
|
|
/* Only get unselected neighbors. */
|
|
Sequence *neighbor = find_neighboring_sequence(scene, seq, SEQ_SIDE_LEFT, 0);
|
|
if (neighbor) {
|
|
neighbor->flag |= SELECT;
|
|
recurs_sel_seq(neighbor);
|
|
changed = true;
|
|
}
|
|
neighbor = find_neighboring_sequence(scene, seq, SEQ_SIDE_RIGHT, 0);
|
|
if (neighbor) {
|
|
neighbor->flag |= SELECT;
|
|
recurs_sel_seq(neighbor);
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
/* Select only one linked strip on each side. */
|
|
static bool select_more_less_seq__internal(Scene *scene, bool select_more)
|
|
{
|
|
Editing *ed = SEQ_editing_get(scene);
|
|
|
|
if (ed == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
GSet *neighbors = BLI_gset_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, "Linked strips");
|
|
const int neighbor_selection_filter = select_more ? 0 : SELECT;
|
|
const int selection_filter = select_more ? SELECT : 0;
|
|
|
|
LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) {
|
|
if ((seq->flag & SELECT) != selection_filter) {
|
|
continue;
|
|
}
|
|
Sequence *neighbor = find_neighboring_sequence(
|
|
scene, seq, SEQ_SIDE_LEFT, neighbor_selection_filter);
|
|
if (neighbor) {
|
|
BLI_gset_add(neighbors, neighbor);
|
|
}
|
|
neighbor = find_neighboring_sequence(scene, seq, SEQ_SIDE_RIGHT, neighbor_selection_filter);
|
|
if (neighbor) {
|
|
BLI_gset_add(neighbors, neighbor);
|
|
}
|
|
}
|
|
|
|
bool changed = false;
|
|
GSetIterator gsi;
|
|
BLI_gsetIterator_init(&gsi, neighbors);
|
|
while (!BLI_gsetIterator_done(&gsi)) {
|
|
Sequence *neighbor = static_cast<Sequence *>(BLI_gsetIterator_getKey(&gsi));
|
|
if (select_more) {
|
|
neighbor->flag |= SELECT;
|
|
recurs_sel_seq(neighbor);
|
|
}
|
|
else {
|
|
neighbor->flag &= ~SELECT;
|
|
}
|
|
changed = true;
|
|
BLI_gsetIterator_step(&gsi);
|
|
}
|
|
|
|
BLI_gset_free(neighbors, nullptr);
|
|
return changed;
|
|
}
|
|
|
|
static int sequencer_select_more_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
|
|
if (!select_more_less_seq__internal(scene, true)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
ED_outliner_select_sync_from_sequence_tag(C);
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_select_more(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Select More";
|
|
ot->idname = "SEQUENCER_OT_select_more";
|
|
ot->description = "Select more strips adjacent to the current selection";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_select_more_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Select Less Operator
|
|
* \{ */
|
|
|
|
static int sequencer_select_less_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
|
|
if (!select_more_less_seq__internal(scene, false)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
ED_outliner_select_sync_from_sequence_tag(C);
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_select_less(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Select Less";
|
|
ot->idname = "SEQUENCER_OT_select_less";
|
|
ot->description = "Shrink the current selection of adjacent selected strips";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_select_less_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Select Pick Linked Operator
|
|
* \{ */
|
|
|
|
static int sequencer_select_linked_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
View2D *v2d = UI_view2d_fromcontext(C);
|
|
|
|
bool extend = RNA_boolean_get(op->ptr, "extend");
|
|
|
|
Sequence *mouse_seq;
|
|
int selected, hand;
|
|
|
|
/* This works like UV, not mesh. */
|
|
mouse_seq = find_nearest_seq(scene, v2d, event->mval, &hand);
|
|
if (!mouse_seq) {
|
|
return OPERATOR_FINISHED; /* User error as with mesh?? */
|
|
}
|
|
|
|
if (extend == 0) {
|
|
ED_sequencer_deselect_all(scene);
|
|
}
|
|
|
|
mouse_seq->flag |= SELECT;
|
|
recurs_sel_seq(mouse_seq);
|
|
|
|
selected = 1;
|
|
while (selected) {
|
|
selected = select_linked_internal(scene);
|
|
}
|
|
|
|
ED_outliner_select_sync_from_sequence_tag(C);
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_select_linked_pick(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Select Pick Linked";
|
|
ot->idname = "SEQUENCER_OT_select_linked_pick";
|
|
ot->description = "Select a chain of linked strips nearest to the mouse pointer";
|
|
|
|
/* Api callbacks. */
|
|
ot->invoke = sequencer_select_linked_pick_invoke;
|
|
ot->poll = ED_operator_sequencer_active;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
/* Properties. */
|
|
PropertyRNA *prop;
|
|
prop = RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend the selection");
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Select Linked Operator
|
|
* \{ */
|
|
|
|
static int sequencer_select_linked_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
bool selected;
|
|
|
|
selected = true;
|
|
while (selected) {
|
|
selected = select_linked_internal(scene);
|
|
}
|
|
|
|
ED_outliner_select_sync_from_sequence_tag(C);
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_select_linked(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Select Linked";
|
|
ot->idname = "SEQUENCER_OT_select_linked";
|
|
ot->description = "Select all strips adjacent to the current selection";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_select_linked_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Select Handles Operator
|
|
* \{ */
|
|
|
|
enum {
|
|
SEQ_SELECT_HANDLES_SIDE_LEFT,
|
|
SEQ_SELECT_HANDLES_SIDE_RIGHT,
|
|
SEQ_SELECT_HANDLES_SIDE_BOTH,
|
|
SEQ_SELECT_HANDLES_SIDE_LEFT_NEIGHBOR,
|
|
SEQ_SELECT_HANDLES_SIDE_RIGHT_NEIGHBOR,
|
|
SEQ_SELECT_HANDLES_SIDE_BOTH_NEIGHBORS,
|
|
};
|
|
|
|
static const EnumPropertyItem prop_select_handles_side_types[] = {
|
|
{SEQ_SELECT_HANDLES_SIDE_LEFT, "LEFT", 0, "Left", ""},
|
|
{SEQ_SELECT_HANDLES_SIDE_RIGHT, "RIGHT", 0, "Right", ""},
|
|
{SEQ_SELECT_HANDLES_SIDE_BOTH, "BOTH", 0, "Both", ""},
|
|
{SEQ_SELECT_HANDLES_SIDE_LEFT_NEIGHBOR, "LEFT_NEIGHBOR", 0, "Left Neighbor", ""},
|
|
{SEQ_SELECT_HANDLES_SIDE_RIGHT_NEIGHBOR, "RIGHT_NEIGHBOR", 0, "Right Neighbor", ""},
|
|
{SEQ_SELECT_HANDLES_SIDE_BOTH_NEIGHBORS, "BOTH_NEIGHBORS", 0, "Both Neighbors", ""},
|
|
{0, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
static int sequencer_select_handles_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = SEQ_editing_get(scene);
|
|
int sel_side = RNA_enum_get(op->ptr, "side");
|
|
LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) {
|
|
if (seq->flag & SELECT) {
|
|
Sequence *l_neighbor = find_neighboring_sequence(scene, seq, SEQ_SIDE_LEFT, -1);
|
|
Sequence *r_neighbor = find_neighboring_sequence(scene, seq, SEQ_SIDE_RIGHT, -1);
|
|
|
|
switch (sel_side) {
|
|
case SEQ_SELECT_HANDLES_SIDE_LEFT:
|
|
seq->flag &= ~SEQ_RIGHTSEL;
|
|
seq->flag |= SEQ_LEFTSEL;
|
|
break;
|
|
case SEQ_SELECT_HANDLES_SIDE_RIGHT:
|
|
seq->flag &= ~SEQ_LEFTSEL;
|
|
seq->flag |= SEQ_RIGHTSEL;
|
|
break;
|
|
case SEQ_SELECT_HANDLES_SIDE_BOTH:
|
|
seq->flag |= SEQ_LEFTSEL | SEQ_RIGHTSEL;
|
|
break;
|
|
case SEQ_SELECT_HANDLES_SIDE_LEFT_NEIGHBOR:
|
|
if (l_neighbor) {
|
|
if (!(l_neighbor->flag & SELECT)) {
|
|
l_neighbor->flag |= SEQ_RIGHTSEL;
|
|
}
|
|
}
|
|
break;
|
|
case SEQ_SELECT_HANDLES_SIDE_RIGHT_NEIGHBOR:
|
|
if (r_neighbor) {
|
|
if (!(r_neighbor->flag & SELECT)) {
|
|
r_neighbor->flag |= SEQ_LEFTSEL;
|
|
}
|
|
}
|
|
break;
|
|
case SEQ_SELECT_HANDLES_SIDE_BOTH_NEIGHBORS:
|
|
if (l_neighbor) {
|
|
if (!(l_neighbor->flag & SELECT)) {
|
|
l_neighbor->flag |= SEQ_RIGHTSEL;
|
|
}
|
|
}
|
|
if (r_neighbor) {
|
|
if (!(r_neighbor->flag & SELECT)) {
|
|
r_neighbor->flag |= SEQ_LEFTSEL;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* Select strips */
|
|
LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) {
|
|
if ((seq->flag & SEQ_LEFTSEL) || (seq->flag & SEQ_RIGHTSEL)) {
|
|
if (!(seq->flag & SELECT)) {
|
|
seq->flag |= SELECT;
|
|
recurs_sel_seq(seq);
|
|
}
|
|
}
|
|
}
|
|
|
|
ED_outliner_select_sync_from_sequence_tag(C);
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_select_handles(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Select Handles";
|
|
ot->idname = "SEQUENCER_OT_select_handles";
|
|
ot->description = "Select gizmo handles on the sides of the selected strip";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_select_handles_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
/* Properties. */
|
|
RNA_def_enum(ot->srna,
|
|
"side",
|
|
prop_select_handles_side_types,
|
|
SEQ_SELECT_HANDLES_SIDE_BOTH,
|
|
"Side",
|
|
"The side of the handle that is selected");
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Select Side of Frame Operator
|
|
* \{ */
|
|
|
|
static int sequencer_select_side_of_frame_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = SEQ_editing_get(scene);
|
|
const bool extend = RNA_boolean_get(op->ptr, "extend");
|
|
const int side = RNA_enum_get(op->ptr, "side");
|
|
|
|
if (ed == nullptr) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
if (extend == false) {
|
|
ED_sequencer_deselect_all(scene);
|
|
}
|
|
const int timeline_frame = scene->r.cfra;
|
|
LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) {
|
|
bool test = false;
|
|
switch (side) {
|
|
case -1:
|
|
test = (timeline_frame >= SEQ_time_right_handle_frame_get(scene, seq));
|
|
break;
|
|
case 1:
|
|
test = (timeline_frame <= SEQ_time_left_handle_frame_get(scene, seq));
|
|
break;
|
|
case 2:
|
|
test = SEQ_time_strip_intersects_frame(scene, seq, timeline_frame);
|
|
break;
|
|
}
|
|
|
|
if (test) {
|
|
seq->flag |= SELECT;
|
|
recurs_sel_seq(seq);
|
|
}
|
|
}
|
|
|
|
ED_outliner_select_sync_from_sequence_tag(C);
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_select_side_of_frame(wmOperatorType *ot)
|
|
{
|
|
static const EnumPropertyItem sequencer_select_left_right_types[] = {
|
|
{-1, "LEFT", 0, "Left", "Select to the left of the current frame"},
|
|
{1, "RIGHT", 0, "Right", "Select to the right of the current frame"},
|
|
{2, "CURRENT", 0, "Current Frame", "Select intersecting with the current frame"},
|
|
{0, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
/* Identifiers. */
|
|
ot->name = "Select Side of Frame";
|
|
ot->idname = "SEQUENCER_OT_select_side_of_frame";
|
|
ot->description = "Select strips relative to the current frame";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_select_side_of_frame_exec;
|
|
ot->poll = ED_operator_sequencer_active;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_UNDO;
|
|
|
|
/* Properties. */
|
|
PropertyRNA *prop;
|
|
prop = RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend the selection");
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
|
ot->prop = RNA_def_enum(ot->srna, "side", sequencer_select_left_right_types, 0, "Side", "");
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Select Side Operator
|
|
* \{ */
|
|
|
|
static int sequencer_select_side_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = SEQ_editing_get(scene);
|
|
|
|
const int sel_side = RNA_enum_get(op->ptr, "side");
|
|
const int frame_init = sel_side == SEQ_SIDE_LEFT ? INT_MIN : INT_MAX;
|
|
int frame_ranges[MAXSEQ];
|
|
bool selected = false;
|
|
|
|
copy_vn_i(frame_ranges, ARRAY_SIZE(frame_ranges), frame_init);
|
|
|
|
LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) {
|
|
if (UNLIKELY(seq->machine >= MAXSEQ)) {
|
|
continue;
|
|
}
|
|
int *frame_limit_p = &frame_ranges[seq->machine];
|
|
if (seq->flag & SELECT) {
|
|
selected = true;
|
|
if (sel_side == SEQ_SIDE_LEFT) {
|
|
*frame_limit_p = max_ii(*frame_limit_p, SEQ_time_left_handle_frame_get(scene, seq));
|
|
}
|
|
else {
|
|
*frame_limit_p = min_ii(*frame_limit_p, SEQ_time_left_handle_frame_get(scene, seq));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (selected == false) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
select_active_side_range(scene, ed->seqbasep, sel_side, frame_ranges, frame_init);
|
|
|
|
ED_outliner_select_sync_from_sequence_tag(C);
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_select_side(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Select Side";
|
|
ot->idname = "SEQUENCER_OT_select_side";
|
|
ot->description = "Select strips on the nominated side of the selected strips";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_select_side_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
/* Properties. */
|
|
RNA_def_enum(ot->srna,
|
|
"side",
|
|
prop_side_types,
|
|
SEQ_SIDE_BOTH,
|
|
"Side",
|
|
"The side to which the selection is applied");
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Box Select Operator
|
|
* \{ */
|
|
|
|
static bool seq_box_select_rect_image_isect(const Scene *scene, const Sequence *seq, rctf *rect)
|
|
{
|
|
float seq_image_quad[4][2];
|
|
SEQ_image_transform_final_quad_get(scene, seq, seq_image_quad);
|
|
float rect_quad[4][2] = {{rect->xmax, rect->ymax},
|
|
{rect->xmax, rect->ymin},
|
|
{rect->xmin, rect->ymin},
|
|
{rect->xmin, rect->ymax}};
|
|
|
|
return seq_point_image_isect(scene, seq, rect_quad[0]) ||
|
|
seq_point_image_isect(scene, seq, rect_quad[1]) ||
|
|
seq_point_image_isect(scene, seq, rect_quad[2]) ||
|
|
seq_point_image_isect(scene, seq, rect_quad[3]) ||
|
|
isect_point_quad_v2(
|
|
seq_image_quad[0], rect_quad[0], rect_quad[1], rect_quad[2], rect_quad[3]) ||
|
|
isect_point_quad_v2(
|
|
seq_image_quad[1], rect_quad[0], rect_quad[1], rect_quad[2], rect_quad[3]) ||
|
|
isect_point_quad_v2(
|
|
seq_image_quad[2], rect_quad[0], rect_quad[1], rect_quad[2], rect_quad[3]) ||
|
|
isect_point_quad_v2(
|
|
seq_image_quad[3], rect_quad[0], rect_quad[1], rect_quad[2], rect_quad[3]);
|
|
}
|
|
|
|
static void seq_box_select_seq_from_preview(const bContext *C, rctf *rect, const eSelectOp mode)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = SEQ_editing_get(scene);
|
|
ListBase *seqbase = SEQ_active_seqbase_get(ed);
|
|
ListBase *channels = SEQ_channels_displayed_get(ed);
|
|
SpaceSeq *sseq = CTX_wm_space_seq(C);
|
|
|
|
blender::VectorSet strips = SEQ_query_rendered_strips(
|
|
scene, channels, seqbase, scene->r.cfra, sseq->chanshown);
|
|
for (Sequence *seq : strips) {
|
|
if (!seq_box_select_rect_image_isect(scene, seq, rect)) {
|
|
continue;
|
|
}
|
|
|
|
if (ELEM(mode, SEL_OP_ADD, SEL_OP_SET)) {
|
|
seq->flag |= SELECT;
|
|
}
|
|
else {
|
|
BLI_assert(mode == SEL_OP_SUB);
|
|
seq->flag &= ~SELECT;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int sequencer_box_select_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
View2D *v2d = UI_view2d_fromcontext(C);
|
|
Editing *ed = SEQ_editing_get(scene);
|
|
|
|
if (ed == nullptr) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (sequencer_retiming_mode_is_active(C) && retiming_keys_are_visible(C)) {
|
|
return sequencer_retiming_box_select_exec(C, op);
|
|
}
|
|
|
|
const eSelectOp sel_op = eSelectOp(RNA_enum_get(op->ptr, "mode"));
|
|
const bool handles = RNA_boolean_get(op->ptr, "include_handles");
|
|
const bool select = (sel_op != SEL_OP_SUB);
|
|
|
|
bool changed = false;
|
|
|
|
if (SEL_OP_USE_PRE_DESELECT(sel_op)) {
|
|
changed |= ED_sequencer_deselect_all(scene);
|
|
}
|
|
|
|
rctf rectf;
|
|
WM_operator_properties_border_to_rctf(op, &rectf);
|
|
UI_view2d_region_to_view_rctf(v2d, &rectf, &rectf);
|
|
|
|
ARegion *region = CTX_wm_region(C);
|
|
if (region->regiontype == RGN_TYPE_PREVIEW) {
|
|
if (!sequencer_view_preview_only_poll(C)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
seq_box_select_seq_from_preview(C, &rectf, sel_op);
|
|
sequencer_select_do_updates(C, scene);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) {
|
|
rctf rq;
|
|
seq_rectf(scene, seq, &rq);
|
|
if (BLI_rctf_isect(&rq, &rectf, nullptr)) {
|
|
if (handles) {
|
|
/* Get the handles draw size. */
|
|
float pixelx = BLI_rctf_size_x(&v2d->cur) / BLI_rcti_size_x(&v2d->mask);
|
|
float handsize = sequence_handle_size_get_clamped(scene, seq, pixelx);
|
|
|
|
/* Right handle. */
|
|
if (rectf.xmax > (SEQ_time_right_handle_frame_get(scene, seq) - handsize)) {
|
|
if (select) {
|
|
seq->flag |= SELECT | SEQ_RIGHTSEL;
|
|
}
|
|
else {
|
|
/* Deselect the strip if it's left with no handles selected. */
|
|
if ((seq->flag & SEQ_RIGHTSEL) && ((seq->flag & SEQ_LEFTSEL) == 0)) {
|
|
seq->flag &= ~SELECT;
|
|
}
|
|
seq->flag &= ~SEQ_RIGHTSEL;
|
|
}
|
|
|
|
changed = true;
|
|
}
|
|
/* Left handle. */
|
|
if (rectf.xmin < (SEQ_time_left_handle_frame_get(scene, seq) + handsize)) {
|
|
if (select) {
|
|
seq->flag |= SELECT | SEQ_LEFTSEL;
|
|
}
|
|
else {
|
|
/* Deselect the strip if it's left with no handles selected. */
|
|
if ((seq->flag & SEQ_LEFTSEL) && ((seq->flag & SEQ_RIGHTSEL) == 0)) {
|
|
seq->flag &= ~SELECT;
|
|
}
|
|
seq->flag &= ~SEQ_LEFTSEL;
|
|
}
|
|
}
|
|
|
|
changed = true;
|
|
}
|
|
|
|
/* Regular box selection. */
|
|
else {
|
|
SET_FLAG_FROM_TEST(seq->flag, select, SELECT);
|
|
seq->flag &= ~(SEQ_LEFTSEL | SEQ_RIGHTSEL);
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!changed) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
sequencer_select_do_updates(C, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static int sequencer_box_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
View2D *v2d = &CTX_wm_region(C)->v2d;
|
|
ARegion *region = CTX_wm_region(C);
|
|
|
|
if (region->regiontype == RGN_TYPE_PREVIEW && !sequencer_view_preview_only_poll(C)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
const bool tweak = RNA_boolean_get(op->ptr, "tweak");
|
|
|
|
if (tweak) {
|
|
int hand_dummy;
|
|
int mval[2];
|
|
WM_event_drag_start_mval(event, region, mval);
|
|
Sequence *seq = find_nearest_seq(scene, v2d, mval, &hand_dummy);
|
|
if (seq != nullptr) {
|
|
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
|
|
}
|
|
}
|
|
|
|
return WM_gesture_box_invoke(C, op, event);
|
|
}
|
|
|
|
void SEQUENCER_OT_select_box(wmOperatorType *ot)
|
|
{
|
|
PropertyRNA *prop;
|
|
|
|
/* Identifiers. */
|
|
ot->name = "Box Select";
|
|
ot->idname = "SEQUENCER_OT_select_box";
|
|
ot->description = "Select strips using box selection";
|
|
|
|
/* Api callbacks. */
|
|
ot->invoke = sequencer_box_select_invoke;
|
|
ot->exec = sequencer_box_select_exec;
|
|
ot->modal = WM_gesture_box_modal;
|
|
ot->cancel = WM_gesture_box_cancel;
|
|
|
|
ot->poll = ED_operator_sequencer_active;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_UNDO;
|
|
|
|
/* Properties. */
|
|
WM_operator_properties_gesture_box(ot);
|
|
WM_operator_properties_select_operation_simple(ot);
|
|
|
|
prop = RNA_def_boolean(
|
|
ot->srna, "tweak", false, "Tweak", "Operator has been activated using a click-drag event");
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
|
prop = RNA_def_boolean(
|
|
ot->srna, "include_handles", false, "Select Handles", "Select the strips and their handles");
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Select Grouped Operator
|
|
* \{ */
|
|
|
|
enum {
|
|
SEQ_SELECT_GROUP_TYPE,
|
|
SEQ_SELECT_GROUP_TYPE_BASIC,
|
|
SEQ_SELECT_GROUP_TYPE_EFFECT,
|
|
SEQ_SELECT_GROUP_DATA,
|
|
SEQ_SELECT_GROUP_EFFECT,
|
|
SEQ_SELECT_GROUP_EFFECT_LINK,
|
|
SEQ_SELECT_GROUP_OVERLAP,
|
|
};
|
|
|
|
static const EnumPropertyItem sequencer_prop_select_grouped_types[] = {
|
|
{SEQ_SELECT_GROUP_TYPE, "TYPE", 0, "Type", "Shared strip type"},
|
|
{SEQ_SELECT_GROUP_TYPE_BASIC,
|
|
"TYPE_BASIC",
|
|
0,
|
|
"Global Type",
|
|
"All strips of same basic type (graphical or sound)"},
|
|
{SEQ_SELECT_GROUP_TYPE_EFFECT,
|
|
"TYPE_EFFECT",
|
|
0,
|
|
"Effect Type",
|
|
"Shared strip effect type (if active strip is not an effect one, select all non-effect "
|
|
"strips)"},
|
|
{SEQ_SELECT_GROUP_DATA, "DATA", 0, "Data", "Shared data (scene, image, sound, etc.)"},
|
|
{SEQ_SELECT_GROUP_EFFECT, "EFFECT", 0, "Effect", "Shared effects"},
|
|
{SEQ_SELECT_GROUP_EFFECT_LINK,
|
|
"EFFECT_LINK",
|
|
0,
|
|
"Effect/Linked",
|
|
"Other strips affected by the active one (sharing some time, and below or "
|
|
"effect-assigned)"},
|
|
{SEQ_SELECT_GROUP_OVERLAP, "OVERLAP", 0, "Overlap", "Overlapping time"},
|
|
{0, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
#define SEQ_IS_SOUND(_seq) ((_seq->type & SEQ_TYPE_SOUND_RAM) && !(_seq->type & SEQ_TYPE_EFFECT))
|
|
|
|
#define SEQ_IS_EFFECT(_seq) ((_seq->type & SEQ_TYPE_EFFECT) != 0)
|
|
|
|
#define SEQ_USE_DATA(_seq) \
|
|
(ELEM(_seq->type, SEQ_TYPE_SCENE, SEQ_TYPE_MOVIECLIP, SEQ_TYPE_MASK) || SEQ_HAS_PATH(_seq))
|
|
|
|
#define SEQ_CHANNEL_CHECK(_seq, _chan) ELEM((_chan), 0, (_seq)->machine)
|
|
|
|
static bool select_grouped_type(blender::Span<Sequence *> strips,
|
|
ListBase * /*seqbase*/,
|
|
Sequence *actseq,
|
|
const int channel)
|
|
{
|
|
bool changed = false;
|
|
|
|
for (Sequence *seq : strips) {
|
|
if (SEQ_CHANNEL_CHECK(seq, channel) && seq->type == actseq->type) {
|
|
seq->flag |= SELECT;
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
static bool select_grouped_type_basic(blender::Span<Sequence *> strips,
|
|
ListBase * /*seqbase*/,
|
|
Sequence *actseq,
|
|
const int channel)
|
|
{
|
|
bool changed = false;
|
|
const bool is_sound = SEQ_IS_SOUND(actseq);
|
|
|
|
for (Sequence *seq : strips) {
|
|
if (SEQ_CHANNEL_CHECK(seq, channel) && (is_sound ? SEQ_IS_SOUND(seq) : !SEQ_IS_SOUND(seq))) {
|
|
seq->flag |= SELECT;
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
static bool select_grouped_type_effect(blender::Span<Sequence *> strips,
|
|
ListBase * /*seqbase*/,
|
|
Sequence *actseq,
|
|
const int channel)
|
|
{
|
|
bool changed = false;
|
|
const bool is_effect = SEQ_IS_EFFECT(actseq);
|
|
|
|
for (Sequence *seq : strips) {
|
|
if (SEQ_CHANNEL_CHECK(seq, channel) && (is_effect ? SEQ_IS_EFFECT(seq) : !SEQ_IS_EFFECT(seq)))
|
|
{
|
|
seq->flag |= SELECT;
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
static bool select_grouped_data(blender::Span<Sequence *> strips,
|
|
ListBase * /*seqbase*/,
|
|
Sequence *actseq,
|
|
const int channel)
|
|
{
|
|
bool changed = false;
|
|
const char *dirpath = actseq->strip ? actseq->strip->dirpath : nullptr;
|
|
|
|
if (!SEQ_USE_DATA(actseq)) {
|
|
return changed;
|
|
}
|
|
|
|
if (SEQ_HAS_PATH(actseq) && dirpath) {
|
|
for (Sequence *seq : strips) {
|
|
if (SEQ_CHANNEL_CHECK(seq, channel) && SEQ_HAS_PATH(seq) && seq->strip &&
|
|
STREQ(seq->strip->dirpath, dirpath))
|
|
{
|
|
seq->flag |= SELECT;
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
else if (actseq->type == SEQ_TYPE_SCENE) {
|
|
Scene *sce = actseq->scene;
|
|
for (Sequence *seq : strips) {
|
|
if (SEQ_CHANNEL_CHECK(seq, channel) && seq->type == SEQ_TYPE_SCENE && seq->scene == sce) {
|
|
seq->flag |= SELECT;
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
else if (actseq->type == SEQ_TYPE_MOVIECLIP) {
|
|
MovieClip *clip = actseq->clip;
|
|
for (Sequence *seq : strips) {
|
|
if (SEQ_CHANNEL_CHECK(seq, channel) && seq->type == SEQ_TYPE_MOVIECLIP && seq->clip == clip)
|
|
{
|
|
seq->flag |= SELECT;
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
else if (actseq->type == SEQ_TYPE_MASK) {
|
|
Mask *mask = actseq->mask;
|
|
for (Sequence *seq : strips) {
|
|
if (SEQ_CHANNEL_CHECK(seq, channel) && seq->type == SEQ_TYPE_MASK && seq->mask == mask) {
|
|
seq->flag |= SELECT;
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
static bool select_grouped_effect(blender::Span<Sequence *> strips,
|
|
ListBase * /*seqbase*/,
|
|
Sequence *actseq,
|
|
const int channel)
|
|
{
|
|
bool changed = false;
|
|
bool effects[SEQ_TYPE_MAX + 1];
|
|
|
|
for (int i = 0; i <= SEQ_TYPE_MAX; i++) {
|
|
effects[i] = false;
|
|
}
|
|
|
|
for (Sequence *seq : strips) {
|
|
if (SEQ_CHANNEL_CHECK(seq, channel) && (seq->type & SEQ_TYPE_EFFECT) &&
|
|
SEQ_relation_is_effect_of_strip(seq, actseq))
|
|
{
|
|
effects[seq->type] = true;
|
|
}
|
|
}
|
|
|
|
for (Sequence *seq : strips) {
|
|
if (SEQ_CHANNEL_CHECK(seq, channel) && effects[seq->type]) {
|
|
if (seq->seq1) {
|
|
seq->seq1->flag |= SELECT;
|
|
}
|
|
if (seq->seq2) {
|
|
seq->seq2->flag |= SELECT;
|
|
}
|
|
if (seq->seq3) {
|
|
seq->seq3->flag |= SELECT;
|
|
}
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
static bool select_grouped_time_overlap(const Scene *scene,
|
|
blender::Span<Sequence *> strips,
|
|
ListBase * /*seqbase*/,
|
|
Sequence *actseq)
|
|
{
|
|
bool changed = false;
|
|
|
|
for (Sequence *seq : strips) {
|
|
if (SEQ_time_left_handle_frame_get(scene, seq) <
|
|
SEQ_time_right_handle_frame_get(scene, actseq) &&
|
|
SEQ_time_right_handle_frame_get(scene, seq) >
|
|
SEQ_time_left_handle_frame_get(scene, actseq))
|
|
{
|
|
seq->flag |= SELECT;
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
/* Query strips that are in lower channel and intersect in time with seq_reference. */
|
|
static void query_lower_channel_strips(const Scene *scene,
|
|
Sequence *seq_reference,
|
|
ListBase *seqbase,
|
|
blender::VectorSet<Sequence *> &strips)
|
|
{
|
|
LISTBASE_FOREACH (Sequence *, seq_test, seqbase) {
|
|
if (seq_test->machine > seq_reference->machine) {
|
|
continue; /* Not lower channel. */
|
|
}
|
|
if (SEQ_time_right_handle_frame_get(scene, seq_test) <=
|
|
SEQ_time_left_handle_frame_get(scene, seq_reference) ||
|
|
SEQ_time_left_handle_frame_get(scene, seq_test) >=
|
|
SEQ_time_right_handle_frame_get(scene, seq_reference))
|
|
{
|
|
continue; /* Not intersecting in time. */
|
|
}
|
|
strips.add(seq_test);
|
|
}
|
|
}
|
|
|
|
/* Select all strips within time range and with lower channel of initial selection. Then select
|
|
* effect chains of these strips. */
|
|
static bool select_grouped_effect_link(const Scene *scene,
|
|
blender::VectorSet<Sequence *> strips,
|
|
ListBase *seqbase,
|
|
Sequence * /*actseq*/,
|
|
const int /*channel*/)
|
|
{
|
|
/* Get collection of strips. */
|
|
strips.remove_if([&](Sequence *seq) { return (seq->flag & SELECT) == 0; });
|
|
const int selected_strip_count = strips.size();
|
|
/* XXX: this uses scene as arg, so it does not work with iterator :( I had thought about this,
|
|
* but expand function is just so useful... I can just add scene and inject it I guess. */
|
|
SEQ_iterator_set_expand(scene, seqbase, strips, query_lower_channel_strips);
|
|
SEQ_iterator_set_expand(scene, seqbase, strips, SEQ_query_strip_effect_chain);
|
|
|
|
/* Check if other strips will be affected. */
|
|
const bool changed = strips.size() > selected_strip_count;
|
|
|
|
/* Actual logic. */
|
|
for (Sequence *seq : strips) {
|
|
seq->flag |= SELECT;
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
#undef SEQ_IS_SOUND
|
|
#undef SEQ_IS_EFFECT
|
|
#undef SEQ_USE_DATA
|
|
|
|
static int sequencer_select_grouped_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
ListBase *seqbase = SEQ_active_seqbase_get(SEQ_editing_get(scene));
|
|
Sequence *actseq = SEQ_select_active_get(scene);
|
|
|
|
const bool is_preview = sequencer_view_has_preview_poll(C);
|
|
if (is_preview && !sequencer_view_preview_only_poll(C)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
blender::VectorSet strips = all_strips_from_context(C);
|
|
|
|
if (actseq == nullptr || (is_preview && !strips.contains(actseq))) {
|
|
BKE_report(op->reports, RPT_ERROR, "No active sequence!");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
const int type = RNA_enum_get(op->ptr, "type");
|
|
const int channel = RNA_boolean_get(op->ptr, "use_active_channel") ? actseq->machine : 0;
|
|
const bool extend = RNA_boolean_get(op->ptr, "extend");
|
|
|
|
bool changed = false;
|
|
|
|
if (!extend) {
|
|
LISTBASE_FOREACH (Sequence *, seq, seqbase) {
|
|
seq->flag &= ~SELECT;
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
switch (type) {
|
|
case SEQ_SELECT_GROUP_TYPE:
|
|
changed |= select_grouped_type(strips, seqbase, actseq, channel);
|
|
break;
|
|
case SEQ_SELECT_GROUP_TYPE_BASIC:
|
|
changed |= select_grouped_type_basic(strips, seqbase, actseq, channel);
|
|
break;
|
|
case SEQ_SELECT_GROUP_TYPE_EFFECT:
|
|
changed |= select_grouped_type_effect(strips, seqbase, actseq, channel);
|
|
break;
|
|
case SEQ_SELECT_GROUP_DATA:
|
|
changed |= select_grouped_data(strips, seqbase, actseq, channel);
|
|
break;
|
|
case SEQ_SELECT_GROUP_EFFECT:
|
|
changed |= select_grouped_effect(strips, seqbase, actseq, channel);
|
|
break;
|
|
case SEQ_SELECT_GROUP_EFFECT_LINK:
|
|
changed |= select_grouped_effect_link(scene, strips, seqbase, actseq, channel);
|
|
break;
|
|
case SEQ_SELECT_GROUP_OVERLAP:
|
|
changed |= select_grouped_time_overlap(scene, strips, seqbase, actseq);
|
|
break;
|
|
default:
|
|
BLI_assert(0);
|
|
break;
|
|
}
|
|
|
|
if (changed) {
|
|
ED_outliner_select_sync_from_sequence_tag(C);
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
void SEQUENCER_OT_select_grouped(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Select Grouped";
|
|
ot->idname = "SEQUENCER_OT_select_grouped";
|
|
ot->description = "Select all strips grouped by various properties";
|
|
|
|
/* Api callbacks. */
|
|
ot->invoke = WM_menu_invoke;
|
|
ot->exec = sequencer_select_grouped_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
/* Properties. */
|
|
ot->prop = RNA_def_enum(ot->srna, "type", sequencer_prop_select_grouped_types, 0, "Type", "");
|
|
RNA_def_boolean(ot->srna,
|
|
"extend",
|
|
false,
|
|
"Extend",
|
|
"Extend selection instead of deselecting everything first");
|
|
RNA_def_boolean(ot->srna,
|
|
"use_active_channel",
|
|
false,
|
|
"Same Channel",
|
|
"Only consider strips on the same channel as the active one");
|
|
}
|
|
|
|
/** \} */
|