Lasso Select for GPencil Strokes
[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_math.h"
39 #include "BLI_blenlib.h"
40 #include "BLI_lasso.h"
41 #include "BLI_utildefines.h"
42
43 #include "DNA_gpencil_types.h"
44 #include "DNA_object_types.h"
45 #include "DNA_scene_types.h"
46 #include "DNA_screen_types.h"
47 #include "DNA_space_types.h"
48 #include "DNA_view3d_types.h"
49
50 #include "BKE_context.h"
51 #include "BKE_curve.h"
52 #include "BKE_depsgraph.h"
53 #include "BKE_global.h"
54 #include "BKE_gpencil.h"
55 #include "BKE_library.h"
56 #include "BKE_object.h"
57 #include "BKE_report.h"
58 #include "BKE_scene.h"
59 #include "BKE_screen.h"
60
61 #include "UI_interface.h"
62
63 #include "WM_api.h"
64 #include "WM_types.h"
65
66 #include "RNA_access.h"
67 #include "RNA_define.h"
68
69 #include "UI_view2d.h"
70
71 #include "ED_gpencil.h"
72 #include "ED_view3d.h"
73 #include "ED_keyframing.h"
74
75 #include "gpencil_intern.h"
76
77 /* ********************************************** */
78 /* Polling callbacks */
79
80 static int gpencil_select_poll(bContext *C)
81 {
82         bGPdata *gpd = ED_gpencil_data_get_active(C);
83         bGPDlayer *gpl = gpencil_layer_getactive(gpd);
84
85         /* only if there's an active layer with an active frame */
86         return (gpl && gpl->actframe);
87 }
88
89 /* ********************************************** */
90 /* Select All Operator */
91
92 static int gpencil_select_all_exec(bContext *C, wmOperator *op)
93 {
94         bGPdata *gpd = ED_gpencil_data_get_active(C);
95         int action = RNA_enum_get(op->ptr, "action");
96         
97         if (gpd == NULL) {
98                 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
99                 return OPERATOR_CANCELLED;
100         }
101         
102         /* for "toggle", test for existing selected strokes */
103         if (action == SEL_TOGGLE) {
104                 action = SEL_SELECT;
105                 
106                 CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
107                 {
108                         if (gps->flag & GP_STROKE_SELECT) {
109                                 action = SEL_DESELECT;
110                                 break; // XXX: this only gets out of the inner loop...
111                         }
112                 }
113                 CTX_DATA_END;
114         }
115         
116         /* if deselecting, we need to deselect strokes across all frames
117          *  - Currently, an exception is only given for deselection
118          *    Selecting and toggling should only affect what's visible,
119          *    while deselecting helps clean up unintended/forgotten
120          *    stuff on other frames
121          */
122         if (action == SEL_DESELECT) {
123                 /* deselect strokes across editable layers
124                  * NOTE: we limit ourselves to editable layers, since once a layer is "locked/hidden
125                  *       nothing should be able to touch it
126                  */
127                 CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
128                 {
129                         bGPDframe *gpf;
130                         
131                         /* deselect all strokes on all frames */
132                         for (gpf = gpl->frames.first; gpf; gpf = gpf->next) {
133                                 bGPDstroke *gps;
134                                 
135                                 for (gps = gpf->strokes.first; gps; gps = gps->next) {
136                                         bGPDspoint *pt;
137                                         int i;
138                                         
139                                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
140                                                 pt->flag &= ~GP_SPOINT_SELECT;
141                                         }
142                                         
143                                         gps->flag &= ~GP_STROKE_SELECT;
144                                 }
145                         }
146                 }
147                 CTX_DATA_END;
148         }
149         else {
150                 /* select or deselect all strokes */
151                 CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
152                 {
153                         bGPDspoint *pt;
154                         int i;
155                         bool selected = false;
156                         
157                         /* Change selection status of all points, then make the stroke match */
158                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
159                                 switch (action) {
160                                         case SEL_SELECT:
161                                                 pt->flag |= GP_SPOINT_SELECT;
162                                                 break;
163                                         //case SEL_DESELECT:
164                                         //      pt->flag &= ~GP_SPOINT_SELECT;
165                                         //      break;
166                                         case SEL_INVERT:
167                                                 pt->flag ^= GP_SPOINT_SELECT;
168                                                 break;
169                                 }
170                                 
171                                 if (pt->flag & GP_SPOINT_SELECT)
172                                         selected = true;
173                         }
174                         
175                         /* Change status of stroke */
176                         if (selected)
177                                 gps->flag |= GP_STROKE_SELECT;
178                         else
179                                 gps->flag &= ~GP_STROKE_SELECT;
180                 }
181                 CTX_DATA_END;
182         }
183         
184         /* updates */
185         WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
186         return OPERATOR_FINISHED;
187 }
188
189 void GPENCIL_OT_select_all(wmOperatorType *ot)
190 {
191         /* identifiers */
192         ot->name = "(De)select All Strokes";
193         ot->idname = "GPENCIL_OT_select_all";
194         ot->description = "Change selection of all Grease Pencil strokes currently visible";
195         
196         /* callbacks */
197         ot->exec = gpencil_select_all_exec;
198         ot->poll = gpencil_select_poll;
199         
200         /* flags */
201         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
202         
203         WM_operator_properties_select_all(ot);
204 }
205
206 /* ********************************************** */
207 /* Select Linked */
208
209 static int gpencil_select_linked_exec(bContext *C, wmOperator *op)
210 {
211         bGPdata *gpd = ED_gpencil_data_get_active(C);
212         
213         if (gpd == NULL) {
214                 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
215                 return OPERATOR_CANCELLED;
216         }
217         
218         /* select all points in selected strokes */
219         CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
220         {
221                 if (gps->flag & GP_STROKE_SELECT) {
222                         bGPDspoint *pt;
223                         int i;
224                         
225                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
226                                 pt->flag |= GP_SPOINT_SELECT;
227                         }
228                 }
229         }
230         CTX_DATA_END;
231         
232         /* updates */
233         WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
234         return OPERATOR_FINISHED;
235 }
236
237 void GPENCIL_OT_select_linked(wmOperatorType *ot)
238 {
239         /* identifiers */
240         ot->name = "Select Linked";
241         ot->idname = "GPENCIL_OT_select_linked";
242         ot->description = "Select all points in same strokes as already selected points";
243         
244         /* callbacks */
245         ot->exec = gpencil_select_linked_exec;
246         ot->poll = gpencil_select_poll;
247         
248         /* flags */
249         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
250 }
251
252 /* ********************************************** */
253 /* Select More */
254
255 static int gpencil_select_more_exec(bContext *C, wmOperator *UNUSED(op))
256 {
257         CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
258         {
259                 if (gps->flag & GP_STROKE_SELECT) {
260                         bGPDspoint *pt;
261                         int i;
262                         bool prev_sel;
263                         
264                         /* First Pass: Go in forward order, expanding selection if previous was selected (pre changes)... 
265                          * - This pass covers the "after" edges of selection islands
266                          */
267                         prev_sel = false;
268                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
269                                 if (pt->flag & GP_SPOINT_SELECT) {
270                                         /* selected point - just set flag for next point */
271                                         prev_sel = true;
272                                 }
273                                 else {
274                                         /* unselected point - expand selection if previous was selected... */
275                                         if (prev_sel) {
276                                                 pt->flag |= GP_SPOINT_SELECT;
277                                         }
278                                         prev_sel = false;
279                                 }
280                         }
281                         
282                         /* Second Pass: Go in reverse order, doing the same as before (except in opposite order) 
283                          * - This pass covers the "before" edges of selection islands
284                          */
285                         prev_sel = false;
286                         for (pt -= 1; i > 0; i--, pt--) {
287                                 if (pt->flag & GP_SPOINT_SELECT) {
288                                         prev_sel = true;
289                                 }
290                                 else {
291                                         /* unselected point - expand selection if previous was selected... */
292                                         if (prev_sel) {
293                                                 pt->flag |= GP_SPOINT_SELECT;
294                                         }
295                                         prev_sel = false;
296                                 }
297                         }
298                 }
299         }
300         CTX_DATA_END;
301         
302         /* updates */
303         WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
304         return OPERATOR_FINISHED;
305 }
306
307 void GPENCIL_OT_select_more(wmOperatorType *ot)
308 {
309         /* identifiers */
310         ot->name = "Select More";
311         ot->idname = "GPENCIL_OT_select_more";
312         ot->description = "Grow sets of selected Grease Pencil points";
313         
314         /* callbacks */
315         ot->exec = gpencil_select_more_exec;
316         ot->poll = gpencil_select_poll;
317         
318         /* flags */
319         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
320 }
321
322 /* ********************************************** */
323 /* Select Less */
324
325 static int gpencil_select_less_exec(bContext *C, wmOperator *UNUSED(op))
326 {
327         CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
328         {
329                 if (gps->flag & GP_STROKE_SELECT) {
330                         bGPDspoint *pt;
331                         int i;
332                         bool prev_sel;
333                         
334                         /* First Pass: Go in forward order, shrinking selection if previous was not selected (pre changes)... 
335                          * - This pass covers the "after" edges of selection islands
336                          */
337                         prev_sel = false;
338                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
339                                 if (pt->flag & GP_SPOINT_SELECT) {
340                                         /* shrink if previous wasn't selected */
341                                         if (prev_sel == false) {
342                                                 pt->flag &= ~GP_SPOINT_SELECT;
343                                         }
344                                         prev_sel = true;
345                                 }
346                                 else {
347                                         /* mark previous as being unselected - and hence, is trigger for shrinking */
348                                         prev_sel = false;
349                                 }
350                         }
351                         
352                         /* Second Pass: Go in reverse order, doing the same as before (except in opposite order) 
353                          * - This pass covers the "before" edges of selection islands
354                          */
355                         prev_sel = false;
356                         for (pt -= 1; i > 0; i--, pt--) {
357                                 if (pt->flag & GP_SPOINT_SELECT) {
358                                         /* shrink if previous wasn't selected */
359                                         if (prev_sel == false) {
360                                                 pt->flag &= ~GP_SPOINT_SELECT;
361                                         }
362                                         prev_sel = true;
363                                 }
364                                 else {
365                                         /* mark previous as being unselected - and hence, is trigger for shrinking */
366                                         prev_sel = false;
367                                 }
368                         }
369                 }
370         }
371         CTX_DATA_END;
372         
373         /* updates */
374         WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
375         return OPERATOR_FINISHED;
376 }
377
378 void GPENCIL_OT_select_less(wmOperatorType *ot)
379 {
380         /* identifiers */
381         ot->name = "Select Less";
382         ot->idname = "GPENCIL_OT_select_less";
383         ot->description = "Shrink sets of selected Grease Pencil points";
384         
385         /* callbacks */
386         ot->exec = gpencil_select_less_exec;
387         ot->poll = gpencil_select_poll;
388         
389         /* flags */
390         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
391 }
392
393 /* ********************************************** */
394 /* Circle Select Operator */
395
396 /* Helper to check if a given stroke is within the area */
397 /* NOTE: Code here is adapted (i.e. copied directly) from gpencil_paint.c::gp_stroke_eraser_dostroke()
398  *       It would be great to de-duplicate the logic here sometime, but that can wait...
399  */
400 static bool gp_stroke_do_circle_sel(bGPDstroke *gps, GP_SpaceConversion *gsc,
401                                     const int mx, const int my, const int radius, 
402                                     const bool select, rcti *rect)
403 {
404         bGPDspoint *pt1, *pt2;
405         int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
406         int i;
407         bool changed = false;
408         
409         if (gps->totpoints == 1) {
410                 gp_point_to_xy(gsc, gps, gps->points, &x0, &y0);
411                 
412                 /* do boundbox check first */
413                 if ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(rect, x0, y0)) {
414                         /* only check if point is inside */
415                         if (((x0 - mx) * (x0 - mx) + (y0 - my) * (y0 - my)) <= radius * radius) {
416                                 /* change selection */
417                                 if (select) {
418                                         gps->points->flag |= GP_SPOINT_SELECT;
419                                         gps->flag |= GP_STROKE_SELECT;
420                                 }
421                                 else {
422                                         gps->points->flag &= ~GP_SPOINT_SELECT;
423                                         gps->flag &= ~GP_STROKE_SELECT;
424                                 }
425                                 
426                                 return true;
427                         }
428                 }
429         }
430         else {
431                 /* Loop over the points in the stroke, checking for intersections 
432                  *  - an intersection means that we touched the stroke
433                  */
434                 for (i = 0; (i + 1) < gps->totpoints; i++) {
435                         /* get points to work with */
436                         pt1 = gps->points + i;
437                         pt2 = gps->points + i + 1;
438                         
439                         gp_point_to_xy(gsc, gps, pt1, &x0, &y0);
440                         gp_point_to_xy(gsc, gps, pt2, &x1, &y1);
441                         
442                         /* check that point segment of the boundbox of the selection stroke */
443                         if (((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(rect, x0, y0)) ||
444                             ((!ELEM(V2D_IS_CLIPPED, x1, y1)) && BLI_rcti_isect_pt(rect, x1, y1)))
445                         {
446                                 int mval[2]  = {mx, my};
447                                 int mvalo[2] = {mx, my}; /* dummy - this isn't used... */
448                                 
449                                 /* check if point segment of stroke had anything to do with
450                                  * eraser region  (either within stroke painted, or on its lines)
451                                  *  - this assumes that linewidth is irrelevant
452                                  */
453                                 if (gp_stroke_inside_circle(mval, mvalo, radius, x0, y0, x1, y1)) {
454                                         /* change selection of stroke, and then of both points 
455                                          * (as the last point otherwise wouldn't get selected
456                                          *  as we only do n-1 loops through) 
457                                          */
458                                         if (select) {
459                                                 pt1->flag |= GP_SPOINT_SELECT;
460                                                 pt2->flag |= GP_SPOINT_SELECT;
461                                                 
462                                                 changed = true;
463                                         }
464                                         else {
465                                                 pt1->flag &= ~GP_SPOINT_SELECT;
466                                                 pt2->flag &= ~GP_SPOINT_SELECT;
467                                                 
468                                                 changed = true;
469                                         }
470                                 }
471                         }
472                 }
473                 
474                 /* Ensure that stroke selection is in sync with its points */
475                 gpencil_stroke_sync_selection(gps);
476         }
477         
478         return changed;
479 }
480
481
482 static int gpencil_circle_select_exec(bContext *C, wmOperator *op)
483 {
484         ScrArea *sa = CTX_wm_area(C);
485         
486         const int mx = RNA_int_get(op->ptr, "x");
487         const int my = RNA_int_get(op->ptr, "y");
488         const int radius = RNA_int_get(op->ptr, "radius");
489         
490         const int gesture_mode = RNA_int_get(op->ptr, "gesture_mode");
491         const bool select = (gesture_mode == GESTURE_MODAL_SELECT);
492         
493         GP_SpaceConversion gsc = {NULL};
494         rcti rect = {0};            /* for bounding rect around circle (for quicky intersection testing) */
495         
496         bool changed = false;
497         
498         
499         /* sanity checks */
500         if (sa == NULL) {
501                 BKE_report(op->reports, RPT_ERROR, "No active area");
502                 return OPERATOR_CANCELLED;
503         }
504         
505         /* init space conversion stuff */
506         gp_point_conversion_init(C, &gsc);
507         
508         
509         /* rect is rectangle of selection circle */
510         rect.xmin = mx - radius;
511         rect.ymin = my - radius;
512         rect.xmax = mx + radius;
513         rect.ymax = my + radius;
514         
515         
516         /* find visible strokes, and select if hit */
517         CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
518         {
519                 changed |= gp_stroke_do_circle_sel(gps, &gsc, mx, my, radius, select, &rect);
520         }
521         CTX_DATA_END;
522         
523         /* updates */
524         if (changed) {
525                 WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
526         }
527         
528         return OPERATOR_FINISHED;
529 }
530
531 void GPENCIL_OT_select_circle(wmOperatorType *ot)
532 {
533         /* identifiers */
534         ot->name = "Circle Select";
535         ot->description = "Select Grease Pencil strokes using brush selection";
536         ot->idname = "GPENCIL_OT_select_circle";
537         
538         /* callbacks */
539         ot->invoke = WM_gesture_circle_invoke;
540         ot->modal = WM_gesture_circle_modal;
541         ot->exec = gpencil_circle_select_exec;
542         ot->poll = gpencil_select_poll;
543         ot->cancel = WM_gesture_circle_cancel;
544         
545         /* flags */
546         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
547         
548         /* properties */
549         RNA_def_int(ot->srna, "x", 0, INT_MIN, INT_MAX, "X", "", INT_MIN, INT_MAX);
550         RNA_def_int(ot->srna, "y", 0, INT_MIN, INT_MAX, "Y", "", INT_MIN, INT_MAX);
551         RNA_def_int(ot->srna, "radius", 1, 1, INT_MAX, "Radius", "", 1, INT_MAX);
552         RNA_def_int(ot->srna, "gesture_mode", 0, INT_MIN, INT_MAX, "Gesture Mode", "", INT_MIN, INT_MAX);
553 }
554
555 /* ********************************************** */
556 /* Box Selection */
557
558 static int gpencil_border_select_exec(bContext *C, wmOperator *op)
559 {
560         ScrArea *sa = CTX_wm_area(C);
561         
562         const int gesture_mode = RNA_int_get(op->ptr, "gesture_mode");
563         const bool select = (gesture_mode == GESTURE_MODAL_SELECT);
564         const bool extend = RNA_boolean_get(op->ptr, "extend");
565         
566         GP_SpaceConversion gsc = {NULL};
567         rcti rect = {0};
568         
569         bool changed = false;
570         
571         
572         /* sanity checks */
573         if (sa == NULL) {
574                 BKE_report(op->reports, RPT_ERROR, "No active area");
575                 return OPERATOR_CANCELLED;
576         }
577         
578         /* init space conversion stuff */
579         gp_point_conversion_init(C, &gsc);
580         
581         
582         /* deselect all strokes first? */
583         if (select && !extend) {
584                 CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
585                 {
586                         bGPDspoint *pt;
587                         int i;
588                         
589                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
590                                 pt->flag &= ~GP_SPOINT_SELECT;
591                         }
592                         
593                         gps->flag &= ~GP_STROKE_SELECT;
594                 }
595                 CTX_DATA_END;
596         }
597         
598         /* get settings from operator */
599         WM_operator_properties_border_to_rcti(op, &rect);
600         
601         /* select/deselect points */
602         CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
603         {
604                 bGPDspoint *pt;
605                 int i;
606                 
607                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
608                         int x0, y0;
609                         
610                         /* convert point coords to screenspace */
611                         gp_point_to_xy(&gsc, gps, pt, &x0, &y0);
612                         
613                         /* test if in selection rect */
614                         if ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(&rect, x0, y0)) {
615                                 if (select) {
616                                         pt->flag |= GP_SPOINT_SELECT;
617                                 }
618                                 else {
619                                         pt->flag &= ~GP_SPOINT_SELECT;
620                                 }
621                                 
622                                 changed = true;
623                         }
624                 }
625                 
626                 /* Ensure that stroke selection is in sync with its points */
627                 gpencil_stroke_sync_selection(gps);
628         }
629         CTX_DATA_END;
630         
631         /* updates */
632         if (changed) {
633                 WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
634         }
635         
636         return OPERATOR_FINISHED;
637 }
638
639 void GPENCIL_OT_select_border(wmOperatorType *ot)
640 {
641         /* identifiers */
642         ot->name = "Border Select";
643         ot->description = "Select Grease Pencil strokes within a rectangular region";
644         ot->idname = "GPENCIL_OT_select_border";
645         
646         /* callbacks */
647         ot->invoke = WM_border_select_invoke;
648         ot->exec = gpencil_border_select_exec;
649         ot->modal = WM_border_select_modal;
650         ot->cancel = WM_border_select_cancel;
651         
652         ot->poll = gpencil_select_poll;
653         
654         /* flags */
655         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
656         
657         /* rna */
658         WM_operator_properties_gesture_border(ot, true);
659 }
660
661 /* ********************************************** */
662 /* Lasso */
663
664 static int gpencil_lasso_select_exec(bContext *C, wmOperator *op)
665 {
666         GP_SpaceConversion gsc = {NULL};
667         rcti rect = {0};
668         
669         const bool extend = RNA_boolean_get(op->ptr, "extend");
670         const bool select = !RNA_boolean_get(op->ptr, "deselect");
671                 
672         int mcords_tot;
673         const int (*mcords)[2] = WM_gesture_lasso_path_to_array(C, op, &mcords_tot);
674         
675         bool changed = false;
676         
677         /* sanity check */
678         if (mcords == NULL)
679                 return OPERATOR_PASS_THROUGH;
680         
681         /* compute boundbox of lasso (for faster testing later) */
682         BLI_lasso_boundbox(&rect, mcords, mcords_tot);
683         
684         /* init space conversion stuff */
685         gp_point_conversion_init(C, &gsc);
686         
687         /* deselect all strokes first? */
688         if (select && !extend) {
689                 CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
690                 {
691                         bGPDspoint *pt;
692                         int i;
693                         
694                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
695                                 pt->flag &= ~GP_SPOINT_SELECT;
696                         }
697                         
698                         gps->flag &= ~GP_STROKE_SELECT;
699                 }
700                 CTX_DATA_END;
701         }
702         
703         /* select/deselect points */
704         CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
705         {
706                 bGPDspoint *pt;
707                 int i;
708                 
709                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
710                         int x0, y0;
711                         
712                         /* convert point coords to screenspace */
713                         gp_point_to_xy(&gsc, gps, pt, &x0, &y0);
714                         
715                         /* test if in lasso boundbox + within the lasso noose */
716                         if ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(&rect, x0, y0) &&
717                                 BLI_lasso_is_point_inside(mcords, mcords_tot, x0, y0, INT_MAX)) 
718                         {
719                                 if (select) {
720                                         pt->flag |= GP_SPOINT_SELECT;
721                                 }
722                                 else {
723                                         pt->flag &= ~GP_SPOINT_SELECT;
724                                 }
725                                 
726                                 changed = true;
727                         }
728                 }
729                 
730                 /* Ensure that stroke selection is in sync with its points */
731                 gpencil_stroke_sync_selection(gps);
732         }
733         CTX_DATA_END;
734         
735         /* cleanup */
736         MEM_freeN((void *)mcords);
737         
738         /* updates */
739         if (changed) {
740                 WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
741         }
742         
743         return OPERATOR_FINISHED;
744 }
745
746 void GPENCIL_OT_select_lasso(wmOperatorType *ot)
747 {
748         ot->name = "Lasso Select Strokes";
749         ot->description = "Select Grease Pencil strokes using lasso selection";
750         ot->idname = "GPENCIL_OT_select_lasso";
751         
752         ot->invoke = WM_gesture_lasso_invoke;
753         ot->modal = WM_gesture_lasso_modal;
754         ot->exec = gpencil_lasso_select_exec;
755         ot->poll = gpencil_select_poll;
756         ot->cancel = WM_gesture_lasso_cancel;
757         
758         /* flags */
759         ot->flag = OPTYPE_UNDO;
760         
761         RNA_def_collection_runtime(ot->srna, "path", &RNA_OperatorMousePath, "Path", "");
762         RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", "Deselect rather than select items");
763         RNA_def_boolean(ot->srna, "extend", 1, "Extend", "Extend selection instead of deselecting everything first");
764 }
765
766 /* ********************************************** */
767 /* Mouse Click to Select */
768
769 static int gpencil_select_exec(bContext *C, wmOperator *op)
770 {
771         ScrArea *sa = CTX_wm_area(C);
772         
773         /* "radius" is simply a threshold (screen space) to make it easier to test with a tolerance */
774         const float radius = 0.75f * U.widget_unit;
775         const int radius_squared = (int)(radius * radius);
776         
777         bool extend = RNA_boolean_get(op->ptr, "extend");
778         bool deselect = RNA_boolean_get(op->ptr, "deselect");
779         bool toggle = RNA_boolean_get(op->ptr, "toggle");
780         bool whole = RNA_boolean_get(op->ptr, "entire_strokes");
781         
782         int location[2] = {0};
783         int mx, my;
784         
785         GP_SpaceConversion gsc = {NULL};
786         
787         bGPDstroke *hit_stroke = NULL;
788         bGPDspoint *hit_point = NULL;
789         
790         /* sanity checks */
791         if (sa == NULL) {
792                 BKE_report(op->reports, RPT_ERROR, "No active area");
793                 return OPERATOR_CANCELLED;
794         }
795         
796         /* init space conversion stuff */
797         gp_point_conversion_init(C, &gsc);
798         
799         /* get mouse location */
800         RNA_int_get_array(op->ptr, "location", location);
801         
802         mx = location[0];
803         my = location[1];
804         
805         /* First Pass: Find stroke point which gets hit */
806         /* XXX: maybe we should go from the top of the stack down instead... */
807         CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
808         {
809                 bGPDspoint *pt;
810                 int i;
811                 int hit_index = -1;
812                 
813                 /* firstly, check for hit-point */
814                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
815                         int x0, y0;
816                         
817                         gp_point_to_xy(&gsc, gps, pt, &x0, &y0);
818                 
819                         /* do boundbox check first */
820                         if (!ELEM(V2D_IS_CLIPPED, x0, x0)) {
821                                 /* only check if point is inside */
822                                 if (((x0 - mx) * (x0 - mx) + (y0 - my) * (y0 - my)) <= radius_squared) {                                
823                                         hit_stroke = gps;
824                                         hit_point  = pt;
825                                         break;
826                                 }
827                         }
828                 }
829                 
830                 /* skip to next stroke if nothing found */
831                 if (hit_index == -1) 
832                         continue;
833         }
834         CTX_DATA_END;
835         
836         /* Abort if nothing hit... */
837         if (ELEM(NULL, hit_stroke, hit_point)) {
838                 return OPERATOR_CANCELLED;
839         }
840         
841         /* adjust selection behaviour - for toggle option */
842         if (toggle) {
843                 deselect = (hit_point->flag & GP_SPOINT_SELECT) != 0;
844         }
845         
846         /* If not extending selection, deselect everything else */
847         if (extend == false) {
848                 CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
849                 {                       
850                         /* deselect stroke and its points if selected */
851                         if (gps->flag & GP_STROKE_SELECT) {
852                                 bGPDspoint *pt;
853                                 int i;
854                         
855                                 /* deselect points */
856                                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
857                                         pt->flag &= ~GP_SPOINT_SELECT;
858                                 }
859                                 
860                                 /* deselect stroke itself too */
861                                 gps->flag &= ~GP_STROKE_SELECT;
862                         }
863                 }
864                 CTX_DATA_END;
865         }
866         
867         /* Perform selection operations... */
868         if (whole) {
869                 bGPDspoint *pt;
870                 int i;
871                 
872                 /* entire stroke's points */
873                 for (i = 0, pt = hit_stroke->points; i < hit_stroke->totpoints; i++, pt++) {
874                         if (deselect == false)
875                                 pt->flag |= GP_SPOINT_SELECT;
876                         else
877                                 pt->flag &= ~GP_SPOINT_SELECT;
878                 }
879                 
880                 /* stroke too... */
881                 if (deselect == false)
882                         hit_stroke->flag |= GP_STROKE_SELECT;
883                 else
884                         hit_stroke->flag &= ~GP_STROKE_SELECT;
885         }
886         else {
887                 /* just the point (and the stroke) */
888                 if (deselect == false) {
889                         /* we're adding selection, so selection must be true */
890                         hit_point->flag  |= GP_SPOINT_SELECT;
891                         hit_stroke->flag |= GP_STROKE_SELECT;
892                 }
893                 else {
894                         /* deselect point */
895                         hit_point->flag &= ~GP_SPOINT_SELECT;
896                         
897                         /* ensure that stroke is selected correctly */
898                         gpencil_stroke_sync_selection(hit_stroke);
899                 }
900         }
901         
902         /* updates */
903         if (hit_point != NULL) {
904                 WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
905         }
906         
907         return OPERATOR_FINISHED;
908 }
909
910 static int gpencil_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
911 {
912         RNA_int_set_array(op->ptr, "location", event->mval);
913         return gpencil_select_exec(C, op);
914 }
915
916 void GPENCIL_OT_select(wmOperatorType *ot)
917 {
918         PropertyRNA *prop;
919         
920         /* identifiers */
921         ot->name = "Select";
922         ot->description = "Select Grease Pencil strokes and/or stroke points";
923         ot->idname = "GPENCIL_OT_select";
924         
925         /* callbacks */
926         ot->invoke = gpencil_select_invoke;
927         ot->exec = gpencil_select_exec;
928         ot->poll = gpencil_select_poll;
929         
930         /* flag */
931         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
932         
933         /* properties */
934         WM_operator_properties_mouse_select(ot);
935         
936         prop = RNA_def_boolean(ot->srna, "entire_strokes", false, "Entire Strokes", "Select entire strokes instead of just the nearest stroke vertex");
937         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
938         
939         prop = RNA_def_int_vector(ot->srna, "location", 2, NULL, INT_MIN, INT_MAX, "Location", "Mouse location", INT_MIN, INT_MAX);
940         RNA_def_property_flag(prop, PROP_HIDDEN);
941 }
942
943 /* ********************************************** */