dff0839b5bda842dc21b2224f5d1d8293dee1ecd
[blender-staging.git] / source / blender / editors / gpencil / editaction_gpencil.c
1 /**
2  * $Id$
3  *
4  * ***** BEGIN GPL LICENSE BLOCK *****
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  *
20  * The Original Code is Copyright (C) 2008, Blender Foundation
21  * This is a new part of Blender
22  *
23  * Contributor(s): Joshua Leung
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27  
28 #include <stdio.h>
29 #include <string.h>
30 #include <stdlib.h>
31 #include <stddef.h>
32 #include <math.h>
33
34 #include "MEM_guardedalloc.h"
35
36 #include "BLI_math.h"
37 #include "BLI_blenlib.h"
38
39
40 #include "BKE_global.h"
41 #include "BKE_utildefines.h"
42 #include "BKE_blender.h"
43 #include "BKE_fcurve.h"
44 #include "BKE_gpencil.h"
45
46 #include "BIF_gl.h"
47 #include "BIF_glutil.h"
48
49
50
51 #include "gpencil_intern.h"
52
53 #if 0 // XXX disabled until grease pencil code stabilises again
54
55 /* XXX */
56 static void actdata_filter() {} // is now ANIM_animdata_filter()
57 static void BIF_undo_push() {}
58 static void error() {}
59 static void *get_action_context() {return NULL;}  // is now ANIM_animdata_get_context()
60 /* XXX */
61
62
63 /* ***************************************** */
64 /* NOTE ABOUT THIS FILE:
65  *      This file contains code for editing Grease Pencil data in the Action Editor
66  *      as a 'keyframes', so that a user can adjust the timing of Grease Pencil drawings.
67  *      Therefore, this file mostly contains functions for selecting Grease-Pencil frames.
68  */
69 /* ***************************************** */
70 /* Generics - Loopers */
71
72 /* Loops over the gp-frames for a gp-layer, and applies the given callback */
73 short gplayer_frames_looper (bGPDlayer *gpl, Scene *scene, short (*gpf_cb)(bGPDframe *, Scene *))
74 {
75         bGPDframe *gpf;
76         
77         /* error checker */
78         if (gpl == NULL)
79                 return 0;
80         
81         /* do loop */
82         for (gpf= gpl->frames.first; gpf; gpf= gpf->next) {
83                 /* execute callback */
84                 if (gpf_cb(gpf, scene))
85                         return 1;
86         }
87                 
88         /* nothing to return */
89         return 0;
90 }
91
92 /* ****************************************** */
93 /* Data Conversion Tools */
94
95 /* make a listing all the gp-frames in a layer as cfraelems */
96 void gplayer_make_cfra_list (bGPDlayer *gpl, ListBase *elems, short onlysel)
97 {
98         bGPDframe *gpf;
99         CfraElem *ce;
100         
101         /* error checking */
102         if (ELEM(NULL, gpl, elems))
103                 return;
104         
105         /* loop through gp-frames, adding */
106         for (gpf= gpl->frames.first; gpf; gpf= gpf->next) {
107                 if ((onlysel == 0) || (gpf->flag & GP_FRAME_SELECT)) {
108                         ce= MEM_callocN(sizeof(CfraElem), "CfraElem");
109                         
110                         ce->cfra= (float)gpf->framenum;
111                         ce->sel= (gpf->flag & GP_FRAME_SELECT) ? 1 : 0;
112                         
113                         BLI_addtail(elems, ce);
114                 }
115         }
116 }
117
118 /* ***************************************** */
119 /* Selection Tools */
120
121 /* check if one of the frames in this layer is selected */
122 short is_gplayer_frame_selected (bGPDlayer *gpl)
123 {
124         bGPDframe *gpf;
125         
126         /* error checking */
127         if (gpl == NULL) 
128                 return 0;
129         
130         /* stop at the first one found */
131         for (gpf= gpl->frames.first; gpf; gpf= gpf->next) {
132                 if (gpf->flag & GP_FRAME_SELECT)
133                         return 1;
134         }
135         
136         /* not found */
137         return 0;
138 }
139
140 /* helper function - select gp-frame based on SELECT_* mode */
141 static void gpframe_select (bGPDframe *gpf, short select_mode)
142 {
143         switch (select_mode) {
144                 case SELECT_ADD:
145                         gpf->flag |= GP_FRAME_SELECT;
146                         break;
147                 case SELECT_SUBTRACT:
148                         gpf->flag &= ~GP_FRAME_SELECT;
149                         break;
150                 case SELECT_INVERT:
151                         gpf->flag ^= GP_FRAME_SELECT;
152                         break;
153         }
154 }
155
156 /* set all/none/invert select (like above, but with SELECT_* modes) */
157 void select_gpencil_frames (bGPDlayer *gpl, short select_mode)
158 {
159         bGPDframe *gpf;
160         
161         /* error checking */
162         if (gpl == NULL) 
163                 return;
164                 
165         /* handle according to mode */
166         for (gpf= gpl->frames.first; gpf; gpf= gpf->next) {
167                 gpframe_select(gpf, select_mode);
168         }
169 }
170
171 /* set all/none/invert select */
172 void set_gplayer_frame_selection (bGPDlayer *gpl, short mode)
173 {
174         /* error checking */
175         if (gpl == NULL) 
176                 return;
177                 
178         /* convert mode to select_mode */
179         switch (mode) {
180                 case 2:
181                         mode= SELECT_INVERT;
182                         break;
183                 case 1:
184                         mode= SELECT_ADD;
185                         break;
186                 case 0:
187                         mode= SELECT_SUBTRACT;
188                         break;
189                 default:
190                         return;
191         }
192         
193         /* now call the standard function */
194         select_gpencil_frames (gpl, mode);
195 }
196
197 /* select the frame in this layer that occurs on this frame (there should only be one at most) */
198 void select_gpencil_frame (bGPDlayer *gpl, int selx, short select_mode)
199 {
200         bGPDframe *gpf;
201    
202         /* search through frames for a match */
203         for (gpf= gpl->frames.first; gpf; gpf= gpf->next) {
204                 /* there should only be one frame with this frame-number */
205                 if (gpf->framenum == selx) {
206                         gpframe_select(gpf, select_mode);
207                         break;
208                 }
209         }
210 }
211
212 /* select the frames in this layer that occur within the bounds specified */
213 void borderselect_gplayer_frames (bGPDlayer *gpl, float min, float max, short select_mode)
214 {
215         bGPDframe *gpf;
216         
217         /* only select those frames which are in bounds */
218         for (gpf= gpl->frames.first; gpf; gpf= gpf->next) {
219                 if (IN_RANGE(gpf->framenum, min, max))
220                         gpframe_select(gpf, select_mode);
221         }
222 }
223
224
225 /* De-selects or inverts the selection of Layers for a grease-pencil block
226  *      mode: 0 = default behaviour (select all), 1 = test if (de)select all, 2 = invert all 
227  */
228 void deselect_gpencil_layers (void *data, short mode)
229 {
230         ListBase act_data = {NULL, NULL};
231         bActListElem *ale;
232         int filter, sel=1;
233         
234         /* filter data */
235         filter= ACTFILTER_VISIBLE;
236         actdata_filter(&act_data, filter, data, ACTCONT_GPENCIL);
237         
238         /* See if we should be selecting or deselecting */
239         if (mode == 1) {
240                 for (ale= act_data.first; ale; ale= ale->next) {
241                         if (sel == 0) 
242                                 break;
243                         
244                         if (ale->flag & GP_LAYER_SELECT)
245                                 sel= 0;
246                 }
247         }
248         else
249                 sel= 0;
250                 
251         /* Now set the flags */
252         for (ale= act_data.first; ale; ale= ale->next) {
253                 bGPDlayer *gpl= (bGPDlayer *)ale->data;
254                 
255                 if (mode == 2)
256                         gpl->flag ^= GP_LAYER_SELECT;
257                 else if (sel)
258                         gpl->flag |= GP_LAYER_SELECT;
259                 else
260                         gpl->flag &= ~GP_LAYER_SELECT;
261                         
262                 gpl->flag &= ~GP_LAYER_ACTIVE;
263         }
264         
265         /* Cleanup */
266         BLI_freelistN(&act_data);
267 }
268
269 /* ***************************************** */
270 /* Frame Editing Tools */
271
272 /* Delete selected grease-pencil layers */
273 void delete_gpencil_layers (void)
274 {
275         ListBase act_data = {NULL, NULL};
276         bActListElem *ale, *next;
277         void *data;
278         short datatype;
279         int filter;
280         
281         /* determine what type of data we are operating on */
282         data = get_action_context(&datatype);
283         if (data == NULL) return;
284         if (datatype != ACTCONT_GPENCIL) return;
285         
286         /* filter data */
287         filter= (ACTFILTER_VISIBLE | ACTFILTER_FOREDIT | ACTFILTER_CHANNELS | ACTFILTER_SEL);
288         actdata_filter(&act_data, filter, data, datatype);
289         
290         /* clean up grease-pencil layers */
291         for (ale= act_data.first; ale; ale= next) {
292                 bGPdata *gpd= (bGPdata *)ale->owner;
293                 bGPDlayer *gpl= (bGPDlayer *)ale->data;
294                 next= ale->next;
295                 
296                 /* free layer and its data */
297                 if (SEL_GPL(gpl)) {
298                         free_gpencil_frames(gpl);
299                         BLI_freelinkN(&gpd->layers, gpl);
300                 }
301                 
302                 /* free temp memory */
303                 BLI_freelinkN(&act_data, ale);
304         }
305         
306         BIF_undo_push("Delete GPencil Layers");
307 }
308
309 /* Delete selected frames */
310 void delete_gplayer_frames (bGPDlayer *gpl)
311 {
312         bGPDframe *gpf, *gpfn;
313         
314         /* error checking */
315         if (gpl == NULL)
316                 return;
317                 
318         /* check for frames to delete */
319         for (gpf= gpl->frames.first; gpf; gpf= gpfn) {
320                 gpfn= gpf->next;
321                 
322                 if (gpf->flag & GP_FRAME_SELECT)
323                         gpencil_layer_delframe(gpl, gpf);
324         }
325 }
326
327 /* Duplicate selected frames from given gp-layer */
328 void duplicate_gplayer_frames (bGPDlayer *gpl)
329 {
330         bGPDframe *gpf, *gpfn;
331         
332         /* error checking */
333         if (gpl == NULL)
334                 return;
335         
336         /* duplicate selected frames  */
337         for (gpf= gpl->frames.first; gpf; gpf= gpfn) {
338                 gpfn= gpf->next;
339                 
340                 /* duplicate this frame */
341                 if (gpf->flag & GP_FRAME_SELECT) {
342                         bGPDframe *gpfd; 
343                         
344                         /* duplicate frame, and deselect self */
345                         gpfd= gpencil_frame_duplicate(gpf);
346                         gpf->flag &= ~GP_FRAME_SELECT;
347                         
348                         BLI_insertlinkafter(&gpl->frames, gpf, gpfd);
349                 }
350         }
351 }
352
353 /* -------------------------------------- */
354 /* Copy and Paste Tools */
355 /* - The copy/paste buffer currently stores a set of GP_Layers, with temporary
356  *      GP_Frames with the necessary strokes
357  * - Unless there is only one element in the buffer, names are also tested to check for compatability.
358  * - All pasted frames are offset by the same amount. This is calculated as the difference in the times of
359  *      the current frame and the 'first keyframe' (i.e. the earliest one in all channels).
360  * - The earliest frame is calculated per copy operation.
361  */
362  
363 /* globals for copy/paste data (like for other copy/paste buffers) */
364 ListBase gpcopybuf = {NULL, NULL};
365 static int gpcopy_firstframe= 999999999;
366
367 /* This function frees any MEM_calloc'ed copy/paste buffer data */
368 void free_gpcopybuf ()
369 {
370         free_gpencil_layers(&gpcopybuf); 
371         
372         gpcopybuf.first= gpcopybuf.last= NULL;
373         gpcopy_firstframe= 999999999;
374 }
375
376 /* This function adds data to the copy/paste buffer, freeing existing data first
377  * Only the selected GP-layers get their selected keyframes copied.
378  */
379 void copy_gpdata ()
380 {
381         ListBase act_data = {NULL, NULL};
382         bActListElem *ale;
383         int filter;
384         void *data;
385         short datatype;
386         
387         /* clear buffer first */
388         free_gpcopybuf();
389         
390         /* get data */
391         data= get_action_context(&datatype);
392         if (data == NULL) return;
393         if (datatype != ACTCONT_GPENCIL) return;
394         
395         /* filter data */
396         filter= (ACTFILTER_VISIBLE | ACTFILTER_SEL);
397         actdata_filter(&act_data, filter, data, datatype);
398         
399         /* assume that each of these is an ipo-block */
400         for (ale= act_data.first; ale; ale= ale->next) {
401                 bGPDlayer *gpls, *gpln;
402                 bGPDframe *gpf, *gpfn;
403                 
404                 /* get new layer to put into buffer */
405                 gpls= (bGPDlayer *)ale->data;
406                 gpln= MEM_callocN(sizeof(bGPDlayer), "GPCopyPasteLayer");
407                 
408                 gpln->frames.first= gpln->frames.last= NULL;
409                 strcpy(gpln->info, gpls->info);
410                 
411                 BLI_addtail(&gpcopybuf, gpln);
412                 
413                 /* loop over frames, and copy only selected frames */
414                 for (gpf= gpls->frames.first; gpf; gpf= gpf->next) {
415                         /* if frame is selected, make duplicate it and its strokes */
416                         if (gpf->flag & GP_FRAME_SELECT) {
417                                 /* add frame to buffer */
418                                 gpfn= gpencil_frame_duplicate(gpf);
419                                 BLI_addtail(&gpln->frames, gpfn);
420                                 
421                                 /* check if this is the earliest frame encountered so far */
422                                 if (gpf->framenum < gpcopy_firstframe)
423                                         gpcopy_firstframe= gpf->framenum;
424                         }
425                 }
426         }
427         
428         /* check if anything ended up in the buffer */
429         if (ELEM(NULL, gpcopybuf.first, gpcopybuf.last))
430                 error("Nothing copied to buffer");
431         
432         /* free temp memory */
433         BLI_freelistN(&act_data);
434 }
435
436 void paste_gpdata (Scene *scene)
437 {
438         ListBase act_data = {NULL, NULL};
439         bActListElem *ale;
440         int filter;
441         void *data;
442         short datatype;
443         
444         const int offset = (CFRA - gpcopy_firstframe);
445         short no_name= 0;
446         
447         /* check if buffer is empty */
448         if (ELEM(NULL, gpcopybuf.first, gpcopybuf.last)) {
449                 error("No data in buffer to paste");
450                 return;
451         }
452         /* check if single channel in buffer (disregard names if so)  */
453         if (gpcopybuf.first == gpcopybuf.last)
454                 no_name= 1;
455         
456         /* get data */
457         data= get_action_context(&datatype);
458         if (data == NULL) return;
459         if (datatype != ACTCONT_GPENCIL) return;
460         
461         /* filter data */
462         filter= (ACTFILTER_VISIBLE | ACTFILTER_SEL | ACTFILTER_FOREDIT);
463         actdata_filter(&act_data, filter, data, datatype);
464         
465         /* from selected channels */
466         for (ale= act_data.first; ale; ale= ale->next) {
467                 bGPDlayer *gpld= (bGPDlayer *)ale->data;
468                 bGPDlayer *gpls= NULL;
469                 bGPDframe *gpfs, *gpf;
470                 
471                 /* find suitable layer from buffer to use to paste from */
472                 for (gpls= gpcopybuf.first; gpls; gpls= gpls->next) {
473                         /* check if layer name matches */
474                         if ((no_name) || (strcmp(gpls->info, gpld->info)==0))
475                                 break;
476                 }
477                 
478                 /* this situation might occur! */
479                 if (gpls == NULL)
480                         continue;
481                 
482                 /* add frames from buffer */
483                 for (gpfs= gpls->frames.first; gpfs; gpfs= gpfs->next) {
484                         /* temporarily apply offset to buffer-frame while copying */
485                         gpfs->framenum += offset;
486                         
487                         /* get frame to copy data into (if no frame returned, then just ignore) */
488                         gpf= gpencil_layer_getframe(gpld, gpfs->framenum, 1);
489                         if (gpf) {
490                                 bGPDstroke *gps, *gpsn;
491                                 ScrArea *sa;
492                                 
493                                 /* get area that gp-data comes from */
494                                 //sa= gpencil_data_findowner((bGPdata *)ale->owner);    
495                                 sa = NULL;
496                                 
497                                 /* this should be the right frame... as it may be a pre-existing frame, 
498                                  * must make sure that only compatible stroke types get copied over 
499                                  *      - we cannot just add a duplicate frame, as that would cause errors
500                                  *      - need to check for compatible types to minimise memory usage (copying 'junk' over)
501                                  */
502                                 for (gps= gpfs->strokes.first; gps; gps= gps->next) {
503                                         short stroke_ok;
504                                         
505                                         /* if there's an area, check that it supports this type of stroke */
506                                         if (sa) {
507                                                 stroke_ok= 0;
508                                                 
509                                                 /* check if spacetype supports this type of stroke
510                                                  *      - NOTE: must sync this with gp_paint_initstroke() in gpencil.c
511                                                  */
512                                                 switch (sa->spacetype) {
513                                                         case SPACE_VIEW3D: /* 3D-View: either screen-aligned or 3d-space */
514                                                                 if ((gps->flag == 0) || (gps->flag & GP_STROKE_3DSPACE))
515                                                                         stroke_ok= 1;
516                                                                 break;
517                                                                 
518                                                         case SPACE_NODE: /* Nodes Editor: either screen-aligned or view-aligned */
519                                                         case SPACE_IMAGE: /* Image Editor: either screen-aligned or view\image-aligned */
520                                                                 if ((gps->flag == 0) || (gps->flag & GP_STROKE_2DSPACE))
521                                                                         stroke_ok= 1;
522                                                                 break;
523                                                                 
524                                                         case SPACE_SEQ: /* Sequence Editor: either screen-aligned or view-aligned */
525                                                                 if ((gps->flag == 0) || (gps->flag & GP_STROKE_2DIMAGE))
526                                                                         stroke_ok= 1;
527                                                                 break;
528                                                 }
529                                         }
530                                         else
531                                                 stroke_ok= 1;
532                                         
533                                         /* if stroke is ok, we make a copy of this stroke and add to frame */
534                                         if (stroke_ok) {
535                                                 /* make a copy of stroke, then of its points array */
536                                                 gpsn= MEM_dupallocN(gps);
537                                                 gpsn->points= MEM_dupallocN(gps->points);
538                                                 
539                                                 /* append stroke to frame */
540                                                 BLI_addtail(&gpf->strokes, gpsn);
541                                         }
542                                 }
543                                 
544                                 /* if no strokes (i.e. new frame) added, free gpf */
545                                 if (gpf->strokes.first == NULL)
546                                         gpencil_layer_delframe(gpld, gpf);
547                         }
548                         
549                         /* unapply offset from buffer-frame */
550                         gpfs->framenum -= offset;
551                 }
552         }
553         
554         /* free temp memory */
555         BLI_freelistN(&act_data);
556         
557         /* undo and redraw stuff */
558         BIF_undo_push("Paste Grease Pencil Frames");
559 }
560
561 /* -------------------------------------- */
562 /* Snap Tools */
563
564 static short snap_gpf_nearest (bGPDframe *gpf, Scene *scene)
565 {
566         if (gpf->flag & GP_FRAME_SELECT)
567                 gpf->framenum= (int)(floor(gpf->framenum+0.5));
568         return 0;
569 }
570
571 static short snap_gpf_nearestsec (bGPDframe *gpf, Scene *scene)
572 {
573         float secf = (float)FPS;
574         if (gpf->flag & GP_FRAME_SELECT)
575                 gpf->framenum= (int)(floor(gpf->framenum/secf + 0.5f) * secf);
576         return 0;
577 }
578
579 static short snap_gpf_cframe (bGPDframe *gpf, Scene *scene)
580 {
581         if (gpf->flag & GP_FRAME_SELECT)
582                 gpf->framenum= (int)CFRA;
583         return 0;
584 }
585
586 static short snap_gpf_nearmarker (bGPDframe *gpf, Scene *scene)
587 {
588         if (gpf->flag & GP_FRAME_SELECT)
589                 gpf->framenum= (int)find_nearest_marker_time(&scene->markers, (float)gpf->framenum);
590         return 0;
591 }
592
593
594 /* snap selected frames to ... */
595 void snap_gplayer_frames (bGPDlayer *gpl, Scene *scene, short mode)
596 {
597         switch (mode) {
598                 case 1: /* snap to nearest frame */
599                         gplayer_frames_looper(gpl, scene, snap_gpf_nearest);
600                         break;
601                 case 2: /* snap to current frame */
602                         gplayer_frames_looper(gpl, scene, snap_gpf_cframe);
603                         break;
604                 case 3: /* snap to nearest marker */
605                         gplayer_frames_looper(gpl, scene, snap_gpf_nearmarker);
606                         break;
607                 case 4: /* snap to nearest second */
608                         gplayer_frames_looper(gpl, scene, snap_gpf_nearestsec);
609                         break;
610                 default: /* just in case */
611                         gplayer_frames_looper(gpl, scene, snap_gpf_nearest);
612                         break;
613         }
614 }
615
616 /* -------------------------------------- */
617 /* Mirror Tools */
618
619 static short mirror_gpf_cframe (bGPDframe *gpf, Scene *scene)
620 {
621         int diff;
622         
623         if (gpf->flag & GP_FRAME_SELECT) {
624                 diff= CFRA - gpf->framenum;
625                 gpf->framenum= CFRA;
626         }
627         
628         return 0;
629 }
630
631 static short mirror_gpf_yaxis (bGPDframe *gpf, Scene *scene)
632 {
633         int diff;
634         
635         if (gpf->flag & GP_FRAME_SELECT) {
636                 diff= -gpf->framenum;
637                 gpf->framenum= diff;
638         }
639         
640         return 0;
641 }
642
643 static short mirror_gpf_xaxis (bGPDframe *gpf, Scene *scene)
644 {
645         int diff;
646         
647         if (gpf->flag & GP_FRAME_SELECT) {
648                 diff= -gpf->framenum;
649                 gpf->framenum= diff;
650         }
651         
652         return 0;
653 }
654
655 static short mirror_gpf_marker (bGPDframe *gpf, Scene *scene)
656 {
657         static TimeMarker *marker;
658         static short initialised = 0;
659         int diff;
660         
661         /* In order for this mirror function to work without
662          * any extra arguments being added, we use the case
663          * of bezt==NULL to denote that we should find the 
664          * marker to mirror over. The static pointer is safe
665          * to use this way, as it will be set to null after 
666          * each cycle in which this is called.
667          */
668         
669         if (gpf) {
670                 /* mirroring time */
671                 if ((gpf->flag & GP_FRAME_SELECT) && (marker)) {
672                         diff= (marker->frame - gpf->framenum);
673                         gpf->framenum= (marker->frame + diff);
674                 }
675         }
676         else {
677                 /* initialisation time */
678                 if (initialised) {
679                         /* reset everything for safety */
680                         marker = NULL;
681                         initialised = 0;
682                 }
683                 else {
684                         /* try to find a marker */
685                         for (marker= scene->markers.first; marker; marker=marker->next) {
686                                 if (marker->flag & SELECT) {
687                                         initialised = 1;
688                                         break;
689                                 }
690                         }
691                         
692                         if (initialised == 0) 
693                                 marker = NULL;
694                 }
695         }
696         
697         return 0;
698 }
699
700
701 /* mirror selected gp-frames on... */
702 void mirror_gplayer_frames (bGPDlayer *gpl, Scene *scene, short mode)
703 {
704         switch (mode) {
705                 case 1: /* mirror over current frame */
706                         gplayer_frames_looper(gpl, scene, mirror_gpf_cframe);
707                         break;
708                 case 2: /* mirror over frame 0 */
709                         gplayer_frames_looper(gpl, scene, mirror_gpf_yaxis);
710                         break;
711                 case 3: /* mirror over value 0 */
712                         gplayer_frames_looper(gpl, scene, mirror_gpf_xaxis);
713                         break;
714                 case 4: /* mirror over marker */
715                         mirror_gpf_marker(NULL, NULL);
716                         gplayer_frames_looper(gpl, scene, mirror_gpf_marker);
717                         mirror_gpf_marker(NULL, NULL);
718                         break;
719                 default: /* just in case */
720                         gplayer_frames_looper(gpl, scene, mirror_gpf_yaxis);
721                         break;
722         }
723 }
724
725 /* ***************************************** */
726 #endif // XXX disabled until Grease Pencil code stabilises again...