Cleanup: move search templates into their own files
This commit is contained in:
parent
6fe3bbed34
commit
c0bbf1553f
Notes:
blender-bot
2023-12-22 20:14:11 +01:00
Referenced by issue #76171, Eevee bsdf shaders glitches Referenced by issue #75932, Belender crash while try to save rendered image
|
@ -69,6 +69,8 @@ set(SRC
|
|||
interface_regions.c
|
||||
interface_style.c
|
||||
interface_templates.c
|
||||
interface_template_search_menu.c
|
||||
interface_template_search_operator.c
|
||||
interface_utils.c
|
||||
interface_widgets.c
|
||||
resources.c
|
||||
|
|
|
@ -1011,6 +1011,7 @@ void UI_OT_eyedropper_driver(struct wmOperatorType *ot);
|
|||
void UI_OT_eyedropper_gpencil_color(struct wmOperatorType *ot);
|
||||
|
||||
/* interface_util.c */
|
||||
bool ui_str_has_word_prefix(const char *haystack, const char *needle, size_t needle_len);
|
||||
|
||||
/**
|
||||
* For use with #ui_rna_collection_search_cb.
|
||||
|
|
|
@ -0,0 +1,761 @@
|
|||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup edinterface
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "DNA_gpencil_modifier_types.h"
|
||||
#include "DNA_node_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_shader_fx_types.h"
|
||||
#include "DNA_texture_types.h"
|
||||
|
||||
#include "BLI_alloca.h"
|
||||
#include "BLI_dynstr.h"
|
||||
#include "BLI_ghash.h"
|
||||
#include "BLI_linklist.h"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_memarena.h"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "BLT_translation.h"
|
||||
|
||||
#include "BKE_context.h"
|
||||
#include "BKE_global.h"
|
||||
#include "BKE_screen.h"
|
||||
|
||||
#include "ED_screen.h"
|
||||
|
||||
#include "RNA_access.h"
|
||||
|
||||
#include "WM_api.h"
|
||||
#include "WM_types.h"
|
||||
|
||||
#include "UI_interface.h"
|
||||
#include "interface_intern.h"
|
||||
|
||||
/* For key-map item access. */
|
||||
#include "wm_event_system.h"
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Menu Search Template Implementation
|
||||
* \{ */
|
||||
|
||||
/* Unicode arrow. */
|
||||
#define MENU_SEP "\xe2\x96\xb6"
|
||||
|
||||
struct MenuSearch_Parent {
|
||||
struct MenuSearch_Parent *parent;
|
||||
MenuType *parent_mt;
|
||||
/* Set while writing menu items only. */
|
||||
struct MenuSearch_Parent *temp_child;
|
||||
const char *drawstr;
|
||||
};
|
||||
|
||||
struct MenuSearch_Item {
|
||||
struct MenuSearch_Item *next, *prev;
|
||||
const char *drawstr;
|
||||
const char *drawwstr_full;
|
||||
/** Support a single level sub-menu nesting (for operator buttons that expand). */
|
||||
const char *drawstr_submenu;
|
||||
int icon;
|
||||
int state;
|
||||
|
||||
struct MenuSearch_Parent *menu_parent;
|
||||
MenuType *mt;
|
||||
|
||||
enum {
|
||||
MENU_SEARCH_TYPE_OP = 1,
|
||||
MENU_SEARCH_TYPE_RNA = 2,
|
||||
} type;
|
||||
|
||||
union {
|
||||
/* Operator menu item. */
|
||||
struct {
|
||||
wmOperatorType *type;
|
||||
PointerRNA *opptr;
|
||||
short opcontext;
|
||||
bContextStore *context;
|
||||
} op;
|
||||
|
||||
/* Property (only for check-boxe/boolean). */
|
||||
struct {
|
||||
PointerRNA ptr;
|
||||
PropertyRNA *prop;
|
||||
int index;
|
||||
/** Only for enum buttons. */
|
||||
int enum_value;
|
||||
} rna;
|
||||
};
|
||||
};
|
||||
|
||||
struct MenuSearch_Data {
|
||||
/** MenuSearch_Item */
|
||||
ListBase items;
|
||||
/** Use for all small allocations. */
|
||||
MemArena *memarena;
|
||||
};
|
||||
|
||||
static int menu_item_sort_by_drawstr_full(const void *menu_item_a_v, const void *menu_item_b_v)
|
||||
{
|
||||
const struct MenuSearch_Item *menu_item_a = menu_item_a_v;
|
||||
const struct MenuSearch_Item *menu_item_b = menu_item_b_v;
|
||||
return strcmp(menu_item_a->drawwstr_full, menu_item_b->drawwstr_full);
|
||||
}
|
||||
|
||||
static const char *strdup_memarena(MemArena *memarena, const char *str)
|
||||
{
|
||||
const uint str_size = strlen(str) + 1;
|
||||
char *str_dst = BLI_memarena_alloc(memarena, str_size);
|
||||
memcpy(str_dst, str, str_size);
|
||||
return str_dst;
|
||||
}
|
||||
|
||||
static const char *strdup_memarena_from_dynstr(MemArena *memarena, DynStr *dyn_str)
|
||||
{
|
||||
const uint str_size = BLI_dynstr_get_len(dyn_str) + 1;
|
||||
char *str_dst = BLI_memarena_alloc(memarena, str_size);
|
||||
BLI_dynstr_get_cstring_ex(dyn_str, str_dst);
|
||||
return str_dst;
|
||||
}
|
||||
|
||||
static bool menu_items_from_ui_create_item_from_button(struct MenuSearch_Data *data,
|
||||
MemArena *memarena,
|
||||
struct MenuType *mt,
|
||||
const char *drawstr_submenu,
|
||||
uiBut *but)
|
||||
{
|
||||
struct MenuSearch_Item *item = NULL;
|
||||
if (but->optype != NULL) {
|
||||
item = BLI_memarena_calloc(memarena, sizeof(*item));
|
||||
item->type = MENU_SEARCH_TYPE_OP;
|
||||
|
||||
item->op.type = but->optype;
|
||||
item->op.opcontext = but->opcontext;
|
||||
item->op.context = but->context;
|
||||
item->op.opptr = but->opptr;
|
||||
but->opptr = NULL;
|
||||
}
|
||||
else if (but->rnaprop != NULL) {
|
||||
const int prop_type = RNA_property_type(but->rnaprop);
|
||||
if (!ELEM(prop_type, PROP_BOOLEAN, PROP_ENUM)) {
|
||||
/* Note that these buttons are not prevented,
|
||||
* but aren't typically used in menus. */
|
||||
printf("Button '%s' in menu '%s' is a menu item with unsupported RNA type %d\n",
|
||||
but->drawstr,
|
||||
mt->idname,
|
||||
prop_type);
|
||||
}
|
||||
else {
|
||||
item = BLI_memarena_calloc(memarena, sizeof(*item));
|
||||
item->type = MENU_SEARCH_TYPE_RNA;
|
||||
|
||||
item->rna.ptr = but->rnapoin;
|
||||
item->rna.prop = but->rnaprop;
|
||||
item->rna.index = but->rnaindex;
|
||||
|
||||
if (prop_type == PROP_ENUM) {
|
||||
item->rna.enum_value = (int)but->hardmax;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item != NULL) {
|
||||
/* Handle shared settings. */
|
||||
item->drawstr = strdup_memarena(memarena, but->drawstr);
|
||||
item->icon = ui_but_icon(but);
|
||||
item->state = (but->flag & (UI_BUT_DISABLED | UI_BUT_INACTIVE | UI_BUT_REDALERT));
|
||||
item->mt = mt;
|
||||
item->drawstr_submenu = drawstr_submenu ? strdup_memarena(memarena, drawstr_submenu) : NULL;
|
||||
BLI_addtail(&data->items, item);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate \a menu_stack with menus from inspecting active key-maps for this context.
|
||||
*/
|
||||
static void menu_types_add_from_keymap_items(bContext *C,
|
||||
wmWindow *win,
|
||||
ScrArea *area,
|
||||
ARegion *region,
|
||||
LinkNode **menuid_stack_p,
|
||||
GHash *menu_to_kmi,
|
||||
GSet *menu_tagged)
|
||||
{
|
||||
wmWindowManager *wm = CTX_wm_manager(C);
|
||||
ListBase *handlers[] = {
|
||||
region ? ®ion->handlers : NULL,
|
||||
area ? &area->handlers : NULL,
|
||||
&win->handlers,
|
||||
};
|
||||
|
||||
for (int handler_index = 0; handler_index < ARRAY_SIZE(handlers); handler_index++) {
|
||||
if (handlers[handler_index] == NULL) {
|
||||
continue;
|
||||
}
|
||||
LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers[handler_index]) {
|
||||
/* During this loop, ui handlers for nested menus can tag multiple handlers free. */
|
||||
if (handler_base->flag & WM_HANDLER_DO_FREE) {
|
||||
continue;
|
||||
}
|
||||
if (handler_base->type != WM_HANDLER_TYPE_KEYMAP) {
|
||||
continue;
|
||||
}
|
||||
|
||||
else if (handler_base->poll == NULL || handler_base->poll(region, win->eventstate)) {
|
||||
wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base;
|
||||
wmKeyMap *keymap = WM_event_get_keymap_from_handler(wm, handler);
|
||||
if (keymap && WM_keymap_poll(C, keymap)) {
|
||||
LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) {
|
||||
if (kmi->flag & KMI_INACTIVE) {
|
||||
continue;
|
||||
}
|
||||
if (STR_ELEM(kmi->idname, "WM_OT_call_menu", "WM_OT_call_menu_pie")) {
|
||||
char menu_idname[MAX_NAME];
|
||||
RNA_string_get(kmi->ptr, "name", menu_idname);
|
||||
MenuType *mt = WM_menutype_find(menu_idname, false);
|
||||
|
||||
if (mt && BLI_gset_add(menu_tagged, mt)) {
|
||||
/* Unlikely, but possible this will be included twice. */
|
||||
BLI_linklist_prepend(menuid_stack_p, mt);
|
||||
|
||||
void **kmi_p;
|
||||
if (!BLI_ghash_ensure_p(menu_to_kmi, mt, &kmi_p)) {
|
||||
*kmi_p = kmi;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void menu_items_from_all_operators(bContext *C, struct MenuSearch_Data *data)
|
||||
{
|
||||
/* Add to temporary list so we can sort them separately. */
|
||||
ListBase operator_items = {NULL, NULL};
|
||||
|
||||
MemArena *memarena = data->memarena;
|
||||
GHashIterator iter;
|
||||
for (WM_operatortype_iter(&iter); !BLI_ghashIterator_done(&iter);
|
||||
BLI_ghashIterator_step(&iter)) {
|
||||
wmOperatorType *ot = BLI_ghashIterator_getValue(&iter);
|
||||
|
||||
if ((ot->flag & OPTYPE_INTERNAL) && (G.debug & G_DEBUG_WM) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (WM_operator_poll((bContext *)C, ot)) {
|
||||
const char *ot_ui_name = CTX_IFACE_(ot->translation_context, ot->name);
|
||||
|
||||
struct MenuSearch_Item *item = NULL;
|
||||
item = BLI_memarena_calloc(memarena, sizeof(*item));
|
||||
item->type = MENU_SEARCH_TYPE_OP;
|
||||
|
||||
item->op.type = ot;
|
||||
item->op.opcontext = WM_OP_EXEC_DEFAULT;
|
||||
item->op.context = NULL;
|
||||
|
||||
char idname_as_py[OP_MAX_TYPENAME];
|
||||
char uiname[256];
|
||||
WM_operator_py_idname(idname_as_py, ot->idname);
|
||||
|
||||
SNPRINTF(uiname, "%s " MENU_SEP "%s", idname_as_py, ot_ui_name);
|
||||
|
||||
item->drawwstr_full = strdup_memarena(memarena, uiname);
|
||||
|
||||
BLI_addtail(&operator_items, item);
|
||||
}
|
||||
}
|
||||
|
||||
BLI_listbase_sort(&operator_items, menu_item_sort_by_drawstr_full);
|
||||
|
||||
BLI_movelisttolist(&data->items, &operator_items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create #MenuSearch_Data by inspecting the current context, this uses two methods:
|
||||
*
|
||||
* - Look-up pre-defined editor-menus.
|
||||
* - Look-up key-map items which call menus.
|
||||
*/
|
||||
static struct MenuSearch_Data *menu_items_from_ui_create(bContext *C,
|
||||
wmWindow *win,
|
||||
ScrArea *area,
|
||||
ARegion *region)
|
||||
{
|
||||
MemArena *memarena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
|
||||
/** Map (#MenuType to #MenuSearch_Parent) */
|
||||
GHash *menu_parent_map = BLI_ghash_ptr_new(__func__);
|
||||
GHash *menu_display_name_map = BLI_ghash_ptr_new(__func__);
|
||||
const uiStyle *style = UI_style_get_dpi();
|
||||
|
||||
/* Convert into non-ui structure. */
|
||||
struct MenuSearch_Data *data = MEM_callocN(sizeof(*data), __func__);
|
||||
|
||||
DynStr *dyn_str = BLI_dynstr_new_memarena();
|
||||
|
||||
/* Use a stack of menus to handle and discover new menus in passes. */
|
||||
LinkNode *menu_stack = NULL;
|
||||
|
||||
/* Tag menu types not to add, either because they have already been added
|
||||
* or they have been blacklisted.
|
||||
* Set of #MenuType. */
|
||||
GSet *menu_tagged = BLI_gset_ptr_new(__func__);
|
||||
/** Map (#MenuType -> #wmKeyMapItem). */
|
||||
GHash *menu_to_kmi = BLI_ghash_ptr_new(__func__);
|
||||
|
||||
/* Blacklist menus we don't want to show. */
|
||||
{
|
||||
const char *idname_array[] = {
|
||||
/* While we could include this, it's just showing filenames to load. */
|
||||
"TOPBAR_MT_file_open_recent",
|
||||
};
|
||||
for (int i = 0; i < ARRAY_SIZE(idname_array); i++) {
|
||||
MenuType *mt = WM_menutype_find(idname_array[i], false);
|
||||
if (mt != NULL) {
|
||||
BLI_gset_add(menu_tagged, mt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Populate menus from the editors,
|
||||
* note that we could create a fake header, draw the header and extract the menus
|
||||
* from the buttons, however this is quite involved and can be avoided as by convention
|
||||
* each space-type has a single root-menu that headers use. */
|
||||
{
|
||||
const char *idname_array[] = {
|
||||
"TOPBAR_MT_editor_menus",
|
||||
/* Optional second menu for the space-type. */
|
||||
NULL,
|
||||
};
|
||||
int idname_array_len = 1;
|
||||
|
||||
#define SPACE_MENU_MAP(space_type, menu_id) \
|
||||
case space_type: \
|
||||
idname_array[idname_array_len++] = menu_id; \
|
||||
break
|
||||
#define SPACE_MENU_NOP(space_type) \
|
||||
case space_type: \
|
||||
break
|
||||
|
||||
if (area != NULL) {
|
||||
switch (area->spacetype) {
|
||||
SPACE_MENU_MAP(SPACE_VIEW3D, "VIEW3D_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_GRAPH, "GRAPH_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_OUTLINER, "OUTLINER_MT_editor_menus");
|
||||
SPACE_MENU_NOP(SPACE_PROPERTIES);
|
||||
SPACE_MENU_MAP(SPACE_FILE, "FILE_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_IMAGE, "IMAGE_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_INFO, "INFO_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_SEQ, "SEQUENCER_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_TEXT, "TEXT_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_ACTION, "DOPESHEET_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_NLA, "NLA_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_NODE, "NODE_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_CONSOLE, "CONSOLE_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_USERPREF, "USERPREF_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_CLIP,
|
||||
(((const SpaceClip *)area->spacedata.first)->mode == SC_MODE_TRACKING) ?
|
||||
"CLIP_MT_tracking_editor_menus" :
|
||||
"CLIP_MT_masking_editor_menus");
|
||||
SPACE_MENU_NOP(SPACE_TOPBAR);
|
||||
SPACE_MENU_NOP(SPACE_STATUSBAR);
|
||||
default:
|
||||
printf("Unknown space type '%d'\n", area->spacetype);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < idname_array_len; i++) {
|
||||
MenuType *mt = WM_menutype_find(idname_array[i], false);
|
||||
if (mt != NULL) {
|
||||
BLI_linklist_prepend(&menu_stack, mt);
|
||||
BLI_gset_add(menu_tagged, mt);
|
||||
}
|
||||
}
|
||||
}
|
||||
#undef SPACE_MENU_MAP
|
||||
#undef SPACE_MENU_NOP
|
||||
|
||||
bool has_keymap_menu_items = false;
|
||||
|
||||
GHashIterator iter;
|
||||
|
||||
while (menu_stack != NULL) {
|
||||
MenuType *mt = BLI_linklist_pop(&menu_stack);
|
||||
if (!WM_menutype_poll(C, mt)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS);
|
||||
uiLayout *layout = UI_block_layout(
|
||||
block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style);
|
||||
|
||||
UI_block_flag_enable(block, UI_BLOCK_SHOW_SHORTCUT_ALWAYS);
|
||||
|
||||
uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_REGION_WIN);
|
||||
UI_menutype_draw(C, mt, layout);
|
||||
|
||||
UI_block_end(C, block);
|
||||
|
||||
LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
|
||||
MenuType *mt_from_but = NULL;
|
||||
/* Support menu titles with dynamic from initial labels
|
||||
* (used by edit-mesh context menu). */
|
||||
if (but->type == UI_BTYPE_LABEL) {
|
||||
|
||||
/* Check if the label is the title. */
|
||||
uiBut *but_test = but->prev;
|
||||
while (but_test && but_test->type == UI_BTYPE_SEPR) {
|
||||
but_test = but_test->prev;
|
||||
}
|
||||
|
||||
if (but_test == NULL) {
|
||||
BLI_ghash_insert(
|
||||
menu_display_name_map, mt, (void *)strdup_memarena(memarena, but->drawstr));
|
||||
}
|
||||
}
|
||||
else if (menu_items_from_ui_create_item_from_button(data, memarena, mt, NULL, but)) {
|
||||
/* pass */
|
||||
}
|
||||
else if ((mt_from_but = UI_but_menutype_get(but))) {
|
||||
|
||||
if (BLI_gset_add(menu_tagged, mt_from_but)) {
|
||||
BLI_linklist_prepend(&menu_stack, mt_from_but);
|
||||
}
|
||||
|
||||
if (!BLI_ghash_haskey(menu_parent_map, mt_from_but)) {
|
||||
struct MenuSearch_Parent *menu_parent = BLI_memarena_calloc(memarena,
|
||||
sizeof(*menu_parent));
|
||||
/* Use brackets for menu key shortcuts,
|
||||
* converting "Text|Some-Shortcut" to "Text (Some-Shortcut)".
|
||||
* This is needed so we don't right align sub-menu contents
|
||||
* we only want to do that for the last menu item, not the path that leads to it.
|
||||
*/
|
||||
const char *drawstr_sep = but->flag & UI_BUT_HAS_SEP_CHAR ?
|
||||
strrchr(but->drawstr, UI_SEP_CHAR) :
|
||||
NULL;
|
||||
bool drawstr_is_empty = false;
|
||||
if (drawstr_sep != NULL) {
|
||||
BLI_assert(BLI_dynstr_get_len(dyn_str) == 0);
|
||||
/* Detect empty string, fallback to menu name. */
|
||||
const char *drawstr = but->drawstr;
|
||||
int drawstr_len = drawstr_sep - but->drawstr;
|
||||
if (UNLIKELY(drawstr_len == 0)) {
|
||||
drawstr = CTX_IFACE_(mt_from_but->translation_context, mt_from_but->label);
|
||||
drawstr_len = strlen(drawstr);
|
||||
if (drawstr[0] == '\0') {
|
||||
drawstr_is_empty = true;
|
||||
}
|
||||
}
|
||||
BLI_dynstr_nappend(dyn_str, drawstr, drawstr_len);
|
||||
BLI_dynstr_appendf(dyn_str, " (%s)", drawstr_sep + 1);
|
||||
menu_parent->drawstr = strdup_memarena_from_dynstr(memarena, dyn_str);
|
||||
BLI_dynstr_clear(dyn_str);
|
||||
}
|
||||
else {
|
||||
const char *drawstr = but->drawstr;
|
||||
if (UNLIKELY(drawstr[0] == '\0')) {
|
||||
drawstr = CTX_IFACE_(mt_from_but->translation_context, mt_from_but->label);
|
||||
if (drawstr[0] == '\0') {
|
||||
drawstr_is_empty = true;
|
||||
}
|
||||
}
|
||||
menu_parent->drawstr = strdup_memarena(memarena, drawstr);
|
||||
}
|
||||
menu_parent->parent_mt = mt;
|
||||
BLI_ghash_insert(menu_parent_map, mt_from_but, menu_parent);
|
||||
|
||||
if (drawstr_is_empty) {
|
||||
printf("Warning: '%s' menu has empty 'bl_label'.\n", mt_from_but->idname);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (but->menu_create_func != NULL) {
|
||||
/* A non 'MenuType' menu button. */
|
||||
|
||||
/* Only expand one level deep, this is mainly for expanding operator menus. */
|
||||
const char *drawstr_submenu = but->drawstr;
|
||||
|
||||
/* +1 to avoid overlap with the current 'block'. */
|
||||
uiBlock *sub_block = UI_block_begin(C, region, __func__ + 1, UI_EMBOSS);
|
||||
uiLayout *sub_layout = UI_block_layout(
|
||||
sub_block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style);
|
||||
|
||||
UI_block_flag_enable(sub_block, UI_BLOCK_SHOW_SHORTCUT_ALWAYS);
|
||||
|
||||
uiLayoutSetOperatorContext(sub_layout, WM_OP_INVOKE_REGION_WIN);
|
||||
|
||||
but->menu_create_func(C, sub_layout, but->poin);
|
||||
|
||||
UI_block_end(C, sub_block);
|
||||
|
||||
LISTBASE_FOREACH (uiBut *, sub_but, &sub_block->buttons) {
|
||||
menu_items_from_ui_create_item_from_button(data, memarena, mt, drawstr_submenu, sub_but);
|
||||
}
|
||||
|
||||
if (region) {
|
||||
BLI_remlink(®ion->uiblocks, sub_block);
|
||||
}
|
||||
UI_block_free(NULL, sub_block);
|
||||
}
|
||||
}
|
||||
if (region) {
|
||||
BLI_remlink(®ion->uiblocks, block);
|
||||
}
|
||||
UI_block_free(NULL, block);
|
||||
|
||||
/* Add key-map items as a second pass,
|
||||
* so all menus are accessed from the header & top-bar before key shortcuts are expanded. */
|
||||
if ((menu_stack == NULL) && (has_keymap_menu_items == false)) {
|
||||
has_keymap_menu_items = true;
|
||||
menu_types_add_from_keymap_items(
|
||||
C, win, area, region, &menu_stack, menu_to_kmi, menu_tagged);
|
||||
}
|
||||
}
|
||||
|
||||
LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) {
|
||||
item->menu_parent = BLI_ghash_lookup(menu_parent_map, item->mt);
|
||||
}
|
||||
|
||||
GHASH_ITER (iter, menu_parent_map) {
|
||||
struct MenuSearch_Parent *menu_parent = BLI_ghashIterator_getValue(&iter);
|
||||
menu_parent->parent = BLI_ghash_lookup(menu_parent_map, menu_parent->parent_mt);
|
||||
}
|
||||
|
||||
/* NOTE: currently this builds the full path for each menu item,
|
||||
* that could be moved into the parent menu. */
|
||||
|
||||
/* Set names as full paths. */
|
||||
LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) {
|
||||
if (item->menu_parent != NULL) {
|
||||
struct MenuSearch_Parent *menu_parent = item->menu_parent;
|
||||
menu_parent->temp_child = NULL;
|
||||
while (menu_parent && menu_parent->parent) {
|
||||
menu_parent->parent->temp_child = menu_parent;
|
||||
menu_parent = menu_parent->parent;
|
||||
}
|
||||
BLI_assert(BLI_dynstr_get_len(dyn_str) == 0);
|
||||
while (menu_parent) {
|
||||
BLI_dynstr_append(dyn_str, menu_parent->drawstr);
|
||||
BLI_dynstr_append(dyn_str, " " MENU_SEP " ");
|
||||
menu_parent = menu_parent->temp_child;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BLI_assert(BLI_dynstr_get_len(dyn_str) == 0);
|
||||
const char *drawstr = BLI_ghash_lookup(menu_display_name_map, item->mt);
|
||||
if (drawstr == NULL) {
|
||||
drawstr = CTX_IFACE_(item->mt->translation_context, item->mt->label);
|
||||
}
|
||||
BLI_dynstr_append(dyn_str, drawstr);
|
||||
|
||||
wmKeyMapItem *kmi = BLI_ghash_lookup(menu_to_kmi, item->mt);
|
||||
if (kmi != NULL) {
|
||||
char kmi_str[128];
|
||||
WM_keymap_item_to_string(kmi, false, kmi_str, sizeof(kmi_str));
|
||||
BLI_dynstr_appendf(dyn_str, " (%s)", kmi_str);
|
||||
}
|
||||
|
||||
BLI_dynstr_append(dyn_str, " " MENU_SEP " ");
|
||||
}
|
||||
|
||||
/* Optional nested menu. */
|
||||
if (item->drawstr_submenu != NULL) {
|
||||
BLI_dynstr_append(dyn_str, item->drawstr_submenu);
|
||||
BLI_dynstr_append(dyn_str, " " MENU_SEP " ");
|
||||
}
|
||||
|
||||
BLI_dynstr_append(dyn_str, item->drawstr);
|
||||
|
||||
item->drawwstr_full = strdup_memarena_from_dynstr(memarena, dyn_str);
|
||||
BLI_dynstr_clear(dyn_str);
|
||||
}
|
||||
BLI_dynstr_free(dyn_str);
|
||||
|
||||
/* Finally sort menu items.
|
||||
*
|
||||
* Note: we might want to keep the in-menu order, for now sort all. */
|
||||
BLI_listbase_sort(&data->items, menu_item_sort_by_drawstr_full);
|
||||
|
||||
BLI_ghash_free(menu_parent_map, NULL, NULL);
|
||||
BLI_ghash_free(menu_display_name_map, NULL, NULL);
|
||||
|
||||
BLI_ghash_free(menu_to_kmi, NULL, NULL);
|
||||
|
||||
BLI_gset_free(menu_tagged, NULL);
|
||||
|
||||
data->memarena = memarena;
|
||||
|
||||
if (U.flag & USER_DEVELOPER_UI) {
|
||||
menu_items_from_all_operators(C, data);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static void menu_items_from_ui_destroy(void *data_v)
|
||||
{
|
||||
struct MenuSearch_Data *data = data_v;
|
||||
LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) {
|
||||
switch (item->type) {
|
||||
case MENU_SEARCH_TYPE_OP: {
|
||||
if (item->op.opptr != NULL) {
|
||||
WM_operator_properties_free(item->op.opptr);
|
||||
MEM_freeN(item->op.opptr);
|
||||
}
|
||||
}
|
||||
case MENU_SEARCH_TYPE_RNA: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BLI_memarena_free(data->memarena);
|
||||
|
||||
MEM_freeN(data);
|
||||
}
|
||||
|
||||
static void menu_call_fn(bContext *C, void *UNUSED(arg1), void *arg2)
|
||||
{
|
||||
struct MenuSearch_Item *item = arg2;
|
||||
if (item == NULL) {
|
||||
return;
|
||||
}
|
||||
if (item->state & UI_BUT_DISABLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (item->type) {
|
||||
case MENU_SEARCH_TYPE_OP: {
|
||||
CTX_store_set(C, item->op.context);
|
||||
WM_operator_name_call_ptr(C, item->op.type, item->op.opcontext, item->op.opptr);
|
||||
CTX_store_set(C, NULL);
|
||||
break;
|
||||
}
|
||||
case MENU_SEARCH_TYPE_RNA: {
|
||||
PointerRNA *ptr = &item->rna.ptr;
|
||||
PropertyRNA *prop = item->rna.prop;
|
||||
int index = item->rna.index;
|
||||
const int prop_type = RNA_property_type(prop);
|
||||
bool changed = false;
|
||||
|
||||
if (prop_type == PROP_BOOLEAN) {
|
||||
const bool is_array = RNA_property_array_check(prop);
|
||||
if (is_array) {
|
||||
const bool value = RNA_property_boolean_get_index(ptr, prop, index);
|
||||
RNA_property_boolean_set_index(ptr, prop, index, !value);
|
||||
}
|
||||
else {
|
||||
const bool value = RNA_property_boolean_get(ptr, prop);
|
||||
RNA_property_boolean_set(ptr, prop, !value);
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
else if (prop_type == PROP_ENUM) {
|
||||
RNA_property_enum_set(ptr, prop, item->rna.enum_value);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
RNA_property_update(C, ptr, prop);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void menu_search_cb(const bContext *UNUSED(C),
|
||||
void *arg,
|
||||
const char *str,
|
||||
uiSearchItems *items)
|
||||
{
|
||||
struct MenuSearch_Data *data = arg;
|
||||
const size_t str_len = strlen(str);
|
||||
const int words_max = (str_len / 2) + 1;
|
||||
int(*words)[2] = BLI_array_alloca(words, words_max);
|
||||
|
||||
const int words_len = BLI_string_find_split_words(str, str_len, ' ', words, words_max);
|
||||
|
||||
for (struct MenuSearch_Item *item = data->items.first; item; item = item->next) {
|
||||
int index;
|
||||
|
||||
/* match name against all search words */
|
||||
for (index = 0; index < words_len; index++) {
|
||||
if (!ui_str_has_word_prefix(item->drawwstr_full, str + words[index][0], words[index][1])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index == words_len) {
|
||||
if (!UI_search_item_add(items, item->drawwstr_full, item, item->icon, item->state)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Menu Search Template Public API
|
||||
* \{ */
|
||||
|
||||
void UI_but_func_menu_search(uiBut *but)
|
||||
{
|
||||
bContext *C = but->block->evil_C;
|
||||
wmWindow *win = CTX_wm_window(C);
|
||||
ScrArea *area = CTX_wm_area(C);
|
||||
ARegion *region = CTX_wm_region(C);
|
||||
struct MenuSearch_Data *data = menu_items_from_ui_create(C, win, area, region);
|
||||
UI_but_func_search_set(but,
|
||||
ui_searchbox_create_menu,
|
||||
menu_search_cb,
|
||||
data,
|
||||
menu_items_from_ui_destroy,
|
||||
menu_call_fn,
|
||||
MENU_SEP,
|
||||
NULL);
|
||||
}
|
||||
|
||||
void uiTemplateMenuSearch(uiLayout *layout)
|
||||
{
|
||||
uiBlock *block;
|
||||
uiBut *but;
|
||||
static char search[256] = "";
|
||||
|
||||
block = uiLayoutGetBlock(layout);
|
||||
UI_block_layout_set_current(block, layout);
|
||||
|
||||
but = uiDefSearchBut(
|
||||
block, search, 0, ICON_VIEWZOOM, sizeof(search), 0, 0, UI_UNIT_X * 6, UI_UNIT_Y, 0, 0, "");
|
||||
UI_but_func_menu_search(but);
|
||||
}
|
||||
|
||||
#undef MENU_SEP
|
||||
|
||||
/** \} */
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup edinterface
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "DNA_gpencil_modifier_types.h"
|
||||
#include "DNA_node_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_shader_fx_types.h"
|
||||
#include "DNA_texture_types.h"
|
||||
|
||||
#include "BLI_alloca.h"
|
||||
#include "BLI_ghash.h"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "BLT_translation.h"
|
||||
|
||||
#include "BKE_context.h"
|
||||
#include "BKE_global.h"
|
||||
|
||||
#include "WM_api.h"
|
||||
#include "WM_types.h"
|
||||
|
||||
#include "UI_interface.h"
|
||||
#include "interface_intern.h"
|
||||
|
||||
/* For key-map item access. */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Operator Search Template Implementation
|
||||
* \{ */
|
||||
|
||||
static void operator_call_cb(bContext *C, void *UNUSED(arg1), void *arg2)
|
||||
{
|
||||
wmOperatorType *ot = arg2;
|
||||
|
||||
if (ot) {
|
||||
WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void operator_search_cb(const bContext *C,
|
||||
void *UNUSED(arg),
|
||||
const char *str,
|
||||
uiSearchItems *items)
|
||||
{
|
||||
GHashIterator iter;
|
||||
const size_t str_len = strlen(str);
|
||||
const int words_max = (str_len / 2) + 1;
|
||||
int(*words)[2] = BLI_array_alloca(words, words_max);
|
||||
|
||||
const int words_len = BLI_string_find_split_words(str, str_len, ' ', words, words_max);
|
||||
|
||||
for (WM_operatortype_iter(&iter); !BLI_ghashIterator_done(&iter);
|
||||
BLI_ghashIterator_step(&iter)) {
|
||||
wmOperatorType *ot = BLI_ghashIterator_getValue(&iter);
|
||||
const char *ot_ui_name = CTX_IFACE_(ot->translation_context, ot->name);
|
||||
int index;
|
||||
|
||||
if ((ot->flag & OPTYPE_INTERNAL) && (G.debug & G_DEBUG_WM) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* match name against all search words */
|
||||
for (index = 0; index < words_len; index++) {
|
||||
if (!ui_str_has_word_prefix(ot_ui_name, str + words[index][0], words[index][1])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index == words_len) {
|
||||
if (WM_operator_poll((bContext *)C, ot)) {
|
||||
char name[256];
|
||||
int len = strlen(ot_ui_name);
|
||||
|
||||
/* display name for menu, can hold hotkey */
|
||||
BLI_strncpy(name, ot_ui_name, sizeof(name));
|
||||
|
||||
/* check for hotkey */
|
||||
if (len < sizeof(name) - 6) {
|
||||
if (WM_key_event_operator_string(C,
|
||||
ot->idname,
|
||||
WM_OP_EXEC_DEFAULT,
|
||||
NULL,
|
||||
true,
|
||||
&name[len + 1],
|
||||
sizeof(name) - len - 1)) {
|
||||
name[len] = UI_SEP_CHAR;
|
||||
}
|
||||
}
|
||||
|
||||
if (!UI_search_item_add(items, name, ot, ICON_NONE, 0)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Operator Search Template API
|
||||
* \{ */
|
||||
|
||||
void UI_but_func_operator_search(uiBut *but)
|
||||
{
|
||||
UI_but_func_search_set(but,
|
||||
ui_searchbox_create_operator,
|
||||
operator_search_cb,
|
||||
NULL,
|
||||
false,
|
||||
operator_call_cb,
|
||||
NULL,
|
||||
NULL);
|
||||
}
|
||||
|
||||
void uiTemplateOperatorSearch(uiLayout *layout)
|
||||
{
|
||||
uiBlock *block;
|
||||
uiBut *but;
|
||||
static char search[256] = "";
|
||||
|
||||
block = uiLayoutGetBlock(layout);
|
||||
UI_block_layout_set_current(block, layout);
|
||||
|
||||
but = uiDefSearchBut(
|
||||
block, search, 0, ICON_VIEWZOOM, sizeof(search), 0, 0, UI_UNIT_X * 6, UI_UNIT_Y, 0, 0, "");
|
||||
UI_but_func_operator_search(but);
|
||||
}
|
||||
|
||||
/** \} */
|
|
@ -37,18 +37,12 @@
|
|||
#include "DNA_shader_fx_types.h"
|
||||
#include "DNA_texture_types.h"
|
||||
|
||||
#include "BLI_alloca.h"
|
||||
#include "BLI_dynstr.h"
|
||||
#include "BLI_fnmatch.h"
|
||||
#include "BLI_ghash.h"
|
||||
#include "BLI_linklist.h"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math.h"
|
||||
#include "BLI_memarena.h"
|
||||
#include "BLI_path_util.h"
|
||||
#include "BLI_rect.h"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_string_utils.h"
|
||||
#include "BLI_timecode.h"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
|
@ -73,7 +67,6 @@
|
|||
#include "BKE_modifier.h"
|
||||
#include "BKE_object.h"
|
||||
#include "BKE_packedFile.h"
|
||||
#include "BKE_paint.h"
|
||||
#include "BKE_particle.h"
|
||||
#include "BKE_report.h"
|
||||
#include "BKE_scene.h"
|
||||
|
@ -103,9 +96,6 @@
|
|||
|
||||
#include "PIL_time.h"
|
||||
|
||||
/* For key-map item access. */
|
||||
#include "wm_event_system.h"
|
||||
|
||||
// #define USE_OP_RESET_BUT // we may want to make this optional, disable for now.
|
||||
|
||||
/* defines for templateID/TemplateSearch */
|
||||
|
@ -6821,818 +6811,6 @@ void uiTemplateList(uiLayout *layout,
|
|||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Operator Search Template
|
||||
* \{ */
|
||||
|
||||
static void operator_call_cb(bContext *C, void *UNUSED(arg1), void *arg2)
|
||||
{
|
||||
wmOperatorType *ot = arg2;
|
||||
|
||||
if (ot) {
|
||||
WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static bool has_word_prefix(const char *haystack, const char *needle, size_t needle_len)
|
||||
{
|
||||
const char *match = BLI_strncasestr(haystack, needle, needle_len);
|
||||
if (match) {
|
||||
if ((match == haystack) || (*(match - 1) == ' ') || ispunct(*(match - 1))) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return has_word_prefix(match + 1, needle, needle_len);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void operator_search_cb(const bContext *C,
|
||||
void *UNUSED(arg),
|
||||
const char *str,
|
||||
uiSearchItems *items)
|
||||
{
|
||||
GHashIterator iter;
|
||||
const size_t str_len = strlen(str);
|
||||
const int words_max = (str_len / 2) + 1;
|
||||
int(*words)[2] = BLI_array_alloca(words, words_max);
|
||||
|
||||
const int words_len = BLI_string_find_split_words(str, str_len, ' ', words, words_max);
|
||||
|
||||
for (WM_operatortype_iter(&iter); !BLI_ghashIterator_done(&iter);
|
||||
BLI_ghashIterator_step(&iter)) {
|
||||
wmOperatorType *ot = BLI_ghashIterator_getValue(&iter);
|
||||
const char *ot_ui_name = CTX_IFACE_(ot->translation_context, ot->name);
|
||||
int index;
|
||||
|
||||
if ((ot->flag & OPTYPE_INTERNAL) && (G.debug & G_DEBUG_WM) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* match name against all search words */
|
||||
for (index = 0; index < words_len; index++) {
|
||||
if (!has_word_prefix(ot_ui_name, str + words[index][0], words[index][1])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index == words_len) {
|
||||
if (WM_operator_poll((bContext *)C, ot)) {
|
||||
char name[256];
|
||||
int len = strlen(ot_ui_name);
|
||||
|
||||
/* display name for menu, can hold hotkey */
|
||||
BLI_strncpy(name, ot_ui_name, sizeof(name));
|
||||
|
||||
/* check for hotkey */
|
||||
if (len < sizeof(name) - 6) {
|
||||
if (WM_key_event_operator_string(C,
|
||||
ot->idname,
|
||||
WM_OP_EXEC_DEFAULT,
|
||||
NULL,
|
||||
true,
|
||||
&name[len + 1],
|
||||
sizeof(name) - len - 1)) {
|
||||
name[len] = UI_SEP_CHAR;
|
||||
}
|
||||
}
|
||||
|
||||
if (!UI_search_item_add(items, name, ot, ICON_NONE, 0)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UI_but_func_operator_search(uiBut *but)
|
||||
{
|
||||
UI_but_func_search_set(but,
|
||||
ui_searchbox_create_operator,
|
||||
operator_search_cb,
|
||||
NULL,
|
||||
false,
|
||||
operator_call_cb,
|
||||
NULL,
|
||||
NULL);
|
||||
}
|
||||
|
||||
void uiTemplateOperatorSearch(uiLayout *layout)
|
||||
{
|
||||
uiBlock *block;
|
||||
uiBut *but;
|
||||
static char search[256] = "";
|
||||
|
||||
block = uiLayoutGetBlock(layout);
|
||||
UI_block_layout_set_current(block, layout);
|
||||
|
||||
but = uiDefSearchBut(
|
||||
block, search, 0, ICON_VIEWZOOM, sizeof(search), 0, 0, UI_UNIT_X * 6, UI_UNIT_Y, 0, 0, "");
|
||||
UI_but_func_operator_search(but);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Menu Search Template
|
||||
* \{ */
|
||||
|
||||
/* Unicode arrow. */
|
||||
#define MENU_SEP "\xe2\x96\xb6"
|
||||
|
||||
struct MenuSearch_Parent {
|
||||
struct MenuSearch_Parent *parent;
|
||||
MenuType *parent_mt;
|
||||
/* Set while writing menu items only. */
|
||||
struct MenuSearch_Parent *temp_child;
|
||||
const char *drawstr;
|
||||
};
|
||||
|
||||
struct MenuSearch_Item {
|
||||
struct MenuSearch_Item *next, *prev;
|
||||
const char *drawstr;
|
||||
const char *drawwstr_full;
|
||||
/** Support a single level sub-menu nesting (for operator buttons that expand). */
|
||||
const char *drawstr_submenu;
|
||||
int icon;
|
||||
int state;
|
||||
|
||||
struct MenuSearch_Parent *menu_parent;
|
||||
MenuType *mt;
|
||||
|
||||
enum {
|
||||
MENU_SEARCH_TYPE_OP = 1,
|
||||
MENU_SEARCH_TYPE_RNA = 2,
|
||||
} type;
|
||||
|
||||
union {
|
||||
/* Operator menu item. */
|
||||
struct {
|
||||
wmOperatorType *type;
|
||||
PointerRNA *opptr;
|
||||
short opcontext;
|
||||
bContextStore *context;
|
||||
} op;
|
||||
|
||||
/* Property (only for check-boxe/boolean). */
|
||||
struct {
|
||||
PointerRNA ptr;
|
||||
PropertyRNA *prop;
|
||||
int index;
|
||||
/** Only for enum buttons. */
|
||||
int enum_value;
|
||||
} rna;
|
||||
};
|
||||
};
|
||||
|
||||
struct MenuSearch_Data {
|
||||
/** MenuSearch_Item */
|
||||
ListBase items;
|
||||
/** Use for all small allocations. */
|
||||
MemArena *memarena;
|
||||
};
|
||||
|
||||
static int menu_item_sort_by_drawstr_full(const void *menu_item_a_v, const void *menu_item_b_v)
|
||||
{
|
||||
const struct MenuSearch_Item *menu_item_a = menu_item_a_v;
|
||||
const struct MenuSearch_Item *menu_item_b = menu_item_b_v;
|
||||
return strcmp(menu_item_a->drawwstr_full, menu_item_b->drawwstr_full);
|
||||
}
|
||||
|
||||
static const char *strdup_memarena(MemArena *memarena, const char *str)
|
||||
{
|
||||
const uint str_size = strlen(str) + 1;
|
||||
char *str_dst = BLI_memarena_alloc(memarena, str_size);
|
||||
memcpy(str_dst, str, str_size);
|
||||
return str_dst;
|
||||
}
|
||||
|
||||
static const char *strdup_memarena_from_dynstr(MemArena *memarena, DynStr *dyn_str)
|
||||
{
|
||||
const uint str_size = BLI_dynstr_get_len(dyn_str) + 1;
|
||||
char *str_dst = BLI_memarena_alloc(memarena, str_size);
|
||||
BLI_dynstr_get_cstring_ex(dyn_str, str_dst);
|
||||
return str_dst;
|
||||
}
|
||||
|
||||
static bool menu_items_from_ui_create_item_from_button(struct MenuSearch_Data *data,
|
||||
MemArena *memarena,
|
||||
struct MenuType *mt,
|
||||
const char *drawstr_submenu,
|
||||
uiBut *but)
|
||||
{
|
||||
struct MenuSearch_Item *item = NULL;
|
||||
if (but->optype != NULL) {
|
||||
item = BLI_memarena_calloc(memarena, sizeof(*item));
|
||||
item->type = MENU_SEARCH_TYPE_OP;
|
||||
|
||||
item->op.type = but->optype;
|
||||
item->op.opcontext = but->opcontext;
|
||||
item->op.context = but->context;
|
||||
item->op.opptr = but->opptr;
|
||||
but->opptr = NULL;
|
||||
}
|
||||
else if (but->rnaprop != NULL) {
|
||||
const int prop_type = RNA_property_type(but->rnaprop);
|
||||
if (!ELEM(prop_type, PROP_BOOLEAN, PROP_ENUM)) {
|
||||
/* Note that these buttons are not prevented,
|
||||
* but aren't typically used in menus. */
|
||||
printf("Button '%s' in menu '%s' is a menu item with unsupported RNA type %d\n",
|
||||
but->drawstr,
|
||||
mt->idname,
|
||||
prop_type);
|
||||
}
|
||||
else {
|
||||
item = BLI_memarena_calloc(memarena, sizeof(*item));
|
||||
item->type = MENU_SEARCH_TYPE_RNA;
|
||||
|
||||
item->rna.ptr = but->rnapoin;
|
||||
item->rna.prop = but->rnaprop;
|
||||
item->rna.index = but->rnaindex;
|
||||
|
||||
if (prop_type == PROP_ENUM) {
|
||||
item->rna.enum_value = (int)but->hardmax;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item != NULL) {
|
||||
/* Handle shared settings. */
|
||||
item->drawstr = strdup_memarena(memarena, but->drawstr);
|
||||
item->icon = ui_but_icon(but);
|
||||
item->state = (but->flag & (UI_BUT_DISABLED | UI_BUT_INACTIVE | UI_BUT_REDALERT));
|
||||
item->mt = mt;
|
||||
item->drawstr_submenu = drawstr_submenu ? strdup_memarena(memarena, drawstr_submenu) : NULL;
|
||||
BLI_addtail(&data->items, item);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate \a menu_stack with menus from inspecting active key-maps for this context.
|
||||
*/
|
||||
static void menu_types_add_from_keymap_items(bContext *C,
|
||||
wmWindow *win,
|
||||
ScrArea *area,
|
||||
ARegion *region,
|
||||
LinkNode **menuid_stack_p,
|
||||
GHash *menu_to_kmi,
|
||||
GSet *menu_tagged)
|
||||
{
|
||||
wmWindowManager *wm = CTX_wm_manager(C);
|
||||
ListBase *handlers[] = {
|
||||
region ? ®ion->handlers : NULL,
|
||||
area ? &area->handlers : NULL,
|
||||
&win->handlers,
|
||||
};
|
||||
|
||||
for (int handler_index = 0; handler_index < ARRAY_SIZE(handlers); handler_index++) {
|
||||
if (handlers[handler_index] == NULL) {
|
||||
continue;
|
||||
}
|
||||
LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers[handler_index]) {
|
||||
/* During this loop, ui handlers for nested menus can tag multiple handlers free. */
|
||||
if (handler_base->flag & WM_HANDLER_DO_FREE) {
|
||||
continue;
|
||||
}
|
||||
if (handler_base->type != WM_HANDLER_TYPE_KEYMAP) {
|
||||
continue;
|
||||
}
|
||||
|
||||
else if (handler_base->poll == NULL || handler_base->poll(region, win->eventstate)) {
|
||||
wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base;
|
||||
wmKeyMap *keymap = WM_event_get_keymap_from_handler(wm, handler);
|
||||
if (keymap && WM_keymap_poll(C, keymap)) {
|
||||
LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) {
|
||||
if (kmi->flag & KMI_INACTIVE) {
|
||||
continue;
|
||||
}
|
||||
if (STR_ELEM(kmi->idname, "WM_OT_call_menu", "WM_OT_call_menu_pie")) {
|
||||
char menu_idname[MAX_NAME];
|
||||
RNA_string_get(kmi->ptr, "name", menu_idname);
|
||||
MenuType *mt = WM_menutype_find(menu_idname, false);
|
||||
|
||||
if (mt && BLI_gset_add(menu_tagged, mt)) {
|
||||
/* Unlikely, but possible this will be included twice. */
|
||||
BLI_linklist_prepend(menuid_stack_p, mt);
|
||||
|
||||
void **kmi_p;
|
||||
if (!BLI_ghash_ensure_p(menu_to_kmi, mt, &kmi_p)) {
|
||||
*kmi_p = kmi;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void menu_items_from_all_operators(bContext *C, struct MenuSearch_Data *data)
|
||||
{
|
||||
/* Add to temporary list so we can sort them separately. */
|
||||
ListBase operator_items = {NULL, NULL};
|
||||
|
||||
MemArena *memarena = data->memarena;
|
||||
GHashIterator iter;
|
||||
for (WM_operatortype_iter(&iter); !BLI_ghashIterator_done(&iter);
|
||||
BLI_ghashIterator_step(&iter)) {
|
||||
wmOperatorType *ot = BLI_ghashIterator_getValue(&iter);
|
||||
|
||||
if ((ot->flag & OPTYPE_INTERNAL) && (G.debug & G_DEBUG_WM) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (WM_operator_poll((bContext *)C, ot)) {
|
||||
const char *ot_ui_name = CTX_IFACE_(ot->translation_context, ot->name);
|
||||
|
||||
struct MenuSearch_Item *item = NULL;
|
||||
item = BLI_memarena_calloc(memarena, sizeof(*item));
|
||||
item->type = MENU_SEARCH_TYPE_OP;
|
||||
|
||||
item->op.type = ot;
|
||||
item->op.opcontext = WM_OP_EXEC_DEFAULT;
|
||||
item->op.context = NULL;
|
||||
|
||||
char idname_as_py[OP_MAX_TYPENAME];
|
||||
char uiname[256];
|
||||
WM_operator_py_idname(idname_as_py, ot->idname);
|
||||
|
||||
SNPRINTF(uiname, "%s " MENU_SEP "%s", idname_as_py, ot_ui_name);
|
||||
|
||||
item->drawwstr_full = strdup_memarena(memarena, uiname);
|
||||
|
||||
BLI_addtail(&operator_items, item);
|
||||
}
|
||||
}
|
||||
|
||||
BLI_listbase_sort(&operator_items, menu_item_sort_by_drawstr_full);
|
||||
|
||||
BLI_movelisttolist(&data->items, &operator_items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create #MenuSearch_Data by inspecting the current context, this uses two methods:
|
||||
*
|
||||
* - Look-up pre-defined editor-menus.
|
||||
* - Look-up key-map items which call menus.
|
||||
*/
|
||||
static struct MenuSearch_Data *menu_items_from_ui_create(bContext *C,
|
||||
wmWindow *win,
|
||||
ScrArea *area,
|
||||
ARegion *region)
|
||||
{
|
||||
MemArena *memarena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
|
||||
/** Map (#MenuType to #MenuSearch_Parent) */
|
||||
GHash *menu_parent_map = BLI_ghash_ptr_new(__func__);
|
||||
GHash *menu_display_name_map = BLI_ghash_ptr_new(__func__);
|
||||
const uiStyle *style = UI_style_get_dpi();
|
||||
|
||||
/* Convert into non-ui structure. */
|
||||
struct MenuSearch_Data *data = MEM_callocN(sizeof(*data), __func__);
|
||||
|
||||
DynStr *dyn_str = BLI_dynstr_new_memarena();
|
||||
|
||||
/* Use a stack of menus to handle and discover new menus in passes. */
|
||||
LinkNode *menu_stack = NULL;
|
||||
|
||||
/* Tag menu types not to add, either because they have already been added
|
||||
* or they have been blacklisted.
|
||||
* Set of #MenuType. */
|
||||
GSet *menu_tagged = BLI_gset_ptr_new(__func__);
|
||||
/** Map (#MenuType -> #wmKeyMapItem). */
|
||||
GHash *menu_to_kmi = BLI_ghash_ptr_new(__func__);
|
||||
|
||||
/* Blacklist menus we don't want to show. */
|
||||
{
|
||||
const char *idname_array[] = {
|
||||
/* While we could include this, it's just showing filenames to load. */
|
||||
"TOPBAR_MT_file_open_recent",
|
||||
};
|
||||
for (int i = 0; i < ARRAY_SIZE(idname_array); i++) {
|
||||
MenuType *mt = WM_menutype_find(idname_array[i], false);
|
||||
if (mt != NULL) {
|
||||
BLI_gset_add(menu_tagged, mt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Populate menus from the editors,
|
||||
* note that we could create a fake header, draw the header and extract the menus
|
||||
* from the buttons, however this is quite involved and can be avoided as by convention
|
||||
* each space-type has a single root-menu that headers use. */
|
||||
{
|
||||
const char *idname_array[] = {
|
||||
"TOPBAR_MT_editor_menus",
|
||||
/* Optional second menu for the space-type. */
|
||||
NULL,
|
||||
};
|
||||
int idname_array_len = 1;
|
||||
|
||||
#define SPACE_MENU_MAP(space_type, menu_id) \
|
||||
case space_type: \
|
||||
idname_array[idname_array_len++] = menu_id; \
|
||||
break
|
||||
#define SPACE_MENU_NOP(space_type) \
|
||||
case space_type: \
|
||||
break
|
||||
|
||||
if (area != NULL) {
|
||||
switch (area->spacetype) {
|
||||
SPACE_MENU_MAP(SPACE_VIEW3D, "VIEW3D_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_GRAPH, "GRAPH_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_OUTLINER, "OUTLINER_MT_editor_menus");
|
||||
SPACE_MENU_NOP(SPACE_PROPERTIES);
|
||||
SPACE_MENU_MAP(SPACE_FILE, "FILE_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_IMAGE, "IMAGE_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_INFO, "INFO_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_SEQ, "SEQUENCER_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_TEXT, "TEXT_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_ACTION, "DOPESHEET_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_NLA, "NLA_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_NODE, "NODE_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_CONSOLE, "CONSOLE_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_USERPREF, "USERPREF_MT_editor_menus");
|
||||
SPACE_MENU_MAP(SPACE_CLIP,
|
||||
(((const SpaceClip *)area->spacedata.first)->mode == SC_MODE_TRACKING) ?
|
||||
"CLIP_MT_tracking_editor_menus" :
|
||||
"CLIP_MT_masking_editor_menus");
|
||||
SPACE_MENU_NOP(SPACE_TOPBAR);
|
||||
SPACE_MENU_NOP(SPACE_STATUSBAR);
|
||||
default:
|
||||
printf("Unknown space type '%d'\n", area->spacetype);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < idname_array_len; i++) {
|
||||
MenuType *mt = WM_menutype_find(idname_array[i], false);
|
||||
if (mt != NULL) {
|
||||
BLI_linklist_prepend(&menu_stack, mt);
|
||||
BLI_gset_add(menu_tagged, mt);
|
||||
}
|
||||
}
|
||||
}
|
||||
#undef SPACE_MENU_MAP
|
||||
#undef SPACE_MENU_NOP
|
||||
|
||||
bool has_keymap_menu_items = false;
|
||||
|
||||
GHashIterator iter;
|
||||
|
||||
while (menu_stack != NULL) {
|
||||
MenuType *mt = BLI_linklist_pop(&menu_stack);
|
||||
if (!WM_menutype_poll(C, mt)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS);
|
||||
uiLayout *layout = UI_block_layout(
|
||||
block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style);
|
||||
|
||||
UI_block_flag_enable(block, UI_BLOCK_SHOW_SHORTCUT_ALWAYS);
|
||||
|
||||
uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_REGION_WIN);
|
||||
UI_menutype_draw(C, mt, layout);
|
||||
|
||||
UI_block_end(C, block);
|
||||
|
||||
LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
|
||||
MenuType *mt_from_but = NULL;
|
||||
/* Support menu titles with dynamic from initial labels
|
||||
* (used by edit-mesh context menu). */
|
||||
if (but->type == UI_BTYPE_LABEL) {
|
||||
|
||||
/* Check if the label is the title. */
|
||||
uiBut *but_test = but->prev;
|
||||
while (but_test && but_test->type == UI_BTYPE_SEPR) {
|
||||
but_test = but_test->prev;
|
||||
}
|
||||
|
||||
if (but_test == NULL) {
|
||||
BLI_ghash_insert(
|
||||
menu_display_name_map, mt, (void *)strdup_memarena(memarena, but->drawstr));
|
||||
}
|
||||
}
|
||||
else if (menu_items_from_ui_create_item_from_button(data, memarena, mt, NULL, but)) {
|
||||
/* pass */
|
||||
}
|
||||
else if ((mt_from_but = UI_but_menutype_get(but))) {
|
||||
|
||||
if (BLI_gset_add(menu_tagged, mt_from_but)) {
|
||||
BLI_linklist_prepend(&menu_stack, mt_from_but);
|
||||
}
|
||||
|
||||
if (!BLI_ghash_haskey(menu_parent_map, mt_from_but)) {
|
||||
struct MenuSearch_Parent *menu_parent = BLI_memarena_calloc(memarena,
|
||||
sizeof(*menu_parent));
|
||||
/* Use brackets for menu key shortcuts,
|
||||
* converting "Text|Some-Shortcut" to "Text (Some-Shortcut)".
|
||||
* This is needed so we don't right align sub-menu contents
|
||||
* we only want to do that for the last menu item, not the path that leads to it.
|
||||
*/
|
||||
const char *drawstr_sep = but->flag & UI_BUT_HAS_SEP_CHAR ?
|
||||
strrchr(but->drawstr, UI_SEP_CHAR) :
|
||||
NULL;
|
||||
bool drawstr_is_empty = false;
|
||||
if (drawstr_sep != NULL) {
|
||||
BLI_assert(BLI_dynstr_get_len(dyn_str) == 0);
|
||||
/* Detect empty string, fallback to menu name. */
|
||||
const char *drawstr = but->drawstr;
|
||||
int drawstr_len = drawstr_sep - but->drawstr;
|
||||
if (UNLIKELY(drawstr_len == 0)) {
|
||||
drawstr = CTX_IFACE_(mt_from_but->translation_context, mt_from_but->label);
|
||||
drawstr_len = strlen(drawstr);
|
||||
if (drawstr[0] == '\0') {
|
||||
drawstr_is_empty = true;
|
||||
}
|
||||
}
|
||||
BLI_dynstr_nappend(dyn_str, drawstr, drawstr_len);
|
||||
BLI_dynstr_appendf(dyn_str, " (%s)", drawstr_sep + 1);
|
||||
menu_parent->drawstr = strdup_memarena_from_dynstr(memarena, dyn_str);
|
||||
BLI_dynstr_clear(dyn_str);
|
||||
}
|
||||
else {
|
||||
const char *drawstr = but->drawstr;
|
||||
if (UNLIKELY(drawstr[0] == '\0')) {
|
||||
drawstr = CTX_IFACE_(mt_from_but->translation_context, mt_from_but->label);
|
||||
if (drawstr[0] == '\0') {
|
||||
drawstr_is_empty = true;
|
||||
}
|
||||
}
|
||||
menu_parent->drawstr = strdup_memarena(memarena, drawstr);
|
||||
}
|
||||
menu_parent->parent_mt = mt;
|
||||
BLI_ghash_insert(menu_parent_map, mt_from_but, menu_parent);
|
||||
|
||||
if (drawstr_is_empty) {
|
||||
printf("Warning: '%s' menu has empty 'bl_label'.\n", mt_from_but->idname);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (but->menu_create_func != NULL) {
|
||||
/* A non 'MenuType' menu button. */
|
||||
|
||||
/* Only expand one level deep, this is mainly for expanding operator menus. */
|
||||
const char *drawstr_submenu = but->drawstr;
|
||||
|
||||
/* +1 to avoid overlap with the current 'block'. */
|
||||
uiBlock *sub_block = UI_block_begin(C, region, __func__ + 1, UI_EMBOSS);
|
||||
uiLayout *sub_layout = UI_block_layout(
|
||||
sub_block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style);
|
||||
|
||||
UI_block_flag_enable(sub_block, UI_BLOCK_SHOW_SHORTCUT_ALWAYS);
|
||||
|
||||
uiLayoutSetOperatorContext(sub_layout, WM_OP_INVOKE_REGION_WIN);
|
||||
|
||||
but->menu_create_func(C, sub_layout, but->poin);
|
||||
|
||||
UI_block_end(C, sub_block);
|
||||
|
||||
LISTBASE_FOREACH (uiBut *, sub_but, &sub_block->buttons) {
|
||||
menu_items_from_ui_create_item_from_button(data, memarena, mt, drawstr_submenu, sub_but);
|
||||
}
|
||||
|
||||
if (region) {
|
||||
BLI_remlink(®ion->uiblocks, sub_block);
|
||||
}
|
||||
UI_block_free(NULL, sub_block);
|
||||
}
|
||||
}
|
||||
if (region) {
|
||||
BLI_remlink(®ion->uiblocks, block);
|
||||
}
|
||||
UI_block_free(NULL, block);
|
||||
|
||||
/* Add key-map items as a second pass,
|
||||
* so all menus are accessed from the header & top-bar before key shortcuts are expanded. */
|
||||
if ((menu_stack == NULL) && (has_keymap_menu_items == false)) {
|
||||
has_keymap_menu_items = true;
|
||||
menu_types_add_from_keymap_items(
|
||||
C, win, area, region, &menu_stack, menu_to_kmi, menu_tagged);
|
||||
}
|
||||
}
|
||||
|
||||
LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) {
|
||||
item->menu_parent = BLI_ghash_lookup(menu_parent_map, item->mt);
|
||||
}
|
||||
|
||||
GHASH_ITER (iter, menu_parent_map) {
|
||||
struct MenuSearch_Parent *menu_parent = BLI_ghashIterator_getValue(&iter);
|
||||
menu_parent->parent = BLI_ghash_lookup(menu_parent_map, menu_parent->parent_mt);
|
||||
}
|
||||
|
||||
/* NOTE: currently this builds the full path for each menu item,
|
||||
* that could be moved into the parent menu. */
|
||||
|
||||
/* Set names as full paths. */
|
||||
LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) {
|
||||
if (item->menu_parent != NULL) {
|
||||
struct MenuSearch_Parent *menu_parent = item->menu_parent;
|
||||
menu_parent->temp_child = NULL;
|
||||
while (menu_parent && menu_parent->parent) {
|
||||
menu_parent->parent->temp_child = menu_parent;
|
||||
menu_parent = menu_parent->parent;
|
||||
}
|
||||
BLI_assert(BLI_dynstr_get_len(dyn_str) == 0);
|
||||
while (menu_parent) {
|
||||
BLI_dynstr_append(dyn_str, menu_parent->drawstr);
|
||||
BLI_dynstr_append(dyn_str, " " MENU_SEP " ");
|
||||
menu_parent = menu_parent->temp_child;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BLI_assert(BLI_dynstr_get_len(dyn_str) == 0);
|
||||
const char *drawstr = BLI_ghash_lookup(menu_display_name_map, item->mt);
|
||||
if (drawstr == NULL) {
|
||||
drawstr = CTX_IFACE_(item->mt->translation_context, item->mt->label);
|
||||
}
|
||||
BLI_dynstr_append(dyn_str, drawstr);
|
||||
|
||||
wmKeyMapItem *kmi = BLI_ghash_lookup(menu_to_kmi, item->mt);
|
||||
if (kmi != NULL) {
|
||||
char kmi_str[128];
|
||||
WM_keymap_item_to_string(kmi, false, kmi_str, sizeof(kmi_str));
|
||||
BLI_dynstr_appendf(dyn_str, " (%s)", kmi_str);
|
||||
}
|
||||
|
||||
BLI_dynstr_append(dyn_str, " " MENU_SEP " ");
|
||||
}
|
||||
|
||||
/* Optional nested menu. */
|
||||
if (item->drawstr_submenu != NULL) {
|
||||
BLI_dynstr_append(dyn_str, item->drawstr_submenu);
|
||||
BLI_dynstr_append(dyn_str, " " MENU_SEP " ");
|
||||
}
|
||||
|
||||
BLI_dynstr_append(dyn_str, item->drawstr);
|
||||
|
||||
item->drawwstr_full = strdup_memarena_from_dynstr(memarena, dyn_str);
|
||||
BLI_dynstr_clear(dyn_str);
|
||||
}
|
||||
BLI_dynstr_free(dyn_str);
|
||||
|
||||
/* Finally sort menu items.
|
||||
*
|
||||
* Note: we might want to keep the in-menu order, for now sort all. */
|
||||
BLI_listbase_sort(&data->items, menu_item_sort_by_drawstr_full);
|
||||
|
||||
BLI_ghash_free(menu_parent_map, NULL, NULL);
|
||||
BLI_ghash_free(menu_display_name_map, NULL, NULL);
|
||||
|
||||
BLI_ghash_free(menu_to_kmi, NULL, NULL);
|
||||
|
||||
BLI_gset_free(menu_tagged, NULL);
|
||||
|
||||
data->memarena = memarena;
|
||||
|
||||
if (U.flag & USER_DEVELOPER_UI) {
|
||||
menu_items_from_all_operators(C, data);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static void menu_items_from_ui_destroy(void *data_v)
|
||||
{
|
||||
struct MenuSearch_Data *data = data_v;
|
||||
LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) {
|
||||
switch (item->type) {
|
||||
case MENU_SEARCH_TYPE_OP: {
|
||||
if (item->op.opptr != NULL) {
|
||||
WM_operator_properties_free(item->op.opptr);
|
||||
MEM_freeN(item->op.opptr);
|
||||
}
|
||||
}
|
||||
case MENU_SEARCH_TYPE_RNA: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BLI_memarena_free(data->memarena);
|
||||
|
||||
MEM_freeN(data);
|
||||
}
|
||||
|
||||
static void menu_call_fn(bContext *C, void *UNUSED(arg1), void *arg2)
|
||||
{
|
||||
struct MenuSearch_Item *item = arg2;
|
||||
if (item == NULL) {
|
||||
return;
|
||||
}
|
||||
if (item->state & UI_BUT_DISABLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (item->type) {
|
||||
case MENU_SEARCH_TYPE_OP: {
|
||||
CTX_store_set(C, item->op.context);
|
||||
WM_operator_name_call_ptr(C, item->op.type, item->op.opcontext, item->op.opptr);
|
||||
CTX_store_set(C, NULL);
|
||||
break;
|
||||
}
|
||||
case MENU_SEARCH_TYPE_RNA: {
|
||||
PointerRNA *ptr = &item->rna.ptr;
|
||||
PropertyRNA *prop = item->rna.prop;
|
||||
int index = item->rna.index;
|
||||
const int prop_type = RNA_property_type(prop);
|
||||
bool changed = false;
|
||||
|
||||
if (prop_type == PROP_BOOLEAN) {
|
||||
const bool is_array = RNA_property_array_check(prop);
|
||||
if (is_array) {
|
||||
const bool value = RNA_property_boolean_get_index(ptr, prop, index);
|
||||
RNA_property_boolean_set_index(ptr, prop, index, !value);
|
||||
}
|
||||
else {
|
||||
const bool value = RNA_property_boolean_get(ptr, prop);
|
||||
RNA_property_boolean_set(ptr, prop, !value);
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
else if (prop_type == PROP_ENUM) {
|
||||
RNA_property_enum_set(ptr, prop, item->rna.enum_value);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
RNA_property_update(C, ptr, prop);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void menu_search_cb(const bContext *UNUSED(C),
|
||||
void *arg,
|
||||
const char *str,
|
||||
uiSearchItems *items)
|
||||
{
|
||||
struct MenuSearch_Data *data = arg;
|
||||
const size_t str_len = strlen(str);
|
||||
const int words_max = (str_len / 2) + 1;
|
||||
int(*words)[2] = BLI_array_alloca(words, words_max);
|
||||
|
||||
const int words_len = BLI_string_find_split_words(str, str_len, ' ', words, words_max);
|
||||
|
||||
for (struct MenuSearch_Item *item = data->items.first; item; item = item->next) {
|
||||
int index;
|
||||
|
||||
/* match name against all search words */
|
||||
for (index = 0; index < words_len; index++) {
|
||||
if (!has_word_prefix(item->drawwstr_full, str + words[index][0], words[index][1])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index == words_len) {
|
||||
if (!UI_search_item_add(items, item->drawwstr_full, item, item->icon, item->state)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UI_but_func_menu_search(uiBut *but)
|
||||
{
|
||||
bContext *C = but->block->evil_C;
|
||||
wmWindow *win = CTX_wm_window(C);
|
||||
ScrArea *area = CTX_wm_area(C);
|
||||
ARegion *region = CTX_wm_region(C);
|
||||
struct MenuSearch_Data *data = menu_items_from_ui_create(C, win, area, region);
|
||||
UI_but_func_search_set(but,
|
||||
ui_searchbox_create_menu,
|
||||
menu_search_cb,
|
||||
data,
|
||||
menu_items_from_ui_destroy,
|
||||
menu_call_fn,
|
||||
MENU_SEP,
|
||||
NULL);
|
||||
}
|
||||
|
||||
void uiTemplateMenuSearch(uiLayout *layout)
|
||||
{
|
||||
uiBlock *block;
|
||||
uiBut *but;
|
||||
static char search[256] = "";
|
||||
|
||||
block = uiLayoutGetBlock(layout);
|
||||
UI_block_layout_set_current(block, layout);
|
||||
|
||||
but = uiDefSearchBut(
|
||||
block, search, 0, ICON_VIEWZOOM, sizeof(search), 0, 0, UI_UNIT_X * 6, UI_UNIT_Y, 0, 0, "");
|
||||
UI_but_func_menu_search(but);
|
||||
}
|
||||
|
||||
#undef MENU_SEP
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Running Jobs Template
|
||||
* \{ */
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
@ -51,6 +52,22 @@
|
|||
|
||||
#include "interface_intern.h"
|
||||
|
||||
bool ui_str_has_word_prefix(const char *haystack, const char *needle, size_t needle_len)
|
||||
{
|
||||
const char *match = BLI_strncasestr(haystack, needle, needle_len);
|
||||
if (match) {
|
||||
if ((match == haystack) || (*(match - 1) == ' ') || ispunct(*(match - 1))) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return ui_str_has_word_prefix(match + 1, needle, needle_len);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************** RNA Utilities ******************************/
|
||||
|
||||
uiBut *uiDefAutoButR(uiBlock *block,
|
||||
|
|
Loading…
Reference in New Issue