Implement asymmetric and free handles type for masks
[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         eMaskWhichHandle which_handle;
259         const float threshold = 19;
260
261         RNA_float_get_array(op->ptr, "location", co);
262
263         point = ED_mask_point_find_nearest(C, mask, co, threshold, &masklay, &spline, &which_handle, NULL);
264
265         if (extend == false && deselect == false && toggle == false)
266                 ED_mask_select_toggle_all(mask, SEL_DESELECT);
267
268         if (point) {
269                 if (which_handle != MASK_WHICH_HANDLE_NONE) {
270                         if (extend) {
271                                 masklay->act_spline = spline;
272                                 masklay->act_point = point;
273
274                                 BKE_mask_point_select_set_handle(point, which_handle, TRUE);
275                         }
276                         else if (deselect) {
277                                 BKE_mask_point_select_set_handle(point, which_handle, FALSE);
278                         }
279                         else {
280                                 masklay->act_spline = spline;
281                                 masklay->act_point = point;
282
283                                 if (!MASKPOINT_ISSEL_HANDLE(point, which_handle)) {
284                                         BKE_mask_point_select_set_handle(point, which_handle, TRUE);
285                                 }
286                                 else if (toggle) {
287                                         BKE_mask_point_select_set_handle(point, which_handle, FALSE);
288                                 }
289                         }
290                 }
291                 else {
292                         if (extend) {
293                                 masklay->act_spline = spline;
294                                 masklay->act_point = point;
295
296                                 BKE_mask_point_select_set(point, TRUE);
297                         }
298                         else if (deselect) {
299                                 BKE_mask_point_select_set(point, FALSE);
300                         }
301                         else {
302                                 masklay->act_spline = spline;
303                                 masklay->act_point = point;
304
305                                 if (!MASKPOINT_ISSEL_ANY(point)) {
306                                         BKE_mask_point_select_set(point, TRUE);
307                                 }
308                                 else if (toggle) {
309                                         BKE_mask_point_select_set(point, FALSE);
310                                 }
311                         }
312                 }
313
314                 masklay->act_spline = spline;
315                 masklay->act_point = point;
316
317                 ED_mask_select_flush_all(mask);
318
319                 WM_event_add_notifier(C, NC_MASK | ND_SELECT, mask);
320
321                 return OPERATOR_FINISHED;
322         }
323         else {
324                 MaskSplinePointUW *uw;
325
326                 if (ED_mask_feather_find_nearest(C, mask, co, threshold, &masklay, &spline, &point, &uw, NULL)) {
327
328                         if (extend) {
329                                 masklay->act_spline = spline;
330                                 masklay->act_point = point;
331
332                                 if (uw) uw->flag |= SELECT;
333                         }
334                         else if (deselect) {
335                                 if (uw) uw->flag &= ~SELECT;
336                         }
337                         else {
338                                 masklay->act_spline = spline;
339                                 masklay->act_point = point;
340
341                                 if (uw) {
342                                         if (!(uw->flag & SELECT)) {
343                                                 uw->flag |= SELECT;
344                                         }
345                                         else if (toggle) {
346                                                 uw->flag &= ~SELECT;
347                                         }
348                                 }
349                         }
350
351                         ED_mask_select_flush_all(mask);
352
353                         WM_event_add_notifier(C, NC_MASK | ND_SELECT, mask);
354
355                         return OPERATOR_FINISHED;
356                 }
357         }
358
359         return OPERATOR_PASS_THROUGH;
360 }
361
362 static int select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
363 {
364         ScrArea *sa = CTX_wm_area(C);
365         ARegion *ar = CTX_wm_region(C);
366
367         float co[2];
368
369         ED_mask_mouse_pos(sa, ar, event->mval, co);
370
371         RNA_float_set_array(op->ptr, "location", co);
372
373         return select_exec(C, op);
374 }
375
376 void MASK_OT_select(wmOperatorType *ot)
377 {
378         /* identifiers */
379         ot->name = "Select";
380         ot->description = "Select spline points";
381         ot->idname = "MASK_OT_select";
382
383         /* api callbacks */
384         ot->exec = select_exec;
385         ot->invoke = select_invoke;
386         ot->poll = ED_maskedit_mask_poll;
387
388         /* flags */
389         ot->flag = OPTYPE_UNDO;
390
391         /* properties */
392         WM_operator_properties_mouse_select(ot);
393
394         RNA_def_float_vector(ot->srna, "location", 2, NULL, -FLT_MAX, FLT_MAX,
395                              "Location", "Location of vertex in normalized space", -1.0f, 1.0f);
396 }
397
398
399
400 /********************** border select operator *********************/
401
402 static int border_select_exec(bContext *C, wmOperator *op)
403 {
404         ScrArea *sa = CTX_wm_area(C);
405         ARegion *ar = CTX_wm_region(C);
406
407         Mask *mask = CTX_data_edit_mask(C);
408         MaskLayer *masklay;
409         int i;
410
411         rcti rect;
412         rctf rectf;
413         int mode;
414         bool changed = false, extend;
415
416         /* get rectangle from operator */
417         WM_operator_properties_border_to_rcti(op, &rect);
418
419         ED_mask_point_pos(sa, ar, rect.xmin, rect.ymin, &rectf.xmin, &rectf.ymin);
420         ED_mask_point_pos(sa, ar, rect.xmax, rect.ymax, &rectf.xmax, &rectf.ymax);
421
422         mode = RNA_int_get(op->ptr, "gesture_mode");
423         extend = RNA_boolean_get(op->ptr, "extend");
424
425         /* do actual selection */
426         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
427                 MaskSpline *spline;
428
429                 if (masklay->restrictflag & (MASK_RESTRICT_VIEW | MASK_RESTRICT_SELECT)) {
430                         continue;
431                 }
432
433                 for (spline = masklay->splines.first; spline; spline = spline->next) {
434                         MaskSplinePoint *points_array = BKE_mask_spline_point_array(spline);
435
436                         for (i = 0; i < spline->tot_point; i++) {
437                                 MaskSplinePoint *point = &spline->points[i];
438                                 MaskSplinePoint *point_deform = &points_array[i];
439
440                                 /* TODO: handles? */
441                                 /* TODO: uw? */
442
443                                 if (BLI_rctf_isect_pt_v(&rectf, point_deform->bezt.vec[1])) {
444                                         BKE_mask_point_select_set(point, mode == GESTURE_MODAL_SELECT);
445                                         BKE_mask_point_select_set_handle(point, MASK_WHICH_HANDLE_BOTH, mode == GESTURE_MODAL_SELECT);
446                                 }
447                                 else if (!extend) {
448                                         BKE_mask_point_select_set(point, FALSE);
449                                         BKE_mask_point_select_set_handle(point, MASK_WHICH_HANDLE_BOTH, FALSE);
450                                 }
451
452                                 changed = true;
453                         }
454                 }
455         }
456
457         if (changed) {
458                 ED_mask_select_flush_all(mask);
459
460                 WM_event_add_notifier(C, NC_MASK | ND_SELECT, mask);
461
462                 return OPERATOR_FINISHED;
463         }
464
465         return OPERATOR_CANCELLED;
466 }
467
468 void MASK_OT_select_border(wmOperatorType *ot)
469 {
470         /* identifiers */
471         ot->name = "Border Select";
472         ot->description = "Select curve points using border selection";
473         ot->idname = "MASK_OT_select_border";
474
475         /* api callbacks */
476         ot->invoke = WM_border_select_invoke;
477         ot->exec = border_select_exec;
478         ot->modal = WM_border_select_modal;
479         ot->poll = ED_maskedit_mask_poll;
480
481         /* flags */
482         ot->flag = OPTYPE_UNDO;
483
484         /* properties */
485         WM_operator_properties_gesture_border(ot, TRUE);
486 }
487
488 static bool do_lasso_select_mask(bContext *C, const int mcords[][2], short moves, short select)
489 {
490         ScrArea *sa = CTX_wm_area(C);
491         ARegion *ar = CTX_wm_region(C);
492
493         Mask *mask = CTX_data_edit_mask(C);
494         MaskLayer *masklay;
495         int i;
496
497         rcti rect;
498         bool changed = false;
499
500         /* get rectangle from operator */
501         BLI_lasso_boundbox(&rect, mcords, moves);
502
503         /* do actual selection */
504         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
505                 MaskSpline *spline;
506
507                 if (masklay->restrictflag & (MASK_RESTRICT_VIEW | MASK_RESTRICT_SELECT)) {
508                         continue;
509                 }
510
511                 for (spline = masklay->splines.first; spline; spline = spline->next) {
512                         MaskSplinePoint *points_array = BKE_mask_spline_point_array(spline);
513
514                         for (i = 0; i < spline->tot_point; i++) {
515                                 MaskSplinePoint *point = &spline->points[i];
516                                 MaskSplinePoint *point_deform = &points_array[i];
517
518                                 /* TODO: handles? */
519                                 /* TODO: uw? */
520
521                                 float screen_co[2];
522
523                                 /* point in screen coords */
524                                 ED_mask_point_pos__reverse(sa, ar,
525                                                            point_deform->bezt.vec[1][0], point_deform->bezt.vec[1][1],
526                                                            &screen_co[0], &screen_co[1]);
527
528                                 if (BLI_rcti_isect_pt(&rect, screen_co[0], screen_co[1]) &&
529                                     BLI_lasso_is_point_inside(mcords, moves, screen_co[0], screen_co[1], INT_MAX))
530                                 {
531                                         BKE_mask_point_select_set(point, select);
532                                         BKE_mask_point_select_set_handle(point, MASK_WHICH_HANDLE_BOTH, select);
533                                 }
534
535                                 changed = true;
536                         }
537                 }
538         }
539
540         if (changed) {
541                 ED_mask_select_flush_all(mask);
542
543                 WM_event_add_notifier(C, NC_MASK | ND_SELECT, mask);
544         }
545
546         return changed;
547 }
548
549 static int clip_lasso_select_exec(bContext *C, wmOperator *op)
550 {
551         int mcords_tot;
552         const int (*mcords)[2] = WM_gesture_lasso_path_to_array(C, op, &mcords_tot);
553
554         if (mcords) {
555                 short select;
556
557                 select = !RNA_boolean_get(op->ptr, "deselect");
558                 do_lasso_select_mask(C, mcords, mcords_tot, select);
559
560                 MEM_freeN((void *)mcords);
561
562                 return OPERATOR_FINISHED;
563         }
564         return OPERATOR_PASS_THROUGH;
565 }
566
567 void MASK_OT_select_lasso(wmOperatorType *ot)
568 {
569         /* identifiers */
570         ot->name = "Lasso Select";
571         ot->description = "Select curve points using lasso selection";
572         ot->idname = "MASK_OT_select_lasso";
573
574         /* api callbacks */
575         ot->invoke = WM_gesture_lasso_invoke;
576         ot->modal = WM_gesture_lasso_modal;
577         ot->exec = clip_lasso_select_exec;
578         ot->poll = ED_maskedit_mask_poll;
579         ot->cancel = WM_gesture_lasso_cancel;
580
581         /* flags */
582         ot->flag = OPTYPE_UNDO;
583
584         /* properties */
585         RNA_def_collection_runtime(ot->srna, "path", &RNA_OperatorMousePath, "Path", "");
586         RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", "Deselect rather than select items");
587         RNA_def_boolean(ot->srna, "extend", 1, "Extend", "Extend selection instead of deselecting everything first");
588 }
589
590 /********************** circle select operator *********************/
591
592 static int mask_spline_point_inside_ellipse(BezTriple *bezt, const float offset[2], const float ellipse[2])
593 {
594         /* normalized ellipse: ell[0] = scaleX, ell[1] = scaleY */
595         float x, y;
596
597         x = (bezt->vec[1][0] - offset[0]) * ellipse[0];
598         y = (bezt->vec[1][1] - offset[1]) * ellipse[1];
599
600         return x * x + y * y < 1.0f;
601 }
602
603 static int circle_select_exec(bContext *C, wmOperator *op)
604 {
605         ScrArea *sa = CTX_wm_area(C);
606         ARegion *ar = CTX_wm_region(C);
607
608         Mask *mask = CTX_data_edit_mask(C);
609         MaskLayer *masklay;
610         int i;
611
612         float zoomx, zoomy, offset[2], ellipse[2];
613         int x, y, radius, width, height, mode;
614         bool changed = false;
615
616         /* get operator properties */
617         x = RNA_int_get(op->ptr, "x");
618         y = RNA_int_get(op->ptr, "y");
619         radius = RNA_int_get(op->ptr, "radius");
620
621         mode = RNA_int_get(op->ptr, "gesture_mode");
622
623         /* compute ellipse and position in unified coordinates */
624         ED_mask_get_size(sa, &width, &height);
625         ED_mask_zoom(sa, ar, &zoomx, &zoomy);
626         width = height = max_ii(width, height);
627
628         ellipse[0] = width * zoomx / radius;
629         ellipse[1] = height * zoomy / radius;
630
631         ED_mask_point_pos(sa, ar, x, y, &offset[0], &offset[1]);
632
633         /* do actual selection */
634         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
635                 MaskSpline *spline;
636
637                 if (masklay->restrictflag & (MASK_RESTRICT_VIEW | MASK_RESTRICT_SELECT)) {
638                         continue;
639                 }
640
641                 for (spline = masklay->splines.first; spline; spline = spline->next) {
642                         MaskSplinePoint *points_array = BKE_mask_spline_point_array(spline);
643
644                         for (i = 0; i < spline->tot_point; i++) {
645                                 MaskSplinePoint *point = &spline->points[i];
646                                 MaskSplinePoint *point_deform = &points_array[i];
647
648                                 if (mask_spline_point_inside_ellipse(&point_deform->bezt, offset, ellipse)) {
649                                         BKE_mask_point_select_set(point, mode == GESTURE_MODAL_SELECT);
650                                         BKE_mask_point_select_set_handle(point, MASK_WHICH_HANDLE_BOTH, mode == GESTURE_MODAL_SELECT);
651
652                                         changed = true;
653                                 }
654                         }
655                 }
656         }
657
658         if (changed) {
659                 ED_mask_select_flush_all(mask);
660
661                 WM_event_add_notifier(C, NC_MASK | ND_SELECT, mask);
662
663                 return OPERATOR_FINISHED;
664         }
665
666         return OPERATOR_CANCELLED;
667 }
668
669 void MASK_OT_select_circle(wmOperatorType *ot)
670 {
671         /* identifiers */
672         ot->name = "Circle Select";
673         ot->description = "Select curve points using circle selection";
674         ot->idname = "MASK_OT_select_circle";
675
676         /* api callbacks */
677         ot->invoke = WM_gesture_circle_invoke;
678         ot->modal = WM_gesture_circle_modal;
679         ot->exec = circle_select_exec;
680         ot->poll = ED_maskedit_mask_poll;
681
682         /* flags */
683         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
684
685         /* properties */
686         RNA_def_int(ot->srna, "x", 0, INT_MIN, INT_MAX, "X", "", INT_MIN, INT_MAX);
687         RNA_def_int(ot->srna, "y", 0, INT_MIN, INT_MAX, "Y", "", INT_MIN, INT_MAX);
688         RNA_def_int(ot->srna, "radius", 0, INT_MIN, INT_MAX, "Radius", "", INT_MIN, INT_MAX);
689         RNA_def_int(ot->srna, "gesture_mode", 0, INT_MIN, INT_MAX, "Gesture Mode", "", INT_MIN, INT_MAX);
690 }
691
692 static int mask_select_linked_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
693 {
694         ScrArea *sa = CTX_wm_area(C);
695         ARegion *ar = CTX_wm_region(C);
696
697         Mask *mask = CTX_data_edit_mask(C);
698         MaskLayer *masklay;
699         MaskSpline *spline;
700         MaskSplinePoint *point = NULL;
701         float co[2];
702         int do_select = !RNA_boolean_get(op->ptr, "deselect");
703         const float threshold = 19;
704         bool changed = false;
705
706         ED_mask_mouse_pos(sa, ar, event->mval, co);
707
708         point = ED_mask_point_find_nearest(C, mask, co, threshold, &masklay, &spline, NULL, NULL);
709
710         if (point) {
711                 ED_mask_spline_select_set(spline, do_select);
712                 masklay->act_spline = spline;
713                 masklay->act_point = point;
714
715                 changed = true;
716         }
717
718         if (changed) {
719                 ED_mask_select_flush_all(mask);
720
721                 WM_event_add_notifier(C, NC_MASK | ND_SELECT, mask);
722
723                 return OPERATOR_FINISHED;
724         }
725
726         return OPERATOR_CANCELLED;
727 }
728
729 void MASK_OT_select_linked_pick(wmOperatorType *ot)
730 {
731         /* identifiers */
732         ot->name = "Select Linked";
733         ot->idname = "MASK_OT_select_linked_pick";
734         ot->description = "(De)select all points linked to the curve under the mouse cursor";
735
736         /* api callbacks */
737         ot->invoke = mask_select_linked_pick_invoke;
738         ot->poll = ED_maskedit_mask_poll;
739
740         /* flags */
741         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
742
743         RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", "");
744 }
745
746 static int mask_select_linked_exec(bContext *C, wmOperator *UNUSED(op))
747 {
748         Mask *mask = CTX_data_edit_mask(C);
749         MaskLayer *masklay;
750
751         bool changed = false;
752
753         /* do actual selection */
754         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
755                 MaskSpline *spline;
756
757                 if (masklay->restrictflag & (MASK_RESTRICT_VIEW | MASK_RESTRICT_SELECT)) {
758                         continue;
759                 }
760
761                 for (spline = masklay->splines.first; spline; spline = spline->next) {
762                         if (ED_mask_spline_select_check(spline)) {
763                                 ED_mask_spline_select_set(spline, TRUE);
764                                 changed = true;
765                         }
766                 }
767         }
768
769         if (changed) {
770                 ED_mask_select_flush_all(mask);
771
772                 WM_event_add_notifier(C, NC_MASK | ND_SELECT, mask);
773
774                 return OPERATOR_FINISHED;
775         }
776
777         return OPERATOR_CANCELLED;
778 }
779
780 void MASK_OT_select_linked(wmOperatorType *ot)
781 {
782         /* identifiers */
783         ot->name = "Select Linked All";
784         ot->idname = "MASK_OT_select_linked";
785         ot->description = "Select all curve points linked to already selected ones";
786
787         /* api callbacks */
788         ot->exec = mask_select_linked_exec;
789         ot->poll = ED_maskedit_mask_poll;
790
791         /* flags */
792         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
793 }
794
795 /**************** Select more/less **************/
796
797 static int mask_select_more_less(bContext *C, bool more)
798 {
799         Mask *mask = CTX_data_edit_mask(C);
800         MaskLayer *masklay;
801
802         for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
803                 MaskSpline *spline;
804
805                 if (masklay->restrictflag & (MASK_RESTRICT_VIEW | MASK_RESTRICT_SELECT)) {
806                         continue;
807                 }
808
809                 for (spline = masklay->splines.first; spline; spline = spline->next) {
810                         const bool cyclic = (spline->flag & MASK_SPLINE_CYCLIC) != 0;
811                         bool start_sel, end_sel, prev_sel, cur_sel;
812                         int i;
813
814                         /* reselect point if any handle is selected to make the result more predictable */
815                         for (i = 0; i < spline->tot_point; i++) {
816                                 BKE_mask_point_select_set(spline->points + i, MASKPOINT_ISSEL_ANY(spline->points + i));
817                         }
818
819                         /* select more/less does not affect empty/single point splines */
820                         if (spline->tot_point < 2) {
821                                 continue;
822                         }
823
824                         if (cyclic) {
825                                 start_sel = !!MASKPOINT_ISSEL_KNOT(spline->points);
826                                 end_sel = !!MASKPOINT_ISSEL_KNOT(&spline->points[spline->tot_point - 1]);
827                         }
828                         else {
829                                 start_sel = false;
830                                 end_sel = false;
831                         }
832
833                         for (i = 0; i < spline->tot_point; i++) {
834                                 if (i == 0 && !cyclic) {
835                                         continue;
836                                 }
837
838                                 prev_sel = (i > 0) ? !!MASKPOINT_ISSEL_KNOT(&spline->points[i - 1]) : end_sel;
839                                 cur_sel = !!MASKPOINT_ISSEL_KNOT(&spline->points[i]);
840
841                                 if (cur_sel != more) {
842                                         if (prev_sel == more) {
843                                                 BKE_mask_point_select_set(&spline->points[i], more);
844                                         }
845                                         i++;
846                                 }
847                         }
848
849                         for (i = spline->tot_point - 1; i >= 0; i--) {
850                                 if (i == spline->tot_point - 1 && !cyclic) {
851                                         continue;
852                                 }
853
854                                 prev_sel = (i < spline->tot_point - 1) ? !!MASKPOINT_ISSEL_KNOT(&spline->points[i + 1]) : start_sel;
855                                 cur_sel = !!MASKPOINT_ISSEL_KNOT(&spline->points[i]);
856
857                                 if (cur_sel != more) {
858                                         if (prev_sel == more) {
859                                                 BKE_mask_point_select_set(&spline->points[i], more);
860                                         }
861                                         i--;
862                                 }
863                         }
864                 }
865         }
866
867         WM_event_add_notifier(C, NC_MASK | ND_SELECT, mask);
868
869         return OPERATOR_FINISHED;
870 }
871
872 static int mask_select_more_exec(bContext *C, wmOperator *UNUSED(op))
873 {
874         return mask_select_more_less(C, true);
875 }
876
877 void MASK_OT_select_more(wmOperatorType *ot)
878 {
879         /* identifiers */
880         ot->name = "Select More";
881         ot->idname = "MASK_OT_select_more";
882         ot->description = "Select more spline points connected to initial selection";
883
884         /* api callbacks */
885         ot->exec = mask_select_more_exec;
886         ot->poll = ED_maskedit_mask_poll;
887
888         /* flags */
889         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
890 }
891
892 static int mask_select_less_exec(bContext *C, wmOperator *UNUSED(op))
893 {
894         return mask_select_more_less(C, false);
895 }
896
897 void MASK_OT_select_less(wmOperatorType *ot)
898 {
899         /* identifiers */
900         ot->name = "Select Less";
901         ot->idname = "MASK_OT_select_less";
902         ot->description = "Deselect spline points at the boundary of each selection region";
903
904         /* api callbacks */
905         ot->exec = mask_select_less_exec;
906         ot->poll = ED_maskedit_mask_poll;
907
908         /* flags */
909         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
910 }