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