GPencil: Layers with alpha = 0 should not be editable
[blender.git] / source / blender / editors / gpencil / gpencil_edit.c
index dbf04e772f5f112ca9d57ef1cb7d4ac74d693538..03d5ed3e24f87531f629f9e6d8abe4d07023db57 100644 (file)
  * Contributor(s): Joshua Leung
  *
  * ***** END GPL LICENSE BLOCK *****
+ *
+ * Operators for editing Grease Pencil strokes
  */
 
 /** \file blender/editors/gpencil/gpencil_edit.c
  *  \ingroup edgpencil
  */
 
 
 #include <stdio.h>
 #include <string.h>
 
 #include "MEM_guardedalloc.h"
 
-
 #include "BLI_math.h"
 #include "BLI_blenlib.h"
-#include "BLI_rand.h"
 #include "BLI_utildefines.h"
 
-#include "DNA_anim_types.h"
-#include "DNA_curve_types.h"
+#include "BLT_translation.h"
+
 #include "DNA_object_types.h"
-#include "DNA_node_types.h"
 #include "DNA_scene_types.h"
 #include "DNA_screen_types.h"
 #include "DNA_space_types.h"
 #include "DNA_view3d_types.h"
 #include "DNA_gpencil_types.h"
 
-#include "BKE_animsys.h"
 #include "BKE_context.h"
-#include "BKE_curve.h"
-#include "BKE_depsgraph.h"
-#include "BKE_fcurve.h"
 #include "BKE_global.h"
 #include "BKE_gpencil.h"
 #include "BKE_library.h"
-#include "BKE_object.h"
 #include "BKE_report.h"
-#include "BKE_tracking.h"
+#include "BKE_screen.h"
 
 #include "UI_interface.h"
+#include "UI_resources.h"
 
 #include "WM_api.h"
 #include "WM_types.h"
 
 #include "RNA_access.h"
 #include "RNA_define.h"
+#include "RNA_enum_types.h"
 
 #include "UI_view2d.h"
 
 #include "ED_gpencil.h"
+#include "ED_object.h"
 #include "ED_view3d.h"
-#include "ED_clip.h"
-#include "ED_keyframing.h"
 
 #include "gpencil_intern.h"
 
 /* ************************************************ */
-/* Context Wrangling... */
+/* Stroke Edit Mode Management */
 
