Grease pencil: non-blocking sketch sessions

- Implement own undo stack for grease pencil, so now there'll be no keymaps conflicts.
- Supported redo's during sketch session.
- Get rid of flag stored in Globals -- use undo stack to check if grease pencil session is active.
This commit is contained in:
Sergey Sharybin 2011-09-06 07:59:18 +00:00
parent a41f45946f
commit c94fe5e299
8 changed files with 263 additions and 62 deletions

@ -111,7 +111,7 @@ typedef struct Global {
#define G_SCRIPT_OVERRIDE_PREF (1 << 14) /* when this flag is set ignore the userprefs */
/* #define G_NOFROZEN (1 << 17) also removed */
#define G_GREASEPENCIL (1 << 17)
/* #define G_GREASEPENCIL (1 << 17) also removed */
/* #define G_AUTOMATKEYS (1 << 30) also removed */

@ -42,6 +42,7 @@ set(SRC
gpencil_edit.c
gpencil_ops.c
gpencil_paint.c
gpencil_undo.c
gpencil_intern.h
)

@ -644,7 +644,7 @@ static void gp_draw_data (bGPdata *gpd, int offsx, int offsy, int winx, int winy
/* Check if may need to draw the active stroke cache, only if this layer is the active layer
* that is being edited. (Stroke buffer is currently stored in gp-data)
*/
if ((G.f & G_GREASEPENCIL) && (gpl->flag & GP_LAYER_ACTIVE) &&
if (ED_gpencil_session_active() && (gpl->flag & GP_LAYER_ACTIVE) &&
(gpf->flag & GP_FRAME_PAINT))
{
/* Buffer stroke needs to be drawn with a different linestyle to help differentiate them from normal strokes. */

@ -37,6 +37,7 @@
/* ***************************************************** */
/* Operator Defines */
struct bGPdata;
struct wmOperatorType;
/* drawing ---------- */
@ -61,6 +62,11 @@ void GPENCIL_OT_active_frame_delete(struct wmOperatorType *ot);
void GPENCIL_OT_convert(struct wmOperatorType *ot);
/* undo stack ---------- */
void gpencil_undo_init(struct bGPdata *gpd);
void gpencil_undo_push(struct bGPdata *gpd);
void gpencil_undo_finish(void);
/******************************************************* */
/* FILTERED ACTION DATA - TYPES ---> XXX DEPRECEATED OLD ANIM SYSTEM CODE! */

@ -152,7 +152,7 @@ static int gpencil_draw_poll (bContext *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)
if (ED_gpencil_session_active() == 0)
return 1;
else
CTX_wm_operator_poll_msg_set(C, "Grease Pencil operator is already active");
@ -893,8 +893,10 @@ static void gp_session_validatebuffer (tGPsdata *p)
/* clear memory of buffer (or allocate it if starting a new session) */
if (gpd->sbuffer)
memset(gpd->sbuffer, 0, sizeof(tGPspoint)*GP_STROKE_BUFFER_MAX);
else
else {
//printf("\t\tGP - allocate sbuffer\n");
gpd->sbuffer= MEM_callocN(sizeof(tGPspoint)*GP_STROKE_BUFFER_MAX, "gp_session_strokebuffer");
}
/* reset indices */
gpd->sbuffer_size = 0;
@ -1051,8 +1053,11 @@ static tGPsdata *gp_session_initpaint (bContext *C)
p->gpd= *gpd_ptr;
}
/* set edit flags - so that buffer will get drawn */
G.f |= G_GREASEPENCIL;
if(ED_gpencil_session_active()==0) {
/* initialize undo stack,
also, existing undo stack would make buffer drawn */
gpencil_undo_init(p->gpd);
}
/* clear out buffer (stored in gp-data), in case something contaminated it */
gp_session_validatebuffer(p);
@ -1078,6 +1083,7 @@ static void gp_session_cleanup (tGPsdata *p)
/* free stroke buffer */
if (gpd->sbuffer) {
//printf("\t\tGP - free sbuffer\n");
MEM_freeN(gpd->sbuffer);
gpd->sbuffer= NULL;
}
@ -1247,7 +1253,8 @@ static void gp_paint_strokeend (tGPsdata *p)
static void gp_paint_cleanup (tGPsdata *p)
{
/* finish off a stroke */
gp_paint_strokeend(p);
if(p->gpd)
gp_paint_strokeend(p);
/* "unlock" frame */
if (p->gpf)
@ -1260,8 +1267,8 @@ static void gpencil_draw_exit (bContext *C, wmOperator *op)
{
tGPsdata *p= op->customdata;
/* clear edit flags */
G.f &= ~G_GREASEPENCIL;
/* clear undo stack */
gpencil_undo_finish();
/* restore cursor to indicate end of drawing */
WM_cursor_restore(CTX_wm_window(C));
@ -1592,6 +1599,7 @@ static int gpencil_draw_invoke (bContext *C, wmOperator *op, wmEvent *event)
//printf("\tGP - hotkey invoked... waiting for click-drag\n");
}
WM_event_add_notifier(C, NC_SCREEN|ND_GPENCIL, NULL);
/* add a modal handler for this operator, so that we can then draw continuous strokes */
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
@ -1609,16 +1617,60 @@ static int gpencil_area_exists(bContext *C, ScrArea *satest)
return 0;
}
static tGPsdata *gpencil_stroke_begin(bContext *C, wmOperator *op)
{
tGPsdata *p= op->customdata;
/* 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");
p->status= GP_STATUS_ERROR;
}
/* free pointer used by previous stroke */
if(p)
MEM_freeN(p);
//printf("\t\tGP - start stroke \n");
/* 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
gpencil_draw_init(C, op);
p= op->customdata;
if(p->status != GP_STATUS_ERROR)
p->status= GP_STATUS_PAINTING;
return op->customdata;
}
static void gpencil_stroke_end(wmOperator *op)
{
tGPsdata *p= op->customdata;
gp_paint_cleanup(p);
gpencil_undo_push(p->gpd);
gp_session_cleanup(p);
p->status= GP_STATUS_IDLING;
p->gpd= NULL;
p->gpl= NULL;
p->gpf= NULL;
}
/* events handling during interactive drawing part of operator */
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 */
/* currently, grease pencil conflicts with such operators as undo and set object mode
which makes behavior of operator totally unpredictable and crash for some cases.
the only way to solve this proper is to ger rid of pointers to data which can
chage stored in operator custom data (sergey) */
int estate = OPERATOR_RUNNING_MODAL;
int estate = OPERATOR_PASS_THROUGH; /* default exit state - not handled, so let others have a share of the pie */
// if (event->type == NDOF_MOTION)
// return OPERATOR_PASS_THROUGH;
@ -1652,11 +1704,13 @@ static int gpencil_draw_modal (bContext *C, wmOperator *op, wmEvent *event)
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;
gpencil_stroke_end(op);
/* we've just entered idling state, so this event was processed (but no others yet) */
estate = OPERATOR_RUNNING_MODAL;
/* stroke could be smoothed, send notifier to refresh screen */
ED_region_tag_redraw(p->ar);
}
else {
//printf("\t\tGP - end of stroke + op\n");
@ -1664,35 +1718,19 @@ static int gpencil_draw_modal (bContext *C, wmOperator *op, wmEvent *event)
estate = OPERATOR_FINISHED;
}
}
else {
else if (event->val == KM_PRESS) {
/* 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");
p->status= GP_STATUS_ERROR;
p= gpencil_stroke_begin(C, op);
if (p->status == GP_STATUS_ERROR) {
estate = OPERATOR_CANCELLED;
}
else {
//printf("\t\tGP - start stroke \n");
p->status= GP_STATUS_PAINTING;
/* 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) {
estate = OPERATOR_CANCELLED;
}
}
} else {
p->status = GP_STATUS_IDLING;
}
}
/* handle mode-specific events */
if (p->status == GP_STATUS_PAINTING) {
/* handle painting mouse-movements? */
@ -1704,7 +1742,7 @@ static int gpencil_draw_modal (bContext *C, wmOperator *op, wmEvent *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");
printf("\t\t\t\tGP - add error done! \n");
estate = OPERATOR_CANCELLED;
}
else {
@ -1721,28 +1759,6 @@ static int gpencil_draw_modal (bContext *C, wmOperator *op, wmEvent *event)
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 && event->val == KM_RELEASE) {
/* 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);
/* undoing the last line can free p->gpf
* note, could do this in a bit more of an elegant way then a search but it at least prevents a crash */
if(BLI_findindex(&p->gpl->frames, p->gpf) == -1) {
p->gpf= NULL;
}
/* 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;
}
}
}
/* gpencil modal operator stores area, which can be removed while using it (like fullscreen) */
if(0==gpencil_area_exists(C, p->sa))

@ -0,0 +1,168 @@
/*
* $Id$
*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2011 Blender Foundation.
* All rights reserved.
*
*
* Contributor(s): Blender Foundation,
* Sergey Sharybin
*
* ***** END GPL LICENSE BLOCK *****
*/
#include <stdlib.h>
#include <string.h>
#include "MEM_guardedalloc.h"
#include "DNA_gpencil_types.h"
#include "DNA_listBase.h"
#include "DNA_windowmanager_types.h"
#include "BKE_context.h"
#include "BKE_gpencil.h"
#include "BLI_listbase.h"
#include "ED_gpencil.h"
#include "WM_api.h"
#include "WM_types.h"
#include "gpencil_intern.h"
#define MAXUNDONAME 64
typedef struct bGPundonode {
struct bGPundonode *next, *prev;
char name[MAXUNDONAME];
struct bGPdata *gpd;
} bGPundonode;
static ListBase undo_nodes = {NULL, NULL};
static bGPundonode *cur_node = NULL;
int ED_gpencil_session_active(void)
{
return undo_nodes.first != NULL;
}
int ED_undo_gpencil_step(bContext *C, int step, const char *name)
{
bGPdata **gpd_ptr= NULL, *new_gpd= NULL;
gpd_ptr= gpencil_data_get_pointers(C, NULL);
if(step==1) { /* undo */
//printf("\t\tGP - undo step\n");
if(cur_node->prev) {
if(!name || strcmp(cur_node->name, name) == 0) {
cur_node= cur_node->prev;
new_gpd= cur_node->gpd;
}
}
}
else if (step==-1) {
//printf("\t\tGP - redo step\n");
if(cur_node->next) {
if(!name || strcmp(cur_node->name, name) == 0) {
cur_node= cur_node->next;
new_gpd= cur_node->gpd;
}
}
}
if(new_gpd) {
if(gpd_ptr) {
if(*gpd_ptr) {
bGPdata *gpd= *gpd_ptr;
bGPDlayer *gpl, *gpld;
free_gpencil_layers(&gpd->layers);
/* copy layers */
gpd->layers.first= gpd->layers.last= NULL;
for (gpl= new_gpd->layers.first; gpl; gpl= gpl->next) {
/* make a copy of source layer and its data */
gpld= gpencil_layer_duplicate(gpl);
BLI_addtail(&gpd->layers, gpld);
}
}
}
}
WM_event_add_notifier(C, NC_SCREEN|ND_GPENCIL|NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
void gpencil_undo_init(bGPdata *gpd)
{
gpencil_undo_push(gpd);
}
void gpencil_undo_push(bGPdata *gpd)
{
bGPundonode *undo_node;
//printf("\t\tGP - undo push\n");
if(cur_node) {
/* remove all un-done nodes from stack */
undo_node= cur_node->next;
while(undo_node) {
bGPundonode *next_node= undo_node->next;
free_gpencil_data(undo_node->gpd);
MEM_freeN(undo_node->gpd);
BLI_freelinkN(&undo_nodes, undo_node);
undo_node= next_node;
}
}
/* create new undo node */
undo_node= MEM_callocN(sizeof(bGPundonode), "gpencil undo node");
undo_node->gpd= gpencil_data_duplicate(gpd);
cur_node= undo_node;
BLI_addtail(&undo_nodes, undo_node);
}
void gpencil_undo_finish(void)
{
bGPundonode *undo_node= undo_nodes.first;
while(undo_node) {
free_gpencil_data(undo_node->gpd);
MEM_freeN(undo_node->gpd);
undo_node= undo_node->next;
}
BLI_freelistN(&undo_nodes);
cur_node= NULL;
}

@ -106,4 +106,8 @@ void paste_gpdata(void);
void snap_gplayer_frames(struct bGPDlayer *gpl, short mode);
void mirror_gplayer_frames(struct bGPDlayer *gpl, short mode);
/* ------------ Grease-Pencil Undo System ------------------ */
int ED_gpencil_session_active(void);
int ED_undo_gpencil_step(struct bContext *C, int step, const char *name);
#endif /* ED_GPENCIL_H */

@ -54,6 +54,7 @@
#include "ED_armature.h"
#include "ED_particle.h"
#include "ED_curve.h"
#include "ED_gpencil.h"
#include "ED_mball.h"
#include "ED_mesh.h"
#include "ED_object.h"
@ -126,6 +127,11 @@ static int ed_undo_step(bContext *C, int step, const char *undoname)
Object *obact= CTX_data_active_object(C);
ScrArea *sa= CTX_wm_area(C);
/* grease pencil can be can be used in plenty of spaces, so check it first */
if(ED_gpencil_session_active()) {
return ED_undo_gpencil_step(C, step, undoname);
}
if(sa && sa->spacetype==SPACE_IMAGE) {
SpaceImage *sima= (SpaceImage *)sa->spacedata.first;