diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index 60f8ea6d9e1..bc52f96e9c0 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -1768,6 +1768,7 @@ def km_graph_editor(params): ("graph.select_leftright", {"type": 'RIGHT_BRACKET', "value": 'PRESS'}, {"properties": [("mode", 'RIGHT')]}), *_template_items_select_actions(params, "graph.select_all"), + ("graph.select_grouped", {"type": 'G', "value": 'PRESS', "shift": True}, None), ("graph.select_box", {"type": 'B', "value": 'PRESS'}, None), ("graph.select_box", {"type": 'B', "value": 'PRESS', "alt": True}, {"properties": [("axis_range", True)]}), @@ -2430,6 +2431,7 @@ def km_dopesheet(params): ("action.select_more", {"type": 'NUMPAD_PLUS', "value": 'PRESS', "ctrl": True, "repeat": True}, None), ("action.select_less", {"type": 'NUMPAD_MINUS', "value": 'PRESS', "ctrl": True, "repeat": True}, None), ("action.select_linked", {"type": 'L', "value": 'PRESS'}, None), + ("action.select_grouped", {"type": 'G', "value": 'PRESS', "shift": True}, None), ("action.frame_jump", {"type": 'G', "value": 'PRESS', "ctrl": True}, None), ( op_menu_pie("DOPESHEET_MT_snap_pie", {"type": 'S', "value": 'PRESS', "shift": True}) @@ -2556,6 +2558,7 @@ def km_nla_editor(params): ("nla.select_leftright", {"type": 'RIGHT_BRACKET', "value": 'PRESS'}, {"properties": [("mode", 'RIGHT')]}), *_template_items_select_actions(params, "nla.select_all"), + ("nla.select_grouped", {"type": 'G', "value": 'PRESS', "shift": True}, None), ("nla.select_box", {"type": 'B', "value": 'PRESS'}, {"properties": [("axis_range", False)]}), ("nla.select_box", {"type": 'B', "value": 'PRESS', "alt": True}, diff --git a/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py b/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py index 98cee34519f..68c517d2f7e 100644 --- a/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py +++ b/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py @@ -903,6 +903,7 @@ def km_graph_editor(params): {"properties": [("mode", 'LEFT'), ("extend", False)]}), ("graph.select_leftright", {"type": 'RIGHT_BRACKET', "value": 'PRESS'}, {"properties": [("mode", 'RIGHT'), ("extend", False)]}), + ("graph.select_grouped", {"type": 'G', "value": 'PRESS', "shift": True}, None), ("graph.select_all", {"type": 'A', "value": 'PRESS', "ctrl": True}, {"properties": [("action", 'SELECT')]}), ("graph.select_all", {"type": 'A', "value": 'PRESS', "ctrl": True, "shift": True}, {"properties": [("action", 'DESELECT')]}), @@ -1393,6 +1394,7 @@ def km_dopesheet(params): ("action.select_more", {"type": 'UP_ARROW', "value": 'PRESS', "ctrl": True, "repeat": True}, None), ("action.select_less", {"type": 'DOWN_ARROW', "value": 'PRESS', "ctrl": True, "repeat": True}, None), ("action.select_linked", {"type": 'RIGHT_BRACKET', "value": 'PRESS'}, None), + ("action.select_grouped", {"type": 'G', "value": 'PRESS', "shift": True}, None), ("action.frame_jump", {"type": 'G', "value": 'PRESS', "ctrl": True}, None), ("wm.context_menu_enum", {"type": 'X', "value": 'PRESS'}, {"properties": [("data_path", 'space_data.auto_snap')]}), @@ -1495,6 +1497,7 @@ def km_nla_editor(params): {"properties": [("mode", 'LEFT'), ("extend", False)]}), ("nla.select_leftright", {"type": 'RIGHT_BRACKET', "value": 'PRESS'}, {"properties": [("mode", 'RIGHT'), ("extend", False)]}), + ("nla.select_grouped", {"type": 'G', "value": 'PRESS', "shift": True}, None), ("nla.select_all", {"type": 'A', "value": 'PRESS', "ctrl": True}, {"properties": [("action", 'SELECT')]}), ("nla.select_all", {"type": 'A', "value": 'PRESS', "ctrl": True, "shift": True}, {"properties": [("action", 'DESELECT')]}), diff --git a/scripts/startup/bl_ui/space_dopesheet.py b/scripts/startup/bl_ui/space_dopesheet.py index eed77d93de9..d879471a079 100644 --- a/scripts/startup/bl_ui/space_dopesheet.py +++ b/scripts/startup/bl_ui/space_dopesheet.py @@ -415,6 +415,7 @@ class DOPESHEET_MT_select(Menu): layout.separator() layout.operator("action.select_linked") + layout.operator_menu_enum("action.select_grouped", "type", text="Select Grouped") class DOPESHEET_MT_marker(Menu): diff --git a/scripts/startup/bl_ui/space_graph.py b/scripts/startup/bl_ui/space_graph.py index 5a84bd5140d..cf2c296d43f 100644 --- a/scripts/startup/bl_ui/space_graph.py +++ b/scripts/startup/bl_ui/space_graph.py @@ -178,6 +178,9 @@ class GRAPH_MT_select(Menu): props.extend = False props.mode = 'RIGHT' + layout.separator() + layout.operator_menu_enum("graph.select_grouped", "type", text="Select Grouped") + layout.separator() layout.operator("graph.select_more") layout.operator("graph.select_less") diff --git a/scripts/startup/bl_ui/space_nla.py b/scripts/startup/bl_ui/space_nla.py index b43434d9988..8e705e4e5b4 100644 --- a/scripts/startup/bl_ui/space_nla.py +++ b/scripts/startup/bl_ui/space_nla.py @@ -138,6 +138,9 @@ class NLA_MT_select(Menu): props.extend = False props.mode = 'RIGHT' + layout.separator() + layout.operator_menu_enum("nla.select_grouped", "type", text="Select Grouped") + class NLA_MT_marker(Menu): bl_label = "Marker" diff --git a/source/blender/editors/space_action/action_intern.h b/source/blender/editors/space_action/action_intern.h index c227651d1d7..ddb7a5c2b70 100644 --- a/source/blender/editors/space_action/action_intern.h +++ b/source/blender/editors/space_action/action_intern.h @@ -51,6 +51,7 @@ void ACTION_OT_select_linked(struct wmOperatorType *ot); void ACTION_OT_select_more(struct wmOperatorType *ot); void ACTION_OT_select_less(struct wmOperatorType *ot); void ACTION_OT_select_leftright(struct wmOperatorType *ot); +void ACTION_OT_select_grouped(struct wmOperatorType *ot); void ACTION_OT_clickselect(struct wmOperatorType *ot); /* defines for left-right select tool */ diff --git a/source/blender/editors/space_action/action_ops.c b/source/blender/editors/space_action/action_ops.c index b2af86959c8..79e36258f9b 100644 --- a/source/blender/editors/space_action/action_ops.c +++ b/source/blender/editors/space_action/action_ops.c @@ -36,6 +36,7 @@ void action_operatortypes(void) WM_operatortype_append(ACTION_OT_select_more); WM_operatortype_append(ACTION_OT_select_less); WM_operatortype_append(ACTION_OT_select_leftright); + WM_operatortype_append(ACTION_OT_select_grouped); /* editing */ WM_operatortype_append(ACTION_OT_snap); diff --git a/source/blender/editors/space_action/action_select.c b/source/blender/editors/space_action/action_select.c index 0c416712a0e..fb868790aff 100644 --- a/source/blender/editors/space_action/action_select.c +++ b/source/blender/editors/space_action/action_select.c @@ -14,6 +14,7 @@ #include "BLI_blenlib.h" #include "BLI_dlrbTree.h" +#include "BLI_ghash.h" #include "BLI_lasso_2d.h" #include "BLI_utildefines.h" @@ -1906,3 +1907,403 @@ void ACTION_OT_clickselect(wmOperatorType *ot) } /* ************************************************************************** */ + +/* Select Grouped Operator */ + +enum { + ACTKEYS_SELECT_GROUP_CHANNEL_TYPE, + ACTKEYS_SELECT_GROUP_CHANNEL_TYPE_STRICT, + ACTKEYS_SELECT_GROUP_KEY_TYPE, + ACTKEYS_SELECT_GROUP_INTERPOLATION_TYPE, + ACTKEYS_SELECT_GROUP_HANDLE_TYPE, + ACTKEYS_SELECT_GROUP_INTERPOLATION_HANDLE_TYPE, + ACTKEYS_SELECT_GROUP_MODIFIERS, + ACTKEYS_SELECT_GROUP_DATABLOCK, +}; + +static const EnumPropertyItem actkeys_prop_select_grouped_types[] = { + {ACTKEYS_SELECT_GROUP_CHANNEL_TYPE, + "CHANNEL_TYPE", + 0, + "Channel Type", + "All keyframes with the same RNA path (Location, etc.)"}, + {ACTKEYS_SELECT_GROUP_CHANNEL_TYPE_STRICT, + "CHANNEL_TYPE_STRICT", + 0, + "Channel Type (Strict)", + "All keyframes with the same RNA path, including array index (X Location, etc.)"}, + {ACTKEYS_SELECT_GROUP_KEY_TYPE, + "KEY_TYPE", + 0, + "Key Type", + "All keyframes of the same type (breakdown, extreme, etc.)"}, + {ACTKEYS_SELECT_GROUP_INTERPOLATION_TYPE, + "INTERPOLATION_TYPE", + 0, + "Interpolation Mode", + "All keyframes of the same interpolation mode (constant, bezier, etc.)"}, + {ACTKEYS_SELECT_GROUP_HANDLE_TYPE, + "HANDLE_TYPE", + 0, + "Handle Type", + "All keyframes of the same handle type (vector, automatic, etc.)"}, + {ACTKEYS_SELECT_GROUP_MODIFIERS, + "MODIFIERS", + 0, + "Modifiers", + "All keyframes on each channel that have all of the same types of modifiers"}, + {ACTKEYS_SELECT_GROUP_DATABLOCK, + "DATABLOCK", + 0, + "Datablock", + "All keyframes that are part of the same datablock"}, + {0, NULL, 0, NULL, NULL}, +}; + +/* ACTKEYS_SELECT_GROUP_CHANNEL_TYPE */ + +static short select_grouped_channel_type(KeyframeEditData *ked, struct BezTriple *bezt) +{ + if (BLI_gset_haskey(ked->data, ked->fcu->rna_path)) { + return KEYFRAME_OK_ALL; + } + return 0; +} + +static short select_grouped_active_channel_type(KeyframeEditData *ked, struct BezTriple *bezt) +{ + BLI_gset_add(ked->data, BLI_strdup(ked->fcu->rna_path)); + return 0; +} + +/* ACTKEYS_SELECT_GROUP_CHANNEL_TYPE_STRICT */ + +static short select_grouped_channel_type_strict(KeyframeEditData *ked, struct BezTriple *bezt) +{ + char name[256]; + BLI_snprintf(name, 256, "%s[%d]", ked->fcu->rna_path, ked->fcu->array_index); + if (BLI_gset_haskey(ked->data, name)) { + return KEYFRAME_OK_ALL; + } + return 0; +} + +static short select_grouped_active_channel_type_strict(KeyframeEditData *ked, + struct BezTriple *bezt) +{ + char *name = MEM_mallocN(256, __func__); + BLI_snprintf(name, 256, "%s[%d]", ked->fcu->rna_path, ked->fcu->array_index); + BLI_gset_add(ked->data, name); + return 0; +} + +/* ACTKEYS_SELECT_GROUP_MODIFIERS */ + +static int convert_modifiers_to_bitmask(ListBase modifiers) +{ + int bitmask = 0; + FModifier *modifier; + for (modifier = modifiers.first; modifier; modifier = modifier->next) { + bitmask |= 1 << modifier->type; + } + return bitmask; +} + +static short select_grouped_modifiers(KeyframeEditData *ked, struct BezTriple *bezt) +{ + if (BLI_gset_haskey(ked->data, + POINTER_FROM_INT(convert_modifiers_to_bitmask(ked->fcu->modifiers)))) { + return KEYFRAME_OK_ALL; + } + return 0; +} + +static short select_grouped_active_modifiers(KeyframeEditData *ked, struct BezTriple *bezt) +{ + BLI_gset_add(ked->data, POINTER_FROM_INT(convert_modifiers_to_bitmask(ked->fcu->modifiers))); + return 0; +} + +/* ACTKEYS_SELECT_GROUP_DATABLOCK */ + +static short select_grouped_datablock(KeyframeEditData *ked, struct BezTriple *bezt) +{ + /* Actual filtering is done via the anim data list. */ + return KEYFRAME_OK_ALL; +} + +static short select_grouped_active_datablock(KeyframeEditData *ked, struct BezTriple *bezt) +{ + /* Cancel iteration if we found any selected key. The key will be added to the set in the loop, + * since the info comes from the anim data list. */ + return 1; +} + +/* ACTKEYS_SELECT_GROUP_HANDLE_TYPE */ + +static short select_grouped_handle_type(KeyframeEditData *ked, struct BezTriple *bezt) +{ + short ok = 0; + if (BLI_gset_haskey(ked->data, POINTER_FROM_INT(bezt->h1))) { + ok |= KEYFRAME_OK_H1; + } + if (BLI_gset_haskey(ked->data, POINTER_FROM_INT(bezt->h2))) { + ok |= KEYFRAME_OK_H2; + } + if (ok == (KEYFRAME_OK_H1 | KEYFRAME_OK_H2)) { + ok |= KEYFRAME_OK_KEY; + } + return ok; +} + +static short select_grouped_active_handle_type(KeyframeEditData *ked, struct BezTriple *bezt) +{ + BLI_gset_add(ked->data, POINTER_FROM_INT(bezt->h1)); + BLI_gset_add(ked->data, POINTER_FROM_INT(bezt->h2)); + return 0; +} + +/* ACTKEYS_SELECT_GROUP_INTERPOLATION_TYPE */ + +static short select_grouped_interpolation_type(KeyframeEditData *ked, struct BezTriple *bezt) +{ + if (BLI_gset_haskey(ked->data, POINTER_FROM_INT(bezt->ipo))) { + return KEYFRAME_OK_ALL; + } + return 0; +} + +static short select_grouped_active_interpolation_type(KeyframeEditData *ked, + struct BezTriple *bezt) +{ + BLI_gset_add(ked->data, POINTER_FROM_INT(bezt->ipo)); + return 0; +} + +/* ACTKEYS_SELECT_GROUP_KEY_TYPE */ + +static short select_grouped_key_type(KeyframeEditData *ked, struct BezTriple *bezt) +{ + if (BLI_gset_haskey(ked->data, POINTER_FROM_INT(BEZKEYTYPE(bezt)))) { + return KEYFRAME_OK_ALL; + } + return 0; +} + +static short select_grouped_active_key_type(KeyframeEditData *ked, struct BezTriple *bezt) +{ + BLI_gset_add(ked->data, POINTER_FROM_INT(BEZKEYTYPE(bezt))); + return 0; +} + +static KeyframeEditFunc select_grouped_get_filter_callback(ListBase *anim_data, + KeyframeEditData *ked, + int type) +{ + KeyframeEditFunc ok_cb = ANIM_editkeyframes_ok(BEZT_OK_SELECTED); + GSet *comp_set; + + if (type == ACTKEYS_SELECT_GROUP_DATABLOCK) { + comp_set = BLI_gset_ptr_new(__func__); + } + else if (type == ACTKEYS_SELECT_GROUP_CHANNEL_TYPE || + type == ACTKEYS_SELECT_GROUP_CHANNEL_TYPE_STRICT) { + comp_set = BLI_gset_str_new(__func__); + } + else { + comp_set = BLI_gset_int_new(__func__); + } + + ked->data = comp_set; + + /* Last iteration of this loop will be used to return a callback based on type. */ + bAnimListElem *ale; + for (ale = anim_data->first;; ale = ale->next) { + FCurve *fcu; + if (ale != NULL) { + /* TODO(redmser): Other types (GP, mask, etc.). */ + if (ale->datatype != ALE_FCURVE) { + continue; + } + + /* Only continue if F-Curve has keyframes. */ + fcu = (FCurve *)ale->key_data; + if (fcu->bezt == NULL) { + continue; + } + } + + /* Find first selected keyframe for context info. */ + switch (type) { + case ACTKEYS_SELECT_GROUP_CHANNEL_TYPE: { + if (ale == NULL) { + return select_grouped_channel_type; + } + + ANIM_fcurve_keyframes_loop(ked, fcu, ok_cb, select_grouped_active_channel_type, NULL); + break; + } + case ACTKEYS_SELECT_GROUP_CHANNEL_TYPE_STRICT: { + if (ale == NULL) { + return select_grouped_channel_type_strict; + } + + ANIM_fcurve_keyframes_loop( + ked, fcu, ok_cb, select_grouped_active_channel_type_strict, NULL); + break; + } + case ACTKEYS_SELECT_GROUP_HANDLE_TYPE: { + if (ale == NULL) { + return select_grouped_handle_type; + } + + ANIM_fcurve_keyframes_loop(ked, fcu, ok_cb, select_grouped_active_handle_type, NULL); + break; + } + case ACTKEYS_SELECT_GROUP_INTERPOLATION_TYPE: { + if (ale == NULL) { + return select_grouped_interpolation_type; + } + + ANIM_fcurve_keyframes_loop( + ked, fcu, ok_cb, select_grouped_active_interpolation_type, NULL); + break; + } + case ACTKEYS_SELECT_GROUP_KEY_TYPE: { + if (ale == NULL) { + return select_grouped_key_type; + } + + ANIM_fcurve_keyframes_loop(ked, fcu, ok_cb, select_grouped_active_key_type, NULL); + break; + } + case ACTKEYS_SELECT_GROUP_MODIFIERS: { + if (ale == NULL) { + return select_grouped_modifiers; + } + + ANIM_fcurve_keyframes_loop(ked, fcu, ok_cb, select_grouped_active_modifiers, NULL); + break; + } + case ACTKEYS_SELECT_GROUP_DATABLOCK: { + if (ale == NULL) { + return select_grouped_datablock; + } + + short result = ANIM_fcurve_keyframes_loop( + ked, fcu, ok_cb, select_grouped_active_datablock, NULL); + if (result == 1) { + /* Found a selected key in this datablock, add its id. */ + BLI_gset_add(ked->data, ale->id); + } + break; + } + default: { + BLI_assert(0); + break; + } + } + } + + return NULL; +} + +static int actkeys_select_grouped_exec(bContext *C, wmOperator *op) +{ + bAnimContext ac; + + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + const int type = RNA_enum_get(op->ptr, "type"); + const bool use_selected_channels = RNA_boolean_get(op->ptr, "use_selected_channels"); + + /* Filter data. */ + int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS); + if (use_selected_channels) { + /* TODO(redmser): Doing it this way means that the keyframe that we're getting the reference + * value from must also be in a selected channel. But not sure if filtering twice is a better + * solution here either. */ + filter |= ANIMFILTER_SEL; + } + + ListBase anim_data = {NULL, NULL}; + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + KeyframeEditData ked = {{NULL}}; + KeyframeEditFunc ok_cb = select_grouped_get_filter_callback(&anim_data, &ked, type); + KeyframeEditFunc select_cb = ANIM_editkeyframes_select(SELECT_ADD); + + if (!RNA_boolean_get(op->ptr, "extend")) { + /* If not extending, deselect all first. */ + deselect_action_keys(&ac, 0, SELECT_SUBTRACT); + } + + bAnimListElem *ale; + for (ale = anim_data.first; ale; ale = ale->next) { + /* TODO(redmser): Other types (GP, mask, etc.). */ + if (ale->datatype != ALE_FCURVE) { + continue; + } + + /* Only continue if F-Curve has keyframes. */ + FCurve *fcu = (FCurve *)ale->key_data; + if (fcu->bezt == NULL) { + continue; + } + + /* Filtering by datablock has to be done on anim data level. */ + if (type == ACTKEYS_SELECT_GROUP_DATABLOCK && !BLI_gset_haskey(ked.data, ale->id)) { + continue; + } + + ANIM_fcurve_keyframes_loop(&ked, fcu, ok_cb, select_cb, NULL); + } + + ANIM_animdata_freelist(&anim_data); + if (type == ACTKEYS_SELECT_GROUP_CHANNEL_TYPE || + type == ACTKEYS_SELECT_GROUP_CHANNEL_TYPE_STRICT) { + BLI_gset_free(ked.data, MEM_freeN); + } + else { + BLI_gset_free(ked.data, NULL); + } + + WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_SELECTED, NULL); + return OPERATOR_FINISHED; +} + +void ACTION_OT_select_grouped(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Select Grouped"; + ot->idname = "ACTION_OT_select_grouped"; + ot->description = "Select all keyframes grouped by various properties"; + + /* Api callbacks. */ + ot->invoke = WM_menu_invoke; + ot->exec = actkeys_select_grouped_exec; + ot->poll = ED_operator_action_active; + + /* Flags. */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* Properties. */ + ot->prop = RNA_def_enum(ot->srna, + "type", + actkeys_prop_select_grouped_types, + 0, + "Type", + "Which criterion to filter selection by"); + RNA_def_boolean(ot->srna, + "extend", + false, + "Extend", + "Extend selection instead of deselecting everything first"); + RNA_def_boolean(ot->srna, + "use_selected_channels", + false, + "Use Selected Channels", + "Only consider keyframes on the same channels as the selected ones"); +} diff --git a/source/blender/editors/space_graph/graph_intern.h b/source/blender/editors/space_graph/graph_intern.h index a685216db31..fe7a9ea0b9e 100644 --- a/source/blender/editors/space_graph/graph_intern.h +++ b/source/blender/editors/space_graph/graph_intern.h @@ -65,6 +65,7 @@ void GRAPH_OT_select_linked(struct wmOperatorType *ot); void GRAPH_OT_select_more(struct wmOperatorType *ot); void GRAPH_OT_select_less(struct wmOperatorType *ot); void GRAPH_OT_select_leftright(struct wmOperatorType *ot); +void GRAPH_OT_select_grouped(struct wmOperatorType *ot); void GRAPH_OT_clickselect(struct wmOperatorType *ot); /* defines for left-right select tool */ diff --git a/source/blender/editors/space_graph/graph_ops.c b/source/blender/editors/space_graph/graph_ops.c index b178a3d3430..2661965dfb0 100644 --- a/source/blender/editors/space_graph/graph_ops.c +++ b/source/blender/editors/space_graph/graph_ops.c @@ -442,6 +442,7 @@ void graphedit_operatortypes(void) WM_operatortype_append(GRAPH_OT_select_more); WM_operatortype_append(GRAPH_OT_select_less); WM_operatortype_append(GRAPH_OT_select_leftright); + WM_operatortype_append(GRAPH_OT_select_grouped); /* editing */ WM_operatortype_append(GRAPH_OT_snap); diff --git a/source/blender/editors/space_graph/graph_select.c b/source/blender/editors/space_graph/graph_select.c index f92fe71455b..9a4d494f0f5 100644 --- a/source/blender/editors/space_graph/graph_select.c +++ b/source/blender/editors/space_graph/graph_select.c @@ -13,6 +13,7 @@ #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" +#include "BLI_ghash.h" #include "BLI_lasso_2d.h" #include "BLI_math.h" #include "BLI_utildefines.h" @@ -2020,3 +2021,462 @@ void GRAPH_OT_clickselect(wmOperatorType *ot) } /** \} */ + +/* Select Grouped Operator */ +/* TODO(redmser): This can be de-duplicated almost completely from the action variant of this + * operator. */ + +enum { + GRAPHKEYS_SELECT_GROUP_CHANNEL_TYPE, + GRAPHKEYS_SELECT_GROUP_CHANNEL_TYPE_STRICT, + GRAPHKEYS_SELECT_GROUP_KEY_TYPE, + GRAPHKEYS_SELECT_GROUP_HANDLE_SIDE, + GRAPHKEYS_SELECT_GROUP_INTERPOLATION_TYPE, + GRAPHKEYS_SELECT_GROUP_HANDLE_TYPE, + GRAPHKEYS_SELECT_GROUP_INTERPOLATION_HANDLE_TYPE, + GRAPHKEYS_SELECT_GROUP_MODIFIERS, + GRAPHKEYS_SELECT_GROUP_DATABLOCK, +}; + +static const EnumPropertyItem graphkeys_prop_select_grouped_types[] = { + {GRAPHKEYS_SELECT_GROUP_CHANNEL_TYPE, + "CHANNEL_TYPE", + 0, + "Channel Type", + "All keyframes with the same RNA path (Location, etc.)"}, + {GRAPHKEYS_SELECT_GROUP_CHANNEL_TYPE_STRICT, + "CHANNEL_TYPE_STRICT", + 0, + "Channel Type (Strict)", + "All keyframes with the same RNA path, including array index (X Location, etc.)"}, + {GRAPHKEYS_SELECT_GROUP_KEY_TYPE, + "KEY_TYPE", + 0, + "Key Type", + "All keyframes of the same type (breakdown, extreme, etc.)"}, + {GRAPHKEYS_SELECT_GROUP_HANDLE_SIDE, + "HANDLE_SIDE", + 0, + "Handle Side", + "All handles that are on the same side as the selected (left or right)"}, + {GRAPHKEYS_SELECT_GROUP_INTERPOLATION_TYPE, + "INTERPOLATION_TYPE", + 0, + "Interpolation Mode", + "All keyframes of the same interpolation mode (constant, bezier, etc.)"}, + {GRAPHKEYS_SELECT_GROUP_HANDLE_TYPE, + "HANDLE_TYPE", + 0, + "Handle Type", + "All keyframes of the same handle type (vector, automatic, etc.)"}, + {GRAPHKEYS_SELECT_GROUP_MODIFIERS, + "MODIFIERS", + 0, + "Modifiers", + "All keyframes on each channel that have all of the same types of modifiers"}, + {GRAPHKEYS_SELECT_GROUP_DATABLOCK, + "DATABLOCK", + 0, + "Datablock", + "All keyframes that are part of the same datablock"}, + {0, NULL, 0, NULL, NULL}, +}; + +/* GRAPHKEYS_SELECT_GROUP_CHANNEL_TYPE */ + +static short select_grouped_channel_type(KeyframeEditData *ked, struct BezTriple *bezt) +{ + if (BLI_gset_haskey(ked->data, ked->fcu->rna_path)) { + return KEYFRAME_OK_ALL; + } + return 0; +} + +static short select_grouped_active_channel_type(KeyframeEditData *ked, struct BezTriple *bezt) +{ + BLI_gset_add(ked->data, BLI_strdup(ked->fcu->rna_path)); + return 0; +} + +/* GRAPHKEYS_SELECT_GROUP_CHANNEL_TYPE_STRICT */ + +static short select_grouped_channel_type_strict(KeyframeEditData *ked, struct BezTriple *bezt) +{ + char name[256]; + BLI_snprintf(name, 256, "%s[%d]", ked->fcu->rna_path, ked->fcu->array_index); + if (BLI_gset_haskey(ked->data, name)) { + return KEYFRAME_OK_ALL; + } + return 0; +} + +static short select_grouped_active_channel_type_strict(KeyframeEditData *ked, + struct BezTriple *bezt) +{ + char *name = MEM_mallocN(256, __func__); + BLI_snprintf(name, 256, "%s[%d]", ked->fcu->rna_path, ked->fcu->array_index); + BLI_gset_add(ked->data, name); + return 0; +} + +/* GRAPHKEYS_SELECT_GROUP_MODIFIERS */ + +static int convert_modifiers_to_bitmask(ListBase modifiers) +{ + int bitmask = 0; + FModifier *modifier; + for (modifier = modifiers.first; modifier; modifier = modifier->next) { + bitmask |= 1 << modifier->type; + } + return bitmask; +} + +static short select_grouped_modifiers(KeyframeEditData *ked, struct BezTriple *bezt) +{ + if (BLI_gset_haskey(ked->data, + POINTER_FROM_INT(convert_modifiers_to_bitmask(ked->fcu->modifiers)))) { + return KEYFRAME_OK_ALL; + } + return 0; +} + +static short select_grouped_active_modifiers(KeyframeEditData *ked, struct BezTriple *bezt) +{ + BLI_gset_add(ked->data, POINTER_FROM_INT(convert_modifiers_to_bitmask(ked->fcu->modifiers))); + return 0; +} + +/* GRAPHKEYS_SELECT_GROUP_DATABLOCK */ + +static short select_grouped_datablock(KeyframeEditData *ked, struct BezTriple *bezt) +{ + /* Actual filtering is done via the anim data list. */ + return KEYFRAME_OK_ALL; +} + +static short select_grouped_active_datablock(KeyframeEditData *ked, struct BezTriple *bezt) +{ + /* Cancel iteration if we found any selected key. The key will be added to the set in the loop, + * since the info comes from the anim data list. */ + return 1; +} + +/* GRAPHKEYS_SELECT_GROUP_HANDLE_SIDE */ + +static short select_grouped_handle_side(KeyframeEditData *ked, struct BezTriple *bezt) +{ + short ok = 0; + if (BLI_gset_haskey(ked->data, POINTER_FROM_INT(1))) { + ok |= KEYFRAME_OK_H1; + } + if (BLI_gset_haskey(ked->data, POINTER_FROM_INT(2))) { + ok |= KEYFRAME_OK_H2; + } + return ok; +} + +static short select_grouped_active_handle_side(KeyframeEditData *ked, struct BezTriple *bezt) +{ + /* Left handle. */ + if ((bezt->f1 & SELECT) != 0) { + BLI_gset_add(ked->data, POINTER_FROM_INT(1)); + } + /* Right handle. */ + if ((bezt->f3 & SELECT) != 0) { + BLI_gset_add(ked->data, POINTER_FROM_INT(2)); + } + return 0; +} + +/* GRAPHKEYS_SELECT_GROUP_HANDLE_TYPE */ + +static short select_grouped_handle_type(KeyframeEditData *ked, struct BezTriple *bezt) +{ + short ok = 0; + if (BLI_gset_haskey(ked->data, POINTER_FROM_INT(bezt->h1))) { + ok |= KEYFRAME_OK_H1; + } + if (BLI_gset_haskey(ked->data, POINTER_FROM_INT(bezt->h2))) { + ok |= KEYFRAME_OK_H2; + } + if (ok == (KEYFRAME_OK_H1 | KEYFRAME_OK_H2)) { + ok |= KEYFRAME_OK_KEY; + } + return ok; +} + +static short select_grouped_active_handle_type(KeyframeEditData *ked, struct BezTriple *bezt) +{ + /* Left handle. */ + if ((bezt->f1 & SELECT) != 0) { + BLI_gset_add(ked->data, POINTER_FROM_INT(bezt->h1)); + } + /* Right handle. */ + if ((bezt->f3 & SELECT) != 0) { + BLI_gset_add(ked->data, POINTER_FROM_INT(bezt->h2)); + } + return 0; +} + +/* GRAPHKEYS_SELECT_GROUP_INTERPOLATION_TYPE */ + +static short select_grouped_interpolation_type(KeyframeEditData *ked, struct BezTriple *bezt) +{ + if (BLI_gset_haskey(ked->data, POINTER_FROM_INT(bezt->ipo))) { + return KEYFRAME_OK_ALL; + } + return 0; +} + +static short select_grouped_active_interpolation_type(KeyframeEditData *ked, + struct BezTriple *bezt) +{ + BLI_gset_add(ked->data, POINTER_FROM_INT(bezt->ipo)); + return 0; +} + +/* GRAPHKEYS_SELECT_GROUP_KEY_TYPE */ + +static short select_grouped_key_type(KeyframeEditData *ked, struct BezTriple *bezt) +{ + if (BLI_gset_haskey(ked->data, POINTER_FROM_INT(BEZKEYTYPE(bezt)))) { + return KEYFRAME_OK_ALL; + } + return 0; +} + +static short select_grouped_active_key_type(KeyframeEditData *ked, struct BezTriple *bezt) +{ + BLI_gset_add(ked->data, POINTER_FROM_INT(BEZKEYTYPE(bezt))); + return 0; +} + +static KeyframeEditFunc select_grouped_get_filter_callback(ListBase *anim_data, + KeyframeEditData *ked, + int type) +{ + KeyframeEditFunc ok_cb = ANIM_editkeyframes_ok(BEZT_OK_SELECTED); + GSet *comp_set; + + if (type == GRAPHKEYS_SELECT_GROUP_DATABLOCK) { + comp_set = BLI_gset_ptr_new(__func__); + } + else if (type == GRAPHKEYS_SELECT_GROUP_CHANNEL_TYPE || + type == GRAPHKEYS_SELECT_GROUP_CHANNEL_TYPE_STRICT) { + comp_set = BLI_gset_str_new(__func__); + } + else { + comp_set = BLI_gset_int_new(__func__); + } + + ked->data = comp_set; + + /* Last iteration of this loop will be used to return a callback based on type. */ + bAnimListElem *ale; + for (ale = anim_data->first;; ale = ale->next) { + FCurve *fcu; + if (ale != NULL) { + /* TODO(redmser): Other types (GP, mask, etc.). */ + if (ale->datatype != ALE_FCURVE) { + continue; + } + + /* Only continue if F-Curve has keyframes. */ + fcu = (FCurve *)ale->key_data; + if (fcu->bezt == NULL) { + continue; + } + } + + /* Find first selected keyframe for context info. */ + switch (type) { + case GRAPHKEYS_SELECT_GROUP_CHANNEL_TYPE: { + if (ale == NULL) { + return select_grouped_channel_type; + } + + ANIM_fcurve_keyframes_loop(ked, fcu, ok_cb, select_grouped_active_channel_type, NULL); + break; + } + case GRAPHKEYS_SELECT_GROUP_CHANNEL_TYPE_STRICT: { + if (ale == NULL) { + return select_grouped_channel_type_strict; + } + + ANIM_fcurve_keyframes_loop( + ked, fcu, ok_cb, select_grouped_active_channel_type_strict, NULL); + break; + } + case GRAPHKEYS_SELECT_GROUP_HANDLE_TYPE: { + if (ale == NULL) { + return select_grouped_handle_type; + } + + ANIM_fcurve_keyframes_loop(ked, fcu, ok_cb, select_grouped_active_handle_type, NULL); + break; + } + case GRAPHKEYS_SELECT_GROUP_INTERPOLATION_TYPE: { + if (ale == NULL) { + return select_grouped_interpolation_type; + } + + ANIM_fcurve_keyframes_loop( + ked, fcu, ok_cb, select_grouped_active_interpolation_type, NULL); + break; + } + case GRAPHKEYS_SELECT_GROUP_HANDLE_SIDE: { + if (ale == NULL) { + return select_grouped_handle_side; + } + + ANIM_fcurve_keyframes_loop(ked, fcu, ok_cb, select_grouped_active_handle_side, NULL); + break; + } + case GRAPHKEYS_SELECT_GROUP_KEY_TYPE: { + if (ale == NULL) { + return select_grouped_key_type; + } + + ANIM_fcurve_keyframes_loop(ked, fcu, ok_cb, select_grouped_active_key_type, NULL); + break; + } + case GRAPHKEYS_SELECT_GROUP_MODIFIERS: { + if (ale == NULL) { + return select_grouped_modifiers; + } + + ANIM_fcurve_keyframes_loop(ked, fcu, ok_cb, select_grouped_active_modifiers, NULL); + break; + } + case GRAPHKEYS_SELECT_GROUP_DATABLOCK: { + if (ale == NULL) { + return select_grouped_datablock; + } + + short result = ANIM_fcurve_keyframes_loop( + ked, fcu, ok_cb, select_grouped_active_datablock, NULL); + if (result == 1) { + /* Found a selected key in this datablock, add its id. */ + BLI_gset_add(ked->data, ale->id); + } + break; + } + default: { + BLI_assert(0); + break; + } + } + } + + return NULL; +} + +static int graphkeys_select_grouped_exec(bContext *C, wmOperator *op) +{ + bAnimContext ac; + + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + const int type = RNA_enum_get(op->ptr, "type"); + const bool use_selected_channels = RNA_boolean_get(op->ptr, "use_selected_channels"); + + /* Filter data. */ + int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS); + if (use_selected_channels) { + /* TODO(redmser): Doing it this way means that the keyframe that we're getting the reference + * value from must also be in a selected channel. But not sure if filtering twice is a better + * solution here either. */ + filter |= ANIMFILTER_SEL; + } + + ListBase anim_data = {NULL, NULL}; + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + KeyframeEditData ked = {{NULL}}; + KeyframeEditFunc ok_cb = select_grouped_get_filter_callback(&anim_data, &ked, type); + KeyframeEditFunc select_cb = ANIM_editkeyframes_select(SELECT_ADD); + + if (!RNA_boolean_get(op->ptr, "extend")) { + /* If not extending, deselect all first. */ + deselect_graph_keys(&ac, 0, SELECT_SUBTRACT, true); + } + + bAnimListElem *ale; + SpaceGraph *sipo = (SpaceGraph *)ac.sl; + ked.iterflags |= KEYFRAME_ITER_INCL_HANDLES; + for (ale = anim_data.first; ale; ale = ale->next) { + if (ale->datatype != ALE_FCURVE) { + continue; + } + + /* Only continue if F-Curve has keyframes. */ + FCurve *fcu = (FCurve *)ale->key_data; + if (fcu->bezt == NULL) { + continue; + } + + /* Filtering by datablock has to be done on anim data level. */ + if (type == GRAPHKEYS_SELECT_GROUP_DATABLOCK && !BLI_gset_haskey(ked.data, ale->id)) { + continue; + } + + /* Check if any keyframe will be selected. */ + if (ANIM_fcurve_keyframes_loop(&ked, fcu, NULL, ok_cb, NULL)) { + ANIM_fcurve_keyframes_loop(&ked, fcu, ok_cb, select_cb, NULL); + + /* Only change selection of channel when the visibility of keyframes + * doesn't depend on this. */ + if ((sipo->flag & SIPO_SELCUVERTSONLY) == 0) { + fcu->flag |= FCURVE_SELECTED; + } + } + } + + ANIM_animdata_freelist(&anim_data); + if (type == GRAPHKEYS_SELECT_GROUP_CHANNEL_TYPE || + type == GRAPHKEYS_SELECT_GROUP_CHANNEL_TYPE_STRICT) { + BLI_gset_free(ked.data, MEM_freeN); + } + else { + BLI_gset_free(ked.data, NULL); + } + + WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_SELECTED, NULL); + return OPERATOR_FINISHED; +} + +void GRAPH_OT_select_grouped(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Select Grouped"; + ot->idname = "GRAPH_OT_select_grouped"; + ot->description = "Select all keyframes grouped by various properties"; + + /* Api callbacks. */ + ot->invoke = WM_menu_invoke; + ot->exec = graphkeys_select_grouped_exec; + ot->poll = graphop_visible_keyframes_poll; + + /* Flags. */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* Properties. */ + ot->prop = RNA_def_enum(ot->srna, + "type", + graphkeys_prop_select_grouped_types, + 0, + "Type", + "Which criterion to filter selection by"); + RNA_def_boolean(ot->srna, + "extend", + false, + "Extend", + "Extend selection instead of deselecting everything first"); + RNA_def_boolean(ot->srna, + "use_selected_channels", + false, + "Use Selected Channels", + "Only consider keyframes on the same channels as the selected ones"); +} diff --git a/source/blender/editors/space_nla/nla_intern.h b/source/blender/editors/space_nla/nla_intern.h index 33aa30f32da..e4fe281b0f1 100644 --- a/source/blender/editors/space_nla/nla_intern.h +++ b/source/blender/editors/space_nla/nla_intern.h @@ -44,6 +44,7 @@ enum eNlaEdit_LeftRightSelect_Mode { void NLA_OT_select_all(wmOperatorType *ot); void NLA_OT_select_box(wmOperatorType *ot); void NLA_OT_select_leftright(wmOperatorType *ot); +void NLA_OT_select_grouped(wmOperatorType *ot); void NLA_OT_click_select(wmOperatorType *ot); /* **************************************** */ diff --git a/source/blender/editors/space_nla/nla_ops.c b/source/blender/editors/space_nla/nla_ops.c index 3ae73282230..373103b39c6 100644 --- a/source/blender/editors/space_nla/nla_ops.c +++ b/source/blender/editors/space_nla/nla_ops.c @@ -97,6 +97,7 @@ void nla_operatortypes(void) WM_operatortype_append(NLA_OT_select_box); WM_operatortype_append(NLA_OT_select_all); WM_operatortype_append(NLA_OT_select_leftright); + WM_operatortype_append(NLA_OT_select_grouped); /* view */ WM_operatortype_append(NLA_OT_view_all); diff --git a/source/blender/editors/space_nla/nla_select.c b/source/blender/editors/space_nla/nla_select.c index 281782123d1..e72be7f14f5 100644 --- a/source/blender/editors/space_nla/nla_select.c +++ b/source/blender/editors/space_nla/nla_select.c @@ -14,6 +14,7 @@ #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" +#include "BLI_ghash.h" #include "BLI_math_base.h" #include "BKE_context.h" @@ -742,3 +743,240 @@ void NLA_OT_click_select(wmOperatorType *ot) } /* *********************************************** */ + +/* Select Grouped Operator */ + +enum { + NLAEDIT_SELECT_GROUP_BLENDING, + NLAEDIT_SELECT_GROUP_ACTION, + NLAEDIT_SELECT_GROUP_EXTRAPOLATION, + NLAEDIT_SELECT_GROUP_REVERSED, + NLAEDIT_SELECT_GROUP_DATABLOCK, + NLAEDIT_SELECT_GROUP_MUTED, +}; + +static const EnumPropertyItem nlaedit_prop_select_grouped_types[] = { + {NLAEDIT_SELECT_GROUP_BLENDING, + "BLENDING", + 0, + "Blending Mode", + "All strips with the same blending mode (replace, combine, add, etc.)"}, + {NLAEDIT_SELECT_GROUP_ACTION, + "ACTION", + 0, + "Action", + "All strips that use the same action datablock"}, + {NLAEDIT_SELECT_GROUP_EXTRAPOLATION, + "EXTRAPOLATION", + 0, + "Extrapolation Mode", + "All strips the use the same extrapolation mode (hold, hold forward, etc.)"}, + {NLAEDIT_SELECT_GROUP_REVERSED, + "REVERSED", + 0, + "Reversed", + "All strips that have the same value for the reversed option"}, + {NLAEDIT_SELECT_GROUP_DATABLOCK, + "DATABLOCK", + 0, + "Datablock", + "All strips that are part of the same datablock"}, + {NLAEDIT_SELECT_GROUP_MUTED, + "MUTED", + 0, + "Muted", + "All strips that have the same value for the muted option"}, + {0, NULL, 0, NULL, NULL}, +}; + +static void select_grouped_tag_strips(int type, bAnimListElem *ale, GSet *comp_set) +{ + NlaTrack *track = (NlaTrack *)ale->data; + + for (NlaStrip *strip = track->strips.first; strip; strip = strip->next) { + if ((strip->flag & NLASTRIP_FLAG_SELECT) == 0) { + continue; + } + + switch (type) { + case NLAEDIT_SELECT_GROUP_ACTION: { + BLI_gset_add(comp_set, strip->act); + break; + } + case NLAEDIT_SELECT_GROUP_BLENDING: { + BLI_gset_add(comp_set, POINTER_FROM_INT(strip->blendmode)); + break; + } + case NLAEDIT_SELECT_GROUP_DATABLOCK: { + BLI_gset_add(comp_set, ale->id); + + /* No need to check all strips in the track. */ + return; + } + case NLAEDIT_SELECT_GROUP_EXTRAPOLATION: { + BLI_gset_add(comp_set, POINTER_FROM_INT(strip->extendmode)); + break; + } + case NLAEDIT_SELECT_GROUP_REVERSED: { + BLI_gset_add(comp_set, POINTER_FROM_INT(strip->flag & NLASTRIP_FLAG_REVERSE)); + break; + } + case NLAEDIT_SELECT_GROUP_MUTED: { + BLI_gset_add(comp_set, POINTER_FROM_INT(strip->flag & NLASTRIP_FLAG_MUTED)); + break; + } + default: { + BLI_assert(0); + break; + } + } + } +} + +static void select_grouped_select_matching_strips(int type, bAnimListElem *ale, GSet *comp_set) +{ + NlaTrack *track = (NlaTrack *)ale->data; + + for (NlaStrip *strip = track->strips.first; strip; strip = strip->next) { + switch (type) { + case NLAEDIT_SELECT_GROUP_ACTION: { + if (!BLI_gset_haskey(comp_set, strip->act)) { + continue; + } + break; + } + case NLAEDIT_SELECT_GROUP_BLENDING: { + if (!BLI_gset_haskey(comp_set, POINTER_FROM_INT(strip->blendmode))) { + continue; + } + break; + } + case NLAEDIT_SELECT_GROUP_DATABLOCK: { + if (!BLI_gset_haskey(comp_set, ale->id)) { + /* No need to check all strips in the track. */ + return; + } + break; + } + case NLAEDIT_SELECT_GROUP_EXTRAPOLATION: { + if (!BLI_gset_haskey(comp_set, POINTER_FROM_INT(strip->extendmode))) { + continue; + } + break; + } + case NLAEDIT_SELECT_GROUP_REVERSED: { + if (!BLI_gset_haskey(comp_set, POINTER_FROM_INT(strip->flag & NLASTRIP_FLAG_REVERSE))) { + continue; + } + break; + } + case NLAEDIT_SELECT_GROUP_MUTED: { + if (!BLI_gset_haskey(comp_set, POINTER_FROM_INT(strip->flag & NLASTRIP_FLAG_MUTED))) { + continue; + } + break; + } + default: { + BLI_assert(0); + break; + } + } + + ACHANNEL_SET_FLAG(strip, SELECT_ADD, NLASTRIP_FLAG_SELECT); + } +} + +static int nlaedit_select_grouped_exec(bContext *C, wmOperator *op) +{ + bAnimContext ac; + + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + Scene *scene = ac.scene; + /* If currently in tweak-mode, exit tweak-mode first. */ + if (scene->flag & SCE_NLA_EDIT_ON) { + WM_operator_name_call(C, "NLA_OT_tweakmode_exit", WM_OP_EXEC_DEFAULT, NULL, NULL); + } + + const int type = RNA_enum_get(op->ptr, "type"); + const bool use_selected_tracks = RNA_boolean_get(op->ptr, "use_selected_tracks"); + + /* Filter data. */ + int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE); + if (use_selected_tracks) { + /* TODO(redmser): Doing it this way means that the strip that we're getting the reference + * value from must also be in a selected track. But not sure if filtering twice is a better + * solution here either. */ + filter |= ANIMFILTER_SEL; + } + + ListBase anim_data = {NULL, NULL}; + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* Collect list of selected strips' properties. */ + GSet *comp_set; + if (type == NLAEDIT_SELECT_GROUP_DATABLOCK || type == NLAEDIT_SELECT_GROUP_ACTION) { + comp_set = BLI_gset_ptr_new(__func__); + } + else { + comp_set = BLI_gset_int_new(__func__); + } + + for (bAnimListElem *ale = anim_data.first; ale; ale = ale->next) { + select_grouped_tag_strips(type, ale, comp_set); + } + + if (!RNA_boolean_get(op->ptr, "extend")) { + /* If not extending, deselect all first. */ + /* TODO(redmser): This is acting a bit weirdly... */ + deselect_nla_strips(&ac, 0, SELECT_SUBTRACT); + } + + /* Select all strips that match. */ + for (bAnimListElem *ale = anim_data.first; ale; ale = ale->next) { + select_grouped_select_matching_strips(type, ale, comp_set); + } + + ANIM_animdata_freelist(&anim_data); + BLI_gset_free(comp_set, NULL); + + WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_SELECTED, NULL); + WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_SELECTED, NULL); + return OPERATOR_FINISHED; +} + +void NLA_OT_select_grouped(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Select Grouped"; + ot->idname = "NLA_OT_select_grouped"; + ot->description = "Select all strips grouped by various properties"; + + /* Api callbacks. */ + ot->invoke = WM_menu_invoke; + ot->exec = nlaedit_select_grouped_exec; + ot->poll = ED_operator_nla_active; + + /* Flags. */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* Properties. */ + ot->prop = RNA_def_enum(ot->srna, + "type", + nlaedit_prop_select_grouped_types, + 0, + "Type", + "Which criterion to filter selection by"); + RNA_def_boolean(ot->srna, + "extend", + false, + "Extend", + "Extend selection instead of deselecting everything first"); + RNA_def_boolean(ot->srna, + "use_selected_tracks", + false, + "Use Selected Tracks", + "Only consider strips on the same track as the selected ones"); +}