Cleanup: style
[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) && (segmentmode)) {
1147                                                 bool hit_select = (bool)(pt->flag & GP_SPOINT_SELECT);
1148                                                 float r_hita[3], r_hitb[3];
1149                                                 ED_gpencil_select_stroke_segment(
1150                                                         gpl, gps, pt, hit_select, false, scale, r_hita, r_hitb);
1151                                         }
1152
1153                                 }
1154                         }
1155                         else {
1156                                 if (is_inside) {
1157                                         hit = true;
1158                                         break;
1159                                 }
1160                         }
1161                 }
1162
1163                 /* if stroke mode expand selection */
1164                 if (strokemode) {
1165                         const bool is_select = BKE_gpencil_stroke_select_check(gps);
1166                         const bool is_inside = hit;
1167                         const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside);
1168                         if (sel_op_result != -1) {
1169                                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1170                                         if (sel_op_result) {
1171                                                 pt->flag |= GP_SPOINT_SELECT;
1172                                         }
1173                                         else {
1174                                                 pt->flag &= ~GP_SPOINT_SELECT;
1175                                         }
1176                                 }
1177                                 changed = true;
1178                         }
1179                 }
1180
1181                 /* Ensure that stroke selection is in sync with its points */
1182                 BKE_gpencil_stroke_sync_selection(gps);
1183         }
1184         GP_EDITABLE_STROKES_END(gpstroke_iter);
1185
1186         /* if paint mode,delete selected points */
1187         if (gpd->flag & GP_DATA_STROKE_PAINTMODE) {
1188                 gp_delete_selected_point_wrap(C);
1189                 changed = true;
1190                 DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
1191         }
1192
1193         /* updates */
1194         if (changed) {
1195                 DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY);
1196
1197                 /* copy on write tag is needed, or else no refresh happens */
1198                 DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE);
1199
1200                 WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
1201                 WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL);
1202         }
1203
1204         return OPERATOR_FINISHED;
1205 }
1206
1207 /** \} */
1208
1209 /* -------------------------------------------------------------------- */
1210 /** \name Box Select Operator
1211  * \{ */
1212
1213 struct GP_SelectBoxUserData {
1214         rcti rect;
1215 };
1216
1217 static bool gpencil_test_box(
1218         bGPDstroke *gps, bGPDspoint *pt,
1219         const GP_SpaceConversion *gsc, const float diff_mat[4][4], void *user_data)
1220 {
1221         const struct GP_SelectBoxUserData *data = user_data;
1222         bGPDspoint pt2;
1223         int x0, y0;
1224         gp_point_to_parent_space(pt, diff_mat, &pt2);
1225         gp_point_to_xy(gsc, gps, &pt2, &x0, &y0);
1226         return ((!ELEM(V2D_IS_CLIPPED, x0, y0)) &&
1227                 BLI_rcti_isect_pt(&data->rect, x0, y0));
1228 }
1229
1230 static int gpencil_box_select_exec(bContext *C, wmOperator *op)
1231 {
1232         struct GP_SelectBoxUserData data = {0};
1233         WM_operator_properties_border_to_rcti(op, &data.rect);
1234         return gpencil_generic_select_exec(
1235                 C, op,
1236                 gpencil_test_box, &data);
1237 }
1238
1239 void GPENCIL_OT_select_box(wmOperatorType *ot)
1240 {
1241         /* identifiers */
1242         ot->name = "Box Select";
1243         ot->description = "Select Grease Pencil strokes within a rectangular region";
1244         ot->idname = "GPENCIL_OT_select_box";
1245
1246         /* callbacks */
1247         ot->invoke = WM_gesture_box_invoke;
1248         ot->exec = gpencil_box_select_exec;
1249         ot->modal = WM_gesture_box_modal;
1250         ot->cancel = WM_gesture_box_cancel;
1251
1252         ot->poll = gpencil_select_poll;
1253
1254         /* flags */
1255         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_USE_EVAL_DATA;
1256
1257         /* rna */
1258         WM_operator_properties_select_operation(ot);
1259         WM_operator_properties_gesture_box(ot);
1260 }
1261
1262 /** \} */
1263
1264 /* -------------------------------------------------------------------- */
1265 /** \name Lasso Select Operator
1266  * \{ */
1267
1268 struct GP_SelectLassoUserData {
1269         rcti rect;
1270         const int (*mcords)[2];
1271         int         mcords_len;
1272 };
1273
1274 static bool gpencil_test_lasso(
1275         bGPDstroke *gps, bGPDspoint *pt,
1276         const GP_SpaceConversion *gsc, const float diff_mat[4][4],
1277         void *user_data)
1278 {
1279         const struct GP_SelectLassoUserData *data = user_data;
1280         bGPDspoint pt2;
1281         int x0, y0;
1282         gp_point_to_parent_space(pt, diff_mat, &pt2);
1283         gp_point_to_xy(gsc, gps, &pt2, &x0, &y0);
1284         /* test if in lasso boundbox + within the lasso noose */
1285         return ((!ELEM(V2D_IS_CLIPPED, x0, y0)) &&
1286                 BLI_rcti_isect_pt(&data->rect, x0, y0) &&
1287                 BLI_lasso_is_point_inside(data->mcords, data->mcords_len, x0, y0, INT_MAX));
1288 }
1289
1290 static int gpencil_lasso_select_exec(bContext *C, wmOperator *op)
1291 {
1292         struct GP_SelectLassoUserData data = {0};
1293         data.mcords = WM_gesture_lasso_path_to_array(C, op, &data.mcords_len);
1294
1295         /* Sanity check. */
1296         if (data.mcords == NULL) {
1297                 return OPERATOR_PASS_THROUGH;
1298         }
1299
1300         /* Compute boundbox of lasso (for faster testing later). */
1301         BLI_lasso_boundbox(&data.rect, data.mcords, data.mcords_len);
1302
1303         int ret = gpencil_generic_select_exec(
1304                 C, op,
1305                 gpencil_test_lasso, &data);
1306
1307         MEM_freeN((void *)data.mcords);
1308
1309         return ret;
1310 }
1311
1312 void GPENCIL_OT_select_lasso(wmOperatorType *ot)
1313 {
1314         ot->name = "Lasso Select Strokes";
1315         ot->description = "Select Grease Pencil strokes using lasso selection";
1316         ot->idname = "GPENCIL_OT_select_lasso";
1317
1318         ot->invoke = WM_gesture_lasso_invoke;
1319         ot->modal = WM_gesture_lasso_modal;
1320         ot->exec = gpencil_lasso_select_exec;
1321         ot->poll = gpencil_select_poll;
1322         ot->cancel = WM_gesture_lasso_cancel;
1323
1324         /* flags */
1325         ot->flag = OPTYPE_UNDO | OPTYPE_USE_EVAL_DATA;
1326
1327         /* properties */
1328         WM_operator_properties_select_operation(ot);
1329         WM_operator_properties_gesture_lasso(ot);
1330 }
1331
1332 /** \} */
1333
1334 /* -------------------------------------------------------------------- */
1335 /** \name Mouse Pick Select Operator
1336  * \{ */
1337
1338 /* helper to deselect all selected strokes/points */
1339 static void deselect_all_selected(bContext *C)
1340 {
1341         CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
1342         {
1343                 /* deselect stroke and its points if selected */
1344                 if (gps->flag & GP_STROKE_SELECT) {
1345                         bGPDspoint *pt;
1346                         int i;
1347
1348                         /* deselect points */
1349                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1350                                 pt->flag &= ~GP_SPOINT_SELECT;
1351                         }
1352
1353                         /* deselect stroke itself too */
1354                         gps->flag &= ~GP_STROKE_SELECT;
1355                 }
1356         }
1357         CTX_DATA_END;
1358 }
1359
1360 static int gpencil_select_exec(bContext *C, wmOperator *op)
1361 {
1362         ScrArea *sa = CTX_wm_area(C);
1363         bGPdata *gpd = ED_gpencil_data_get_active(C);
1364         ToolSettings *ts = CTX_data_tool_settings(C);
1365         const float scale = ts->gp_sculpt.isect_threshold;
1366
1367         /* "radius" is simply a threshold (screen space) to make it easier to test with a tolerance */
1368         const float radius = 0.50f * U.widget_unit;
1369         const int radius_squared = (int)(radius * radius);
1370
1371         bool extend = RNA_boolean_get(op->ptr, "extend");
1372         bool deselect = RNA_boolean_get(op->ptr, "deselect");
1373         bool toggle = RNA_boolean_get(op->ptr, "toggle");
1374         bool whole = RNA_boolean_get(op->ptr, "entire_strokes");
1375
1376         int mval[2] = {0};
1377
1378         GP_SpaceConversion gsc = {NULL};
1379
1380         bGPDlayer *hit_layer = NULL;
1381         bGPDstroke *hit_stroke = NULL;
1382         bGPDspoint *hit_point = NULL;
1383         int hit_distance = radius_squared;
1384
1385         /* sanity checks */
1386         if (sa == NULL) {
1387                 BKE_report(op->reports, RPT_ERROR, "No active area");
1388                 return OPERATOR_CANCELLED;
1389         }
1390
1391         /* if select mode is stroke, use whole stroke */
1392         if (ts->gpencil_selectmode == GP_SELECTMODE_STROKE) {
1393                 whole = true;
1394         }
1395
1396         /* init space conversion stuff */
1397         gp_point_conversion_init(C, &gsc);
1398
1399         /* get mouse location */
1400         RNA_int_get_array(op->ptr, "location", mval);
1401
1402         /* First Pass: Find stroke point which gets hit */
1403         /* XXX: maybe we should go from the top of the stack down instead... */
1404         GP_EDITABLE_STROKES_BEGIN(gpstroke_iter, C, gpl, gps)
1405         {
1406                 bGPDspoint *pt;
1407                 int i;
1408
1409                 /* firstly, check for hit-point */
1410                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1411                         int xy[2];
1412
1413                         bGPDspoint pt2;
1414                         gp_point_to_parent_space(pt, gpstroke_iter.diff_mat, &pt2);
1415                         gp_point_to_xy(&gsc, gps, &pt2, &xy[0], &xy[1]);
1416
1417                         /* do boundbox check first */
1418                         if (!ELEM(V2D_IS_CLIPPED, xy[0], xy[1])) {
1419                                 const int pt_distance = len_manhattan_v2v2_int(mval, xy);
1420
1421                                 /* check if point is inside */
1422                                 if (pt_distance <= radius_squared) {
1423                                         /* only use this point if it is a better match than the current hit - T44685 */
1424                                         if (pt_distance < hit_distance) {
1425                                                 hit_layer = gpl;
1426                                                 hit_stroke = gps;
1427                                                 hit_point = pt;
1428                                                 hit_distance = pt_distance;
1429                                         }
1430                                 }
1431                         }
1432                 }
1433         }
1434         GP_EDITABLE_STROKES_END(gpstroke_iter);
1435
1436         /* Abort if nothing hit... */
1437         if (ELEM(NULL, hit_stroke, hit_point)) {
1438
1439                 /* since left mouse select change, deselect all if click outside any hit */
1440                 deselect_all_selected(C);
1441
1442                 /* copy on write tag is needed, or else no refresh happens */
1443                 DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY);
1444                 DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE);
1445                 WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
1446                 WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL);
1447
1448                 return OPERATOR_CANCELLED;
1449         }
1450
1451         /* adjust selection behavior - for toggle option */
1452         if (toggle) {
1453                 deselect = (hit_point->flag & GP_SPOINT_SELECT) != 0;
1454         }
1455
1456         /* If not extending selection, deselect everything else */
1457         if (extend == false) {
1458                 deselect_all_selected(C);
1459         }
1460
1461         /* Perform selection operations... */
1462         if (whole) {
1463                 bGPDspoint *pt;
1464                 int i;
1465
1466                 /* entire stroke's points */
1467                 for (i = 0, pt = hit_stroke->points; i < hit_stroke->totpoints; i++, pt++) {
1468                         if (deselect == false)
1469                                 pt->flag |= GP_SPOINT_SELECT;
1470                         else
1471                                 pt->flag &= ~GP_SPOINT_SELECT;
1472                 }
1473
1474                 /* stroke too... */
1475                 if (deselect == false)
1476                         hit_stroke->flag |= GP_STROKE_SELECT;
1477                 else
1478                         hit_stroke->flag &= ~GP_STROKE_SELECT;
1479         }
1480         else {
1481                 /* just the point (and the stroke) */
1482                 if (deselect == false) {
1483                         /* we're adding selection, so selection must be true */
1484                         hit_point->flag  |= GP_SPOINT_SELECT;
1485                         hit_stroke->flag |= GP_STROKE_SELECT;
1486
1487                         /* expand selection to segment */
1488                         if (ts->gpencil_selectmode == GP_SELECTMODE_SEGMENT) {
1489                                 float r_hita[3], r_hitb[3];
1490                                 bool hit_select = (bool)(hit_point->flag & GP_SPOINT_SELECT);
1491                                 ED_gpencil_select_stroke_segment(
1492                                         hit_layer, hit_stroke, hit_point, hit_select,
1493                                         false, scale, r_hita, r_hitb);
1494                         }
1495                 }
1496                 else {
1497                         /* deselect point */
1498                         hit_point->flag &= ~GP_SPOINT_SELECT;
1499
1500                         /* ensure that stroke is selected correctly */
1501                         BKE_gpencil_stroke_sync_selection(hit_stroke);
1502                 }
1503         }
1504
1505         /* updates */
1506         if (hit_point != NULL) {
1507                 DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY);
1508
1509                 /* copy on write tag is needed, or else no refresh happens */
1510                 DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE);
1511
1512                 WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
1513                 WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL);
1514         }
1515
1516         return OPERATOR_FINISHED;
1517 }
1518
1519 static int gpencil_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1520 {
1521         RNA_int_set_array(op->ptr, "location", event->mval);
1522         return gpencil_select_exec(C, op);
1523 }
1524
1525 void GPENCIL_OT_select(wmOperatorType *ot)
1526 {
1527         PropertyRNA *prop;
1528
1529         /* identifiers */
1530         ot->name = "Select";
1531         ot->description = "Select Grease Pencil strokes and/or stroke points";
1532         ot->idname = "GPENCIL_OT_select";
1533
1534         /* callbacks */
1535         ot->invoke = gpencil_select_invoke;
1536         ot->exec = gpencil_select_exec;
1537         ot->poll = gpencil_select_poll;
1538
1539         /* flag */
1540         ot->flag = OPTYPE_UNDO | OPTYPE_USE_EVAL_DATA;
1541
1542         /* properties */
1543         WM_operator_properties_mouse_select(ot);
1544
1545         prop = RNA_def_boolean(ot->srna, "entire_strokes", false, "Entire Strokes", "Select entire strokes instead of just the nearest stroke vertex");
1546         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
1547
1548         prop = RNA_def_int_vector(ot->srna, "location", 2, NULL, INT_MIN, INT_MAX, "Location", "Mouse location", INT_MIN, INT_MAX);
1549         RNA_def_property_flag(prop, PROP_HIDDEN);
1550 }
1551
1552 /** \} */