Code Cleanup: Reshuffling some of the GPencil code
[blender.git] / source / blender / editors / gpencil / gpencil_edit.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) 2008, Blender Foundation, Joshua Leung
19  * This is a new part of Blender
20  *
21  * Contributor(s): Joshua Leung
22  *
23  * ***** END GPL LICENSE BLOCK *****
24  *
25  * Operators for editing Grease Pencil strokes
26  */
27
28 /** \file blender/editors/gpencil/gpencil_edit.c
29  *  \ingroup edgpencil
30  */
31
32
33 #include <stdio.h>
34 #include <string.h>
35 #include <stdlib.h>
36 #include <stddef.h>
37 #include <math.h>
38
39 #include "MEM_guardedalloc.h"
40
41 #include "BLI_math.h"
42 #include "BLI_blenlib.h"
43 #include "BLI_rand.h"
44 #include "BLI_utildefines.h"
45
46 #include "BLF_translation.h"
47
48 #include "DNA_anim_types.h"
49 #include "DNA_curve_types.h"
50 #include "DNA_object_types.h"
51 #include "DNA_node_types.h"
52 #include "DNA_scene_types.h"
53 #include "DNA_screen_types.h"
54 #include "DNA_space_types.h"
55 #include "DNA_view3d_types.h"
56 #include "DNA_gpencil_types.h"
57
58 #include "BKE_context.h"
59 #include "BKE_curve.h"
60 #include "BKE_depsgraph.h"
61 #include "BKE_fcurve.h"
62 #include "BKE_global.h"
63 #include "BKE_gpencil.h"
64 #include "BKE_library.h"
65 #include "BKE_object.h"
66 #include "BKE_report.h"
67 #include "BKE_scene.h"
68 #include "BKE_screen.h"
69 #include "BKE_tracking.h"
70
71 #include "UI_interface.h"
72
73 #include "WM_api.h"
74 #include "WM_types.h"
75
76 #include "RNA_access.h"
77 #include "RNA_define.h"
78
79 #include "UI_view2d.h"
80
81 #include "ED_gpencil.h"
82 #include "ED_view3d.h"
83 #include "ED_clip.h"
84 #include "ED_keyframing.h"
85
86 #include "gpencil_intern.h"
87
88 /* ************************************************ */
89 /* Stroke Editing Operators */
90
91 /* poll callback for all stroke editing operators */
92 static int gp_stroke_edit_poll(bContext *C)
93 {
94         /* NOTE: this is a bit slower, but is the most accurate... */
95         return CTX_DATA_COUNT(C, editable_gpencil_strokes) != 0;
96 }
97
98 /* ************** Duplicate Selected Strokes **************** */
99
100 /* Make copies of selected point segments in a selected stroke */
101 static void gp_duplicate_points(const bGPDstroke *gps, ListBase *new_strokes)
102 {
103         bGPDspoint *pt;
104         int i;
105         
106         int start_idx = -1;
107         
108         
109         /* Step through the original stroke's points:
110          * - We accumulate selected points (from start_idx to current index)
111          *   and then convert that to a new stroke
112          */
113         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
114                 /* searching for start, are waiting for end? */
115                 if (start_idx == -1) {
116                         /* is this the first selected point for a new island? */
117                         if (pt->flag & GP_SPOINT_SELECT) {
118                                 start_idx = i;
119                         }
120                 }
121                 else {
122                         size_t len = 0;
123                         
124                         /* is this the end of current island yet?
125                          * 1) Point i-1 was the last one that was selected
126                          * 2) Point i is the last in the array
127                          */
128                         if ((pt->flag & GP_SPOINT_SELECT) == 0) {
129                                 len = i - start_idx;
130                         }
131                         else if (i == gps->totpoints - 1) {
132                                 len = i - start_idx + 1;
133                         }
134                         //printf("copying from %d to %d = %d\n", start_idx, i, len);
135                 
136                         /* make copies of the relevant data */
137                         if (len) {
138                                 bGPDstroke *gpsd;
139                                 
140                                 /* make a stupid copy first of the entire stroke (to get the flags too) */
141                                 gpsd = MEM_dupallocN(gps);
142                                 
143                                 /* now, make a new points array, and copy of the relevant parts */
144                                 gpsd->points = MEM_callocN(sizeof(bGPDspoint) * len, "gps stroke points copy");
145                                 memcpy(gpsd->points, gps->points + start_idx, sizeof(bGPDspoint) * len);
146                                 gpsd->totpoints = len;
147                                 
148                                 /* add to temp buffer */
149                                 gpsd->next = gpsd->prev = NULL;
150                                 BLI_addtail(new_strokes, gpsd);
151                                 
152                                 /* cleanup + reset for next */
153                                 start_idx = -1;
154                         }
155                 }
156         }
157 }
158
159 static int gp_duplicate_exec(bContext *C, wmOperator *op)
160 {
161         bGPdata *gpd = ED_gpencil_data_get_active(C);
162         
163         if (gpd == NULL) {
164                 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
165                 return OPERATOR_CANCELLED;
166         }
167         
168         /* for each visible (and editable) layer's selected strokes,
169          * copy the strokes into a temporary buffer, then append
170          * once all done
171          */
172         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
173         {
174                 ListBase new_strokes = {NULL, NULL};
175                 bGPDframe *gpf = gpl->actframe;
176                 bGPDstroke *gps;
177                 
178                 if (gpf == NULL)
179                         continue;
180                 
181                 /* make copies of selected strokes, and deselect these once we're done */
182                 for (gps = gpf->strokes.first; gps; gps = gps->next) {
183                         /* skip strokes that are invalid for current view */
184                         if (ED_gpencil_stroke_can_use(C, gps) == false)
185                                 continue;
186                         
187                         if (gps->flag & GP_STROKE_SELECT) {
188                                 if (gps->totpoints == 1) {
189                                         /* Special Case: If there's just a single point in this stroke... */
190                                         bGPDstroke *gpsd;
191                                         
192                                         /* make direct copies of the stroke and its points */
193                                         gpsd = MEM_dupallocN(gps);
194                                         gpsd->points = MEM_dupallocN(gps->points);
195                                         
196                                         /* add to temp buffer */
197                                         gpsd->next = gpsd->prev = NULL;
198                                         BLI_addtail(&new_strokes, gpsd);
199                                 }
200                                 else {
201                                         /* delegate to a helper, as there's too much to fit in here (for copying subsets)... */
202                                         gp_duplicate_points(gps, &new_strokes);
203                                 }
204                                 
205                                 /* deselect original stroke, or else the originals get moved too
206                                  * (when using the copy + move macro)
207                                  */
208                                 gps->flag &= ~GP_STROKE_SELECT;
209                         }
210                 }
211                 
212                 /* add all new strokes in temp buffer to the frame (preventing double-copies) */
213                 BLI_movelisttolist(&gpf->strokes, &new_strokes);
214                 BLI_assert(new_strokes.first == NULL);
215         }
216         CTX_DATA_END;
217         
218         /* updates */
219         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
220         
221         return OPERATOR_FINISHED;
222 }
223
224 void GPENCIL_OT_duplicate(wmOperatorType *ot)
225 {
226         /* identifiers */
227         ot->name = "Duplicate Strokes";
228         ot->idname = "GPENCIL_OT_duplicate";
229         ot->description = "Duplicate the selected Grease Pencil strokes";
230         
231         /* callbacks */
232         ot->exec = gp_duplicate_exec;
233         ot->poll = gp_stroke_edit_poll;
234         
235         /* flags */
236         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
237 }
238
239 /* ******************* Copy/Paste Strokes ************************* */
240 /* Grease Pencil stroke data copy/paste buffer:
241  * - The copy operation collects all segments of selected strokes,
242  *   dumping "ready to be copied" copies of the strokes into the buffer.
243  * - The paste operation makes a copy of those elements, and adds them
244  *   to the active layer. This effectively flattens down the strokes
245  *   from several different layers into a single layer.
246  */
247
248 /* list of bGPDstroke instances */
249 static ListBase gp_strokes_copypastebuf = {NULL, NULL};
250
251 /* Free copy/paste buffer data */
252 void ED_gpencil_strokes_copybuf_free(void)
253 {
254         bGPDstroke *gps, *gpsn;
255         
256         for (gps = gp_strokes_copypastebuf.first; gps; gps = gpsn) {
257                 gpsn = gps->next;
258                 
259                 MEM_freeN(gps->points);
260                 BLI_freelinkN(&gp_strokes_copypastebuf, gps);
261         }
262         
263         gp_strokes_copypastebuf.first = gp_strokes_copypastebuf.last = NULL;
264 }
265
266 /* --------------------- */
267 /* Copy selected strokes */
268
269 static int gp_strokes_copy_exec(bContext *C, wmOperator *op)
270 {
271         bGPdata *gpd = ED_gpencil_data_get_active(C);
272         
273         if (gpd == NULL) {
274                 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
275                 return OPERATOR_CANCELLED;
276         }
277         
278         /* clear the buffer first */
279         ED_gpencil_strokes_copybuf_free();
280         
281         /* for each visible (and editable) layer's selected strokes,
282          * copy the strokes into a temporary buffer, then append
283          * once all done
284          */
285         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
286         {
287                 bGPDframe *gpf = gpl->actframe;
288                 bGPDstroke *gps;
289                 
290                 if (gpf == NULL)
291                         continue;
292                 
293                 /* make copies of selected strokes, and deselect these once we're done */
294                 for (gps = gpf->strokes.first; gps; gps = gps->next) {
295                         /* skip strokes that are invalid for current view */
296                         if (ED_gpencil_stroke_can_use(C, gps) == false)
297                                 continue;
298                         
299                         if (gps->flag & GP_STROKE_SELECT) {
300                                 if (gps->totpoints == 1) {
301                                         /* Special Case: If there's just a single point in this stroke... */
302                                         bGPDstroke *gpsd;
303                                         
304                                         /* make direct copies of the stroke and its points */
305                                         gpsd = MEM_dupallocN(gps);
306                                         gpsd->points = MEM_dupallocN(gps->points);
307                                         
308                                         /* add to temp buffer */
309                                         gpsd->next = gpsd->prev = NULL;
310                                         BLI_addtail(&gp_strokes_copypastebuf, gpsd);
311                                 }
312                                 else {
313                                         /* delegate to a helper, as there's too much to fit in here (for copying subsets)... */
314                                         gp_duplicate_points(gps, &gp_strokes_copypastebuf);
315                                 }
316                         }
317                 }
318         }
319         CTX_DATA_END;
320         
321         /* done - no updates needed */
322         return OPERATOR_FINISHED;
323 }
324
325 void GPENCIL_OT_copy(wmOperatorType *ot)
326 {
327         /* identifiers */
328         ot->name = "Copy Strokes";
329         ot->idname = "GPENCIL_OT_copy";
330         ot->description = "Copy selected Grease Pencil points and strokes";
331         
332         /* callbacks */
333         ot->exec = gp_strokes_copy_exec;
334         ot->poll = gp_stroke_edit_poll;
335         
336         /* flags */
337         //ot->flag = OPTYPE_REGISTER;
338 }
339
340 /* --------------------- */
341 /* Paste selected strokes */
342
343 static int gp_strokes_paste_exec(bContext *C, wmOperator *op)
344 {
345         Scene *scene = CTX_data_scene(C);
346         bGPdata *gpd = ED_gpencil_data_get_active(C);
347         bGPDlayer *gpl = CTX_data_active_gpencil_layer(C);
348         bGPDframe *gpf;
349         
350         /* check for various error conditions */
351         if (gpd == NULL) {
352                 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
353                 return OPERATOR_CANCELLED;
354         }
355         else if (gp_strokes_copypastebuf.first == NULL) {
356                 BKE_report(op->reports, RPT_ERROR, "No strokes to paste, select and copy some points before trying again");
357                 return OPERATOR_CANCELLED;
358         }
359         else if (gpl == NULL) {
360                 /* no active layer - let's just create one */
361                 gpl = gpencil_layer_addnew(gpd, DATA_("GP_Layer"), 1);
362         }
363         else if (gpl->flag & (GP_LAYER_HIDE | GP_LAYER_LOCKED)) {
364                 BKE_report(op->reports, RPT_ERROR, "Can not paste strokes when active layer is hidden or locked");
365                 return OPERATOR_CANCELLED;
366         }
367         else {
368                 /* Check that some of the strokes in the buffer can be used */
369                 bGPDstroke *gps;
370                 bool ok = false;
371                 
372                 for (gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) {
373                         if (ED_gpencil_stroke_can_use(C, gps)) {
374                                 ok = true;
375                                 break;
376                         }
377                 }
378                 
379                 if (ok == false) {
380                         /* XXX: this check is not 100% accurate (i.e. image editor is incompatible with normal 2D strokes),
381                          * but should be enough to give users a good idea of what's going on
382                          */
383                         if (CTX_wm_area(C)->spacetype == SPACE_VIEW3D)
384                                 BKE_report(op->reports, RPT_ERROR, "Cannot paste 2D strokes in 3D View");
385                         else
386                                 BKE_report(op->reports, RPT_ERROR, "Cannot paste 3D strokes in 2D editors");
387                                 
388                         return OPERATOR_CANCELLED;
389                 }
390         }
391         
392         /* Deselect all strokes first */
393         CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
394         {
395                 bGPDspoint *pt;
396                 int i;
397                 
398                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
399                         pt->flag &= ~GP_SPOINT_SELECT;
400                 }
401                 
402                 gps->flag &= ~GP_STROKE_SELECT;
403         }
404         CTX_DATA_END;
405         
406         /* Ensure we have a frame to draw into
407          * NOTE: Since this is an op which creates strokes,
408          *       we are obliged to add a new frame if one
409          *       doesn't exist already
410          */
411         gpf = gpencil_layer_getframe(gpl, CFRA, true);
412         
413         if (gpf) {
414                 bGPDstroke *gps;
415                 
416                 /* Copy each stroke into the layer */
417                 for (gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) {
418                         if (ED_gpencil_stroke_can_use(C, gps)) {
419                                 bGPDstroke *new_stroke = MEM_dupallocN(gps);
420                                 
421                                 new_stroke->points = MEM_dupallocN(gps->points);
422                                 new_stroke->next = new_stroke->prev = NULL;
423                                 
424                                 BLI_addtail(&gpf->strokes, new_stroke);
425                         }
426                 }
427         }
428         
429         /* updates */
430         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
431         
432         return OPERATOR_FINISHED;
433 }
434
435 void GPENCIL_OT_paste(wmOperatorType *ot)
436 {
437         /* identifiers */
438         ot->name = "Paste Strokes";
439         ot->idname = "GPENCIL_OT_paste";
440         ot->description = "Paste previously copied strokes into active layer";
441         
442         /* callbacks */
443         ot->exec = gp_strokes_paste_exec;
444         ot->poll = gp_stroke_edit_poll;
445         
446         /* flags */
447         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
448 }
449
450 /* ******************* Delete Active Frame ************************ */
451
452 static int gp_actframe_delete_poll(bContext *C)
453 {
454         bGPdata *gpd = ED_gpencil_data_get_active(C);
455         bGPDlayer *gpl = gpencil_layer_getactive(gpd);
456         
457         /* only if there's an active layer with an active frame */
458         return (gpl && gpl->actframe);
459 }
460
461 /* delete active frame - wrapper around API calls */
462 static int gp_actframe_delete_exec(bContext *C, wmOperator *op)
463 {
464         Scene *scene = CTX_data_scene(C);
465         bGPdata *gpd = ED_gpencil_data_get_active(C);
466         bGPDlayer *gpl = gpencil_layer_getactive(gpd);
467         bGPDframe *gpf = gpencil_layer_getframe(gpl, CFRA, 0);
468         
469         /* if there's no existing Grease-Pencil data there, add some */
470         if (gpd == NULL) {
471                 BKE_report(op->reports, RPT_ERROR, "No grease pencil data");
472                 return OPERATOR_CANCELLED;
473         }
474         if (ELEM(NULL, gpl, gpf)) {
475                 BKE_report(op->reports, RPT_ERROR, "No active frame to delete");
476                 return OPERATOR_CANCELLED;
477         }
478         
479         /* delete it... */
480         gpencil_layer_delframe(gpl, gpf);
481         
482         /* notifiers */
483         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
484         
485         return OPERATOR_FINISHED;
486 }
487
488 void GPENCIL_OT_active_frame_delete(wmOperatorType *ot)
489 {
490         /* identifiers */
491         ot->name = "Delete Active Frame";
492         ot->idname = "GPENCIL_OT_active_frame_delete";
493         ot->description = "Delete the active frame for the active Grease Pencil datablock";
494         
495         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
496         
497         /* callbacks */
498         ot->exec = gp_actframe_delete_exec;
499         ot->poll = gp_actframe_delete_poll;
500 }
501
502 /* ******************* Delete Operator ************************ */
503
504 typedef enum eGP_DeleteMode {
505         /* delete selected stroke points */
506         GP_DELETEOP_POINTS          = 0,
507         /* delete selected strokes */
508         GP_DELETEOP_STROKES         = 1,
509         /* delete active frame */
510         GP_DELETEOP_FRAME           = 2,
511         /* delete selected stroke points (without splitting stroke) */
512         GP_DELETEOP_POINTS_DISSOLVE = 3,
513 } eGP_DeleteMode;
514
515
516 /* Delete selected strokes */
517 static int gp_delete_selected_strokes(bContext *C)
518 {
519         bool changed = false;
520         
521         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
522         {
523                 bGPDframe *gpf = gpl->actframe;
524                 bGPDstroke *gps, *gpsn;
525                 
526                 if (gpf == NULL)
527                         continue;
528                 
529                 /* simply delete strokes which are selected */
530                 for (gps = gpf->strokes.first; gps; gps = gpsn) {
531                         gpsn = gps->next;
532                         
533                         /* skip strokes that are invalid for current view */
534                         if (ED_gpencil_stroke_can_use(C, gps) == false)
535                                 continue;
536                         
537                         /* free stroke if selected */
538                         if (gps->flag & GP_STROKE_SELECT) {
539                                 /* free stroke memory arrays, then stroke itself */
540                                 if (gps->points) MEM_freeN(gps->points);
541                                 BLI_freelinkN(&gpf->strokes, gps);
542                                 
543                                 changed = true;
544                         }
545                 }
546         }
547         CTX_DATA_END;
548         
549         if (changed) {
550                 WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
551                 return OPERATOR_FINISHED;
552         }
553         else {
554                 return OPERATOR_CANCELLED;
555         }
556 }
557
558 /* Delete selected points but keep the stroke */
559 static int gp_dissolve_selected_points(bContext *C)
560 {
561         bool changed = false;
562         
563         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
564         {
565                 bGPDframe *gpf = gpl->actframe;
566                 bGPDstroke *gps, *gpsn;
567                 
568                 if (gpf == NULL)
569                         continue;
570                 
571                 /* simply delete points from selected strokes
572                  * NOTE: we may still have to remove the stroke if it ends up having no points!
573                  */
574                 for (gps = gpf->strokes.first; gps; gps = gpsn) {
575                         gpsn = gps->next;
576                         
577                         /* skip strokes that are invalid for current view */
578                         if (ED_gpencil_stroke_can_use(C, gps) == false)
579                                 continue;
580                         
581                         if (gps->flag & GP_STROKE_SELECT) {
582                                 bGPDspoint *pt;
583                                 int i;
584                                 
585                                 int tot = gps->totpoints; /* number of points in new buffer */
586                                 
587                                 /* First Pass: Count how many points are selected (i.e. how many to remove) */
588                                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
589                                         if (pt->flag & GP_SPOINT_SELECT) {
590                                                 /* selected point - one of the points to remove */
591                                                 tot--;
592                                         }
593                                 }
594                                 
595                                 /* if no points are left, we simply delete the entire stroke */
596                                 if (tot <= 0) {
597                                         /* remove the entire stroke */
598                                         MEM_freeN(gps->points);
599                                         BLI_freelinkN(&gpf->strokes, gps);
600                                 }
601                                 else {
602                                         /* just copy all unselected into a smaller buffer */
603                                         bGPDspoint *new_points = MEM_callocN(sizeof(bGPDspoint) * tot, "new gp stroke points copy");
604                                         bGPDspoint *npt        = new_points;
605                                         
606                                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
607                                                 if ((pt->flag & GP_SPOINT_SELECT) == 0) {
608                                                         *npt = *pt;
609                                                         npt++;
610                                                 }
611                                         }
612                                         
613                                         /* free the old buffer */
614                                         MEM_freeN(gps->points);
615                                         
616                                         /* save the new buffer */
617                                         gps->points = new_points;
618                                         gps->totpoints = tot;
619                                         
620                                         /* deselect the stroke, since none of its selected points will still be selected */
621                                         gps->flag &= ~GP_STROKE_SELECT;
622                                 }
623                                 
624                                 changed = true;
625                         }
626                 }
627         }
628         CTX_DATA_END;
629         
630         if (changed) {
631                 WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
632                 return OPERATOR_FINISHED;
633         }
634         else {
635                 return OPERATOR_CANCELLED;
636         }
637 }
638
639 /* Split selected strokes into segments, splitting on selected points */
640 static int gp_delete_selected_points(bContext *C)
641 {
642         bool changed = false;
643         
644         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
645         {
646                 bGPDframe *gpf = gpl->actframe;
647                 bGPDstroke *gps, *gpsn;
648                 
649                 if (gpf == NULL)
650                         continue;
651                 
652                 /* simply delete strokes which are selected */
653                 for (gps = gpf->strokes.first; gps; gps = gpsn) {
654                         gpsn = gps->next;
655                         
656                         /* skip strokes that are invalid for current view */
657                         if (ED_gpencil_stroke_can_use(C, gps) == false)
658                                 continue;
659                         
660                         
661                         if (gps->flag & GP_STROKE_SELECT) {
662                                 bGPDspoint *pt;
663                                 int i;
664                                 
665                                 /* The algorithm used here is as follows:
666                                  * 1) We firstly identify the number of "islands" of non-selected points
667                                  *    which will all end up being in new strokes.
668                                  *    - In the most extreme case (i.e. every other vert is a 1-vert island),
669                                  *      we have at most n / 2 islands
670                                  *    - Once we start having larger islands than that, the number required
671                                  *      becomes much less
672                                  * 2) Each island gets converted to a new stroke
673                                  */
674                                 typedef struct tGPDeleteIsland {
675                                         int start_idx;
676                                         int end_idx;
677                                 } tGPDeleteIsland;
678                                 
679                                 tGPDeleteIsland *islands = MEM_callocN(sizeof(tGPDeleteIsland) * (gps->totpoints + 1) / 2, "gp_point_islands");
680                                 bool in_island  = false;
681                                 int num_islands = 0;
682                                 
683                                 /* First Pass: Identify start/end of islands */
684                                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
685                                         if (pt->flag & GP_SPOINT_SELECT) {
686                                                 /* selected - stop accumulating to island */
687                                                 in_island = false;
688                                         }
689                                         else {
690                                                 /* unselected - start of a new island? */
691                                                 int idx;
692                                                 
693                                                 if (in_island) {
694                                                         /* extend existing island */
695                                                         idx = num_islands - 1;
696                                                         islands[idx].end_idx = i;
697                                                 }
698                                                 else {
699                                                         /* start of new island */
700                                                         in_island = true;
701                                                         num_islands++;
702                                                         
703                                                         idx = num_islands - 1;
704                                                         islands[idx].start_idx = islands[idx].end_idx = i;
705                                                 }
706                                         }
707                                 }
708                                 
709                                 /* Watch out for special case where No islands = All points selected = Delete Stroke only */
710                                 if (num_islands) {
711                                         /* there are islands, so create a series of new strokes, adding them before the "next" stroke */
712                                         int idx;
713                                         
714                                         /* deselect old stroke, since it will be used as template for the new strokes */
715                                         gps->flag &= ~GP_STROKE_SELECT;
716                                         
717                                         /* create each new stroke... */
718                                         for (idx = 0; idx < num_islands; idx++) {
719                                                 tGPDeleteIsland *island = &islands[idx];
720                                                 bGPDstroke *new_stroke  = MEM_dupallocN(gps);
721                                                 
722                                                 /* compute new buffer size (+ 1 needed as the endpoint index is "inclusive") */
723                                                 new_stroke->totpoints = island->end_idx - island->start_idx + 1;
724                                                 new_stroke->points    = MEM_callocN(sizeof(bGPDspoint) * new_stroke->totpoints, "gp delete stroke fragment");
725                                                 
726                                                 /* copy over the relevant points */
727                                                 memcpy(new_stroke->points, gps->points + island->start_idx, sizeof(bGPDspoint) * new_stroke->totpoints);
728                                                 
729                                                 /* add new stroke to the frame */
730                                                 if (gpsn) {
731                                                         BLI_insertlinkbefore(&gpf->strokes, gpsn, new_stroke);
732                                                 }
733                                                 else {
734                                                         BLI_addtail(&gpf->strokes, new_stroke);
735                                                 }
736                                         }
737                                 }
738                                 
739                                 /* free islands */
740                                 MEM_freeN(islands);
741                                 
742                                 /* Delete the old stroke */
743                                 MEM_freeN(gps->points);
744                                 BLI_freelinkN(&gpf->strokes, gps);
745                                 
746                                 changed = true;
747                         }
748                 }
749         }
750         CTX_DATA_END;
751         
752         if (changed) {
753                 WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
754                 return OPERATOR_FINISHED;
755         }
756         else {
757                 return OPERATOR_CANCELLED;
758         }
759 }
760
761
762 static int gp_delete_exec(bContext *C, wmOperator *op)
763 {
764         eGP_DeleteMode mode = RNA_enum_get(op->ptr, "type");
765         int result = OPERATOR_CANCELLED;
766         
767         switch (mode) {
768                 case GP_DELETEOP_STROKES:       /* selected strokes */
769                         result = gp_delete_selected_strokes(C);
770                         break;
771                 
772                 case GP_DELETEOP_POINTS:        /* selected points (breaks the stroke into segments) */
773                         result = gp_delete_selected_points(C);
774                         break;
775                 
776                 case GP_DELETEOP_POINTS_DISSOLVE: /* selected points (without splitting the stroke) */
777                         result = gp_dissolve_selected_points(C);
778                         break;
779                 
780                 case GP_DELETEOP_FRAME:         /* active frame */
781                         result = gp_actframe_delete_exec(C, op);
782                         break;
783         }
784         
785         return result;
786 }
787
788 void GPENCIL_OT_delete(wmOperatorType *ot)
789 {
790         static EnumPropertyItem prop_gpencil_delete_types[] = {
791                 {GP_DELETEOP_POINTS, "POINTS", 0, "Points", "Delete selected points and split strokes into segments"},
792                 {GP_DELETEOP_STROKES, "STROKES", 0, "Strokes", "Delete selected strokes"},
793                 {GP_DELETEOP_FRAME, "FRAME", 0, "Frame", "Delete active frame"},
794                 {0, "", 0, NULL, NULL},
795                 {GP_DELETEOP_POINTS_DISSOLVE, "DISSOLVE_POINTS", 0, "Dissolve Points",
796                                               "Delete selected points without splitting strokes"},
797                 {0, NULL, 0, NULL, NULL}
798         };
799         
800         /* identifiers */
801         ot->name = "Delete...";
802         ot->idname = "GPENCIL_OT_delete";
803         ot->description = "Delete selected Grease Pencil strokes, vertices, or frames";
804         
805         /* callbacks */
806         ot->invoke = WM_menu_invoke;
807         ot->exec = gp_delete_exec;
808         ot->poll = gp_stroke_edit_poll;
809         
810         /* flags */
811         ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
812         
813         /* props */
814         ot->prop = RNA_def_enum(ot->srna, "type", prop_gpencil_delete_types, 0, "Type", "Method used for deleting Grease Pencil data");
815 }
816
817 /* ************************************************ */