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