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