diff options
21 files changed, 761 insertions, 65 deletions
diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py index 0f063da40fb..09dbd2e5334 100644 --- a/release/scripts/startup/bl_operators/wm.py +++ b/release/scripts/startup/bl_operators/wm.py @@ -21,10 +21,28 @@ from bpy.props import ( ) from bpy.app.translations import pgettext_iface as iface_ + +def rna_path_prop_search_for_context(self, context, edit_text): + # Use the same logic as auto-completing in the Python console to expand the data-path. + from bl_console_utils.autocomplete import intellisense + context_prefix = "context." + line = context_prefix + edit_text + cursor = len(line) + namespace = {"context": context} + comp_prefix, _, comp_options = intellisense.expand(line=line, cursor=len(line), namespace=namespace, private=False) + prefix = comp_prefix[len(context_prefix):] # Strip "context." + for attr in comp_options.split("\n"): + # Exclude function calls because they are generally not part of data-paths. + if attr.endswith(("(", ")")): + continue + yield prefix + attr.lstrip() + + rna_path_prop = StringProperty( name="Context Attributes", description="RNA context string", maxlen=1024, + search=rna_path_prop_search_for_context, ) rna_reverse_prop = BoolProperty( diff --git a/source/blender/blenkernel/BKE_global.h b/source/blender/blenkernel/BKE_global.h index 06feb07aef2..96b6f7a53b0 100644 --- a/source/blender/blenkernel/BKE_global.h +++ b/source/blender/blenkernel/BKE_global.h @@ -81,6 +81,7 @@ typedef struct Global { * * 1 - 30: EEVEE debug/stats values (01/2018). * * 31: Enable the Select Debug Engine. Only available with #WITH_DRAW_DEBUG (08/2021). * * 101: Enable UI debug drawing of fullscreen area's corner widget (10/2014). + * * 102: Enable extra items in string search UI (05/2022). * * 666: Use quicker batch delete for outliners' delete hierarchy (01/2019). * * 777: Enable UI node panel's sockets polling (11/2011). * * 799: Enable some mysterious new depsgraph behavior (05/2015). diff --git a/source/blender/editors/animation/keyframing.c b/source/blender/editors/animation/keyframing.c index 14a3b958ea6..941125b9ad5 100644 --- a/source/blender/editors/animation/keyframing.c +++ b/source/blender/editors/animation/keyframing.c @@ -2051,6 +2051,8 @@ void ANIM_OT_keyframe_insert_by_name(wmOperatorType *ot) /* keyingset to use (idname) */ prop = RNA_def_string( ot->srna, "type", NULL, MAX_ID_NAME - 2, "Keying Set", "The Keying Set to use"); + RNA_def_property_string_search_func_runtime( + prop, ANIM_keyingset_visit_for_search_no_poll, PROP_STRING_SEARCH_SUGGESTION); RNA_def_property_flag(prop, PROP_HIDDEN); ot->prop = prop; } @@ -2246,6 +2248,8 @@ void ANIM_OT_keyframe_delete_by_name(wmOperatorType *ot) /* keyingset to use (idname) */ prop = RNA_def_string( ot->srna, "type", NULL, MAX_ID_NAME - 2, "Keying Set", "The Keying Set to use"); + RNA_def_property_string_search_func_runtime( + prop, ANIM_keyingset_visit_for_search_no_poll, PROP_STRING_SEARCH_SUGGESTION); RNA_def_property_flag(prop, PROP_HIDDEN); ot->prop = prop; } diff --git a/source/blender/editors/animation/keyingsets.c b/source/blender/editors/animation/keyingsets.c index 6fcdd21bad8..97b81277008 100644 --- a/source/blender/editors/animation/keyingsets.c +++ b/source/blender/editors/animation/keyingsets.c @@ -708,6 +708,72 @@ KeyingSet *ANIM_get_keyingset_for_autokeying(const Scene *scene, const char *tra return ANIM_builtin_keyingset_get_named(NULL, transformKSName); } +static void anim_keyingset_visit_for_search_impl(const bContext *C, + StringPropertySearchVisitFunc visit_fn, + void *visit_user_data, + const bool use_poll) +{ + /* Poll requires context. */ + if (use_poll && (C == NULL)) { + return; + } + + Scene *scene = C ? CTX_data_scene(C) : NULL; + KeyingSet *ks; + + /* Active Keying Set. */ + if (!use_poll || (scene && scene->active_keyingset)) { + StringPropertySearchVisitParams visit_params = {NULL}; + visit_params.text = "__ACTIVE__"; + visit_params.info = "Active Keying Set"; + visit_fn(visit_user_data, &visit_params); + } + + /* User-defined Keying Sets. */ + if (scene && scene->keyingsets.first) { + for (ks = scene->keyingsets.first; ks; ks = ks->next) { + if (use_poll && !ANIM_keyingset_context_ok_poll((bContext *)C, ks)) { + continue; + } + StringPropertySearchVisitParams visit_params = {NULL}; + visit_params.text = ks->idname; + visit_params.info = ks->name; + visit_fn(visit_user_data, &visit_params); + } + } + + /* Builtin Keying Sets. */ + for (ks = builtin_keyingsets.first; ks; ks = ks->next) { + if (use_poll && !ANIM_keyingset_context_ok_poll((bContext *)C, ks)) { + continue; + } + StringPropertySearchVisitParams visit_params = {NULL}; + visit_params.text = ks->idname; + visit_params.info = ks->name; + visit_fn(visit_user_data, &visit_params); + } +} + +void ANIM_keyingset_visit_for_search(const bContext *C, + PointerRNA *UNUSED(ptr), + PropertyRNA *UNUSED(prop), + const char *UNUSED(edit_text), + StringPropertySearchVisitFunc visit_fn, + void *visit_user_data) +{ + anim_keyingset_visit_for_search_impl(C, visit_fn, visit_user_data, false); +} + +void ANIM_keyingset_visit_for_search_no_poll(const bContext *C, + PointerRNA *UNUSED(ptr), + PropertyRNA *UNUSED(prop), + const char *UNUSED(edit_text), + StringPropertySearchVisitFunc visit_fn, + void *visit_user_data) +{ + anim_keyingset_visit_for_search_impl(C, visit_fn, visit_user_data, true); +} + /* Menu of All Keying Sets ----------------------------- */ const EnumPropertyItem *ANIM_keying_sets_enum_itemf(bContext *C, diff --git a/source/blender/editors/include/ED_keyframing.h b/source/blender/editors/include/ED_keyframing.h index 6a730225da9..8c0147612fb 100644 --- a/source/blender/editors/include/ED_keyframing.h +++ b/source/blender/editors/include/ED_keyframing.h @@ -351,6 +351,19 @@ int ANIM_scene_get_keyingset_index(struct Scene *scene, struct KeyingSet *ks); struct KeyingSet *ANIM_get_keyingset_for_autokeying(const struct Scene *scene, const char *transformKSName); +void ANIM_keyingset_visit_for_search(const struct bContext *C, + struct PointerRNA *ptr, + struct PropertyRNA *prop, + const char *edit_text, + StringPropertySearchVisitFunc visit_fn, + void *visit_user_data); + +void ANIM_keyingset_visit_for_search_no_poll(const struct bContext *C, + struct PointerRNA *ptr, + struct PropertyRNA *prop, + const char *edit_text, + StringPropertySearchVisitFunc visit_fn, + void *visit_user_data); /** * Dynamically populate an enum of Keying Sets. */ diff --git a/source/blender/editors/interface/interface_layout.c b/source/blender/editors/interface/interface_layout.c index 933d7efb4d6..3465373c85d 100644 --- a/source/blender/editors/interface/interface_layout.c +++ b/source/blender/editors/interface/interface_layout.c @@ -2338,7 +2338,14 @@ void uiItemFullR(uiLayout *layout, /* property with separate label */ else if (ELEM(type, PROP_ENUM, PROP_STRING, PROP_POINTER)) { but = ui_item_with_label(layout, block, name, icon, ptr, prop, index, 0, 0, w, h, flag); - but = ui_but_add_search(but, ptr, prop, NULL, NULL, false); + bool results_are_suggestions = false; + if (type == PROP_STRING) { + const eStringPropertySearchFlag search_flag = RNA_property_string_search_flag(prop); + if (search_flag & PROP_STRING_SEARCH_SUGGESTION) { + results_are_suggestions = true; + } + } + but = ui_but_add_search(but, ptr, prop, NULL, NULL, results_are_suggestions); if (layout->redalert) { UI_but_flag_enable(but, UI_BUT_REDALERT); @@ -2711,11 +2718,16 @@ uiBut *ui_but_add_search(uiBut *but, PropertyRNA *prop, PointerRNA *searchptr, PropertyRNA *searchprop, - bool results_are_suggestions) + const bool results_are_suggestions) { /* for ID's we do automatic lookup */ + bool has_search_fn = false; + PointerRNA sptr; if (!searchprop) { + if (RNA_property_type(prop) == PROP_STRING) { + has_search_fn = (RNA_property_string_search_flag(prop) != 0); + } if (RNA_property_type(prop) == PROP_POINTER) { StructRNA *ptype = RNA_property_pointer_type(ptr, prop); search_id_collection(ptype, &sptr, &searchprop); @@ -2724,14 +2736,18 @@ uiBut *ui_but_add_search(uiBut *but, } /* turn button into search button */ - if (searchprop) { + if (has_search_fn || searchprop) { uiRNACollectionSearch *coll_search = MEM_mallocN(sizeof(*coll_search), __func__); uiButSearch *search_but; but = ui_but_change_type(but, UI_BTYPE_SEARCH_MENU); search_but = (uiButSearch *)but; - search_but->rnasearchpoin = *searchptr; - search_but->rnasearchprop = searchprop; + + if (searchptr) { + search_but->rnasearchpoin = *searchptr; + search_but->rnasearchprop = searchprop; + } + but->hardmax = MAX2(but->hardmax, 256.0f); but->drawflag |= UI_BUT_ICON_LEFT | UI_BUT_TEXT_LEFT; if (RNA_property_is_unlink(prop)) { @@ -2740,8 +2756,17 @@ uiBut *ui_but_add_search(uiBut *but, coll_search->target_ptr = *ptr; coll_search->target_prop = prop; - coll_search->search_ptr = *searchptr; - coll_search->search_prop = searchprop; + + if (searchptr) { + coll_search->search_ptr = *searchptr; + coll_search->search_prop = searchprop; + } + else { + /* Rely on `has_search_fn`. */ + coll_search->search_ptr = PointerRNA_NULL; + coll_search->search_prop = NULL; + } + coll_search->search_but = but; coll_search->butstore_block = but->block; coll_search->butstore = UI_butstore_create(coll_search->butstore_block); diff --git a/source/blender/editors/interface/interface_utils.cc b/source/blender/editors/interface/interface_utils.cc index 993ccdf92f7..b7ca2d9aa11 100644 --- a/source/blender/editors/interface/interface_utils.cc +++ b/source/blender/editors/interface/interface_utils.cc @@ -24,6 +24,7 @@ #include "BLT_translation.h" #include "BKE_context.h" +#include "BKE_global.h" #include "BKE_lib_id.h" #include "BKE_report.h" @@ -516,71 +517,136 @@ void ui_rna_collection_search_update_fn( StringSearch *search = skip_filter ? nullptr : BLI_string_search_new(); - /* build a temporary list of relevant items first */ - int item_index = 0; - RNA_PROP_BEGIN (&data->search_ptr, itemptr, data->search_prop) { + if (data->search_prop != nullptr) { + /* build a temporary list of relevant items first */ + int item_index = 0; + RNA_PROP_BEGIN (&data->search_ptr, itemptr, data->search_prop) { - if (flag & PROP_ID_SELF_CHECK) { - if (itemptr.data == data->target_ptr.owner_id) { - continue; + if (flag & PROP_ID_SELF_CHECK) { + if (itemptr.data == data->target_ptr.owner_id) { + continue; + } } - } - /* use filter */ - if (is_ptr_target) { - if (RNA_property_pointer_poll(&data->target_ptr, data->target_prop, &itemptr) == 0) { - continue; + /* use filter */ + if (is_ptr_target) { + if (RNA_property_pointer_poll(&data->target_ptr, data->target_prop, &itemptr) == 0) { + continue; + } } - } - int name_prefix_offset = 0; - int iconid = ICON_NONE; - bool has_sep_char = false; - const bool is_id = itemptr.type && RNA_struct_is_ID(itemptr.type); + int name_prefix_offset = 0; + int iconid = ICON_NONE; + bool has_sep_char = false; + const bool is_id = itemptr.type && RNA_struct_is_ID(itemptr.type); - if (is_id) { - iconid = ui_id_icon_get(C, static_cast<ID *>(itemptr.data), false); - if (!ELEM(iconid, 0, ICON_BLANK1)) { - has_id_icon = true; - } + if (is_id) { + iconid = ui_id_icon_get(C, static_cast<ID *>(itemptr.data), false); + if (!ELEM(iconid, 0, ICON_BLANK1)) { + has_id_icon = true; + } - if (requires_exact_data_name) { - name = RNA_struct_name_get_alloc(&itemptr, name_buf, sizeof(name_buf), nullptr); + if (requires_exact_data_name) { + name = RNA_struct_name_get_alloc(&itemptr, name_buf, sizeof(name_buf), nullptr); + } + else { + const ID *id = static_cast<ID *>(itemptr.data); + BKE_id_full_name_ui_prefix_get(name_buf, id, true, UI_SEP_CHAR, &name_prefix_offset); + BLI_STATIC_ASSERT(sizeof(name_buf) >= MAX_ID_FULL_NAME_UI, + "Name string buffer should be big enough to hold full UI ID name"); + name = name_buf; + has_sep_char = ID_IS_LINKED(id); + } } else { - const ID *id = static_cast<ID *>(itemptr.data); - BKE_id_full_name_ui_prefix_get(name_buf, id, true, UI_SEP_CHAR, &name_prefix_offset); - BLI_STATIC_ASSERT(sizeof(name_buf) >= MAX_ID_FULL_NAME_UI, - "Name string buffer should be big enough to hold full UI ID name"); - name = name_buf; - has_sep_char = ID_IS_LINKED(id); + name = RNA_struct_name_get_alloc(&itemptr, name_buf, sizeof(name_buf), nullptr); } - } - else { - name = RNA_struct_name_get_alloc(&itemptr, name_buf, sizeof(name_buf), nullptr); - } - if (name) { - CollItemSearch *cis = MEM_cnew<CollItemSearch>(__func__); - cis->data = itemptr.data; - cis->name = BLI_strdup(name); - cis->index = item_index; - cis->iconid = iconid; - cis->is_id = is_id; - cis->name_prefix_offset = name_prefix_offset; - cis->has_sep_char = has_sep_char; - if (!skip_filter) { - BLI_string_search_add(search, name, cis, 0); + if (name) { + CollItemSearch *cis = MEM_cnew<CollItemSearch>(__func__); + cis->data = itemptr.data; + cis->name = BLI_strdup(name); + cis->index = item_index; + cis->iconid = iconid; + cis->is_id = is_id; + cis->name_prefix_offset = name_prefix_offset; + cis->has_sep_char = has_sep_char; + if (!skip_filter) { + BLI_string_search_add(search, name, cis, 0); + } + BLI_addtail(items_list, cis); + if (name != name_buf) { + MEM_freeN(name); + } } - BLI_addtail(items_list, cis); - if (name != name_buf) { - MEM_freeN(name); + + item_index++; + } + RNA_PROP_END; + } + else { + BLI_assert(RNA_property_type(data->target_prop) == PROP_STRING); + const eStringPropertySearchFlag search_flag = RNA_property_string_search_flag( + data->target_prop); + BLI_assert(search_flag & PROP_STRING_SEARCH_SUPPORTED); + + struct SearchVisitUserData { + StringSearch *search; + bool skip_filter; + int item_index; + ListBase *items_list; + const char *func_id; + } user_data = {nullptr}; + + user_data.search = search; + user_data.skip_filter = skip_filter; + user_data.items_list = items_list; + user_data.func_id = __func__; + + RNA_property_string_search( + C, + &data->target_ptr, + data->target_prop, + str, + [](void *user_data, const StringPropertySearchVisitParams *visit_params) { + const bool show_extra_info = (G.debug_value == 102); + + SearchVisitUserData *search_data = (struct SearchVisitUserData *)user_data; + CollItemSearch *cis = MEM_cnew<CollItemSearch>(search_data->func_id); + cis->data = nullptr; + if (visit_params->info && show_extra_info) { + cis->name = BLI_sprintfN( + "%s" UI_SEP_CHAR_S "%s", visit_params->text, visit_params->info); + } + else { + cis->name = BLI_strdup(visit_params->text); + } + cis->index = search_data->item_index; + cis->iconid = ICON_NONE; + cis->is_id = false; + cis->name_prefix_offset = 0; + cis->has_sep_char = visit_params->info != nullptr; + if (!search_data->skip_filter) { + BLI_string_search_add(search_data->search, visit_params->text, cis, 0); + } + BLI_addtail(search_data->items_list, cis); + search_data->item_index++; + }, + (void *)&user_data); + + if (search_flag & PROP_STRING_SEARCH_SORT) { + BLI_listbase_sort(items_list, [](const void *a_, const void *b_) -> int { + const CollItemSearch *cis_a = (const CollItemSearch *)a_; + const CollItemSearch *cis_b = (const CollItemSearch *)b_; + return BLI_strcasecmp_natural(cis_a->name, cis_b->name); + }); + int i = 0; + LISTBASE_FOREACH (CollItemSearch *, cis, items_list) { + cis->index = i; + i++; } } - - item_index++; } - RNA_PROP_END; if (skip_filter) { LISTBASE_FOREACH (CollItemSearch *, cis, items_list) { diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index f0a60929431..4267ce47d81 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -358,6 +358,21 @@ char *RNA_property_string_get_alloc( PointerRNA *ptr, PropertyRNA *prop, char *fixedbuf, int fixedlen, int *r_len); void RNA_property_string_set(PointerRNA *ptr, PropertyRNA *prop, const char *value); void RNA_property_string_set_bytes(PointerRNA *ptr, PropertyRNA *prop, const char *value, int len); + +eStringPropertySearchFlag RNA_property_string_search_flag(PropertyRNA *prop); +/** + * Search candidates for string `prop` by calling `visit_fn` with each string. + * Typically these strings are collected in `visit_user_data` in a format defined by the caller. + * + * See #PropStringSearchFunc for details. + */ +void RNA_property_string_search(const struct bContext *C, + PointerRNA *ptr, + PropertyRNA *prop, + const char *edit_text, + StringPropertySearchVisitFunc visit_fn, + void *visit_user_data); + /** * \return the length without `\0` terminator. */ diff --git a/source/blender/makesrna/RNA_define.h b/source/blender/makesrna/RNA_define.h index 13a5ec66a16..0389d1b3b16 100644 --- a/source/blender/makesrna/RNA_define.h +++ b/source/blender/makesrna/RNA_define.h @@ -446,6 +446,9 @@ void RNA_def_property_string_funcs(PropertyRNA *prop, const char *get, const char *length, const char *set); +void RNA_def_property_string_search_func(PropertyRNA *prop, + const char *search, + eStringPropertySearchFlag search_flag); void RNA_def_property_pointer_funcs( PropertyRNA *prop, const char *get, const char *set, const char *type_fn, const char *poll); void RNA_def_property_collection_funcs(PropertyRNA *prop, @@ -490,6 +493,9 @@ void RNA_def_property_string_funcs_runtime(PropertyRNA *prop, StringPropertyGetFunc getfunc, StringPropertyLengthFunc lengthfunc, StringPropertySetFunc setfunc); +void RNA_def_property_string_search_func_runtime(PropertyRNA *prop, + StringPropertySearchFunc search_fn, + eStringPropertySearchFlag search_flag); void RNA_def_property_translation_context(PropertyRNA *prop, const char *context); diff --git a/source/blender/makesrna/RNA_types.h b/source/blender/makesrna/RNA_types.h index 3ebcae5f947..75b514cdb13 100644 --- a/source/blender/makesrna/RNA_types.h +++ b/source/blender/makesrna/RNA_types.h @@ -515,6 +515,55 @@ typedef int (*StringPropertyLengthFunc)(struct PointerRNA *ptr, struct PropertyR typedef void (*StringPropertySetFunc)(struct PointerRNA *ptr, struct PropertyRNA *prop, const char *value); + +typedef struct StringPropertySearchVisitParams { + /** Text being searched for (never NULL). */ + const char *text; + /** Additional information to display (optional, may be NULL). */ + const char *info; +} StringPropertySearchVisitParams; + +typedef enum eStringPropertySearchFlag { + /** + * Used so the result of #RNA_property_string_search_flag can be used to check + * if search is supported. + */ + PROP_STRING_SEARCH_SUPPORTED = (1 << 0), + /** Items resulting from the search must be sorted. */ + PROP_STRING_SEARCH_SORT = (1 << 1), + /** + * Allow members besides the ones listed to be entered. + * + * \warning disabling this options causes the search callback to run on redraw and should + * only be enabled this doesn't cause performance issues. + */ + PROP_STRING_SEARCH_SUGGESTION = (1 << 2), +} eStringPropertySearchFlag; + +/** + * Visit string search candidates, `text` may be freed once this callback has finished, + * so references to it should not be held. + */ +typedef void (*StringPropertySearchVisitFunc)(void *visit_user_data, + const StringPropertySearchVisitParams *params); +/** + * \param C: context, may be NULL (in this case all available items should be shown). + * \param ptr: RNA pointer. + * \param prop: RNA property. This must have it's #StringPropertyRNA.search callback set, + * to check this use `RNA_property_string_search_flag(prop) & PROP_STRING_SEARCH_SUPPORTED`. + * \param edit_text: Optionally use the string being edited by the user as a basis + * for the search results (auto-complete Python attributes for e.g.). + * \param visit_fn: This function is called with every search candidate and is typically + * responsible for storing the search results. + * \param visit_user_data: Caller defined data, passed to `visit_fn`. + */ +typedef void (*StringPropertySearchFunc)(const struct bContext *C, + struct PointerRNA *ptr, + struct PropertyRNA *prop, + const char *edit_text, + StringPropertySearchVisitFunc visit_fn, + void *visit_user_data); + typedef int (*EnumPropertyGetFunc)(struct PointerRNA *ptr, struct PropertyRNA *prop); typedef void (*EnumPropertySetFunc)(struct PointerRNA *ptr, struct PropertyRNA *prop, int value); /* same as PropEnumItemFunc */ diff --git a/source/blender/makesrna/intern/makesrna.c b/source/blender/makesrna/intern/makesrna.c index c3ca57b38bf..400944d60d4 100644 --- a/source/blender/makesrna/intern/makesrna.c +++ b/source/blender/makesrna/intern/makesrna.c @@ -1039,6 +1039,38 @@ static void rna_clamp_value(FILE *f, PropertyRNA *prop, int array) } } +static char *rna_def_property_search_func(FILE *f, + StructRNA *srna, + PropertyRNA *prop, + PropertyDefRNA *UNUSED(dp), + const char *manualfunc) +{ + char *func; + + if (prop->flag & PROP_IDPROPERTY && manualfunc == NULL) { + return NULL; + } + if (!manualfunc) { + return NULL; + } + + func = rna_alloc_function_name(srna->identifier, rna_safe_id(prop->identifier), "search"); + + fprintf(f, + "void %s(" + "const bContext *C, " + "PointerRNA *ptr, " + "PropertyRNA *prop, " + "const char *edit_text, " + "StringPropertySearchVisitFunc visit_fn, " + "void *visit_user_data)\n", + func); + fprintf(f, "{\n"); + fprintf(f, "\n %s(C, ptr, prop, edit_text, visit_fn, visit_user_data);\n", manualfunc); + fprintf(f, "}\n\n"); + return func; +} + static char *rna_def_property_set_func( FILE *f, StructRNA *srna, PropertyRNA *prop, PropertyDefRNA *dp, const char *manualfunc) { @@ -1895,6 +1927,8 @@ static void rna_def_property_funcs(FILE *f, StructRNA *srna, PropertyDefRNA *dp) sprop->length = (void *)rna_def_property_length_func( f, srna, prop, dp, (const char *)sprop->length); sprop->set = (void *)rna_def_property_set_func(f, srna, prop, dp, (const char *)sprop->set); + sprop->search = (void *)rna_def_property_search_func( + f, srna, prop, dp, (const char *)sprop->search); break; } case PROP_POINTER: { @@ -4081,13 +4115,15 @@ static void rna_generate_property(FILE *f, StructRNA *srna, const char *nest, Pr case PROP_STRING: { StringPropertyRNA *sprop = (StringPropertyRNA *)prop; fprintf(f, - "\t%s, %s, %s, %s, %s, %s, %d, ", + "\t%s, %s, %s, %s, %s, %s, %s, %d, %d, ", rna_function_string(sprop->get), rna_function_string(sprop->length), rna_function_string(sprop->set), rna_function_string(sprop->get_ex), rna_function_string(sprop->length_ex), rna_function_string(sprop->set_ex), + rna_function_string(sprop->search), + (int)sprop->search_flag, sprop->maxlength); rna_print_c_string(f, sprop->defaultvalue); fprintf(f, "\n"); diff --git a/source/blender/makesrna/intern/rna_access.c b/source/blender/makesrna/intern/rna_access.c index ccb7e1c69bc..c8cb0b7ffb8 100644 --- a/source/blender/makesrna/intern/rna_access.c +++ b/source/blender/makesrna/intern/rna_access.c @@ -3369,6 +3369,34 @@ int RNA_property_string_default_length(PointerRNA *UNUSED(ptr), PropertyRNA *pro return strlen(sprop->defaultvalue); } +eStringPropertySearchFlag RNA_property_string_search_flag(PropertyRNA *prop) +{ + StringPropertyRNA *sprop = (StringPropertyRNA *)rna_ensure_property(prop); + if (prop->magic != RNA_MAGIC) { + return false; + } + BLI_assert(RNA_property_type(prop) == PROP_STRING); + if (sprop->search) { + BLI_assert(sprop->search_flag & PROP_STRING_SEARCH_SUPPORTED); + } + else { + BLI_assert(sprop->search_flag == 0); + } + return sprop->search_flag; +} + +void RNA_property_string_search(const bContext *C, + PointerRNA *ptr, + PropertyRNA *prop, + const char *edit_text, + StringPropertySearchVisitFunc visit_fn, + void *visit_user_data) +{ + BLI_assert(RNA_property_string_search_flag(prop) & PROP_STRING_SEARCH_SUPPORTED); + StringPropertyRNA *sprop = (StringPropertyRNA *)rna_ensure_property(prop); + sprop->search(C, ptr, prop, edit_text, visit_fn, visit_user_data); +} + int RNA_property_enum_get(PointerRNA *ptr, PropertyRNA *prop) { EnumPropertyRNA *eprop = (EnumPropertyRNA *)prop; diff --git a/source/blender/makesrna/intern/rna_define.c b/source/blender/makesrna/intern/rna_define.c index 1e950b883ab..dfb551fcb05 100644 --- a/source/blender/makesrna/intern/rna_define.c +++ b/source/blender/makesrna/intern/rna_define.c @@ -3316,6 +3316,33 @@ void RNA_def_property_string_funcs(PropertyRNA *prop, } } +void RNA_def_property_string_search_func(PropertyRNA *prop, + const char *search, + const eStringPropertySearchFlag search_flag) +{ + StructRNA *srna = DefRNA.laststruct; + + if (!DefRNA.preprocess) { + CLOG_ERROR(&LOG, "only during preprocessing."); + return; + } + + switch (prop->type) { + case PROP_STRING: { + StringPropertyRNA *sprop = (StringPropertyRNA *)prop; + sprop->search = (StringPropertySearchFunc)search; + if (search != NULL) { + sprop->search_flag = search_flag | PROP_STRING_SEARCH_SUPPORTED; + } + break; + } + default: + CLOG_ERROR(&LOG, "\"%s.%s\", type is not string.", srna->identifier, prop->identifier); + DefRNA.error = true; + break; + } +} + void RNA_def_property_string_funcs_runtime(PropertyRNA *prop, StringPropertyGetFunc getfunc, StringPropertyLengthFunc lengthfunc, @@ -3343,6 +3370,18 @@ void RNA_def_property_string_funcs_runtime(PropertyRNA *prop, } } +void RNA_def_property_string_search_func_runtime(PropertyRNA *prop, + StringPropertySearchFunc search_fn, + const eStringPropertySearchFlag search_flag) +{ + StringPropertyRNA *sprop = (StringPropertyRNA *)prop; + + sprop->search = search_fn; + if (search_fn != NULL) { + sprop->search_flag = search_flag | PROP_STRING_SEARCH_SUPPORTED; + } +} + void RNA_def_property_pointer_funcs( PropertyRNA *prop, const char *get, const char *set, const char *type_fn, const char *poll) { diff --git a/source/blender/makesrna/intern/rna_internal_types.h b/source/blender/makesrna/intern/rna_internal_types.h index a5b719cf753..4f88959b5ba 100644 --- a/source/blender/makesrna/intern/rna_internal_types.h +++ b/source/blender/makesrna/intern/rna_internal_types.h @@ -440,6 +440,15 @@ typedef struct StringPropertyRNA { PropStringLengthFuncEx length_ex; PropStringSetFuncEx set_ex; + /** + * Optional callback to list candidates for a string. + * This is only for use as suggestions in UI, other values may be assigned. + * + * \note The callback type is public, hence the difference in naming convention. + */ + StringPropertySearchFunc search; + eStringPropertySearchFlag search_flag; + int maxlength; /* includes string terminator! */ const char *defaultvalue; diff --git a/source/blender/makesrna/intern/rna_wm.c b/source/blender/makesrna/intern/rna_wm.c index ac1b7e53a9b..c80d0e2da39 100644 --- a/source/blender/makesrna/intern/rna_wm.c +++ b/source/blender/makesrna/intern/rna_wm.c @@ -2599,6 +2599,9 @@ static void rna_def_keyconfig(BlenderRNA *brna) "rna_wmKeyMapItem_idname_get", "rna_wmKeyMapItem_idname_length", "rna_wmKeyMapItem_idname_set"); + RNA_def_property_string_search_func(prop, + "WM_operatortype_idname_visit_for_search", + PROP_STRING_SEARCH_SORT | PROP_STRING_SEARCH_SUGGESTION); RNA_def_struct_name_property(srna, prop); RNA_def_property_update(prop, 0, "rna_KeyMapItem_update"); diff --git a/source/blender/python/intern/bpy_props.c b/source/blender/python/intern/bpy_props.c index a6aa1f46b0c..f4ebc68b5ef 100644 --- a/source/blender/python/intern/bpy_props.c +++ b/source/blender/python/intern/bpy_props.c @@ -191,6 +191,17 @@ static const EnumPropertyItem property_subtype_array_items[] = { "'XYZ', 'XYZ_LENGTH', 'COLOR_GAMMA', 'COORDINATES', 'LAYER', 'LAYER_MEMBER', 'NONE'].\n" \ " :type subtype: string\n" +static const EnumPropertyItem property_string_search_options_items[] = { + {PROP_STRING_SEARCH_SORT, "SORT", 0, "Sort Search Results", ""}, + {PROP_STRING_SEARCH_SUGGESTION, + "SUGGESTION", + 0, + "Suggestion", + "Search results are suggestions (other values may be entered)"}, + + {0, NULL, 0, NULL, NULL}, +}; + /** \} */ /* -------------------------------------------------------------------- */ @@ -257,6 +268,11 @@ struct BPyPropStore { /** Wrap: #RNA_def_property_poll_runtime */ PyObject *poll_fn; } pointer_data; + /** #PROP_STRING type. */ + struct { + /** Wrap: #RNA_def_property_string_search_func_runtime */ + PyObject *search_fn; + } string_data; }; } py_data; }; @@ -1672,6 +1688,163 @@ static void bpy_prop_string_set_fn(struct PointerRNA *ptr, } } +static bool bpy_prop_string_visit_fn_call(PyObject *py_func, + PyObject *item, + StringPropertySearchVisitFunc visit_fn, + void *visit_user_data) +{ + const char *text; + const char *info = NULL; + + if (PyTuple_CheckExact(item)) { + /* Positional only. */ + static const char *_keywords[] = { + "", + "", + NULL, + }; + static _PyArg_Parser _parser = { + "s" /* `text` */ + "s" /* `info` */ + ":search", + _keywords, + 0, + }; + if (!_PyArg_ParseTupleAndKeywordsFast(item, NULL, &_parser, &text, &info)) { + PyC_Err_PrintWithFunc(py_func); + return false; + } + } + else { + text = PyUnicode_AsUTF8(item); + if (UNLIKELY(text == NULL)) { + PyErr_Clear(); + PyErr_Format(PyExc_TypeError, + "expected sequence of strings or tuple pairs of strings, not %.200s", + Py_TYPE(item)->tp_name); + PyC_Err_PrintWithFunc(py_func); + return false; + } + } + + StringPropertySearchVisitParams visit_params = {NULL}; + visit_params.text = text; + visit_params.info = info; + visit_fn(visit_user_data, &visit_params); + return true; +} + +static void bpy_prop_string_visit_for_search_fn(const struct bContext *C, + struct PointerRNA *ptr, + struct PropertyRNA *prop, + const char *edit_text, + StringPropertySearchVisitFunc visit_fn, + void *visit_user_data) +{ + struct BPyPropStore *prop_store = RNA_property_py_data_get(prop); + PyObject *py_func; + PyObject *args; + PyObject *self; + PyObject *ret; + PyGILState_STATE gilstate; + PyObject *py_edit_text; + + BLI_assert(prop_store != NULL); + + if (C) { + bpy_context_set((struct bContext *)C, &gilstate); + } + else { + gilstate = PyGILState_Ensure(); + } + + py_func = prop_store->py_data.string_data.search_fn; + + args = PyTuple_New(3); + self = pyrna_struct_as_instance(ptr); + PyTuple_SET_ITEM(args, 0, self); + + Py_INCREF(bpy_context_module); + PyTuple_SET_ITEM(args, 1, (PyObject *)bpy_context_module); + + py_edit_text = PyUnicode_FromString(edit_text); + PyTuple_SET_ITEM(args, 2, py_edit_text); + + ret = PyObject_CallObject(py_func, args); + + Py_DECREF(args); + + if (ret == NULL) { + PyC_Err_PrintWithFunc(py_func); + } + else { + if (PyIter_Check(ret)) { + /* Iterators / generator types. */ + PyObject *it; + PyObject *(*iternext)(PyObject *); + it = PyObject_GetIter(ret); + if (it == NULL) { + PyC_Err_PrintWithFunc(py_func); + } + else { + iternext = *Py_TYPE(it)->tp_iternext; + for (;;) { + PyObject *py_text = iternext(it); + if (py_text == NULL) { + break; + } + const bool ok = bpy_prop_string_visit_fn_call( + py_func, py_text, visit_fn, visit_user_data); + Py_DECREF(py_text); + if (!ok) { + break; + } + } + Py_DECREF(it); + if (PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_StopIteration)) { + PyErr_Clear(); + } + else { + PyC_Err_PrintWithFunc(py_func); + } + } + } + } + else { + /* Sequence (typically list/tuple). */ + PyObject *ret_fast = PySequence_Fast( + ret, + "StringProperty(...): " + "return value from search callback was not a sequence, iterator or generator"); + if (ret_fast == NULL) { + PyC_Err_PrintWithFunc(py_func); + } + else { + const Py_ssize_t ret_num = PySequence_Fast_GET_SIZE(ret_fast); + PyObject **ret_fast_items = PySequence_Fast_ITEMS(ret_fast); + for (Py_ssize_t i = 0; i < ret_num; i++) { + const bool ok = bpy_prop_string_visit_fn_call( + py_func, ret_fast_items[i], visit_fn, visit_user_data); + if (!ok) { + break; + } + } + Py_DECREF(ret_fast); + } + } + + Py_DECREF(ret); + } + + if (C) { + bpy_context_clear((struct bContext *)C, &gilstate); + } + else { + PyGILState_Release(gilstate); + } +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -2352,11 +2525,14 @@ static void bpy_prop_callback_assign_float_array(struct PropertyRNA *prop, static void bpy_prop_callback_assign_string(struct PropertyRNA *prop, PyObject *get_fn, - PyObject *set_fn) + PyObject *set_fn, + PyObject *search_fn, + const eStringPropertySearchFlag search_flag) { StringPropertyGetFunc rna_get_fn = NULL; StringPropertyLengthFunc rna_length_fn = NULL; StringPropertySetFunc rna_set_fn = NULL; + StringPropertySearchFunc rna_search_fn = NULL; if (get_fn && get_fn != Py_None) { struct BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); @@ -2372,8 +2548,17 @@ static void bpy_prop_callback_assign_string(struct PropertyRNA *prop, rna_set_fn = bpy_prop_string_set_fn; ASSIGN_PYOBJECT_INCREF(prop_store->py_data.set_fn, set_fn); } + if (search_fn) { + struct BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); + + rna_search_fn = bpy_prop_string_visit_for_search_fn; + ASSIGN_PYOBJECT_INCREF(prop_store->py_data.string_data.search_fn, search_fn); + } RNA_def_property_string_funcs_runtime(prop, rna_get_fn, rna_length_fn, rna_set_fn); + if (rna_search_fn) { + RNA_def_property_string_search_func_runtime(prop, rna_search_fn, search_flag); + } } static void bpy_prop_callback_assign_enum(struct PropertyRNA *prop, @@ -2628,6 +2813,24 @@ static int bpy_prop_arg_parse_tag_defines(PyObject *o, void *p) " This function must take 2 values (self, value) and return None.\n" \ " :type set: function\n" +#define BPY_PROPDEF_SEARCH_DOC \ + " :arg search: Function to be called to show candidates for this string (shown in the UI).\n" \ + " This function must take 3 values (self, context, edit_text)\n" \ + " and return a sequence, iterator or generator where each item must be:\n" \ + "\n" \ + " - A single string (representing a candidate to display).\n" \ + " - A tuple-pair of strings, where the first is a candidate and the second\n" \ + " is additional information about the candidate.\n" \ + " :type search: function\n" \ + " :arg search_options: Set of strings in:\n" \ + "\n" \ + " - 'SORT' sorts the resulting items.\n" \ + " - 'SUGGESTION' lets the user enter values not found in search candidates.\n" \ + " **WARNING** disabling this flag causes the search callback to run on redraw,\n" \ + " so only disable this flag if it's not likely to cause performance issues.\n" \ + "\n" \ + " :type search_options: set\n" + #define BPY_PROPDEF_POINTER_TYPE_DOC \ " :arg type: A subclass of :class:`bpy.types.PropertyGroup` or :class:`bpy.types.ID`.\n" \ " :type type: class\n" @@ -3721,7 +3924,9 @@ PyDoc_STRVAR(BPy_StringProperty_doc, "subtype='NONE', " "update=None, " "get=None, " - "set=None)\n" + "set=None, " + "search=None, " + "search_options={'SUGGESTION'})\n" "\n" " Returns a new string property definition.\n" "\n" BPY_PROPDEF_NAME_DOC BPY_PROPDEF_DESC_DOC @@ -3730,7 +3935,7 @@ PyDoc_STRVAR(BPy_StringProperty_doc, " :arg maxlen: maximum length of the string.\n" " :type maxlen: int\n" BPY_PROPDEF_OPTIONS_DOC BPY_PROPDEF_OPTIONS_OVERRIDE_DOC BPY_PROPDEF_TAGS_DOC BPY_PROPDEF_SUBTYPE_STRING_DOC BPY_PROPDEF_UPDATE_DOC - BPY_PROPDEF_GET_DOC BPY_PROPDEF_SET_DOC); + BPY_PROPDEF_GET_DOC BPY_PROPDEF_SET_DOC BPY_PROPDEF_SEARCH_DOC); static PyObject *BPy_StringProperty(PyObject *self, PyObject *args, PyObject *kw) { StructRNA *srna; @@ -3767,6 +3972,11 @@ static PyObject *BPy_StringProperty(PyObject *self, PyObject *args, PyObject *kw PyObject *update_fn = NULL; PyObject *get_fn = NULL; PyObject *set_fn = NULL; + PyObject *search_fn = NULL; + static struct BPy_EnumProperty_Parse search_options_enum = { + .items = property_string_search_options_items, + .value = PROP_STRING_SEARCH_SUGGESTION, + }; static const char *_keywords[] = { "attr", @@ -3781,6 +3991,8 @@ static PyObject *BPy_StringProperty(PyObject *self, PyObject *args, PyObject *kw "update", "get", "set", + "search", + "search_options", NULL, }; static _PyArg_Parser _parser = { @@ -3797,6 +4009,8 @@ static PyObject *BPy_StringProperty(PyObject *self, PyObject *args, PyObject *kw "O" /* `update` */ "O" /* `get` */ "O" /* `set` */ + "O" /* `search` */ + "O&" /* `search_options` */ ":StringProperty", _keywords, 0, @@ -3820,7 +4034,10 @@ static PyObject *BPy_StringProperty(PyObject *self, PyObject *args, PyObject *kw &subtype_enum, &update_fn, &get_fn, - &set_fn)) { + &set_fn, + &search_fn, + pyrna_enum_bitfield_parse_set, + &search_options_enum)) { return NULL; } @@ -3833,6 +4050,9 @@ static PyObject *BPy_StringProperty(PyObject *self, PyObject *args, PyObject *kw if (bpy_prop_callback_check(set_fn, "set", 2) == -1) { return NULL; } + if (bpy_prop_callback_check(set_fn, "search", 3) == -1) { + return NULL; + } if (id_data.prop_free_handle != NULL) { RNA_def_property_free_identifier_deferred_finish(srna, id_data.prop_free_handle); @@ -3858,7 +4078,7 @@ static PyObject *BPy_StringProperty(PyObject *self, PyObject *args, PyObject *kw bpy_prop_assign_flag_override(prop, override_enum.value); } bpy_prop_callback_assign_update(prop, update_fn); - bpy_prop_callback_assign_string(prop, get_fn, set_fn); + bpy_prop_callback_assign_string(prop, get_fn, set_fn, search_fn, search_options_enum.value); RNA_def_property_duplicate_pointers(srna, prop); Py_RETURN_NONE; diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index 1ed3174ee6f..dda60975a96 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -974,6 +974,14 @@ bool WM_operatortype_remove(const char *idname); * Remove memory of all previously executed tools. */ void WM_operatortype_last_properties_clear_all(void); + +void WM_operatortype_idname_visit_for_search(const struct bContext *C, + PointerRNA *ptr, + PropertyRNA *prop, + const char *edit_text, + StringPropertySearchVisitFunc visit_fn, + void *visit_user_data); + /** * Tag all operator-properties of \a ot defined after calling this, until * the next #WM_operatortype_props_advanced_end call (if available), with @@ -1077,6 +1085,13 @@ void WM_menutype_freelink(struct MenuType *mt); void WM_menutype_free(void); bool WM_menutype_poll(struct bContext *C, struct MenuType *mt); +void WM_menutype_idname_visit_for_search(const struct bContext *C, + struct PointerRNA *ptr, + struct PropertyRNA *prop, + const char *edit_text, + StringPropertySearchVisitFunc visit_fn, + void *visit_user_data); + /* wm_panel_type.c */ /** @@ -1088,6 +1103,13 @@ struct PanelType *WM_paneltype_find(const char *idname, bool quiet); bool WM_paneltype_add(struct PanelType *pt); void WM_paneltype_remove(struct PanelType *pt); +void WM_paneltype_idname_visit_for_search(const struct bContext *C, + struct PointerRNA *ptr, + struct PropertyRNA *prop, + const char *edit_text, + StringPropertySearchVisitFunc visit_fn, + void *visit_user_data); + /* wm_gesture_ops.c */ int WM_gesture_box_invoke(struct bContext *C, struct wmOperator *op, const struct wmEvent *event); diff --git a/source/blender/windowmanager/intern/wm_menu_type.c b/source/blender/windowmanager/intern/wm_menu_type.c index 9e50ebb1ce5..b4cf5a79cfa 100644 --- a/source/blender/windowmanager/intern/wm_menu_type.c +++ b/source/blender/windowmanager/intern/wm_menu_type.c @@ -99,3 +99,21 @@ bool WM_menutype_poll(bContext *C, MenuType *mt) } return true; } + +void WM_menutype_idname_visit_for_search(const bContext *UNUSED(C), + PointerRNA *UNUSED(ptr), + PropertyRNA *UNUSED(prop), + const char *UNUSED(edit_text), + StringPropertySearchVisitFunc visit_fn, + void *visit_user_data) +{ + GHashIterator gh_iter; + GHASH_ITER (gh_iter, menutypes_hash) { + MenuType *mt = BLI_ghashIterator_getValue(&gh_iter); + + StringPropertySearchVisitParams visit_params = {NULL}; + visit_params.text = mt->idname; + visit_params.info = mt->label; + visit_fn(visit_user_data, &visit_params); + } +} diff --git a/source/blender/windowmanager/intern/wm_operator_type.c b/source/blender/windowmanager/intern/wm_operator_type.c index 8aa8469f0bc..d1c27504628 100644 --- a/source/blender/windowmanager/intern/wm_operator_type.c +++ b/source/blender/windowmanager/intern/wm_operator_type.c @@ -246,6 +246,27 @@ void WM_operatortype_last_properties_clear_all(void) } } +void WM_operatortype_idname_visit_for_search(const bContext *UNUSED(C), + PointerRNA *UNUSED(ptr), + PropertyRNA *UNUSED(prop), + const char *UNUSED(edit_text), + StringPropertySearchVisitFunc visit_fn, + void *visit_user_data) +{ + GHashIterator gh_iter; + GHASH_ITER (gh_iter, global_ops_hash) { + wmOperatorType *ot = BLI_ghashIterator_getValue(&gh_iter); + + char idname_py[OP_MAX_TYPENAME]; + WM_operator_py_idname(idname_py, ot->idname); + + StringPropertySearchVisitParams visit_params = {NULL}; + visit_params.text = idname_py; + visit_params.info = ot->name; + visit_fn(visit_user_data, &visit_params); + } +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c index c3f2f9d15ca..307d3282659 100644 --- a/source/blender/windowmanager/intern/wm_operators.c +++ b/source/blender/windowmanager/intern/wm_operators.c @@ -1870,7 +1870,14 @@ static void WM_OT_call_menu(wmOperatorType *ot) ot->flag = OPTYPE_INTERNAL; - RNA_def_string(ot->srna, "name", NULL, BKE_ST_MAXNAME, "Name", "Name of the menu"); + PropertyRNA *prop; + + prop = RNA_def_string(ot->srna, "name", NULL, BKE_ST_MAXNAME, "Name", "Name of the menu"); + RNA_def_property_string_search_func_runtime( + prop, + WM_menutype_idname_visit_for_search, + /* Only a suggestion as menu items may be referenced from add-ons that have been disabled. */ + (PROP_STRING_SEARCH_SORT | PROP_STRING_SEARCH_SUGGESTION)); } static int wm_call_pie_menu_invoke(bContext *C, wmOperator *op, const wmEvent *event) @@ -1902,7 +1909,14 @@ static void WM_OT_call_menu_pie(wmOperatorType *ot) ot->flag = OPTYPE_INTERNAL; - RNA_def_string(ot->srna, "name", NULL, BKE_ST_MAXNAME, "Name", "Name of the pie menu"); + PropertyRNA *prop; + + prop = RNA_def_string(ot->srna, "name", NULL, BKE_ST_MAXNAME, "Name", "Name of the pie menu"); + RNA_def_property_string_search_func_runtime( + prop, + WM_menutype_idname_visit_for_search, + /* Only a suggestion as menu items may be referenced from add-ons that have been disabled. */ + (PROP_STRING_SEARCH_SORT | PROP_STRING_SEARCH_SUGGESTION)); } static int wm_call_panel_exec(bContext *C, wmOperator *op) @@ -1938,6 +1952,11 @@ static void WM_OT_call_panel(wmOperatorType *ot) PropertyRNA *prop; prop = RNA_def_string(ot->srna, "name", NULL, BKE_ST_MAXNAME, "Name", "Name of the menu"); + RNA_def_property_string_search_func_runtime( + prop, + WM_paneltype_idname_visit_for_search, + /* Only a suggestion as menu items may be referenced from add-ons that have been disabled. */ + (PROP_STRING_SEARCH_SORT | PROP_STRING_SEARCH_SUGGESTION)); RNA_def_property_flag(prop, PROP_SKIP_SAVE); prop = RNA_def_boolean(ot->srna, "keep_open", true, "Keep Open", ""); RNA_def_property_flag(prop, PROP_SKIP_SAVE); diff --git a/source/blender/windowmanager/intern/wm_panel_type.c b/source/blender/windowmanager/intern/wm_panel_type.c index bfcc86c38e7..860b53c1071 100644 --- a/source/blender/windowmanager/intern/wm_panel_type.c +++ b/source/blender/windowmanager/intern/wm_panel_type.c @@ -65,3 +65,21 @@ void WM_paneltype_clear(void) { BLI_ghash_free(g_paneltypes_hash, NULL, NULL); } + +void WM_paneltype_idname_visit_for_search(const bContext *UNUSED(C), + PointerRNA *UNUSED(ptr), + PropertyRNA *UNUSED(prop), + const char *UNUSED(edit_text), + StringPropertySearchVisitFunc visit_fn, + void *visit_user_data) +{ + GHashIterator gh_iter; + GHASH_ITER (gh_iter, g_paneltypes_hash) { + PanelType *pt = BLI_ghashIterator_getValue(&gh_iter); + + StringPropertySearchVisitParams visit_params = {NULL}; + visit_params.text = pt->idname; + visit_params.info = pt->label; + visit_fn(visit_user_data, &visit_params); + } +} |