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