Merge branch 'master' into blender2.8
[blender.git] / source / blender / editors / animation / anim_channels_edit.c
index 85a4fd49b73d34f822fdcd4fd86659b5684e83f4..20e5bec44a37b188e19d4c9aed57ee5b60b59849 100644 (file)
@@ -34,6 +34,7 @@
 
 #include "MEM_guardedalloc.h"
 
+#include "BKE_depsgraph.h"
 #include "BLI_blenlib.h"
 #include "BLI_utildefines.h"
 #include "BLI_listbase.h"
@@ -119,10 +120,10 @@ void ANIM_set_active_channel(bAnimContext *ac, void *data, eAnimCont_Types datat
                        case ANIMTYPE_DSMAT:    /* Datablock AnimData Expanders */
                        case ANIMTYPE_DSLAM:
                        case ANIMTYPE_DSCAM:
+                       case ANIMTYPE_DSCACHEFILE:
                        case ANIMTYPE_DSCUR:
                        case ANIMTYPE_DSSKEY:
                        case ANIMTYPE_DSWOR:
-                       case ANIMTYPE_DSPART:
                        case ANIMTYPE_DSMBALL:
                        case ANIMTYPE_DSARM:
                        case ANIMTYPE_DSMESH:
@@ -174,10 +175,10 @@ void ANIM_set_active_channel(bAnimContext *ac, void *data, eAnimCont_Types datat
                        case ANIMTYPE_DSMAT:    /* Datablock AnimData Expanders */
                        case ANIMTYPE_DSLAM:
                        case ANIMTYPE_DSCAM:
+                       case ANIMTYPE_DSCACHEFILE:
                        case ANIMTYPE_DSCUR:
                        case ANIMTYPE_DSSKEY:
                        case ANIMTYPE_DSWOR:
-                       case ANIMTYPE_DSPART:
                        case ANIMTYPE_DSMBALL:
                        case ANIMTYPE_DSARM:
                        case ANIMTYPE_DSMESH:
@@ -274,10 +275,10 @@ void ANIM_deselect_anim_channels(bAnimContext *ac, void *data, eAnimCont_Types d
                                case ANIMTYPE_DSMAT:    /* Datablock AnimData Expanders */
                                case ANIMTYPE_DSLAM:
                                case ANIMTYPE_DSCAM:
+                               case ANIMTYPE_DSCACHEFILE:
                                case ANIMTYPE_DSCUR:
                                case ANIMTYPE_DSSKEY:
                                case ANIMTYPE_DSWOR:
-                               case ANIMTYPE_DSPART:
                                case ANIMTYPE_DSMBALL:
                                case ANIMTYPE_DSARM:
                                case ANIMTYPE_DSMESH:
@@ -369,10 +370,10 @@ void ANIM_deselect_anim_channels(bAnimContext *ac, void *data, eAnimCont_Types d
                        case ANIMTYPE_DSMAT:    /* Datablock AnimData Expanders */
                        case ANIMTYPE_DSLAM:
                        case ANIMTYPE_DSCAM:
+                       case ANIMTYPE_DSCACHEFILE:
                        case ANIMTYPE_DSCUR:
                        case ANIMTYPE_DSSKEY:
                        case ANIMTYPE_DSWOR:
-                       case ANIMTYPE_DSPART:
                        case ANIMTYPE_DSMBALL:
                        case ANIMTYPE_DSARM:
                        case ANIMTYPE_DSMESH:
@@ -448,7 +449,7 @@ void ANIM_flush_setting_anim_channels(bAnimContext *ac, ListBase *anim_data, bAn
                return;
        }
        else {
-               bAnimChannelType *acf = ANIM_channel_get_typeinfo(ale_setting);
+               const bAnimChannelType *acf = ANIM_channel_get_typeinfo(ale_setting);
                
                if (acf == NULL) {
                        printf("ERROR: no channel info for the changed channel\n");
@@ -477,7 +478,7 @@ void ANIM_flush_setting_anim_channels(bAnimContext *ac, ListBase *anim_data, bAn
        {
                /* go backwards in the list, until the highest-ranking element (by indention has been covered) */
                for (ale = match->prev; ale; ale = ale->prev) {
-                       bAnimChannelType *acf = ANIM_channel_get_typeinfo(ale);
+                       const bAnimChannelType *acf = ANIM_channel_get_typeinfo(ale);
                        int level;
                        
                        /* if no channel info was found, skip, since this type might not have any useful info */
@@ -521,7 +522,7 @@ void ANIM_flush_setting_anim_channels(bAnimContext *ac, ListBase *anim_data, bAn
        {
                /* go forwards in the list, until the lowest-ranking element (by indention has been covered) */
                for (ale = match->next; ale; ale = ale->next) {
-                       bAnimChannelType *acf = ANIM_channel_get_typeinfo(ale);
+                       const bAnimChannelType *acf = ANIM_channel_get_typeinfo(ale);
                        int level;
                        
                        /* if no channel info was found, skip, since this type might not have any useful info */
@@ -851,6 +852,7 @@ static void rearrange_animchannel_add_to_islands(ListBase *islands, ListBase *sr
                        break;
                }
                case ANIMTYPE_FCURVE:
+               case ANIMTYPE_NLACURVE:
                {
                        FCurve *fcu = (FCurve *)channel;
                        
@@ -872,7 +874,7 @@ static void rearrange_animchannel_add_to_islands(ListBase *islands, ListBase *sr
                        break;
                }
                default:
-                       printf("rearrange_animchannel_add_to_islands(): don't know how to handle channels of type %d\n", type);
+                       printf("rearrange_animchannel_add_to_islands(): don't know how to handle channels of type %u\n", type);
                        return;
        }
        
@@ -1195,6 +1197,40 @@ static void rearrange_action_channels(bAnimContext *ac, bAction *act, eRearrange
 
 /* ------------------- */
 
+static void rearrange_nla_control_channels(bAnimContext *ac, AnimData *adt, eRearrangeAnimChan_Mode mode)
+{
+       ListBase anim_data_visible = {NULL, NULL};
+       
+       NlaTrack *nlt;
+       NlaStrip *strip;
+       
+       /* get rearranging function */
+       AnimChanRearrangeFp rearrange_func = rearrange_get_mode_func(mode);
+       
+       if (rearrange_func == NULL)
+               return;
+               
+       /* skip if these curves aren't being shown */
+       if (adt->flag & ADT_NLA_SKEYS_COLLAPSED)
+               return;
+       
+       /* Filter visible data. */
+       rearrange_animchannels_filter_visible(&anim_data_visible, ac, ANIMTYPE_NLACURVE);
+       
+       /* we cannot rearrange between strips, but within each strip, we can rearrange those curves */
+       for (nlt = adt->nla_tracks.first; nlt; nlt = nlt->next) {
+               for (strip = nlt->strips.first; strip; strip = strip->next) {
+                       rearrange_animchannel_islands(&strip->fcurves, rearrange_func, mode, ANIMTYPE_NLACURVE,
+                                                         &anim_data_visible);
+               }
+       }
+       
+       /* free temp data */
+       BLI_freelistN(&anim_data_visible);
+}
+
+/* ------------------- */
+
 static void rearrange_gpencil_channels(bAnimContext *ac, eRearrangeAnimChan_Mode mode)
 {
        ListBase anim_data = {NULL, NULL};
@@ -1282,13 +1318,29 @@ static int animchannels_rearrange_exec(bContext *C, wmOperator *op)
                                        rearrange_driver_channels(&ac, adt, mode);
                                        break;
                                
+                               case ANIMCONT_ACTION: /* Single Action only... */
                                case ANIMCONT_SHAPEKEY: // DOUBLE CHECK ME...
-                               default: /* some collection of actions */
+                               {
+                                       if (adt->action)
+                                               rearrange_action_channels(&ac, adt->action, mode);
+                                       else if (G.debug & G_DEBUG)
+                                               printf("Animdata has no action\n");
+                                       break;
+                               }
+                               
+                               default: /* DopeSheet/Graph Editor - Some Actions + NLA Control Curves */
+                               {
+                                       /* NLA Control Curves */
+                                       if (adt->nla_tracks.first)
+                                               rearrange_nla_control_channels(&ac, adt, mode);
+                                       
+                                       /* Action */
                                        if (adt->action)
                                                rearrange_action_channels(&ac, adt->action, mode);
                                        else if (G.debug & G_DEBUG)
                                                printf("Animdata has no action\n");
                                        break;
+                               }
                        }
                }
                
@@ -1344,9 +1396,9 @@ static int animchannels_grouping_poll(bContext *C)
                        /* dopesheet and action only - all others are for other datatypes or have no groups */
                        if (ELEM(saction->mode, SACTCONT_ACTION, SACTCONT_DOPESHEET) == 0)
                                return 0;
-               }
+
                        break;
-                       
+               }
                case SPACE_IPO:
                {
                        SpaceIpo *sipo = (SpaceIpo *)sl;
@@ -1354,9 +1406,9 @@ static int animchannels_grouping_poll(bContext *C)
                        /* drivers can't have groups... */
                        if (sipo->mode != SIPO_MODE_ANIMATION)
                                return 0;
-               }
+
                        break;
-                       
+               }
                /* unsupported... */
                default:
                        return 0;
@@ -1606,6 +1658,27 @@ static int animchannels_delete_exec(bContext *C, wmOperator *UNUSED(op))
                                ANIM_fcurve_delete_from_animdata(&ac, adt, fcu);
                                break;
                        }
+                       case ANIMTYPE_NLACURVE:
+                       {
+                               /* NLA Control Curve - Deleting it should disable the corresponding setting... */
+                               NlaStrip *strip = (NlaStrip *)ale->owner;
+                               FCurve *fcu = (FCurve *)ale->data;
+                               
+                               if (STREQ(fcu->rna_path, "strip_time")) {
+                                       strip->flag &= ~NLASTRIP_FLAG_USR_TIME;
+                               }
+                               else if (STREQ(fcu->rna_path, "influence")) {
+                                       strip->flag &= ~NLASTRIP_FLAG_USR_INFLUENCE;
+                               }
+                               else {
+                                       printf("ERROR: Trying to delete NLA Control Curve for unknown property '%s'\n", fcu->rna_path);
+                               }
+                               
+                               /* unlink and free the F-Curve */
+                               BLI_remlink(&strip->fcurves, fcu);
+                               free_fcurve(fcu);
+                               break;
+                       }
                        case ANIMTYPE_GPLAYER:
                        {
                                /* Grease Pencil layer */
@@ -1613,7 +1686,7 @@ static int animchannels_delete_exec(bContext *C, wmOperator *UNUSED(op))
                                bGPDlayer *gpl = (bGPDlayer *)ale->data;
                                
                                /* try to delete the layer's data and the layer itself */
-                               free_gpencil_frames(gpl);
+                               BKE_gpencil_free_frames(gpl);
                                BLI_freelinkN(&gpd->layers, gpl);
                                break;
                        }
@@ -1635,7 +1708,8 @@ static int animchannels_delete_exec(bContext *C, wmOperator *UNUSED(op))
        
        /* send notifier that things have changed */
        WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_EDITED, NULL);
-       
+       DAG_relations_tag_update(CTX_data_main(C));
+
        return OPERATOR_FINISHED;
 }
  
@@ -2045,7 +2119,7 @@ static int animchannels_clean_empty_exec(bContext *C, wmOperator *UNUSED(op))
                
                /* remove AnimData? */
                if (action_empty && nla_empty && drivers_empty) {
-                       BKE_free_animdata(id);
+                       BKE_animdata_free(id, true);
                }
        }
        
@@ -2282,7 +2356,7 @@ static void borderselect_anim_channels(bAnimContext *ac, rcti *rect, short selec
        }
        else {
                ymin = 0.0f;
-               ymax = (float)(-ACHANNEL_HEIGHT);
+               ymax = (float)(-ACHANNEL_HEIGHT(ac));
        }
        
        /* convert border-region to view coordinates */
@@ -2298,7 +2372,7 @@ static void borderselect_anim_channels(bAnimContext *ac, rcti *rect, short selec
                if (ac->datatype == ANIMCONT_NLA)
                        ymin = ymax - NLACHANNEL_STEP(snla);
                else
-                       ymin = ymax - ACHANNEL_STEP;
+                       ymin = ymax - ACHANNEL_STEP(ac);
                
                /* if channel is within border-select region, alter it */
                if (!((ymax < rectf.ymin) || (ymin > rectf.ymax))) {
@@ -2424,12 +2498,13 @@ static void ANIM_OT_channels_select_border(wmOperatorType *ot)
 /* ******************* Rename Operator ***************************** */
 /* Allow renaming some channels by clicking on them */
 
-static void rename_anim_channels(bAnimContext *ac, int channel_index)
+static bool rename_anim_channels(bAnimContext *ac, int channel_index)
 {
        ListBase anim_data = {NULL, NULL};
-       bAnimChannelType *acf;
+       const bAnimChannelType *acf;
        bAnimListElem *ale;
        int filter;
+       bool success = false;
        
        /* get the channel that was clicked on */
        /* filter channels */
@@ -2444,7 +2519,7 @@ static void rename_anim_channels(bAnimContext *ac, int channel_index)
                        printf("Error: animation channel (index = %d) not found in rename_anim_channels()\n", channel_index);
                
                ANIM_animdata_freelist(&anim_data);
-               return;
+               return false;
        }
        
        /* check that channel can be renamed */
@@ -2464,6 +2539,7 @@ static void rename_anim_channels(bAnimContext *ac, int channel_index)
                         */
                        if (ac->ads) {
                                ac->ads->renameIndex = channel_index + 1;
+                               success = true;
                        }
                }
        }
@@ -2471,22 +2547,18 @@ static void rename_anim_channels(bAnimContext *ac, int channel_index)
        /* free temp data and tag for refresh */
        ANIM_animdata_freelist(&anim_data);
        ED_region_tag_redraw(ac->ar);
+       return success;
 }
 
-static int animchannels_rename_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
+static int animchannels_channel_get(bAnimContext *ac, const int mval[2])
 {
-       bAnimContext ac;
        ARegion *ar;
        View2D *v2d;
        int channel_index;
        float x, y;
        
-       /* get editor data */
-       if (ANIM_animdata_get_context(C, &ac) == 0)
-               return OPERATOR_CANCELLED;
-               
        /* get useful pointers from animation context data */
-       ar = ac.ar;
+       ar = ac->ar;
        v2d = &ar->v2d;
        
        /* figure out which channel user clicked in 
@@ -2494,20 +2566,36 @@ static int animchannels_rename_invoke(bContext *C, wmOperator *UNUSED(op), const
         *              so that the tops of channels get caught ok. Since ACHANNEL_FIRST is really ACHANNEL_HEIGHT, we simply use
         *              ACHANNEL_HEIGHT_HALF.
         */
-       UI_view2d_region_to_view(v2d, event->mval[0], event->mval[1], &x, &y);
+       UI_view2d_region_to_view(v2d, mval[0], mval[1], &x, &y);
        
-       if (ac.datatype == ANIMCONT_NLA) {
-               SpaceNla *snla = (SpaceNla *)ac.sl;
+       if (ac->datatype == ANIMCONT_NLA) {
+               SpaceNla *snla = (SpaceNla *)ac->sl;
                UI_view2d_listview_view_to_cell(v2d, NLACHANNEL_NAMEWIDTH, NLACHANNEL_STEP(snla), 0, (float)NLACHANNEL_HEIGHT_HALF(snla), x, y, NULL, &channel_index);
        }
        else {
-               UI_view2d_listview_view_to_cell(v2d, ACHANNEL_NAMEWIDTH, ACHANNEL_STEP, 0, (float)ACHANNEL_HEIGHT_HALF, x, y, NULL, &channel_index);
+               UI_view2d_listview_view_to_cell(v2d, ACHANNEL_NAMEWIDTH, ACHANNEL_STEP(ac), 0, (float)ACHANNEL_HEIGHT_HALF(ac), x, y, NULL, &channel_index);
        }
-       
+
+       return channel_index;
+}
+
+static int animchannels_rename_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
+{
+       bAnimContext ac;
+       int channel_index;
+
+       /* get editor data */
+       if (ANIM_animdata_get_context(C, &ac) == 0)
+               return OPERATOR_CANCELLED;
+
+       channel_index = animchannels_channel_get(&ac, event->mval);
+
        /* handle click */
-       rename_anim_channels(&ac, channel_index);
-       
-       return OPERATOR_FINISHED;
+       if (rename_anim_channels(&ac, channel_index))
+               return OPERATOR_FINISHED;
+       else
+               /* allow event to be handled by selectall operator */
+               return OPERATOR_PASS_THROUGH;
 }
 
 static void ANIM_OT_channels_rename(wmOperatorType *ot)
@@ -2617,6 +2705,10 @@ static int mouse_anim_channels(bContext *C, bAnimContext *ac, int channel_index,
                        if ((adt) && (adt->flag & ADT_UI_SELECTED))
                                adt->flag |= ADT_UI_ACTIVE;
                        
+                       /* ensure we exit editmode on whatever object was active before to avoid getting stuck there - T48747 */
+                       if (ob != sce->obedit)
+                               ED_object_editmode_exit(C, EM_FREEDATA | EM_FREEUNDO | EM_WAITCURSOR | EM_DO_UNDO);
+                       
                        notifierFlags |= (ND_ANIMCHAN | NA_SELECTED);
                        break;
                }
@@ -2624,10 +2716,10 @@ static int mouse_anim_channels(bContext *C, bAnimContext *ac, int channel_index,
                case ANIMTYPE_DSMAT:    /* Datablock AnimData Expanders */
                case ANIMTYPE_DSLAM:
                case ANIMTYPE_DSCAM:
+               case ANIMTYPE_DSCACHEFILE:
                case ANIMTYPE_DSCUR:
                case ANIMTYPE_DSSKEY:
                case ANIMTYPE_DSWOR:
-               case ANIMTYPE_DSPART:
                case ANIMTYPE_DSMBALL:
                case ANIMTYPE_DSARM:
                case ANIMTYPE_DSMESH:
@@ -2772,6 +2864,19 @@ static int mouse_anim_channels(bContext *C, bAnimContext *ac, int channel_index,
                        notifierFlags |= (ND_ANIMCHAN | NA_SELECTED);
                        break;
                }
+               case ANIMTYPE_NLACONTROLS:
+               {
+                       AnimData *adt = (AnimData *)ale->data;
+                       
+                       /* toggle expand
+                        *   - Although the triangle widget already allows this, since there's nothing else that can be done here now,
+                        *     let's just use it for easier expand/collapse for now
+                        */
+                       adt->flag ^= ADT_NLA_SKEYS_COLLAPSED;
+                       
+                       notifierFlags |= (ND_ANIMCHAN | NA_EDITED);
+                       break;
+               }
                case ANIMTYPE_GPDATABLOCK:
                {
                        bGPdata *gpd = (bGPdata *)ale->data;
@@ -2887,7 +2992,7 @@ static int animchannels_mouseclick_invoke(bContext *C, wmOperator *op, const wmE
         *              ACHANNEL_HEIGHT_HALF.
         */
        UI_view2d_region_to_view(v2d, event->mval[0], event->mval[1], &x, &y);
-       UI_view2d_listview_view_to_cell(v2d, ACHANNEL_NAMEWIDTH, ACHANNEL_STEP, 0, (float)ACHANNEL_HEIGHT_HALF, x, y, NULL, &channel_index);
+       UI_view2d_listview_view_to_cell(v2d, ACHANNEL_NAMEWIDTH, ACHANNEL_STEP(&ac), 0, (float)ACHANNEL_HEIGHT_HALF(&ac), x, y, NULL, &channel_index);
        
        /* handle mouse-click in the relevant channel then */
        notifierFlags = mouse_anim_channels(C, &ac, channel_index, selectmode);
@@ -2923,6 +3028,105 @@ static void ANIM_OT_channels_click(wmOperatorType *ot)
        RNA_def_property_flag(prop, PROP_SKIP_SAVE);
 }
 
+static bool select_anim_channel_keys(bAnimContext *ac, int channel_index, bool extend)
+{
+       ListBase anim_data = {NULL, NULL};
+       bAnimListElem *ale;
+       int filter;
+       bool success = false;
+       FCurve *fcu;
+       int i;
+
+       /* get the channel that was clicked on */
+       /* filter channels */
+       filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS);
+       ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
+
+       /* get channel from index */
+       ale = BLI_findlink(&anim_data, channel_index);
+       if (ale == NULL) {
+               /* channel not found */
+               if (G.debug & G_DEBUG)
+                       printf("Error: animation channel (index = %d) not found in rename_anim_channels()\n", channel_index);
+
+               ANIM_animdata_freelist(&anim_data);
+               return false;
+       }
+
+       fcu = (FCurve *)ale->key_data;
+       success = (fcu != NULL);
+
+       ANIM_animdata_freelist(&anim_data);
+
+       /* F-Curve may not have any keyframes */
+       if (fcu && fcu->bezt) {
+               BezTriple *bezt;
+
+               if (!extend) {
+                       filter = (ANIMFILTER_DATA_VISIBLE);
+                       ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
+                       for (ale = anim_data.first; ale; ale = ale->next) {
+                               FCurve *fcu_inner = (FCurve *)ale->key_data;
+
+                               if (fcu_inner) {
+                                       for (i = 0, bezt = fcu_inner->bezt; i < fcu_inner->totvert; i++, bezt++) {
+                                               bezt->f2 = bezt->f1 = bezt->f3 = 0;
+                                       }
+                               }
+                       }
+
+                       ANIM_animdata_freelist(&anim_data);
+               }
+
+               for (i = 0, bezt = fcu->bezt; i < fcu->totvert; i++, bezt++) {
+                       bezt->f2 = bezt->f1 = bezt->f3 = SELECT;
+               }
+       }
+
+       /* free temp data and tag for refresh */
+       ED_region_tag_redraw(ac->ar);
+       return success;
+}
+
+static int animchannels_channel_select_keys_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+       bAnimContext ac;
+       int channel_index;
+       bool extend = RNA_boolean_get(op->ptr, "extend");
+
+       /* get editor data */
+       if (ANIM_animdata_get_context(C, &ac) == 0)
+               return OPERATOR_CANCELLED;
+
+       channel_index = animchannels_channel_get(&ac, event->mval);
+
+       /* handle click */
+       if (select_anim_channel_keys(&ac, channel_index, extend)) {
+               WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_SELECTED, NULL);
+               return OPERATOR_FINISHED;
+       }
+       else
+               /* allow event to be handled by selectall operator */
+               return OPERATOR_PASS_THROUGH;
+}
+
+static void ANIM_OT_channel_select_keys(wmOperatorType *ot)
+{
+       PropertyRNA *prop;
+
+       /* identifiers */
+       ot->name = "Select Channel keyframes";
+       ot->idname = "ANIM_OT_channel_select_keys";
+       ot->description = "Select all keyframes of channel under mouse";
+
+       /* api callbacks */
+       ot->invoke = animchannels_channel_select_keys_invoke;
+       ot->poll = animedit_poll_channels_active;
+
+       prop = RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend selection");
+       RNA_def_property_flag(prop, PROP_SKIP_SAVE);
+}
+
 /* ************************************************************************** */
 /* Operator Registration */
 
@@ -2932,8 +3136,9 @@ void ED_operatortypes_animchannels(void)
        WM_operatortype_append(ANIM_OT_channels_select_border);
        
        WM_operatortype_append(ANIM_OT_channels_click);
+       WM_operatortype_append(ANIM_OT_channel_select_keys);
        WM_operatortype_append(ANIM_OT_channels_rename);
-       
+
        WM_operatortype_append(ANIM_OT_channels_find);
        
        WM_operatortype_append(ANIM_OT_channels_setting_enable);
@@ -2973,7 +3178,9 @@ void ED_keymap_animchannels(wmKeyConfig *keyconf)
        /* rename */
        WM_keymap_add_item(keymap, "ANIM_OT_channels_rename", LEFTMOUSE, KM_PRESS, KM_CTRL, 0);
        WM_keymap_add_item(keymap, "ANIM_OT_channels_rename", LEFTMOUSE, KM_DBL_CLICK, 0, 0);
-       
+       WM_keymap_add_item(keymap, "ANIM_OT_channel_select_keys", LEFTMOUSE, KM_DBL_CLICK, 0, 0);
+       RNA_boolean_set(WM_keymap_add_item(keymap, "ANIM_OT_channel_select_keys", LEFTMOUSE, KM_DBL_CLICK, KM_SHIFT, 0)->ptr, "extend", true);
+
        /* find (i.e. a shortcut for setting the name filter) */
        WM_keymap_add_item(keymap, "ANIM_OT_channels_find", FKEY, KM_PRESS, KM_CTRL, 0);