Blender V5.0
sequencer_select.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include <cmath>
10#include <cstdlib>
11#include <cstring>
12
13#include "BLI_lasso_2d.hh"
14#include "BLI_rect.h"
15#include "MEM_guardedalloc.h"
16
17#include "BLI_ghash.h"
18#include "BLI_listbase.h"
19#include "BLI_math_geom.h"
20#include "BLI_math_vector.h"
22#include "BLI_set.hh"
23#include "BLI_string.h"
24#include "BLI_utildefines.h"
25
26#include "DNA_scene_types.h"
27#include "DNA_space_types.h"
28
29#include "BKE_context.hh"
30#include "BKE_report.hh"
31
32#include "BLT_translation.hh"
33
34#include "WM_api.hh"
35#include "WM_types.hh"
36
37#include "RNA_define.hh"
38
39#include "SEQ_channels.hh"
40#include "SEQ_connect.hh"
41#include "SEQ_effects.hh"
42#include "SEQ_iterator.hh"
43#include "SEQ_relations.hh"
44#include "SEQ_retiming.hh"
45#include "SEQ_select.hh"
46#include "SEQ_sequencer.hh"
47#include "SEQ_time.hh"
48#include "SEQ_transform.hh"
49
50/* For menu, popup, icons, etc. */
51
52#include "ED_outliner.hh"
53#include "ED_screen.hh"
54#include "ED_select_utils.hh"
55#include "ED_sequencer.hh"
56
57#include "UI_view2d.hh"
58
59/* Own include. */
60#include "sequencer_intern.hh"
61
62namespace blender::ed::vse {
63
64/* -------------------------------------------------------------------- */
67
69 public:
72
73 MouseCoords(const View2D *v2d, int x, int y)
74 {
75 region[0] = x;
76 region[1] = y;
77 UI_view2d_region_to_view(v2d, x, y, &view[0], &view[1]);
78 }
79};
80
81bool deselect_all_strips(const Scene *scene)
82{
83 Editing *ed = seq::editing_get(scene);
84 bool changed = false;
85
86 if (ed == nullptr) {
87 return changed;
88 }
89
91 for (Strip *strip : strips) {
92 if (strip->flag & STRIP_ALLSEL) {
93 strip->flag &= ~STRIP_ALLSEL;
94 changed = true;
95 }
96 }
97 return changed;
98}
99
100Strip *strip_under_mouse_get(const Scene *scene, const View2D *v2d, const int mval[2])
101{
102 float mouse_co[2];
103 UI_view2d_region_to_view(v2d, mval[0], mval[1], &mouse_co[0], &mouse_co[1]);
104
106 int mouse_channel = int(mouse_co[1]);
107 for (Strip *strip : visible) {
108 if (strip->channel != mouse_channel) {
109 continue;
110 }
111 rctf body;
112 strip_rectf(scene, strip, &body);
113 if (BLI_rctf_isect_pt_v(&body, mouse_co)) {
114 return strip;
115 }
116 }
117
118 return nullptr;
119}
120
122{
124 Editing *ed = seq::editing_get(scene);
127
128 const bool is_preview = sequencer_view_has_preview_poll(C);
129 if (is_preview) {
130 return seq::query_rendered_strips(scene, channels, seqbase, scene->r.cfra, 0);
131 }
132
133 return seq::query_all_strips(seqbase);
134}
135
137{
138 const Scene *scene = CTX_data_sequencer_scene(C);
139 Editing *ed = seq::editing_get(scene);
142
143 const bool is_preview = sequencer_view_has_preview_poll(C);
144
145 if (is_preview) {
147 scene, channels, seqbase, scene->r.cfra, 0);
148 strips.remove_if([&](Strip *strip) { return (strip->flag & SELECT) == 0; });
149 return strips;
150 }
151
152 return seq::query_selected_strips(seqbase);
153}
154
155static void select_surrounding_handles(Scene *scene, Strip *test) /* XXX BRING BACK */
156{
157 Strip *neighbor;
158
159 neighbor = find_neighboring_strip(scene, test, seq::SIDE_LEFT, -1);
160 if (neighbor) {
161 /* Only select neighbor handle if matching handle from test strip is also selected,
162 * or if neighbor was not selected at all up till now.
163 * Otherwise, we get odd mismatch when shift-alt-rmb selecting neighbor strips... */
164 if (!(neighbor->flag & SELECT) || (test->flag & SEQ_LEFTSEL)) {
165 neighbor->flag |= SEQ_RIGHTSEL;
166 }
167 neighbor->flag |= SELECT;
168 recurs_sel_strip(neighbor);
169 }
170 neighbor = find_neighboring_strip(scene, test, seq::SIDE_RIGHT, -1);
171 if (neighbor) {
172 if (!(neighbor->flag & SELECT) || (test->flag & SEQ_RIGHTSEL)) { /* See comment above. */
173 neighbor->flag |= SEQ_LEFTSEL;
174 }
175 neighbor->flag |= SELECT;
176 recurs_sel_strip(neighbor);
177 }
178}
179
180/* Used for mouse selection in #SEQUENCER_OT_select. */
182 const Scene *scene, ListBase *seqbase, int sel_side, int channel, int frame)
183{
184
185 LISTBASE_FOREACH (Strip *, strip, seqbase) {
186 if (channel == strip->channel) {
187 switch (sel_side) {
188 case seq::SIDE_LEFT:
189 if (frame > seq::time_left_handle_frame_get(scene, strip)) {
190 strip->flag &= ~(SEQ_RIGHTSEL | SEQ_LEFTSEL);
191 strip->flag |= SELECT;
192 }
193 break;
194 case seq::SIDE_RIGHT:
195 if (frame < seq::time_left_handle_frame_get(scene, strip)) {
196 strip->flag &= ~(SEQ_RIGHTSEL | SEQ_LEFTSEL);
197 strip->flag |= SELECT;
198 }
199 break;
200 case seq::SIDE_BOTH:
201 strip->flag &= ~(SEQ_RIGHTSEL | SEQ_LEFTSEL);
202 strip->flag |= SELECT;
203 break;
204 }
205 }
206 }
207}
208
209/* Used for mouse selection in #SEQUENCER_OT_select_side. */
210static void select_active_side_range(const Scene *scene,
211 ListBase *seqbase,
212 const int sel_side,
213 const int frame_ranges[seq::MAX_CHANNELS],
214 const int frame_ignore)
215{
216 LISTBASE_FOREACH (Strip *, strip, seqbase) {
217 if (strip->channel < seq::MAX_CHANNELS) {
218 const int frame = frame_ranges[strip->channel];
219 if (frame == frame_ignore) {
220 continue;
221 }
222 switch (sel_side) {
223 case seq::SIDE_LEFT:
224 if (frame > seq::time_left_handle_frame_get(scene, strip)) {
225 strip->flag &= ~(SEQ_RIGHTSEL | SEQ_LEFTSEL);
226 strip->flag |= SELECT;
227 }
228 break;
229 case seq::SIDE_RIGHT:
230 if (frame < seq::time_left_handle_frame_get(scene, strip)) {
231 strip->flag &= ~(SEQ_RIGHTSEL | SEQ_LEFTSEL);
232 strip->flag |= SELECT;
233 }
234 break;
235 case seq::SIDE_BOTH:
236 strip->flag &= ~(SEQ_RIGHTSEL | SEQ_LEFTSEL);
237 strip->flag |= SELECT;
238 break;
239 }
240 }
241 }
242}
243
244/* Used alongside `select_linked_time` helper function in #SEQUENCER_OT_select. */
245static void select_linked_time_strip(const Scene *scene,
246 const Strip *strip_source,
247 const eStripHandle handle_clicked)
248{
249 ListBase *seqbase = seq::active_seqbase_get(scene->ed);
250 int source_left = seq::time_left_handle_frame_get(scene, strip_source);
251 int source_right = seq::time_right_handle_frame_get(scene, strip_source);
252
253 LISTBASE_FOREACH (Strip *, strip_dest, seqbase) {
254 if (strip_source->channel != strip_dest->channel) {
255 const bool left_match = (seq::time_left_handle_frame_get(scene, strip_dest) == source_left);
256 const bool right_match = (seq::time_right_handle_frame_get(scene, strip_dest) ==
257 source_right);
258
259 if (left_match && right_match) {
260 /* Direct match, copy all selection settings. */
261 strip_dest->flag &= ~STRIP_ALLSEL;
262 strip_dest->flag |= strip_source->flag & (STRIP_ALLSEL);
263 recurs_sel_strip(strip_dest);
264 }
265 else if (left_match && handle_clicked == STRIP_HANDLE_LEFT) {
266 strip_dest->flag &= ~(SELECT | SEQ_LEFTSEL);
267 strip_dest->flag |= strip_source->flag & (SELECT | SEQ_LEFTSEL);
268 recurs_sel_strip(strip_dest);
269 }
270 else if (right_match && handle_clicked == STRIP_HANDLE_RIGHT) {
271 strip_dest->flag &= ~(SELECT | SEQ_RIGHTSEL);
272 strip_dest->flag |= strip_source->flag & (SELECT | SEQ_RIGHTSEL);
273 recurs_sel_strip(strip_dest);
274 }
275 }
276 }
277}
278
279#if 0 /* BRING BACK */
280void select_surround_from_last(Scene *scene)
281{
282 Strip *strip = get_last_seq(scene);
283
284 if (strip == nullptr) {
285 return;
286 }
287
288 select_surrounding_handles(scene, strip);
289}
290#endif
291
292void select_strip_single(Scene *scene, Strip *strip, bool deselect_all)
293{
294 if (deselect_all) {
295 deselect_all_strips(scene);
296 }
297
298 seq::select_active_set(scene, strip);
299
300 strip->flag |= SELECT;
301 recurs_sel_strip(strip);
302}
303
304void strip_rectf(const Scene *scene, const Strip *strip, rctf *r_rect)
305{
306 r_rect->xmin = seq::time_left_handle_frame_get(scene, strip);
307 r_rect->xmax = seq::time_right_handle_frame_get(scene, strip);
308 r_rect->ymin = strip->channel + STRIP_OFSBOTTOM;
309 r_rect->ymax = strip->channel + STRIP_OFSTOP;
310}
311
312Strip *find_neighboring_strip(const Scene *scene, const Strip *test, const int lr, int sel)
313{
314 /* sel: 0==unselected, 1==selected, -1==don't care. */
315 Editing *ed = seq::editing_get(scene);
316
317 if (ed == nullptr) {
318 return nullptr;
319 }
320
321 if (sel > 0) {
322 sel = SELECT;
323 }
324 LISTBASE_FOREACH (Strip *, strip, ed->current_strips()) {
325 if ((strip != test) && (test->channel == strip->channel) &&
326 ((sel == -1) || (sel && (strip->flag & SELECT)) ||
327 (sel == 0 && (strip->flag & SELECT) == 0)))
328 {
329 switch (lr) {
330 case seq::SIDE_LEFT:
331 if (seq::time_left_handle_frame_get(scene, test) ==
333 {
334 return strip;
335 }
336 break;
337 case seq::SIDE_RIGHT:
338 if (seq::time_right_handle_frame_get(scene, test) ==
340 {
341 return strip;
342 }
343 break;
344 }
345 }
346 }
347 return nullptr;
348}
349
350#if 0
351static void select_neighbor_from_last(Scene *scene, int lr)
352{
353 Strip *strip = seq::SEQ_select_active_get(scene);
354 Strip *neighbor;
355 bool changed = false;
356 if (strip) {
357 neighbor = find_neighboring_strip(scene, strip, lr, -1);
358 if (neighbor) {
359 switch (lr) {
360 case seq::SIDE_LEFT:
361 neighbor->flag |= SELECT;
362 recurs_sel_strip(neighbor);
363 neighbor->flag |= SEQ_RIGHTSEL;
364 strip->flag |= SEQ_LEFTSEL;
365 break;
366 case seq::SIDE_RIGHT:
367 neighbor->flag |= SELECT;
368 recurs_sel_strip(neighbor);
369 neighbor->flag |= SEQ_LEFTSEL;
370 strip->flag |= SEQ_RIGHTSEL;
371 break;
372 }
373 strip->flag |= SELECT;
374 changed = true;
375 }
376 }
377 if (changed) {
378 /* Pass. */
379 }
380}
381#endif
382
383void recurs_sel_strip(Strip *strip_meta)
384{
385 Strip *strip;
386 strip = static_cast<Strip *>(strip_meta->seqbase.first);
387
388 while (strip) {
389
390 if (strip_meta->flag & (SEQ_LEFTSEL + SEQ_RIGHTSEL)) {
391 strip->flag &= ~STRIP_ALLSEL;
392 }
393 else if (strip_meta->flag & SELECT) {
394 strip->flag |= SELECT;
395 }
396 else {
397 strip->flag &= ~STRIP_ALLSEL;
398 }
399
400 if (strip->seqbase.first) {
401 recurs_sel_strip(strip);
402 }
403
404 strip = static_cast<Strip *>(strip->next);
405 }
406}
407
408bool strip_point_image_isect(const Scene *scene, const Strip *strip, float point_view[2])
409{
411 scene, strip);
412 return isect_point_quad_v2(point_view,
413 strip_image_quad[0],
414 strip_image_quad[1],
415 strip_image_quad[2],
416 strip_image_quad[3]);
417}
418
424
426
427/* -------------------------------------------------------------------- */
430
432{
433 int action = RNA_enum_get(op->ptr, "action");
435
437 return OPERATOR_CANCELLED;
438 }
439
441 {
443 }
444
446
447 if (action == SEL_TOGGLE) {
448 action = SEL_SELECT;
449 for (Strip *strip : strips) {
450 if (strip->flag & STRIP_ALLSEL) {
451 action = SEL_DESELECT;
452 break;
453 }
454 }
455 }
456 if (ELEM(action, SEL_INVERT, SEL_SELECT)) {
457 if (action == SEL_INVERT) {
458 for (Strip *strip : strips) {
459 if (strip->flag & STRIP_ALLSEL) {
460 strips.remove(strip);
461 }
462 }
463 }
464 deselect_all_strips(scene);
465 }
466 for (Strip *strip : strips) {
467 switch (action) {
468 case SEL_SELECT:
469 strip->flag |= SELECT;
470 break;
471 case SEL_DESELECT:
472 strip->flag &= ~STRIP_ALLSEL;
473 break;
474 case SEL_INVERT:
475 strip->flag |= SELECT;
476 break;
477 }
478 }
481
482 return OPERATOR_FINISHED;
483}
484
486{
487 /* Identifiers. */
488 ot->name = "(De)select All";
489 ot->idname = "SEQUENCER_OT_select_all";
490 ot->description = "Select or deselect all strips";
491
492 /* API callbacks. */
494 ot->poll = sequencer_edit_poll;
495
496 /* Flags. */
497 ot->flag = OPTYPE_UNDO;
498
500}
501
503
504/* -------------------------------------------------------------------- */
507
509{
511
513 return OPERATOR_CANCELLED;
514 }
515
517
518 for (Strip *strip : strips) {
519 if (strip->flag & SELECT) {
520 strip->flag &= ~STRIP_ALLSEL;
521 }
522 else {
523 strip->flag &= ~(SEQ_LEFTSEL + SEQ_RIGHTSEL);
524 strip->flag |= SELECT;
525 }
526 }
527
530
531 return OPERATOR_FINISHED;
532}
533
535{
536 /* Identifiers. */
537 ot->name = "Select Inverse";
538 ot->idname = "SEQUENCER_OT_select_inverse";
539 ot->description = "Select unselected strips";
540
541 /* API callbacks. */
543 ot->poll = sequencer_edit_poll;
544
545 /* Flags. */
546 ot->flag = OPTYPE_UNDO;
547}
548
550
551/* -------------------------------------------------------------------- */
554
555static void sequencer_select_set_active(Scene *scene, Strip *strip)
556{
557 seq::select_active_set(scene, strip);
558 recurs_sel_strip(strip);
559}
560
562 const View2D *v2d,
563 const int mval[2],
564 Scene *scene)
565{
566 Editing *ed = seq::editing_get(scene);
567
568 const float x = UI_view2d_region_to_view_x(v2d, mval[0]);
570 if (((x < scene->r.cfra) &&
571 (seq::time_right_handle_frame_get(scene, strip_iter) <= scene->r.cfra)) ||
572 ((x >= scene->r.cfra) &&
573 (seq::time_left_handle_frame_get(scene, strip_iter) >= scene->r.cfra)))
574 {
575 /* Select left or right. */
576 strip_iter->flag |= SELECT;
577 recurs_sel_strip(strip_iter);
578 }
579 }
580
581 {
582 SpaceSeq *sseq = CTX_wm_space_seq(C);
583 if (sseq && sseq->flag & SEQ_MARKER_TRANS) {
584
585 LISTBASE_FOREACH (TimeMarker *, tmarker, &scene->markers) {
586 if (((x < scene->r.cfra) && (tmarker->frame <= scene->r.cfra)) ||
587 ((x >= scene->r.cfra) && (tmarker->frame >= scene->r.cfra)))
588 {
589 tmarker->flag |= SELECT;
590 }
591 else {
592 tmarker->flag &= ~SELECT;
593 }
594 }
595 }
596 }
597}
598
600 Strip *strip,
601 const eStripHandle handle_clicked)
602{
604 Editing *ed = seq::editing_get(scene);
605 if (!ELEM(handle_clicked, STRIP_HANDLE_LEFT, STRIP_HANDLE_RIGHT)) {
606 /* First click selects the strip and its adjacent handles (if valid).
607 * Second click selects the strip,
608 * both of its handles and its adjacent handles (if valid). */
609 const bool is_striponly_selected = ((strip->flag & STRIP_ALLSEL) == SELECT);
610 strip->flag &= ~STRIP_ALLSEL;
611 strip->flag |= is_striponly_selected ? STRIP_ALLSEL : SELECT;
612 select_surrounding_handles(scene, strip);
613 }
614 else {
615 /* Always select the strip under the cursor. */
616 strip->flag |= SELECT;
617
618 /* First click selects adjacent handles on that side.
619 * Second click selects all strips in that direction.
620 * If there are no adjacent strips, it just selects all in that direction.
621 */
622 const int sel_side = (handle_clicked == STRIP_HANDLE_LEFT) ? seq::SIDE_LEFT : seq::SIDE_RIGHT;
623
624 Strip *neighbor = find_neighboring_strip(scene, strip, sel_side, -1);
625 if (neighbor) {
626 switch (sel_side) {
627 case seq::SIDE_LEFT:
628 if ((strip->flag & SEQ_LEFTSEL) && (neighbor->flag & SEQ_RIGHTSEL)) {
629 strip->flag |= SELECT;
630 select_active_side(scene,
631 ed->current_strips(),
633 strip->channel,
634 seq::time_left_handle_frame_get(scene, strip));
635 }
636 else {
637 strip->flag |= SELECT;
638 neighbor->flag |= SELECT;
639 recurs_sel_strip(neighbor);
640 neighbor->flag |= SEQ_RIGHTSEL;
641 strip->flag |= SEQ_LEFTSEL;
642 }
643 break;
644 case seq::SIDE_RIGHT:
645 if ((strip->flag & SEQ_RIGHTSEL) && (neighbor->flag & SEQ_LEFTSEL)) {
646 strip->flag |= SELECT;
647 select_active_side(scene,
648 ed->current_strips(),
650 strip->channel,
651 seq::time_left_handle_frame_get(scene, strip));
652 }
653 else {
654 strip->flag |= SELECT;
655 neighbor->flag |= SELECT;
656 recurs_sel_strip(neighbor);
657 neighbor->flag |= SEQ_LEFTSEL;
658 strip->flag |= SEQ_RIGHTSEL;
659 }
660 break;
661 }
662 }
663 else {
664
665 select_active_side(scene,
666 ed->current_strips(),
667 sel_side,
668 strip->channel,
669 seq::time_left_handle_frame_get(scene, strip));
670 }
671 }
672}
673
681
682static int strip_sort_for_depth_select(const void *a, const void *b)
683{
684 const SeqSelect_Link *slink_a = static_cast<const SeqSelect_Link *>(a);
685 const SeqSelect_Link *slink_b = static_cast<const SeqSelect_Link *>(b);
686
687 /* Exactly overlapping strips, sort by channel (so the top-most is first). */
688 if (slink_a->strip->channel < slink_b->strip->channel) {
689 return 1;
690 }
691 if (slink_a->strip->channel > slink_b->strip->channel) {
692 return -1;
693 }
694 return 0;
695}
696
697static int strip_sort_for_center_select(const void *a, const void *b)
698{
699 const SeqSelect_Link *slink_a = static_cast<const SeqSelect_Link *>(a);
700 const SeqSelect_Link *slink_b = static_cast<const SeqSelect_Link *>(b);
701 if (slink_a->center_dist_sq > slink_b->center_dist_sq) {
702 return 1;
703 }
704 if (slink_a->center_dist_sq < slink_b->center_dist_sq) {
705 return -1;
706 }
707
708 /* Exactly overlapping strips, use depth. */
710}
711
718 const bContext *C, const int mval[2], const bool toggle, const bool extend, const bool center)
719{
721 Editing *ed = seq::editing_get(scene);
724 SpaceSeq *sseq = CTX_wm_space_seq(C);
726
727 float mouseco_view[2];
728 UI_view2d_region_to_view(v2d, mval[0], mval[1], &mouseco_view[0], &mouseco_view[1]);
729
730 /* Always update the coordinates (check extended after). */
731 const bool use_cycle = (!WM_cursor_test_motion_and_update(mval) || extend || toggle);
732
733 /* Allow strips this far from the closest center to be included.
734 * This allows cycling over center points which are near enough
735 * to overlapping from the users perspective. */
736 const float center_dist_sq_max = square_f(75.0f * U.pixelsize);
737 const float center_scale_px[2] = {
740 };
741
743 scene, channels, seqbase, scene->r.cfra, sseq->chanshown);
744
745 SeqSelect_Link *slink_active = nullptr;
746 Strip *strip_active = seq::select_active_get(scene);
747 ListBase strips_ordered = {nullptr};
748 for (Strip *strip : strips) {
749 bool isect = false;
750 float center_dist_sq_test = 0.0f;
751 if (center) {
752 /* Detect overlapping center points (scaled by the zoom level). */
754 sub_v2_v2(co, mouseco_view);
755 mul_v2_v2(co, center_scale_px);
756 center_dist_sq_test = len_squared_v2(co);
757 isect = center_dist_sq_test <= center_dist_sq_max;
758 if (isect) {
759 /* Use an active strip penalty for "center" selection when cycle is enabled. */
760 if (use_cycle && (strip == strip_active) && (strip_active->flag & SELECT)) {
761 center_dist_sq_test = square_f(sqrtf(center_dist_sq_test) + (3.0f * U.pixelsize));
762 }
763 }
764 }
765 else {
766 isect = strip_point_image_isect(scene, strip, mouseco_view);
767 }
768
769 if (isect) {
771 slink->strip = strip;
772 slink->center_dist_sq = center_dist_sq_test;
773 BLI_addtail(&strips_ordered, slink);
774
775 if (strip == strip_active) {
776 slink_active = slink;
777 }
778 }
779 }
780
781 BLI_listbase_sort(&strips_ordered,
783
784 SeqSelect_Link *slink_select = static_cast<SeqSelect_Link *>(strips_ordered.first);
785 Strip *strip_select = nullptr;
786 if (slink_select != nullptr) {
787 /* Only use special behavior for the active strip when it's selected. */
788 if ((center == false) && slink_active && (strip_active->flag & SELECT)) {
789 if (use_cycle) {
790 if (slink_active->next) {
791 slink_select = slink_active->next;
792 }
793 }
794 else {
795 /* Match object selection behavior: keep the current active item unless cycle is enabled.
796 * Clicking again in the same location will cycle away from the active object. */
797 slink_select = slink_active;
798 }
799 }
800 strip_select = slink_select->strip;
801 }
802
803 BLI_freelistN(&strips_ordered);
804
805 return strip_select;
806}
807
808bool handle_is_selected(const Strip *strip, const eStripHandle handle)
809{
810 return ((strip->flag & SEQ_LEFTSEL) && (handle == STRIP_HANDLE_LEFT)) ||
811 ((strip->flag & SEQ_RIGHTSEL) && (handle == STRIP_HANDLE_RIGHT));
812}
813
819static bool element_already_selected(const StripSelection &selection)
820{
821 if (selection.strip1 == nullptr) {
822 return false;
823 }
824
825 const bool strip1_already_selected = ((selection.strip1->flag & SELECT) != 0);
826 if (selection.strip2 == nullptr) {
827 if (selection.handle == STRIP_HANDLE_NONE) {
828 return strip1_already_selected && !(selection.strip1->flag & (SEQ_LEFTSEL | SEQ_RIGHTSEL));
829 }
830 return strip1_already_selected && handle_is_selected(selection.strip1, selection.handle);
831 }
832
833 /* If we are here, the strip selection is dual handle. */
834 const bool strip2_already_selected = ((selection.strip2->flag & SELECT) != 0);
835 const int strip1_handle = selection.strip1->flag & (SEQ_RIGHTSEL | SEQ_LEFTSEL);
836 const int strip2_handle = selection.strip2->flag & (SEQ_RIGHTSEL | SEQ_LEFTSEL);
837 /* Handles must be selected in XOR fashion, with `strip1` matching `handle_clicked`. */
838 const bool both_handles_selected = strip1_handle == selection.handle && strip2_handle != 0 &&
839 strip1_handle != strip2_handle;
840 return strip1_already_selected && strip2_already_selected && both_handles_selected;
841}
842
844{
846 sources.add(selection.strip1);
847 if (selection.strip2) {
848 sources.add(selection.strip2);
849 }
850
851 for (Strip *source : sources) {
853 for (Strip *connection : connections) {
854 /* Copy selection settings exactly for connected strips. */
855 connection->flag &= ~STRIP_ALLSEL;
856 connection->flag |= source->flag & (STRIP_ALLSEL);
857 }
858 }
859}
860
862 const StripSelection &selection,
863 VectorSet<Strip *> copy_to)
864{
865 /* TODO(john): Dual handle propagation is not supported for now due to its complexity,
866 * but once we simplify selection assumptions in 5.0 we can add support for it. */
867 if (selection.strip2) {
868 return;
869 }
870
871 Strip *source = selection.strip1;
872 /* Test for neighboring strips in the `copy_to` list. If any border one another, remove them,
873 * since we don't want to mess with dual handles. */
874 blender::VectorSet<Strip *> test(copy_to);
875 test.add(source);
876 for (Strip *test_strip : test) {
877 /* Don't copy left handle over to a `test_strip` that has a strip directly on its left. */
878 if ((source->flag & SEQ_LEFTSEL) &&
879 find_neighboring_strip(scene, test_strip, seq::SIDE_LEFT, -1))
880 {
881 /* If this was the source strip, do not copy handles at all and prematurely return. */
882 if (test_strip == source) {
883 return;
884 }
885 copy_to.remove(test_strip);
886 }
887
888 /* Don't copy right handle over to a `test_strip` that has a strip directly on its right. */
889 if ((source->flag & SEQ_RIGHTSEL) &&
890 find_neighboring_strip(scene, test_strip, seq::SIDE_RIGHT, -1))
891 {
892 /* If this was the source strip, do not copy handles at all and prematurely return. */
893 if (test_strip == source) {
894 return;
895 }
896 copy_to.remove(test_strip);
897 }
898 }
899
900 for (Strip *strip : copy_to) {
901 /* NOTE that this can be `ALLSEL` since `prev_selection` was deselected earlier. */
902 strip->flag &= ~STRIP_ALLSEL;
903 strip->flag |= source->flag & STRIP_ALLSEL;
904 }
905}
906
908 Strip *strip,
909 const eStripHandle handle_clicked,
910 const bool extend,
911 const bool deselect,
912 const bool toggle)
913{
914 const bool is_active = (ed->act_strip == strip);
915
916 /* Exception for active strip handles. */
917 if ((handle_clicked != STRIP_HANDLE_NONE) && (strip->flag & SELECT) && is_active && toggle) {
918 if (handle_clicked == STRIP_HANDLE_LEFT) {
919 strip->flag ^= SEQ_LEFTSEL;
920 }
921 else if (handle_clicked == STRIP_HANDLE_RIGHT) {
922 strip->flag ^= SEQ_RIGHTSEL;
923 }
924 return;
925 }
926
927 /* Select strip. */
928 /* Match object selection behavior. */
929 int action = -1;
930 if (extend) {
931 action = 1;
932 }
933 else if (deselect) {
934 action = 0;
935 }
936 else {
937 if (!((strip->flag & SELECT) && is_active)) {
938 action = 1;
939 }
940 else if (toggle) {
941 action = 0;
942 }
943 }
944
945 if (action == 1) {
946 strip->flag |= SELECT;
947 if (handle_clicked == STRIP_HANDLE_LEFT) {
948 strip->flag |= SEQ_LEFTSEL;
949 }
950 if (handle_clicked == STRIP_HANDLE_RIGHT) {
951 strip->flag |= SEQ_RIGHTSEL;
952 }
953 }
954 else if (action == 0) {
955 strip->flag &= ~STRIP_ALLSEL;
956 }
957}
958
959static void select_linked_time(const Scene *scene,
960 const StripSelection &selection,
961 const bool extend,
962 const bool deselect,
963 const bool toggle)
964{
965 Editing *ed = seq::editing_get(scene);
966
967 sequencer_select_strip_impl(ed, selection.strip1, selection.handle, extend, deselect, toggle);
968 select_linked_time_strip(scene, selection.strip1, selection.handle);
969
970 if (selection.strip2 != nullptr) {
971 eStripHandle strip2_handle_clicked = (selection.handle == STRIP_HANDLE_LEFT) ?
975 ed, selection.strip2, strip2_handle_clicked, extend, deselect, toggle);
976 select_linked_time_strip(scene, selection.strip2, strip2_handle_clicked);
977 }
978}
979
986static float inner_clickable_handle_size_get(const Scene *scene,
987 const Strip *strip,
988 const View2D *v2d)
989{
990 const float pixelx = 1 / UI_view2d_scale_get_x(v2d);
991 const float strip_len = seq::time_right_handle_frame_get(scene, strip) -
993 return min_ff(15.0f * pixelx * U.pixelsize, strip_len / 4);
994}
995
996bool can_select_handle(const Scene *scene, const Strip *strip, const View2D *v2d)
997{
998 if (seq::effect_get_num_inputs(strip->type) > 0) {
999 return false;
1000 }
1001
1002 Editing *ed = seq::editing_get(scene);
1004 if (seq::transform_is_locked(channels, strip)) {
1005 return false;
1006 }
1007
1008 /* This ensures clickable handles are deactivated when the strip gets too small
1009 * (25 pixels). Since the full handle size for a small strip is 1/3 of the strip size (see
1010 * `inner_clickable_handle_size_get`), this means handles cannot be smaller than 25/3 = 8px. */
1011 int min_len = 25 * U.pixelsize;
1012
1013 const float pixelx = 1 / UI_view2d_scale_get_x(v2d);
1014 const int strip_len = seq::time_right_handle_frame_get(scene, strip) -
1015 seq::time_left_handle_frame_get(scene, strip);
1016 if (strip_len / pixelx < min_len) {
1017 return false;
1018 }
1019
1020 if (UI_view2d_scale_get_y(v2d) < 16 * U.pixelsize) {
1021 return false;
1022 }
1023
1024 return true;
1025}
1026
1027static void strip_clickable_areas_get(const Scene *scene,
1028 const Strip *strip,
1029 const View2D *v2d,
1030 rctf *r_body,
1031 rctf *r_left_handle,
1032 rctf *r_right_handle)
1033{
1034 strip_rectf(scene, strip, r_body);
1035 *r_left_handle = *r_body;
1036 *r_right_handle = *r_body;
1037
1038 const float handsize = inner_clickable_handle_size_get(scene, strip, v2d);
1039 r_left_handle->xmax = r_body->xmin + handsize;
1040 r_right_handle->xmin = r_body->xmax - handsize;
1041 BLI_rctf_pad(r_left_handle, handsize / 3, 0.0f);
1042 BLI_rctf_pad(r_right_handle, handsize / 3, 0.0f);
1043 BLI_rctf_pad(r_body, -handsize, 0.0f);
1044}
1045
1046static rctf strip_clickable_area_get(const Scene *scene, const View2D *v2d, const Strip *strip)
1047{
1048 rctf body, left, right;
1049 strip_clickable_areas_get(scene, strip, v2d, &body, &left, &right);
1050 BLI_rctf_union(&body, &left);
1051 BLI_rctf_union(&body, &right);
1052 return body;
1053}
1054
1055static float strip_to_frame_distance(const Scene *scene,
1056 const View2D *v2d,
1057 const Strip *strip,
1058 float timeline_frame)
1059{
1060 rctf body, left, right;
1061 strip_clickable_areas_get(scene, strip, v2d, &body, &left, &right);
1062 return BLI_rctf_length_x(&body, timeline_frame);
1063}
1064
1071 const View2D *v2d,
1072 float mouse_co[2])
1073{
1074 Editing *ed = seq::editing_get(scene);
1075
1076 if (ed == nullptr) {
1077 return {};
1078 }
1079
1081 LISTBASE_FOREACH (Strip *, strip, ed->current_strips()) {
1082 if (strip->channel != int(mouse_co[1])) {
1083 continue;
1084 }
1085 if (seq::time_left_handle_frame_get(scene, strip) > v2d->cur.xmax) {
1086 continue;
1087 }
1088 if (seq::time_right_handle_frame_get(scene, strip) < v2d->cur.xmin) {
1089 continue;
1090 }
1091 const rctf body = strip_clickable_area_get(scene, v2d, strip);
1092 if (!BLI_rctf_isect_pt_v(&body, mouse_co)) {
1093 continue;
1094 }
1095 strips.append(strip);
1096 }
1097
1098 std::sort(strips.begin(), strips.end(), [&](const Strip *strip1, const Strip *strip2) {
1099 return strip_to_frame_distance(scene, v2d, strip1, mouse_co[0]) <
1100 strip_to_frame_distance(scene, v2d, strip2, mouse_co[0]);
1101 });
1102
1103 return strips;
1104}
1105
1106static bool strips_are_adjacent(const Scene *scene, const Strip *strip1, const Strip *strip2)
1107{
1108 const int s1_left = seq::time_left_handle_frame_get(scene, strip1);
1109 const int s1_right = seq::time_right_handle_frame_get(scene, strip1);
1110 const int s2_left = seq::time_left_handle_frame_get(scene, strip2);
1111 const int s2_right = seq::time_right_handle_frame_get(scene, strip2);
1112
1113 return s1_right == s2_left || s1_left == s2_right;
1114}
1115
1117 const Strip *strip,
1118 const View2D *v2d,
1119 float mouse_co[2])
1120{
1121 if (!can_select_handle(scene, strip, v2d)) {
1122 return STRIP_HANDLE_NONE;
1123 }
1124
1125 rctf body, left, right;
1126 strip_clickable_areas_get(scene, strip, v2d, &body, &left, &right);
1127 if (BLI_rctf_isect_pt_v(&left, mouse_co)) {
1128 return STRIP_HANDLE_LEFT;
1129 }
1130 if (BLI_rctf_isect_pt_v(&right, mouse_co)) {
1131 return STRIP_HANDLE_RIGHT;
1132 }
1133
1134 return STRIP_HANDLE_NONE;
1135}
1136
1139 const View2D *v2d,
1140 float mouse_co[2])
1141{
1142 const eStripHandle strip1_handle = strip_handle_under_cursor_get(
1143 scene, strips[0], v2d, mouse_co);
1144
1145 if (strip1_handle == STRIP_HANDLE_NONE) {
1146 return false;
1147 }
1148 if (!strips_are_adjacent(scene, strips[0], strips[1])) {
1149 return false;
1150 }
1151 const eStripHandle strip2_handle = strip_handle_under_cursor_get(
1152 scene, strips[1], v2d, mouse_co);
1153 if (strip1_handle == STRIP_HANDLE_RIGHT && strip2_handle != STRIP_HANDLE_LEFT) {
1154 return false;
1155 }
1156 if (strip1_handle == STRIP_HANDLE_LEFT && strip2_handle != STRIP_HANDLE_RIGHT) {
1157 return false;
1158 }
1159
1160 return true;
1161}
1162
1163StripSelection pick_strip_and_handle(const Scene *scene, const View2D *v2d, float mouse_co[2])
1164{
1165 StripSelection selection;
1166 /* Do not pick strips when clicking inside time scrub region. */
1167 float time_scrub_y = v2d->cur.ymax - UI_TIME_SCRUB_MARGIN_Y / UI_view2d_scale_get_y(v2d);
1168 if (mouse_co[1] > time_scrub_y) {
1169 return selection;
1170 }
1171
1172 blender::Vector<Strip *> strips = padded_strips_under_mouse_get(scene, v2d, mouse_co);
1173
1174 if (strips.size() == 0) {
1175 return selection;
1176 }
1177
1178 selection.strip1 = strips[0];
1179 selection.handle = strip_handle_under_cursor_get(scene, selection.strip1, v2d, mouse_co);
1180
1181 if (strips.size() == 2 &&
1182 is_mouse_over_both_handles_of_adjacent_strips(scene, strips, v2d, mouse_co))
1183 {
1184 selection.strip2 = strips[1];
1185 }
1186
1187 return selection;
1188}
1189
1191{
1192 const View2D *v2d = UI_view2d_fromcontext(C);
1194 Editing *ed = seq::editing_get(scene);
1195 ARegion *region = CTX_wm_region(C);
1196
1197 if (ed == nullptr) {
1198 return OPERATOR_CANCELLED;
1199 }
1200
1201 if (region->regiontype == RGN_TYPE_PREVIEW) {
1203 return OPERATOR_CANCELLED;
1204 }
1205 const SpaceSeq *sseq = CTX_wm_space_seq(C);
1206 if (sseq->mainb != SEQ_DRAW_IMG_IMBUF) {
1207 return OPERATOR_CANCELLED;
1208 }
1209 }
1210
1211 const bool was_retiming = sequencer_retiming_mode_is_active(C);
1212
1213 MouseCoords mouse_co(v2d, RNA_int_get(op->ptr, "mouse_x"), RNA_int_get(op->ptr, "mouse_y"));
1214
1215 /* Check to see if the mouse cursor intersects with the retiming box; if so, `strip_key_owner` is
1216 * set. If the cursor intersects with a retiming key, `key` will be set too. */
1217 Strip *strip_key_owner = nullptr;
1218 SeqRetimingKey *key = retiming_mouseover_key_get(C, mouse_co.region, &strip_key_owner);
1219
1220 /* If no key was found, the mouse cursor may still intersect with a "fake key" that has not been
1221 * realized yet. */
1222 if (strip_key_owner != nullptr && key == nullptr &&
1224 seq::retiming_data_is_editable(strip_key_owner))
1225 {
1226 key = try_to_realize_fake_keys(C, strip_key_owner, mouse_co.region);
1227 }
1228
1229 if (key != nullptr) {
1230 if (!was_retiming) {
1231 deselect_all_strips(scene);
1233 }
1234 /* Attempt to realize any other connected strips' fake keys. */
1235 if (seq::is_strip_connected(strip_key_owner)) {
1236 const int key_frame = seq::retiming_key_timeline_frame_get(scene, strip_key_owner, key);
1237 blender::VectorSet<Strip *> connections = seq::connected_strips_get(strip_key_owner);
1238 for (Strip *connection : connections) {
1239 if (key_frame == left_fake_key_frame_get(C, connection) ||
1240 key_frame == right_fake_key_frame_get(C, connection))
1241 {
1242 realize_fake_keys(scene, connection);
1243 }
1244 }
1245 }
1246 return sequencer_retiming_key_select_exec(C, op, key, strip_key_owner);
1247 }
1248
1249 /* We should only reach here if no retiming selection is happening. */
1250 if (was_retiming) {
1253 }
1254
1255 const bool extend = RNA_boolean_get(op->ptr, "extend");
1256 const bool deselect = RNA_boolean_get(op->ptr, "deselect");
1257 const bool deselect_all = RNA_boolean_get(op->ptr, "deselect_all");
1258 const bool toggle = RNA_boolean_get(op->ptr, "toggle");
1259 const bool center = RNA_boolean_get(op->ptr, "center");
1260
1261 StripSelection selection;
1262 if (region->regiontype == RGN_TYPE_PREVIEW) {
1263 selection.strip1 = strip_select_from_preview(C, mouse_co.region, toggle, extend, center);
1264 }
1265 else {
1266 selection = pick_strip_and_handle(scene, v2d, mouse_co.view);
1267 }
1268
1269 /* NOTE: `side_of_frame` and `linked_time` functionality is designed to be shared on one
1270 * keymap, therefore both properties can be true at the same time. */
1271 if (selection.strip1 && RNA_boolean_get(op->ptr, "linked_time")) {
1272 if (!extend && !toggle) {
1273 deselect_all_strips(scene);
1274 }
1275 select_linked_time(scene, selection, extend, deselect, toggle);
1277 sequencer_select_set_active(scene, selection.strip1);
1278 return OPERATOR_FINISHED;
1279 }
1280
1281 /* Select left, right or overlapping the current frame. */
1282 if (RNA_boolean_get(op->ptr, "side_of_frame")) {
1283 if (!extend && !toggle) {
1284 deselect_all_strips(scene);
1285 }
1286 sequencer_select_side_of_frame(C, v2d, mouse_co.region, scene);
1288 return OPERATOR_FINISHED;
1289 }
1290
1291 /* On Alt selection, select the strip and bordering handles. */
1292 if (selection.strip1 && RNA_boolean_get(op->ptr, "linked_handle")) {
1293 if (!extend && !toggle) {
1294 deselect_all_strips(scene);
1295 }
1296 sequencer_select_linked_handle(C, selection.strip1, selection.handle);
1298 sequencer_select_set_active(scene, selection.strip1);
1299 return OPERATOR_FINISHED;
1300 }
1301
1302 const bool wait_to_deselect_others = RNA_boolean_get(op->ptr, "wait_to_deselect_others");
1303 const bool already_selected = element_already_selected(selection);
1304
1305 SpaceSeq *sseq = CTX_wm_space_seq(C);
1306 if (selection.handle != STRIP_HANDLE_NONE && already_selected) {
1308 }
1309 else {
1311 }
1312 const bool ignore_connections = RNA_boolean_get(op->ptr, "ignore_connections");
1313
1314 /* Clicking on already selected element falls on modal operation.
1315 * All strips are deselected on mouse button release unless extend mode is used. */
1316 if (already_selected && wait_to_deselect_others && !toggle && !ignore_connections) {
1318 }
1319
1320 VectorSet<Strip *> copy_to;
1321 /* True if the user selects either handle of a strip that is already selected, meaning that
1322 * handles should be propagated to all currently selected strips. */
1323 bool copy_handles_to_sel = (selection.handle != STRIP_HANDLE_NONE) &&
1324 (selection.strip1->flag & SELECT);
1325
1326 /* TODO(john): Dual handle propagation is not supported for now due to its complexity,
1327 * but once we simplify selection assumptions in 5.0 we can add support for it. */
1328 copy_handles_to_sel &= (selection.strip2 == nullptr);
1329
1330 if (copy_handles_to_sel) {
1332 copy_to.remove(selection.strip1);
1333 copy_to.remove_if([](Strip *strip) { return strip->is_effect(); });
1334 }
1335
1336 bool changed = false;
1337 /* Deselect everything for now. NOTE that this condition runs for almost every click with no
1338 * modifiers. `sequencer_select_strip_impl` expects this and will re-select any strips in
1339 * `selection`. */
1340 if (deselect_all ||
1341 (selection.strip1 && (extend == false && deselect == false && toggle == false)))
1342 {
1343 changed |= deselect_all_strips(scene);
1344 }
1345
1346 /* Nothing to select, but strips might have been deselected, in which case we should update. */
1347 if (!selection.strip1) {
1348 if (changed) {
1350 }
1351 return changed ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
1352 }
1353
1354 /* Do actual selection. */
1355 sequencer_select_strip_impl(ed, selection.strip1, selection.handle, extend, deselect, toggle);
1356 if (selection.strip2 != nullptr) {
1357 /* Invert handle selection for second strip. */
1358 eStripHandle strip2_handle_clicked = (selection.handle == STRIP_HANDLE_LEFT) ?
1362 ed, selection.strip2, strip2_handle_clicked, extend, deselect, toggle);
1363 }
1364
1365 if (!ignore_connections) {
1366 if (copy_handles_to_sel) {
1367 sequencer_copy_handles_to_selected_strips(scene, selection, copy_to);
1368 }
1369
1371 }
1372
1374 sequencer_select_set_active(scene, selection.strip1);
1375 return OPERATOR_FINISHED;
1376}
1377
1379{
1380 const wmOperatorStatus retval = WM_generic_select_invoke(C, op, event);
1381 ARegion *region = CTX_wm_region(C);
1382 if (region && (region->regiontype == RGN_TYPE_PREVIEW)) {
1383 return WM_operator_flag_only_pass_through_on_press(retval, event);
1384 }
1385 return retval;
1386}
1387
1389{
1390 if (RNA_boolean_get(ptr, "ignore_connections")) {
1391 return CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Select (Unconnected)");
1392 }
1393 if (RNA_boolean_get(ptr, "linked_time")) {
1394 return CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Select (Linked Time)");
1395 }
1396 if (RNA_boolean_get(ptr, "linked_handle")) {
1397 return CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Select (Linked Handle)");
1398 }
1399 if (RNA_boolean_get(ptr, "side_of_frame")) {
1400 return CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Select (Side of Frame)");
1401 }
1402
1404}
1405
1407{
1408 PropertyRNA *prop;
1409
1410 /* Identifiers. */
1411 ot->name = "Select";
1412 ot->idname = "SEQUENCER_OT_select";
1413 ot->description = "Select a strip (last selected becomes the \"active strip\")";
1414
1415 /* API callbacks. */
1416 ot->exec = sequencer_select_exec;
1417 ot->invoke = sequencer_select_invoke;
1418 ot->modal = WM_generic_select_modal;
1420 ot->get_name = sequencer_select_get_name;
1421
1422 /* Flags. */
1423 ot->flag = OPTYPE_UNDO;
1424
1425 /* Properties. */
1427
1429
1430 prop = RNA_def_boolean(
1431 ot->srna,
1432 "center",
1433 false,
1434 "Center",
1435 "Use the object center when selecting, in edit mode used to extend object selection");
1437
1438 prop = RNA_def_boolean(ot->srna,
1439 "linked_handle",
1440 false,
1441 "Linked Handle",
1442 "Select handles next to the active strip");
1444
1445 prop = RNA_def_boolean(ot->srna,
1446 "linked_time",
1447 false,
1448 "Linked Time",
1449 "Select other strips or handles at the same time, or all retiming keys "
1450 "after the current in retiming mode");
1452
1453 prop = RNA_def_boolean(
1454 ot->srna,
1455 "side_of_frame",
1456 false,
1457 "Side of Frame",
1458 "Select all strips on same side of the current frame as the mouse cursor");
1460
1461 prop = RNA_def_boolean(ot->srna,
1462 "ignore_connections",
1463 false,
1464 "Ignore Connections",
1465 "Select strips individually whether or not they are connected");
1467}
1468
1470
1471/* -------------------------------------------------------------------- */
1474
1477{
1478 const View2D *v2d = UI_view2d_fromcontext(C);
1480 Editing *ed = seq::editing_get(scene);
1481
1482 if (ed == nullptr) {
1483 return OPERATOR_CANCELLED;
1484 }
1485
1486 MouseCoords mouse_co(v2d, RNA_int_get(op->ptr, "mouse_x"), RNA_int_get(op->ptr, "mouse_y"));
1487
1488 StripSelection selection = pick_strip_and_handle(scene, v2d, mouse_co.view);
1489 if (selection.strip1 == nullptr || selection.handle == STRIP_HANDLE_NONE) {
1491 }
1492
1493 /* Ignore clicks on retiming keys. */
1494 Strip *strip_key_test = nullptr;
1495 SeqRetimingKey *key = retiming_mouseover_key_get(C, mouse_co.region, &strip_key_test);
1496 if (key != nullptr) {
1498 }
1499
1500 SpaceSeq *sseq = CTX_wm_space_seq(C);
1501 if (element_already_selected(selection)) {
1504 }
1506 deselect_all_strips(scene);
1507
1508 /* Do actual selection. */
1509 sequencer_select_strip_impl(ed, selection.strip1, selection.handle, false, false, false);
1510 if (selection.strip2 != nullptr) {
1511 /* Invert handle selection for second strip */
1512 eStripHandle strip2_handle_clicked = (selection.handle == STRIP_HANDLE_LEFT) ?
1515 sequencer_select_strip_impl(ed, selection.strip2, strip2_handle_clicked, false, false, false);
1516 }
1517
1518 const bool ignore_connections = RNA_boolean_get(op->ptr, "ignore_connections");
1519 if (!ignore_connections) {
1521 }
1522
1525 sequencer_select_set_active(scene, selection.strip1);
1527}
1528
1530 wmOperator *op,
1531 const wmEvent *event)
1532{
1533 ARegion *region = CTX_wm_region(C);
1534
1535 int mval[2];
1536 WM_event_drag_start_mval(event, region, mval);
1537
1538 RNA_int_set(op->ptr, "mouse_x", mval[0]);
1539 RNA_int_set(op->ptr, "mouse_y", mval[1]);
1540
1541 return sequencer_select_handle_exec(C, op);
1542}
1543
1545{
1546 PropertyRNA *prop;
1547
1548 /* Identifiers. */
1549 ot->name = "Select Handle";
1550 ot->idname = "SEQUENCER_OT_select_handle";
1551 ot->description = "Select strip handle";
1552
1553 /* API callbacks. */
1557
1558 /* Flags. */
1559 ot->flag = OPTYPE_UNDO;
1560
1561 /* Properties. */
1563
1564 prop = RNA_def_boolean(ot->srna,
1565 "ignore_connections",
1566 false,
1567 "Ignore Connections",
1568 "Select strips individually whether or not they are connected");
1570}
1571
1573
1574/* -------------------------------------------------------------------- */
1577
1578/* Run recursively to select linked. */
1580{
1581 Editing *ed = seq::editing_get(scene);
1582
1583 if (ed == nullptr) {
1584 return false;
1585 }
1586
1587 bool changed = false;
1588
1590 if ((strip->flag & SELECT) == 0) {
1591 continue;
1592 }
1593 /* Only get unselected neighbors. */
1594 Strip *neighbor = find_neighboring_strip(scene, strip, seq::SIDE_LEFT, 0);
1595 if (neighbor) {
1596 neighbor->flag |= SELECT;
1597 recurs_sel_strip(neighbor);
1598 changed = true;
1599 }
1600 neighbor = find_neighboring_strip(scene, strip, seq::SIDE_RIGHT, 0);
1601 if (neighbor) {
1602 neighbor->flag |= SELECT;
1603 recurs_sel_strip(neighbor);
1604 changed = true;
1605 }
1606 }
1607
1608 return changed;
1609}
1610
1611/* Select only one linked strip on each side. */
1612static bool select_more_less_impl(Scene *scene, bool select_more)
1613{
1614 Editing *ed = seq::editing_get(scene);
1615
1616 if (ed == nullptr) {
1617 return false;
1618 }
1619
1620 GSet *neighbors = BLI_gset_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, "Linked strips");
1621 const int neighbor_selection_filter = select_more ? 0 : SELECT;
1622 const int selection_filter = select_more ? SELECT : 0;
1623
1625 if ((strip->flag & SELECT) != selection_filter) {
1626 continue;
1627 }
1628 Strip *neighbor = find_neighboring_strip(
1629 scene, strip, seq::SIDE_LEFT, neighbor_selection_filter);
1630 if (neighbor) {
1631 BLI_gset_add(neighbors, neighbor);
1632 }
1633 neighbor = find_neighboring_strip(scene, strip, seq::SIDE_RIGHT, neighbor_selection_filter);
1634 if (neighbor) {
1635 BLI_gset_add(neighbors, neighbor);
1636 }
1637 }
1638
1639 bool changed = false;
1640 GSetIterator gsi;
1641 BLI_gsetIterator_init(&gsi, neighbors);
1642 while (!BLI_gsetIterator_done(&gsi)) {
1643 Strip *neighbor = static_cast<Strip *>(BLI_gsetIterator_getKey(&gsi));
1644 if (select_more) {
1645 neighbor->flag |= SELECT;
1646 recurs_sel_strip(neighbor);
1647 }
1648 else {
1649 neighbor->flag &= ~SELECT;
1650 }
1651 changed = true;
1653 }
1654
1655 BLI_gset_free(neighbors, nullptr);
1656 return changed;
1657}
1658
1660{
1662
1663 if (!select_more_less_impl(scene, true)) {
1664 return OPERATOR_CANCELLED;
1665 }
1666
1668
1670
1671 return OPERATOR_FINISHED;
1672}
1673
1675{
1676 /* Identifiers. */
1677 ot->name = "Select More";
1678 ot->idname = "SEQUENCER_OT_select_more";
1679 ot->description = "Select more strips adjacent to the current selection";
1680
1681 /* API callbacks. */
1683 ot->poll = sequencer_edit_poll;
1684
1685 /* Flags. */
1686 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1687}
1688
1690
1691/* -------------------------------------------------------------------- */
1694
1696{
1698
1699 if (!select_more_less_impl(scene, false)) {
1700 return OPERATOR_CANCELLED;
1701 }
1702
1704
1706
1707 return OPERATOR_FINISHED;
1708}
1709
1711{
1712 /* Identifiers. */
1713 ot->name = "Select Less";
1714 ot->idname = "SEQUENCER_OT_select_less";
1715 ot->description = "Shrink the current selection of adjacent selected strips";
1716
1717 /* API callbacks. */
1719 ot->poll = sequencer_edit_poll;
1720
1721 /* Flags. */
1722 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1723}
1724
1726
1727/* -------------------------------------------------------------------- */
1730
1732 wmOperator *op,
1733 const wmEvent *event)
1734{
1736 const View2D *v2d = UI_view2d_fromcontext(C);
1737
1738 bool extend = RNA_boolean_get(op->ptr, "extend");
1739
1740 float mouse_co[2];
1741 UI_view2d_region_to_view(v2d, event->mval[0], event->mval[1], &mouse_co[0], &mouse_co[1]);
1742
1743 /* This works like UV, not mesh. */
1744 StripSelection mouse_selection = pick_strip_and_handle(scene, v2d, mouse_co);
1745 if (!mouse_selection.strip1) {
1746 return OPERATOR_FINISHED; /* User error as with mesh?? */
1747 }
1748
1749 if (extend == 0) {
1750 deselect_all_strips(scene);
1751 }
1752
1753 mouse_selection.strip1->flag |= SELECT;
1754 recurs_sel_strip(mouse_selection.strip1);
1755
1756 bool selected = true;
1757 while (selected) {
1758 selected = select_linked_internal(scene);
1759 }
1760
1762
1764
1765 return OPERATOR_FINISHED;
1766}
1767
1769{
1770 /* Identifiers. */
1771 ot->name = "Select Pick Linked";
1772 ot->idname = "SEQUENCER_OT_select_linked_pick";
1773 ot->description = "Select a chain of linked strips nearest to the mouse pointer";
1774
1775 /* API callbacks. */
1778
1779 /* Flags. */
1780 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1781
1782 /* Properties. */
1783 PropertyRNA *prop;
1784 prop = RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend the selection");
1786}
1787
1789
1790/* -------------------------------------------------------------------- */
1793
1795{
1797 bool selected;
1798
1799 selected = true;
1800 while (selected) {
1801 selected = select_linked_internal(scene);
1802 }
1803
1805
1807
1808 return OPERATOR_FINISHED;
1809}
1810
1812{
1813 /* Identifiers. */
1814 ot->name = "Select Linked";
1815 ot->idname = "SEQUENCER_OT_select_linked";
1816 ot->description = "Select all strips adjacent to the current selection";
1817
1818 /* API callbacks. */
1820 ot->poll = sequencer_edit_poll;
1821
1822 /* Flags. */
1823 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1824}
1825
1827
1828/* -------------------------------------------------------------------- */
1831
1832enum {
1839};
1840
1842 {SEQ_SELECT_HANDLES_SIDE_LEFT, "LEFT", 0, "Left", ""},
1843 {SEQ_SELECT_HANDLES_SIDE_RIGHT, "RIGHT", 0, "Right", ""},
1844 {SEQ_SELECT_HANDLES_SIDE_BOTH, "BOTH", 0, "Both", ""},
1845 {SEQ_SELECT_HANDLES_SIDE_LEFT_NEIGHBOR, "LEFT_NEIGHBOR", 0, "Left Neighbor", ""},
1846 {SEQ_SELECT_HANDLES_SIDE_RIGHT_NEIGHBOR, "RIGHT_NEIGHBOR", 0, "Right Neighbor", ""},
1847 {SEQ_SELECT_HANDLES_SIDE_BOTH_NEIGHBORS, "BOTH_NEIGHBORS", 0, "Both Neighbors", ""},
1848 {0, nullptr, 0, nullptr, nullptr},
1849};
1850
1852{
1854 Editing *ed = seq::editing_get(scene);
1855 int sel_side = RNA_enum_get(op->ptr, "side");
1856 LISTBASE_FOREACH (Strip *, strip, ed->current_strips()) {
1857 if (strip->flag & SELECT) {
1858 Strip *l_neighbor = find_neighboring_strip(scene, strip, seq::SIDE_LEFT, -1);
1859 Strip *r_neighbor = find_neighboring_strip(scene, strip, seq::SIDE_RIGHT, -1);
1860
1861 switch (sel_side) {
1863 strip->flag &= ~SEQ_RIGHTSEL;
1864 strip->flag |= SEQ_LEFTSEL;
1865 break;
1867 strip->flag &= ~SEQ_LEFTSEL;
1868 strip->flag |= SEQ_RIGHTSEL;
1869 break;
1871 strip->flag |= SEQ_LEFTSEL | SEQ_RIGHTSEL;
1872 break;
1874 if (l_neighbor) {
1875 if (!(l_neighbor->flag & SELECT)) {
1876 l_neighbor->flag |= SEQ_RIGHTSEL;
1877 }
1878 }
1879 break;
1881 if (r_neighbor) {
1882 if (!(r_neighbor->flag & SELECT)) {
1883 r_neighbor->flag |= SEQ_LEFTSEL;
1884 }
1885 }
1886 break;
1888 if (l_neighbor) {
1889 if (!(l_neighbor->flag & SELECT)) {
1890 l_neighbor->flag |= SEQ_RIGHTSEL;
1891 }
1892 }
1893 if (r_neighbor) {
1894 if (!(r_neighbor->flag & SELECT)) {
1895 r_neighbor->flag |= SEQ_LEFTSEL;
1896 }
1897 break;
1898 }
1899 }
1900 }
1901 }
1902 /* Select strips. */
1903 LISTBASE_FOREACH (Strip *, strip, ed->current_strips()) {
1904 if ((strip->flag & SEQ_LEFTSEL) || (strip->flag & SEQ_RIGHTSEL)) {
1905 if (!(strip->flag & SELECT)) {
1906 strip->flag |= SELECT;
1907 recurs_sel_strip(strip);
1908 }
1909 }
1910 }
1911
1913
1915
1916 return OPERATOR_FINISHED;
1917}
1918
1920{
1921 /* Identifiers. */
1922 ot->name = "Select Handles";
1923 ot->idname = "SEQUENCER_OT_select_handles";
1924 ot->description = "Select gizmo handles on the sides of the selected strip";
1925
1926 /* API callbacks. */
1928 ot->poll = sequencer_edit_poll;
1929
1930 /* Flags. */
1931 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1932
1933 /* Properties. */
1934 RNA_def_enum(ot->srna,
1935 "side",
1938 "Side",
1939 "The side of the handle that is selected");
1940}
1941
1943
1944/* -------------------------------------------------------------------- */
1947
1949{
1951 Editing *ed = seq::editing_get(scene);
1952 const bool extend = RNA_boolean_get(op->ptr, "extend");
1953 const int side = RNA_enum_get(op->ptr, "side");
1954
1955 if (ed == nullptr) {
1956 return OPERATOR_CANCELLED;
1957 }
1958 if (extend == false) {
1959 deselect_all_strips(scene);
1960 }
1961 const int timeline_frame = scene->r.cfra;
1963 bool test = false;
1964 switch (side) {
1965 case -1:
1966 test = (timeline_frame >= seq::time_right_handle_frame_get(scene, strip));
1967 break;
1968 case 1:
1969 test = (timeline_frame <= seq::time_left_handle_frame_get(scene, strip));
1970 break;
1971 case 2:
1972 test = seq::time_strip_intersects_frame(scene, strip, timeline_frame);
1973 break;
1974 }
1975
1976 if (test) {
1977 strip->flag |= SELECT;
1978 recurs_sel_strip(strip);
1979 }
1980 }
1981
1983
1985
1986 return OPERATOR_FINISHED;
1987}
1988
1990{
1991 static const EnumPropertyItem sequencer_select_left_right_types[] = {
1992 {-1, "LEFT", 0, "Left", "Select to the left of the current frame"},
1993 {1, "RIGHT", 0, "Right", "Select to the right of the current frame"},
1994 {2, "CURRENT", 0, "Current Frame", "Select intersecting with the current frame"},
1995 {0, nullptr, 0, nullptr, nullptr},
1996 };
1997
1998 /* Identifiers. */
1999 ot->name = "Select Side of Frame";
2000 ot->idname = "SEQUENCER_OT_select_side_of_frame";
2001 ot->description = "Select strips relative to the current frame";
2002
2003 /* API callbacks. */
2006
2007 /* Flags. */
2008 ot->flag = OPTYPE_UNDO;
2009
2010 /* Properties. */
2011 PropertyRNA *prop;
2012 prop = RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend the selection");
2014 ot->prop = RNA_def_enum(ot->srna, "side", sequencer_select_left_right_types, 0, "Side", "");
2015}
2016
2018
2019/* -------------------------------------------------------------------- */
2022
2024{
2026 Editing *ed = seq::editing_get(scene);
2027
2028 const int sel_side = RNA_enum_get(op->ptr, "side");
2029 const int frame_init = sel_side == seq::SIDE_LEFT ? INT_MIN : INT_MAX;
2030 int frame_ranges[seq::MAX_CHANNELS];
2031 bool selected = false;
2032
2033 copy_vn_i(frame_ranges, ARRAY_SIZE(frame_ranges), frame_init);
2034
2035 LISTBASE_FOREACH (Strip *, strip, ed->current_strips()) {
2036 if (UNLIKELY(strip->channel >= seq::MAX_CHANNELS)) {
2037 continue;
2038 }
2039 int *frame_limit_p = &frame_ranges[strip->channel];
2040 if (strip->flag & SELECT) {
2041 selected = true;
2042 if (sel_side == seq::SIDE_LEFT) {
2043 *frame_limit_p = max_ii(*frame_limit_p, seq::time_left_handle_frame_get(scene, strip));
2044 }
2045 else {
2046 *frame_limit_p = min_ii(*frame_limit_p, seq::time_left_handle_frame_get(scene, strip));
2047 }
2048 }
2049 }
2050
2051 if (selected == false) {
2052 return OPERATOR_CANCELLED;
2053 }
2054
2055 select_active_side_range(scene, ed->current_strips(), sel_side, frame_ranges, frame_init);
2056
2058
2060
2061 return OPERATOR_FINISHED;
2062}
2063
2065{
2066 /* Identifiers. */
2067 ot->name = "Select Side";
2068 ot->idname = "SEQUENCER_OT_select_side";
2069 ot->description = "Select strips on the nominated side of the selected strips";
2070
2071 /* API callbacks. */
2073 ot->poll = sequencer_edit_poll;
2074
2075 /* Flags. */
2076 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2077
2078 /* Properties. */
2079 RNA_def_enum(ot->srna,
2080 "side",
2083 "Side",
2084 "The side to which the selection is applied");
2085}
2086
2088
2089/* -------------------------------------------------------------------- */
2092
2094 const Strip *strip,
2095 const rctf *rect)
2096{
2098 scene, strip);
2099 float rect_quad[4][2] = {{rect->xmax, rect->ymax},
2100 {rect->xmax, rect->ymin},
2101 {rect->xmin, rect->ymin},
2102 {rect->xmin, rect->ymax}};
2103
2104 return strip_point_image_isect(scene, strip, rect_quad[0]) ||
2105 strip_point_image_isect(scene, strip, rect_quad[1]) ||
2106 strip_point_image_isect(scene, strip, rect_quad[2]) ||
2107 strip_point_image_isect(scene, strip, rect_quad[3]) ||
2109 strip_image_quad[0], rect_quad[0], rect_quad[1], rect_quad[2], rect_quad[3]) ||
2111 strip_image_quad[1], rect_quad[0], rect_quad[1], rect_quad[2], rect_quad[3]) ||
2113 strip_image_quad[2], rect_quad[0], rect_quad[1], rect_quad[2], rect_quad[3]) ||
2115 strip_image_quad[3], rect_quad[0], rect_quad[1], rect_quad[2], rect_quad[3]);
2116}
2117
2119 const rctf *rect,
2120 const eSelectOp mode)
2121{
2123 Editing *ed = seq::editing_get(scene);
2126 SpaceSeq *sseq = CTX_wm_space_seq(C);
2127
2129 scene, channels, seqbase, scene->r.cfra, sseq->chanshown);
2130 for (Strip *strip : strips) {
2131 if (!strip_box_select_rect_image_isect(scene, strip, rect)) {
2132 continue;
2133 }
2134
2135 if (ELEM(mode, SEL_OP_ADD, SEL_OP_SET)) {
2136 strip->flag |= SELECT;
2137 }
2138 else {
2139 BLI_assert(mode == SEL_OP_SUB);
2140 strip->flag &= ~SELECT;
2141 }
2142 }
2143}
2144
2146{
2149 Editing *ed = seq::editing_get(scene);
2150
2151 if (ed == nullptr) {
2152 return OPERATOR_CANCELLED;
2153 }
2154
2156 {
2158 }
2159
2160 const eSelectOp sel_op = eSelectOp(RNA_enum_get(op->ptr, "mode"));
2161 const bool handles = RNA_boolean_get(op->ptr, "include_handles");
2162 const bool select = (sel_op != SEL_OP_SUB);
2163
2164 bool changed = false;
2165
2166 if (SEL_OP_USE_PRE_DESELECT(sel_op)) {
2167 changed |= deselect_all_strips(scene);
2168 }
2169
2170 rctf rectf;
2172 UI_view2d_region_to_view_rctf(v2d, &rectf, &rectf);
2173
2174 ARegion *region = CTX_wm_region(C);
2175 if (region->regiontype == RGN_TYPE_PREVIEW) {
2177 return OPERATOR_CANCELLED;
2178 }
2179 seq_box_select_strip_from_preview(C, &rectf, sel_op);
2181 return OPERATOR_FINISHED;
2182 }
2183
2184 LISTBASE_FOREACH (Strip *, strip, ed->current_strips()) {
2185 rctf rq;
2186 strip_rectf(scene, strip, &rq);
2187 if (BLI_rctf_isect(&rq, &rectf, nullptr)) {
2188 if (handles) {
2189 /* Get the clickable handle size, ignoring padding. */
2190 float handsize = inner_clickable_handle_size_get(scene, strip, v2d) * 4;
2191
2192 /* Right handle. */
2193 if (rectf.xmax > (seq::time_right_handle_frame_get(scene, strip) - handsize)) {
2194 if (select) {
2195 strip->flag |= SELECT | SEQ_RIGHTSEL;
2196 }
2197 else {
2198 /* Deselect the strip if it's left with no handles selected. */
2199 if ((strip->flag & SEQ_RIGHTSEL) && ((strip->flag & SEQ_LEFTSEL) == 0)) {
2200 strip->flag &= ~SELECT;
2201 }
2202 strip->flag &= ~SEQ_RIGHTSEL;
2203 }
2204
2205 changed = true;
2206 }
2207 /* Left handle. */
2208 if (rectf.xmin < (seq::time_left_handle_frame_get(scene, strip) + handsize)) {
2209 if (select) {
2210 strip->flag |= SELECT | SEQ_LEFTSEL;
2211 }
2212 else {
2213 /* Deselect the strip if it's left with no handles selected. */
2214 if ((strip->flag & SEQ_LEFTSEL) && ((strip->flag & SEQ_RIGHTSEL) == 0)) {
2215 strip->flag &= ~SELECT;
2216 }
2217 strip->flag &= ~SEQ_LEFTSEL;
2218 }
2219 }
2220
2221 changed = true;
2222 }
2223
2224 /* Regular box selection. */
2225 else {
2227 strip->flag &= ~(SEQ_LEFTSEL | SEQ_RIGHTSEL);
2228 changed = true;
2229 }
2230
2231 const bool ignore_connections = RNA_boolean_get(op->ptr, "ignore_connections");
2232 if (!ignore_connections) {
2233 /* Propagate selection to connected strips. */
2234 StripSelection selection;
2235 selection.strip1 = strip;
2237 }
2238 }
2239 }
2240
2241 if (!changed) {
2242 return OPERATOR_CANCELLED;
2243 }
2244
2246
2247 return OPERATOR_FINISHED;
2248}
2249
2251 wmOperator *op,
2252 const wmEvent *event)
2253{
2255 const View2D *v2d = UI_view2d_fromcontext(C);
2256 ARegion *region = CTX_wm_region(C);
2257
2259 return OPERATOR_CANCELLED;
2260 }
2261
2262 const bool tweak = RNA_boolean_get(op->ptr, "tweak");
2263
2264 if (tweak) {
2265 int mval[2];
2266 float mouse_co[2];
2267 WM_event_drag_start_mval(event, region, mval);
2268 UI_view2d_region_to_view(v2d, mval[0], mval[1], &mouse_co[0], &mouse_co[1]);
2269
2270 StripSelection selection = pick_strip_and_handle(scene, v2d, mouse_co);
2271
2272 if (selection.strip1 != nullptr) {
2274 }
2275 }
2276
2277 return WM_gesture_box_invoke(C, op, event);
2278}
2279
2281{
2282 PropertyRNA *prop;
2283
2284 /* Identifiers. */
2285 ot->name = "Box Select";
2286 ot->idname = "SEQUENCER_OT_select_box";
2287 ot->description = "Select strips using box selection";
2288
2289 /* API callbacks. */
2292 ot->modal = WM_gesture_box_modal;
2293 ot->cancel = WM_gesture_box_cancel;
2294
2296
2297 /* Flags. */
2298 ot->flag = OPTYPE_UNDO;
2299
2300 /* Properties. */
2303
2304 prop = RNA_def_boolean(
2305 ot->srna,
2306 "tweak",
2307 false,
2308 "Tweak",
2309 "Make box select pass through to sequence slide when the cursor is hovering on a strip");
2311
2312 prop = RNA_def_boolean(
2313 ot->srna, "include_handles", false, "Select Handles", "Select the strips and their handles");
2315
2316 prop = RNA_def_boolean(ot->srna,
2317 "ignore_connections",
2318 false,
2319 "Ignore Connections",
2320 "Select strips individually whether or not they are connected");
2322}
2323
2324
2325/* -------------------------------------------------------------------- */
2329 const rcti *clip_rect,
2330 const Span<int2> mcoords,
2331 const float co_test[2])
2332{
2333 int co_screen[2];
2335 &region->v2d, co_test[0], co_test[1], &co_screen[0], &co_screen[1]) &&
2336 BLI_rcti_isect_pt_v(clip_rect, co_screen) &&
2337 BLI_lasso_is_point_inside(mcoords, co_screen[0], co_screen[1], V2D_IS_CLIPPED))
2338 {
2339 return true;
2340 }
2341 return false;
2342}
2343
2344static bool rcti_in_lasso(const rcti rect, const Span<int2> mcoords)
2345{
2346 rcti lasso_rect;
2347 BLI_lasso_boundbox(&lasso_rect, mcoords);
2348 /* Check if edge of strip is in the lasso. */
2350 mcoords, rect.xmin, rect.ymin, rect.xmax, rect.ymin, V2D_IS_CLIPPED) ||
2352 mcoords, rect.xmax, rect.ymin, rect.xmax, rect.ymax, V2D_IS_CLIPPED) ||
2354 mcoords, rect.xmax, rect.ymax, rect.xmin, rect.ymax, V2D_IS_CLIPPED) ||
2356 mcoords, rect.xmin, rect.ymax, rect.xmin, rect.ymin, V2D_IS_CLIPPED))
2357 {
2358 return true;
2359 }
2360
2361 /* Check if lasso is in the strip rect. Used when the lasso is only inside one strip. */
2362 if (BLI_rcti_inside_rcti(&rect, &lasso_rect)) {
2363 return true;
2364 }
2365 return false;
2366}
2367
2369 const Span<int2> mcoords,
2370 ARegion *region,
2371 const eSelectOp sel_op)
2372{
2373 Scene *scene = CTX_data_scene(C);
2374 Editing *ed = seq::editing_get(scene);
2375
2376 bool changed = false;
2377 const bool select = (sel_op != SEL_OP_SUB);
2378
2379 LISTBASE_FOREACH (Strip *, strip, &ed->seqbase) {
2380 rctf strip_rct;
2381 rcti region_rct;
2382 strip_rectf(scene, strip, &strip_rct);
2384 &region->v2d, strip_rct.xmin, strip_rct.ymin, &region_rct.xmin, &region_rct.ymin);
2386 &region->v2d, strip_rct.xmax, strip_rct.ymax, &region_rct.xmax, &region_rct.ymax);
2387
2388 if (rcti_in_lasso(region_rct, mcoords)) {
2390 changed = true;
2391 }
2392 }
2393 return changed;
2394}
2395
2397 Editing *ed,
2398 const Span<int2> mcoords,
2399 const eSelectOp sel_op)
2400{
2401 Scene *scene = CTX_data_scene(C);
2402 const ARegion *region = CTX_wm_region(C);
2403
2404 bool changed = false;
2405 rcti rect;
2406 BLI_lasso_boundbox(&rect, mcoords);
2407
2410 SpaceSeq *sseq = CTX_wm_space_seq(C);
2411
2413 scene, channels, seqbase, scene->r.cfra, sseq->chanshown);
2414 for (Strip *strip : strips) {
2416 if (do_lasso_select_is_origin_inside(region, &rect, mcoords, origin)) {
2417 changed = true;
2418 if (ELEM(sel_op, SEL_OP_ADD, SEL_OP_SET)) {
2419 strip->flag |= SELECT;
2420 }
2421 else {
2422 BLI_assert(sel_op == SEL_OP_SUB);
2423 strip->flag &= ~SELECT;
2424 }
2425 }
2426 }
2427
2428 return changed;
2429}
2430
2432{
2433 Scene *scene = CTX_data_scene(C);
2434 ARegion *region = CTX_wm_region(C);
2436 Editing *ed = seq::editing_get(scene);
2437
2438 if (ed == nullptr) {
2439 return OPERATOR_CANCELLED;
2440 }
2441
2442 if (mcoords.is_empty()) {
2443 return OPERATOR_PASS_THROUGH;
2444 }
2445
2446 const eSelectOp sel_op = eSelectOp(RNA_enum_get(op->ptr, "mode"));
2447 const bool use_pre_deselect = SEL_OP_USE_PRE_DESELECT(sel_op);
2448 bool changed = false;
2449
2450 if (use_pre_deselect) {
2451 changed |= deselect_all_strips(scene);
2452 }
2453
2454 if (region->regiontype == RGN_TYPE_PREVIEW) {
2455 changed = do_lasso_select_preview(C, ed, mcoords, sel_op);
2456 }
2457 else {
2458 changed = do_lasso_select_timeline(C, mcoords, region, sel_op);
2459 }
2460
2461 if (changed) {
2463 return OPERATOR_FINISHED;
2464 }
2465
2466 return OPERATOR_CANCELLED;
2467}
2468
2470{
2471 ot->name = "Lasso Select";
2472 ot->description = "Select strips using lasso selection";
2473 ot->idname = "SEQUENCER_OT_select_lasso";
2474
2475 ot->invoke = WM_gesture_lasso_invoke;
2476 ot->modal = WM_gesture_lasso_modal;
2477 ot->exec = vse_lasso_select_exec;
2479 ot->cancel = WM_gesture_lasso_cancel;
2480
2481 /* flags */
2483
2484 /* properties */
2487}
2488
2489
2490/* -------------------------------------------------------------------- */
2494 const Strip *strip,
2495 const int *radius,
2496 const float2 mval)
2497{
2499
2500 float dx = origin.x - float(mval[0]);
2501 float dy = origin.y - float(mval[1]);
2502 float dist_sq = sqrt(dx * dx + dy * dy);
2503
2504 return dist_sq <= *radius;
2505}
2506
2508 int radius,
2509 const float2 mval,
2510 const eSelectOp mode)
2511{
2512 Scene *scene = CTX_data_scene(C);
2513 Editing *ed = seq::editing_get(scene);
2516 SpaceSeq *sseq = CTX_wm_space_seq(C);
2517
2519 scene, channels, seqbase, scene->r.cfra, sseq->chanshown);
2520 for (Strip *strip : strips) {
2521 if (!strip_circle_select_radius_image_isect(scene, strip, &radius, mval)) {
2522 continue;
2523 }
2524
2525 if (ELEM(mode, SEL_OP_ADD, SEL_OP_SET)) {
2526 strip->flag |= SELECT;
2527 }
2528 else {
2529 BLI_assert(mode == SEL_OP_SUB);
2530 strip->flag &= ~SELECT;
2531 }
2532 }
2533}
2534
2536 const float xy[2],
2537 const float x_radius,
2538 const float y_radius)
2539{
2540 float dx, dy;
2541
2542 if (xy[0] >= rect->xmin && xy[0] <= rect->xmax) {
2543 dx = 0;
2544 }
2545 else {
2546 dx = (xy[0] < rect->xmin) ? (rect->xmin - xy[0]) : (xy[0] - rect->xmax);
2547 }
2548
2549 if (xy[1] >= rect->ymin && xy[1] <= rect->ymax) {
2550 dy = 0;
2551 }
2552 else {
2553 dy = (xy[1] < rect->ymin) ? (rect->ymin - xy[1]) : (xy[1] - rect->ymax);
2554 }
2555
2556 return ((dx * dx) / (x_radius * x_radius) + (dy * dy) / (y_radius * y_radius) <= 1.0f);
2557}
2559{
2560 const int radius = RNA_int_get(op->ptr, "radius");
2561 const int mval[2] = {RNA_int_get(op->ptr, "x"), RNA_int_get(op->ptr, "y")};
2562 wmGesture *gesture = static_cast<wmGesture *>(op->customdata);
2563 const eSelectOp sel_op = eSelectOp(RNA_enum_get(op->ptr, "mode"));
2564
2565 Scene *scene = CTX_data_scene(C);
2567 Editing *ed = seq::editing_get(scene);
2568 ARegion *region = CTX_wm_region(C);
2569
2570 const bool use_pre_deselect = SEL_OP_USE_PRE_DESELECT(sel_op);
2571
2572 if (use_pre_deselect && WM_gesture_is_modal_first(gesture)) {
2573 deselect_all_strips(scene);
2575 }
2576
2577 if (ed == nullptr) {
2578 return OPERATOR_CANCELLED;
2579 }
2580
2581 float2 view_mval;
2582 UI_view2d_region_to_view(v2d, mval[0], mval[1], &view_mval[0], &view_mval[1]);
2583 float pixel_radius = radius / UI_view2d_scale_get_x(v2d);
2584
2585 if (region->regiontype == RGN_TYPE_PREVIEW) {
2586 seq_circle_select_strip_from_preview(C, pixel_radius, view_mval, sel_op);
2588 return OPERATOR_FINISHED;
2589 }
2590
2591 float x_radius = radius / UI_view2d_scale_get_x(v2d);
2592 float y_radius = radius / UI_view2d_scale_get_y(v2d);
2593 bool changed = false;
2594 LISTBASE_FOREACH (Strip *, strip, ed->current_strips()) {
2595 rctf rq;
2596 strip_rectf(scene, strip, &rq);
2597 /* Use custom function to check the distance because in timeline the circle is a ellipse. */
2598 if (check_circle_intersection_in_timeline(&rq, view_mval, x_radius, y_radius)) {
2599 if (ELEM(sel_op, SEL_OP_ADD, SEL_OP_SET)) {
2600 strip->flag |= SELECT;
2601 }
2602 else {
2603 BLI_assert(sel_op == SEL_OP_SUB);
2604 strip->flag &= ~SELECT;
2605 }
2606 changed = true;
2607
2608 const bool ignore_connections = RNA_boolean_get(op->ptr, "ignore_connections");
2609 if (!ignore_connections) {
2610 /* Propagate selection to connected strips. */
2611 StripSelection selection;
2612 selection.strip1 = strip;
2614 }
2615 }
2616 }
2617 if (changed) {
2619 }
2620 return OPERATOR_FINISHED;
2621}
2622
2624{
2625 PropertyRNA *prop;
2626
2627 ot->name = "Circle Select";
2628 ot->description = "Select strips using circle selection";
2629 ot->idname = "SEQUENCER_OT_select_circle";
2630
2631 ot->invoke = WM_gesture_circle_invoke;
2632 ot->modal = WM_gesture_circle_modal;
2633 ot->exec = vse_circle_select_exec;
2634
2636
2637 ot->get_name = ED_select_circle_get_name;
2638
2639 /* flags */
2640 ot->flag = OPTYPE_UNDO;
2641
2642 /* properties */
2645
2646 prop = RNA_def_boolean(ot->srna,
2647 "ignore_connections",
2648 false,
2649 "Ignore Connections",
2650 "Select strips individually whether or not they are connected");
2652}
2653
2655
2656/* -------------------------------------------------------------------- */
2659
2660enum {
2668};
2669
2671 {SEQ_SELECT_GROUP_TYPE, "TYPE", 0, "Type", "Shared strip type"},
2673 "TYPE_BASIC",
2674 0,
2675 "Global Type",
2676 "All strips of same basic type (graphical or sound)"},
2678 "TYPE_EFFECT",
2679 0,
2680 "Effect Type",
2681 "Shared strip effect type (if active strip is not an effect one, select all non-effect "
2682 "strips)"},
2683 {SEQ_SELECT_GROUP_DATA, "DATA", 0, "Data", "Shared data (scene, image, sound, etc.)"},
2684 {SEQ_SELECT_GROUP_EFFECT, "EFFECT", 0, "Effect", "Shared effects"},
2686 "EFFECT_LINK",
2687 0,
2688 "Effect/Linked",
2689 "Other strips affected by the active one (sharing some time, and below or "
2690 "effect-assigned)"},
2691 {SEQ_SELECT_GROUP_OVERLAP, "OVERLAP", 0, "Overlap", "Overlapping time"},
2692 {0, nullptr, 0, nullptr, nullptr},
2693};
2694
2695#define STRIP_IS_SOUND(_strip) (_strip->type == STRIP_TYPE_SOUND_RAM)
2696
2697#define STRIP_USE_DATA(_strip) \
2698 (ELEM(_strip->type, STRIP_TYPE_SCENE, STRIP_TYPE_MOVIECLIP, STRIP_TYPE_MASK) || \
2699 STRIP_HAS_PATH(_strip))
2700
2701#define STRIP_CHANNEL_CHECK(_strip, _chan) ELEM((_chan), 0, (_strip)->channel)
2702
2704 ListBase * /*seqbase*/,
2705 Strip *act_strip,
2706 const int channel)
2707{
2708 bool changed = false;
2709
2710 for (Strip *strip : strips) {
2711 if (STRIP_CHANNEL_CHECK(strip, channel) && strip->type == act_strip->type) {
2712 strip->flag |= SELECT;
2713 changed = true;
2714 }
2715 }
2716
2717 return changed;
2718}
2719
2721 ListBase * /*seqbase*/,
2722 Strip *act_strip,
2723 const int channel)
2724{
2725 bool changed = false;
2726 const bool is_sound = STRIP_IS_SOUND(act_strip);
2727
2728 for (Strip *strip : strips) {
2729 if (STRIP_CHANNEL_CHECK(strip, channel) &&
2730 (is_sound ? STRIP_IS_SOUND(strip) : !STRIP_IS_SOUND(strip)))
2731 {
2732 strip->flag |= SELECT;
2733 changed = true;
2734 }
2735 }
2736
2737 return changed;
2738}
2739
2741 ListBase * /*seqbase*/,
2742 Strip *act_strip,
2743 const int channel)
2744{
2745 bool changed = false;
2746 const bool is_effect = act_strip->is_effect();
2747
2748 for (Strip *strip : strips) {
2749 if (STRIP_CHANNEL_CHECK(strip, channel) &&
2750 (is_effect ? strip->is_effect() : !strip->is_effect()))
2751 {
2752 strip->flag |= SELECT;
2753 changed = true;
2754 }
2755 }
2756
2757 return changed;
2758}
2759
2761 ListBase * /*seqbase*/,
2762 Strip *act_strip,
2763 const int channel)
2764{
2765 bool changed = false;
2766 const char *dirpath = act_strip->data ? act_strip->data->dirpath : nullptr;
2767
2768 if (!STRIP_USE_DATA(act_strip)) {
2769 return changed;
2770 }
2771
2772 if (STRIP_HAS_PATH(act_strip) && dirpath) {
2773 for (Strip *strip : strips) {
2774 if (STRIP_CHANNEL_CHECK(strip, channel) && STRIP_HAS_PATH(strip) && strip->data &&
2775 STREQ(strip->data->dirpath, dirpath))
2776 {
2777 strip->flag |= SELECT;
2778 changed = true;
2779 }
2780 }
2781 }
2782 else if (act_strip->type == STRIP_TYPE_SCENE) {
2783 Scene *sce = act_strip->scene;
2784 for (Strip *strip : strips) {
2785 if (STRIP_CHANNEL_CHECK(strip, channel) && strip->type == STRIP_TYPE_SCENE &&
2786 strip->scene == sce)
2787 {
2788 strip->flag |= SELECT;
2789 changed = true;
2790 }
2791 }
2792 }
2793 else if (act_strip->type == STRIP_TYPE_MOVIECLIP) {
2794 MovieClip *clip = act_strip->clip;
2795 for (Strip *strip : strips) {
2796 if (STRIP_CHANNEL_CHECK(strip, channel) && strip->type == STRIP_TYPE_MOVIECLIP &&
2797 strip->clip == clip)
2798 {
2799 strip->flag |= SELECT;
2800 changed = true;
2801 }
2802 }
2803 }
2804 else if (act_strip->type == STRIP_TYPE_MASK) {
2805 Mask *mask = act_strip->mask;
2806 for (Strip *strip : strips) {
2807 if (STRIP_CHANNEL_CHECK(strip, channel) && strip->type == STRIP_TYPE_MASK &&
2808 strip->mask == mask)
2809 {
2810 strip->flag |= SELECT;
2811 changed = true;
2812 }
2813 }
2814 }
2815
2816 return changed;
2817}
2818
2820 ListBase * /*seqbase*/,
2821 Strip *act_strip,
2822 const int channel)
2823{
2824 bool changed = false;
2826
2827 for (const Strip *strip : strips) {
2828 if (STRIP_CHANNEL_CHECK(strip, channel) && strip->is_effect() &&
2829 seq::relation_is_effect_of_strip(strip, act_strip))
2830 {
2831 effects.add(StripType(strip->type));
2832 }
2833 }
2834
2835 for (Strip *strip : strips) {
2836 if (STRIP_CHANNEL_CHECK(strip, channel) && effects.contains(StripType(strip->type))) {
2837 if (strip->input1) {
2838 strip->input1->flag |= SELECT;
2839 }
2840 if (strip->input2) {
2841 strip->input2->flag |= SELECT;
2842 }
2843 changed = true;
2844 }
2845 }
2846
2847 return changed;
2848}
2849
2850static bool select_grouped_time_overlap(const Scene *scene,
2852 ListBase * /*seqbase*/,
2853 Strip *act_strip)
2854{
2855 bool changed = false;
2856
2857 for (Strip *strip : strips) {
2858 if (seq::time_left_handle_frame_get(scene, strip) <
2859 seq::time_right_handle_frame_get(scene, act_strip) &&
2860 seq::time_right_handle_frame_get(scene, strip) >
2861 seq::time_left_handle_frame_get(scene, act_strip))
2862 {
2863 strip->flag |= SELECT;
2864 changed = true;
2865 }
2866 }
2867
2868 return changed;
2869}
2870
2871/* Query strips that are in lower channel and intersect in time with strip_reference. */
2872static void query_lower_channel_strips(const Scene *scene,
2873 Strip *strip_reference,
2874 ListBase *seqbase,
2876{
2877 LISTBASE_FOREACH (Strip *, strip_test, seqbase) {
2878 if (strip_test->channel > strip_reference->channel) {
2879 continue; /* Not lower channel. */
2880 }
2881 if (seq::time_right_handle_frame_get(scene, strip_test) <=
2882 seq::time_left_handle_frame_get(scene, strip_reference) ||
2883 seq::time_left_handle_frame_get(scene, strip_test) >=
2884 seq::time_right_handle_frame_get(scene, strip_reference))
2885 {
2886 continue; /* Not intersecting in time. */
2887 }
2888 strips.add(strip_test);
2889 }
2890}
2891
2892/* Select all strips within time range and with lower channel of initial selection. Then select
2893 * effect chains of these strips. */
2894static bool select_grouped_effect_link(const Scene *scene,
2896 ListBase *seqbase,
2897 Strip * /*act_strip*/,
2898 const int /*channel*/)
2899{
2900 /* Get collection of strips. */
2901 strips.remove_if([&](Strip *strip) { return (strip->flag & SELECT) == 0; });
2902 const int selected_strip_count = strips.size();
2903 /* XXX: this uses scene as arg, so it does not work with iterator :( I had thought about this,
2904 * but expand function is just so useful... I can just add scene and inject it I guess. */
2907
2908 /* Check if other strips will be affected. */
2909 const bool changed = strips.size() > selected_strip_count;
2910
2911 /* Actual logic. */
2912 for (Strip *strip : strips) {
2913 strip->flag |= SELECT;
2914 }
2915
2916 return changed;
2917}
2918
2919#undef STRIP_IS_SOUND
2920#undef STRIP_USE_DATA
2921
2923{
2926 Strip *act_strip = seq::select_active_get(scene);
2927
2928 const bool is_preview = sequencer_view_has_preview_poll(C);
2929 if (is_preview && !sequencer_view_preview_only_poll(C)) {
2930 return OPERATOR_CANCELLED;
2931 }
2932
2934
2935 if (act_strip == nullptr || (is_preview && !strips.contains(act_strip))) {
2936 BKE_report(op->reports, RPT_ERROR, "No active strip!");
2937 return OPERATOR_CANCELLED;
2938 }
2939
2940 const int type = RNA_enum_get(op->ptr, "type");
2941 const int channel = RNA_boolean_get(op->ptr, "use_active_channel") ? act_strip->channel : 0;
2942 const bool extend = RNA_boolean_get(op->ptr, "extend");
2943
2944 bool changed = false;
2945
2946 if (!extend) {
2947 LISTBASE_FOREACH (Strip *, strip, seqbase) {
2948 strip->flag &= ~SELECT;
2949 changed = true;
2950 }
2951 }
2952
2953 switch (type) {
2955 changed |= select_grouped_type(strips, seqbase, act_strip, channel);
2956 break;
2958 changed |= select_grouped_type_basic(strips, seqbase, act_strip, channel);
2959 break;
2961 changed |= select_grouped_type_effect(strips, seqbase, act_strip, channel);
2962 break;
2964 changed |= select_grouped_data(strips, seqbase, act_strip, channel);
2965 break;
2967 changed |= select_grouped_effect(strips, seqbase, act_strip, channel);
2968 break;
2970 changed |= select_grouped_effect_link(scene, strips, seqbase, act_strip, channel);
2971 break;
2973 changed |= select_grouped_time_overlap(scene, strips, seqbase, act_strip);
2974 break;
2975 default:
2976 BLI_assert(0);
2977 break;
2978 }
2979
2980 if (changed) {
2983 return OPERATOR_FINISHED;
2984 }
2985
2986 return OPERATOR_CANCELLED;
2987}
2988
2990{
2991 /* Identifiers. */
2992 ot->name = "Select Grouped";
2993 ot->idname = "SEQUENCER_OT_select_grouped";
2994 ot->description = "Select all strips grouped by various properties";
2995
2996 /* API callbacks. */
2997 ot->invoke = WM_menu_invoke;
2999 ot->poll = sequencer_edit_poll;
3000
3001 /* Flags. */
3002 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3003
3004 /* Properties. */
3005 ot->prop = RNA_def_enum(ot->srna, "type", sequencer_prop_select_grouped_types, 0, "Type", "");
3006 RNA_def_boolean(ot->srna,
3007 "extend",
3008 false,
3009 "Extend",
3010 "Extend selection instead of deselecting everything first");
3011 RNA_def_boolean(ot->srna,
3012 "use_active_channel",
3013 false,
3014 "Same Channel",
3015 "Only consider strips on the same channel as the active one");
3016}
3017
3019
3020} // namespace blender::ed::vse
Scene * CTX_data_scene(const bContext *C)
SpaceSeq * CTX_wm_space_seq(const bContext *C)
ARegion * CTX_wm_region(const bContext *C)
Scene * CTX_data_sequencer_scene(const bContext *C)
@ RPT_ERROR
Definition BKE_report.hh:39
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:153
#define BLI_assert(a)
Definition BLI_assert.h:46
struct GSet GSet
Definition BLI_ghash.h:337
unsigned int BLI_ghashutil_ptrhash(const void *key)
BLI_INLINE bool BLI_gsetIterator_done(const GSetIterator *gsi)
Definition BLI_ghash.h:463
BLI_INLINE void * BLI_gsetIterator_getKey(GSetIterator *gsi)
Definition BLI_ghash.h:455
GSet * BLI_gset_new(GSetHashFP hashfp, GSetCmpFP cmpfp, const char *info) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
Definition BLI_ghash.cc:944
bool BLI_ghashutil_ptrcmp(const void *a, const void *b)
BLI_INLINE void BLI_gsetIterator_init(GSetIterator *gsi, GSet *gs)
Definition BLI_ghash.h:447
void BLI_gset_free(GSet *gs, GSetKeyFreeFP keyfreefp)
BLI_INLINE void BLI_gsetIterator_step(GSetIterator *gsi)
Definition BLI_ghash.h:459
bool BLI_gset_add(GSet *gs, void *key)
Definition BLI_ghash.cc:966
bool BLI_lasso_is_point_inside(blender::Span< blender::int2 > mcoords, int sx, int sy, int error_value)
void BLI_lasso_boundbox(rcti *rect, blender::Span< blender::int2 > mcoords)
bool BLI_lasso_is_edge_inside(blender::Span< blender::int2 > mcoords, int x0, int y0, int x1, int y1, int error_value)
#define LISTBASE_FOREACH(type, var, list)
void void BLI_freelistN(ListBase *listbase) ATTR_NONNULL(1)
Definition listbase.cc:497
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
void void BLI_listbase_sort(ListBase *listbase, int(*cmp)(const void *, const void *)) ATTR_NONNULL(1
MINLINE int min_ii(int a, int b)
MINLINE float min_ff(float a, float b)
MINLINE int max_ii(int a, int b)
MINLINE float square_f(float a)
int isect_point_quad_v2(const float p[2], const float v1[2], const float v2[2], const float v3[2], const float v4[2])
MINLINE float len_squared_v2(const float v[2]) ATTR_WARN_UNUSED_RESULT
MINLINE void sub_v2_v2(float r[2], const float a[2])
MINLINE void mul_v2_v2(float r[2], const float a[2])
void copy_vn_i(int *array_tar, int size, int val)
bool BLI_rcti_isect_pt_v(const struct rcti *rect, const int xy[2])
void BLI_rctf_union(struct rctf *rct_a, const struct rctf *rct_b)
bool BLI_rctf_isect_pt_v(const struct rctf *rect, const float xy[2])
bool BLI_rctf_isect(const struct rctf *src1, const struct rctf *src2, struct rctf *dest)
float BLI_rctf_length_x(const rctf *rect, float x)
Definition rct.cc:171
void BLI_rctf_pad(struct rctf *rect, float pad_x, float pad_y)
Definition rct.cc:637
bool BLI_rcti_inside_rcti(const rcti *rct_a, const rcti *rct_b)
Definition rct.cc:198
#define ARRAY_SIZE(arr)
#define SET_FLAG_FROM_TEST(value, test, flag)
#define UNLIKELY(x)
#define ELEM(...)
#define STREQ(a, b)
#define CTX_IFACE_(context, msgid)
#define BLT_I18NCONTEXT_OPERATOR_DEFAULT
@ RGN_TYPE_PREVIEW
#define STRIP_OFSBOTTOM
#define STRIP_OFSTOP
@ STRIP_TYPE_SCENE
@ STRIP_TYPE_MOVIECLIP
@ STRIP_TYPE_MASK
#define STRIP_ALLSEL
@ SEQ_RIGHTSEL
@ SEQ_LEFTSEL
#define STRIP_HAS_PATH(_strip)
@ SEQ_DRAW_IMG_IMBUF
@ SPACE_SEQ_DESELECT_STRIP_HANDLE
@ SEQ_MARKER_TRANS
@ OPERATOR_CANCELLED
@ OPERATOR_FINISHED
@ OPERATOR_RUNNING_MODAL
@ OPERATOR_PASS_THROUGH
void ED_outliner_select_sync_from_sequence_tag(const bContext *C)
bool ED_operator_sequencer_active(bContext *C)
eSelectOp
@ SEL_OP_ADD
@ SEL_OP_SUB
@ SEL_OP_SET
@ SEL_SELECT
@ SEL_INVERT
@ SEL_DESELECT
@ SEL_TOGGLE
#define SEL_OP_USE_PRE_DESELECT(sel_op)
std::string ED_select_circle_get_name(wmOperatorType *ot, PointerRNA *ptr)
std::string ED_select_pick_get_name(wmOperatorType *ot, PointerRNA *ptr)
Read Guarded memory(de)allocation.
@ PROP_SKIP_SAVE
Definition RNA_types.hh:344
#define C
Definition RandGen.cpp:29
#define UI_TIME_SCRUB_MARGIN_Y
Definition UI_view2d.hh:479
bool UI_view2d_view_to_region_clip(const View2D *v2d, float x, float y, int *r_region_x, int *r_region_y) ATTR_NONNULL()
Definition view2d.cc:1702
View2D * UI_view2d_fromcontext(const bContext *C)
Definition view2d.cc:1855
float UI_view2d_scale_get_y(const View2D *v2d)
Definition view2d.cc:1925
void UI_view2d_region_to_view(const View2D *v2d, float x, float y, float *r_view_x, float *r_view_y) ATTR_NONNULL()
Definition view2d.cc:1668
float UI_view2d_region_to_view_x(const View2D *v2d, float x)
Definition view2d.cc:1657
#define V2D_IS_CLIPPED
Definition UI_view2d.hh:21
void UI_view2d_region_to_view_rctf(const View2D *v2d, const rctf *rect_src, rctf *rect_dst) ATTR_NONNULL()
Definition view2d.cc:1675
float UI_view2d_scale_get_x(const View2D *v2d)
Definition view2d.cc:1921
#define ND_SEQUENCER
Definition WM_types.hh:437
#define NC_SCENE
Definition WM_types.hh:378
@ OPTYPE_DEPENDS_ON_CURSOR
Definition WM_types.hh:218
@ OPTYPE_UNDO
Definition WM_types.hh:182
@ OPTYPE_REGISTER
Definition WM_types.hh:180
#define NA_SELECTED
Definition WM_types.hh:589
#define U
bool is_empty() const
Definition BLI_array.hh:264
bool contains(const Key &key) const
Definition BLI_set.hh:310
bool add(const Key &key)
Definition BLI_set.hh:248
bool remove(const Key &key)
bool add(const Key &key)
int64_t size() const
bool contains(const Key &key) const
int64_t remove_if(Predicate &&predicate)
int64_t size() const
void append(const T &value)
MouseCoords(const View2D *v2d, int x, int y)
nullptr float
#define SELECT
#define select(A, B, C)
#define sqrt
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
ccl_device_inline float2 mask(const MaskType mask, const float2 a)
static int left
static bool select_grouped_type_basic(blender::Span< Strip * > strips, ListBase *, Strip *act_strip, const int channel)
static void select_active_side(const Scene *scene, ListBase *seqbase, int sel_side, int channel, int frame)
bool sequencer_edit_poll(bContext *C)
static wmOperatorStatus sequencer_select_linked_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static void sequencer_copy_handles_to_selected_strips(const Scene *scene, const StripSelection &selection, VectorSet< Strip * > copy_to)
static void sequencer_select_linked_handle(const bContext *C, Strip *strip, const eStripHandle handle_clicked)
static Strip * strip_select_from_preview(const bContext *C, const int mval[2], const bool toggle, const bool extend, const bool center)
static bool do_lasso_select_is_origin_inside(const ARegion *region, const rcti *clip_rect, const Span< int2 > mcoords, const float co_test[2])
static void sequencer_select_side_of_frame(const bContext *C, const View2D *v2d, const int mval[2], Scene *scene)
static bool select_grouped_effect(blender::Span< Strip * > strips, ListBase *, Strip *act_strip, const int channel)
static bool element_already_selected(const StripSelection &selection)
static wmOperatorStatus sequencer_select_side_of_frame_exec(bContext *C, wmOperator *op)
void SEQUENCER_OT_select_linked_pick(wmOperatorType *ot)
static std::string sequencer_select_get_name(wmOperatorType *ot, PointerRNA *ptr)
static wmOperatorStatus sequencer_select_more_exec(bContext *C, wmOperator *)
static float inner_clickable_handle_size_get(const Scene *scene, const Strip *strip, const View2D *v2d)
static bool select_more_less_impl(Scene *scene, bool select_more)
void SEQUENCER_OT_select_all(wmOperatorType *ot)
SeqRetimingKey * retiming_mouseover_key_get(const bContext *C, const int mval[2], Strip **r_strip)
static bool check_circle_intersection_in_timeline(const rctf *rect, const float xy[2], const float x_radius, const float y_radius)
int right_fake_key_frame_get(const bContext *C, const Strip *strip)
StripSelection pick_strip_and_handle(const struct Scene *scene, const View2D *v2d, float mouse_co[2])
static void sequencer_select_connected_strips(const StripSelection &selection)
void SEQUENCER_OT_select_box(wmOperatorType *ot)
static bool is_mouse_over_both_handles_of_adjacent_strips(const Scene *scene, blender::Vector< Strip * > strips, const View2D *v2d, float mouse_co[2])
void SEQUENCER_OT_select_inverse(wmOperatorType *ot)
static void select_surrounding_handles(Scene *scene, Strip *test)
void SEQUENCER_OT_select_circle(wmOperatorType *ot)
static bool strips_are_adjacent(const Scene *scene, const Strip *strip1, const Strip *strip2)
static void seq_circle_select_strip_from_preview(bContext *C, int radius, const float2 mval, const eSelectOp mode)
bool handle_is_selected(const Strip *strip, eStripHandle handle)
void SEQUENCER_OT_select_side_of_frame(wmOperatorType *ot)
void select_strip_single(Scene *scene, Strip *strip, bool deselect_all)
static bool is_sound(wmDrag *drag)
void SEQUENCER_OT_select_grouped(wmOperatorType *ot)
void recurs_sel_strip(Strip *strip_meta)
static void seq_box_select_strip_from_preview(const bContext *C, const rctf *rect, const eSelectOp mode)
static int strip_sort_for_depth_select(const void *a, const void *b)
blender::VectorSet< Strip * > all_strips_from_context(bContext *C)
blender::VectorSet< Strip * > selected_strips_from_context(bContext *C)
wmOperatorStatus sequencer_select_exec(bContext *C, wmOperator *op)
static bool do_lasso_select_timeline(bContext *C, const Span< int2 > mcoords, ARegion *region, const eSelectOp sel_op)
void SEQUENCER_OT_select(wmOperatorType *ot)
Strip * find_neighboring_strip(const Scene *scene, const Strip *test, const int lr, int sel)
void SEQUENCER_OT_select_side(wmOperatorType *ot)
static wmOperatorStatus sequencer_select_inverse_exec(bContext *C, wmOperator *)
static wmOperatorStatus sequencer_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
void SEQUENCER_OT_select_less(wmOperatorType *ot)
static bool select_grouped_type_effect(blender::Span< Strip * > strips, ListBase *, Strip *act_strip, const int channel)
static void select_linked_time_strip(const Scene *scene, const Strip *strip_source, const eStripHandle handle_clicked)
void SEQUENCER_OT_select_more(wmOperatorType *ot)
static void query_lower_channel_strips(const Scene *scene, Strip *strip_reference, ListBase *seqbase, blender::VectorSet< Strip * > &strips)
static void strip_clickable_areas_get(const Scene *scene, const Strip *strip, const View2D *v2d, rctf *r_body, rctf *r_left_handle, rctf *r_right_handle)
static wmOperatorStatus sequencer_select_linked_exec(bContext *C, wmOperator *)
wmOperatorStatus sequencer_retiming_box_select_exec(bContext *C, wmOperator *op)
void SEQUENCER_OT_select_handles(wmOperatorType *ot)
static bool strip_circle_select_radius_image_isect(const Scene *scene, const Strip *strip, const int *radius, const float2 mval)
static wmOperatorStatus vse_lasso_select_exec(bContext *C, wmOperator *op)
void SEQUENCER_OT_select_lasso(wmOperatorType *ot)
static wmOperatorStatus sequencer_select_handle_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static float strip_to_frame_distance(const Scene *scene, const View2D *v2d, const Strip *strip, float timeline_frame)
SeqRetimingKey * try_to_realize_fake_keys(const bContext *C, Strip *strip, const int mval[2])
static int strip_sort_for_center_select(const void *a, const void *b)
static const EnumPropertyItem prop_select_handles_side_types[]
static wmOperatorStatus vse_circle_select_exec(bContext *C, wmOperator *op)
Strip * strip_under_mouse_get(const Scene *scene, const View2D *v2d, const int mval[2])
void sequencer_select_do_updates(const bContext *C, Scene *scene)
bool can_select_handle(const Scene *scene, const Strip *strip, const View2D *v2d)
void strip_rectf(const Scene *scene, const Strip *strip, rctf *r_rect)
static eStripHandle strip_handle_under_cursor_get(const Scene *scene, const Strip *strip, const View2D *v2d, float mouse_co[2])
bool sequencer_view_preview_only_poll(const bContext *C)
static void select_active_side_range(const Scene *scene, ListBase *seqbase, const int sel_side, const int frame_ranges[seq::MAX_CHANNELS], const int frame_ignore)
bool retiming_keys_can_be_displayed(const SpaceSeq *sseq)
void SEQUENCER_OT_select_handle(wmOperatorType *ot)
static bool rcti_in_lasso(const rcti rect, const Span< int2 > mcoords)
static const EnumPropertyItem sequencer_prop_select_grouped_types[]
void realize_fake_keys(const Scene *scene, Strip *strip)
bool deselect_all_strips(const Scene *scene)
static void select_linked_time(const Scene *scene, const StripSelection &selection, const bool extend, const bool deselect, const bool toggle)
bool sequencer_view_has_preview_poll(bContext *C)
wmOperatorStatus sequencer_retiming_select_all_exec(bContext *C, wmOperator *op)
static wmOperatorStatus sequencer_select_side_exec(bContext *C, wmOperator *op)
int left_fake_key_frame_get(const bContext *C, const Strip *strip)
static wmOperatorStatus sequencer_select_handles_exec(bContext *C, wmOperator *op)
static blender::Vector< Strip * > padded_strips_under_mouse_get(const Scene *scene, const View2D *v2d, float mouse_co[2])
static rctf strip_clickable_area_get(const Scene *scene, const View2D *v2d, const Strip *strip)
static wmOperatorStatus sequencer_select_handle_exec(bContext *C, wmOperator *op)
const EnumPropertyItem prop_side_types[]
void SEQUENCER_OT_select_linked(wmOperatorType *ot)
static wmOperatorStatus sequencer_select_grouped_exec(bContext *C, wmOperator *op)
static wmOperatorStatus sequencer_de_select_all_exec(bContext *C, wmOperator *op)
static wmOperatorStatus sequencer_select_less_exec(bContext *C, wmOperator *)
bool strip_point_image_isect(const Scene *scene, const Strip *strip, float point_view[2])
static bool select_linked_internal(Scene *scene)
static wmOperatorStatus sequencer_box_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static void sequencer_select_set_active(Scene *scene, Strip *strip)
static void sequencer_select_strip_impl(const Editing *ed, Strip *strip, const eStripHandle handle_clicked, const bool extend, const bool deselect, const bool toggle)
blender::Vector< Strip * > sequencer_visible_strips_get(const bContext *C)
static wmOperatorStatus sequencer_box_select_exec(bContext *C, wmOperator *op)
static bool select_grouped_effect_link(const Scene *scene, blender::VectorSet< Strip * > strips, ListBase *seqbase, Strip *, const int)
static bool select_grouped_time_overlap(const Scene *scene, blender::Span< Strip * > strips, ListBase *, Strip *act_strip)
static bool select_grouped_data(blender::Span< Strip * > strips, ListBase *, Strip *act_strip, const int channel)
static bool strip_box_select_rect_image_isect(const Scene *scene, const Strip *strip, const rctf *rect)
bool sequencer_retiming_mode_is_active(const bContext *C)
wmOperatorStatus sequencer_retiming_key_select_exec(bContext *C, wmOperator *op, SeqRetimingKey *key, const Strip *key_owner)
static bool select_grouped_type(blender::Span< Strip * > strips, ListBase *, Strip *act_strip, const int channel)
static bool do_lasso_select_preview(bContext *C, Editing *ed, const Span< int2 > mcoords, const eSelectOp sel_op)
int time_right_handle_frame_get(const Scene *scene, const Strip *strip)
VectorSet< Strip * > query_all_strips(ListBase *seqbase)
Definition iterator.cc:143
Array< float2 > image_transform_final_quad_get(const Scene *scene, const Strip *strip)
ListBase * channels_displayed_get(const Editing *ed)
Definition channels.cc:28
VectorSet< Strip * > query_selected_strips(ListBase *seqbase)
Definition iterator.cc:152
int retiming_key_timeline_frame_get(const Scene *scene, const Strip *strip, const SeqRetimingKey *key)
Editing * editing_get(const Scene *scene)
Definition sequencer.cc:286
int time_left_handle_frame_get(const Scene *, const Strip *strip)
void query_strip_effect_chain(const Scene *scene, Strip *reference_strip, ListBase *seqbase, VectorSet< Strip * > &r_strips)
Definition iterator.cc:254
blender::VectorSet< Strip * > connected_strips_get(const Strip *strip)
Strip * select_active_get(const Scene *scene)
constexpr int MAX_CHANNELS
bool is_strip_connected(const Strip *strip)
bool retiming_data_is_editable(const Strip *strip)
void iterator_set_expand(const Scene *scene, ListBase *seqbase, VectorSet< Strip * > &strips, void strip_query_func(const Scene *scene, Strip *strip_reference, ListBase *seqbase, VectorSet< Strip * > &strips))
Definition iterator.cc:82
bool time_strip_intersects_frame(const Scene *scene, const Strip *strip, const int timeline_frame)
float2 image_transform_origin_offset_pixelspace_get(const Scene *scene, const Strip *strip)
bool retiming_selection_clear(const Editing *ed)
void select_active_set(Scene *scene, Strip *strip)
bool transform_is_locked(ListBase *channels, const Strip *strip)
bool relation_is_effect_of_strip(const Strip *effect, const Strip *input)
VectorSet< Strip * > query_rendered_strips(const Scene *scene, ListBase *channels, ListBase *seqbase, const int timeline_frame, const int displayed_channel)
Definition iterator.cc:228
ListBase * active_seqbase_get(const Editing *ed)
Definition sequencer.cc:433
int effect_get_num_inputs(int strip_type)
Definition effects.cc:327
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
#define sqrtf
void RNA_int_set(PointerRNA *ptr, const char *name, int value)
int RNA_int_get(PointerRNA *ptr, const char *name)
bool RNA_boolean_get(PointerRNA *ptr, const char *name)
int RNA_enum_get(PointerRNA *ptr, const char *name)
PropertyRNA * RNA_def_enum(StructOrFunctionRNA *cont_, const char *identifier, const EnumPropertyItem *items, const int default_value, const char *ui_name, const char *ui_description)
PropertyRNA * RNA_def_boolean(StructOrFunctionRNA *cont_, const char *identifier, const bool default_value, const char *ui_name, const char *ui_description)
void RNA_def_property_flag(PropertyRNA *prop, PropertyFlag flag)
#define STRIP_IS_SOUND(_strip)
#define STRIP_USE_DATA(_strip)
#define STRIP_CHANNEL_CHECK(_strip, _chan)
void * first
struct Editing * ed
struct RenderData r
ListBase markers
char dirpath[768]
struct Strip * input1
struct Mask * mask
StripData * data
struct Scene * scene
struct MovieClip * clip
ListBase seqbase
struct Strip * next
struct Strip * input2
float xmax
float xmin
float ymax
float ymin
int ymin
int ymax
int xmin
int xmax
int mval[2]
Definition WM_types.hh:763
struct ReportList * reports
struct PointerRNA * ptr
ParamHandle ** handles
int xy[2]
Definition wm_draw.cc:178
void WM_event_drag_start_mval(const wmEvent *event, const ARegion *region, int r_mval[2])
bool WM_cursor_test_motion_and_update(const int mval[2])
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
PointerRNA * ptr
Definition wm_files.cc:4238
wmOperatorType * ot
Definition wm_files.cc:4237
bool WM_gesture_is_modal_first(const wmGesture *gesture)
void WM_gesture_box_cancel(bContext *C, wmOperator *op)
wmOperatorStatus WM_gesture_circle_invoke(bContext *C, wmOperator *op, const wmEvent *event)
Array< int2 > WM_gesture_lasso_path_to_array(bContext *, wmOperator *op)
wmOperatorStatus WM_gesture_lasso_modal(bContext *C, wmOperator *op, const wmEvent *event)
wmOperatorStatus WM_gesture_box_modal(bContext *C, wmOperator *op, const wmEvent *event)
wmOperatorStatus WM_gesture_box_invoke(bContext *C, wmOperator *op, const wmEvent *event)
void WM_gesture_lasso_cancel(bContext *C, wmOperator *op)
wmOperatorStatus WM_gesture_circle_modal(bContext *C, wmOperator *op, const wmEvent *event)
wmOperatorStatus WM_gesture_lasso_invoke(bContext *C, wmOperator *op, const wmEvent *event)
void WM_operator_properties_gesture_box(wmOperatorType *ot)
void WM_operator_properties_select_operation_simple(wmOperatorType *ot)
void WM_operator_properties_border_to_rctf(wmOperator *op, rctf *r_rect)
void WM_operator_properties_generic_select(wmOperatorType *ot)
void WM_operator_properties_gesture_lasso(wmOperatorType *ot)
void WM_operator_properties_gesture_circle(wmOperatorType *ot)
void WM_operator_properties_select_all(wmOperatorType *ot)
void WM_operator_properties_mouse_select(wmOperatorType *ot)
wmOperatorStatus WM_operator_flag_only_pass_through_on_press(wmOperatorStatus retval, const wmEvent *event)
wmOperatorStatus WM_menu_invoke(bContext *C, wmOperator *op, const wmEvent *)
wmOperatorStatus WM_generic_select_modal(bContext *C, wmOperator *op, const wmEvent *event)
wmOperatorStatus WM_generic_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)