-/* Get pointer to active Grease Pencil datablock, and an RNA-pointer to trace back to whatever owns it */
-bGPdata **gpencil_data_get_pointers(const bContext *C, PointerRNA *ptr)
+static int gpencil_editmode_toggle_poll(bContext *C)
 {
-       ID *screen_id = (ID *)CTX_wm_screen(C);
-       Scene *scene = CTX_data_scene(C);
-       ScrArea *sa = CTX_wm_area(C);
+       return ED_gpencil_data_get_active(C) != NULL;
+}
+
+static int gpencil_editmode_toggle_exec(bContext *C, wmOperator *UNUSED(op))
+{
+       bGPdata *gpd = ED_gpencil_data_get_active(C);
        
-       /* if there's an active area, check if the particular editor may
-        * have defined any special Grease Pencil context for editing...
-        */
-       if (sa) {
-               switch (sa->spacetype) {
-                       case SPACE_VIEW3D: /* 3D-View */
-                       {
-                               Object *ob = CTX_data_active_object(C);
-                               
-                               /* TODO: we can include other data-types such as bones later if need be... */
+       if (gpd == NULL)
+               return OPERATOR_CANCELLED;
+       
+       /* Just toggle editmode flag... */
+       gpd->flag ^= GP_DATA_STROKE_EDITMODE;
+       
+       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | ND_GPENCIL_EDITMODE, NULL);
+       WM_event_add_notifier(C, NC_SCENE | ND_MODE, NULL);
+       
+       return OPERATOR_FINISHED;
+}
 
-                               /* just in case no active object */
-                               if (ob) {
-                                       /* for now, as long as there's an object, default to using that in 3D-View */
-                                       if (ptr) RNA_id_pointer_create(&ob->id, ptr);
-                                       return &ob->gpd;
-                               }
+void GPENCIL_OT_editmode_toggle(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Strokes Edit Mode Toggle";
+       ot->idname = "GPENCIL_OT_editmode_toggle";
+       ot->description = "Enter/Exit edit mode for Grease Pencil strokes";
+       
+       /* callbacks */
+       ot->exec = gpencil_editmode_toggle_exec;
+       ot->poll = gpencil_editmode_toggle_poll;
+       
+       /* flags */
+       ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
+}
+
+/* ************************************************ */
+/* Stroke Editing Operators */
+
+/* poll callback for all stroke editing operators */
+static int gp_stroke_edit_poll(bContext *C)
+{
+       /* NOTE: this is a bit slower, but is the most accurate... */
+       return CTX_DATA_COUNT(C, editable_gpencil_strokes) != 0;
+}
+
+/* ************** Duplicate Selected Strokes **************** */
+
+/* Make copies of selected point segments in a selected stroke */
+static void gp_duplicate_points(const bGPDstroke *gps, ListBase *new_strokes)
+{
+       bGPDspoint *pt;
+       int i;
+       
+       int start_idx = -1;
+       
+       
+       /* Step through the original stroke's points:
+        * - We accumulate selected points (from start_idx to current index)
+        *   and then convert that to a new stroke
+        */
+       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+               /* searching for start, are waiting for end? */
+               if (start_idx == -1) {
+                       /* is this the first selected point for a new island? */
+                       if (pt->flag & GP_SPOINT_SELECT) {
+                               start_idx = i;
                        }
-                       break;
+               }
+               else {
+                       size_t len = 0;
                        
-                       case SPACE_NODE: /* Nodes Editor */
-                       {
-                               SpaceNode *snode = (SpaceNode *)CTX_wm_space_data(C);
-                               
-                               /* return the GP data for the active node block/node */
-                               if (snode && snode->nodetree) {
-                                       /* for now, as long as there's an active node tree, default to using that in the Nodes Editor */
-                                       if (ptr) RNA_id_pointer_create(&snode->nodetree->id, ptr);
-                                       return &snode->nodetree->gpd;
-                               }
-                               else {
-                                       /* even when there is no node-tree, don't allow this to flow to scene */
-                                       return NULL;
-                               }
+                       /* is this the end of current island yet?
+                        * 1) Point i-1 was the last one that was selected
+                        * 2) Point i is the last in the array
+                        */
+                       if ((pt->flag & GP_SPOINT_SELECT) == 0) {
+                               len = i - start_idx;
                        }
-                       break;
+                       else if (i == gps->totpoints - 1) {
+                               len = i - start_idx + 1;
+                       }
+                       //printf("copying from %d to %d = %d\n", start_idx, i, len);
+               
+                       /* make copies of the relevant data */
+                       if (len) {
+                               bGPDstroke *gpsd;
                                
-                       case SPACE_SEQ: /* Sequencer */
-                       {
-                               SpaceSeq *sseq = (SpaceSeq *)CTX_wm_space_data(C);
+                               /* make a stupid copy first of the entire stroke (to get the flags too) */
+                               gpsd = MEM_dupallocN(gps);
                                
-                               /* for now, Grease Pencil data is associated with the space (actually preview region only) */
-                               /* XXX our convention for everything else is to link to data though... */
-                               if (ptr) RNA_pointer_create(screen_id, &RNA_SpaceSequenceEditor, sseq, ptr);
-                               return &sseq->gpd;
-                       }
-                       break;
-
-                       case SPACE_IMAGE: /* Image/UV Editor */
-                       {
-                               SpaceImage *sima = (SpaceImage *)CTX_wm_space_data(C);
-
-                               /* for now, Grease Pencil data is associated with the space... */
-                               /* XXX our convention for everything else is to link to data though... */
-                               if (ptr) RNA_pointer_create(screen_id, &RNA_SpaceImageEditor, sima, ptr);
-                               return &sima->gpd;
-                       }
-                       break;
+                               /* now, make a new points array, and copy of the relevant parts */
+                               gpsd->points = MEM_callocN(sizeof(bGPDspoint) * len, "gps stroke points copy");
+                               memcpy(gpsd->points, gps->points + start_idx, sizeof(bGPDspoint) * len);
+                               gpsd->totpoints = len;
                                
-                       case SPACE_CLIP: /* Nodes Editor */
-                       {
-                               SpaceClip *sc = (SpaceClip *)CTX_wm_space_data(C);
-                               MovieClip *clip = ED_space_clip_get_clip(sc);
+                               /* add to temp buffer */
+                               gpsd->next = gpsd->prev = NULL;
+                               BLI_addtail(new_strokes, gpsd);
                                
-                               if (clip) {
-                                       if (sc->gpencil_src == SC_GPENCIL_SRC_TRACK) {
-                                               MovieTrackingTrack *track = BKE_tracking_track_get_active(&clip->tracking);
-
-                                               if (!track)
-                                                       return NULL;
-
-                                               if (ptr)
-                                                       RNA_pointer_create(&clip->id, &RNA_MovieTrackingTrack, track, ptr);
-
-                                               return &track->gpd;
-                                       }
-                                       else {
-                                               if (ptr)
-                                                       RNA_id_pointer_create(&clip->id, ptr);
+                               /* cleanup + reset for next */
+                               start_idx = -1;
+                       }
+               }
+       }
+}
 
-                                               return &clip->gpd;
-                                       }
+static int gp_duplicate_exec(bContext *C, wmOperator *op)
+{
+       bGPdata *gpd = ED_gpencil_data_get_active(C);
+       
+       if (gpd == NULL) {
+               BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
+               return OPERATOR_CANCELLED;
+       }
+       
+       /* for each visible (and editable) layer's selected strokes,
+        * copy the strokes into a temporary buffer, then append
+        * once all done
+        */
+       CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
+       {
+               ListBase new_strokes = {NULL, NULL};
+               bGPDframe *gpf = gpl->actframe;
+               bGPDstroke *gps;
+               
+               if (gpf == NULL)
+                       continue;
+               
+               /* make copies of selected strokes, and deselect these once we're done */
+               for (gps = gpf->strokes.first; gps; gps = gps->next) {
+                       /* skip strokes that are invalid for current view */
+                       if (ED_gpencil_stroke_can_use(C, gps) == false)
+                               continue;
+                       
+                       if (gps->flag & GP_STROKE_SELECT) {
+                               if (gps->totpoints == 1) {
+                                       /* Special Case: If there's just a single point in this stroke... */
+                                       bGPDstroke *gpsd;
+                                       
+                                       /* make direct copies of the stroke and its points */
+                                       gpsd = MEM_dupallocN(gps);
+                                       gpsd->points = MEM_dupallocN(gps->points);
+                                       
+                                       /* add to temp buffer */
+                                       gpsd->next = gpsd->prev = NULL;
+                                       BLI_addtail(&new_strokes, gpsd);
+                               }
+                               else {
+                                       /* delegate to a helper, as there's too much to fit in here (for copying subsets)... */
+                                       gp_duplicate_points(gps, &new_strokes);
                                }
-                       }
-                       break;
                                
-                       default: /* unsupported space */
-                               return NULL;
+                               /* deselect original stroke, or else the originals get moved too
+                                * (when using the copy + move macro)
+                                */
+                               gps->flag &= ~GP_STROKE_SELECT;
+                       }
                }
+               
+               /* add all new strokes in temp buffer to the frame (preventing double-copies) */
+               BLI_movelisttolist(&gpf->strokes, &new_strokes);
+               BLI_assert(new_strokes.first == NULL);
        }
+       CTX_DATA_END;
+       
+       /* updates */
+       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
        
-       /* just fall back on the scene's GP data */
-       if (ptr) RNA_id_pointer_create((ID *)scene, ptr);
-       return (scene) ? &scene->gpd : NULL;
+       return OPERATOR_FINISHED;
 }
 
-/* Get the active Grease Pencil datablock */
-bGPdata *gpencil_data_get_active(const bContext *C)
+void GPENCIL_OT_duplicate(wmOperatorType *ot)
 {
-       bGPdata **gpd_ptr = gpencil_data_get_pointers(C, NULL);
-       return (gpd_ptr) ? *(gpd_ptr) : NULL;
+       /* identifiers */
+       ot->name = "Duplicate Strokes";
+       ot->idname = "GPENCIL_OT_duplicate";
+       ot->description = "Duplicate the selected Grease Pencil strokes";
+       
+       /* callbacks */
+       ot->exec = gp_duplicate_exec;
+       ot->poll = gp_stroke_edit_poll;
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 }
 
-/* needed for offscreen rendering */
-bGPdata *gpencil_data_get_active_v3d(Scene *scene)
-{
-       bGPdata *gpd = scene->basact ? scene->basact->object->gpd : NULL;
-       return gpd ? gpd : scene->gpd;
-}
+/* ******************* Copy/Paste Strokes ************************* */
+/* Grease Pencil stroke data copy/paste buffer:
+ * - The copy operation collects all segments of selected strokes,
+ *   dumping "ready to be copied" copies of the strokes into the buffer.
+ * - The paste operation makes a copy of those elements, and adds them
+ *   to the active layer. This effectively flattens down the strokes
+ *   from several different layers into a single layer.
+ */
 
-/* ************************************************ */
-/* Panel Operators */
+/* list of bGPDstroke instances */
+/* NOTE: is exposed within the editors/gpencil module so that other tools can use it too */
+ListBase gp_strokes_copypastebuf = {NULL, NULL};
 
-/* poll callback for adding data/layers - special */
-static int gp_add_poll(bContext *C)
+/* Free copy/paste buffer data */
+void ED_gpencil_strokes_copybuf_free(void)
 {
-       /* the base line we have is that we have somewhere to add Grease Pencil data */
-       return gpencil_data_get_pointers(C, NULL) != NULL;
+       bGPDstroke *gps, *gpsn;
+       
+       for (gps = gp_strokes_copypastebuf.first; gps; gps = gpsn) {
+               gpsn = gps->next;
+               
+               MEM_freeN(gps->points);
+               BLI_freelinkN(&gp_strokes_copypastebuf, gps);
+       }
+       
+       BLI_listbase_clear(&gp_strokes_copypastebuf);
 }
 
-/* ******************* Add New Data ************************ */
+/* --------------------- */
+/* Copy selected strokes */
 
-/* add new datablock - wrapper around API */
-static int gp_data_add_exec(bContext *C, wmOperator *op)
+static int gp_strokes_copy_exec(bContext *C, wmOperator *op)
 {
-       bGPdata **gpd_ptr = gpencil_data_get_pointers(C, NULL);
+       bGPdata *gpd = ED_gpencil_data_get_active(C);
        
-       if (gpd_ptr == NULL) {
-               BKE_report(op->reports, RPT_ERROR, "Nowhere for grease pencil data to go");
+       if (gpd == NULL) {
+               BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
                return OPERATOR_CANCELLED;
        }
-       else {
-               /* decrement user count and add new datablock */
-               bGPdata *gpd = (*gpd_ptr);
+       
+       /* clear the buffer first */
+       ED_gpencil_strokes_copybuf_free();
+       
+       /* for each visible (and editable) layer's selected strokes,
+        * copy the strokes into a temporary buffer, then append
+        * once all done
+        */
+       CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
+       {
+               bGPDframe *gpf = gpl->actframe;
+               bGPDstroke *gps;
                
-               id_us_min(&gpd->id);
-               *gpd_ptr = gpencil_data_addnew("GPencil");
+               if (gpf == NULL)
+                       continue;
+               
+               /* make copies of selected strokes, and deselect these once we're done */
+               for (gps = gpf->strokes.first; gps; gps = gps->next) {
+                       /* skip strokes that are invalid for current view */
+                       if (ED_gpencil_stroke_can_use(C, gps) == false)
+                               continue;
+                       
+                       if (gps->flag & GP_STROKE_SELECT) {
+                               if (gps->totpoints == 1) {
+                                       /* Special Case: If there's just a single point in this stroke... */
+                                       bGPDstroke *gpsd;
+                                       
+                                       /* make direct copies of the stroke and its points */
+                                       gpsd = MEM_dupallocN(gps);
+                                       gpsd->points = MEM_dupallocN(gps->points);
+                                       
+                                       /* add to temp buffer */
+                                       gpsd->next = gpsd->prev = NULL;
+                                       BLI_addtail(&gp_strokes_copypastebuf, gpsd);
+                               }
+                               else {
+                                       /* delegate to a helper, as there's too much to fit in here (for copying subsets)... */
+                                       gp_duplicate_points(gps, &gp_strokes_copypastebuf);
+                               }
+                       }
+               }
        }
+       CTX_DATA_END;
        
-       /* notifiers */
-       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
-       
+       /* done - no updates needed */
        return OPERATOR_FINISHED;
 }
 
-void GPENCIL_OT_data_add(wmOperatorType *ot)
+void GPENCIL_OT_copy(wmOperatorType *ot)
 {
        /* identifiers */
-       ot->name = "Grease Pencil Add New";
-       ot->idname = "GPENCIL_OT_data_add";
-       ot->description = "Add new Grease Pencil datablock";
-       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+       ot->name = "Copy Strokes";
+       ot->idname = "GPENCIL_OT_copy";
+       ot->description = "Copy selected Grease Pencil points and strokes";
        
        /* callbacks */
-       ot->exec = gp_data_add_exec;
-       ot->poll = gp_add_poll;
-}
-
-/* ******************* Unlink Data ************************ */
-
-/* poll callback for adding data/layers - special */
-static int gp_data_unlink_poll(bContext *C)
-{
-       bGPdata **gpd_ptr = gpencil_data_get_pointers(C, NULL);
+       ot->exec = gp_strokes_copy_exec;
+       ot->poll = gp_stroke_edit_poll;
        
-       /* if we have access to some active data, make sure there's a datablock before enabling this */
-       return (gpd_ptr && *gpd_ptr);
+       /* flags */
+       //ot->flag = OPTYPE_REGISTER;
 }
 
+/* --------------------- */
+/* Paste selected strokes */
 
-/* unlink datablock - wrapper around API */
-static int gp_data_unlink_exec(bContext *C, wmOperator *op)
+static int gp_strokes_paste_exec(bContext *C, wmOperator *op)
 {
-       bGPdata **gpd_ptr = gpencil_data_get_pointers(C, NULL);
+       Scene *scene = CTX_data_scene(C);
+       bGPdata *gpd = ED_gpencil_data_get_active(C);
+       bGPDlayer *gpl = CTX_data_active_gpencil_layer(C);
+       bGPDframe *gpf;
        
-       if (gpd_ptr == NULL) {
-               BKE_report(op->reports, RPT_ERROR, "Nowhere for grease pencil data to go");
+       /* check for various error conditions */
+       if (gpd == NULL) {
+               BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
+               return OPERATOR_CANCELLED;
+       }
+       else if (BLI_listbase_is_empty(&gp_strokes_copypastebuf)) {
+               BKE_report(op->reports, RPT_ERROR, "No strokes to paste, select and copy some points before trying again");
+               return OPERATOR_CANCELLED;
+       }
+       else if (gpl == NULL) {
+               /* no active layer - let's just create one */
+               gpl = gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true);
+       }
+       else if (gpencil_layer_is_editable(gpl) == false) {
+               BKE_report(op->reports, RPT_ERROR, "Can not paste strokes when active layer is hidden or locked");
                return OPERATOR_CANCELLED;
        }
        else {
-               /* just unlink datablock now, decreasing its user count */
-               bGPdata *gpd = (*gpd_ptr);
+               /* Check that some of the strokes in the buffer can be used */
+               bGPDstroke *gps;
+               bool ok = false;
+               
+               for (gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) {
+                       if (ED_gpencil_stroke_can_use(C, gps)) {
+                               ok = true;
+                               break;
+                       }
+               }
                
-               id_us_min(&gpd->id);
-               *gpd_ptr = NULL;
+               if (ok == false) {
+                       /* XXX: this check is not 100% accurate (i.e. image editor is incompatible with normal 2D strokes),
+                        * but should be enough to give users a good idea of what's going on
+                        */
+                       if (CTX_wm_area(C)->spacetype == SPACE_VIEW3D)
+                               BKE_report(op->reports, RPT_ERROR, "Cannot paste 2D strokes in 3D View");
+                       else
+                               BKE_report(op->reports, RPT_ERROR, "Cannot paste 3D strokes in 2D editors");
+                               
+                       return OPERATOR_CANCELLED;
+               }
        }
        
-       /* notifiers */
-       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); 
+       /* Deselect all strokes first */
+       CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
+       {
+               bGPDspoint *pt;
+               int i;
+               
+               for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                       pt->flag &= ~GP_SPOINT_SELECT;
+               }
+               
+               gps->flag &= ~GP_STROKE_SELECT;
+       }
+       CTX_DATA_END;
+       
+       /* Ensure we have a frame to draw into
+        * NOTE: Since this is an op which creates strokes,
+        *       we are obliged to add a new frame if one
+        *       doesn't exist already
+        */
+       gpf = gpencil_layer_getframe(gpl, CFRA, true);
+       
+       if (gpf) {
+               bGPDstroke *gps;
+               
+               /* Copy each stroke into the layer */
+               for (gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) {
+                       if (ED_gpencil_stroke_can_use(C, gps)) {
+                               bGPDstroke *new_stroke = MEM_dupallocN(gps);
+                               
+                               new_stroke->points = MEM_dupallocN(gps->points);
+                               new_stroke->next = new_stroke->prev = NULL;
+                               
+                               BLI_addtail(&gpf->strokes, new_stroke);
+                       }
+               }
+       }
+       
+       /* updates */
+       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
        
        return OPERATOR_FINISHED;
 }
 
-void GPENCIL_OT_data_unlink(wmOperatorType *ot)
+void GPENCIL_OT_paste(wmOperatorType *ot)
 {
        /* identifiers */
-       ot->name = "Grease Pencil Unlink";
-       ot->idname = "GPENCIL_OT_data_unlink";
-       ot->description = "Unlink active Grease Pencil datablock";
-       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+       ot->name = "Paste Strokes";
+       ot->idname = "GPENCIL_OT_paste";
+       ot->description = "Paste previously copied strokes into active layer";
        
        /* callbacks */
-       ot->exec = gp_data_unlink_exec;
-       ot->poll = gp_data_unlink_poll;
+       ot->exec = gp_strokes_paste_exec;
+       ot->poll = gp_stroke_edit_poll;
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 }
 
-/* ******************* Add New Layer ************************ */
+/* ******************* Move To Layer ****************************** */
 
-/* add new layer - wrapper around API */
-static int gp_layer_add_exec(bContext *C, wmOperator *op)
+static int gp_move_to_layer_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(evt))
 {
-       bGPdata **gpd_ptr = gpencil_data_get_pointers(C, NULL);
+       uiPopupMenu *pup;
+       uiLayout *layout;
        
-       /* if there's no existing Grease-Pencil data there, add some */
-       if (gpd_ptr == NULL) {
-               BKE_report(op->reports, RPT_ERROR, "Nowhere for grease pencil data to go");
-               return OPERATOR_CANCELLED;
+       /* call the menu, which will call this operator again, hence the canceled */
+       pup = UI_popup_menu_begin(C, op->type->name, ICON_NONE);
+       layout = UI_popup_menu_layout(pup);
+       uiItemsEnumO(layout, "GPENCIL_OT_move_to_layer", "layer");
+       UI_popup_menu_end(C, pup);
+       
+       return OPERATOR_INTERFACE;
+}
+
+// FIXME: allow moving partial strokes
+static int gp_move_to_layer_exec(bContext *C, wmOperator *op)
+{
+       bGPdata *gpd = CTX_data_gpencil_data(C);
+       bGPDlayer *target_layer = NULL;
+       ListBase strokes = {NULL, NULL};
+       int layer_num = RNA_enum_get(op->ptr, "layer");
+       
+       /* Get layer or create new one */
+       if (layer_num == -1) {
+               /* Create layer */
+               target_layer = gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true);
        }
-       if (*gpd_ptr == NULL)
-               *gpd_ptr = gpencil_data_addnew("GPencil");
+       else {
+               /* Try to get layer */
+               target_layer = BLI_findlink(&gpd->layers, layer_num);
                
-       /* add new layer now */
-       gpencil_layer_addnew(*gpd_ptr, "GP_Layer", 1);
+               if (target_layer == NULL) {
+                       BKE_reportf(op->reports, RPT_ERROR, "There is no layer number %d", layer_num);
+                       return OPERATOR_CANCELLED;
+               }
+       }
        
-       /* notifiers */
+       /* Extract all strokes to move to this layer
+        * NOTE: We need to do this in a two-pass system to avoid conflicts with strokes
+        *       getting repeatedly moved
+        */
+       CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
+       {
+               bGPDframe *gpf = gpl->actframe;
+               bGPDstroke *gps, *gpsn;
+               
+               /* skip if no frame with strokes, or if this is the layer we're moving strokes to */
+               if ((gpl == target_layer) || (gpf == NULL))
+                       continue;
+               
+               /* make copies of selected strokes, and deselect these once we're done */
+               for (gps = gpf->strokes.first; gps; gps = gpsn) {
+                       gpsn = gps->next;
+                       
+                       /* skip strokes that are invalid for current view */
+                       if (ED_gpencil_stroke_can_use(C, gps) == false)
+                               continue;
+                       
+                       /* TODO: Don't just move entire strokes - instead, only copy the selected portions... */
+                       if (gps->flag & GP_STROKE_SELECT) {
+                               BLI_remlink(&gpf->strokes, gps);
+                               BLI_addtail(&strokes, gps);
+                       }
+               }
+       }
+       CTX_DATA_END;
+       
+       /* Paste them all in one go */
+       if (strokes.first) {
+               Scene *scene = CTX_data_scene(C);
+               bGPDframe *gpf = gpencil_layer_getframe(target_layer, CFRA, true);
+               
+               BLI_movelisttolist(&gpf->strokes, &strokes);
+               BLI_assert((strokes.first == strokes.last) && (strokes.first == NULL));
+       }
+       
+       /* updates */
        WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
        
        return OPERATOR_FINISHED;
 }
 
-void GPENCIL_OT_layer_add(wmOperatorType *ot)
+void GPENCIL_OT_move_to_layer(wmOperatorType *ot)
 {
        /* identifiers */
-       ot->name = "Add New Layer";
-       ot->idname = "GPENCIL_OT_layer_add";
-       ot->description = "Add new Grease Pencil layer for the active Grease Pencil datablock";
-       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+       ot->name = "Move Strokes to Layer";
+       ot->idname = "GPENCIL_OT_move_to_layer";
+       ot->description = "Move selected strokes to another layer"; // XXX: allow moving individual points too?
        
        /* callbacks */
-       ot->exec = gp_layer_add_exec;
-       ot->poll = gp_add_poll;
+       ot->invoke = gp_move_to_layer_invoke;
+       ot->exec = gp_move_to_layer_exec;
+       ot->poll = gp_stroke_edit_poll; // XXX?
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+       
+       /* gp layer to use (dynamic enum) */
+       ot->prop = RNA_def_enum(ot->srna, "layer", DummyRNA_DEFAULT_items, 0, "Grease Pencil Layer", "");
+       RNA_def_enum_funcs(ot->prop, ED_gpencil_layers_with_new_enum_itemf);
 }
 
 /* ******************* Delete Active Frame ************************ */
 
 static int gp_actframe_delete_poll(bContext *C)
 {
-       bGPdata *gpd = gpencil_data_get_active(C);
+       bGPdata *gpd = ED_gpencil_data_get_active(C);
        bGPDlayer *gpl = gpencil_layer_getactive(gpd);
        
        /* only if there's an active layer with an active frame */
@@ -350,7 +597,7 @@ static int gp_actframe_delete_poll(bContext *C)
 static int gp_actframe_delete_exec(bContext *C, wmOperator *op)
 {
        Scene *scene = CTX_data_scene(C);
-       bGPdata *gpd = gpencil_data_get_active(C);
+       bGPdata *gpd = ED_gpencil_data_get_active(C);
        bGPDlayer *gpl = gpencil_layer_getactive(gpd);
        bGPDframe *gpf = gpencil_layer_getframe(gpl, CFRA, 0);
        
@@ -379,6 +626,7 @@ void GPENCIL_OT_active_frame_delete(wmOperatorType *ot)
        ot->name = "Delete Active Frame";
        ot->idname = "GPENCIL_OT_active_frame_delete";
        ot->description = "Delete the active frame for the active Grease Pencil datablock";
+       
        ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
        
        /* callbacks */
@@ -386,1214 +634,560 @@ void GPENCIL_OT_active_frame_delete(wmOperatorType *ot)
        ot->poll = gp_actframe_delete_poll;
 }
 
-/* ************************************************ */
-/* Grease Pencil to Data Operator */
-
-/* defines for possible modes */
-enum {
-       GP_STROKECONVERT_PATH = 1,
-       GP_STROKECONVERT_CURVE,
-};
-
-/* Defines for possible timing modes */
-enum {
-       GP_STROKECONVERT_TIMING_NONE = 1,
-       GP_STROKECONVERT_TIMING_LINEAR = 2,
-       GP_STROKECONVERT_TIMING_FULL = 3,
-       GP_STROKECONVERT_TIMING_CUSTOMGAP = 4,
-};
-
-/* RNA enum define */
-static EnumPropertyItem prop_gpencil_convertmodes[] = {
-       {GP_STROKECONVERT_PATH, "PATH", 0, "Path", ""},
-       {GP_STROKECONVERT_CURVE, "CURVE", 0, "Bezier Curve", ""},
-       {0, NULL, 0, NULL, NULL}
-};
-
-static EnumPropertyItem prop_gpencil_convert_timingmodes_restricted[] = {
-       {GP_STROKECONVERT_TIMING_NONE, "NONE", 0, "No Timing", "Ignore timing"},
-       {GP_STROKECONVERT_TIMING_LINEAR, "LINEAR", 0, "Linear", "Simple linear timing"},
-       {0, NULL, 0, NULL, NULL},
-};
-
-static EnumPropertyItem prop_gpencil_convert_timingmodes[] = {
-       {GP_STROKECONVERT_TIMING_NONE, "NONE", 0, "No Timing", "Ignore timing"},
-       {GP_STROKECONVERT_TIMING_LINEAR, "LINEAR", 0, "Linear", "Simple linear timing"},
-       {GP_STROKECONVERT_TIMING_FULL, "FULL", 0, "Original", "Use the original timing, gaps included"},
-       {GP_STROKECONVERT_TIMING_CUSTOMGAP, "CUSTOMGAP", 0, "Custom Gaps",
-                                           "Use the original timing, but with custom gap lengths (in frames)"},
-       {0, NULL, 0, NULL, NULL},
-};
-
-static EnumPropertyItem *rna_GPConvert_mode_items(bContext *UNUSED(C), PointerRNA *ptr, PropertyRNA *UNUSED(prop),
-                                                  int *free)
-{
-       *free = FALSE;
-       if (RNA_boolean_get(ptr, "use_timing_data")) {
-               return prop_gpencil_convert_timingmodes;
-       }
-       return prop_gpencil_convert_timingmodes_restricted;
-}
+/* ******************* Delete Operator ************************ */
 
-/* --- */
+typedef enum eGP_DeleteMode {
+       /* delete selected stroke points */
+       GP_DELETEOP_POINTS          = 0,
+       /* delete selected strokes */
+       GP_DELETEOP_STROKES         = 1,
+       /* delete active frame */
+       GP_DELETEOP_FRAME           = 2,
+} eGP_DeleteMode;
 
-/* convert the coordinates from the given stroke point into 3d-coordinates 
- *     - assumes that the active space is the 3D-View
- */
-static void gp_strokepoint_convertcoords(bContext *C, bGPDstroke *gps, bGPDspoint *pt, float p3d[3], rctf *subrect)
+/* ----------------------------------- */
+
+/* Delete selected strokes */
+static int gp_delete_selected_strokes(bContext *C)
 {
-       Scene *scene = CTX_data_scene(C);
-       View3D *v3d = CTX_wm_view3d(C);
-       ARegion *ar = CTX_wm_region(C);
+       bool changed = false;
        
-       if (gps->flag & GP_STROKE_3DSPACE) {
-               /* directly use 3d-coordinates */
-               copy_v3_v3(p3d, &pt->x);
-       }
-       else {
-               float *fp = give_cursor(scene, v3d);
-               float mvalf[2];
+       CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
+       {
+               bGPDframe *gpf = gpl->actframe;
+               bGPDstroke *gps, *gpsn;
                
-               /* get screen coordinate */
-               if (gps->flag & GP_STROKE_2DSPACE) {
-                       int mvali[2];
-                       View2D *v2d = &ar->v2d;
-                       UI_view2d_view_to_region(v2d, pt->x, pt->y, mvali, mvali + 1);
-                       VECCOPY2D(mvalf, mvali);
-               }
-               else {
-                       if (subrect) {
-                               mvalf[0] = (((float)pt->x / 100.0f) * BLI_rctf_size_x(subrect)) + subrect->xmin;
-                               mvalf[1] = (((float)pt->y / 100.0f) * BLI_rctf_size_y(subrect)) + subrect->ymin;
-                       }
-                       else {
-                               mvalf[0] = (float)pt->x / 100.0f * ar->winx;
-                               mvalf[1] = (float)pt->y / 100.0f * ar->winy;
+               if (gpf == NULL)
+                       continue;
+               
+               /* simply delete strokes which are selected */
+               for (gps = gpf->strokes.first; gps; gps = gpsn) {
+                       gpsn = gps->next;
+                       
+                       /* skip strokes that are invalid for current view */
+                       if (ED_gpencil_stroke_can_use(C, gps) == false)
+                               continue;
+                       
+                       /* free stroke if selected */
+                       if (gps->flag & GP_STROKE_SELECT) {
+                               /* free stroke memory arrays, then stroke itself */
+                               if (gps->points) MEM_freeN(gps->points);
+                               BLI_freelinkN(&gpf->strokes, gps);
+                               
+                               changed = true;
                        }
                }
-
-               /* convert screen coordinate to 3d coordinates 
-                *      - method taken from editview.c - mouse_cursor() 
-                */
-               ED_view3d_win_to_3d(ar, fp, mvalf, p3d);
-       }
-}
-
-/* --- */
-
-/* temp struct for gp_stroke_path_animation() */
-typedef struct tGpTimingData {
-       /* Data set from operator settings */
-       int mode;
-       int frame_range; /* Number of frames evaluated for path animation */
-       int start_frame, end_frame;
-       int realtime; /* A bool, actually, will overwrite end_frame in case of Original or CustomGap timing... */
-       float gap_duration, gap_randomness; /* To be used with CustomGap mode*/
-       int seed;
-
-       /* Data set from points, used to compute final timing FCurve */
-       int num_points, cur_point;
-
-       /* Distances */
-       float *dists;
-       float tot_dist;
-
-       /* Times */
-       float *times; /* Note: Gap times will be negative! */
-       float tot_time, gap_tot_time;
-       double inittime;
-} tGpTimingData;
-
-static void _gp_timing_data_set_nbr(tGpTimingData *gtd, int nbr)
-{
-       float *tmp;
-
-       BLI_assert(nbr > gtd->num_points);
-
-       tmp = gtd->dists;
-       gtd->dists = MEM_callocN(sizeof(float) * nbr, __func__);
-       if (tmp) {
-               memcpy(gtd->dists, tmp, sizeof(float) * gtd->num_points);
-               MEM_freeN(tmp);
        }
-
-       tmp = gtd->times;
-       gtd->times = MEM_callocN(sizeof(float) * nbr, __func__);
-       if (tmp) {
-               memcpy(gtd->times, tmp, sizeof(float) * gtd->num_points);
-               MEM_freeN(tmp);
+       CTX_DATA_END;
+       
+       if (changed) {
+               WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+               return OPERATOR_FINISHED;
        }
-
-       gtd->num_points = nbr;
-}
-
-static void gp_timing_data_add_point(tGpTimingData *gtd, double stroke_inittime, float time, float delta_dist)
-{
-       if (time < 0.0f) {
-               /* This is a gap, negative value! */
-               gtd->tot_time = -(gtd->times[gtd->cur_point] = -(((float)(stroke_inittime - gtd->inittime)) + time));
-               gtd->gap_tot_time += gtd->times[gtd->cur_point] - gtd->times[gtd->cur_point - 1];
+       else {
+               return OPERATOR_CANCELLED;
        }
-       else
-               gtd->tot_time = (gtd->times[gtd->cur_point] = (((float)(stroke_inittime - gtd->inittime)) + time));
-       gtd->dists[gtd->cur_point] = (gtd->tot_dist += delta_dist);
-       gtd->cur_point++;
 }
 
-/* In frames! Binary search for FCurve keys have a threshold of 0.01, so we can’t set
- * arbitrarily close points - this is esp. important with NoGaps mode!
- */
-#define MIN_TIME_DELTA 0.02f
+/* ----------------------------------- */
 
-/* Loop over next points to find the end of the stroke, and compute */
-static int gp_find_end_of_stroke_idx(tGpTimingData *gtd, int idx, int nbr_gaps, int *nbr_done_gaps,
-                                     float tot_gaps_time, float delta_time, float *next_delta_time)
+/* Delete selected points but keep the stroke */
+static int gp_dissolve_selected_points(bContext *C)
 {
-       int j;
-
-       for (j = idx + 1; j < gtd->num_points; j++) {
-               if (gtd->times[j] < 0) {
-                       gtd->times[j] = -gtd->times[j];
-                       if (gtd->mode == GP_STROKECONVERT_TIMING_CUSTOMGAP) {
-                               /* In this mode, gap time between this stroke and the next should be 0 currently...
-                                * So we have to compute its final duration!
-                                */
-                               if (gtd->gap_randomness > 0.0f) {
-                                       /* We want gaps that are in gtd->gap_duration +/- gtd->gap_randomness range,
-                                        * and which sum to exactly tot_gaps_time...
-                                        */
-                                       int rem_gaps = nbr_gaps - *nbr_done_gaps;
-                                       if (rem_gaps < 2) {
-                                               /* Last gap, just give remaining time! */
-                                               *next_delta_time = tot_gaps_time;
-                                       }
-                                       else {
-                                               float delta, min, max;
-                                               /* This code ensures that if the first gaps have been shorter than average gap_duration,
-                                                * next gaps will tend to be longer (i.e. try to recover the lateness), and vice-versa!
-                                                */
-                                               delta = delta_time - (gtd->gap_duration * *nbr_done_gaps);
-                                               /* Clamp min between [-gap_randomness, 0.0], with lower delta giving higher min */
-                                               min = -gtd->gap_randomness - delta;
-                                               CLAMP(min, -gtd->gap_randomness, 0.0f);
-                                               /* Clamp max between [0.0, gap_randomness], with lower delta giving higher max */
-                                               max = gtd->gap_randomness - delta;
-                                               CLAMP(max, 0.0f, gtd->gap_randomness);
-                                               *next_delta_time += gtd->gap_duration + (BLI_frand() * (max - min)) + min;
+       bool changed = false;
+       
+       CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
+       {
+               bGPDframe *gpf = gpl->actframe;
+               bGPDstroke *gps, *gpsn;
+               
+               if (gpf == NULL)
+                       continue;
+               
+               /* simply delete points from selected strokes
+                * NOTE: we may still have to remove the stroke if it ends up having no points!
+                */
+               for (gps = gpf->strokes.first; gps; gps = gpsn) {
+                       gpsn = gps->next;
+                       
+                       /* skip strokes that are invalid for current view */
+                       if (ED_gpencil_stroke_can_use(C, gps) == false)
+                               continue;
+                       
+                       if (gps->flag & GP_STROKE_SELECT) {
+                               bGPDspoint *pt;
+                               int i;
+                               
+                               int tot = gps->totpoints; /* number of points in new buffer */
+                               
+                               /* First Pass: Count how many points are selected (i.e. how many to remove) */
+                               for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                                       if (pt->flag & GP_SPOINT_SELECT) {
+                                               /* selected point - one of the points to remove */
+                                               tot--;
                                        }
                                }
+                               
+                               /* if no points are left, we simply delete the entire stroke */
+                               if (tot <= 0) {
+                                       /* remove the entire stroke */
+                                       MEM_freeN(gps->points);
+                                       BLI_freelinkN(&gpf->strokes, gps);
+                               }
                                else {
-                                       *next_delta_time += gtd->gap_duration;
+                                       /* just copy all unselected into a smaller buffer */
+                                       bGPDspoint *new_points = MEM_callocN(sizeof(bGPDspoint) * tot, "new gp stroke points copy");
+                                       bGPDspoint *npt        = new_points;
+                                       
+                                       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                                               if ((pt->flag & GP_SPOINT_SELECT) == 0) {
+                                                       *npt = *pt;
+                                                       npt++;
+                                               }
+                                       }
+                                       
+                                       /* free the old buffer */
+                                       MEM_freeN(gps->points);
+                                       
+                                       /* save the new buffer */
+                                       gps->points = new_points;
+                                       gps->totpoints = tot;
+                                       
+                                       /* deselect the stroke, since none of its selected points will still be selected */
+                                       gps->flag &= ~GP_STROKE_SELECT;
                                }
+                               
+                               changed = true;
                        }
-                       (*nbr_done_gaps)++;
-                       break;
                }
        }
-
-       return j - 1;
+       CTX_DATA_END;
+       
+       if (changed) {
+               WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+               return OPERATOR_FINISHED;
+       }
+       else {
+               return OPERATOR_CANCELLED;
+       }
 }
 
-static void gp_stroke_path_animation_preprocess_gaps(tGpTimingData *gtd, int *nbr_gaps, float *tot_gaps_time)
-{
-       int i;
-       float delta_time = 0.0f;
-
-       for (i = 0; i < gtd->num_points; i++) {
-               if (gtd->times[i] < 0 && i) {
-                       (*nbr_gaps)++;
-                       gtd->times[i] = -gtd->times[i] - delta_time;
-                       delta_time += gtd->times[i] - gtd->times[i - 1];
-                       gtd->times[i] = -gtd->times[i - 1]; /* Temp marker, values *have* to be different! */
-               }
-               else {
-                       gtd->times[i] -= delta_time;
-               }
-       }
-       gtd->tot_time -= delta_time;
-
-       *tot_gaps_time = (float)(*nbr_gaps) * gtd->gap_duration;
-       gtd->tot_time += *tot_gaps_time;
-       if (G.debug & G_DEBUG) {
-               printf("%f, %f, %f, %d\n", gtd->tot_time, delta_time, *tot_gaps_time, *nbr_gaps);
-       }
-       if (gtd->gap_randomness > 0.0f) {
-               BLI_srandom(gtd->seed);
-       }
-}
-
-static void gp_stroke_path_animation_add_keyframes(ReportList *reports, PointerRNA ptr, PropertyRNA *prop, FCurve *fcu,
-                                                   Curve *cu, tGpTimingData *gtd, float time_range,
-                                                   int nbr_gaps, float tot_gaps_time)
-{
-       /* Use actual recorded timing! */
-       float time_start = (float)gtd->start_frame;
+/* ----------------------------------- */
 
-       float last_valid_time = 0.0f;
-       int end_stroke_idx = -1, start_stroke_idx = 0;
-       float end_stroke_time = 0.0f;
+/* Temp data for storing information about an "island" of points
+ * that should be kept when splitting up a stroke. Used in:
+ * gp_stroke_delete_tagged_points()
+ */
+typedef struct tGPDeleteIsland {
+       int start_idx;
+       int end_idx;
+} tGPDeleteIsland;
 
-       /* CustomGaps specific */
-       float delta_time = 0.0f, next_delta_time = 0.0f;
-       int nbr_done_gaps = 0;
 
+/* Split the given stroke into several new strokes, partitioning
+ * it based on whether the stroke points have a particular flag
+ * is set (e.g. "GP_SPOINT_SELECT" in most cases, but not always)
+ *
+ * The algorithm used here is as follows:
+ * 1) We firstly identify the number of "islands" of non-tagged points
+ *    which will all end up being in new strokes.
+ *    - In the most extreme case (i.e. every other vert is a 1-vert island),
+ *      we have at most n / 2 islands
+ *    - Once we start having larger islands than that, the number required
+ *      becomes much less
+ * 2) Each island gets converted to a new stroke
+ */
+void gp_stroke_delete_tagged_points(bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *next_stroke, int tag_flags)
+{
+       tGPDeleteIsland *islands = MEM_callocN(sizeof(tGPDeleteIsland) * (gps->totpoints + 1) / 2, "gp_point_islands");
+       bool in_island  = false;
+       int num_islands = 0;
+       
+       bGPDspoint *pt;
        int i;
-       float cfra;
-
-       /* This is a bit tricky, as:
-        * - We can't add arbitrarily close points on FCurve (in time).
-        * - We *must* have all "caps" points of all strokes in FCurve, as much as possible!
-        */
-       for (i = 0; i < gtd->num_points; i++) {
-               /* If new stroke... */
-               if (i > end_stroke_idx) {
-                       start_stroke_idx = i;
-                       delta_time = next_delta_time;
-                       /* find end of that new stroke */
-                       end_stroke_idx = gp_find_end_of_stroke_idx(gtd, i, nbr_gaps, &nbr_done_gaps,
-                                                                  tot_gaps_time, delta_time, &next_delta_time);
-                       /* This one should *never* be negative! */
-                       end_stroke_time = time_start + ((gtd->times[end_stroke_idx] + delta_time) / gtd->tot_time * time_range);
+       
+       /* First Pass: Identify start/end of islands */
+       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+               if (pt->flag & tag_flags) {
+                       /* selected - stop accumulating to island */
+                       in_island = false;
                }
-
-               /* Simple proportional stuff... */
-               cu->ctime = gtd->dists[i] / gtd->tot_dist * cu->pathlen;
-               cfra = time_start + ((gtd->times[i] + delta_time) / gtd->tot_time * time_range);
-
-               /* And now, the checks about timing... */
-               if (i == start_stroke_idx) {
-                       /* If first point of a stroke, be sure it's enough ahead of last valid keyframe, and
-                        * that the end point of the stroke is far enough!
-                        * In case it is not, we keep the end point...
-                        * Note that with CustomGaps mode, this is here we set the actual gap timing!
-                        */
-                       if ((end_stroke_time - last_valid_time) > MIN_TIME_DELTA * 2) {
-                               if ((cfra - last_valid_time) < MIN_TIME_DELTA) {
-                                       cfra = last_valid_time + MIN_TIME_DELTA;
-                               }
-                               insert_keyframe_direct(reports, ptr, prop, fcu, cfra, INSERTKEY_FAST);
-                               last_valid_time = cfra;
-                       }
-                       else if (G.debug & G_DEBUG) {
-                               printf("\t Skipping start point %d, too close from end point %d\n", i, end_stroke_idx);
+               else {
+                       /* unselected - start of a new island? */
+                       int idx;
+                       
+                       if (in_island) {
+                               /* extend existing island */
+                               idx = num_islands - 1;
+                               islands[idx].end_idx = i;
                        }
-               }
-               else if (i == end_stroke_idx) {
-                       /* Always try to insert end point of a curve (should be safe enough, anyway...) */
-                       if ((cfra - last_valid_time) < MIN_TIME_DELTA) {
-                               cfra = last_valid_time + MIN_TIME_DELTA;
+                       else {
+                               /* start of new island */
+                               in_island = true;
+                               num_islands++;
+                               
+                               idx = num_islands - 1;
+                               islands[idx].start_idx = islands[idx].end_idx = i;
                        }
-                       insert_keyframe_direct(reports, ptr, prop, fcu, cfra, INSERTKEY_FAST);
-                       last_valid_time = cfra;
                }
-               else {
-                       /* Else ("middle" point), we only insert it if it's far enough from last keyframe,
-                        * and also far enough from (not yet added!) end_stroke keyframe!
+       }
+       
+       /* Watch out for special case where No islands = All points selected = Delete Stroke only */
+       if (num_islands) {
+               /* there are islands, so create a series of new strokes, adding them before the "next" stroke */
+               int idx;
+               
+               /* Create each new stroke... */
+               for (idx = 0; idx < num_islands; idx++) {
+                       tGPDeleteIsland *island = &islands[idx];
+                       bGPDstroke *new_stroke  = MEM_dupallocN(gps);
+                       
+                       /* Compute new buffer size (+ 1 needed as the endpoint index is "inclusive") */
+                       new_stroke->totpoints = island->end_idx - island->start_idx + 1;
+                       new_stroke->points    = MEM_callocN(sizeof(bGPDspoint) * new_stroke->totpoints, "gp delete stroke fragment");
+                       
+                       /* Copy over the relevant points */
+                       memcpy(new_stroke->points, gps->points + island->start_idx, sizeof(bGPDspoint) * new_stroke->totpoints);
+                       
+                       
+                       /* Each island corresponds to a new stroke. We must adjust the 
+                        * timings of these new strokes:
+                        *
+                        * Each point's timing data is a delta from stroke's inittime, so as we erase some points from
+                        * the start of the stroke, we have to offset this inittime and all remaining points' delta values.
+                        * This way we get a new stroke with exactly the same timing as if user had started drawing from
+                        * the first non-removed point...
                         */
-                       if ((cfra - last_valid_time) > MIN_TIME_DELTA && (end_stroke_time - cfra) > MIN_TIME_DELTA) {
-                               insert_keyframe_direct(reports, ptr, prop, fcu, cfra, INSERTKEY_FAST);
-                               last_valid_time = cfra;
+                       {
+                               bGPDspoint *pts;
+                               float delta = gps->points[island->start_idx].time;
+                               int j;
+                               
+                               new_stroke->inittime += (double)delta;
+                               
+                               pts = new_stroke->points;
+                               for (j = 0; j < new_stroke->totpoints; j++, pts++) {
+                                       pts->time -= delta;
+                               }
                        }
-                       else if (G.debug & G_DEBUG) {
-                               printf("\t Skipping \"middle\" point %d, too close from last added point or end point %d\n",
-                                      i, end_stroke_idx);
+                       
+                       /* Add new stroke to the frame */
+                       if (next_stroke) {
+                               BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke);
+                       }
+                       else {
+                               BLI_addtail(&gpf->strokes, new_stroke);
                        }
                }
        }
+       
+       /* free islands */
+       MEM_freeN(islands);
+       
+       /* Delete the old stroke */
+       MEM_freeN(gps->points);
+       BLI_freelinkN(&gpf->strokes, gps);
 }
 
-static void gp_stroke_path_animation(bContext *C, ReportList *reports, Curve *cu, tGpTimingData *gtd)
+
+/* Split selected strokes into segments, splitting on selected points */
+static int gp_delete_selected_points(bContext *C)
 {
-       Scene *scene = CTX_data_scene(C);
-       bAction *act;
-       FCurve *fcu;
-       PointerRNA ptr;
-       PropertyRNA *prop = NULL;
-
-       int nbr_gaps = 0, i;
-
-       if (gtd->mode == GP_STROKECONVERT_TIMING_NONE)
-               return;
-
-       /* gap_duration and gap_randomness are in frames, but we need seconds!!! */
-       gtd->gap_duration = FRA2TIME(gtd->gap_duration);
-       gtd->gap_randomness = FRA2TIME(gtd->gap_randomness);
-
-       /* Enable path! */
-       cu->flag |= CU_PATH;
-       cu->pathlen = gtd->frame_range;
-
-       /* Get or create default action to add F-Curve+keyframe to */
-       act = verify_adt_action((ID*)cu, TRUE);
-       /* Create RNA stuff */
-       RNA_id_pointer_create((ID*)cu, &ptr);
-       prop = RNA_struct_find_property(&ptr, "eval_time");
-       /* Get or create fcurve */
-       fcu = verify_fcurve(act, NULL, &ptr, "eval_time", 0, TRUE);
-
-       if (G.debug & G_DEBUG) {
-               printf("%s: tot len: %f\t\ttot time: %f\n", __func__, gtd->tot_dist, gtd->tot_time);
-               for (i = 0; i < gtd->num_points; i++) {
-                       printf("\tpoint %d:\t\tlen: %f\t\ttime: %f\n", i, gtd->dists[i], gtd->times[i]);
+       bool changed = false;
+       
+       CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
+       {
+               bGPDframe *gpf = gpl->actframe;
+               bGPDstroke *gps, *gpsn;
+               
+               if (gpf == NULL)
+                       continue;
+               
+               /* simply delete strokes which are selected */
+               for (gps = gpf->strokes.first; gps; gps = gpsn) {
+                       gpsn = gps->next;
+                       
+                       /* skip strokes that are invalid for current view */
+                       if (ED_gpencil_stroke_can_use(C, gps) == false)
+                               continue;
+                       
+                       
+                       if (gps->flag & GP_STROKE_SELECT) {
+                               /* deselect old stroke, since it will be used as template for the new strokes */
+                               gps->flag &= ~GP_STROKE_SELECT;
+                               
+                               /* delete unwanted points by splitting stroke into several smaller ones */
+                               gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT);
+                               
+                               changed = true;
+                       }
                }
        }
-
-       if (gtd->mode == GP_STROKECONVERT_TIMING_LINEAR) {
-               float cfra;
-
-               /* Linear extrapolation! */
-               fcu->extend = FCURVE_EXTRAPOLATE_LINEAR;
-
-               cu->ctime = 0.0f;
-               cfra = (float)gtd->start_frame;
-               insert_keyframe_direct(reports, ptr, prop, fcu, cfra, INSERTKEY_FAST);
-
-               cu->ctime = cu->pathlen;
-               if (gtd->realtime) {
-                       cfra += (float)TIME2FRA(gtd->tot_time); /* Seconds to frames */
-               }
-               else {
-                       cfra = (float)gtd->end_frame;
-               }
-               insert_keyframe_direct(reports, ptr, prop, fcu, cfra, INSERTKEY_FAST);
+       CTX_DATA_END;
+       
+       if (changed) {
+               WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+               return OPERATOR_FINISHED;
        }
        else {
-               /* Use actual recorded timing! */
-               float time_range;
-
-               /* CustomGaps specific */
-               float tot_gaps_time = 0.0f;
-
-               /* Pre-process gaps, in case we don't want to keep their org timing */
-               if (gtd->mode == GP_STROKECONVERT_TIMING_CUSTOMGAP) {
-                       gp_stroke_path_animation_preprocess_gaps(gtd, &nbr_gaps, &tot_gaps_time);
-               }
-
-               if (gtd->realtime) {
-                       time_range = (float)TIME2FRA(gtd->tot_time); /* Seconds to frames */
-               }
-               else {
-                       time_range = (float)(gtd->end_frame - gtd->start_frame);
-               }
-
-               if (G.debug & G_DEBUG) {
-                       printf("Starting keying!\n");
-               }
-
-               gp_stroke_path_animation_add_keyframes(reports, ptr, prop, fcu, cu, gtd, time_range,
-                                                      nbr_gaps, tot_gaps_time);
-
-       }
-
-       /* As we used INSERTKEY_FAST mode, we need to recompute all curve's handles now */
-       calchandles_fcurve(fcu);
-
-       if (G.debug & G_DEBUG) {
-               printf("%s: \ntot len: %f\t\ttot time: %f\n", __func__, gtd->tot_dist, gtd->tot_time);
-               for (i = 0; i < gtd->num_points; i++) {
-                       printf("\tpoint %d:\t\tlen: %f\t\ttime: %f\n", i, gtd->dists[i], gtd->times[i]);
-               }
-               printf("\n\n");
+               return OPERATOR_CANCELLED;
        }
-
-       WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
-
-       /* send updates */
-       DAG_id_tag_update((ID*)cu, 0);
 }
 
-#undef MIN_TIME_DELTA
+/* ----------------------------------- */
 
-#define GAP_DFAC 0.05f
-#define WIDTH_CORR_FAC 0.1f
-#define BEZT_HANDLE_FAC 0.3f
-
-/* convert stroke to 3d path */
-static void gp_stroke_to_path(bContext *C, bGPDlayer *gpl, bGPDstroke *gps, Curve *cu, rctf *subrect, Nurb **curnu,
-                              float minmax_weights[2], float rad_fac, int stitch, tGpTimingData *gtd)
+static int gp_delete_exec(bContext *C, wmOperator *op)
 {
-       bGPDspoint *pt;
-       Nurb *nu = curnu ? *curnu : NULL;
-       BPoint *bp, *prev_bp = NULL;
-       int i, old_nbp = 0;
-       const int do_gtd = (gtd->mode != GP_STROKECONVERT_TIMING_NONE);
-
-       /* create new 'nurb' or extend current one within the curve */
-       if (nu) {
-               old_nbp = nu->pntsu;
-               /* If stitch, the first point of this stroke is already present in current nu.
-                * Else, we have to add to additional points to make the zero-radius link between strokes.
-                */
-               BKE_nurb_points_add(nu, gps->totpoints + (stitch ? -1 : 2));
-       }
-       else {
-               nu = (Nurb *)MEM_callocN(sizeof(Nurb), "gpstroke_to_path(nurb)");
+       eGP_DeleteMode mode = RNA_enum_get(op->ptr, "type");
+       int result = OPERATOR_CANCELLED;
+       
+       switch (mode) {
+               case GP_DELETEOP_STROKES:       /* selected strokes */
+                       result = gp_delete_selected_strokes(C);
+                       break;
                
-               nu->pntsu = gps->totpoints;
-               nu->pntsv = 1;
-               nu->orderu = 2; /* point-to-point! */
-               nu->type = CU_NURBS;
-               nu->flagu = CU_NURB_ENDPOINT;
-               nu->resolu = cu->resolu;
-               nu->resolv = cu->resolv;
-               nu->knotsu = NULL;
-
-               nu->bp = (BPoint *)MEM_callocN(sizeof(BPoint) * nu->pntsu, "bpoints");
-
-               stitch = FALSE; /* Security! */
-       }
-
-       if (do_gtd) {
-               _gp_timing_data_set_nbr(gtd, nu->pntsu);
-       }
-
-       /* If needed, make the link between both strokes with two zero-radius additional points */
-       /* About "zero-radius" point interpolations:
-        * - If we have at least two points in current curve (most common case), we linearly extrapolate
-        *   the last segment to get the first point (p1) position and timing.
-        * - If we do not have those (quite odd, but may happen), we linearly interpolate the last point
-        *   with the first point of the current stroke.
-        * The same goes for the second point, first segment of the current stroke is "negatively" extrapolated
-        * if it exists, else (if the stroke is a single point), linear interpolation with last curve point...
-        */
-       if (curnu && !stitch && old_nbp) {
-               float p1[3], p2[3], p[3], next_p[3];
-               float delta_time;
-
-               prev_bp = NULL;
-               if (old_nbp > 1 && gps->prev && gps->prev->totpoints > 1) {
-                       /* Only use last curve segment if previous stroke was not a single-point one! */
-                       prev_bp = nu->bp + old_nbp - 2;
-               }
-               bp = nu->bp + old_nbp - 1;
-               /* XXX We do this twice... Not sure it's worth to bother about this! */
-               gp_strokepoint_convertcoords(C, gps, gps->points, p, subrect);
-               if (prev_bp) {
-                       interp_v3_v3v3(p1, prev_bp->vec, bp->vec, 1.0f + GAP_DFAC);
-               }
-               else {
-                       interp_v3_v3v3(p1, bp->vec, p, GAP_DFAC);
-               }
-               if (gps->totpoints > 1) {
-                       /* XXX We do this twice... Not sure it's worth to bother about this! */
-                       gp_strokepoint_convertcoords(C, gps, gps->points + 1, next_p, subrect);
-                       interp_v3_v3v3(p2, p, next_p, -GAP_DFAC);
-               }
-               else {
-                       interp_v3_v3v3(p2, p, bp->vec, GAP_DFAC);
-               }
-
-               /* First point */
-               bp++;
-               copy_v3_v3(bp->vec, p1);
-               bp->vec[3] = 1.0f;
-               bp->f1 = SELECT;
-               minmax_weights[0] = bp->radius = bp->weight = 0.0f;
-               if (do_gtd) {
-                       if (prev_bp) {
-                               delta_time = gtd->tot_time + (gtd->tot_time - gtd->times[gtd->cur_point - 1]) * GAP_DFAC;
-                       }
-                       else {
-                               delta_time = gtd->tot_time + (((float)(gps->inittime - gtd->inittime)) - gtd->tot_time) * GAP_DFAC;
-                       }
-                       gp_timing_data_add_point(gtd, gtd->inittime, delta_time, len_v3v3((bp - 1)->vec, p1));
-               }
-
-               /* Second point */
-               bp++;
-               copy_v3_v3(bp->vec, p2);
-               bp->vec[3] = 1.0f;
-               bp->f1 = SELECT;
-               minmax_weights[0] = bp->radius = bp->weight = 0.0f;
-               if (do_gtd) {
-                       /* This negative delta_time marks the gap! */
-                       if (gps->totpoints > 1) {
-                               delta_time = ((gps->points + 1)->time - gps->points->time) * -GAP_DFAC;
-                       }
-                       else {
-                               delta_time = -(((float)(gps->inittime - gtd->inittime)) - gtd->tot_time) * GAP_DFAC;
-                       }
-                       gp_timing_data_add_point(gtd, gps->inittime, delta_time, len_v3v3(p1, p2));
-               }
-
-               old_nbp += 2;
-       }
-       if (old_nbp && do_gtd) {
-               prev_bp = nu->bp + old_nbp - 1;
-       }
-       /* add points */
-       for (i = stitch ? 1 : 0, pt = gps->points + (stitch ? 1 : 0), bp = nu->bp + old_nbp;
-            i < gps->totpoints;
-            i++, pt++, bp++)
-       {
-               float p3d[3];
-               float width = pt->pressure * gpl->thickness * WIDTH_CORR_FAC;
-
-               /* get coordinates to add at */
-               gp_strokepoint_convertcoords(C, gps, pt, p3d, subrect);
-               copy_v3_v3(bp->vec, p3d);
-               bp->vec[3] = 1.0f;
-
-               /* set settings */
-               bp->f1 = SELECT;
-               bp->radius = width * rad_fac;
-               bp->weight = width;
-               CLAMP(bp->weight, 0.0f, 1.0f);
-               if (bp->weight < minmax_weights[0]) {
-                       minmax_weights[0] = bp->weight;
-               }
-               else if (bp->weight > minmax_weights[1]) {
-                       minmax_weights[1] = bp->weight;
-               }
-
-               /* Update timing data */
-               if (do_gtd) {
-                       gp_timing_data_add_point(gtd, gps->inittime, pt->time, prev_bp ? len_v3v3(prev_bp->vec, p3d) : 0.0f);
-               }
-               prev_bp = bp;
-       }
+               case GP_DELETEOP_POINTS:        /* selected points (breaks the stroke into segments) */
+                       result = gp_delete_selected_points(C);
+                       break;
 
-       /* add nurb to curve */
-       if (!curnu || !*curnu) {
-               BLI_addtail(&cu->nurb, nu);
-       }
-       if (curnu) {
-               *curnu = nu;
+               case GP_DELETEOP_FRAME:         /* active frame */
+                       result = gp_actframe_delete_exec(C, op);
+                       break;
        }
-
-       BKE_nurb_knot_calc_u(nu);
+       
+       return result;
 }
 
-static int gp_camera_view_subrect(bContext *C, rctf *subrect)
+void GPENCIL_OT_delete(wmOperatorType *ot)
 {
-       View3D *v3d = CTX_wm_view3d(C);
-       ARegion *ar = CTX_wm_region(C);
-
-       if (v3d) {
-               RegionView3D *rv3d = ar->regiondata;
-               
-               /* for camera view set the subrect */
-               if (rv3d->persp == RV3D_CAMOB) {
-                       Scene *scene = CTX_data_scene(C);
-                       ED_view3d_calc_camera_border(scene, ar, v3d, rv3d, subrect, TRUE); /* no shift */
-                       return 1;
-               }
-       }
-
-       return 0;
+       static EnumPropertyItem prop_gpencil_delete_types[] = {
+               {GP_DELETEOP_POINTS, "POINTS", 0, "Points", "Delete selected points and split strokes into segments"},
+               {GP_DELETEOP_STROKES, "STROKES", 0, "Strokes", "Delete selected strokes"},
+               {GP_DELETEOP_FRAME, "FRAME", 0, "Frame", "Delete active frame"},
+               {0, NULL, 0, NULL, NULL}
+       };
+       
+       /* identifiers */
+       ot->name = "Delete...";
+       ot->idname = "GPENCIL_OT_delete";
+       ot->description = "Delete selected Grease Pencil strokes, vertices, or frames";
+       
+       /* callbacks */
+       ot->invoke = WM_menu_invoke;
+       ot->exec = gp_delete_exec;
+       ot->poll = gp_stroke_edit_poll;
+       
+       /* flags */
+       ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
+       
+       /* props */
+       ot->prop = RNA_def_enum(ot->srna, "type", prop_gpencil_delete_types, 0, "Type", "Method used for deleting Grease Pencil data");
 }
 
-/* convert stroke to 3d bezier */
-static void gp_stroke_to_bezier(bContext *C, bGPDlayer *gpl, bGPDstroke *gps, Curve *cu, rctf *subrect, Nurb **curnu,
-                                float minmax_weights[2], float rad_fac, int stitch, tGpTimingData *gtd)
+static int gp_dissolve_exec(bContext *C, wmOperator *UNUSED(op))
 {
-       bGPDspoint *pt;
-       Nurb *nu = curnu ? *curnu : NULL;
-       BezTriple *bezt, *prev_bezt = NULL;
-       int i, tot, old_nbezt = 0;
-       float p3d_cur[3], p3d_prev[3], p3d_next[3];
-       const int do_gtd = (gtd->mode != GP_STROKECONVERT_TIMING_NONE);
-
-       /* create new 'nurb' or extend current one within the curve */
-       if (nu) {
-               old_nbezt = nu->pntsu;
-               /* If we do stitch, first point of current stroke is assumed the same as last point of previous stroke,
-                * so no need to add it.
-                * If no stitch, we want to add two additional points to make a "zero-radius" link between both strokes.
-                */
-               BKE_nurb_bezierPoints_add(nu, gps->totpoints + (stitch ? -1 : 2));
-       }
-       else {
-               nu = (Nurb *)MEM_callocN(sizeof(Nurb), "gpstroke_to_bezier(nurb)");
-
-               nu->pntsu = gps->totpoints;
-               nu->resolu = 12;
-               nu->resolv = 12;
-               nu->type = CU_BEZIER;
-               nu->bezt = (BezTriple *)MEM_callocN(gps->totpoints * sizeof(BezTriple), "bezts");
-
-               stitch = FALSE; /* Security! */
-       }
-
-       if (do_gtd) {
-               _gp_timing_data_set_nbr(gtd, nu->pntsu);
-       }
-
-       tot = gps->totpoints;
-
-       /* get initial coordinates */
-       pt = gps->points;
-       if (tot) {
-               gp_strokepoint_convertcoords(C, gps, pt, stitch ? p3d_prev : p3d_cur, subrect);
-               if (tot > 1) {
-                       gp_strokepoint_convertcoords(C, gps, pt + 1, stitch ? p3d_cur : p3d_next, subrect);
-               }
-               if (stitch && tot > 2) {
-                       gp_strokepoint_convertcoords(C, gps, pt + 2, p3d_next, subrect);
-               }
-       }
-
-       /* If needed, make the link between both strokes with two zero-radius additional points */
-       if (curnu && old_nbezt) {
-               /* Update last point's second handle! */
-               if (stitch) {
-                       float h2[3];
-                       bezt = nu->bezt + old_nbezt - 1;
-                       interp_v3_v3v3(h2, bezt->vec[1], p3d_cur, BEZT_HANDLE_FAC);
-                       copy_v3_v3(bezt->vec[2], h2);
-                       pt++;
-               }
-               /* Create "link points" */
-               /* About "zero-radius" point interpolations:
-                * - If we have at least two points in current curve (most common case), we linearly extrapolate
-                *   the last segment to get the first point (p1) position and timing.
-                * - If we do not have those (quite odd, but may happen), we linearly interpolate the last point
-                *   with the first point of the current stroke.
-                * The same goes for the second point, first segment of the current stroke is "negatively" extrapolated
-                * if it exists, else (if the stroke is a single point), linear interpolation with last curve point...
-                */
-               else {
-                       float h1[3], h2[3], p1[3], p2[3];
-                       float delta_time;
-
-                       prev_bezt = NULL;
-                       if (old_nbezt > 1 && gps->prev && gps->prev->totpoints > 1) {
-                               /* Only use last curve segment if previous stroke was not a single-point one! */
-                               prev_bezt = nu->bezt + old_nbezt - 2;
-                       }
-                       bezt = nu->bezt + old_nbezt - 1;
-                       if (prev_bezt) {
-                               interp_v3_v3v3(p1, prev_bezt->vec[1], bezt->vec[1], 1.0f + GAP_DFAC);
-                       }
-                       else {
-                               interp_v3_v3v3(p1, bezt->vec[1], p3d_cur, GAP_DFAC);
-                       }
-                       if (tot > 1) {
-                               interp_v3_v3v3(p2, p3d_cur, p3d_next, -GAP_DFAC);
-                       }
-                       else {
-                               interp_v3_v3v3(p2, p3d_cur, bezt->vec[1], GAP_DFAC);
-                       }
-
-                       /* Second handle of last point */
-                       interp_v3_v3v3(h2, bezt->vec[1], p1, BEZT_HANDLE_FAC);
-                       copy_v3_v3(bezt->vec[2], h2);
-
-                       /* First point */
-                       interp_v3_v3v3(h1, p1, bezt->vec[1], BEZT_HANDLE_FAC);
-                       interp_v3_v3v3(h2, p1, p2, BEZT_HANDLE_FAC);
-
-                       bezt++;
-                       copy_v3_v3(bezt->vec[0], h1);
-                       copy_v3_v3(bezt->vec[1], p1);
-                       copy_v3_v3(bezt->vec[2], h2);
-                       bezt->h1 = bezt->h2 = HD_FREE;
-                       bezt->f1 = bezt->f2 = bezt->f3 = SELECT;
-                       minmax_weights[0] = bezt->radius = bezt->weight = 0.0f;
-
-                       if (do_gtd) {
-                               if (prev_bezt) {
-                                       delta_time = gtd->tot_time + (gtd->tot_time - gtd->times[gtd->cur_point - 1]) * GAP_DFAC;
-                               }
-                               else {
-                                       delta_time = gtd->tot_time + (((float)(gps->inittime - gtd->inittime)) - gtd->tot_time) * GAP_DFAC;
-                               }
-                               gp_timing_data_add_point(gtd, gtd->inittime, delta_time, len_v3v3((bezt - 1)->vec[1], p1));
-                       }
-
-                       /* Second point */
-                       interp_v3_v3v3(h1, p2, p1, BEZT_HANDLE_FAC);
-                       interp_v3_v3v3(h2, p2, p3d_cur, BEZT_HANDLE_FAC);
-
-                       bezt++;
-                       copy_v3_v3(bezt->vec[0], h1);
-                       copy_v3_v3(bezt->vec[1], p2);
-                       copy_v3_v3(bezt->vec[2], h2);
-                       bezt->h1 = bezt->h2 = HD_FREE;
-                       bezt->f1 = bezt->f2 = bezt->f3 = SELECT;
-                       minmax_weights[0] = bezt->radius = bezt->weight = 0.0f;
-
-                       if (do_gtd) {
-                               /* This negative delta_time marks the gap! */
-                               if (tot > 1) {
-                                       delta_time = ((gps->points + 1)->time - gps->points->time) * -GAP_DFAC;
-                               }
-                               else {
-                                       delta_time = -(((float)(gps->inittime - gtd->inittime)) - gtd->tot_time) * GAP_DFAC;
-                               }
-                               gp_timing_data_add_point(gtd, gps->inittime, delta_time, len_v3v3(p1, p2));
-                       }
-
-                       old_nbezt += 2;
-                       copy_v3_v3(p3d_prev, p2);
-               }
-       }
-       if (old_nbezt && do_gtd) {
-               prev_bezt = nu->bezt + old_nbezt - 1;
-       }
-       /* add points */
-       for (i = stitch ? 1 : 0, bezt = nu->bezt + old_nbezt; i < tot; i++, pt++, bezt++) {
-               float h1[3], h2[3];
-               float width = pt->pressure * gpl->thickness * WIDTH_CORR_FAC;
-
-               if (i || old_nbezt) {
-                       interp_v3_v3v3(h1, p3d_cur, p3d_prev, BEZT_HANDLE_FAC);
-               }
-               else {
-                       interp_v3_v3v3(h1, p3d_cur, p3d_next, -BEZT_HANDLE_FAC);
-               }
-
-               if (i < tot - 1) {
-                       interp_v3_v3v3(h2, p3d_cur, p3d_next, BEZT_HANDLE_FAC);
-               }
-               else {
-                       interp_v3_v3v3(h2, p3d_cur, p3d_prev, -BEZT_HANDLE_FAC);
-               }
-
-               copy_v3_v3(bezt->vec[0], h1);
-               copy_v3_v3(bezt->vec[1], p3d_cur);
-               copy_v3_v3(bezt->vec[2], h2);
-
-               /* set settings */
-               bezt->h1 = bezt->h2 = HD_FREE;
-               bezt->f1 = bezt->f2 = bezt->f3 = SELECT;
-               bezt->radius = width * rad_fac;
-               bezt->weight = width;
-               CLAMP(bezt->weight, 0.0f, 1.0f);
-               if (bezt->weight < minmax_weights[0]) {
-                       minmax_weights[0] = bezt->weight;
-               }
-               else if (bezt->weight > minmax_weights[1]) {
-                       minmax_weights[1] = bezt->weight;
-               }
-
-               /* Update timing data */
-               if (do_gtd) {
-                       gp_timing_data_add_point(gtd, gps->inittime, pt->time, prev_bezt ? len_v3v3(prev_bezt->vec[1], p3d_cur) : 0.0f);
-               }
-
-               /* shift coord vects */
-               copy_v3_v3(p3d_prev, p3d_cur);
-               copy_v3_v3(p3d_cur, p3d_next);
-
-               if (i + 2 < tot) {
-                       gp_strokepoint_convertcoords(C, gps, pt + 2, p3d_next, subrect);
-               }
+       return gp_dissolve_selected_points(C);
+}
 
-               prev_bezt = bezt;
-       }
+void GPENCIL_OT_dissolve(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Dissolve";
+       ot->idname = "GPENCIL_OT_dissolve";
+       ot->description = "Delete selected points without splitting strokes";
 
-       /* must calculate handles or else we crash */
-       BKE_nurb_handles_calc(nu);
+       /* callbacks */
+       ot->exec = gp_dissolve_exec;
+       ot->poll = gp_stroke_edit_poll;
 
-       if (!curnu || !*curnu) {
-               BLI_addtail(&cu->nurb, nu);
-       }
-       if (curnu) {
-               *curnu = nu;
-       }
+       /* flags */
+       ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
 }
 
-#undef GAP_DFAC
-#undef WIDTH_CORR_FAC
-#undef BEZT_HANDLE_FAC
+/* ****************** Snapping - Strokes <-> Cursor ************************ */
 
-static void gp_stroke_finalize_curve_endpoints(Curve *cu)
+/* Poll callback for snap operators */
+/* NOTE: For now, we only allow these in the 3D view, as other editors do not
+ *       define a cursor or gridstep which can be used
+ */
+static int gp_snap_poll(bContext *C)
 {
-       Nurb *nu = cu->nurb.first;
-       int i = 0;
-       if (nu->bezt) {
-               BezTriple *bezt = nu->bezt;
-               if (bezt) {
-                       bezt[i].weight = bezt[i].radius = 0.0f;
-               }
-       }
-       else if (nu->bp) {
-               BPoint *bp = nu->bp;
-               if (bp) {
-                       bp[i].weight = bp[i].radius = 0.0f;
-               }
-       }
-
-       nu = cu->nurb.last;
-       i = nu->pntsu - 1;
-       if (nu->bezt) {
-               BezTriple *bezt = nu->bezt;
-               if (bezt) {
-                       bezt[i].weight = bezt[i].radius = 0.0f;
-               }
-       }
-       else if (nu->bp) {
-               BPoint *bp = nu->bp;
-               if (bp) {
-                       bp[i].weight = bp[i].radius = 0.0f;
-               }
-       }
+       bGPdata *gpd = CTX_data_gpencil_data(C);
+       ScrArea *sa = CTX_wm_area(C);
+       
+       return (gpd != NULL) && ((sa != NULL) && (sa->spacetype == SPACE_VIEW3D));
 }
 
-static void gp_stroke_norm_curve_weights(Curve *cu, float minmax_weights[2])
-{
-       Nurb *nu;
-       const float delta = minmax_weights[0];
-       const float fac = 1.0f / (minmax_weights[1] - delta);
-       int i;
+/* --------------------------------- */
 
-       for (nu = cu->nurb.first; nu; nu = nu->next) {
-               if (nu->bezt) {
-                       BezTriple *bezt = nu->bezt;
-                       for (i = 0; i < nu->pntsu; i++, bezt++) {
-                               bezt->weight = (bezt->weight - delta) * fac;
-                       }
-               }
-               else if (nu->bp) {
-                       BPoint *bp = nu->bp;
-                       for (i = 0; i < nu->pntsu; i++, bp++) {
-                               bp->weight = (bp->weight - delta) * fac;
+static int gp_snap_to_grid(bContext *C, wmOperator *UNUSED(op))
+{
+       RegionView3D *rv3d = CTX_wm_region_data(C);
+       float gridf = rv3d->gridview;
+       
+       CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
+       {
+               bGPDspoint *pt;
+               int i;
+               
+               // TOOD: if entire stroke is selected, offset entire stroke by same amount?
+               
+               for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                       /* only if point is selected.. */
+                       if (pt->flag & GP_SPOINT_SELECT) {
+                               pt->x = gridf * floorf(0.5f + pt->x / gridf);
+                               pt->y = gridf * floorf(0.5f + pt->y / gridf);
+                               pt->z = gridf * floorf(0.5f + pt->z / gridf);
                        }
                }
        }
+       CTX_DATA_END;
+       
+       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+       return OPERATOR_FINISHED;
 }
 
-/* convert a given grease-pencil layer to a 3d-curve representation (using current view if appropriate) */
-static void gp_layer_to_curve(bContext *C, ReportList *reports, bGPdata *gpd, bGPDlayer *gpl, int mode,
-                              int norm_weights, float rad_fac, int link_strokes, tGpTimingData *gtd)
+void GPENCIL_OT_snap_to_grid(wmOperatorType *ot)
 {
-       Scene *scene = CTX_data_scene(C);
-       bGPDframe *gpf = gpencil_layer_getframe(gpl, CFRA, 0);
-       bGPDstroke *gps, *prev_gps = NULL;
-       Object *ob;
-       Curve *cu;
-       Nurb *nu = NULL;
-       Base *base = BASACT, *newbase = NULL;
-       float minmax_weights[2] = {1.0f, 0.0f};
-
-       /* camera framing */
-       rctf subrect, *subrect_ptr = NULL;
-
-       /* error checking */
-       if (ELEM3(NULL, gpd, gpl, gpf))
-               return;
-
-       /* only convert if there are any strokes on this layer's frame to convert */
-       if (gpf->strokes.first == NULL)
-               return;
-
-       /* initialize camera framing */
-       if (gp_camera_view_subrect(C, &subrect)) {
-               subrect_ptr = &subrect;
-       }
-
-       /* init the curve object (remove rotation and get curve data from it)
-        *      - must clear transforms set on object, as those skew our results
-        */
-       ob = BKE_object_add(scene, OB_CURVE);
-       zero_v3(ob->loc);
-       zero_v3(ob->rot);
-       cu = ob->data;
-       cu->flag |= CU_3D;
-
-       /* rename object and curve to layer name */
-       rename_id((ID *)ob, gpl->info);
-       rename_id((ID *)cu, gpl->info);
-
-       gtd->inittime = ((bGPDstroke*)gpf->strokes.first)->inittime;
-
-       /* add points to curve */
-       for (gps = gpf->strokes.first; gps; gps = gps->next) {
-               /* Detect new strokes created because of GP_STROKE_BUFFER_MAX reached,
-                * and stitch them to previous one.
-                */
-               int stitch = FALSE;
-               if (prev_gps) {
-                       bGPDspoint *pt1 = prev_gps->points + prev_gps->totpoints - 1;
-                       bGPDspoint *pt2 = gps->points;
-                       if (pt1->x == pt2->x && pt1->y == pt2->y)
-                               stitch = TRUE;
-               }
-               /* Decide whether we connect this stroke to previous one */
-               if (!(stitch || link_strokes))
-                       nu = NULL;
-               switch (mode) {
-                       case GP_STROKECONVERT_PATH: 
-                               gp_stroke_to_path(C, gpl, gps, cu, subrect_ptr, &nu, minmax_weights, rad_fac, stitch, gtd);
-                               break;
-                       case GP_STROKECONVERT_CURVE:
-                               gp_stroke_to_bezier(C, gpl, gps, cu, subrect_ptr, &nu, minmax_weights, rad_fac, stitch, gtd);
-                               break;
-                       default:
-                               BLI_assert(!"invalid mode");
-                               break;
-               }
-               prev_gps = gps;
-       }
-
-       /* If link_strokes, be sure first and last points have a zero weight/size! */
-       if (link_strokes)
-               gp_stroke_finalize_curve_endpoints(cu);
-
-       /* Update curve's weights, if needed */
-       if (norm_weights && (minmax_weights[0] > 0.0f || minmax_weights[1] < 1.0f))
-               gp_stroke_norm_curve_weights(cu, minmax_weights);
-
-       /* Create the path animation, if needed */
-       gp_stroke_path_animation(C, reports, cu, gtd);
-
-       /* Reset org object as active, else we can't edit operator's settings!!! */
-       /* set layers OK */
-       newbase = BASACT;
-       newbase->lay = base->lay;
-       ob->lay = newbase->lay;
-       /* restore, BKE_object_add sets active */
-       BASACT = base;
-       base->flag |= SELECT;
+       /* identifiers */
+       ot->name = "Snap Selection to Grid";
+       ot->idname = "GPENCIL_OT_snap_to_grid";
+       ot->description = "Snap selected points to the nearest grid points";
+       
+       /* callbacks */
+       ot->exec = gp_snap_to_grid;
+       ot->poll = gp_snap_poll;
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 }
 
-/* --- */
+/* ------------------------------- */
 
-/* Check a GP layer has valid timing data! Else, most timing options are hidden in the operator.
- * op may be NULL.
- */
-static int gp_convert_check_has_valid_timing(bContext *C, bGPDlayer *gpl, wmOperator *op)
+static int gp_snap_to_cursor(bContext *C, wmOperator *op)
 {
        Scene *scene = CTX_data_scene(C);
-       bGPDframe *gpf = gpencil_layer_getframe(gpl, CFRA, 0);
-       bGPDstroke *gps = gpf->strokes.first;
-       bGPDspoint *pt;
-       double base_time, cur_time, prev_time = -1.0;
-       int i, valid = TRUE;
-
-       do {
-               base_time = cur_time = gps->inittime;
-               if (cur_time <= prev_time) {
-                       valid = FALSE;
-                       break;
-               }
-               prev_time = cur_time;
-               for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
-                       cur_time = base_time + (double)pt->time;
-                       /* First point of a stroke should have the same time as stroke's inittime,
-                        * so it's the only case where equality is allowed!
-                        */
-                       if ((i && cur_time <= prev_time) || (cur_time < prev_time)) {
-                               valid = FALSE;
-                               break;
+       View3D *v3d = CTX_wm_view3d(C);
+       
+       const bool use_offset = RNA_boolean_get(op->ptr, "use_offset");
+       const float *cursor_global = ED_view3d_cursor3d_get(scene, v3d);
+       
+       CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
+       {
+               bGPDspoint *pt;
+               int i;
+               
+               /* only continue if this stroke is selected (editable doesn't guarantee this)... */
+               if ((gps->flag & GP_STROKE_SELECT) == 0)
+                       continue;
+               
+               if (use_offset) {
+                       float offset[3];
+                       
+                       /* compute offset from first point of stroke to cursor */
+                       /* TODO: Allow using midpoint instead? */
+                       sub_v3_v3v3(offset, cursor_global, &gps->points->x);
+                       
+                       /* apply offset to all points in the stroke */
+                       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                               add_v3_v3(&pt->x, offset);
                        }
-                       prev_time = cur_time;
                }
-               if (!valid) {
-                       break;
+               else {
+                       /* affect each selected point */
+                       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                               if (pt->flag & GP_SPOINT_SELECT) {
+                                       copy_v3_v3(&pt->x, cursor_global);
+                               }
+                       }
                }
-       } while ((gps = gps->next));
-
-       if (op) {
-               RNA_boolean_set(op->ptr, "use_timing_data", valid);
        }
-       return valid;
+       CTX_DATA_END;
+       
+       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+       return OPERATOR_FINISHED;
 }
 
-/* Check end_frame is always > start frame! */
-static void gp_convert_set_end_frame(struct Main *UNUSED(main), struct Scene *UNUSED(scene), struct PointerRNA *ptr)
+void GPENCIL_OT_snap_to_cursor(wmOperatorType *ot)
 {
-       int start_frame = RNA_int_get(ptr, "start_frame");
-       int end_frame = RNA_int_get(ptr, "end_frame");
-       if (end_frame <= start_frame) {
-               RNA_int_set(ptr, "end_frame", start_frame + 1);
-       }
+       /* identifiers */
+       ot->name = "Snap Selection to Cursor";
+       ot->idname = "GPENCIL_OT_snap_to_cursor";
+       ot->description = "Snap selected points/strokes to the cursor";
+       
+       /* callbacks */
+       ot->exec = gp_snap_to_cursor;
+       ot->poll = gp_snap_poll;
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+       
+       /* props */
+       ot->prop = RNA_def_boolean(ot->srna, "use_offset", true, "With Offset",
+                                  "Offset the entire stroke instead of selected points only");
 }
 
-static int gp_convert_poll(bContext *C)
-{
-       bGPdata *gpd = gpencil_data_get_active(C);
-       ScrArea *sa = CTX_wm_area(C);
-       Scene *scene = CTX_data_scene(C);
+/* ------------------------------- */
 
-       /* only if there's valid data, and the current view is 3D View */
-       return ((sa && sa->spacetype == SPACE_VIEW3D) && gpencil_layer_getactive(gpd) && (scene->obedit == NULL));
-}
-
-static int gp_convert_layer_exec(bContext *C, wmOperator *op)
+static int gp_snap_cursor_to_sel(bContext *C, wmOperator *UNUSED(op))
 {
-       PropertyRNA *prop = RNA_struct_find_property(op->ptr, "use_timing_data");
-       bGPdata *gpd = gpencil_data_get_active(C);
-       bGPDlayer *gpl = gpencil_layer_getactive(gpd);
        Scene *scene = CTX_data_scene(C);
-       int mode = RNA_enum_get(op->ptr, "type");
-       int norm_weights = RNA_boolean_get(op->ptr, "use_normalize_weights");
-       float rad_fac = RNA_float_get(op->ptr, "radius_multiplier");
-       int link_strokes = RNA_boolean_get(op->ptr, "use_link_strokes");
-       int valid_timing;
-       tGpTimingData gtd;
-
-       /* check if there's data to work with */
-       if (gpd == NULL) {
-               BKE_report(op->reports, RPT_ERROR, "No grease pencil data to work on");
-               return OPERATOR_CANCELLED;
-       }
-
-       if (!RNA_property_is_set(op->ptr, prop) && !gp_convert_check_has_valid_timing(C, gpl, op)) {
-               BKE_report(op->reports, RPT_WARNING,
-                          "Current grease pencil strokes have no valid timing data, most timing options will be hidden!");
-       }
-       valid_timing = RNA_property_boolean_get(op->ptr, prop);
-
-       gtd.mode = RNA_enum_get(op->ptr, "timing_mode");
-       /* Check for illegal timing mode! */
-       if (!valid_timing && !ELEM(gtd.mode, GP_STROKECONVERT_TIMING_NONE, GP_STROKECONVERT_TIMING_LINEAR)) {
-               gtd.mode = GP_STROKECONVERT_TIMING_LINEAR;
-               RNA_enum_set(op->ptr, "timing_mode", gtd.mode);
-       }
-       if (!link_strokes) {
-               gtd.mode = GP_STROKECONVERT_TIMING_NONE;
-       }
-
-       gtd.frame_range = RNA_int_get(op->ptr, "frame_range");
-       gtd.start_frame = RNA_int_get(op->ptr, "start_frame");
-       gtd.realtime = valid_timing ? RNA_boolean_get(op->ptr, "use_realtime") : FALSE;
-       gtd.end_frame = RNA_int_get(op->ptr, "end_frame");
-       gtd.gap_duration = RNA_float_get(op->ptr, "gap_duration");
-       gtd.gap_randomness = RNA_float_get(op->ptr, "gap_randomness");
-       gtd.gap_randomness = min_ff(gtd.gap_randomness, gtd.gap_duration);
-       gtd.seed = RNA_int_get(op->ptr, "seed");
-       gtd.num_points = gtd.cur_point = 0;
-       gtd.dists = gtd.times = NULL;
-       gtd.tot_dist = gtd.tot_time = gtd.gap_tot_time = 0.0f;
-       gtd.inittime = 0.0;
-
-       gp_layer_to_curve(C, op->reports, gpd, gpl, mode, norm_weights, rad_fac, link_strokes, &gtd);
-
-       /* notifiers */
-       WM_event_add_notifier(C, NC_OBJECT | NA_ADDED, NULL);
-       WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene);
-
-       /* done */
-       return OPERATOR_FINISHED;
-}
-
-static int gp_convert_draw_check_prop(PointerRNA *ptr, PropertyRNA *prop)
-{
-       const char *prop_id = RNA_property_identifier(prop);
-       int link_strokes = RNA_boolean_get(ptr, "use_link_strokes");
-       int timing_mode = RNA_enum_get(ptr, "timing_mode");
-       int realtime = RNA_boolean_get(ptr, "use_realtime");
-       float gap_duration = RNA_float_get(ptr, "gap_duration");
-       float gap_randomness = RNA_float_get(ptr, "gap_randomness");
-       int valid_timing = RNA_boolean_get(ptr, "use_timing_data");
-
-       /* Always show those props */
-       if (strcmp(prop_id, "type") == 0 ||
-           strcmp(prop_id, "use_normalize_weights") == 0 ||
-           strcmp(prop_id, "radius_multiplier") == 0 ||
-           strcmp(prop_id, "use_link_strokes") == 0)
+       View3D *v3d = CTX_wm_view3d(C);
+       
+       float *cursor = ED_view3d_cursor3d_get(scene, v3d);
+       float centroid[3] = {0.0f};
+       float min[3], max[3];
+       size_t count = 0;
+       
+       INIT_MINMAX(min, max);
+       
+       /* calculate midpoints from selected points */
+       CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
        {
-               return TRUE;
-       }
-
-       /* Never show this prop */
-       if (strcmp(prop_id, "use_timing_data") == 0)
-               return FALSE;
-
-       if (link_strokes) {
-               /* Only show when link_stroke is TRUE */
-               if (strcmp(prop_id, "timing_mode") == 0)
-                       return TRUE;
-
-               if (timing_mode != GP_STROKECONVERT_TIMING_NONE) {
-                       /* Only show when link_stroke is TRUE and stroke timing is enabled */
-                       if (strcmp(prop_id, "frame_range") == 0 ||
-                           strcmp(prop_id, "start_frame") == 0)
-                       {
-                               return TRUE;
-                       }
-
-                       /* Only show if we have valid timing data! */
-                       if (valid_timing && strcmp(prop_id, "use_realtime") == 0)
-                               return TRUE;
-
-                       /* Only show if realtime or valid_timing is FALSE! */
-                       if ((!realtime || !valid_timing) && strcmp(prop_id, "end_frame") == 0)
-                               return TRUE;
-
-                       if (valid_timing && timing_mode == GP_STROKECONVERT_TIMING_CUSTOMGAP) {
-                               /* Only show for custom gaps! */
-                               if (strcmp(prop_id, "gap_duration") == 0)
-                                       return TRUE;
-
-                               /* Only show randomness for non-null custom gaps! */
-                               if (strcmp(prop_id, "gap_randomness") == 0 && gap_duration > 0.0f)
-                                       return TRUE;
-
-                               /* Only show seed for randomize action! */
-                               if (strcmp(prop_id, "seed") == 0 && gap_duration > 0.0f && gap_randomness > 0.0f)
-                                       return TRUE;
+               bGPDspoint *pt;
+               int i;
+               
+               /* only continue if this stroke is selected (editable doesn't guarantee this)... */
+               if ((gps->flag & GP_STROKE_SELECT) == 0)
+                       continue;
+               
+               for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                       if (pt->flag & GP_SPOINT_SELECT) {
+                               add_v3_v3(centroid, &pt->x);
+                               minmax_v3v3_v3(min, max, &pt->x);
+                               count++;
                        }
                }
        }
+       CTX_DATA_END;
+       
+       if (v3d->around == V3D_AROUND_CENTER_MEAN && count) {
+               mul_v3_fl(centroid, 1.0f / (float)count);
+               copy_v3_v3(cursor, centroid);
+       }
+       else {
+               mid_v3_v3v3(cursor, min, max);
+       }
 
-       /* Else, hidden! */
-       return FALSE;
-}
-
-static void gp_convert_ui(bContext *C, wmOperator *op)
-{
-       uiLayout *layout = op->layout;
-       wmWindowManager *wm = CTX_wm_manager(C);
-       PointerRNA ptr;
-
-       RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr);
-
-       /* Main auto-draw call */
-       uiDefAutoButsRNA(layout, &ptr, gp_convert_draw_check_prop, '\0');
+       
+       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+       return OPERATOR_FINISHED;
 }
 
-void GPENCIL_OT_convert(wmOperatorType *ot)
+void GPENCIL_OT_snap_cursor_to_selected(wmOperatorType *ot)
 {
-       PropertyRNA *prop;
-       
        /* identifiers */
-       ot->name = "Convert Grease Pencil";
-       ot->idname = "GPENCIL_OT_convert";
-       ot->description = "Convert the active Grease Pencil layer to a new Curve Object";
+       ot->name = "Snap Cursor to Selected Points";
+       ot->idname = "GPENCIL_OT_snap_cursor_to_selected";
+       ot->description = "Snap cursor to center of selected points";
        
        /* callbacks */
-       ot->invoke = WM_menu_invoke;
-       ot->exec = gp_convert_layer_exec;
-       ot->poll = gp_convert_poll;
-       ot->ui = gp_convert_ui;
+       ot->exec = gp_snap_cursor_to_sel;
+       ot->poll = gp_snap_poll;
        
        /* flags */
        ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-       
-       /* properties */
-       ot->prop = RNA_def_enum(ot->srna, "type", prop_gpencil_convertmodes, 0, "Type", "Which type of curve to convert to");
-       RNA_def_boolean(ot->srna, "use_normalize_weights", TRUE, "Normalize Weight",
-                       "Normalize weight (set from stroke width)");
-       RNA_def_float(ot->srna, "radius_multiplier", 1.0f, 0.0f, 10.0f, "Radius Fac",
-                     "Multiplier for the points' radii (set from stroke width)", 0.0f, 1000.0f);
-       RNA_def_boolean(ot->srna, "use_link_strokes", TRUE, "Link Strokes",
-                       "Whether to link strokes with zero-radius sections of curves");
-       prop = RNA_def_enum(ot->srna, "timing_mode", prop_gpencil_convert_timingmodes, GP_STROKECONVERT_TIMING_FULL,
-                           "Timing Mode", "How to use timing data stored in strokes");
-       RNA_def_enum_funcs(prop, rna_GPConvert_mode_items);
-       RNA_def_int(ot->srna, "frame_range", 100, 1, 10000, "Frame Range",
-                   "The duration of evaluation of the path control curve", 1, 1000);
-       RNA_def_int(ot->srna, "start_frame", 1, 1, 100000, "Start Frame",
-                   "The start frame of the path control curve", 1, 100000);
-       RNA_def_boolean(ot->srna, "use_realtime", FALSE, "Realtime",
-                       "Whether the path control curve reproduces the drawing in realtime, starting from Start Frame");
-       prop = RNA_def_int(ot->srna, "end_frame", 250, 1, 100000, "End Frame",
-                          "The end frame of the path control curve (if Realtime is not set)", 1, 100000);
-       RNA_def_property_update_runtime(prop, gp_convert_set_end_frame);
-       RNA_def_float(ot->srna, "gap_duration", 0.0f, 0.0f, 1000.0f, "Gap Duration",
-                     "Custom Gap mode: (Average) length of gaps, in frames "
-                     "(note: realtime value, will be scaled if Realtime is not set)", 0.0f, 10000.0f);
-       RNA_def_float(ot->srna, "gap_randomness", 0.0f, 0.0f, 100.0f, "Gap Randomness",
-                     "Custom Gap mode: Number of frames that gap lengths can vary", 0.0f, 1000.0f);
-       RNA_def_int(ot->srna, "seed", 0, 0, 100, "Random Seed",
-                   "Custom Gap mode: Random generator seed", 0, 1000);
-       /* Note: Internal use, this one will always be hidden by UI code... */
-       prop = RNA_def_boolean(ot->srna, "use_timing_data", FALSE, "Has Valid Timing",
-                              "Whether the converted grease pencil layer has valid timing data (internal use)");
-       RNA_def_property_flag(prop, PROP_SKIP_SAVE);
 }
 
+
 /* ************************************************ */