Merge branch 'master' into greasepencil-object
[blender.git] / source / blender / editors / gpencil / gpencil_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) 2014, Blender Foundation
17  * This is a new part of Blender
18  */
19
20 /** \file
21  * \ingroup edgpencil
22  */
23
24 #include <stdio.h>
25 #include <string.h>
26 #include <stdlib.h>
27 #include <stddef.h>
28 #include <math.h>
29
30 #include "MEM_guardedalloc.h"
31
32 #include "BLI_blenlib.h"
33 #include "BLI_ghash.h"
34 #include "BLI_lasso_2d.h"
35 #include "BLI_utildefines.h"
36 #include "BLI_math_vector.h"
37
38 #include "DNA_gpencil_types.h"
39 #include "DNA_scene_types.h"
40 #include "DNA_space_types.h"
41 #include "DNA_screen_types.h"
42 #include "DNA_object_types.h"
43
44 #include "BKE_context.h"
45 #include "BKE_gpencil.h"
46 #include "BKE_report.h"
47
48 #include "UI_interface.h"
49
50 #include "WM_api.h"
51 #include "WM_types.h"
52
53 #include "RNA_access.h"
54 #include "RNA_define.h"
55
56 #include "UI_view2d.h"
57
58 #include "ED_gpencil.h"
59 #include "ED_select_utils.h"
60
61 #include "DEG_depsgraph.h"
62 #include "DEG_depsgraph_query.h"
63
64 #include "gpencil_intern.h"
65
66 /* -------------------------------------------------------------------- */
67 /** \name Shared Utilities
68  * \{ */
69
70 static bool gpencil_select_poll(bContext *C)
71 {
72   bGPdata *gpd = ED_gpencil_data_get_active(C);
73
74   /* we just need some visible strokes, and to be in editmode or other modes only to catch event */
75   if (GPENCIL_ANY_MODE(gpd)) {
76     /* TODO: include a check for visible strokes? */
77     if (gpd->layers.first) {
78       return true;
79     }
80   }
81
82   return false;
83 }
84
85 /** \} */
86
87 /* -------------------------------------------------------------------- */
88 /** \name Select All Operator
89  * \{ */
90
91 static int gpencil_select_all_exec(bContext *C, wmOperator *op)
92 {
93   bGPdata *gpd = ED_gpencil_data_get_active(C);
94   int action = RNA_enum_get(op->ptr, "action");
95
96   if (gpd == NULL) {
97     BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
98     return OPERATOR_CANCELLED;
99   }
100
101   /* if not edit/sculpt mode, the event is catched but not processed */
102   if (GPENCIL_NONE_EDIT_MODE(gpd)) {
103     return OPERATOR_CANCELLED;
104   }
105
106   ED_gpencil_select_toggle_all(C, action);
107
108   /* updates */
109   DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY);
110
111   /* copy on write tag is needed, or else no refresh happens */
112   DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE);
113
114   WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
115   WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL);
116   return OPERATOR_FINISHED;
117 }
118
119 void GPENCIL_OT_select_all(wmOperatorType *ot)
120 {
121   /* identifiers */
122   ot->name = "(De)select All Strokes";
123   ot->idname = "GPENCIL_OT_select_all";
124   ot->description = "Change selection of all Grease Pencil strokes currently visible";
125
126   /* callbacks */
127   ot->exec = gpencil_select_all_exec;
128   ot->poll = gpencil_select_poll;
129
130   /* flags */
131   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
132
133   WM_operator_properties_select_all(ot);
134 }
135
136 /** \} */
137
138 /* -------------------------------------------------------------------- */
139 /** \name Select Linked Operator
140  * \{ */
141
142 static int gpencil_select_linked_exec(bContext *C, wmOperator *op)
143 {
144   bGPdata *gpd = ED_gpencil_data_get_active(C);
145
146   if (gpd == NULL) {
147     BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
148     return OPERATOR_CANCELLED;
149   }
150
151   /* if not edit/sculpt mode, the event is catched but not processed */
152   if (GPENCIL_NONE_EDIT_MODE(gpd)) {
153     return OPERATOR_CANCELLED;
154   }
155
156   /* select all points in selected strokes */
157   CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) {
158     if (gps->flag & GP_STROKE_SELECT) {
159       bGPDspoint *pt;
160       int i;
161
162       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
163         pt->flag |= GP_SPOINT_SELECT;
164       }
165     }
166   }
167   CTX_DATA_END;
168
169   /* updates */
170   DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY);
171
172   /* copy on write tag is needed, or else no refresh happens */
173   DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE);
174
175   WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
176   WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL);
177   return OPERATOR_FINISHED;
178 }
179
180 void GPENCIL_OT_select_linked(wmOperatorType *ot)
181 {
182   /* identifiers */
183   ot->name = "Select Linked";
184   ot->idname = "GPENCIL_OT_select_linked";
185   ot->description = "Select all points in same strokes as already selected points";
186
187   /* callbacks */
188   ot->exec = gpencil_select_linked_exec;
189   ot->poll = gpencil_select_poll;
190
191   /* flags */
192   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
193 }
194
195 /** \} */
196
197 /* -------------------------------------------------------------------- */
198 /** \name Select Alternate Operator
199  * \{ */
200
201 static int gpencil_select_alternate_exec(bContext *C, wmOperator *op)
202 {
203   const bool unselect_ends = RNA_boolean_get(op->ptr, "unselect_ends");
204   bGPdata *gpd = ED_gpencil_data_get_active(C);
205
206   if (gpd == NULL) {
207     BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
208     return OPERATOR_CANCELLED;
209   }
210
211   /* if not edit/sculpt mode, the event is catched but not processed */
212   if (GPENCIL_NONE_EDIT_MODE(gpd)) {
213     return OPERATOR_CANCELLED;
214   }
215
216   /* select all points in selected strokes */
217   CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) {
218     if ((gps->flag & GP_STROKE_SELECT) && (gps->totpoints > 1)) {
219       bGPDspoint *pt;
220       int row = 0;
221       int start = 0;
222       if (unselect_ends) {
223         start = 1;
224       }
225
226       for (int i = start; i < gps->totpoints; i++) {
227         pt = &gps->points[i];
228         if ((row % 2) == 0) {
229           pt->flag |= GP_SPOINT_SELECT;
230         }
231         else {
232           pt->flag &= ~GP_SPOINT_SELECT;
233         }
234         row++;
235       }
236
237       /* unselect start and end points */
238       if (unselect_ends) {
239         pt = &gps->points[0];
240         pt->flag &= ~GP_SPOINT_SELECT;
241
242         pt = &gps->points[gps->totpoints - 1];
243         pt->flag &= ~GP_SPOINT_SELECT;
244       }
245     }
246   }
247   CTX_DATA_END;
248
249   /* updates */
250   DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY);
251
252   /* copy on write tag is needed, or else no refresh happens */
253   DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE);
254
255   WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
256   WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL);
257   return OPERATOR_FINISHED;
258 }
259
260 void GPENCIL_OT_select_alternate(wmOperatorType *ot)
261 {
262   /* identifiers */
263   ot->name = "Alternated";
264   ot->idname = "GPENCIL_OT_select_alternate";
265   ot->description = "Select alternative points in same strokes as already selected points";
266
267   /* callbacks */
268   ot->exec = gpencil_select_alternate_exec;
269   ot->poll = gpencil_select_poll;
270
271   /* flags */
272   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
273
274   /* properties */
275   RNA_def_boolean(ot->srna,
276                   "unselect_ends",
277                   true,
278                   "Unselect Ends",
279                   "Do not select the first and last point of the stroke");
280 }
281
282 /** \} */
283
284 /* -------------------------------------------------------------------- */
285 /** \name Select Grouped Operator
286  * \{ */
287
288 typedef enum eGP_SelectGrouped {
289   /* Select strokes in the same layer */
290   GP_SEL_SAME_LAYER = 0,
291
292   /* Select strokes with the same color */
293   GP_SEL_SAME_MATERIAL = 1,
294
295   /* TODO: All with same prefix -
296    * Useful for isolating all layers for a particular character for instance. */
297   /* TODO: All with same appearance - color/opacity/volumetric/fills ? */
298 } eGP_SelectGrouped;
299
300 /* ----------------------------------- */
301
302 /* On each visible layer, check for selected strokes - if found, select all others */
303 static void gp_select_same_layer(bContext *C)
304 {
305   Depsgraph *depsgraph = CTX_data_depsgraph(C);
306   int cfra_eval = (int)DEG_get_ctime(depsgraph);
307
308   CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) {
309     bGPDframe *gpf = BKE_gpencil_layer_getframe(gpl, cfra_eval, GP_GETFRAME_USE_PREV);
310     bGPDstroke *gps;
311     bool found = false;
312
313     if (gpf == NULL) {
314       continue;
315     }
316
317     /* Search for a selected stroke */
318     for (gps = gpf->strokes.first; gps; gps = gps->next) {
319       if (ED_gpencil_stroke_can_use(C, gps)) {
320         if (gps->flag & GP_STROKE_SELECT) {
321           found = true;
322           break;
323         }
324       }
325     }
326
327     /* Select all if found */
328     if (found) {
329       for (gps = gpf->strokes.first; gps; gps = gps->next) {
330         if (ED_gpencil_stroke_can_use(C, gps)) {
331           bGPDspoint *pt;
332           int i;
333
334           for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
335             pt->flag |= GP_SPOINT_SELECT;
336           }
337
338           gps->flag |= GP_STROKE_SELECT;
339         }
340       }
341     }
342   }
343   CTX_DATA_END;
344 }
345
346 /* Select all strokes with same colors as selected ones */
347 static void gp_select_same_material(bContext *C)
348 {
349   /* First, build set containing all the colors of selected strokes */
350   GSet *selected_colors = BLI_gset_str_new("GP Selected Colors");
351
352   CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) {
353     if (gps->flag & GP_STROKE_SELECT) {
354       /* add instead of insert here, otherwise the uniqueness check gets skipped,
355        * and we get many duplicate entries...
356        */
357       BLI_gset_add(selected_colors, &gps->mat_nr);
358     }
359   }
360   CTX_DATA_END;
361
362   /* Second, select any visible stroke that uses these colors */
363   CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) {
364     if (BLI_gset_haskey(selected_colors, &gps->mat_nr)) {
365       /* select this stroke */
366       bGPDspoint *pt;
367       int i;
368
369       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
370         pt->flag |= GP_SPOINT_SELECT;
371       }
372
373       gps->flag |= GP_STROKE_SELECT;
374     }
375   }
376   CTX_DATA_END;
377
378   /* free memomy */
379   if (selected_colors != NULL) {
380     BLI_gset_free(selected_colors, NULL);
381   }
382 }
383
384 /* ----------------------------------- */
385
386 static int gpencil_select_grouped_exec(bContext *C, wmOperator *op)
387 {
388   eGP_SelectGrouped mode = RNA_enum_get(op->ptr, "type");
389   bGPdata *gpd = ED_gpencil_data_get_active(C);
390   /* if not edit/sculpt mode, the event is catched but not processed */
391   if (GPENCIL_NONE_EDIT_MODE(gpd)) {
392     return OPERATOR_CANCELLED;
393   }
394
395   switch (mode) {
396     case GP_SEL_SAME_LAYER:
397       gp_select_same_layer(C);
398       break;
399     case GP_SEL_SAME_MATERIAL:
400       gp_select_same_material(C);
401       break;
402
403     default:
404       BLI_assert(!"unhandled select grouped gpencil mode");
405       break;
406   }
407
408   /* updates */
409   DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY);
410
411   /* copy on write tag is needed, or else no refresh happens */
412   DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE);
413
414   WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
415   WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL);
416   return OPERATOR_FINISHED;
417 }
418
419 void GPENCIL_OT_select_grouped(wmOperatorType *ot)
420 {
421   static const EnumPropertyItem prop_select_grouped_types[] = {
422       {GP_SEL_SAME_LAYER, "LAYER", 0, "Layer", "Shared layers"},
423       {GP_SEL_SAME_MATERIAL, "MATERIAL", 0, "Material", "Shared materials"},
424       {0, NULL, 0, NULL, NULL},
425   };
426
427   /* identifiers */
428   ot->name = "Select Grouped";
429   ot->idname = "GPENCIL_OT_select_grouped";
430   ot->description = "Select all strokes with similar characteristics";
431
432   /* callbacks */
433   ot->invoke = WM_menu_invoke;
434   ot->exec = gpencil_select_grouped_exec;
435   ot->poll = gpencil_select_poll;
436
437   /* flags */
438   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
439
440   /* props */
441   ot->prop = RNA_def_enum(
442       ot->srna, "type", prop_select_grouped_types, GP_SEL_SAME_LAYER, "Type", "");
443 }
444
445 /** \} */
446
447 /* -------------------------------------------------------------------- */
448 /** \name Select First
449  * \{ */
450
451 static int gpencil_select_first_exec(bContext *C, wmOperator *op)
452 {
453   bGPdata *gpd = ED_gpencil_data_get_active(C);
454   /* if not edit/sculpt mode, the event is catched but not processed */
455   if (GPENCIL_NONE_EDIT_MODE(gpd)) {
456     return OPERATOR_CANCELLED;
457   }
458
459   const bool only_selected = RNA_boolean_get(op->ptr, "only_selected_strokes");
460   const bool extend = RNA_boolean_get(op->ptr, "extend");
461
462   CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) {
463     /* skip stroke if we're only manipulating selected strokes */
464     if (only_selected && !(gps->flag & GP_STROKE_SELECT)) {
465       continue;
466     }
467
468     /* select first point */
469     BLI_assert(gps->totpoints >= 1);
470
471     gps->points->flag |= GP_SPOINT_SELECT;
472     gps->flag |= GP_STROKE_SELECT;
473
474     /* deselect rest? */
475     if ((extend == false) && (gps->totpoints > 1)) {
476       /* start from index 1, to skip the first point that we'd just selected... */
477       bGPDspoint *pt = &gps->points[1];
478       int i = 1;
479
480       for (; i < gps->totpoints; i++, pt++) {
481         pt->flag &= ~GP_SPOINT_SELECT;
482       }
483     }
484   }
485   CTX_DATA_END;
486
487   /* updates */
488   DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY);
489
490   /* copy on write tag is needed, or else no refresh happens */
491   DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE);
492
493   WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
494   WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL);
495   return OPERATOR_FINISHED;
496 }
497
498 void GPENCIL_OT_select_first(wmOperatorType *ot)
499 {
500   /* identifiers */
501   ot->name = "Select First";
502   ot->idname = "GPENCIL_OT_select_first";
503   ot->description = "Select first point in Grease Pencil strokes";
504
505   /* callbacks */
506   ot->exec = gpencil_select_first_exec;
507   ot->poll = gpencil_select_poll;
508
509   /* flags */
510   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
511
512   /* properties */
513   RNA_def_boolean(ot->srna,
514                   "only_selected_strokes",
515                   false,
516                   "Selected Strokes Only",
517                   "Only select the first point of strokes that already have points selected");
518
519   RNA_def_boolean(ot->srna,
520                   "extend",
521                   false,
522                   "Extend",
523                   "Extend selection instead of deselecting all other selected points");
524 }
525
526 /** \} */
527
528 /* -------------------------------------------------------------------- */
529 /** \name Select First
530  * \{ */
531
532 static int gpencil_select_last_exec(bContext *C, wmOperator *op)
533 {
534   bGPdata *gpd = ED_gpencil_data_get_active(C);
535   /* if not edit/sculpt mode, the event is catched but not processed */
536   if (GPENCIL_NONE_EDIT_MODE(gpd)) {
537     return OPERATOR_CANCELLED;
538   }
539
540   const bool only_selected = RNA_boolean_get(op->ptr, "only_selected_strokes");
541   const bool extend = RNA_boolean_get(op->ptr, "extend");
542
543   CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) {
544     /* skip stroke if we're only manipulating selected strokes */
545     if (only_selected && !(gps->flag & GP_STROKE_SELECT)) {
546       continue;
547     }
548
549     /* select last point */
550     BLI_assert(gps->totpoints >= 1);
551
552     gps->points[gps->totpoints - 1].flag |= GP_SPOINT_SELECT;
553     gps->flag |= GP_STROKE_SELECT;
554
555     /* deselect rest? */
556     if ((extend == false) && (gps->totpoints > 1)) {
557       /* don't include the last point... */
558       bGPDspoint *pt = gps->points;
559       int i = 1;
560
561       for (; i < gps->totpoints - 1; i++, pt++) {
562         pt->flag &= ~GP_SPOINT_SELECT;
563       }
564     }
565   }
566   CTX_DATA_END;
567
568   /* updates */
569   DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY);
570
571   /* copy on write tag is needed, or else no refresh happens */
572   DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE);
573
574   WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
575   WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL);
576   return OPERATOR_FINISHED;
577 }
578
579 void GPENCIL_OT_select_last(wmOperatorType *ot)
580 {
581   /* identifiers */
582   ot->name = "Select Last";
583   ot->idname = "GPENCIL_OT_select_last";
584   ot->description = "Select last point in Grease Pencil strokes";
585
586   /* callbacks */
587   ot->exec = gpencil_select_last_exec;
588   ot->poll = gpencil_select_poll;
589
590   /* flags */
591   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
592
593   /* properties */
594   RNA_def_boolean(ot->srna,
595                   "only_selected_strokes",
596                   false,
597                   "Selected Strokes Only",
598                   "Only select the last point of strokes that already have points selected");
599
600   RNA_def_boolean(ot->srna,
601                   "extend",
602                   false,
603                   "Extend",
604                   "Extend selection instead of deselecting all other selected points");
605 }
606
607 /** \} */
608
609 /* -------------------------------------------------------------------- */
610 /** \name Select Mode Operator
611  * \{ */
612
613 static int gpencil_select_more_exec(bContext *C, wmOperator *UNUSED(op))
614 {
615   bGPdata *gpd = ED_gpencil_data_get_active(C);
616   /* if not edit/sculpt mode, the event is catched but not processed */
617   if (GPENCIL_NONE_EDIT_MODE(gpd)) {
618     return OPERATOR_CANCELLED;
619   }
620
621   CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) {
622     if (gps->flag & GP_STROKE_SELECT) {
623       bGPDspoint *pt;
624       int i;
625       bool prev_sel;
626
627       /* First Pass: Go in forward order,
628        * expanding selection if previous was selected (pre changes).
629        * - This pass covers the "after" edges of selection islands
630        */
631       prev_sel = false;
632       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
633         if (pt->flag & GP_SPOINT_SELECT) {
634           /* selected point - just set flag for next point */
635           prev_sel = true;
636         }
637         else {
638           /* unselected point - expand selection if previous was selected... */
639           if (prev_sel) {
640             pt->flag |= GP_SPOINT_SELECT;
641           }
642           prev_sel = false;
643         }
644       }
645
646       /* Second Pass: Go in reverse order, doing the same as before (except in opposite order)
647        * - This pass covers the "before" edges of selection islands
648        */
649       prev_sel = false;
650       for (pt -= 1; i > 0; i--, pt--) {
651         if (pt->flag & GP_SPOINT_SELECT) {
652           prev_sel = true;
653         }
654         else {
655           /* unselected point - expand selection if previous was selected... */
656           if (prev_sel) {
657             pt->flag |= GP_SPOINT_SELECT;
658           }
659           prev_sel = false;
660         }
661       }
662     }
663   }
664   CTX_DATA_END;
665
666   /* updates */
667   DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY);
668
669   /* copy on write tag is needed, or else no refresh happens */
670   DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE);
671
672   WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
673   WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL);
674   return OPERATOR_FINISHED;
675 }
676
677 void GPENCIL_OT_select_more(wmOperatorType *ot)
678 {
679   /* identifiers */
680   ot->name = "Select More";
681   ot->idname = "GPENCIL_OT_select_more";
682   ot->description = "Grow sets of selected Grease Pencil points";
683
684   /* callbacks */
685   ot->exec = gpencil_select_more_exec;
686   ot->poll = gpencil_select_poll;
687
688   /* flags */
689   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
690 }
691
692 /** \} */
693
694 /* -------------------------------------------------------------------- */
695 /** \name Select Less Operator
696  * \{ */
697
698 static int gpencil_select_less_exec(bContext *C, wmOperator *UNUSED(op))
699 {
700   bGPdata *gpd = ED_gpencil_data_get_active(C);
701   /* if not edit/sculpt mode, the event is catched but not processed */
702   if (GPENCIL_NONE_EDIT_MODE(gpd)) {
703     return OPERATOR_CANCELLED;
704   }
705
706   CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) {
707     if (gps->flag & GP_STROKE_SELECT) {
708       bGPDspoint *pt;
709       int i;
710       bool prev_sel;
711
712       /* First Pass: Go in forward order, shrinking selection
713        * if previous was not selected (pre changes).
714        * - This pass covers the "after" edges of selection islands
715        */
716       prev_sel = false;
717       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
718         if (pt->flag & GP_SPOINT_SELECT) {
719           /* shrink if previous wasn't selected */
720           if (prev_sel == false) {
721             pt->flag &= ~GP_SPOINT_SELECT;
722           }
723           prev_sel = true;
724         }
725         else {
726           /* mark previous as being unselected - and hence, is trigger for shrinking */
727           prev_sel = false;
728         }
729       }
730
731       /* Second Pass: Go in reverse order, doing the same as before (except in opposite order)
732        * - This pass covers the "before" edges of selection islands
733        */
734       prev_sel = false;
735       for (pt -= 1; i > 0; i--, pt--) {
736         if (pt->flag & GP_SPOINT_SELECT) {
737           /* shrink if previous wasn't selected */
738           if (prev_sel == false) {
739             pt->flag &= ~GP_SPOINT_SELECT;
740           }
741           prev_sel = true;
742         }
743         else {
744           /* mark previous as being unselected - and hence, is trigger for shrinking */
745           prev_sel = false;
746         }
747       }
748     }
749   }
750   CTX_DATA_END;
751
752   /* updates */
753   DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY);
754
755   /* copy on write tag is needed, or else no refresh happens */
756   DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE);
757
758   WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
759   WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL);
760   return OPERATOR_FINISHED;
761 }
762
763 void GPENCIL_OT_select_less(wmOperatorType *ot)
764 {
765   /* identifiers */
766   ot->name = "Select Less";
767   ot->idname = "GPENCIL_OT_select_less";
768   ot->description = "Shrink sets of selected Grease Pencil points";
769
770   /* callbacks */
771   ot->exec = gpencil_select_less_exec;
772   ot->poll = gpencil_select_poll;
773
774   /* flags */
775   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
776 }
777
778 /** \} */
779
780 /* -------------------------------------------------------------------- */
781 /** \name Circle Select Operator
782  * \{ */
783
784 /**
785  * Helper to check if a given stroke is within the area.
786  *
787  * \note Code here is adapted (i.e. copied directly)
788  * from gpencil_paint.c #gp_stroke_eraser_dostroke().
789  * It would be great to de-duplicate the logic here sometime, but that can wait.
790  */
791 static bool gp_stroke_do_circle_sel(bGPDlayer *gpl,
792                                     bGPDstroke *gps,
793                                     GP_SpaceConversion *gsc,
794                                     const int mx,
795                                     const int my,
796                                     const int radius,
797                                     const bool select,
798                                     rcti *rect,
799                                     float diff_mat[4][4],
800                                     const int selectmode,
801                                     const float scale)
802 {
803   bGPDspoint *pt1 = NULL;
804   bGPDspoint *pt2 = NULL;
805   bGPDstroke *gps_orig = gps->runtime.gps_orig;
806   int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
807   int i;
808   bool changed = false;
809
810   if (gps->totpoints == 1) {
811     bGPDspoint pt_temp;
812     gp_point_to_parent_space(gps->points, diff_mat, &pt_temp);
813     gp_point_to_xy(gsc, gps, &pt_temp, &x0, &y0);
814
815     /* do boundbox check first */
816     if ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(rect, x0, y0)) {
817       /* only check if point is inside */
818       if (((x0 - mx) * (x0 - mx) + (y0 - my) * (y0 - my)) <= radius * radius) {
819         /* change selection */
820         if (select) {
821           gps_orig->points->flag |= GP_SPOINT_SELECT;
822           gps_orig->flag |= GP_STROKE_SELECT;
823         }
824         else {
825           gps_orig->points->flag &= ~GP_SPOINT_SELECT;
826           gps_orig->flag &= ~GP_STROKE_SELECT;
827         }
828
829         return true;
830       }
831     }
832   }
833   else {
834     /* Loop over the points in the stroke, checking for intersections
835      * - an intersection means that we touched the stroke
836      */
837     bool hit = false;
838     for (i = 0; (i + 1) < gps->totpoints; i++) {
839       /* get points to work with */
840       pt1 = gps->points + i;
841       pt2 = gps->points + i + 1;
842       bGPDspoint npt;
843       gp_point_to_parent_space(pt1, diff_mat, &npt);
844       gp_point_to_xy(gsc, gps, &npt, &x0, &y0);
845
846       gp_point_to_parent_space(pt2, diff_mat, &npt);
847       gp_point_to_xy(gsc, gps, &npt, &x1, &y1);
848
849       /* check that point segment of the boundbox of the selection stroke */
850       if (((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(rect, x0, y0)) ||
851           ((!ELEM(V2D_IS_CLIPPED, x1, y1)) && BLI_rcti_isect_pt(rect, x1, y1))) {
852         float mval[2] = {(float)mx, (float)my};
853         float mvalo[2] = {(float)mx, (float)my}; /* dummy - this isn't used... */
854
855         /* check if point segment of stroke had anything to do with
856          * eraser region  (either within stroke painted, or on its lines)
857          * - this assumes that linewidth is irrelevant
858          */
859         if (gp_stroke_inside_circle(mval, mvalo, radius, x0, y0, x1, y1)) {
860           /* change selection of stroke, and then of both points
861            * (as the last point otherwise wouldn't get selected
862            * as we only do n-1 loops through).
863            */
864           hit = true;
865           if (select) {
866             if (pt1->runtime.pt_orig != NULL) {
867               pt1->runtime.pt_orig->flag |= GP_SPOINT_SELECT;
868             }
869             if (pt2->runtime.pt_orig != NULL) {
870               pt2->runtime.pt_orig->flag |= GP_SPOINT_SELECT;
871             }
872             changed = true;
873           }
874           else {
875             if (pt1->runtime.pt_orig != NULL) {
876               pt1->runtime.pt_orig->flag &= ~GP_SPOINT_SELECT;
877             }
878             if (pt2->runtime.pt_orig != NULL) {
879               pt2->runtime.pt_orig->flag &= ~GP_SPOINT_SELECT;
880             }
881             changed = true;
882           }
883         }
884       }
885       /* if stroke mode, don't check more points */
886       if ((hit) && (selectmode == GP_SELECTMODE_STROKE)) {
887         break;
888       }
889     }
890
891     /* if stroke mode expand selection */
892     if ((hit) && (selectmode == GP_SELECTMODE_STROKE)) {
893       for (i = 0, pt1 = gps->points; i < gps->totpoints; i++, pt1++) {
894         if (pt1->runtime.pt_orig != NULL) {
895           if (select) {
896             pt1->runtime.pt_orig->flag |= GP_SPOINT_SELECT;
897           }
898           else {
899             pt1->runtime.pt_orig->flag &= ~GP_SPOINT_SELECT;
900           }
901         }
902       }
903     }
904
905     /* expand selection to segment */
906     if ((hit) && (selectmode == GP_SELECTMODE_SEGMENT) && (select) &&
907         (pt1->runtime.pt_orig != NULL)) {
908       float r_hita[3], r_hitb[3];
909       bool hit_select = (bool)(pt1->flag & GP_SPOINT_SELECT);
910       ED_gpencil_select_stroke_segment(
911           gpl, gps_orig, pt1->runtime.pt_orig, hit_select, false, scale, r_hita, r_hitb);
912     }
913
914     /* Ensure that stroke selection is in sync with its points */
915     BKE_gpencil_stroke_sync_selection(gps_orig);
916   }
917
918   return changed;
919 }
920
921 static int gpencil_circle_select_exec(bContext *C, wmOperator *op)
922 {
923   bGPdata *gpd = ED_gpencil_data_get_active(C);
924   ToolSettings *ts = CTX_data_tool_settings(C);
925   const int selectmode = ts->gpencil_selectmode;
926   const float scale = ts->gp_sculpt.isect_threshold;
927
928   /* if not edit/sculpt mode, the event is catched but not processed */
929   if (GPENCIL_NONE_EDIT_MODE(gpd)) {
930     return OPERATOR_CANCELLED;
931   }
932
933   ScrArea *sa = CTX_wm_area(C);
934
935   const int mx = RNA_int_get(op->ptr, "x");
936   const int my = RNA_int_get(op->ptr, "y");
937   const int radius = RNA_int_get(op->ptr, "radius");
938
939   GP_SpaceConversion gsc = {NULL};
940   /* for bounding rect around circle (for quicky intersection testing) */
941   rcti rect = {0};
942
943   bool changed = false;
944
945   /* sanity checks */
946   if (sa == NULL) {
947     BKE_report(op->reports, RPT_ERROR, "No active area");
948     return OPERATOR_CANCELLED;
949   }
950
951   const eSelectOp sel_op = ED_select_op_modal(RNA_enum_get(op->ptr, "mode"),
952                                               WM_gesture_is_modal_first(op->customdata));
953   const bool select = (sel_op != SEL_OP_SUB);
954   if (SEL_OP_USE_PRE_DESELECT(sel_op)) {
955     ED_gpencil_select_toggle_all(C, SEL_DESELECT);
956     changed = true;
957   }
958
959   /* init space conversion stuff */
960   gp_point_conversion_init(C, &gsc);
961
962   /* rect is rectangle of selection circle */
963   rect.xmin = mx - radius;
964   rect.ymin = my - radius;
965   rect.xmax = mx + radius;
966   rect.ymax = my + radius;
967
968   /* find visible strokes, and select if hit */
969   GP_DERIVED_STROKES_BEGIN(gpstroke_iter, C, gpl, gps)
970   {
971     changed |= gp_stroke_do_circle_sel(
972         gpl, gps, &gsc, mx, my, radius, select, &rect, gpstroke_iter.diff_mat, selectmode, scale);
973   }
974   GP_DERIVED_STROKES_END(gpstroke_iter);
975
976   /* updates */
977   if (changed) {
978     DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY);
979
980     /* copy on write tag is needed, or else no refresh happens */
981     DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE);
982
983     WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
984     WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL);
985   }
986
987   return OPERATOR_FINISHED;
988 }
989
990 void GPENCIL_OT_select_circle(wmOperatorType *ot)
991 {
992   /* identifiers */
993   ot->name = "Circle Select";
994   ot->description = "Select Grease Pencil strokes using brush selection";
995   ot->idname = "GPENCIL_OT_select_circle";
996
997   /* callbacks */
998   ot->invoke = WM_gesture_circle_invoke;
999   ot->modal = WM_gesture_circle_modal;
1000   ot->exec = gpencil_circle_select_exec;
1001   ot->poll = gpencil_select_poll;
1002   ot->cancel = WM_gesture_circle_cancel;
1003
1004   /* flags */
1005   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_USE_EVAL_DATA;
1006
1007   /* properties */
1008   WM_operator_properties_gesture_circle(ot);
1009   WM_operator_properties_select_operation_simple(ot);
1010 }
1011
1012 /** \} */
1013
1014 /* -------------------------------------------------------------------- */
1015 /** \name Generic Select Utility
1016  *
1017  * Use for lasso & box select.
1018  *
1019  * \{ */
1020
1021 typedef bool (*GPencilTestFn)(bGPDstroke *gps,
1022                               bGPDspoint *pt,
1023                               const GP_SpaceConversion *gsc,
1024                               const float diff_mat[4][4],
1025                               void *user_data);
1026
1027 static int gpencil_generic_select_exec(bContext *C,
1028                                        wmOperator *op,
1029                                        GPencilTestFn is_inside_fn,
1030                                        void *user_data)
1031 {
1032   bGPdata *gpd = ED_gpencil_data_get_active(C);
1033   ToolSettings *ts = CTX_data_tool_settings(C);
1034   ScrArea *sa = CTX_wm_area(C);
1035   const bool strokemode = ((ts->gpencil_selectmode == GP_SELECTMODE_STROKE) &&
1036                            ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0));
1037   const bool segmentmode = ((ts->gpencil_selectmode == GP_SELECTMODE_SEGMENT) &&
1038                             ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0));
1039   const eSelectOp sel_op = RNA_enum_get(op->ptr, "mode");
1040   const float scale = ts->gp_sculpt.isect_threshold;
1041
1042   GP_SpaceConversion gsc = {NULL};
1043
1044   bool changed = false;
1045
1046   /* sanity checks */
1047   if (sa == NULL) {
1048     BKE_report(op->reports, RPT_ERROR, "No active area");
1049     return OPERATOR_CANCELLED;
1050   }
1051
1052   /* init space conversion stuff */
1053   gp_point_conversion_init(C, &gsc);
1054
1055   /* deselect all strokes first? */
1056   if (SEL_OP_USE_PRE_DESELECT(sel_op)) {
1057
1058     CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) {
1059       bGPDspoint *pt;
1060       int i;
1061
1062       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1063         pt->flag &= ~GP_SPOINT_SELECT;
1064       }
1065
1066       gps->flag &= ~GP_STROKE_SELECT;
1067     }
1068     CTX_DATA_END;
1069   }
1070
1071   /* select/deselect points */
1072   GP_DERIVED_STROKES_BEGIN(gpstroke_iter, C, gpl, gps)
1073   {
1074
1075     bGPDspoint *pt;
1076     int i;
1077     bool hit = false;
1078     for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1079       if (pt->runtime.pt_orig == NULL) {
1080         continue;
1081       }
1082
1083       /* convert point coords to screenspace */
1084       const bool is_inside = is_inside_fn(gps, pt, &gsc, gpstroke_iter.diff_mat, user_data);
1085
1086       if (strokemode == false) {
1087         const bool is_select = (pt->runtime.pt_orig->flag & GP_SPOINT_SELECT) != 0;
1088         const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside);
1089         if (sel_op_result != -1) {
1090           SET_FLAG_FROM_TEST(pt->runtime.pt_orig->flag, sel_op_result, GP_SPOINT_SELECT);
1091           changed = true;
1092
1093           /* expand selection to segment */
1094           if ((sel_op_result != -1) && (segmentmode)) {
1095             bool hit_select = (bool)(pt->runtime.pt_orig->flag & GP_SPOINT_SELECT);
1096             float r_hita[3], r_hitb[3];
1097             ED_gpencil_select_stroke_segment(gpl,
1098                                              gps->runtime.gps_orig,
1099                                              pt->runtime.pt_orig,
1100                                              hit_select,
1101                                              false,
1102                                              scale,
1103                                              r_hita,
1104                                              r_hitb);
1105           }
1106         }
1107       }
1108       else {
1109         if (is_inside) {
1110           hit = true;
1111           break;
1112         }
1113       }
1114     }
1115
1116     /* if stroke mode expand selection */
1117     if (strokemode) {
1118       const bool is_select = BKE_gpencil_stroke_select_check(gps->runtime.gps_orig);
1119       const bool is_inside = hit;
1120       const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside);
1121       if (sel_op_result != -1) {
1122         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1123           if (pt->runtime.pt_orig == NULL) {
1124             continue;
1125           }
1126           if (sel_op_result) {
1127             pt->runtime.pt_orig->flag |= GP_SPOINT_SELECT;
1128           }
1129           else {
1130             pt->runtime.pt_orig->flag &= ~GP_SPOINT_SELECT;
1131           }
1132         }
1133         changed = true;
1134       }
1135     }
1136
1137     /* Ensure that stroke selection is in sync with its points */
1138     BKE_gpencil_stroke_sync_selection(gps->runtime.gps_orig);
1139   }
1140   GP_DERIVED_STROKES_END(gpstroke_iter);
1141
1142   /* if paint mode,delete selected points */
1143   if (gpd->flag & GP_DATA_STROKE_PAINTMODE) {
1144     gp_delete_selected_point_wrap(C);
1145     changed = true;
1146     DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
1147   }
1148
1149   /* updates */
1150   if (changed) {
1151     DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY);
1152
1153     /* copy on write tag is needed, or else no refresh happens */
1154     DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE);
1155
1156     WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
1157     WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL);
1158   }
1159
1160   return OPERATOR_FINISHED;
1161 }
1162
1163 /** \} */
1164
1165 /* -------------------------------------------------------------------- */
1166 /** \name Box Select Operator
1167  * \{ */
1168
1169 struct GP_SelectBoxUserData {
1170   rcti rect;
1171 };
1172
1173 static bool gpencil_test_box(bGPDstroke *gps,
1174                              bGPDspoint *pt,
1175                              const GP_SpaceConversion *gsc,
1176                              const float diff_mat[4][4],
1177                              void *user_data)
1178 {
1179   const struct GP_SelectBoxUserData *data = user_data;
1180   bGPDspoint pt2;
1181   int x0, y0;
1182   gp_point_to_parent_space(pt, diff_mat, &pt2);
1183   gp_point_to_xy(gsc, gps, &pt2, &x0, &y0);
1184   return ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(&data->rect, x0, y0));
1185 }
1186
1187 static int gpencil_box_select_exec(bContext *C, wmOperator *op)
1188 {
1189   struct GP_SelectBoxUserData data = {0};
1190   WM_operator_properties_border_to_rcti(op, &data.rect);
1191   return gpencil_generic_select_exec(C, op, gpencil_test_box, &data);
1192 }
1193
1194 void GPENCIL_OT_select_box(wmOperatorType *ot)
1195 {
1196   /* identifiers */
1197   ot->name = "Box Select";
1198   ot->description = "Select Grease Pencil strokes within a rectangular region";
1199   ot->idname = "GPENCIL_OT_select_box";
1200
1201   /* callbacks */
1202   ot->invoke = WM_gesture_box_invoke;
1203   ot->exec = gpencil_box_select_exec;
1204   ot->modal = WM_gesture_box_modal;
1205   ot->cancel = WM_gesture_box_cancel;
1206
1207   ot->poll = gpencil_select_poll;
1208
1209   /* flags */
1210   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_USE_EVAL_DATA;
1211
1212   /* properties */
1213   WM_operator_properties_gesture_box(ot);
1214   WM_operator_properties_select_operation(ot);
1215 }
1216
1217 /** \} */
1218
1219 /* -------------------------------------------------------------------- */
1220 /** \name Lasso Select Operator
1221  * \{ */
1222
1223 struct GP_SelectLassoUserData {
1224   rcti rect;
1225   const int (*mcords)[2];
1226   int mcords_len;
1227 };
1228
1229 static bool gpencil_test_lasso(bGPDstroke *gps,
1230                                bGPDspoint *pt,
1231                                const GP_SpaceConversion *gsc,
1232                                const float diff_mat[4][4],
1233                                void *user_data)
1234 {
1235   const struct GP_SelectLassoUserData *data = user_data;
1236   bGPDspoint pt2;
1237   int x0, y0;
1238   gp_point_to_parent_space(pt, diff_mat, &pt2);
1239   gp_point_to_xy(gsc, gps, &pt2, &x0, &y0);
1240   /* test if in lasso boundbox + within the lasso noose */
1241   return ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(&data->rect, x0, y0) &&
1242           BLI_lasso_is_point_inside(data->mcords, data->mcords_len, x0, y0, INT_MAX));
1243 }
1244
1245 static int gpencil_lasso_select_exec(bContext *C, wmOperator *op)
1246 {
1247   struct GP_SelectLassoUserData data = {0};
1248   data.mcords = WM_gesture_lasso_path_to_array(C, op, &data.mcords_len);
1249
1250   /* Sanity check. */
1251   if (data.mcords == NULL) {
1252     return OPERATOR_PASS_THROUGH;
1253   }
1254
1255   /* Compute boundbox of lasso (for faster testing later). */
1256   BLI_lasso_boundbox(&data.rect, data.mcords, data.mcords_len);
1257
1258   int ret = gpencil_generic_select_exec(C, op, gpencil_test_lasso, &data);
1259
1260   MEM_freeN((void *)data.mcords);
1261
1262   return ret;
1263 }
1264
1265 void GPENCIL_OT_select_lasso(wmOperatorType *ot)
1266 {
1267   ot->name = "Lasso Select Strokes";
1268   ot->description = "Select Grease Pencil strokes using lasso selection";
1269   ot->idname = "GPENCIL_OT_select_lasso";
1270
1271   ot->invoke = WM_gesture_lasso_invoke;
1272   ot->modal = WM_gesture_lasso_modal;
1273   ot->exec = gpencil_lasso_select_exec;
1274   ot->poll = gpencil_select_poll;
1275   ot->cancel = WM_gesture_lasso_cancel;
1276
1277   /* flags */
1278   ot->flag = OPTYPE_UNDO | OPTYPE_USE_EVAL_DATA;
1279
1280   /* properties */
1281   WM_operator_properties_select_operation(ot);
1282   WM_operator_properties_gesture_lasso(ot);
1283 }
1284
1285 /** \} */
1286
1287 /* -------------------------------------------------------------------- */
1288 /** \name Mouse Pick Select Operator
1289  * \{ */
1290
1291 /* helper to deselect all selected strokes/points */
1292 static void deselect_all_selected(bContext *C)
1293 {
1294   CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) {
1295     /* deselect stroke and its points if selected */
1296     if (gps->flag & GP_STROKE_SELECT) {
1297       bGPDspoint *pt;
1298       int i;
1299
1300       /* deselect points */
1301       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1302         pt->flag &= ~GP_SPOINT_SELECT;
1303       }
1304
1305       /* deselect stroke itself too */
1306       gps->flag &= ~GP_STROKE_SELECT;
1307     }
1308   }
1309   CTX_DATA_END;
1310 }
1311
1312 static int gpencil_select_exec(bContext *C, wmOperator *op)
1313 {
1314   ScrArea *sa = CTX_wm_area(C);
1315   bGPdata *gpd = ED_gpencil_data_get_active(C);
1316   ToolSettings *ts = CTX_data_tool_settings(C);
1317   const float scale = ts->gp_sculpt.isect_threshold;
1318
1319   /* "radius" is simply a threshold (screen space) to make it easier to test with a tolerance */
1320   const float radius = 0.50f * U.widget_unit;
1321   const int radius_squared = (int)(radius * radius);
1322
1323   bool extend = RNA_boolean_get(op->ptr, "extend");
1324   bool deselect = RNA_boolean_get(op->ptr, "deselect");
1325   bool toggle = RNA_boolean_get(op->ptr, "toggle");
1326   bool whole = RNA_boolean_get(op->ptr, "entire_strokes");
1327   const bool deselect_all = RNA_boolean_get(op->ptr, "deselect_all");
1328
1329   int mval[2] = {0};
1330
1331   GP_SpaceConversion gsc = {NULL};
1332
1333   bGPDlayer *hit_layer = NULL;
1334   bGPDstroke *hit_stroke = NULL;
1335   bGPDspoint *hit_point = NULL;
1336   int hit_distance = radius_squared;
1337
1338   /* sanity checks */
1339   if (sa == NULL) {
1340     BKE_report(op->reports, RPT_ERROR, "No active area");
1341     return OPERATOR_CANCELLED;
1342   }
1343
1344   /* if select mode is stroke, use whole stroke */
1345   if (ts->gpencil_selectmode == GP_SELECTMODE_STROKE) {
1346     whole = true;
1347   }
1348
1349   /* init space conversion stuff */
1350   gp_point_conversion_init(C, &gsc);
1351
1352   /* get mouse location */
1353   RNA_int_get_array(op->ptr, "location", mval);
1354
1355   /* First Pass: Find stroke point which gets hit */
1356   /* XXX: maybe we should go from the top of the stack down instead... */
1357   GP_DERIVED_STROKES_BEGIN(gpstroke_iter, C, gpl, gps)
1358   {
1359     bGPDspoint *pt;
1360     int i;
1361
1362     /* firstly, check for hit-point */
1363     for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1364       int xy[2];
1365       if (pt->runtime.pt_orig == NULL) {
1366         continue;
1367       }
1368
1369       bGPDspoint pt2;
1370       gp_point_to_parent_space(pt, gpstroke_iter.diff_mat, &pt2);
1371       gp_point_to_xy(&gsc, gps, &pt2, &xy[0], &xy[1]);
1372
1373       /* do boundbox check first */
1374       if (!ELEM(V2D_IS_CLIPPED, xy[0], xy[1])) {
1375         const int pt_distance = len_manhattan_v2v2_int(mval, xy);
1376
1377         /* check if point is inside */
1378         if (pt_distance <= radius_squared) {
1379           /* only use this point if it is a better match than the current hit - T44685 */
1380           if (pt_distance < hit_distance) {
1381             hit_layer = gpl;
1382             hit_stroke = gps->runtime.gps_orig;
1383             hit_point = pt->runtime.pt_orig;
1384             hit_distance = pt_distance;
1385           }
1386         }
1387       }
1388     }
1389   }
1390   GP_DERIVED_STROKES_END(gpstroke_iter);
1391
1392   /* Abort if nothing hit... */
1393   if (ELEM(NULL, hit_stroke, hit_point)) {
1394     if (deselect_all) {
1395       /* since left mouse select change, deselect all if click outside any hit */
1396       deselect_all_selected(C);
1397
1398       /* copy on write tag is needed, or else no refresh happens */
1399       DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY);
1400       DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE);
1401       WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
1402       WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL);
1403
1404       return OPERATOR_FINISHED;
1405     }
1406
1407     return OPERATOR_CANCELLED;
1408   }
1409
1410   /* adjust selection behavior - for toggle option */
1411   if (toggle) {
1412     deselect = (hit_point->flag & GP_SPOINT_SELECT) != 0;
1413   }
1414
1415   /* If not extending selection, deselect everything else */
1416   if (extend == false) {
1417     deselect_all_selected(C);
1418   }
1419
1420   /* Perform selection operations... */
1421   if (whole) {
1422     bGPDspoint *pt;
1423     int i;
1424
1425     /* entire stroke's points */
1426     for (i = 0, pt = hit_stroke->points; i < hit_stroke->totpoints; i++, pt++) {
1427       if (deselect == false) {
1428         pt->flag |= GP_SPOINT_SELECT;
1429       }
1430       else {
1431         pt->flag &= ~GP_SPOINT_SELECT;
1432       }
1433     }
1434
1435     /* stroke too... */
1436     if (deselect == false) {
1437       hit_stroke->flag |= GP_STROKE_SELECT;
1438     }
1439     else {
1440       hit_stroke->flag &= ~GP_STROKE_SELECT;
1441     }
1442   }
1443   else {
1444     /* just the point (and the stroke) */
1445     if (deselect == false) {
1446       /* we're adding selection, so selection must be true */
1447       hit_point->flag |= GP_SPOINT_SELECT;
1448       hit_stroke->flag |= GP_STROKE_SELECT;
1449
1450       /* expand selection to segment */
1451       if (ts->gpencil_selectmode == GP_SELECTMODE_SEGMENT) {
1452         float r_hita[3], r_hitb[3];
1453         bool hit_select = (bool)(hit_point->flag & GP_SPOINT_SELECT);
1454         ED_gpencil_select_stroke_segment(
1455             hit_layer, hit_stroke, hit_point, hit_select, false, scale, r_hita, r_hitb);
1456       }
1457     }
1458     else {
1459       /* deselect point */
1460       hit_point->flag &= ~GP_SPOINT_SELECT;
1461
1462       /* ensure that stroke is selected correctly */
1463       BKE_gpencil_stroke_sync_selection(hit_stroke);
1464     }
1465   }
1466
1467   /* updates */
1468   if (hit_point != NULL) {
1469     DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY);
1470
1471     /* copy on write tag is needed, or else no refresh happens */
1472     DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE);
1473
1474     WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
1475     WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL);
1476   }
1477
1478   return OPERATOR_FINISHED;
1479 }
1480
1481 static int gpencil_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1482 {
1483   RNA_int_set_array(op->ptr, "location", event->mval);
1484   return gpencil_select_exec(C, op);
1485 }
1486
1487 void GPENCIL_OT_select(wmOperatorType *ot)
1488 {
1489   PropertyRNA *prop;
1490
1491   /* identifiers */
1492   ot->name = "Select";
1493   ot->description = "Select Grease Pencil strokes and/or stroke points";
1494   ot->idname = "GPENCIL_OT_select";
1495
1496   /* callbacks */
1497   ot->invoke = gpencil_select_invoke;
1498   ot->exec = gpencil_select_exec;
1499   ot->poll = gpencil_select_poll;
1500
1501   /* flag */
1502   ot->flag = OPTYPE_UNDO | OPTYPE_USE_EVAL_DATA;
1503
1504   /* properties */
1505   WM_operator_properties_mouse_select(ot);
1506
1507   prop = RNA_def_boolean(ot->srna,
1508                          "entire_strokes",
1509                          false,
1510                          "Entire Strokes",
1511                          "Select entire strokes instead of just the nearest stroke vertex");
1512   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
1513
1514   prop = RNA_def_int_vector(ot->srna,
1515                             "location",
1516                             2,
1517                             NULL,
1518                             INT_MIN,
1519                             INT_MAX,
1520                             "Location",
1521                             "Mouse location",
1522                             INT_MIN,
1523                             INT_MAX);
1524   RNA_def_property_flag(prop, PROP_HIDDEN);
1525 }
1526
1527 /** \} */