doxygen: blender/editors tagged.
[blender.git] / source / blender / editors / gpencil / gpencil_paint.c
index deb8e738ef76361963c11185b08b8975b1bd5edc..472750113b91d27db3bafd40535e55b3250d6732 100644 (file)
@@ -1,4 +1,4 @@
-/**
+/*
  * $Id$
  *
  * ***** BEGIN GPL LICENSE BLOCK *****
  * ***** END GPL LICENSE BLOCK *****
  */
 
+/** \file blender/editors/gpencil/gpencil_paint.c
+ *  \ingroup edgpencil
+ */
+
+
 #include <stdio.h>
 #include <stddef.h>
 #include <stdlib.h>
 
 #include "BLI_blenlib.h"
 #include "BLI_math.h"
+#include "BLI_utildefines.h"
 
 #include "BKE_gpencil.h"
 #include "BKE_context.h"
 #include "BKE_global.h"
 #include "BKE_report.h"
-#include "BKE_utildefines.h"
 
-#include "DNA_gpencil_types.h"
-#include "DNA_action_types.h"
 #include "DNA_object_types.h"
 #include "DNA_scene_types.h"
-#include "DNA_screen_types.h"
-#include "DNA_space_types.h"
-#include "DNA_view2d_types.h"
-#include "DNA_view3d_types.h"
+#include "DNA_gpencil_types.h"
 #include "DNA_windowmanager_types.h"
 
 #include "UI_view2d.h"
 
-#include "ED_armature.h"
 #include "ED_gpencil.h"
