1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2008 Blender Foundation
17  */
18 
19 /** \file
20  * \ingroup spgraph
21  */
22 
23 #include <float.h>
24 #include <math.h>
25 #include <stdlib.h>
26 #include <string.h>
27 
28 #include "MEM_guardedalloc.h"
29 
30 #include "BLI_blenlib.h"
31 #include "BLI_lasso_2d.h"
32 #include "BLI_math.h"
33 #include "BLI_utildefines.h"
34 
35 #include "DNA_anim_types.h"
36 #include "DNA_scene_types.h"
37 #include "DNA_screen_types.h"
38 #include "DNA_space_types.h"
39 
40 #include "RNA_access.h"
41 #include "RNA_define.h"
42 
43 #include "BKE_context.h"
44 #include "BKE_fcurve.h"
45 #include "BKE_nla.h"
46 
47 #include "UI_view2d.h"
48 
49 #include "ED_anim_api.h"
50 #include "ED_keyframes_edit.h"
51 #include "ED_markers.h"
52 #include "ED_select_utils.h"
53 
54 #include "WM_api.h"
55 #include "WM_types.h"
56 
57 #include "graph_intern.h"
58 
59 /* ************************************************************************** */
60 /* KEYFRAMES STUFF */
61 
62 /* temp info for caching handle vertices close */
63 typedef struct tNearestVertInfo {
64   struct tNearestVertInfo *next, *prev;
65 
66   FCurve *fcu; /* F-Curve that keyframe comes from */
67 
68   BezTriple *bezt; /* keyframe to consider */
69   FPoint *fpt;     /* sample point to consider */
70 
71   short hpoint; /* the handle index that we hit (eHandleIndex) */
72   short sel;    /* whether the handle is selected or not */
73   int dist;     /* distance from mouse to vert */
74 
75   eAnim_ChannelType ctype; /* type of animation channel this FCurve comes from */
76 
77   float frame; /* frame that point was on when it matched (global time) */
78 } tNearestVertInfo;
79 
80 /* Tags for the type of graph vert that we have */
81 typedef enum eGraphVertIndex {
82   NEAREST_HANDLE_LEFT = -1,
83   NEAREST_HANDLE_KEY,
84   NEAREST_HANDLE_RIGHT,
85 } eGraphVertIndex;
86 
87 /* Tolerance for absolute radius (in pixels) of the vert from the cursor to use */
88 /* TODO: perhaps this should depend a bit on the size that the user set the vertices to be? */
89 #define GVERTSEL_TOL (10 * U.pixelsize)
90 
91 /* ....... */
92 
93 /* check if its ok to select a handle */
94 /* XXX also need to check for int-values only? */
fcurve_handle_sel_check(SpaceGraph * sipo,BezTriple * bezt)95 static bool fcurve_handle_sel_check(SpaceGraph *sipo, BezTriple *bezt)
96 {
97   if (sipo->flag & SIPO_NOHANDLES) {
98     return false;
99   }
100   if ((sipo->flag & SIPO_SELVHANDLESONLY) && BEZT_ISSEL_ANY(bezt) == 0) {
101     return false;
102   }
103   return true;
104 }
105 
106 /* check if the given vertex is within bounds or not */
107 /* TODO: should we return if we hit something? */
nearest_fcurve_vert_store(ListBase * matches,View2D * v2d,FCurve * fcu,eAnim_ChannelType ctype,BezTriple * bezt,FPoint * fpt,short hpoint,const int mval[2],float unit_scale,float offset)108 static void nearest_fcurve_vert_store(ListBase *matches,
109                                       View2D *v2d,
110                                       FCurve *fcu,
111                                       eAnim_ChannelType ctype,
112                                       BezTriple *bezt,
113                                       FPoint *fpt,
114                                       short hpoint,
115                                       const int mval[2],
116                                       float unit_scale,
117                                       float offset)
118 {
119   /* Keyframes or Samples? */
120   if (bezt) {
121     int screen_co[2], dist;
122 
123     /* convert from data-space to screen coordinates
124      * NOTE: hpoint+1 gives us 0,1,2 respectively for each handle,
125      *  needed to access the relevant vertex coordinates in the 3x3
126      *  'vec' matrix
127      */
128     if (UI_view2d_view_to_region_clip(v2d,
129                                       bezt->vec[hpoint + 1][0],
130                                       (bezt->vec[hpoint + 1][1] + offset) * unit_scale,
131                                       &screen_co[0],
132                                       &screen_co[1]) &&
133         /* check if distance from mouse cursor to vert in screen space is within tolerance */
134         ((dist = len_v2v2_int(mval, screen_co)) <= GVERTSEL_TOL)) {
135       tNearestVertInfo *nvi = (tNearestVertInfo *)matches->last;
136       bool replace = false;
137 
138       /* If there is already a point for the F-Curve,
139        * check if this point is closer than that was. */
140       if ((nvi) && (nvi->fcu == fcu)) {
141         /* replace if we are closer, or if equal and that one wasn't selected but we are... */
142         if ((nvi->dist > dist) || ((nvi->sel == 0) && BEZT_ISSEL_ANY(bezt))) {
143           replace = 1;
144         }
145       }
146       /* add new if not replacing... */
147       if (replace == 0) {
148         nvi = MEM_callocN(sizeof(tNearestVertInfo), "Nearest Graph Vert Info - Bezt");
149       }
150 
151       /* store values */
152       nvi->fcu = fcu;
153       nvi->ctype = ctype;
154 
155       nvi->bezt = bezt;
156       nvi->hpoint = hpoint;
157       nvi->dist = dist;
158 
159       nvi->frame = bezt->vec[1][0]; /* currently in global time... */
160 
161       nvi->sel = BEZT_ISSEL_ANY(bezt); /* XXX... should this use the individual verts instead? */
162 
163       /* add to list of matches if appropriate... */
164       if (replace == 0) {
165         BLI_addtail(matches, nvi);
166       }
167     }
168   }
169   else if (fpt) {
170     /* TODO... */
171   }
172 }
173 
174 /* helper for find_nearest_fcurve_vert() - build the list of nearest matches */
get_nearest_fcurve_verts_list(bAnimContext * ac,const int mval[2],ListBase * matches)175 static void get_nearest_fcurve_verts_list(bAnimContext *ac, const int mval[2], ListBase *matches)
176 {
177   ListBase anim_data = {NULL, NULL};
178   bAnimListElem *ale;
179   int filter;
180 
181   SpaceGraph *sipo = (SpaceGraph *)ac->sl;
182   View2D *v2d = &ac->region->v2d;
183   short mapping_flag = 0;
184 
185   /* get curves to search through
186    * - if the option to only show keyframes that belong to selected F-Curves is enabled,
187    *   include the 'only selected' flag...
188    */
189   filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS);
190   if (sipo->flag &
191       SIPO_SELCUVERTSONLY) { /* FIXME: this should really be check for by the filtering code... */
192     filter |= ANIMFILTER_SEL;
193   }
194   mapping_flag |= ANIM_get_normalization_flags(ac);
195   ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
196 
197   for (ale = anim_data.first; ale; ale = ale->next) {
198     FCurve *fcu = (FCurve *)ale->key_data;
199     AnimData *adt = ANIM_nla_mapping_get(ac, ale);
200     float offset;
201     float unit_scale = ANIM_unit_mapping_get_factor(
202         ac->scene, ale->id, fcu, mapping_flag, &offset);
203 
204     /* apply NLA mapping to all the keyframes */
205     if (adt) {
206       ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 0);
207     }
208 
209     if (fcu->bezt) {
210       BezTriple *bezt1 = fcu->bezt, *prevbezt = NULL;
211       int i;
212 
213       for (i = 0; i < fcu->totvert; i++, prevbezt = bezt1, bezt1++) {
214         /* keyframe */
215         nearest_fcurve_vert_store(matches,
216                                   v2d,
217                                   fcu,
218                                   ale->type,
219                                   bezt1,
220                                   NULL,
221                                   NEAREST_HANDLE_KEY,
222                                   mval,
223                                   unit_scale,
224                                   offset);
225 
226         /* handles - only do them if they're visible */
227         if (fcurve_handle_sel_check(sipo, bezt1) && (fcu->totvert > 1)) {
228           /* first handle only visible if previous segment had handles */
229           if ((!prevbezt && (bezt1->ipo == BEZT_IPO_BEZ)) ||
230               (prevbezt && (prevbezt->ipo == BEZT_IPO_BEZ))) {
231             nearest_fcurve_vert_store(matches,
232                                       v2d,
233                                       fcu,
234                                       ale->type,
235                                       bezt1,
236                                       NULL,
237                                       NEAREST_HANDLE_LEFT,
238                                       mval,
239                                       unit_scale,
240                                       offset);
241           }
242 
243           /* second handle only visible if this segment is bezier */
244           if (bezt1->ipo == BEZT_IPO_BEZ) {
245             nearest_fcurve_vert_store(matches,
246                                       v2d,
247                                       fcu,
248                                       ale->type,
249                                       bezt1,
250                                       NULL,
251                                       NEAREST_HANDLE_RIGHT,
252                                       mval,
253                                       unit_scale,
254                                       offset);
255           }
256         }
257       }
258     }
259     else if (fcu->fpt) {
260       /* TODO; do this for samples too */
261     }
262 
263     /* un-apply NLA mapping from all the keyframes */
264     if (adt) {
265       ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 0);
266     }
267   }
268 
269   /* free channels */
270   ANIM_animdata_freelist(&anim_data);
271 }
272 
273 /* helper for find_nearest_fcurve_vert() - get the best match to use */
get_best_nearest_fcurve_vert(ListBase * matches)274 static tNearestVertInfo *get_best_nearest_fcurve_vert(ListBase *matches)
275 {
276   tNearestVertInfo *nvi = NULL;
277   short found = 0;
278 
279   /* abort if list is empty */
280   if (BLI_listbase_is_empty(matches)) {
281     return NULL;
282   }
283 
284   /* if list only has 1 item, remove it from the list and return */
285   if (BLI_listbase_is_single(matches)) {
286     /* need to remove from the list, otherwise it gets freed and then we can't return it */
287     return BLI_pophead(matches);
288   }
289 
290   /* try to find the first selected F-Curve vert, then take the one after it */
291   for (nvi = matches->first; nvi; nvi = nvi->next) {
292     /* which mode of search are we in: find first selected, or find vert? */
293     if (found) {
294       /* Just take this vert now that we've found the selected one
295        * - We'll need to remove this from the list
296        *   so that it can be returned to the original caller.
297        */
298       BLI_remlink(matches, nvi);
299       return nvi;
300     }
301 
302     /* if vert is selected, we've got what we want... */
303     if (nvi->sel) {
304       found = 1;
305     }
306   }
307 
308   /* if we're still here, this means that we failed to find anything appropriate in the first pass,
309    * so just take the first item now...
310    */
311   return BLI_pophead(matches);
312 }
313 
314 /**
315  * Find the nearest vertices (either a handle or the keyframe)
316  * that are nearest to the mouse cursor (in area coordinates)
317  *
318  * \note the match info found must still be freed.
319  */
find_nearest_fcurve_vert(bAnimContext * ac,const int mval[2])320 static tNearestVertInfo *find_nearest_fcurve_vert(bAnimContext *ac, const int mval[2])
321 {
322   ListBase matches = {NULL, NULL};
323   tNearestVertInfo *nvi;
324 
325   /* step 1: get the nearest verts */
326   get_nearest_fcurve_verts_list(ac, mval, &matches);
327 
328   /* step 2: find the best vert */
329   nvi = get_best_nearest_fcurve_vert(&matches);
330 
331   BLI_freelistN(&matches);
332 
333   /* return the best vert found */
334   return nvi;
335 }
336 
337 /* ******************** Deselect All Operator ***************************** */
338 /* This operator works in one of three ways:
339  * 1) (de)select all (AKEY) - test if select all or deselect all
340  * 2) invert all (CTRL-IKEY) - invert selection of all keyframes
341  * 3) (de)select all - no testing is done; only for use internal tools as normal function...
342  */
343 
344 /* Deselects keyframes in the Graph Editor
345  * - This is called by the deselect all operator, as well as other ones!
346  *
347  * - test: check if select or deselect all
348  * - sel: how to select keyframes
349  *   0 = deselect
350  *   1 = select
351  *   2 = invert
352  * - do_channels: whether to affect selection status of channels
353  */
deselect_graph_keys(bAnimContext * ac,bool test,short sel,bool do_channels)354 void deselect_graph_keys(bAnimContext *ac, bool test, short sel, bool do_channels)
355 {
356   ListBase anim_data = {NULL, NULL};
357   bAnimListElem *ale;
358   int filter;
359 
360   SpaceGraph *sipo = (SpaceGraph *)ac->sl;
361   KeyframeEditData ked = {{NULL}};
362   KeyframeEditFunc test_cb, sel_cb;
363 
364   /* determine type-based settings */
365   filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS);
366 
367   /* filter data */
368   ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
369 
370   /* init BezTriple looping data */
371   test_cb = ANIM_editkeyframes_ok(BEZT_OK_SELECTED);
372 
373   /* See if we should be selecting or deselecting */
374   if (test) {
375     for (ale = anim_data.first; ale; ale = ale->next) {
376       if (ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, test_cb, NULL)) {
377         sel = SELECT_SUBTRACT;
378         break;
379       }
380     }
381   }
382 
383   /* convert sel to selectmode, and use that to get editor */
384   sel_cb = ANIM_editkeyframes_select(sel);
385 
386   /* Now set the flags */
387   for (ale = anim_data.first; ale; ale = ale->next) {
388     FCurve *fcu = (FCurve *)ale->key_data;
389 
390     /* Keyframes First */
391     ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, sel_cb, NULL);
392 
393     /* affect channel selection status? */
394     if (do_channels) {
395       /* Only change selection of channel when the visibility of keyframes
396        * doesn't depend on this. */
397       if ((sipo->flag & SIPO_SELCUVERTSONLY) == 0) {
398         /* deactivate the F-Curve, and deselect if deselecting keyframes.
399          * otherwise select the F-Curve too since we've selected all the keyframes
400          */
401         if (sel == SELECT_SUBTRACT) {
402           fcu->flag &= ~FCURVE_SELECTED;
403         }
404         else {
405           fcu->flag |= FCURVE_SELECTED;
406         }
407       }
408 
409       /* always deactivate all F-Curves if we perform batch ops for selection */
410       fcu->flag &= ~FCURVE_ACTIVE;
411     }
412   }
413 
414   /* Cleanup */
415   ANIM_animdata_freelist(&anim_data);
416 }
417 
418 /* ------------------- */
419 
graphkeys_deselectall_exec(bContext * C,wmOperator * op)420 static int graphkeys_deselectall_exec(bContext *C, wmOperator *op)
421 {
422   bAnimContext ac;
423   bAnimListElem *ale_active = NULL;
424 
425   /* get editor data */
426   if (ANIM_animdata_get_context(C, &ac) == 0) {
427     return OPERATOR_CANCELLED;
428   }
429 
430   /* find active F-Curve, and preserve this for later
431    * or else it becomes annoying with the current active
432    * curve keeps fading out even while you're editing it
433    */
434   ale_active = get_active_fcurve_channel(&ac);
435 
436   /* 'standard' behavior - check if selected, then apply relevant selection */
437   const int action = RNA_enum_get(op->ptr, "action");
438   switch (action) {
439     case SEL_TOGGLE:
440       deselect_graph_keys(&ac, 1, SELECT_ADD, true);
441       break;
442     case SEL_SELECT:
443       deselect_graph_keys(&ac, 0, SELECT_ADD, true);
444       break;
445     case SEL_DESELECT:
446       deselect_graph_keys(&ac, 0, SELECT_SUBTRACT, true);
447       break;
448     case SEL_INVERT:
449       deselect_graph_keys(&ac, 0, SELECT_INVERT, true);
450       break;
451     default:
452       BLI_assert(0);
453       break;
454   }
455 
456   /* restore active F-Curve... */
457   if (ale_active) {
458     FCurve *fcu = (FCurve *)ale_active->data;
459 
460     /* all others should not be disabled, so we should be able to just set this directly...
461      * - selection needs to be set too, or else this won't work...
462      */
463     fcu->flag |= (FCURVE_SELECTED | FCURVE_ACTIVE);
464 
465     MEM_freeN(ale_active);
466     ale_active = NULL;
467   }
468 
469   /* set notifier that things have changed */
470   WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_SELECTED, NULL);
471 
472   return OPERATOR_FINISHED;
473 }
474 
GRAPH_OT_select_all(wmOperatorType * ot)475 void GRAPH_OT_select_all(wmOperatorType *ot)
476 {
477   /* identifiers */
478   ot->name = "Select All";
479   ot->idname = "GRAPH_OT_select_all";
480   ot->description = "Toggle selection of all keyframes";
481 
482   /* api callbacks */
483   ot->exec = graphkeys_deselectall_exec;
484   ot->poll = graphop_visible_keyframes_poll;
485 
486   /* flags */
487   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
488 
489   /* properties */
490   WM_operator_properties_select_all(ot);
491 }
492 
493 /* ******************** Box Select Operator **************************** */
494 /* This operator currently works in one of three ways:
495  * -> BKEY     - 1) all keyframes within region are selected (validation with BEZT_OK_REGION)
496  * -> ALT-BKEY - depending on which axis of the region was larger...
497  *    -> 2) x-axis, so select all frames within frame range (validation with BEZT_OK_FRAMERANGE)
498  *    -> 3) y-axis, so select all frames within channels that region included
499  *          (validation with BEZT_OK_VALUERANGE).
500  *
501  * The selection backend is also reused for the Lasso and Circle select operators.
502  */
503 
504 /* Box Select only selects keyframes now, as overshooting handles often get caught too,
505  * which means that they may be inadvertently moved as well. However, incl_handles overrides
506  * this, and allow handles to be considered independently too.
507  * Also, for convenience, handles should get same status as keyframe (if it was within bounds).
508  */
box_select_graphkeys(bAnimContext * ac,const rctf * rectf_view,short mode,short selectmode,bool incl_handles,void * data)509 static void box_select_graphkeys(bAnimContext *ac,
510                                  const rctf *rectf_view,
511                                  short mode,
512                                  short selectmode,
513                                  bool incl_handles,
514                                  void *data)
515 {
516   ListBase anim_data = {NULL, NULL};
517   bAnimListElem *ale;
518   int filter, mapping_flag;
519 
520   SpaceGraph *sipo = (SpaceGraph *)ac->sl;
521   KeyframeEditData ked;
522   KeyframeEditFunc ok_cb, select_cb;
523   View2D *v2d = &ac->region->v2d;
524   rctf rectf, scaled_rectf;
525 
526   /* Convert mouse coordinates to frame ranges and
527    * channel coordinates corrected for view pan/zoom. */
528   UI_view2d_region_to_view_rctf(v2d, rectf_view, &rectf);
529 
530   /* filter data */
531   filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS);
532   if (sipo->flag & SIPO_SELCUVERTSONLY) {
533     filter |= ANIMFILTER_FOREDIT | ANIMFILTER_SELEDIT;
534   }
535   ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
536 
537   /* get beztriple editing/validation funcs  */
538   select_cb = ANIM_editkeyframes_select(selectmode);
539   ok_cb = ANIM_editkeyframes_ok(mode);
540 
541   /* init editing data */
542   memset(&ked, 0, sizeof(KeyframeEditData));
543   if (mode == BEZT_OK_REGION_LASSO) {
544     KeyframeEdit_LassoData *data_lasso = data;
545     data_lasso->rectf_scaled = &scaled_rectf;
546     ked.data = data_lasso;
547   }
548   else if (mode == BEZT_OK_REGION_CIRCLE) {
549     KeyframeEdit_CircleData *data_circle = data;
550     data_circle->rectf_scaled = &scaled_rectf;
551     ked.data = data;
552   }
553   else {
554     ked.data = &scaled_rectf;
555   }
556 
557   if (sipo->flag & SIPO_SELVHANDLESONLY) {
558     ked.iterflags |= KEYFRAME_ITER_HANDLES_DEFAULT_INVISIBLE;
559   }
560 
561   /* treat handles separately? */
562   if (incl_handles) {
563     ked.iterflags |= KEYFRAME_ITER_INCL_HANDLES;
564     mapping_flag = 0;
565   }
566   else {
567     mapping_flag = ANIM_UNITCONV_ONLYKEYS;
568   }
569 
570   mapping_flag |= ANIM_get_normalization_flags(ac);
571 
572   /* loop over data, doing box select */
573   for (ale = anim_data.first; ale; ale = ale->next) {
574     AnimData *adt = ANIM_nla_mapping_get(ac, ale);
575     FCurve *fcu = (FCurve *)ale->key_data;
576     float offset;
577     float unit_scale = ANIM_unit_mapping_get_factor(
578         ac->scene, ale->id, fcu, mapping_flag, &offset);
579 
580     /* apply NLA mapping to all the keyframes, since it's easier than trying to
581      * guess when a callback might use something different
582      */
583     if (adt) {
584       ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, incl_handles == 0);
585     }
586 
587     scaled_rectf.xmin = rectf.xmin;
588     scaled_rectf.xmax = rectf.xmax;
589     scaled_rectf.ymin = rectf.ymin / unit_scale - offset;
590     scaled_rectf.ymax = rectf.ymax / unit_scale - offset;
591 
592     /* set horizontal range (if applicable)
593      * NOTE: these values are only used for x-range and y-range but not region
594      *      (which uses ked.data, i.e. rectf)
595      */
596     if (mode != BEZT_OK_VALUERANGE) {
597       ked.f1 = rectf.xmin;
598       ked.f2 = rectf.xmax;
599     }
600     else {
601       ked.f1 = rectf.ymin;
602       ked.f2 = rectf.ymax;
603     }
604 
605     /* firstly, check if any keyframes will be hit by this */
606     if (ANIM_fcurve_keyframes_loop(&ked, fcu, NULL, ok_cb, NULL)) {
607       /* select keyframes that are in the appropriate places */
608       ANIM_fcurve_keyframes_loop(&ked, fcu, ok_cb, select_cb, NULL);
609 
610       /* Only change selection of channel when the visibility of keyframes
611        * doesn't depend on this. */
612       if ((sipo->flag & SIPO_SELCUVERTSONLY) == 0) {
613         /* select the curve too now that curve will be touched */
614         if (selectmode == SELECT_ADD) {
615           fcu->flag |= FCURVE_SELECTED;
616         }
617       }
618     }
619 
620     /* un-apply NLA mapping from all the keyframes */
621     if (adt) {
622       ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, incl_handles == 0);
623     }
624   }
625 
626   /* cleanup */
627   ANIM_animdata_freelist(&anim_data);
628 }
629 
630 /* ------------------- */
631 
graphkeys_box_select_invoke(bContext * C,wmOperator * op,const wmEvent * event)632 static int graphkeys_box_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
633 {
634   bAnimContext ac;
635   if (ANIM_animdata_get_context(C, &ac) == 0) {
636     return OPERATOR_CANCELLED;
637   }
638 
639   if (RNA_boolean_get(op->ptr, "tweak")) {
640     tNearestVertInfo *under_mouse = find_nearest_fcurve_vert(&ac, event->mval);
641     bool mouse_is_over_element = under_mouse != NULL;
642     if (under_mouse) {
643       MEM_freeN(under_mouse);
644     }
645 
646     if (mouse_is_over_element) {
647       return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
648     }
649   }
650 
651   return WM_gesture_box_invoke(C, op, event);
652 }
653 
graphkeys_box_select_exec(bContext * C,wmOperator * op)654 static int graphkeys_box_select_exec(bContext *C, wmOperator *op)
655 {
656   bAnimContext ac;
657   rcti rect;
658   rctf rect_fl;
659   short mode = 0;
660 
661   /* get editor data */
662   if (ANIM_animdata_get_context(C, &ac) == 0) {
663     return OPERATOR_CANCELLED;
664   }
665 
666   const eSelectOp sel_op = RNA_enum_get(op->ptr, "mode");
667   const int selectmode = (sel_op != SEL_OP_SUB) ? SELECT_ADD : SELECT_SUBTRACT;
668   if (SEL_OP_USE_PRE_DESELECT(sel_op)) {
669     deselect_graph_keys(&ac, 1, SELECT_SUBTRACT, true);
670   }
671 
672   /* 'include_handles' from the operator specifies whether to include handles in the selection. */
673   const bool incl_handles = RNA_boolean_get(op->ptr, "include_handles");
674 
675   /* get settings from operator */
676   WM_operator_properties_border_to_rcti(op, &rect);
677 
678   /* selection 'mode' depends on whether box_select region only matters on one axis */
679   if (RNA_boolean_get(op->ptr, "axis_range")) {
680     /* mode depends on which axis of the range is larger to determine which axis to use
681      * - Checking this in region-space is fine, as it's fundamentally still going to be a
682      *   different rect size.
683      * - The frame-range select option is favored over the channel one (x over y),
684      *   as frame-range one is often used for tweaking timing when "blocking",
685      *   while channels is not that useful.
686      */
687     if ((BLI_rcti_size_x(&rect)) >= (BLI_rcti_size_y(&rect))) {
688       mode = BEZT_OK_FRAMERANGE;
689     }
690     else {
691       mode = BEZT_OK_VALUERANGE;
692     }
693   }
694   else {
695     mode = BEZT_OK_REGION;
696   }
697 
698   BLI_rctf_rcti_copy(&rect_fl, &rect);
699 
700   /* apply box_select action */
701   box_select_graphkeys(&ac, &rect_fl, mode, selectmode, incl_handles, NULL);
702 
703   /* send notifier that keyframe selection has changed */
704   WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_SELECTED, NULL);
705 
706   return OPERATOR_FINISHED;
707 }
708 
GRAPH_OT_select_box(wmOperatorType * ot)709 void GRAPH_OT_select_box(wmOperatorType *ot)
710 {
711   /* identifiers */
712   ot->name = "Box Select";
713   ot->idname = "GRAPH_OT_select_box";
714   ot->description = "Select all keyframes within the specified region";
715 
716   /* api callbacks */
717   ot->invoke = graphkeys_box_select_invoke;
718   ot->exec = graphkeys_box_select_exec;
719   ot->modal = WM_gesture_box_modal;
720   ot->cancel = WM_gesture_box_cancel;
721 
722   ot->poll = graphop_visible_keyframes_poll;
723 
724   /* flags */
725   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
726 
727   /* properties */
728   ot->prop = RNA_def_boolean(ot->srna, "axis_range", 0, "Axis Range", "");
729   RNA_def_property_flag(ot->prop, PROP_SKIP_SAVE);
730 
731   PropertyRNA *prop;
732   prop = RNA_def_boolean(ot->srna,
733                          "include_handles",
734                          true,
735                          "Include Handles",
736                          "Are handles tested individually against the selection criteria");
737   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
738 
739   prop = RNA_def_boolean(
740       ot->srna, "tweak", 0, "Tweak", "Operator has been activated using a tweak event");
741   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
742 
743   WM_operator_properties_gesture_box(ot);
744   WM_operator_properties_select_operation_simple(ot);
745 }
746 
747 /* ------------------- */
748 
graphkeys_lassoselect_exec(bContext * C,wmOperator * op)749 static int graphkeys_lassoselect_exec(bContext *C, wmOperator *op)
750 {
751   bAnimContext ac;
752 
753   KeyframeEdit_LassoData data_lasso = {0};
754   rcti rect;
755   rctf rect_fl;
756 
757   bool incl_handles;
758 
759   /* get editor data */
760   if (ANIM_animdata_get_context(C, &ac) == 0) {
761     return OPERATOR_CANCELLED;
762   }
763 
764   data_lasso.rectf_view = &rect_fl;
765   data_lasso.mcoords = WM_gesture_lasso_path_to_array(C, op, &data_lasso.mcoords_len);
766   if (data_lasso.mcoords == NULL) {
767     return OPERATOR_CANCELLED;
768   }
769 
770   const eSelectOp sel_op = RNA_enum_get(op->ptr, "mode");
771   const short selectmode = (sel_op != SEL_OP_SUB) ? SELECT_ADD : SELECT_SUBTRACT;
772   if (SEL_OP_USE_PRE_DESELECT(sel_op)) {
773     deselect_graph_keys(&ac, 0, SELECT_SUBTRACT, true);
774   }
775 
776   {
777     SpaceGraph *sipo = (SpaceGraph *)ac.sl;
778     if (selectmode == SELECT_ADD) {
779       incl_handles = ((sipo->flag & SIPO_SELVHANDLESONLY) || (sipo->flag & SIPO_NOHANDLES)) == 0;
780     }
781     else {
782       incl_handles = (sipo->flag & SIPO_NOHANDLES) == 0;
783     }
784   }
785 
786   /* get settings from operator */
787   BLI_lasso_boundbox(&rect, data_lasso.mcoords, data_lasso.mcoords_len);
788   BLI_rctf_rcti_copy(&rect_fl, &rect);
789 
790   /* apply box_select action */
791   box_select_graphkeys(&ac, &rect_fl, BEZT_OK_REGION_LASSO, selectmode, incl_handles, &data_lasso);
792 
793   MEM_freeN((void *)data_lasso.mcoords);
794 
795   /* send notifier that keyframe selection has changed */
796   WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_SELECTED, NULL);
797 
798   return OPERATOR_FINISHED;
799 }
800 
GRAPH_OT_select_lasso(wmOperatorType * ot)801 void GRAPH_OT_select_lasso(wmOperatorType *ot)
802 {
803   /* identifiers */
804   ot->name = "Lasso Select";
805   ot->description = "Select keyframe points using lasso selection";
806   ot->idname = "GRAPH_OT_select_lasso";
807 
808   /* api callbacks */
809   ot->invoke = WM_gesture_lasso_invoke;
810   ot->modal = WM_gesture_lasso_modal;
811   ot->exec = graphkeys_lassoselect_exec;
812   ot->poll = graphop_visible_keyframes_poll;
813   ot->cancel = WM_gesture_lasso_cancel;
814 
815   /* flags */
816   ot->flag = OPTYPE_UNDO;
817 
818   /* properties */
819   WM_operator_properties_gesture_lasso(ot);
820   WM_operator_properties_select_operation_simple(ot);
821 }
822 
823 /* ------------------- */
824 
graph_circle_select_exec(bContext * C,wmOperator * op)825 static int graph_circle_select_exec(bContext *C, wmOperator *op)
826 {
827   bAnimContext ac;
828   bool incl_handles = false;
829 
830   KeyframeEdit_CircleData data = {0};
831   rctf rect_fl;
832 
833   float x = RNA_int_get(op->ptr, "x");
834   float y = RNA_int_get(op->ptr, "y");
835   float radius = RNA_int_get(op->ptr, "radius");
836 
837   /* get editor data */
838   if (ANIM_animdata_get_context(C, &ac) == 0) {
839     return OPERATOR_CANCELLED;
840   }
841 
842   const eSelectOp sel_op = ED_select_op_modal(RNA_enum_get(op->ptr, "mode"),
843                                               WM_gesture_is_modal_first(op->customdata));
844   const short selectmode = (sel_op != SEL_OP_SUB) ? SELECT_ADD : SELECT_SUBTRACT;
845   if (SEL_OP_USE_PRE_DESELECT(sel_op)) {
846     deselect_graph_keys(&ac, 0, SELECT_SUBTRACT, true);
847   }
848 
849   data.mval[0] = x;
850   data.mval[1] = y;
851   data.radius_squared = radius * radius;
852   data.rectf_view = &rect_fl;
853 
854   rect_fl.xmin = x - radius;
855   rect_fl.xmax = x + radius;
856   rect_fl.ymin = y - radius;
857   rect_fl.ymax = y + radius;
858 
859   {
860     SpaceGraph *sipo = (SpaceGraph *)ac.sl;
861     if (selectmode == SELECT_ADD) {
862       incl_handles = ((sipo->flag & SIPO_SELVHANDLESONLY) || (sipo->flag & SIPO_NOHANDLES)) == 0;
863     }
864     else {
865       incl_handles = (sipo->flag & SIPO_NOHANDLES) == 0;
866     }
867   }
868 
869   /* apply box_select action */
870   box_select_graphkeys(&ac, &rect_fl, BEZT_OK_REGION_CIRCLE, selectmode, incl_handles, &data);
871 
872   /* send notifier that keyframe selection has changed */
873   WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_SELECTED, NULL);
874 
875   return OPERATOR_FINISHED;
876 }
877 
GRAPH_OT_select_circle(wmOperatorType * ot)878 void GRAPH_OT_select_circle(wmOperatorType *ot)
879 {
880   ot->name = "Circle Select";
881   ot->description = "Select keyframe points using circle selection";
882   ot->idname = "GRAPH_OT_select_circle";
883 
884   ot->invoke = WM_gesture_circle_invoke;
885   ot->modal = WM_gesture_circle_modal;
886   ot->exec = graph_circle_select_exec;
887   ot->poll = graphop_visible_keyframes_poll;
888   ot->cancel = WM_gesture_circle_cancel;
889 
890   /* flags */
891   ot->flag = OPTYPE_UNDO;
892 
893   /* properties */
894   WM_operator_properties_gesture_circle(ot);
895   WM_operator_properties_select_operation_simple(ot);
896 }
897 
898 /* ******************** Column Select Operator **************************** */
899 /* This operator works in one of four ways:
900  * - 1) select all keyframes in the same frame as a selected one  (KKEY)
901  * - 2) select all keyframes in the same frame as the current frame marker (CTRL-KKEY)
902  * - 3) select all keyframes in the same frame as a selected markers (SHIFT-KKEY)
903  * - 4) select all keyframes that occur between selected markers (ALT-KKEY)
904  */
905 
906 /* defines for column-select mode */
907 static const EnumPropertyItem prop_column_select_types[] = {
908     {GRAPHKEYS_COLUMNSEL_KEYS, "KEYS", 0, "On Selected Keyframes", ""},
909     {GRAPHKEYS_COLUMNSEL_CFRA, "CFRA", 0, "On Current Frame", ""},
910     {GRAPHKEYS_COLUMNSEL_MARKERS_COLUMN, "MARKERS_COLUMN", 0, "On Selected Markers", ""},
911     {GRAPHKEYS_COLUMNSEL_MARKERS_BETWEEN,
912      "MARKERS_BETWEEN",
913      0,
914      "Between Min/Max Selected Markers",
915      ""},
916     {0, NULL, 0, NULL, NULL},
917 };
918 
919 /* ------------------- */
920 
921 /* Selects all visible keyframes between the specified markers */
922 /* TODO, this is almost an _exact_ duplicate of a function of the same name in action_select.c
923  * should de-duplicate - campbell */
markers_selectkeys_between(bAnimContext * ac)924 static void markers_selectkeys_between(bAnimContext *ac)
925 {
926   ListBase anim_data = {NULL, NULL};
927   bAnimListElem *ale;
928   int filter;
929 
930   KeyframeEditFunc ok_cb, select_cb;
931   KeyframeEditData ked = {{NULL}};
932   float min, max;
933 
934   /* get extreme markers */
935   ED_markers_get_minmax(ac->markers, 1, &min, &max);
936   min -= 0.5f;
937   max += 0.5f;
938 
939   /* get editing funcs + data */
940   ok_cb = ANIM_editkeyframes_ok(BEZT_OK_FRAMERANGE);
941   select_cb = ANIM_editkeyframes_select(SELECT_ADD);
942 
943   ked.f1 = min;
944   ked.f2 = max;
945 
946   /* filter data */
947   filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS);
948   ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
949 
950   /* select keys in-between */
951   for (ale = anim_data.first; ale; ale = ale->next) {
952     AnimData *adt = ANIM_nla_mapping_get(ac, ale);
953 
954     if (adt) {
955       ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 1);
956       ANIM_fcurve_keyframes_loop(&ked, ale->key_data, ok_cb, select_cb, NULL);
957       ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 1);
958     }
959     else {
960       ANIM_fcurve_keyframes_loop(&ked, ale->key_data, ok_cb, select_cb, NULL);
961     }
962   }
963 
964   /* Cleanup */
965   ANIM_animdata_freelist(&anim_data);
966 }
967 
968 /* Selects all visible keyframes in the same frames as the specified elements */
columnselect_graph_keys(bAnimContext * ac,short mode)969 static void columnselect_graph_keys(bAnimContext *ac, short mode)
970 {
971   ListBase anim_data = {NULL, NULL};
972   bAnimListElem *ale;
973   int filter;
974 
975   Scene *scene = ac->scene;
976   CfraElem *ce;
977   KeyframeEditFunc select_cb, ok_cb;
978   KeyframeEditData ked;
979 
980   /* initialize keyframe editing data */
981   memset(&ked, 0, sizeof(KeyframeEditData));
982 
983   /* build list of columns */
984   switch (mode) {
985     case GRAPHKEYS_COLUMNSEL_KEYS: /* list of selected keys */
986       filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS);
987       ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
988 
989       for (ale = anim_data.first; ale; ale = ale->next) {
990         ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, bezt_to_cfraelem, NULL);
991       }
992 
993       ANIM_animdata_freelist(&anim_data);
994       break;
995 
996     case GRAPHKEYS_COLUMNSEL_CFRA: /* current frame */
997       /* make a single CfraElem for storing this */
998       ce = MEM_callocN(sizeof(CfraElem), "cfraElem");
999       BLI_addtail(&ked.list, ce);
1000 
1001       ce->cfra = (float)CFRA;
1002       break;
1003 
1004     case GRAPHKEYS_COLUMNSEL_MARKERS_COLUMN: /* list of selected markers */
1005       ED_markers_make_cfra_list(ac->markers, &ked.list, SELECT);
1006       break;
1007 
1008     default: /* invalid option */
1009       return;
1010   }
1011 
1012   /* set up BezTriple edit callbacks */
1013   select_cb = ANIM_editkeyframes_select(SELECT_ADD);
1014   ok_cb = ANIM_editkeyframes_ok(BEZT_OK_FRAME);
1015 
1016   /* loop through all of the keys and select additional keyframes
1017    * based on the keys found to be selected above
1018    */
1019   filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS);
1020   ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
1021 
1022   for (ale = anim_data.first; ale; ale = ale->next) {
1023     AnimData *adt = ANIM_nla_mapping_get(ac, ale);
1024 
1025     /* loop over cfraelems (stored in the KeyframeEditData->list)
1026      * - we need to do this here, as we can apply fewer NLA-mapping conversions
1027      */
1028     for (ce = ked.list.first; ce; ce = ce->next) {
1029       /* set frame for validation callback to refer to */
1030       ked.f1 = BKE_nla_tweakedit_remap(adt, ce->cfra, NLATIME_CONVERT_UNMAP);
1031 
1032       /* select elements with frame number matching cfraelem */
1033       ANIM_fcurve_keyframes_loop(&ked, ale->key_data, ok_cb, select_cb, NULL);
1034     }
1035   }
1036 
1037   /* free elements */
1038   BLI_freelistN(&ked.list);
1039   ANIM_animdata_freelist(&anim_data);
1040 }
1041 
1042 /* ------------------- */
1043 
graphkeys_columnselect_exec(bContext * C,wmOperator * op)1044 static int graphkeys_columnselect_exec(bContext *C, wmOperator *op)
1045 {
1046   bAnimContext ac;
1047   short mode;
1048 
1049   /* get editor data */
1050   if (ANIM_animdata_get_context(C, &ac) == 0) {
1051     return OPERATOR_CANCELLED;
1052   }
1053 
1054   /* action to take depends on the mode */
1055   mode = RNA_enum_get(op->ptr, "mode");
1056 
1057   if (mode == GRAPHKEYS_COLUMNSEL_MARKERS_BETWEEN) {
1058     markers_selectkeys_between(&ac);
1059   }
1060   else {
1061     columnselect_graph_keys(&ac, mode);
1062   }
1063 
1064   /* set notifier that keyframe selection has changed */
1065   WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_SELECTED, NULL);
1066 
1067   return OPERATOR_FINISHED;
1068 }
1069 
GRAPH_OT_select_column(wmOperatorType * ot)1070 void GRAPH_OT_select_column(wmOperatorType *ot)
1071 {
1072   /* identifiers */
1073   ot->name = "Select All";
1074   ot->idname = "GRAPH_OT_select_column";
1075   ot->description = "Select all keyframes on the specified frame(s)";
1076 
1077   /* api callbacks */
1078   ot->exec = graphkeys_columnselect_exec;
1079   ot->poll = graphop_visible_keyframes_poll;
1080 
1081   /* flags */
1082   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1083 
1084   /* props */
1085   ot->prop = RNA_def_enum(ot->srna, "mode", prop_column_select_types, 0, "Mode", "");
1086 }
1087 
1088 /* ******************** Select Linked Operator *********************** */
1089 
graphkeys_select_linked_exec(bContext * C,wmOperator * UNUSED (op))1090 static int graphkeys_select_linked_exec(bContext *C, wmOperator *UNUSED(op))
1091 {
1092   bAnimContext ac;
1093 
1094   ListBase anim_data = {NULL, NULL};
1095   bAnimListElem *ale;
1096   int filter;
1097 
1098   KeyframeEditFunc ok_cb = ANIM_editkeyframes_ok(BEZT_OK_SELECTED);
1099   KeyframeEditFunc sel_cb = ANIM_editkeyframes_select(SELECT_ADD);
1100 
1101   /* get editor data */
1102   if (ANIM_animdata_get_context(C, &ac) == 0) {
1103     return OPERATOR_CANCELLED;
1104   }
1105 
1106   /* loop through all of the keys and select additional keyframes based on these */
1107   filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS);
1108   ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
1109 
1110   for (ale = anim_data.first; ale; ale = ale->next) {
1111     FCurve *fcu = (FCurve *)ale->key_data;
1112 
1113     /* check if anything selected? */
1114     if (ANIM_fcurve_keyframes_loop(NULL, fcu, NULL, ok_cb, NULL)) {
1115       /* select every keyframe in this curve then */
1116       ANIM_fcurve_keyframes_loop(NULL, fcu, NULL, sel_cb, NULL);
1117     }
1118   }
1119 
1120   /* Cleanup */
1121   ANIM_animdata_freelist(&anim_data);
1122 
1123   /* set notifier that keyframe selection has changed */
1124   WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_SELECTED, NULL);
1125 
1126   return OPERATOR_FINISHED;
1127 }
1128 
GRAPH_OT_select_linked(wmOperatorType * ot)1129 void GRAPH_OT_select_linked(wmOperatorType *ot)
1130 {
1131   /* identifiers */
1132   ot->name = "Select Linked";
1133   ot->idname = "GRAPH_OT_select_linked";
1134   ot->description = "Select keyframes occurring in the same F-Curves as selected ones";
1135 
1136   /* api callbacks */
1137   ot->exec = graphkeys_select_linked_exec;
1138   ot->poll = graphop_visible_keyframes_poll;
1139 
1140   /* flags */
1141   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1142 }
1143 
1144 /* ******************** Select More/Less Operators *********************** */
1145 
1146 /* Common code to perform selection */
select_moreless_graph_keys(bAnimContext * ac,short mode)1147 static void select_moreless_graph_keys(bAnimContext *ac, short mode)
1148 {
1149   ListBase anim_data = {NULL, NULL};
1150   bAnimListElem *ale;
1151   int filter;
1152 
1153   KeyframeEditData ked;
1154   KeyframeEditFunc build_cb;
1155 
1156   /* init selmap building data */
1157   build_cb = ANIM_editkeyframes_buildselmap(mode);
1158   memset(&ked, 0, sizeof(KeyframeEditData));
1159 
1160   /* loop through all of the keys and select additional keyframes based on these */
1161   filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS);
1162   ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
1163 
1164   for (ale = anim_data.first; ale; ale = ale->next) {
1165     FCurve *fcu = (FCurve *)ale->key_data;
1166 
1167     /* only continue if F-Curve has keyframes */
1168     if (fcu->bezt == NULL) {
1169       continue;
1170     }
1171 
1172     /* build up map of whether F-Curve's keyframes should be selected or not */
1173     ked.data = MEM_callocN(fcu->totvert, "selmap graphEdit");
1174     ANIM_fcurve_keyframes_loop(&ked, fcu, NULL, build_cb, NULL);
1175 
1176     /* based on this map, adjust the selection status of the keyframes */
1177     ANIM_fcurve_keyframes_loop(&ked, fcu, NULL, bezt_selmap_flush, NULL);
1178 
1179     /* free the selmap used here */
1180     MEM_freeN(ked.data);
1181     ked.data = NULL;
1182   }
1183 
1184   /* Cleanup */
1185   ANIM_animdata_freelist(&anim_data);
1186 }
1187 
1188 /* ----------------- */
1189 
graphkeys_select_more_exec(bContext * C,wmOperator * UNUSED (op))1190 static int graphkeys_select_more_exec(bContext *C, wmOperator *UNUSED(op))
1191 {
1192   bAnimContext ac;
1193 
1194   /* get editor data */
1195   if (ANIM_animdata_get_context(C, &ac) == 0) {
1196     return OPERATOR_CANCELLED;
1197   }
1198 
1199   /* perform select changes */
1200   select_moreless_graph_keys(&ac, SELMAP_MORE);
1201 
1202   /* set notifier that keyframe selection has changed */
1203   WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_SELECTED, NULL);
1204 
1205   return OPERATOR_FINISHED;
1206 }
1207 
GRAPH_OT_select_more(wmOperatorType * ot)1208 void GRAPH_OT_select_more(wmOperatorType *ot)
1209 {
1210   /* identifiers */
1211   ot->name = "Select More";
1212   ot->idname = "GRAPH_OT_select_more";
1213   ot->description = "Select keyframes beside already selected ones";
1214 
1215   /* api callbacks */
1216   ot->exec = graphkeys_select_more_exec;
1217   ot->poll = graphop_visible_keyframes_poll;
1218 
1219   /* flags */
1220   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1221 }
1222 
1223 /* ----------------- */
1224 
graphkeys_select_less_exec(bContext * C,wmOperator * UNUSED (op))1225 static int graphkeys_select_less_exec(bContext *C, wmOperator *UNUSED(op))
1226 {
1227   bAnimContext ac;
1228 
1229   /* get editor data */
1230   if (ANIM_animdata_get_context(C, &ac) == 0) {
1231     return OPERATOR_CANCELLED;
1232   }
1233 
1234   /* perform select changes */
1235   select_moreless_graph_keys(&ac, SELMAP_LESS);
1236 
1237   /* set notifier that keyframe selection has changed */
1238   WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_SELECTED, NULL);
1239 
1240   return OPERATOR_FINISHED;
1241 }
1242 
GRAPH_OT_select_less(wmOperatorType * ot)1243 void GRAPH_OT_select_less(wmOperatorType *ot)
1244 {
1245   /* identifiers */
1246   ot->name = "Select Less";
1247   ot->idname = "GRAPH_OT_select_less";
1248   ot->description = "Deselect keyframes on ends of selection islands";
1249 
1250   /* api callbacks */
1251   ot->exec = graphkeys_select_less_exec;
1252   ot->poll = graphop_visible_keyframes_poll;
1253 
1254   /* flags */
1255   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1256 }
1257 
1258 /* ******************** Select Left/Right Operator ************************* */
1259 /* Select keyframes left/right of the current frame indicator */
1260 
1261 /* defines for left-right select tool */
1262 static const EnumPropertyItem prop_graphkeys_leftright_select_types[] = {
1263     {GRAPHKEYS_LRSEL_TEST, "CHECK", 0, "Check if Select Left or Right", ""},
1264     {GRAPHKEYS_LRSEL_LEFT, "LEFT", 0, "Before current frame", ""},
1265     {GRAPHKEYS_LRSEL_RIGHT, "RIGHT", 0, "After current frame", ""},
1266     {0, NULL, 0, NULL, NULL},
1267 };
1268 
1269 /* --------------------------------- */
1270 
graphkeys_select_leftright(bAnimContext * ac,short leftright,short select_mode)1271 static void graphkeys_select_leftright(bAnimContext *ac, short leftright, short select_mode)
1272 {
1273   ListBase anim_data = {NULL, NULL};
1274   bAnimListElem *ale;
1275   int filter;
1276 
1277   KeyframeEditFunc ok_cb, select_cb;
1278   KeyframeEditData ked = {{NULL}};
1279   Scene *scene = ac->scene;
1280 
1281   /* if select mode is replace, deselect all keyframes (and channels) first */
1282   if (select_mode == SELECT_REPLACE) {
1283     select_mode = SELECT_ADD;
1284 
1285     /* - deselect all other keyframes, so that just the newly selected remain
1286      * - channels aren't deselected, since we don't re-select any as a consequence
1287      */
1288     deselect_graph_keys(ac, 0, SELECT_SUBTRACT, false);
1289   }
1290 
1291   /* set callbacks and editing data */
1292   ok_cb = ANIM_editkeyframes_ok(BEZT_OK_FRAMERANGE);
1293   select_cb = ANIM_editkeyframes_select(select_mode);
1294 
1295   if (leftright == GRAPHKEYS_LRSEL_LEFT) {
1296     ked.f1 = MINAFRAMEF;
1297     ked.f2 = (float)(CFRA + 0.1f);
1298   }
1299   else {
1300     ked.f1 = (float)(CFRA - 0.1f);
1301     ked.f2 = MAXFRAMEF;
1302   }
1303 
1304   /* filter data */
1305   filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_NODUPLIS);
1306   ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
1307 
1308   /* select keys */
1309   for (ale = anim_data.first; ale; ale = ale->next) {
1310     AnimData *adt = ANIM_nla_mapping_get(ac, ale);
1311 
1312     if (adt) {
1313       ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 1);
1314       ANIM_fcurve_keyframes_loop(&ked, ale->key_data, ok_cb, select_cb, NULL);
1315       ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 1);
1316     }
1317     else {
1318       ANIM_fcurve_keyframes_loop(&ked, ale->key_data, ok_cb, select_cb, NULL);
1319     }
1320   }
1321 
1322   /* Cleanup */
1323   ANIM_animdata_freelist(&anim_data);
1324 }
1325 
1326 /* ----------------- */
1327 
graphkeys_select_leftright_exec(bContext * C,wmOperator * op)1328 static int graphkeys_select_leftright_exec(bContext *C, wmOperator *op)
1329 {
1330   bAnimContext ac;
1331   short leftright = RNA_enum_get(op->ptr, "mode");
1332   short selectmode;
1333 
1334   /* get editor data */
1335   if (ANIM_animdata_get_context(C, &ac) == 0) {
1336     return OPERATOR_CANCELLED;
1337   }
1338 
1339   /* select mode is either replace (deselect all, then add) or add/extend */
1340   if (RNA_boolean_get(op->ptr, "extend")) {
1341     selectmode = SELECT_INVERT;
1342   }
1343   else {
1344     selectmode = SELECT_REPLACE;
1345   }
1346 
1347   /* if "test" mode is set, we don't have any info to set this with */
1348   if (leftright == GRAPHKEYS_LRSEL_TEST) {
1349     return OPERATOR_CANCELLED;
1350   }
1351 
1352   /* do the selecting now */
1353   graphkeys_select_leftright(&ac, leftright, selectmode);
1354 
1355   /* set notifier that keyframe selection (and channels too) have changed */
1356   WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_SELECTED, NULL);
1357   WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_SELECTED, NULL);
1358 
1359   return OPERATOR_FINISHED;
1360 }
1361 
graphkeys_select_leftright_invoke(bContext * C,wmOperator * op,const wmEvent * event)1362 static int graphkeys_select_leftright_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1363 {
1364   bAnimContext ac;
1365   short leftright = RNA_enum_get(op->ptr, "mode");
1366 
1367   /* get editor data */
1368   if (ANIM_animdata_get_context(C, &ac) == 0) {
1369     return OPERATOR_CANCELLED;
1370   }
1371 
1372   /* handle mode-based testing */
1373   if (leftright == GRAPHKEYS_LRSEL_TEST) {
1374     Scene *scene = ac.scene;
1375     ARegion *region = ac.region;
1376     View2D *v2d = &region->v2d;
1377     float x;
1378 
1379     /* determine which side of the current frame mouse is on */
1380     x = UI_view2d_region_to_view_x(v2d, event->mval[0]);
1381     if (x < CFRA) {
1382       RNA_enum_set(op->ptr, "mode", GRAPHKEYS_LRSEL_LEFT);
1383     }
1384     else {
1385       RNA_enum_set(op->ptr, "mode", GRAPHKEYS_LRSEL_RIGHT);
1386     }
1387   }
1388 
1389   /* perform selection */
1390   return graphkeys_select_leftright_exec(C, op);
1391 }
1392 
GRAPH_OT_select_leftright(wmOperatorType * ot)1393 void GRAPH_OT_select_leftright(wmOperatorType *ot)
1394 {
1395   PropertyRNA *prop;
1396 
1397   /* identifiers */
1398   ot->name = "Select Left/Right";
1399   ot->idname = "GRAPH_OT_select_leftright";
1400   ot->description = "Select keyframes to the left or the right of the current frame";
1401 
1402   /* api callbacks  */
1403   ot->invoke = graphkeys_select_leftright_invoke;
1404   ot->exec = graphkeys_select_leftright_exec;
1405   ot->poll = graphop_visible_keyframes_poll;
1406 
1407   /* flags */
1408   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1409 
1410   /* id-props */
1411   ot->prop = RNA_def_enum(
1412       ot->srna, "mode", prop_graphkeys_leftright_select_types, GRAPHKEYS_LRSEL_TEST, "Mode", "");
1413   RNA_def_property_flag(ot->prop, PROP_SKIP_SAVE);
1414 
1415   prop = RNA_def_boolean(ot->srna, "extend", 0, "Extend Select", "");
1416   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
1417 }
1418 
1419 /* ******************** Mouse-Click Select Operator *********************** */
1420 /* This operator works in one of three ways:
1421  * - 1) keyframe under mouse - no special modifiers
1422  * - 2) all keyframes on the same side of current frame indicator as mouse - ALT modifier
1423  * - 3) column select all keyframes in frame under mouse - CTRL modifier
1424  *
1425  * In addition to these basic options, the SHIFT modifier can be used to toggle the
1426  * selection mode between replacing the selection (without) and inverting the selection (with).
1427  */
1428 
1429 /* option 1) select keyframe directly under mouse */
mouse_graph_keys(bAnimContext * ac,const int mval[2],eEditKeyframes_Select select_mode,const bool deselect_all,const bool curves_only,bool wait_to_deselect_others)1430 static int mouse_graph_keys(bAnimContext *ac,
1431                             const int mval[2],
1432                             eEditKeyframes_Select select_mode,
1433                             const bool deselect_all,
1434                             const bool curves_only,
1435                             bool wait_to_deselect_others)
1436 {
1437   SpaceGraph *sipo = (SpaceGraph *)ac->sl;
1438   tNearestVertInfo *nvi;
1439   BezTriple *bezt = NULL;
1440   bool run_modal = false;
1441 
1442   /* find the beztriple that we're selecting, and the handle that was clicked on */
1443   nvi = find_nearest_fcurve_vert(ac, mval);
1444 
1445   if (select_mode != SELECT_REPLACE) {
1446     /* The modal execution to delay deselecting other items is only needed for normal click
1447      * selection, i.e. for SELECT_REPLACE. */
1448     wait_to_deselect_others = false;
1449   }
1450 
1451   sipo->runtime.flag &= ~(SIPO_RUNTIME_FLAG_TWEAK_HANDLES_LEFT |
1452                           SIPO_RUNTIME_FLAG_TWEAK_HANDLES_RIGHT);
1453 
1454   const bool already_selected =
1455       (nvi != NULL) && (((nvi->hpoint == NEAREST_HANDLE_KEY) && (nvi->bezt->f2 & SELECT)) ||
1456                         ((nvi->hpoint == NEAREST_HANDLE_LEFT) && (nvi->bezt->f1 & SELECT)) ||
1457                         ((nvi->hpoint == NEAREST_HANDLE_RIGHT) && (nvi->bezt->f3 & SELECT)));
1458 
1459   if (wait_to_deselect_others && nvi && already_selected) {
1460     run_modal = true;
1461   }
1462   /* For replacing selection, if we have something to select, we have to clear existing selection.
1463    * The same goes if we found nothing to select, and deselect_all is true
1464    * (deselect on nothing behavior). */
1465   else if ((nvi != NULL && select_mode == SELECT_REPLACE) || (nvi == NULL && deselect_all)) {
1466     /* reset selection mode */
1467     select_mode = SELECT_ADD;
1468 
1469     /* deselect all other keyframes (+ F-Curves too) */
1470     deselect_graph_keys(ac, 0, SELECT_SUBTRACT, true);
1471 
1472     /* deselect other channels too, but only only do this if
1473      * selection of channel when the visibility of keyframes
1474      * doesn't depend on this
1475      */
1476     if ((sipo->flag & SIPO_SELCUVERTSONLY) == 0) {
1477       ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_CLEAR);
1478     }
1479   }
1480 
1481   if (nvi == NULL) {
1482     return deselect_all ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
1483   }
1484 
1485   /* if points can be selected on this F-Curve */
1486   /* TODO: what about those with no keyframes? */
1487   bool something_was_selected = false;
1488   if (!curves_only && ((nvi->fcu->flag & FCURVE_PROTECTED) == 0)) {
1489     /* only if there's keyframe */
1490     if (nvi->bezt) {
1491       bezt = nvi->bezt; /* Used to check `bezt` selection is set. */
1492       if (select_mode == SELECT_INVERT) {
1493         if (nvi->hpoint == NEAREST_HANDLE_KEY) {
1494           bezt->f2 ^= SELECT;
1495           something_was_selected = (bezt->f2 & SELECT);
1496         }
1497         else if (nvi->hpoint == NEAREST_HANDLE_LEFT) {
1498           /* toggle selection */
1499           bezt->f1 ^= SELECT;
1500           something_was_selected = (bezt->f1 & SELECT);
1501         }
1502         else {
1503           /* toggle selection */
1504           bezt->f3 ^= SELECT;
1505           something_was_selected = (bezt->f3 & SELECT);
1506         }
1507       }
1508       else {
1509         if (nvi->hpoint == NEAREST_HANDLE_KEY) {
1510           bezt->f2 |= SELECT;
1511         }
1512         else if (nvi->hpoint == NEAREST_HANDLE_LEFT) {
1513           bezt->f1 |= SELECT;
1514         }
1515         else {
1516           bezt->f3 |= SELECT;
1517         }
1518         something_was_selected = true;
1519       }
1520 
1521       if (!run_modal && BEZT_ISSEL_ANY(bezt)) {
1522         const bool may_activate = !already_selected ||
1523                                   BKE_fcurve_active_keyframe_index(nvi->fcu) ==
1524                                       FCURVE_ACTIVE_KEYFRAME_NONE;
1525         if (may_activate) {
1526           BKE_fcurve_active_keyframe_set(nvi->fcu, bezt);
1527         }
1528       }
1529     }
1530     else if (nvi->fpt) {
1531       /* TODO: need to handle sample points */
1532     }
1533   }
1534   else {
1535     KeyframeEditFunc select_cb;
1536     KeyframeEditData ked;
1537 
1538     /* initialize keyframe editing data */
1539     memset(&ked, 0, sizeof(KeyframeEditData));
1540 
1541     /* set up BezTriple edit callbacks */
1542     select_cb = ANIM_editkeyframes_select(select_mode);
1543 
1544     /* select all keyframes */
1545     ANIM_fcurve_keyframes_loop(&ked, nvi->fcu, NULL, select_cb, NULL);
1546   }
1547 
1548   /* only change selection of channel when the visibility of keyframes doesn't depend on this */
1549   if ((sipo->flag & SIPO_SELCUVERTSONLY) == 0) {
1550     /* select or deselect curve? */
1551     if (bezt) {
1552       /* take selection status from item that got hit, to prevent flip/flop on channel
1553        * selection status when shift-selecting (i.e. "SELECT_INVERT") points
1554        */
1555       if (BEZT_ISSEL_ANY(bezt)) {
1556         nvi->fcu->flag |= FCURVE_SELECTED;
1557       }
1558       else {
1559         nvi->fcu->flag &= ~FCURVE_SELECTED;
1560       }
1561     }
1562     else {
1563       /* Didn't hit any channel,
1564        * so just apply that selection mode to the curve's selection status. */
1565       if (select_mode == SELECT_INVERT) {
1566         nvi->fcu->flag ^= FCURVE_SELECTED;
1567       }
1568       else if (select_mode == SELECT_ADD) {
1569         nvi->fcu->flag |= FCURVE_SELECTED;
1570       }
1571     }
1572   }
1573 
1574   /* Set active F-Curve when something was actually selected (so not on a deselect), except when
1575    * dragging the selected keys. Needs to be called with (sipo->flag & SIPO_SELCUVERTSONLY),
1576    * otherwise the active flag won't be set T26452. */
1577   if (!run_modal && (nvi->fcu->flag & FCURVE_SELECTED) && something_was_selected) {
1578     /* NOTE: Sync the filter flags with findnearest_fcurve_vert. */
1579     int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS);
1580     ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, nvi->fcu, nvi->ctype);
1581   }
1582 
1583   if (nvi->hpoint == NEAREST_HANDLE_LEFT) {
1584     sipo->runtime.flag |= SIPO_RUNTIME_FLAG_TWEAK_HANDLES_LEFT;
1585   }
1586   else if (nvi->hpoint == NEAREST_HANDLE_RIGHT) {
1587     sipo->runtime.flag |= SIPO_RUNTIME_FLAG_TWEAK_HANDLES_RIGHT;
1588   }
1589 
1590   /* free temp sample data for filtering */
1591   MEM_freeN(nvi);
1592 
1593   return run_modal ? OPERATOR_RUNNING_MODAL : OPERATOR_FINISHED;
1594 }
1595 
1596 /* Option 2) Selects all the keyframes on either side of the current frame
1597  * (depends on which side the mouse is on) */
1598 /* (see graphkeys_select_leftright) */
1599 
1600 /* Option 3) Selects all visible keyframes in the same frame as the mouse click */
graphkeys_mselect_column(bAnimContext * ac,const int mval[2],eEditKeyframes_Select select_mode,bool wait_to_deselect_others)1601 static int graphkeys_mselect_column(bAnimContext *ac,
1602                                     const int mval[2],
1603                                     eEditKeyframes_Select select_mode,
1604                                     bool wait_to_deselect_others)
1605 {
1606   ListBase anim_data = {NULL, NULL};
1607   bAnimListElem *ale;
1608   int filter;
1609   bool run_modal = false;
1610 
1611   KeyframeEditFunc select_cb, ok_cb;
1612   KeyframeEditData ked;
1613   tNearestVertInfo *nvi;
1614   float selx = (float)ac->scene->r.cfra;
1615 
1616   /* find the beztriple that we're selecting, and the handle that was clicked on */
1617   nvi = find_nearest_fcurve_vert(ac, mval);
1618 
1619   /* check if anything to select */
1620   if (nvi == NULL) {
1621     return OPERATOR_CANCELLED;
1622   }
1623 
1624   /* get frame number on which elements should be selected */
1625   /* TODO: should we restrict to integer frames only? */
1626   selx = nvi->frame;
1627 
1628   if (select_mode != SELECT_REPLACE) {
1629     /* Doesn't need to deselect anything -> Pass. */
1630   }
1631   else if (wait_to_deselect_others && (nvi->bezt->f2 & SELECT)) {
1632     run_modal = true;
1633   }
1634   /* If select mode is replace (and we don't do delayed deselection on mouse release), deselect all
1635    * keyframes first. */
1636   else {
1637     /* reset selection mode to add to selection */
1638     select_mode = SELECT_ADD;
1639 
1640     /* - deselect all other keyframes, so that just the newly selected remain
1641      * - channels aren't deselected, since we don't re-select any as a consequence
1642      */
1643     deselect_graph_keys(ac, 0, SELECT_SUBTRACT, false);
1644   }
1645 
1646   /* initialize keyframe editing data */
1647   memset(&ked, 0, sizeof(KeyframeEditData));
1648 
1649   /* set up BezTriple edit callbacks */
1650   select_cb = ANIM_editkeyframes_select(select_mode);
1651   ok_cb = ANIM_editkeyframes_ok(BEZT_OK_FRAME);
1652 
1653   /* loop through all of the keys and select additional keyframes
1654    * based on the keys found to be selected above
1655    */
1656   filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS);
1657   ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
1658 
1659   for (ale = anim_data.first; ale; ale = ale->next) {
1660     AnimData *adt = ANIM_nla_mapping_get(ac, ale);
1661 
1662     /* set frame for validation callback to refer to */
1663     if (adt) {
1664       ked.f1 = BKE_nla_tweakedit_remap(adt, selx, NLATIME_CONVERT_UNMAP);
1665     }
1666     else {
1667       ked.f1 = selx;
1668     }
1669 
1670     /* select elements with frame number matching cfra */
1671     ANIM_fcurve_keyframes_loop(&ked, ale->key_data, ok_cb, select_cb, NULL);
1672   }
1673 
1674   /* free elements */
1675   MEM_freeN(nvi);
1676   BLI_freelistN(&ked.list);
1677   ANIM_animdata_freelist(&anim_data);
1678 
1679   return run_modal ? OPERATOR_RUNNING_MODAL : OPERATOR_FINISHED;
1680 }
1681 
1682 /* ------------------- */
1683 
1684 /* handle clicking */
graphkeys_clickselect_exec(bContext * C,wmOperator * op)1685 static int graphkeys_clickselect_exec(bContext *C, wmOperator *op)
1686 {
1687   bAnimContext ac;
1688 
1689   /* get editor data */
1690   if (ANIM_animdata_get_context(C, &ac) == 0) {
1691     return OPERATOR_CANCELLED;
1692   }
1693 
1694   /* select mode is either replace (deselect all, then add) or add/extend */
1695   const short selectmode = RNA_boolean_get(op->ptr, "extend") ? SELECT_INVERT : SELECT_REPLACE;
1696   const bool deselect_all = RNA_boolean_get(op->ptr, "deselect_all");
1697   /* See #WM_operator_properties_generic_select() for a detailed description of the how and why of
1698    * this. */
1699   const bool wait_to_deselect_others = RNA_boolean_get(op->ptr, "wait_to_deselect_others");
1700   int mval[2];
1701   int ret_val;
1702 
1703   mval[0] = RNA_int_get(op->ptr, "mouse_x");
1704   mval[1] = RNA_int_get(op->ptr, "mouse_y");
1705 
1706   /* figure out action to take */
1707   if (RNA_boolean_get(op->ptr, "column")) {
1708     /* select all keyframes in the same frame as the one that was under the mouse */
1709     ret_val = graphkeys_mselect_column(&ac, mval, selectmode, wait_to_deselect_others);
1710   }
1711   else if (RNA_boolean_get(op->ptr, "curves")) {
1712     /* select all keyframes in the same F-Curve as the one under the mouse */
1713     ret_val = mouse_graph_keys(&ac, mval, selectmode, deselect_all, true, wait_to_deselect_others);
1714   }
1715   else {
1716     /* select keyframe under mouse */
1717     ret_val = mouse_graph_keys(
1718         &ac, mval, selectmode, deselect_all, false, wait_to_deselect_others);
1719   }
1720 
1721   /* set notifier that keyframe selection (and also channel selection in some cases) has
1722    * changed */
1723   WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_SELECTED, NULL);
1724   WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_SELECTED, NULL);
1725 
1726   /* for tweak grab to work */
1727   return ret_val | OPERATOR_PASS_THROUGH;
1728 }
1729 
GRAPH_OT_clickselect(wmOperatorType * ot)1730 void GRAPH_OT_clickselect(wmOperatorType *ot)
1731 {
1732   PropertyRNA *prop;
1733 
1734   /* identifiers */
1735   ot->name = "Select Keyframes";
1736   ot->idname = "GRAPH_OT_clickselect";
1737   ot->description = "Select keyframes by clicking on them";
1738 
1739   /* callbacks */
1740   ot->poll = graphop_visible_keyframes_poll;
1741   ot->exec = graphkeys_clickselect_exec;
1742   ot->invoke = WM_generic_select_invoke;
1743   ot->modal = WM_generic_select_modal;
1744 
1745   /* flags */
1746   ot->flag = OPTYPE_UNDO;
1747 
1748   /* properties */
1749   WM_operator_properties_generic_select(ot);
1750   prop = RNA_def_boolean(ot->srna,
1751                          "extend",
1752                          0,
1753                          "Extend Select",
1754                          "Toggle keyframe selection instead of leaving newly selected "
1755                          "keyframes only"); /* SHIFTKEY */
1756   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
1757 
1758   prop = RNA_def_boolean(ot->srna,
1759                          "deselect_all",
1760                          false,
1761                          "Deselect On Nothing",
1762                          "Deselect all when nothing under the cursor");
1763   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
1764 
1765   prop = RNA_def_boolean(ot->srna,
1766                          "column",
1767                          0,
1768                          "Column Select",
1769                          "Select all keyframes that occur on the same frame as the one under "
1770                          "the mouse"); /* ALTKEY */
1771   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
1772 
1773   prop = RNA_def_boolean(ot->srna,
1774                          "curves",
1775                          0,
1776                          "Only Curves",
1777                          "Select all the keyframes in the curve"); /* CTRLKEY + ALTKEY */
1778   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
1779 }
1780 
1781 /* ************************************************************************** */
1782