GPencil: Added operators to select first and last points of strokes
[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 First */
353
354 static int gpencil_select_first_exec(bContext *C, wmOperator *op)
355 {
356         const bool only_selected = RNA_boolean_get(op->ptr, "only_selected_strokes");
357         const bool extend = RNA_boolean_get(op->ptr, "extend");
358         
359         CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
360         {
361                 /* skip stroke if we're only manipulating selected strokes */
362                 if (only_selected && !(gps->flag & GP_STROKE_SELECT)) {
363                         continue;
364                 }
365                 
366                 /* select first point */
367                 BLI_assert(gps->totpoints >= 1);
368                 
369                 gps->points->flag |= GP_SPOINT_SELECT;
370                 gps->flag |= GP_STROKE_SELECT;
371                 
372                 /* deselect rest? */
373                 if ((extend == false) && (gps->totpoints > 1)) {
374                         /* start from index 1, to skip the first point that we'd just selected... */
375                         bGPDspoint *pt = &gps->points[1];
376                         int i = 1;
377                         
378                         for (; i < gps->totpoints; i++, pt++) {
379                                 pt->flag &= ~GP_SPOINT_SELECT;
380                         }
381                 }
382         }
383         CTX_DATA_END;
384         
385         /* updates */
386         WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
387         return OPERATOR_FINISHED;
388 }
389
390 void GPENCIL_OT_select_first(wmOperatorType *ot)
391 {
392         /* identifiers */
393         ot->name = "Select First";
394         ot->idname = "GPENCIL_OT_select_first";
395         ot->description = "Select first point in Grease Pencil strokes";
396         
397         /* callbacks */
398         ot->exec = gpencil_select_first_exec;
399         ot->poll = gpencil_select_poll;
400         
401         /* flags */
402         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
403         
404         /* properties */
405         RNA_def_boolean(ot->srna, "only_selected_strokes", false, "Selected Strokes Only",
406                         "Only select the first point of strokes that already have points selected");
407         
408         RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend selection instead of deselecting all other selected points");
409 }
410
411 /* ********************************************** */
412 /* Select First */
413
414 static int gpencil_select_last_exec(bContext *C, wmOperator *op)
415 {
416         const bool only_selected = RNA_boolean_get(op->ptr, "only_selected_strokes");
417         const bool extend = RNA_boolean_get(op->ptr, "extend");
418         
419         CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
420         {
421                 /* skip stroke if we're only manipulating selected strokes */
422                 if (only_selected && !(gps->flag & GP_STROKE_SELECT)) {
423                         continue;
424                 }
425                 
426                 /* select last point */
427                 BLI_assert(gps->totpoints >= 1);
428                 
429                 gps->points[gps->totpoints - 1].flag |= GP_SPOINT_SELECT;
430                 gps->flag |= GP_STROKE_SELECT;
431                 
432                 /* deselect rest? */
433                 if ((extend == false) && (gps->totpoints > 1)) {
434                         /* don't include the last point... */
435                         bGPDspoint *pt = gps->points;
436                         int i = 1;
437                         
438                         for (; i < gps->totpoints - 1; i++, pt++) {
439                                 pt->flag &= ~GP_SPOINT_SELECT;
440                         }
441                 }
442         }
443         CTX_DATA_END;
444         
445         /* updates */
446         WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
447         return OPERATOR_FINISHED;
448 }
449
450 void GPENCIL_OT_select_last(wmOperatorType *ot)
451 {
452         /* identifiers */
453         ot->name = "Select Last";
454         ot->idname = "GPENCIL_OT_select_last";
455         ot->description = "Select last point in Grease Pencil strokes";
456         
457         /* callbacks */
458         ot->exec = gpencil_select_last_exec;
459         ot->poll = gpencil_select_poll;
460         
461         /* flags */
462         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
463         
464         /* properties */
465         RNA_def_boolean(ot->srna, "only_selected_strokes", false, "Selected Strokes Only",
466                         "Only select the last point of strokes that already have points selected");
467         
468         RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend selection instead of deselecting all other selected points");
469 }
470
471 /* ********************************************** */
472 /* Select More */
473
474 static int gpencil_select_more_exec(bContext *C, wmOperator *UNUSED(op))
475 {
476         CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
477         {
478                 if (gps->flag & GP_STROKE_SELECT) {
479                         bGPDspoint *pt;
480                         int i;
481                         bool prev_sel;
482                         
483                         /* First Pass: Go in forward order, expanding selection if previous was selected (pre changes)... 
484                          * - This pass covers the "after" edges of selection islands
485                          */
486                         prev_sel = false;
487                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
488                                 if (pt->flag & GP_SPOINT_SELECT) {
489                                         /* selected point - just set flag for next point */
490                                         prev_sel = true;
491                                 }
492                                 else {
493                                         /* unselected point - expand selection if previous was selected... */
494                                         if (prev_sel) {
495                                                 pt->flag |= GP_SPOINT_SELECT;
496                                         }
497                                         prev_sel = false;
498                                 }
499                         }
500                         
501                         /* Second Pass: Go in reverse order, doing the same as before (except in opposite order) 
502                          * - This pass covers the "before" edges of selection islands
503                          */
504                         prev_sel = false;
505                         for (pt -= 1; i > 0; i--, pt--) {
506                                 if (pt->flag & GP_SPOINT_SELECT) {
507                                         prev_sel = true;
508                                 }
509                                 else {
510                                         /* unselected point - expand selection if previous was selected... */
511                                         if (prev_sel) {
512                                                 pt->flag |= GP_SPOINT_SELECT;
513                                         }
514                                         prev_sel = false;
515                                 }
516                         }
517                 }
518         }
519         CTX_DATA_END;
520         
521         /* updates */
522         WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
523         return OPERATOR_FINISHED;
524 }
525
526 void GPENCIL_OT_select_more(wmOperatorType *ot)
527 {
528         /* identifiers */
529         ot->name = "Select More";
530         ot->idname = "GPENCIL_OT_select_more";
531         ot->description = "Grow sets of selected Grease Pencil points";
532         
533         /* callbacks */
534         ot->exec = gpencil_select_more_exec;
535         ot->poll = gpencil_select_poll;
536         
537         /* flags */
538         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
539 }
540
541 /* ********************************************** */
542 /* Select Less */
543
544 static int gpencil_select_less_exec(bContext *C, wmOperator *UNUSED(op))
545 {
546         CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
547         {
548                 if (gps->flag & GP_STROKE_SELECT) {
549                         bGPDspoint *pt;
550                         int i;
551                         bool prev_sel;
552                         
553                         /* First Pass: Go in forward order, shrinking selection if previous was not selected (pre changes)... 
554                          * - This pass covers the "after" edges of selection islands
555                          */
556                         prev_sel = false;
557                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
558                                 if (pt->flag & GP_SPOINT_SELECT) {
559                                         /* shrink if previous wasn't selected */
560                                         if (prev_sel == false) {
561                                                 pt->flag &= ~GP_SPOINT_SELECT;
562                                         }
563                                         prev_sel = true;
564                                 }
565                                 else {
566                                         /* mark previous as being unselected - and hence, is trigger for shrinking */
567                                         prev_sel = false;
568                                 }
569                         }
570                         
571                         /* Second Pass: Go in reverse order, doing the same as before (except in opposite order) 
572                          * - This pass covers the "before" edges of selection islands
573                          */
574                         prev_sel = false;
575                         for (pt -= 1; i > 0; i--, pt--) {
576                                 if (pt->flag & GP_SPOINT_SELECT) {
577                                         /* shrink if previous wasn't selected */
578                                         if (prev_sel == false) {
579                                                 pt->flag &= ~GP_SPOINT_SELECT;
580                                         }
581                                         prev_sel = true;
582                                 }
583                                 else {
584                                         /* mark previous as being unselected - and hence, is trigger for shrinking */
585                                         prev_sel = false;
586                                 }
587                         }
588                 }
589         }
590         CTX_DATA_END;
591         
592         /* updates */
593         WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
594         return OPERATOR_FINISHED;
595 }
596
597 void GPENCIL_OT_select_less(wmOperatorType *ot)
598 {
599         /* identifiers */
600         ot->name = "Select Less";
601         ot->idname = "GPENCIL_OT_select_less";
602         ot->description = "Shrink sets of selected Grease Pencil points";
603         
604         /* callbacks */
605         ot->exec = gpencil_select_less_exec;
606         ot->poll = gpencil_select_poll;
607         
608         /* flags */
609         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
610 }
611
612 /* ********************************************** */
613 /* Circle Select Operator */
614
615 /* Helper to check if a given stroke is within the area */
616 /* NOTE: Code here is adapted (i.e. copied directly) from gpencil_paint.c::gp_stroke_eraser_dostroke()
617  *       It would be great to de-duplicate the logic here sometime, but that can wait...
618  */
619 static bool gp_stroke_do_circle_sel(bGPDstroke *gps, GP_SpaceConversion *gsc,
620                                     const int mx, const int my, const int radius, 
621                                     const bool select, rcti *rect)
622 {
623         bGPDspoint *pt1, *pt2;
624         int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
625         int i;
626         bool changed = false;
627         
628         if (gps->totpoints == 1) {
629                 gp_point_to_xy(gsc, gps, gps->points, &x0, &y0);
630                 
631                 /* do boundbox check first */
632                 if ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(rect, x0, y0)) {
633                         /* only check if point is inside */
634                         if (((x0 - mx) * (x0 - mx) + (y0 - my) * (y0 - my)) <= radius * radius) {
635                                 /* change selection */
636                                 if (select) {
637                                         gps->points->flag |= GP_SPOINT_SELECT;
638                                         gps->flag |= GP_STROKE_SELECT;
639                                 }
640                                 else {
641                                         gps->points->flag &= ~GP_SPOINT_SELECT;
642                                         gps->flag &= ~GP_STROKE_SELECT;
643                                 }
644                                 
645                                 return true;
646                         }
647                 }
648         }
649         else {
650                 /* Loop over the points in the stroke, checking for intersections 
651                  *  - an intersection means that we touched the stroke
652                  */
653                 for (i = 0; (i + 1) < gps->totpoints; i++) {
654                         /* get points to work with */
655                         pt1 = gps->points + i;
656                         pt2 = gps->points + i + 1;
657                         
658                         gp_point_to_xy(gsc, gps, pt1, &x0, &y0);
659                         gp_point_to_xy(gsc, gps, pt2, &x1, &y1);
660                         
661                         /* check that point segment of the boundbox of the selection stroke */
662                         if (((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(rect, x0, y0)) ||
663                             ((!ELEM(V2D_IS_CLIPPED, x1, y1)) && BLI_rcti_isect_pt(rect, x1, y1)))
664                         {
665                                 int mval[2]  = {mx, my};
666                                 int mvalo[2] = {mx, my}; /* dummy - this isn't used... */
667                                 
668                                 /* check if point segment of stroke had anything to do with
669                                  * eraser region  (either within stroke painted, or on its lines)
670                                  *  - this assumes that linewidth is irrelevant
671                                  */
672                                 if (gp_stroke_inside_circle(mval, mvalo, radius, x0, y0, x1, y1)) {
673                                         /* change selection of stroke, and then of both points 
674                                          * (as the last point otherwise wouldn't get selected
675                                          *  as we only do n-1 loops through) 
676                                          */
677                                         if (select) {
678                                                 pt1->flag |= GP_SPOINT_SELECT;
679                                                 pt2->flag |= GP_SPOINT_SELECT;
680                                                 
681                                                 changed = true;
682                                         }
683                                         else {
684                                                 pt1->flag &= ~GP_SPOINT_SELECT;
685                                                 pt2->flag &= ~GP_SPOINT_SELECT;
686                                                 
687                                                 changed = true;
688                                         }
689                                 }
690                         }
691                 }
692                 
693                 /* Ensure that stroke selection is in sync with its points */
694                 gpencil_stroke_sync_selection(gps);
695         }
696         
697         return changed;
698 }
699
700
701 static int gpencil_circle_select_exec(bContext *C, wmOperator *op)
702 {
703         ScrArea *sa = CTX_wm_area(C);
704         
705         const int mx = RNA_int_get(op->ptr, "x");
706         const int my = RNA_int_get(op->ptr, "y");
707         const int radius = RNA_int_get(op->ptr, "radius");
708         
709         const int gesture_mode = RNA_int_get(op->ptr, "gesture_mode");
710         const bool select = (gesture_mode == GESTURE_MODAL_SELECT);
711         
712         GP_SpaceConversion gsc = {NULL};
713         rcti rect = {0};            /* for bounding rect around circle (for quicky intersection testing) */
714         
715         bool changed = false;
716         
717         
718         /* sanity checks */
719         if (sa == NULL) {
720                 BKE_report(op->reports, RPT_ERROR, "No active area");
721                 return OPERATOR_CANCELLED;
722         }
723         
724         /* init space conversion stuff */
725         gp_point_conversion_init(C, &gsc);
726         
727         
728         /* rect is rectangle of selection circle */
729         rect.xmin = mx - radius;
730         rect.ymin = my - radius;
731         rect.xmax = mx + radius;
732         rect.ymax = my + radius;
733         
734         
735         /* find visible strokes, and select if hit */
736         CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
737         {
738                 changed |= gp_stroke_do_circle_sel(gps, &gsc, mx, my, radius, select, &rect);
739         }
740         CTX_DATA_END;
741         
742         /* updates */
743         if (changed) {
744                 WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
745         }
746         
747         return OPERATOR_FINISHED;
748 }
749
750 void GPENCIL_OT_select_circle(wmOperatorType *ot)
751 {
752         /* identifiers */
753         ot->name = "Circle Select";
754         ot->description = "Select Grease Pencil strokes using brush selection";
755         ot->idname = "GPENCIL_OT_select_circle";
756         
757         /* callbacks */
758         ot->invoke = WM_gesture_circle_invoke;
759         ot->modal = WM_gesture_circle_modal;
760         ot->exec = gpencil_circle_select_exec;
761         ot->poll = gpencil_select_poll;
762         ot->cancel = WM_gesture_circle_cancel;
763         
764         /* flags */
765         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
766         
767         /* properties */
768         RNA_def_int(ot->srna, "x", 0, INT_MIN, INT_MAX, "X", "", INT_MIN, INT_MAX);
769         RNA_def_int(ot->srna, "y", 0, INT_MIN, INT_MAX, "Y", "", INT_MIN, INT_MAX);
770         RNA_def_int(ot->srna, "radius", 1, 1, INT_MAX, "Radius", "", 1, INT_MAX);
771         RNA_def_int(ot->srna, "gesture_mode", 0, INT_MIN, INT_MAX, "Gesture Mode", "", INT_MIN, INT_MAX);
772 }
773
774 /* ********************************************** */
775 /* Box Selection */
776
777 static int gpencil_border_select_exec(bContext *C, wmOperator *op)
778 {
779         ScrArea *sa = CTX_wm_area(C);
780         
781         const int gesture_mode = RNA_int_get(op->ptr, "gesture_mode");
782         const bool select = (gesture_mode == GESTURE_MODAL_SELECT);
783         const bool extend = RNA_boolean_get(op->ptr, "extend");
784         
785         GP_SpaceConversion gsc = {NULL};
786         rcti rect = {0};
787         
788         bool changed = false;
789         
790         
791         /* sanity checks */
792         if (sa == NULL) {
793                 BKE_report(op->reports, RPT_ERROR, "No active area");
794                 return OPERATOR_CANCELLED;
795         }
796         
797         /* init space conversion stuff */
798         gp_point_conversion_init(C, &gsc);
799         
800         
801         /* deselect all strokes first? */
802         if (select && !extend) {
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                                 pt->flag &= ~GP_SPOINT_SELECT;
810                         }
811                         
812                         gps->flag &= ~GP_STROKE_SELECT;
813                 }
814                 CTX_DATA_END;
815         }
816         
817         /* get settings from operator */
818         WM_operator_properties_border_to_rcti(op, &rect);
819         
820         /* select/deselect points */
821         CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
822         {
823                 bGPDspoint *pt;
824                 int i;
825                 
826                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
827                         int x0, y0;
828                         
829                         /* convert point coords to screenspace */
830                         gp_point_to_xy(&gsc, gps, pt, &x0, &y0);
831                         
832                         /* test if in selection rect */
833                         if ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(&rect, x0, y0)) {
834                                 if (select) {
835                                         pt->flag |= GP_SPOINT_SELECT;
836                                 }
837                                 else {
838                                         pt->flag &= ~GP_SPOINT_SELECT;
839                                 }
840                                 
841                                 changed = true;
842                         }
843                 }
844                 
845                 /* Ensure that stroke selection is in sync with its points */
846                 gpencil_stroke_sync_selection(gps);
847         }
848         CTX_DATA_END;
849         
850         /* updates */
851         if (changed) {
852                 WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
853         }
854         
855         return OPERATOR_FINISHED;
856 }
857
858 void GPENCIL_OT_select_border(wmOperatorType *ot)
859 {
860         /* identifiers */
861         ot->name = "Border Select";
862         ot->description = "Select Grease Pencil strokes within a rectangular region";
863         ot->idname = "GPENCIL_OT_select_border";
864         
865         /* callbacks */
866         ot->invoke = WM_border_select_invoke;
867         ot->exec = gpencil_border_select_exec;
868         ot->modal = WM_border_select_modal;
869         ot->cancel = WM_border_select_cancel;
870         
871         ot->poll = gpencil_select_poll;
872         
873         /* flags */
874         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
875         
876         /* rna */
877         WM_operator_properties_gesture_border(ot, true);
878 }
879
880 /* ********************************************** */
881 /* Lasso */
882
883 static int gpencil_lasso_select_exec(bContext *C, wmOperator *op)
884 {
885         GP_SpaceConversion gsc = {NULL};
886         rcti rect = {0};
887         
888         const bool extend = RNA_boolean_get(op->ptr, "extend");
889         const bool select = !RNA_boolean_get(op->ptr, "deselect");
890                 
891         int mcords_tot;
892         const int (*mcords)[2] = WM_gesture_lasso_path_to_array(C, op, &mcords_tot);
893         
894         bool changed = false;
895         
896         /* sanity check */
897         if (mcords == NULL)
898                 return OPERATOR_PASS_THROUGH;
899         
900         /* compute boundbox of lasso (for faster testing later) */
901         BLI_lasso_boundbox(&rect, mcords, mcords_tot);
902         
903         /* init space conversion stuff */
904         gp_point_conversion_init(C, &gsc);
905         
906         /* deselect all strokes first? */
907         if (select && !extend) {
908                 CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
909                 {
910                         bGPDspoint *pt;
911                         int i;
912                         
913                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
914                                 pt->flag &= ~GP_SPOINT_SELECT;
915                         }
916                         
917                         gps->flag &= ~GP_STROKE_SELECT;
918                 }
919                 CTX_DATA_END;
920         }
921         
922         /* select/deselect points */
923         CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
924         {
925                 bGPDspoint *pt;
926                 int i;
927                 
928                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
929                         int x0, y0;
930                         
931                         /* convert point coords to screenspace */
932                         gp_point_to_xy(&gsc, gps, pt, &x0, &y0);
933                         
934                         /* test if in lasso boundbox + within the lasso noose */
935                         if ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(&rect, x0, y0) &&
936                             BLI_lasso_is_point_inside(mcords, mcords_tot, x0, y0, INT_MAX))
937                         {
938                                 if (select) {
939                                         pt->flag |= GP_SPOINT_SELECT;
940                                 }
941                                 else {
942                                         pt->flag &= ~GP_SPOINT_SELECT;
943                                 }
944                                 
945                                 changed = true;
946                         }
947                 }
948                 
949                 /* Ensure that stroke selection is in sync with its points */
950                 gpencil_stroke_sync_selection(gps);
951         }
952         CTX_DATA_END;
953         
954         /* cleanup */
955         MEM_freeN((void *)mcords);
956         
957         /* updates */
958         if (changed) {
959                 WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
960         }
961         
962         return OPERATOR_FINISHED;
963 }
964
965 void GPENCIL_OT_select_lasso(wmOperatorType *ot)
966 {
967         ot->name = "Lasso Select Strokes";
968         ot->description = "Select Grease Pencil strokes using lasso selection";
969         ot->idname = "GPENCIL_OT_select_lasso";
970         
971         ot->invoke = WM_gesture_lasso_invoke;
972         ot->modal = WM_gesture_lasso_modal;
973         ot->exec = gpencil_lasso_select_exec;
974         ot->poll = gpencil_select_poll;
975         ot->cancel = WM_gesture_lasso_cancel;
976         
977         /* flags */
978         ot->flag = OPTYPE_UNDO;
979         
980         RNA_def_collection_runtime(ot->srna, "path", &RNA_OperatorMousePath, "Path", "");
981         RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", "Deselect rather than select items");
982         RNA_def_boolean(ot->srna, "extend", 1, "Extend", "Extend selection instead of deselecting everything first");
983 }
984
985 /* ********************************************** */
986 /* Mouse Click to Select */
987
988 static int gpencil_select_exec(bContext *C, wmOperator *op)
989 {
990         ScrArea *sa = CTX_wm_area(C);
991         
992         /* "radius" is simply a threshold (screen space) to make it easier to test with a tolerance */
993         const float radius = 0.75f * U.widget_unit;
994         const int radius_squared = (int)(radius * radius);
995         
996         bool extend = RNA_boolean_get(op->ptr, "extend");
997         bool deselect = RNA_boolean_get(op->ptr, "deselect");
998         bool toggle = RNA_boolean_get(op->ptr, "toggle");
999         bool whole = RNA_boolean_get(op->ptr, "entire_strokes");
1000         
1001         int mval[2] = {0};
1002         
1003         GP_SpaceConversion gsc = {NULL};
1004         
1005         bGPDstroke *hit_stroke = NULL;
1006         bGPDspoint *hit_point = NULL;
1007         int hit_distance = radius_squared;
1008         
1009         /* sanity checks */
1010         if (sa == NULL) {
1011                 BKE_report(op->reports, RPT_ERROR, "No active area");
1012                 return OPERATOR_CANCELLED;
1013         }
1014         
1015         /* init space conversion stuff */
1016         gp_point_conversion_init(C, &gsc);
1017         
1018         /* get mouse location */
1019         RNA_int_get_array(op->ptr, "location", mval);
1020         
1021         /* First Pass: Find stroke point which gets hit */
1022         /* XXX: maybe we should go from the top of the stack down instead... */
1023         CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
1024         {
1025                 bGPDspoint *pt;
1026                 int i;
1027                 
1028                 /* firstly, check for hit-point */
1029                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1030                         int xy[2];
1031                         
1032                         gp_point_to_xy(&gsc, gps, pt, &xy[0], &xy[1]);
1033                 
1034                         /* do boundbox check first */
1035                         if (!ELEM(V2D_IS_CLIPPED, xy[0], xy[1])) {
1036                                 const int pt_distance = len_manhattan_v2v2_int(mval, xy);
1037                                 
1038                                 /* check if point is inside */
1039                                 if (pt_distance <= radius_squared) {
1040                                         /* only use this point if it is a better match than the current hit - T44685 */
1041                                         if (pt_distance < hit_distance) {
1042                                                 hit_stroke = gps;
1043                                                 hit_point  = pt;
1044                                                 hit_distance = pt_distance;
1045                                         }
1046                                 }
1047                         }
1048                 }
1049         }
1050         CTX_DATA_END;
1051         
1052         /* Abort if nothing hit... */
1053         if (ELEM(NULL, hit_stroke, hit_point)) {
1054                 return OPERATOR_CANCELLED;
1055         }
1056         
1057         /* adjust selection behaviour - for toggle option */
1058         if (toggle) {
1059                 deselect = (hit_point->flag & GP_SPOINT_SELECT) != 0;
1060         }
1061         
1062         /* If not extending selection, deselect everything else */
1063         if (extend == false) {
1064                 CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
1065                 {                       
1066                         /* deselect stroke and its points if selected */
1067                         if (gps->flag & GP_STROKE_SELECT) {
1068                                 bGPDspoint *pt;
1069                                 int i;
1070                         
1071                                 /* deselect points */
1072                                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1073                                         pt->flag &= ~GP_SPOINT_SELECT;
1074                                 }
1075                                 
1076                                 /* deselect stroke itself too */
1077                                 gps->flag &= ~GP_STROKE_SELECT;
1078                         }
1079                 }
1080                 CTX_DATA_END;
1081         }
1082         
1083         /* Perform selection operations... */
1084         if (whole) {
1085                 bGPDspoint *pt;
1086                 int i;
1087                 
1088                 /* entire stroke's points */
1089                 for (i = 0, pt = hit_stroke->points; i < hit_stroke->totpoints; i++, pt++) {
1090                         if (deselect == false)
1091                                 pt->flag |= GP_SPOINT_SELECT;
1092                         else
1093                                 pt->flag &= ~GP_SPOINT_SELECT;
1094                 }
1095                 
1096                 /* stroke too... */
1097                 if (deselect == false)
1098                         hit_stroke->flag |= GP_STROKE_SELECT;
1099                 else
1100                         hit_stroke->flag &= ~GP_STROKE_SELECT;
1101         }
1102         else {
1103                 /* just the point (and the stroke) */
1104                 if (deselect == false) {
1105                         /* we're adding selection, so selection must be true */
1106                         hit_point->flag  |= GP_SPOINT_SELECT;
1107                         hit_stroke->flag |= GP_STROKE_SELECT;
1108                 }
1109                 else {
1110                         /* deselect point */
1111                         hit_point->flag &= ~GP_SPOINT_SELECT;
1112                         
1113                         /* ensure that stroke is selected correctly */
1114                         gpencil_stroke_sync_selection(hit_stroke);
1115                 }
1116         }
1117         
1118         /* updates */
1119         if (hit_point != NULL) {
1120                 WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
1121         }
1122         
1123         return OPERATOR_FINISHED;
1124 }
1125
1126 static int gpencil_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1127 {
1128         RNA_int_set_array(op->ptr, "location", event->mval);
1129         return gpencil_select_exec(C, op);
1130 }
1131
1132 void GPENCIL_OT_select(wmOperatorType *ot)
1133 {
1134         PropertyRNA *prop;
1135         
1136         /* identifiers */
1137         ot->name = "Select";
1138         ot->description = "Select Grease Pencil strokes and/or stroke points";
1139         ot->idname = "GPENCIL_OT_select";
1140         
1141         /* callbacks */
1142         ot->invoke = gpencil_select_invoke;
1143         ot->exec = gpencil_select_exec;
1144         ot->poll = gpencil_select_poll;
1145         
1146         /* flag */
1147         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1148         
1149         /* properties */
1150         WM_operator_properties_mouse_select(ot);
1151         
1152         prop = RNA_def_boolean(ot->srna, "entire_strokes", false, "Entire Strokes", "Select entire strokes instead of just the nearest stroke vertex");
1153         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
1154         
1155         prop = RNA_def_int_vector(ot->srna, "location", 2, NULL, INT_MIN, INT_MAX, "Location", "Mouse location", INT_MIN, INT_MAX);
1156         RNA_def_property_flag(prop, PROP_HIDDEN);
1157 }
1158
1159 /* ********************************************** */