16fd8414f22db0938612ef176a0efd327a9bf9ab
[blender.git] / source / blender / editors / mask / mask_select.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2012 Blender Foundation.
19  * All rights reserved.
20  *
21  *
22  * Contributor(s): Blender Foundation,
23  *                 Sergey Sharybin
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27
28 /** \file blender/editors/mask/mask_select.c
29  *  \ingroup edmask
30  */
31
32 #include "MEM_guardedalloc.h"
33
34 #include "BLI_utildefines.h"
35 #include "BLI_rect.h"
36 #include "BLI_lasso.h"
37 #include "BLI_math.h"
38
39 #include "BKE_context.h"
40 #include "BKE_mask.h"
41
42 #include "DNA_mask_types.h"
43 #include "DNA_object_types.h"  /* SELECT */
44
45 #include "WM_api.h"
46 #include "WM_types.h"
47
48 #include "ED_screen.h"
49 #include "ED_clip.h"
50 #include "ED_mask.h"  /* own include */
51
52 #include "RNA_access.h"
53 #include "RNA_define.h"
54
55 #include "mask_intern.h"  /* own include */
56
57 /* 'check' select */
58 bool ED_mask_spline_select_check(MaskSpline *spline)
59 {
60         int i;
61
62         for (i = 0; i < spline->tot_point; i++) {
63                 MaskSplinePoint *point = &spline->points[i];
64
65                 if (MASKPOINT_ISSEL_ANY(point))
66                         return TRUE;
67         }
68
69         return FALSE;
70 }
71
72 bool ED_mask_layer_select_check(MaskLayer *masklay)
73 {
74         MaskSpline *spline;
75
76         if (masklay->restrictflag & (MASK_RESTRICT_VIEW | MASK_RESTRICT_SELECT)) {
77                 return FALSE;
78         }
79
80         for (spline = masklay->splines.first; spline; spline = spline->next) {
81                 if (ED_mask_spline_select_check(spline)) {
82                         return TRUE;
83                 }
84         }
85
86         return FALSE;
87 }
88
89 bool ED_mask_select_check(Mask *mask)
90 {
91         MaskLayer *masklay;
92
93         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
94                 if (ED_mask_layer_select_check(masklay)) {
95                         return TRUE;
96                 }
97         }
98
99         return FALSE;
100 }
101
102 /* 'sel' select  */
103 void ED_mask_spline_select_set(MaskSpline *spline, const short do_select)
104 {
105         int i;
106
107         if (do_select)
108                 spline->flag |= SELECT;
109         else
110                 spline->flag &= ~SELECT;
111
112         for (i = 0; i < spline->tot_point; i++) {
113                 MaskSplinePoint *point = &spline->points[i];
114
115                 BKE_mask_point_select_set(point, do_select);
116         }
117 }
118
119 void ED_mask_layer_select_set(MaskLayer *masklay, const short do_select)
120 {
121         MaskSpline *spline;
122
123         if (masklay->restrictflag & MASK_RESTRICT_SELECT) {
124                 if (do_select == TRUE) {
125                         return;
126                 }
127         }
128
129         for (spline = masklay->splines.first; spline; spline = spline->next) {
130                 ED_mask_spline_select_set(spline, do_select);
131         }
132 }
133
134 void ED_mask_select_toggle_all(Mask *mask, int action)
135 {
136         MaskLayer *masklay;
137
138         if (action == SEL_TOGGLE) {
139                 if (ED_mask_select_check(mask))
140                         action = SEL_DESELECT;
141                 else
142                         action = SEL_SELECT;
143         }
144
145         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
146
147                 if (masklay->restrictflag & MASK_RESTRICT_VIEW) {
148                         continue;
149                 }
150
151                 if (action == SEL_INVERT) {
152                         /* we don't have generic functions for this, its restricted to this operator
153                          * if one day we need to re-use such functionality, they can be split out */
154
155                         MaskSpline *spline;
156                         if (masklay->restrictflag & MASK_RESTRICT_SELECT) {
157                                 continue;
158                         }
159                         for (spline = masklay->splines.first; spline; spline = spline->next) {
160                                 int i;
161                                 for (i = 0; i < spline->tot_point; i++) {
162                                         MaskSplinePoint *point = &spline->points[i];
163                                         BKE_mask_point_select_set(point, !MASKPOINT_ISSEL_ANY(point));
164                                 }
165                         }
166
167                 }
168                 else {
169                         ED_mask_layer_select_set(masklay, (action == SEL_SELECT) ? TRUE : FALSE);
170                 }
171         }
172 }
173
174 void ED_mask_select_flush_all(Mask *mask)
175 {
176         MaskLayer *masklay;
177
178         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
179                 MaskSpline *spline;
180
181                 for (spline = masklay->splines.first; spline; spline = spline->next) {
182                         int i;
183
184                         spline->flag &= ~SELECT;
185
186                         /* intentionally _dont_ do this in the masklay loop
187                          * so we clear flags on all splines */
188                         if (masklay->restrictflag & MASK_RESTRICT_VIEW) {
189                                 continue;
190                         }
191
192                         for (i = 0; i < spline->tot_point; i++) {
193                                 MaskSplinePoint *cur_point = &spline->points[i];
194
195                                 if (MASKPOINT_ISSEL_ANY(cur_point)) {
196                                         spline->flag |= SELECT;
197                                 }
198                                 else {
199                                         int j;
200
201                                         for (j = 0; j < cur_point->tot_uw; j++) {
202                                                 if (cur_point->uw[j].flag & SELECT) {
203                                                         spline->flag |= SELECT;
204                                                         break;
205                                                 }
206                                         }
207                                 }
208                         }
209                 }
210         }
211 }
212
213 /******************** toggle selection *********************/
214
215 static int select_all_exec(bContext *C, wmOperator *op)
216 {
217         Mask *mask = CTX_data_edit_mask(C);
218         int action = RNA_enum_get(op->ptr, "action");
219
220         ED_mask_select_toggle_all(mask, action);
221         ED_mask_select_flush_all(mask);
222
223         WM_event_add_notifier(C, NC_MASK | ND_SELECT, mask);
224
225         return OPERATOR_FINISHED;
226 }
227
228 void MASK_OT_select_all(wmOperatorType *ot)
229 {
230         /* identifiers */
231         ot->name = "(De)select All";
232         ot->description = "Change selection of all curve points";
233         ot->idname = "MASK_OT_select_all";
234
235         /* api callbacks */
236         ot->exec = select_all_exec;
237         ot->poll = ED_maskedit_mask_poll;
238
239         /* flags */
240         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
241
242         /* properties */
243         WM_operator_properties_select_all(ot);
244 }
245
246 /******************** select *********************/
247
248 static int select_exec(bContext *C, wmOperator *op)
249 {
250         Mask *mask = CTX_data_edit_mask(C);
251         MaskLayer *masklay;
252         MaskSpline *spline;
253         MaskSplinePoint *point = NULL;
254         float co[2];
255         bool extend = RNA_boolean_get(op->ptr, "extend");
256         bool deselect = RNA_boolean_get(op->ptr, "deselect");
257         bool toggle = RNA_boolean_get(op->ptr, "toggle");
258
259         bool is_handle = 0;
260         const float threshold = 19;
261
262         RNA_float_get_array(op->ptr, "location", co);
263
264         point = ED_mask_point_find_nearest(C, mask, co, threshold, &masklay, &spline, &is_handle, NULL);
265
266         if (extend == false && deselect == false && toggle == false)
267                 ED_mask_select_toggle_all(mask, SEL_DESELECT);
268
269         if (point) {
270
271                 if (is_handle) {
272                         if (extend) {
273                                 masklay->act_spline = spline;
274                                 masklay->act_point = point;
275
276                                 BKE_mask_point_select_set_handle(point, TRUE);
277                         }
278                         else if (deselect) {
279                                 BKE_mask_point_select_set_handle(point, FALSE);
280                         }
281                         else {
282                                 masklay->act_spline = spline;
283                                 masklay->act_point = point;
284
285                                 if (!MASKPOINT_ISSEL_HANDLE(point)) {
286                                         BKE_mask_point_select_set_handle(point, TRUE);
287                                 }
288                                 else if (toggle) {
289                                         BKE_mask_point_select_set_handle(point, FALSE);
290                                 }
291                         }
292                 }
293                 else {
294                         if (extend) {
295                                 masklay->act_spline = spline;
296                                 masklay->act_point = point;
297
298                                 BKE_mask_point_select_set(point, TRUE);
299                         }
300                         else if (deselect) {
301                                 BKE_mask_point_select_set(point, FALSE);
302                         }
303                         else {
304                                 masklay->act_spline = spline;
305                                 masklay->act_point = point;
306
307                                 if (!MASKPOINT_ISSEL_ANY(point)) {
308                                         BKE_mask_point_select_set(point, TRUE);
309                                 }
310                                 else if (toggle) {
311                                         BKE_mask_point_select_set(point, FALSE);
312                                 }
313                         }
314                 }
315
316                 masklay->act_spline = spline;
317                 masklay->act_point = point;
318
319                 ED_mask_select_flush_all(mask);
320
321                 WM_event_add_notifier(C, NC_MASK | ND_SELECT, mask);
322
323                 return OPERATOR_FINISHED;
324         }
325         else {
326                 MaskSplinePointUW *uw;
327
328                 if (ED_mask_feather_find_nearest(C, mask, co, threshold, &masklay, &spline, &point, &uw, NULL)) {
329
330                         if (extend) {
331                                 masklay->act_spline = spline;
332                                 masklay->act_point = point;
333
334                                 if (uw) uw->flag |= SELECT;
335                         }
336                         else if (deselect) {
337                                 if (uw) uw->flag &= ~SELECT;
338                         }
339                         else {
340                                 masklay->act_spline = spline;
341                                 masklay->act_point = point;
342
343                                 if (uw) {
344                                         if (!(uw->flag & SELECT)) {
345                                                 uw->flag |= SELECT;
346                                         }
347                                         else if (toggle) {
348                                                 uw->flag &= ~SELECT;
349                                         }
350                                 }
351                         }
352
353                         ED_mask_select_flush_all(mask);
354
355                         WM_event_add_notifier(C, NC_MASK | ND_SELECT, mask);
356
357                         return OPERATOR_FINISHED;
358                 }
359         }
360
361         return OPERATOR_PASS_THROUGH;
362 }
363
364 static int select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
365 {
366         ScrArea *sa = CTX_wm_area(C);
367         ARegion *ar = CTX_wm_region(C);
368
369         float co[2];
370
371         ED_mask_mouse_pos(sa, ar, event->mval, co);
372
373         RNA_float_set_array(op->ptr, "location", co);
374
375         return select_exec(C, op);
376 }
377
378 void MASK_OT_select(wmOperatorType *ot)
379 {
380         /* identifiers */
381         ot->name = "Select";
382         ot->description = "Select spline points";
383         ot->idname = "MASK_OT_select";
384
385         /* api callbacks */
386         ot->exec = select_exec;
387         ot->invoke = select_invoke;
388         ot->poll = ED_maskedit_mask_poll;
389
390         /* flags */
391         ot->flag = OPTYPE_UNDO;
392
393         /* properties */
394         WM_operator_properties_mouse_select(ot);
395
396         RNA_def_float_vector(ot->srna, "location", 2, NULL, -FLT_MAX, FLT_MAX,
397                              "Location", "Location of vertex in normalized space", -1.0f, 1.0f);
398 }
399
400
401
402 /********************** border select operator *********************/
403
404 static int border_select_exec(bContext *C, wmOperator *op)
405 {
406         ScrArea *sa = CTX_wm_area(C);
407         ARegion *ar = CTX_wm_region(C);
408
409         Mask *mask = CTX_data_edit_mask(C);
410         MaskLayer *masklay;
411         int i;
412
413         rcti rect;
414         rctf rectf;
415         int mode;
416         bool changed = false, extend;
417
418         /* get rectangle from operator */
419         WM_operator_properties_border_to_rcti(op, &rect);
420
421         ED_mask_point_pos(sa, ar, rect.xmin, rect.ymin, &rectf.xmin, &rectf.ymin);
422         ED_mask_point_pos(sa, ar, rect.xmax, rect.ymax, &rectf.xmax, &rectf.ymax);
423
424         mode = RNA_int_get(op->ptr, "gesture_mode");
425         extend = RNA_boolean_get(op->ptr, "extend");
426
427         /* do actual selection */
428         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
429                 MaskSpline *spline;
430
431                 if (masklay->restrictflag & (MASK_RESTRICT_VIEW | MASK_RESTRICT_SELECT)) {
432                         continue;
433                 }
434
435                 for (spline = masklay->splines.first; spline; spline = spline->next) {
436                         MaskSplinePoint *points_array = BKE_mask_spline_point_array(spline);
437
438                         for (i = 0; i < spline->tot_point; i++) {
439                                 MaskSplinePoint *point = &spline->points[i];
440                                 MaskSplinePoint *point_deform = &points_array[i];
441
442                                 /* TODO: handles? */
443                                 /* TODO: uw? */
444
445                                 if (BLI_rctf_isect_pt_v(&rectf, point_deform->bezt.vec[1])) {
446                                         BKE_mask_point_select_set(point, mode == GESTURE_MODAL_SELECT);
447                                         BKE_mask_point_select_set_handle(point, mode == GESTURE_MODAL_SELECT);
448                                 }
449                                 else if (!extend) {
450                                         BKE_mask_point_select_set(point, FALSE);
451                                         BKE_mask_point_select_set_handle(point, FALSE);
452                                 }
453
454                                 changed = true;
455                         }
456                 }
457         }
458
459         if (changed) {
460                 ED_mask_select_flush_all(mask);
461
462                 WM_event_add_notifier(C, NC_MASK | ND_SELECT, mask);
463
464                 return OPERATOR_FINISHED;
465         }
466
467         return OPERATOR_CANCELLED;
468 }
469
470 void MASK_OT_select_border(wmOperatorType *ot)
471 {
472         /* identifiers */
473         ot->name = "Border Select";
474         ot->description = "Select curve points using border selection";
475         ot->idname = "MASK_OT_select_border";
476
477         /* api callbacks */
478         ot->invoke = WM_border_select_invoke;
479         ot->exec = border_select_exec;
480         ot->modal = WM_border_select_modal;
481         ot->poll = ED_maskedit_mask_poll;
482
483         /* flags */
484         ot->flag = OPTYPE_UNDO;
485
486         /* properties */
487         WM_operator_properties_gesture_border(ot, TRUE);
488 }
489
490 static bool do_lasso_select_mask(bContext *C, const int mcords[][2], short moves, short select)
491 {
492         ScrArea *sa = CTX_wm_area(C);
493         ARegion *ar = CTX_wm_region(C);
494
495         Mask *mask = CTX_data_edit_mask(C);
496         MaskLayer *masklay;
497         int i;
498
499         rcti rect;
500         bool changed = false;
501
502         /* get rectangle from operator */
503         BLI_lasso_boundbox(&rect, mcords, moves);
504
505         /* do actual selection */
506         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
507                 MaskSpline *spline;
508
509                 if (masklay->restrictflag & (MASK_RESTRICT_VIEW | MASK_RESTRICT_SELECT)) {
510                         continue;
511                 }
512
513                 for (spline = masklay->splines.first; spline; spline = spline->next) {
514                         MaskSplinePoint *points_array = BKE_mask_spline_point_array(spline);
515
516                         for (i = 0; i < spline->tot_point; i++) {
517                                 MaskSplinePoint *point = &spline->points[i];
518                                 MaskSplinePoint *point_deform = &points_array[i];
519
520                                 /* TODO: handles? */
521                                 /* TODO: uw? */
522
523                                 float screen_co[2];
524
525                                 /* point in screen coords */
526                                 ED_mask_point_pos__reverse(sa, ar,
527                                                            point_deform->bezt.vec[1][0], point_deform->bezt.vec[1][1],
528                                                            &screen_co[0], &screen_co[1]);
529
530                                 if (BLI_rcti_isect_pt(&rect, screen_co[0], screen_co[1]) &&
531                                     BLI_lasso_is_point_inside(mcords, moves, screen_co[0], screen_co[1], INT_MAX))
532                                 {
533                                         BKE_mask_point_select_set(point, select);
534                                         BKE_mask_point_select_set_handle(point, select);
535                                 }
536
537                                 changed = true;
538                         }
539                 }
540         }
541
542         if (changed) {
543                 ED_mask_select_flush_all(mask);
544
545                 WM_event_add_notifier(C, NC_MASK | ND_SELECT, mask);
546         }
547
548         return changed;
549 }
550
551 static int clip_lasso_select_exec(bContext *C, wmOperator *op)
552 {
553         int mcords_tot;
554         const int (*mcords)[2] = WM_gesture_lasso_path_to_array(C, op, &mcords_tot);
555
556         if (mcords) {
557                 short select;
558
559                 select = !RNA_boolean_get(op->ptr, "deselect");
560                 do_lasso_select_mask(C, mcords, mcords_tot, select);
561
562                 MEM_freeN((void *)mcords);
563
564                 return OPERATOR_FINISHED;
565         }
566         return OPERATOR_PASS_THROUGH;
567 }
568
569 void MASK_OT_select_lasso(wmOperatorType *ot)
570 {
571         /* identifiers */
572         ot->name = "Lasso Select";
573         ot->description = "Select curve points using lasso selection";
574         ot->idname = "MASK_OT_select_lasso";
575
576         /* api callbacks */
577         ot->invoke = WM_gesture_lasso_invoke;
578         ot->modal = WM_gesture_lasso_modal;
579         ot->exec = clip_lasso_select_exec;
580         ot->poll = ED_maskedit_mask_poll;
581         ot->cancel = WM_gesture_lasso_cancel;
582
583         /* flags */
584         ot->flag = OPTYPE_UNDO;
585
586         /* properties */
587         RNA_def_collection_runtime(ot->srna, "path", &RNA_OperatorMousePath, "Path", "");
588         RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", "Deselect rather than select items");
589         RNA_def_boolean(ot->srna, "extend", 1, "Extend", "Extend selection instead of deselecting everything first");
590 }
591
592 /********************** circle select operator *********************/
593
594 static int mask_spline_point_inside_ellipse(BezTriple *bezt, const float offset[2], const float ellipse[2])
595 {
596         /* normalized ellipse: ell[0] = scaleX, ell[1] = scaleY */
597         float x, y;
598
599         x = (bezt->vec[1][0] - offset[0]) * ellipse[0];
600         y = (bezt->vec[1][1] - offset[1]) * ellipse[1];
601
602         return x * x + y * y < 1.0f;
603 }
604
605 static int circle_select_exec(bContext *C, wmOperator *op)
606 {
607         ScrArea *sa = CTX_wm_area(C);
608         ARegion *ar = CTX_wm_region(C);
609
610         Mask *mask = CTX_data_edit_mask(C);
611         MaskLayer *masklay;
612         int i;
613
614         float zoomx, zoomy, offset[2], ellipse[2];
615         int x, y, radius, width, height, mode;
616         bool changed = false;
617
618         /* get operator properties */
619         x = RNA_int_get(op->ptr, "x");
620         y = RNA_int_get(op->ptr, "y");
621         radius = RNA_int_get(op->ptr, "radius");
622
623         mode = RNA_int_get(op->ptr, "gesture_mode");
624
625         /* compute ellipse and position in unified coordinates */
626         ED_mask_get_size(sa, &width, &height);
627         ED_mask_zoom(sa, ar, &zoomx, &zoomy);
628         width = height = max_ii(width, height);
629
630         ellipse[0] = width * zoomx / radius;
631         ellipse[1] = height * zoomy / radius;
632
633         ED_mask_point_pos(sa, ar, x, y, &offset[0], &offset[1]);
634
635         /* do actual selection */
636         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
637                 MaskSpline *spline;
638
639                 if (masklay->restrictflag & (MASK_RESTRICT_VIEW | MASK_RESTRICT_SELECT)) {
640                         continue;
641                 }
642
643                 for (spline = masklay->splines.first; spline; spline = spline->next) {
644                         MaskSplinePoint *points_array = BKE_mask_spline_point_array(spline);
645
646                         for (i = 0; i < spline->tot_point; i++) {
647                                 MaskSplinePoint *point = &spline->points[i];
648                                 MaskSplinePoint *point_deform = &points_array[i];
649
650                                 if (mask_spline_point_inside_ellipse(&point_deform->bezt, offset, ellipse)) {
651                                         BKE_mask_point_select_set(point, mode == GESTURE_MODAL_SELECT);
652                                         BKE_mask_point_select_set_handle(point, mode == GESTURE_MODAL_SELECT);
653
654                                         changed = true;
655                                 }
656                         }
657                 }
658         }
659
660         if (changed) {
661                 ED_mask_select_flush_all(mask);
662
663                 WM_event_add_notifier(C, NC_MASK | ND_SELECT, mask);
664
665                 return OPERATOR_FINISHED;
666         }
667
668         return OPERATOR_CANCELLED;
669 }
670
671 void MASK_OT_select_circle(wmOperatorType *ot)
672 {
673         /* identifiers */
674         ot->name = "Circle Select";
675         ot->description = "Select curve points using circle selection";
676         ot->idname = "MASK_OT_select_circle";
677
678         /* api callbacks */
679         ot->invoke = WM_gesture_circle_invoke;
680         ot->modal = WM_gesture_circle_modal;
681         ot->exec = circle_select_exec;
682         ot->poll = ED_maskedit_mask_poll;
683
684         /* flags */
685         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
686
687         /* properties */
688         RNA_def_int(ot->srna, "x", 0, INT_MIN, INT_MAX, "X", "", INT_MIN, INT_MAX);
689         RNA_def_int(ot->srna, "y", 0, INT_MIN, INT_MAX, "Y", "", INT_MIN, INT_MAX);
690         RNA_def_int(ot->srna, "radius", 0, INT_MIN, INT_MAX, "Radius", "", INT_MIN, INT_MAX);
691         RNA_def_int(ot->srna, "gesture_mode", 0, INT_MIN, INT_MAX, "Gesture Mode", "", INT_MIN, INT_MAX);
692 }
693
694 static int mask_select_linked_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
695 {
696         ScrArea *sa = CTX_wm_area(C);
697         ARegion *ar = CTX_wm_region(C);
698
699         Mask *mask = CTX_data_edit_mask(C);
700         MaskLayer *masklay;
701         MaskSpline *spline;
702         MaskSplinePoint *point = NULL;
703         float co[2];
704         int do_select = !RNA_boolean_get(op->ptr, "deselect");
705
706         bool is_handle = false;
707         const float threshold = 19;
708         bool changed = false;
709
710         ED_mask_mouse_pos(sa, ar, event->mval, co);
711
712         point = ED_mask_point_find_nearest(C, mask, co, threshold, &masklay, &spline, &is_handle, NULL);
713
714         if (point) {
715                 ED_mask_spline_select_set(spline, do_select);
716                 masklay->act_spline = spline;
717                 masklay->act_point = point;
718
719                 changed = true;
720         }
721
722         if (changed) {
723                 ED_mask_select_flush_all(mask);
724
725                 WM_event_add_notifier(C, NC_MASK | ND_SELECT, mask);
726
727                 return OPERATOR_FINISHED;
728         }
729
730         return OPERATOR_CANCELLED;
731 }
732
733 void MASK_OT_select_linked_pick(wmOperatorType *ot)
734 {
735         /* identifiers */
736         ot->name = "Select Linked";
737         ot->idname = "MASK_OT_select_linked_pick";
738         ot->description = "(De)select all points linked to the curve under the mouse cursor";
739
740         /* api callbacks */
741         ot->invoke = mask_select_linked_pick_invoke;
742         ot->poll = ED_maskedit_mask_poll;
743
744         /* flags */
745         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
746
747         RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", "");
748 }
749
750 static int mask_select_linked_exec(bContext *C, wmOperator *UNUSED(op))
751 {
752         Mask *mask = CTX_data_edit_mask(C);
753         MaskLayer *masklay;
754
755         bool changed = false;
756
757         /* do actual selection */
758         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
759                 MaskSpline *spline;
760
761                 if (masklay->restrictflag & (MASK_RESTRICT_VIEW | MASK_RESTRICT_SELECT)) {
762                         continue;
763                 }
764
765                 for (spline = masklay->splines.first; spline; spline = spline->next) {
766                         if (ED_mask_spline_select_check(spline)) {
767                                 ED_mask_spline_select_set(spline, TRUE);
768                                 changed = true;
769                         }
770                 }
771         }
772
773         if (changed) {
774                 ED_mask_select_flush_all(mask);
775
776                 WM_event_add_notifier(C, NC_MASK | ND_SELECT, mask);
777
778                 return OPERATOR_FINISHED;
779         }
780
781         return OPERATOR_CANCELLED;
782 }
783
784 void MASK_OT_select_linked(wmOperatorType *ot)
785 {
786         /* identifiers */
787         ot->name = "Select Linked All";
788         ot->idname = "MASK_OT_select_linked";
789         ot->description = "Select all curve points linked to already selected ones";
790
791         /* api callbacks */
792         ot->exec = mask_select_linked_exec;
793         ot->poll = ED_maskedit_mask_poll;
794
795         /* flags */
796         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
797 }
798
799 /**************** Select more/less **************/
800
801 static int mask_select_more_less(bContext *C, bool more)
802 {
803         Mask *mask = CTX_data_edit_mask(C);
804         MaskLayer *masklay;
805
806         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
807                 MaskSpline *spline;
808
809                 if (masklay->restrictflag & (MASK_RESTRICT_VIEW | MASK_RESTRICT_SELECT)) {
810                         continue;
811                 }
812
813                 for (spline = masklay->splines.first; spline; spline = spline->next) {
814                         const bool cyclic = (spline->flag & MASK_SPLINE_CYCLIC) != 0;
815                         bool start_sel, end_sel, prev_sel, cur_sel;
816                         int i;
817
818                         /* reselect point if any handle is selected to make the result more predictable */
819                         for (i = 0; i < spline->tot_point; i++) {
820                                 BKE_mask_point_select_set(spline->points + i, MASKPOINT_ISSEL_ANY(spline->points + i));
821                         }
822
823                         /* select more/less does not affect empty/single point splines */
824                         if (spline->tot_point < 2) {
825                                 continue;
826                         }
827
828                         if (cyclic) {
829                                 start_sel = !!MASKPOINT_ISSEL_KNOT(spline->points);
830                                 end_sel = !!MASKPOINT_ISSEL_KNOT(&spline->points[spline->tot_point - 1]);
831                         }
832                         else {
833                                 start_sel = false;
834                                 end_sel = false;
835                         }
836
837                         for (i = 0; i < spline->tot_point; i++) {
838                                 if (i == 0 && !cyclic) {
839                                         continue;
840                                 }
841
842                                 prev_sel = (i > 0) ? !!MASKPOINT_ISSEL_KNOT(&spline->points[i - 1]) : end_sel;
843                                 cur_sel = !!MASKPOINT_ISSEL_KNOT(&spline->points[i]);
844
845                                 if (cur_sel != more) {
846                                         if (prev_sel == more) {
847                                                 BKE_mask_point_select_set(&spline->points[i], more);
848                                         }
849                                         i++;
850                                 }
851                         }
852
853                         for (i = spline->tot_point - 1; i >= 0; i--) {
854                                 if (i == spline->tot_point - 1 && !cyclic) {
855                                         continue;
856                                 }
857
858                                 prev_sel = (i < spline->tot_point - 1) ? !!MASKPOINT_ISSEL_KNOT(&spline->points[i + 1]) : start_sel;
859                                 cur_sel = !!MASKPOINT_ISSEL_KNOT(&spline->points[i]);
860
861                                 if (cur_sel != more) {
862                                         if (prev_sel == more) {
863                                                 BKE_mask_point_select_set(&spline->points[i], more);
864                                         }
865                                         i--;
866                                 }
867                         }
868                 }
869         }
870
871         WM_event_add_notifier(C, NC_MASK | ND_SELECT, mask);
872
873         return OPERATOR_FINISHED;
874 }
875
876 static int mask_select_more_exec(bContext *C, wmOperator *UNUSED(op))
877 {
878         return mask_select_more_less(C, true);
879 }
880
881 void MASK_OT_select_more(wmOperatorType *ot)
882 {
883         /* identifiers */
884         ot->name = "Select More";
885         ot->idname = "MASK_OT_select_more";
886         ot->description = "Select more spline points connected to initial selection";
887
888         /* api callbacks */
889         ot->exec = mask_select_more_exec;
890         ot->poll = ED_maskedit_mask_poll;
891
892         /* flags */
893         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
894 }
895
896 static int mask_select_less_exec(bContext *C, wmOperator *UNUSED(op))
897 {
898         return mask_select_more_less(C, false);
899 }
900
901 void MASK_OT_select_less(wmOperatorType *ot)
902 {
903         /* identifiers */
904         ot->name = "Select Less";
905         ot->idname = "MASK_OT_select_less";
906         ot->description = "Deselect spline points at the boundary of each selection region";
907
908         /* api callbacks */
909         ot->exec = mask_select_less_exec;
910         ot->poll = ED_maskedit_mask_poll;
911
912         /* flags */
913         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
914 }