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