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