-#include "ED_sequencer.h"
 #include "ED_screen.h"
 #include "ED_view3d.h"
 
 /* Temporary 'Stroke' Operation data */
 typedef struct tGPsdata {
        Scene *scene;       /* current scene from context */
+       
+       wmWindow *win;          /* window where painting originated */
        ScrArea *sa;            /* area where painting originated */
        ARegion *ar;        /* region where painting originated */
        View2D *v2d;            /* needed for GP_STROKE_2DSPACE */
+       rctf *subrect;          /* for using the camera rect within the 3d view */
+       rctf subrect_data;
+       
        
 #if 0 // XXX review this 2d image stuff...
        ImBuf *ibuf;            /* needed for GP_STROKE_2DIMAGE */
@@ -147,15 +150,31 @@ static void gp_session_validatebuffer(tGPsdata *p);
 /* check if context is suitable for drawing */
 static int gpencil_draw_poll (bContext *C)
 {
-       /* check if current context can support GPencil data */
-       return (gpencil_data_get_pointers(C, NULL) != NULL);
+       if (ED_operator_regionactive(C)) {
+               /* check if current context can support GPencil data */
+               if (gpencil_data_get_pointers(C, NULL) != NULL) {
+                       /* check if Grease Pencil isn't already running */
+                       if ((G.f & G_GREASEPENCIL) == 0)
+                               return 1;
+                       else
+                               CTX_wm_operator_poll_msg_set(C, "Grease Pencil operator is already active");
+               }
+               else {
+                       CTX_wm_operator_poll_msg_set(C, "Failed to find Grease Pencil data to draw into");
+               }
+       }
+       else {
+               CTX_wm_operator_poll_msg_set(C, "Active region not set");
+       }
+       
+       return 0;
 }
 
 /* check if projecting strokes into 3d-geometry in the 3D-View */
 static int gpencil_project_check (tGPsdata *p)
 {
        bGPdata *gpd= p->gpd;
-       return ((gpd->sbuffer_sflag & GP_STROKE_3DSPACE) && (p->gpd->flag & (GP_DATA_DEPTH_VIEW | GP_DATA_DEPTH_STROKE))) ? 1:0;
+       return ((gpd->sbuffer_sflag & GP_STROKE_3DSPACE) && (p->gpd->flag & (GP_DATA_DEPTH_VIEW | GP_DATA_DEPTH_STROKE)));
 }
 
 /* ******************************************* */
@@ -170,7 +189,9 @@ static void gp_get_3d_reference (tGPsdata *p, float *vec)
        float *fp= give_cursor(p->scene, v3d);
        
        /* the reference point used depends on the owner... */
-       if (p->ownerPtr.type == &RNA_Object) {
+#if 0 // XXX: disabled for now, since we can't draw relative to the owner yet
+       if (p->ownerPtr.type == &RNA_Object) 
+       {
                Object *ob= (Object *)p->ownerPtr.data;
                
                /* active Object 
@@ -178,7 +199,9 @@ static void gp_get_3d_reference (tGPsdata *p, float *vec)
                 */
                sub_v3_v3v3(vec, fp, ob->loc);
        }
-       else {
+       else
+#endif 
+       {
                /* use 3D-cursor */
                copy_v3_v3(vec, fp);
        }
@@ -196,13 +219,17 @@ static short gp_stroke_filtermval (tGPsdata *p, int mval[2], int pmval[2])
        if (p->gpd->sbuffer_size == 0)
                return 1;
        
-       /* check if mouse moved at least certain distance on both axes (best case) */
+       /* check if mouse moved at least certain distance on both axes (best case) 
+        *      - aims to eliminate some jitter-noise from input when trying to draw straight lines freehand
+        */
        else if ((dx > MIN_MANHATTEN_PX) && (dy > MIN_MANHATTEN_PX))
                return 1;
        
-       /* check if the distance since the last point is significant enough */
-       // future optimisation: sqrt here may be too slow?
-       else if (sqrt(dx*dx + dy*dy) > MIN_EUCLIDEAN_PX)
+       /* check if the distance since the last point is significant enough 
+        *      - prevents points being added too densely
+        *      - distance here doesn't use sqrt to prevent slowness... we should still be safe from overflows though
+        */
+       else if ((dx*dx + dy*dy) > MIN_EUCLIDEAN_PX*MIN_EUCLIDEAN_PX)
                return 1;
        
        /* mouse 'didn't move' */
@@ -218,7 +245,7 @@ static void gp_stroke_convertcoords (tGPsdata *p, short mval[], float out[], flo
        
        /* in 3d-space - pt->x/y/z are 3 side-by-side floats */
        if (gpd->sbuffer_sflag & GP_STROKE_3DSPACE) {
-               if(gpencil_project_check(p) && (view_autodist_simple(p->ar, mval, out, 0, depth))) {
+               if (gpencil_project_check(p) && (view_autodist_simple(p->ar, mval, out, 0, depth))) {
                        /* projecting onto 3D-Geometry
                         *      - nothing more needs to be done here, since view_autodist_simple() has already done it
                         */
@@ -276,8 +303,14 @@ static void gp_stroke_convertcoords (tGPsdata *p, short mval[], float out[], flo
        
        /* 2d - relative to screen (viewport area) */
        else {
-               out[0] = (float)(mval[0]) / (float)(p->ar->winx) * 100;
-               out[1] = (float)(mval[1]) / (float)(p->ar->winy) * 100;
+               if (p->subrect == NULL) { /* normal 3D view */
+                       out[0] = (float)(mval[0]) / (float)(p->ar->winx) * 100;
+                       out[1] = (float)(mval[1]) / (float)(p->ar->winy) * 100;
+               }
+               else { /* camera view, use subrect */
+                       out[0]= ((mval[0] - p->subrect->xmin) / ((p->subrect->xmax - p->subrect->xmin))) * 100;
+                       out[1]= ((mval[1] - p->subrect->ymin) / ((p->subrect->ymax - p->subrect->ymin))) * 100;
+               }
        }
 }
 
@@ -351,10 +384,18 @@ static short gp_stroke_addpoint (tGPsdata *p, int mval[2], float pressure)
        return GP_STROKEADD_INVALID;
 }
 
+
+/* temp struct for gp_stroke_smooth() */
+typedef struct tGpSmoothCo {
+       short x;
+       short y;
+} tGpSmoothCo;
+
 /* smooth a stroke (in buffer) before storing it */
 static void gp_stroke_smooth (tGPsdata *p)
 {
        bGPdata *gpd= p->gpd;
+       tGpSmoothCo *smoothArray, *spc;
        int i=0, cmx=gpd->sbuffer_size;
        
        /* only smooth if smoothing is enabled, and we're not doing a straight line */
@@ -365,17 +406,31 @@ static void gp_stroke_smooth (tGPsdata *p)
        if ((cmx <= 2) || (gpd->sbuffer == NULL))
                return;
        
-       /* apply weighting-average (note doing this along path sequentially does introduce slight error) */
-       for (i=0; i < gpd->sbuffer_size; i++) {
+       /* create a temporary smoothing coordinates buffer, use to store calculated values to prevent sequential error */
+       smoothArray = MEM_callocN(sizeof(tGpSmoothCo)*cmx, "gp_stroke_smooth smoothArray");
+       
+       /* first pass: calculate smoothing coordinates using weighted-averages */
+       for (i=0, spc=smoothArray; i < gpd->sbuffer_size; i++, spc++) {
+               const tGPspoint *pc= (((tGPspoint *)gpd->sbuffer) + i);
+               const tGPspoint *pb= (i-1 > 0)?(pc-1):(pc);
+               const tGPspoint *pa= (i-2 > 0)?(pc-2):(pb);
+               const tGPspoint *pd= (i+1 < cmx)?(pc+1):(pc);
+               const tGPspoint *pe= (i+2 < cmx)?(pc+2):(pd);
+               
+               spc->x= (short)(0.1*pa->x + 0.2*pb->x + 0.4*pc->x + 0.2*pd->x + 0.1*pe->x);
+               spc->y= (short)(0.1*pa->y + 0.2*pb->y + 0.4*pc->y + 0.2*pd->y + 0.1*pe->y);
+       }
+       
+       /* second pass: apply smoothed coordinates */
+       for (i=0, spc=smoothArray; i < gpd->sbuffer_size; i++, spc++) {
                tGPspoint *pc= (((tGPspoint *)gpd->sbuffer) + i);
-               tGPspoint *pb= (i-1 > 0)?(pc-1):(pc);
-               tGPspoint *pa= (i-2 > 0)?(pc-2):(pb);
-               tGPspoint *pd= (i+1 < cmx)?(pc+1):(pc);
-               tGPspoint *pe= (i+2 < cmx)?(pc+2):(pd);
                
-               pc->x= (short)(0.1*pa->x + 0.2*pb->x + 0.4*pc->x + 0.2*pd->x + 0.1*pe->x);
-               pc->y= (short)(0.1*pa->y + 0.2*pb->y + 0.4*pc->y + 0.2*pd->y + 0.1*pe->y);
+               pc->x = spc->x;
+               pc->y = spc->y;
        }
+       
+       /* free temp array */
+       MEM_freeN(smoothArray);
 }
 
 /* simplify a stroke (in buffer) before storing it 
@@ -390,12 +445,12 @@ static void gp_stroke_simplify (tGPsdata *p)
        short flag= gpd->sbuffer_sflag;
        short i, j;
        
-       /* only simplify if simlification is enabled, and we're not doing a straight line */
+       /* only simplify if simplification is enabled, and we're not doing a straight line */
        if (!(U.gp_settings & GP_PAINT_DOSIMPLIFY) || (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT))
                return;
        
        /* don't simplify if less than 4 points in buffer */
-       if ((num_points <= 2) || (old_points == NULL))
+       if ((num_points <= 4) || (old_points == NULL))
                return;
                
        /* clear buffer (but don't free mem yet) so that we can write to it 
@@ -516,60 +571,69 @@ static void gp_stroke_newfrombuffer (tGPsdata *p)
        }
        else {
                float *depth_arr= NULL;
-
+               
                /* get an array of depths, far depths are blended */
-               if(gpencil_project_check(p)) {
-                       short mval[2];
+               if (gpencil_project_check(p)) {
+                       short mval[2], mval_prev[2]= {0};
                        int interp_depth = 0;
                        int found_depth = 0;
-
+                       
                        depth_arr= MEM_mallocN(sizeof(float) * gpd->sbuffer_size, "depth_points");
 
                        for (i=0, ptc=gpd->sbuffer; i < gpd->sbuffer_size; i++, ptc++, pt++) {
                                mval[0]= ptc->x; mval[1]= ptc->y;
-                               if(view_autodist_depth(p->ar, mval, depth_margin, depth_arr+i) == 0)
+
+                               if ((view_autodist_depth(p->ar, mval, depth_margin, depth_arr+i) == 0) &&
+                                       (i && (view_autodist_depth_segment(p->ar, mval, mval_prev, depth_margin + 1, depth_arr+i) == 0))
+                               ) {
                                        interp_depth= TRUE;
-                               else
+                               }
+                               else {
                                        found_depth= TRUE;
-                       }
+                               }
 
-                       if(found_depth==FALSE) {
+                               VECCOPY2D(mval_prev, mval);
+                       }
+                       
+                       if (found_depth == FALSE) {
                                /* eeh... not much we can do.. :/, ignore depth in this case, use the 3D cursor */
                                for (i=gpd->sbuffer_size-1; i >= 0; i--)
                                        depth_arr[i] = 0.9999f;
                        }
                        else {
-                               if(p->gpd->flag & GP_DATA_DEPTH_STROKE_ENDPOINTS) {
+                               if (p->gpd->flag & GP_DATA_DEPTH_STROKE_ENDPOINTS) {
                                        /* remove all info between the valid endpoints */
                                        int first_valid = 0;
                                        int last_valid = 0;
-
-                                       for (i=0; i < gpd->sbuffer_size; i++)
-                                               if(depth_arr[i] != FLT_MAX)
+                                       
+                                       for (i=0; i < gpd->sbuffer_size; i++) {
+                                               if (depth_arr[i] != FLT_MAX)
                                                        break;
+                                       }
                                        first_valid= i;
-
-                                       for (i=gpd->sbuffer_size-1; i >= 0; i--)
-                                               if(depth_arr[i] != FLT_MAX)
+                                       
+                                       for (i=gpd->sbuffer_size-1; i >= 0; i--) {
+                                               if (depth_arr[i] != FLT_MAX)
                                                        break;
+                                       }
                                        last_valid= i;
-
+                                       
                                        /* invalidate non-endpoints, so only blend between first and last */
                                        for (i=first_valid+1; i < last_valid; i++)
                                                depth_arr[i]= FLT_MAX;
-
+                                       
                                        interp_depth= TRUE;
                                }
-
-                               if(interp_depth) {
+                               
+                               if (interp_depth) {
                                        interp_sparse_array(depth_arr, gpd->sbuffer_size, FLT_MAX);
                                }
                        }
                }
-
-
+               
+               
                pt= gps->points;
-
+               
                /* convert all points (normal behaviour) */
                for (i=0, ptc=gpd->sbuffer; i < gpd->sbuffer_size && ptc; i++, ptc++, pt++) {
                        /* convert screen-coordinates to appropriate coordinates (and store them) */
@@ -578,8 +642,8 @@ static void gp_stroke_newfrombuffer (tGPsdata *p)
                        /* copy pressure */
                        pt->pressure= ptc->pressure;
                }
-
-               if(depth_arr)
+               
+               if (depth_arr)
                        MEM_freeN(depth_arr);
        }
        
@@ -658,7 +722,7 @@ static short gp_stroke_eraser_splitdel (bGPDframe *gpf, bGPDstroke *gps, int i)
 }
 
 /* eraser tool - check if part of stroke occurs within last segment drawn by eraser */
-static short gp_stroke_eraser_strokeinside (int mval[], int mvalo[], short rad, short x0, short y0, short x1, short y1)
+static short gp_stroke_eraser_strokeinside (int mval[], int UNUSED(mvalo[]), short rad, short x0, short y0, short x1, short y1)
 {
        /* simple within-radius check for now */
        if (edge_inside_circle(mval[0], mval[1], rad, x0, y0, x1, y1))
@@ -709,8 +773,14 @@ static void gp_stroke_eraser_dostroke (tGPsdata *p, int mval[], int mvalo[], sho
                }
 #endif
                else {
-                       x0= (int)(gps->points->x / 100 * p->ar->winx);
-                       y0= (int)(gps->points->y / 100 * p->ar->winy);
+                       if (p->subrect == NULL) { /* normal 3D view */
+                               x0= (int)(gps->points->x / 100 * p->ar->winx);
+                               y0= (int)(gps->points->y / 100 * p->ar->winy);
+                       }
+                       else { /* camera view, use subrect */
+                               x0= (int)((gps->points->x / 100) * (p->subrect->xmax - p->subrect->xmin)) + p->subrect->xmin;
+                               y0= (int)((gps->points->y / 100) * (p->subrect->ymax - p->subrect->ymin)) + p->subrect->ymin;
+                       }
                }
                
                /* do boundbox check first */
@@ -766,10 +836,18 @@ static void gp_stroke_eraser_dostroke (tGPsdata *p, int mval[], int mvalo[], sho
                        }
 #endif
                        else {
-                               x0= (int)(pt1->x / 100 * p->ar->winx);
-                               y0= (int)(pt1->y / 100 * p->ar->winy);
-                               x1= (int)(pt2->x / 100 * p->ar->winx);
-                               y1= (int)(pt2->y / 100 * p->ar->winy);
+                               if(p->subrect == NULL) { /* normal 3D view */
+                                       x0= (int)(pt1->x / 100 * p->ar->winx);
+                                       y0= (int)(pt1->y / 100 * p->ar->winy);
+                                       x1= (int)(pt2->x / 100 * p->ar->winx);
+                                       y1= (int)(pt2->y / 100 * p->ar->winy);
+                               }
+                               else { /* camera view, use subrect */ 
+                                       x0= (int)((pt1->x / 100) * (p->subrect->xmax - p->subrect->xmin)) + p->subrect->xmin;
+                                       y0= (int)((pt1->y / 100) * (p->subrect->ymax - p->subrect->ymin)) + p->subrect->ymin;
+                                       x1= (int)((pt2->x / 100) * (p->subrect->xmax - p->subrect->xmin)) + p->subrect->xmin;
+                                       y1= (int)((pt2->y / 100) * (p->subrect->ymax - p->subrect->ymin)) + p->subrect->ymin;
+                               }
                        }
                        
                        /* check that point segment of the boundbox of the eraser stroke */
@@ -847,14 +925,16 @@ static tGPsdata *gp_session_initpaint (bContext *C)
        /* create new context data */
        p= MEM_callocN(sizeof(tGPsdata), "GPencil Drawing Data");
        
-       /* pass on current scene */
+       /* pass on current scene and window */
        p->scene= CTX_data_scene(C);
+       p->win= CTX_wm_window(C);
        
        switch (curarea->spacetype) {
                /* supported views first */
                case SPACE_VIEW3D:
                {
-                       //View3D *v3d= curarea->spacedata.first;
+                       // View3D *v3d= curarea->spacedata.first;
+                       // RegionView3D *rv3d= ar->regiondata;
                        
                        /* set current area 
                         *      - must verify that region data is 3D-view (and not something else)
@@ -868,7 +948,7 @@ static tGPsdata *gp_session_initpaint (bContext *C)
                                        printf("Error: 3D-View active region doesn't have any region data, so cannot be drawable \n");
                                return p;
                        }
-                       
+
 #if 0 // XXX will this sort of antiquated stuff be restored?
                        /* check that gpencil data is allowed to be drawn */
                        if ((v3d->flag2 & V3D_DISPGP)==0) {
@@ -978,9 +1058,6 @@ static tGPsdata *gp_session_initpaint (bContext *C)
        /* set edit flags - so that buffer will get drawn */
        G.f |= G_GREASEPENCIL;
        
-       /* set initial run flag */
-       p->flags |= GP_PAINTFLAG_FIRSTRUN;
-       
        /* clear out buffer (stored in gp-data), in case something contaminated it */
        gp_session_validatebuffer(p);
        
@@ -1043,15 +1120,26 @@ static void gp_paint_initstroke (tGPsdata *p, short paintmode)
        p->paintmode= paintmode;
        if (p->paintmode == GP_PAINTMODE_ERASER)
                p->gpd->sbuffer_sflag |= GP_STROKE_ERASER;
+               
+       /* set 'initial run' flag, which is only used to denote when a new stroke is starting */
+       p->flags |= GP_PAINTFLAG_FIRSTRUN;
        
        /* check if points will need to be made in view-aligned space */
        if (p->gpd->flag & GP_DATA_VIEWALIGN) {
                switch (p->sa->spacetype) {
                        case SPACE_VIEW3D:
                        {
+                               View3D *v3d= p->sa->spacedata.first;
                                RegionView3D *rv3d= p->ar->regiondata;
                                float rvec[3];
                                
+                               /* for camera view set the subrect */
+                               if (rv3d->persp == RV3D_CAMOB) {
+                                       view3d_calc_camera_border(p->scene, p->ar, NULL, v3d, &p->subrect_data, -1); /* negative shift */
+                                       p->subrect= &p->subrect_data;
+                               }
+                               
+                               /* get reference point for 3d space placement */
                                gp_get_3d_reference(p, rvec);
                                initgrabz(rv3d, rvec[0], rvec[1], rvec[2]);
                                
@@ -1101,7 +1189,20 @@ static void gp_paint_initstroke (tGPsdata *p, short paintmode)
 #endif
                        case SPACE_IMAGE:
                        {
-                               p->gpd->sbuffer_sflag |= GP_STROKE_2DSPACE;
+                               SpaceImage *sima= (SpaceImage *)p->sa->spacedata.first;
+                               
+                               /* only set these flags if the image editor doesn't have an image active,
+                                * otherwise user will be confused by strokes not appearing after they're drawn
+                                *
+                                * Admittedly, this is a bit hacky, but it works much nicer from an ergonomic standpoint!
+                                */
+                               if ELEM(NULL, sima, sima->image) {
+                                       /* make strokes be drawn in screen space */
+                                       p->gpd->sbuffer_sflag &= ~GP_STROKE_2DSPACE;
+                                       p->gpd->flag &= ~GP_DATA_VIEWALIGN;
+                               }       
+                               else
+                                       p->gpd->sbuffer_sflag |= GP_STROKE_2DSPACE;
                        }
                                break;
                }
@@ -1111,6 +1212,17 @@ static void gp_paint_initstroke (tGPsdata *p, short paintmode)
 /* finish off a stroke (clears buffer, but doesn't finish the paint operation) */
 static void gp_paint_strokeend (tGPsdata *p)
 {
+       /* for surface sketching, need to set the right OpenGL context stuff so that 
+        * the conversions will project the values correctly...
+        */
+       if (gpencil_project_check(p)) {
+               View3D *v3d= p->sa->spacedata.first;
+               
+               /* need to restore the original projection settings before packing up */
+               view3d_region_operator_needs_opengl(p->win, p->ar);
+               view_autodist_init(p->scene, p->ar, v3d, (p->gpd->flag & GP_DATA_DEPTH_STROKE) ? 1:0);
+       }
+       
        /* check if doing eraser or not */
        if ((p->gpd->sbuffer_sflag & GP_STROKE_ERASER) == 0) {
                /* smooth stroke before transferring? */
@@ -1186,14 +1298,6 @@ static void gpencil_draw_exit (bContext *C, wmOperator *op)
        }
        
        /* cleanup */
-       if(gpencil_project_check(p)) {
-               View3D *v3d= p->sa->spacedata.first;
-               
-               /* need to restore the original projection settings before packing up */
-               view3d_operator_needs_opengl(C);
-               view_autodist_init(p->scene, p->ar, v3d, (p->gpd->flag & GP_DATA_DEPTH_STROKE) ? 1:0);
-       }
-
        gp_paint_cleanup(p);
        gp_session_cleanup(p);
        
@@ -1211,8 +1315,48 @@ static int gpencil_draw_cancel (bContext *C, wmOperator *op)
 
 /* ------------------------------- */
 
+/* update UI indicators of status, including cursor and header prints */
+static void gpencil_draw_status_indicators (tGPsdata *p)
+{
+       /* header prints */
+       switch (p->status) {
+               case GP_STATUS_PAINTING:
+                       /* only print this for paint-sessions, otherwise it gets annoying */
+                       if (GPENCIL_SKETCH_SESSIONS_ON(p->scene))
+                               ED_area_headerprint(p->sa, "Grease Pencil: Drawing/erasing stroke... Release to end stroke");
+                       break;
+               
+               case GP_STATUS_IDLING:
+                       /* print status info */
+                       switch (p->paintmode) {
+                               case GP_PAINTMODE_ERASER:
+                                       ED_area_headerprint(p->sa, "Grease Pencil Erase Session: Hold and drag LMB or RMB to erase | ESC/Enter to end");
+                                       break;
+                               case GP_PAINTMODE_DRAW_STRAIGHT:
+                                       ED_area_headerprint(p->sa, "Grease Pencil Line Session: Hold and drag LMB to draw | ESC/Enter to end");
+                                       break;
+                               case GP_PAINTMODE_DRAW:
+                                       ED_area_headerprint(p->sa, "Grease Pencil Freehand Session: Hold and drag LMB to draw | ESC/Enter to end");
+                                       break;
+                                       
+                               default: /* unhandled future cases */
+                                       ED_area_headerprint(p->sa, "Grease Pencil Session: ESC/Enter to end");
+                                       break;
+                       }
+                       break;
+                       
+               case GP_STATUS_ERROR:
+               case GP_STATUS_DONE:
+                       /* clear status string */
+                       ED_area_headerprint(p->sa, NULL);
+                       break;
+       }
+}
+
+/* ------------------------------- */
+
 /* create a new stroke point at the point indicated by the painting context */
-static void gpencil_draw_apply (bContext *C, wmOperator *op, tGPsdata *p)
+static void gpencil_draw_apply (wmOperator *op, tGPsdata *p)
 {
        /* handle drawing/erasing -> test for erasing first */
        if (p->paintmode == GP_PAINTMODE_ERASER) {
@@ -1256,7 +1400,7 @@ static void gpencil_draw_apply (bContext *C, wmOperator *op, tGPsdata *p)
 }
 
 /* handle draw event */
-static void gpencil_draw_apply_event (bContext *C, wmOperator *op, wmEvent *event)
+static void gpencil_draw_apply_event (wmOperator *op, wmEvent *event)
 {
        tGPsdata *p= op->customdata;
        ARegion *ar= p->ar;
@@ -1275,12 +1419,22 @@ static void gpencil_draw_apply_event (bContext *C, wmOperator *op, wmEvent *even
                
                tablet= (wmtab->Active != EVT_TABLET_NONE);
                p->pressure= wmtab->Pressure;
+               
                //if (wmtab->Active == EVT_TABLET_ERASER)
                        // TODO... this should get caught by the keymaps which call drawing in the first place
        }
        else
                p->pressure= 1.0f;
        
+       /* fill in stroke data (not actually used directly by gpencil_draw_apply) */
+       RNA_collection_add(op->ptr, "stroke", &itemptr);
+       
+       mousef[0]= p->mval[0];
+       mousef[1]= p->mval[1];
+       RNA_float_set_array(&itemptr, "mouse", mousef);
+       RNA_float_set(&itemptr, "pressure", p->pressure);
+       RNA_boolean_set(&itemptr, "is_start", (p->flags & GP_PAINTFLAG_FIRSTRUN));
+       
        /* special exception for start of strokes (i.e. maybe for just a dot) */
        if (p->flags & GP_PAINTFLAG_FIRSTRUN) {
                p->flags &= ~GP_PAINTFLAG_FIRSTRUN;
@@ -1296,16 +1450,8 @@ static void gpencil_draw_apply_event (bContext *C, wmOperator *op, wmEvent *even
                        return;
        }
        
-       /* fill in stroke data (not actually used directly by gpencil_draw_apply) */
-       RNA_collection_add(op->ptr, "stroke", &itemptr);
-
-       mousef[0]= p->mval[0];
-       mousef[1]= p->mval[1];
-       RNA_float_set_array(&itemptr, "mouse", mousef);
-       RNA_float_set(&itemptr, "pressure", p->pressure);
-       
        /* apply the current latest drawing point */
-       gpencil_draw_apply(C, op, p);
+       gpencil_draw_apply(op, p);
        
        /* force refresh */
        ED_region_tag_redraw(p->ar); /* just active area for now, since doing whole screen is too slow */
@@ -1346,6 +1492,17 @@ static int gpencil_draw_exec (bContext *C, wmOperator *op)
                p->mval[1] = (short)mousef[1];
                p->pressure= RNA_float_get(&itemptr, "pressure");
                
+               if (RNA_boolean_get(&itemptr, "is_start")) {
+                       /* if first-run flag isn't set already (i.e. not true first stroke),
+                        * then we must terminate the previous one first before continuing
+                        */
+                       if ((p->flags & GP_PAINTFLAG_FIRSTRUN) == 0) {
+                               // TODO: both of these ops can set error-status, but we probably don't need to worry
+                               gp_paint_strokeend(p);
+                               gp_paint_initstroke(p, p->paintmode);
+                       }
+               }
+               
                /* if first run, set previous data too */
                if (p->flags & GP_PAINTFLAG_FIRSTRUN) {
                        p->flags &= ~GP_PAINTFLAG_FIRSTRUN;
@@ -1356,7 +1513,7 @@ static int gpencil_draw_exec (bContext *C, wmOperator *op)
                }
                
                /* apply this data as necessary now (as per usual) */
-               gpencil_draw_apply(C, op, p);
+               gpencil_draw_apply(op, p);
        }
        RNA_END;
        
@@ -1397,6 +1554,8 @@ static int gpencil_draw_invoke (bContext *C, wmOperator *op, wmEvent *event)
        // TODO: set any additional settings that we can take from the events?
        // TODO? if tablet is erasing, force eraser to be on?
        
+       // TODO: move cursor setting stuff to stroke-start so that paintmode can be changed midway...
+       
        /* if eraser is on, draw radial aid */
        if (p->paintmode == GP_PAINTMODE_ERASER) {
                // TODO: this involves mucking around with radial control, so we leave this for now..
@@ -1418,7 +1577,7 @@ static int gpencil_draw_invoke (bContext *C, wmOperator *op, wmEvent *event)
                p->status= GP_STATUS_PAINTING;
                
                /* handle the initial drawing - i.e. for just doing a simple dot */
-               gpencil_draw_apply_event(C, op, event);
+               gpencil_draw_apply_event(op, event);
        }
        else {
                /* toolbar invoked - don't start drawing yet... */
@@ -1434,66 +1593,136 @@ static int gpencil_draw_invoke (bContext *C, wmOperator *op, wmEvent *event)
 static int gpencil_draw_modal (bContext *C, wmOperator *op, wmEvent *event)
 {
        tGPsdata *p= op->customdata;
+       int estate = OPERATOR_PASS_THROUGH; /* default exit state - not handled, so let others have a share of the pie */
        
        //printf("\tGP - handle modal event...\n");
        
-       switch (event->type) {
-               /* end of stroke -> ONLY when a mouse-button release occurs 
-                * otherwise, carry on to mouse-move...
-                */
-               case LEFTMOUSE:
-               case RIGHTMOUSE: 
-                       /* if painting, end stroke */
-                       if (p->status == GP_STATUS_PAINTING) {
-                               /* basically, this should be mouse-button up */
-                               //printf("\t\tGP - end of stroke \n");
-                               gpencil_draw_exit(C, op);
-                               
-                               /* one last flush before we're done */
-                               WM_event_add_notifier(C, NC_SCREEN|ND_GPENCIL|NA_EDITED, NULL); // XXX need a nicer one that will work  
+       /* exit painting mode (and/or end current stroke) */
+       if (ELEM4(event->type, RETKEY, PADENTER, ESCKEY, SPACEKEY)) {
+               /* exit() ends the current stroke before cleaning up */
+               //printf("\t\tGP - end of paint op + end of stroke\n");
+               gpencil_draw_exit(C, op);
+               p->status= GP_STATUS_DONE;
+               estate = OPERATOR_FINISHED;
+       }
+       
+       /* toggle painting mode upon mouse-button movement */
+       if (ELEM(event->type, LEFTMOUSE, RIGHTMOUSE)) {
+               /* if painting, end stroke */
+               if (p->status == GP_STATUS_PAINTING) {
+                       /* basically, this should be mouse-button up = end stroke 
+                        * BUT what happens next depends on whether we 'painting sessions' is enabled
+                        */
+                       if (GPENCIL_SKETCH_SESSIONS_ON(p->scene)) {
+                               /* end stroke only, and then wait to resume painting soon */
+                               //printf("\t\tGP - end stroke only\n");
+                               gp_paint_cleanup(p);
+                               p->status= GP_STATUS_IDLING;
                                
-                               return OPERATOR_FINISHED;
+                               /* we've just entered idling state, so this event was processed (but no others yet) */
+                               estate = OPERATOR_RUNNING_MODAL;
+                       }
+                       else {
+                               //printf("\t\tGP - end of stroke + op\n");
+                               gpencil_draw_exit(C, op);
+                               p->status= GP_STATUS_DONE;
+                               estate = OPERATOR_FINISHED;
+                       }
+               }
+               else {
+                       /* not painting, so start stroke (this should be mouse-button down) */
+                       
+                       /* we must check that we're still within the area that we're set up to work from
+                        * otherwise we could crash (see bug #20586)
+                        */
+                       if (CTX_wm_area(C) != p->sa) {
+                               //printf("\t\t\tGP - wrong area execution abort! \n");
+                               gpencil_draw_exit(C, op);
+                               p->status= GP_STATUS_ERROR;
+                               estate = OPERATOR_CANCELLED;
                        }
                        else {
-                               /* not painting, so start stroke (this should be mouse-button down) */
-                               
-                               /* we must check that we're still within the area that we're set up to work from
-                                * otherwise we could crash (see bug #20586)
-                                */
-                               if (CTX_wm_area(C) != p->sa) {
-                                       //printf("\t\t\tGP - wrong area execution abort! \n");
-                                       gpencil_draw_exit(C, op);
-                                       return OPERATOR_CANCELLED;
-                               }
-                                
                                //printf("\t\tGP - start stroke \n");
                                p->status= GP_STATUS_PAINTING;
-                               /* no break now, since we should immediately start painting */
-                       }
-               
-               /* moving mouse - assumed that mouse button is down if in painting status */
-               case MOUSEMOVE:
-                       /* check if we're currently painting */
-                       if (p->status == GP_STATUS_PAINTING) {
-                               /* handle drawing event */
-                               //printf("\t\tGP - add point\n");
-                               gpencil_draw_apply_event(C, op, event);
                                
-                               /* finish painting operation if anything went wrong just now */
+                               /* we may need to set up paint env again if we're resuming */
+                               // XXX: watch it with the paintmode! in future, it'd be nice to allow changing paint-mode when in sketching-sessions
+                               // XXX: with tablet events, we may event want to check for eraser here, for nicer tablet support
+                               gp_paint_initstroke(p, p->paintmode);
+                               
                                if (p->status == GP_STATUS_ERROR) {
-                                       //printf("\t\t\tGP - error done! \n");
                                        gpencil_draw_exit(C, op);
-                                       return OPERATOR_CANCELLED;
+                                       estate = OPERATOR_CANCELLED;
                                }
                        }
+               }
+       }
+       
+       /* handle mode-specific events */
+       if (p->status == GP_STATUS_PAINTING) {
+               /* handle painting mouse-movements? */
+               if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE) || (p->flags & GP_PAINTFLAG_FIRSTRUN)) 
+               {
+                       /* handle drawing event */
+                       //printf("\t\tGP - add point\n");
+                       gpencil_draw_apply_event(op, event);
+                       
+                       /* finish painting operation if anything went wrong just now */
+                       if (p->status == GP_STATUS_ERROR) {
+                               //printf("\t\t\t\tGP - add error done! \n");
+                               gpencil_draw_exit(C, op);
+                               estate = OPERATOR_CANCELLED;
+                       }
+                       else {
+                               /* event handled, so just tag as running modal */
+                               //printf("\t\t\t\tGP - add point handled!\n");
+                               estate = OPERATOR_RUNNING_MODAL;
+                       }
+               }
+               /* there shouldn't be any other events, but just in case there are, let's swallow them 
+                * (i.e. to prevent problems with with undo)
+                */
+               else {
+                       /* swallow event to save ourselves trouble */
+                       estate = OPERATOR_RUNNING_MODAL;
+               }
+       }
+       else if (p->status == GP_STATUS_IDLING) {
+               /* standard undo/redo shouldn't be allowed to execute or else it causes crashes, so catch it here */
+               // FIXME: this is a hardcoded hotkey that can't be changed
+               // TODO: catch redo as well, but how?
+               if (event->type == ZKEY) {
+                       /* oskey = cmd key on macs as they seem to use cmd-z for undo as well? */
+                       if ((event->ctrl) || (event->oskey)) {
+                               /* just delete last stroke, which will look like undo to the end user */
+                               //printf("caught attempted undo event... deleting last stroke \n");
+                               gpencil_frame_delete_laststroke(p->gpl, p->gpf);
+                               
+                               /* event handled, so force refresh */
+                               ED_region_tag_redraw(p->ar); /* just active area for now, since doing whole screen is too slow */
+                               estate = OPERATOR_RUNNING_MODAL; 
+                       }
+               }
+       }
+       
+       /* update status indicators - cursor, header, etc. */
+       gpencil_draw_status_indicators(p);
+       
+       /* process last operations before exiting */
+       switch (estate) {
+               case OPERATOR_FINISHED:
+                       /* one last flush before we're done */
+                       WM_event_add_notifier(C, NC_SCREEN|ND_GPENCIL|NA_EDITED, NULL); // XXX need a nicer one that will work
                        break;
-               
-               default:
-                       //printf("\t\tGP unknown event - %d \n", event->type);
+                       
+               case OPERATOR_RUNNING_MODAL|OPERATOR_PASS_THROUGH:
+                       /* event doesn't need to be handled */
+                       //printf("unhandled event -> %d (mmb? = %d | mmv? = %d)\n", event->type, event->type == MIDDLEMOUSE, event->type==MOUSEMOVE);
                        break;
        }
        
-       return OPERATOR_RUNNING_MODAL;
+       /* return status code */
+       return estate;
 }
 
 /* ------------------------------- */
@@ -1524,5 +1753,6 @@ void GPENCIL_OT_draw (wmOperatorType *ot)
        
        /* settings for drawing */
        RNA_def_enum(ot->srna, "mode", prop_gpencil_drawmodes, 0, "Mode", "Way to intepret mouse movements.");
+       
        RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", "");
 }