This patch adds domain and data type information to each row of the attribute search menu. The data type is displayed on the right, just like how the list is exposed for the existing point cloud and hair attribute panels. The domain is exposed on the left like the menu hierarchy from menu search. For the implementation, the attribute hint information is stored as a set instead of a multi-value map so that every item (which we need to point to descretely in the search process) contains the necessary data type and domain information by itself. We also need to allocate a new struct for every button, which requires a change to allow passing a newly allocated argument to search buttons. Note that the search does't yet handle the case where there are two attributes with the same name but different domains or data types in the input geometry set. That will be handled as a separate improvement. Differential Revision: https://developer.blender.org/D10623
1178 lines
38 KiB
C
1178 lines
38 KiB
C
/*
|
|
* 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
|
|
*
|
|
* Search available menu items via the user interface & key-maps.
|
|
* Accessed via the #WM_OT_search_menu operator.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "DNA_action_types.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_math_matrix.h"
|
|
#include "BLI_memarena.h"
|
|
#include "BLI_string.h"
|
|
#include "BLI_string_search.h"
|
|
#include "BLI_string_utils.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"
|
|
|
|
/**
|
|
* Use when #menu_items_from_ui_create is called with `include_all_areas`.
|
|
* so we can run the menu item in the area it was extracted from.
|
|
*/
|
|
struct MenuSearch_Context {
|
|
/**
|
|
* Index into `Area.ui_type` #EnumPropertyItem or the top-bar when -1.
|
|
* Needed to get the display-name to use as a prefix for each menu item.
|
|
*/
|
|
int space_type_ui_index;
|
|
|
|
ScrArea *area;
|
|
ARegion *region;
|
|
};
|
|
|
|
struct MenuSearch_Parent {
|
|
struct MenuSearch_Parent *parent;
|
|
MenuType *parent_mt;
|
|
const char *drawstr;
|
|
|
|
/** Set while writing menu items only. */
|
|
struct MenuSearch_Parent *temp_child;
|
|
};
|
|
|
|
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-box/boolean). */
|
|
struct {
|
|
PointerRNA ptr;
|
|
PropertyRNA *prop;
|
|
int index;
|
|
/** Only for enum buttons. */
|
|
int enum_value;
|
|
} rna;
|
|
};
|
|
|
|
/** Set when we need each menu item to be able to set its own context. may be NULL. */
|
|
struct MenuSearch_Context *wm_context;
|
|
};
|
|
|
|
struct MenuSearch_Data {
|
|
/** MenuSearch_Item */
|
|
ListBase items;
|
|
/** Use for all small allocations. */
|
|
MemArena *memarena;
|
|
|
|
/** Use for context menu, to fake a button to create a context menu. */
|
|
struct {
|
|
uiBut but;
|
|
uiBlock block;
|
|
} context_menu_data;
|
|
};
|
|
|
|
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_Context *wm_context)
|
|
{
|
|
struct MenuSearch_Item *item = NULL;
|
|
|
|
/* Use override if the name is empty, this can happen with popovers. */
|
|
const char *drawstr_override = NULL;
|
|
const char *drawstr_sep = (but->flag & UI_BUT_HAS_SEP_CHAR) ?
|
|
strrchr(but->drawstr, UI_SEP_CHAR) :
|
|
NULL;
|
|
const bool drawstr_is_empty = (drawstr_sep == but->drawstr) || (but->drawstr[0] == '\0');
|
|
|
|
if (but->optype != NULL) {
|
|
if (drawstr_is_empty) {
|
|
drawstr_override = WM_operatortype_name(but->optype, but->opptr);
|
|
}
|
|
|
|
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 (drawstr_is_empty) {
|
|
if (prop_type == PROP_ENUM) {
|
|
const int value_enum = (int)but->hardmax;
|
|
EnumPropertyItem enum_item;
|
|
if (RNA_property_enum_item_from_value_gettexted(
|
|
but->block->evil_C, &but->rnapoin, but->rnaprop, value_enum, &enum_item)) {
|
|
drawstr_override = enum_item.name;
|
|
}
|
|
else {
|
|
/* Should never happen. */
|
|
drawstr_override = "Unknown";
|
|
}
|
|
}
|
|
else {
|
|
drawstr_override = RNA_property_ui_name(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. */
|
|
if (drawstr_override != NULL) {
|
|
const char *drawstr_suffix = drawstr_sep ? drawstr_sep : "";
|
|
char *drawstr_alloc = BLI_string_joinN("(", drawstr_override, ")", drawstr_suffix);
|
|
item->drawstr = strdup_memarena(memarena, drawstr_alloc);
|
|
MEM_freeN(drawstr_alloc);
|
|
}
|
|
else {
|
|
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 | UI_BUT_HAS_SEP_CHAR));
|
|
item->mt = mt;
|
|
item->drawstr_submenu = drawstr_submenu ? strdup_memarena(memarena, drawstr_submenu) : NULL;
|
|
|
|
item->wm_context = wm_context;
|
|
|
|
BLI_addtail(&data->items, item);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Populate a fake button from a menu item (use for context menu).
|
|
*/
|
|
static bool menu_items_to_ui_button(struct MenuSearch_Item *item, uiBut *but)
|
|
{
|
|
bool changed = false;
|
|
switch (item->type) {
|
|
case MENU_SEARCH_TYPE_OP: {
|
|
but->optype = item->op.type;
|
|
but->opcontext = item->op.opcontext;
|
|
but->context = item->op.context;
|
|
but->opptr = item->op.opptr;
|
|
changed = true;
|
|
break;
|
|
}
|
|
case MENU_SEARCH_TYPE_RNA: {
|
|
const int prop_type = RNA_property_type(item->rna.prop);
|
|
|
|
but->rnapoin = item->rna.ptr;
|
|
but->rnaprop = item->rna.prop;
|
|
but->rnaindex = item->rna.index;
|
|
|
|
if (prop_type == PROP_ENUM) {
|
|
but->hardmax = item->rna.enum_value;
|
|
}
|
|
changed = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
STRNCPY(but->drawstr, item->drawstr);
|
|
char *drawstr_sep = (item->state & UI_BUT_HAS_SEP_CHAR) ? strrchr(but->drawstr, UI_SEP_CHAR) :
|
|
NULL;
|
|
if (drawstr_sep) {
|
|
*drawstr_sep = '\0';
|
|
}
|
|
|
|
but->icon = item->icon;
|
|
but->str = but->strdata;
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display all operators (last). Developer-only convenience feature.
|
|
*/
|
|
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_INVOKE_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);
|
|
item->drawstr = ot_ui_name;
|
|
|
|
item->wm_context = NULL;
|
|
|
|
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 predefined 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_init, ARegion *region_init, bool include_all_areas)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
/* Exclude context menus because:
|
|
* - The menu items are available elsewhere (and will show up multiple times).
|
|
* - Menu items depend on exact context, making search results unpredictable
|
|
* (exact number of items selected for example). See design doc T74158.
|
|
* There is one exception,
|
|
* as the outliner only exposes functionality via the context menu. */
|
|
GHashIterator iter;
|
|
|
|
for (WM_menutype_iter(&iter); (!BLI_ghashIterator_done(&iter));
|
|
(BLI_ghashIterator_step(&iter))) {
|
|
MenuType *mt = BLI_ghashIterator_getValue(&iter);
|
|
if (BLI_str_endswith(mt->idname, "_context_menu")) {
|
|
BLI_gset_add(menu_tagged, mt);
|
|
}
|
|
}
|
|
const char *idname_array[] = {
|
|
/* Add back some context menus. */
|
|
"OUTLINER_MT_context_menu",
|
|
};
|
|
for (int i = 0; i < ARRAY_SIZE(idname_array); i++) {
|
|
MenuType *mt = WM_menutype_find(idname_array[i], false);
|
|
if (mt != NULL) {
|
|
BLI_gset_remove(menu_tagged, mt, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Collect contexts, one for each 'ui_type'. */
|
|
struct MenuSearch_Context *wm_contexts = NULL;
|
|
|
|
const EnumPropertyItem *space_type_ui_items = NULL;
|
|
int space_type_ui_items_len = 0;
|
|
bool space_type_ui_items_free = false;
|
|
|
|
/* Text used as prefix for top-bar menu items. */
|
|
const char *global_menu_prefix = NULL;
|
|
|
|
if (include_all_areas) {
|
|
bScreen *screen = WM_window_get_active_screen(win);
|
|
|
|
/* First create arrays for ui_type. */
|
|
PropertyRNA *prop_ui_type = NULL;
|
|
{
|
|
/* This must be a valid pointer, with only it's type checked. */
|
|
ScrArea area_dummy = {
|
|
/* Anything besides #SPACE_EMPTY is fine,
|
|
* as this value is only included in the enum when set. */
|
|
.spacetype = SPACE_TOPBAR,
|
|
};
|
|
PointerRNA ptr;
|
|
RNA_pointer_create(&screen->id, &RNA_Area, &area_dummy, &ptr);
|
|
prop_ui_type = RNA_struct_find_property(&ptr, "ui_type");
|
|
RNA_property_enum_items(C,
|
|
&ptr,
|
|
prop_ui_type,
|
|
&space_type_ui_items,
|
|
&space_type_ui_items_len,
|
|
&space_type_ui_items_free);
|
|
|
|
wm_contexts = BLI_memarena_calloc(memarena, sizeof(*wm_contexts) * space_type_ui_items_len);
|
|
for (int i = 0; i < space_type_ui_items_len; i++) {
|
|
wm_contexts[i].space_type_ui_index = -1;
|
|
}
|
|
}
|
|
|
|
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
|
|
ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW);
|
|
if (region != NULL) {
|
|
PointerRNA ptr;
|
|
RNA_pointer_create(&screen->id, &RNA_Area, area, &ptr);
|
|
const int space_type_ui = RNA_property_enum_get(&ptr, prop_ui_type);
|
|
|
|
const int space_type_ui_index = RNA_enum_from_value(space_type_ui_items, space_type_ui);
|
|
if (space_type_ui_index == -1) {
|
|
continue;
|
|
}
|
|
|
|
if (wm_contexts[space_type_ui_index].space_type_ui_index != -1) {
|
|
ScrArea *area_best = wm_contexts[space_type_ui_index].area;
|
|
const uint value_best = (uint)area_best->winx * (uint)area_best->winy;
|
|
const uint value_test = (uint)area->winx * (uint)area->winy;
|
|
if (value_best > value_test) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
wm_contexts[space_type_ui_index].space_type_ui_index = space_type_ui_index;
|
|
wm_contexts[space_type_ui_index].area = area;
|
|
wm_contexts[space_type_ui_index].region = region;
|
|
}
|
|
}
|
|
|
|
global_menu_prefix = CTX_IFACE_(RNA_property_translation_context(prop_ui_type), "Top Bar");
|
|
}
|
|
|
|
GHashIterator iter;
|
|
|
|
for (int space_type_ui_index = -1; space_type_ui_index < space_type_ui_items_len;
|
|
space_type_ui_index += 1) {
|
|
|
|
ScrArea *area = NULL;
|
|
ARegion *region = NULL;
|
|
struct MenuSearch_Context *wm_context = NULL;
|
|
|
|
if (include_all_areas) {
|
|
if (space_type_ui_index == -1) {
|
|
/* First run without any context, to populate the top-bar without. */
|
|
wm_context = NULL;
|
|
area = NULL;
|
|
region = NULL;
|
|
}
|
|
else {
|
|
wm_context = &wm_contexts[space_type_ui_index];
|
|
if (wm_context->space_type_ui_index == -1) {
|
|
continue;
|
|
}
|
|
|
|
area = wm_context->area;
|
|
region = wm_context->region;
|
|
|
|
CTX_wm_area_set(C, area);
|
|
CTX_wm_region_set(C, region);
|
|
}
|
|
}
|
|
else {
|
|
area = area_init;
|
|
region = region_init;
|
|
}
|
|
|
|
/* 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[2] = {NULL};
|
|
int idname_array_len = 0;
|
|
|
|
/* Use negative for global (no area) context, populate the top-bar. */
|
|
if (space_type_ui_index == -1) {
|
|
idname_array[idname_array_len++] = "TOPBAR_MT_editor_menus";
|
|
}
|
|
|
|
#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) {
|
|
SpaceLink *sl = area->spacedata.first;
|
|
switch ((eSpace_Type)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, "FILEBROWSER_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,
|
|
(((const SpaceAction *)sl)->mode == SACTCONT_TIMELINE) ?
|
|
"TIME_MT_editor_menus" :
|
|
"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 *)sl)->mode == SC_MODE_TRACKING) ?
|
|
"CLIP_MT_tracking_editor_menus" :
|
|
"CLIP_MT_masking_editor_menus");
|
|
SPACE_MENU_NOP(SPACE_EMPTY);
|
|
SPACE_MENU_NOP(SPACE_SCRIPT);
|
|
SPACE_MENU_NOP(SPACE_STATUSBAR);
|
|
SPACE_MENU_NOP(SPACE_TOPBAR);
|
|
SPACE_MENU_NOP(SPACE_SPREADSHEET);
|
|
}
|
|
}
|
|
for (int i = 0; i < idname_array_len; i++) {
|
|
MenuType *mt = WM_menutype_find(idname_array[i], false);
|
|
if (mt != NULL) {
|
|
/* Check if this exists because of 'include_all_areas'. */
|
|
if (BLI_gset_add(menu_tagged, mt)) {
|
|
BLI_linklist_prepend(&menu_stack, mt);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#undef SPACE_MENU_MAP
|
|
#undef SPACE_MENU_NOP
|
|
|
|
bool has_keymap_menu_items = false;
|
|
|
|
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, wm_context)) {
|
|
/* 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, wm_context);
|
|
}
|
|
|
|
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) {
|
|
BLI_assert(BLI_dynstr_get_len(dyn_str) == 0);
|
|
|
|
if (include_all_areas) {
|
|
BLI_dynstr_appendf(dyn_str,
|
|
"%s: ",
|
|
(item->wm_context != NULL) ?
|
|
space_type_ui_items[item->wm_context->space_type_ui_index].name :
|
|
global_menu_prefix);
|
|
}
|
|
|
|
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;
|
|
}
|
|
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 {
|
|
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 (include_all_areas) {
|
|
CTX_wm_area_set(C, area_init);
|
|
CTX_wm_region_set(C, region_init);
|
|
|
|
if (space_type_ui_items_free) {
|
|
MEM_freeN((void *)space_type_ui_items);
|
|
}
|
|
}
|
|
|
|
/* Include all operators for developers,
|
|
* since it can be handy to have a quick way to access any operator,
|
|
* including operators being developed which haven't yet been added into the interface.
|
|
*
|
|
* These are added after all menu items so developers still get normal behavior by default,
|
|
* unless searching for something that isn't already in a menu (or scroll down).
|
|
*
|
|
* Keep this behind a developer only check:
|
|
* - Many operators need options to be set to give useful results, see: T74157.
|
|
* - User who really prefer to list all operators can use #WM_OT_search_operator.
|
|
*/
|
|
if (U.flag & USER_DEVELOPER_UI) {
|
|
menu_items_from_all_operators(C, data);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
static void menu_search_arg_free_fn(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_search_exec_fn(bContext *C, void *UNUSED(arg1), void *arg2)
|
|
{
|
|
struct MenuSearch_Item *item = arg2;
|
|
if (item == NULL) {
|
|
return;
|
|
}
|
|
if (item->state & UI_BUT_DISABLED) {
|
|
return;
|
|
}
|
|
|
|
ScrArea *area_prev = CTX_wm_area(C);
|
|
ARegion *region_prev = CTX_wm_region(C);
|
|
|
|
if (item->wm_context != NULL) {
|
|
CTX_wm_area_set(C, item->wm_context->area);
|
|
CTX_wm_region_set(C, item->wm_context->region);
|
|
}
|
|
|
|
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;
|
|
const 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;
|
|
}
|
|
}
|
|
|
|
if (item->wm_context != NULL) {
|
|
CTX_wm_area_set(C, area_prev);
|
|
CTX_wm_region_set(C, region_prev);
|
|
}
|
|
}
|
|
|
|
static void menu_search_update_fn(const bContext *UNUSED(C),
|
|
void *arg,
|
|
const char *str,
|
|
uiSearchItems *items,
|
|
const bool UNUSED(is_first))
|
|
{
|
|
struct MenuSearch_Data *data = arg;
|
|
|
|
StringSearch *search = BLI_string_search_new();
|
|
|
|
LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) {
|
|
BLI_string_search_add(search, item->drawwstr_full, item);
|
|
}
|
|
|
|
struct MenuSearch_Item **filtered_items;
|
|
const int filtered_amount = BLI_string_search_query(search, str, (void ***)&filtered_items);
|
|
|
|
for (int i = 0; i < filtered_amount; i++) {
|
|
struct MenuSearch_Item *item = filtered_items[i];
|
|
if (!UI_search_item_add(items, item->drawwstr_full, item, item->icon, item->state, 0)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
MEM_freeN(filtered_items);
|
|
BLI_string_search_free(search);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Context Menu
|
|
*
|
|
* This uses a fake button to create a context menu,
|
|
* if this ever causes hard to solve bugs we may need to create
|
|
* a separate context menu just for the search, however this is fairly involved.
|
|
* \{ */
|
|
|
|
static bool ui_search_menu_create_context_menu(struct bContext *C,
|
|
void *arg,
|
|
void *active,
|
|
const struct wmEvent *UNUSED(event))
|
|
{
|
|
struct MenuSearch_Data *data = arg;
|
|
struct MenuSearch_Item *item = active;
|
|
bool has_menu = false;
|
|
|
|
memset(&data->context_menu_data, 0x0, sizeof(data->context_menu_data));
|
|
uiBut *but = &data->context_menu_data.but;
|
|
uiBlock *block = &data->context_menu_data.block;
|
|
|
|
but->block = block;
|
|
|
|
if (menu_items_to_ui_button(item, but)) {
|
|
ScrArea *area_prev = CTX_wm_area(C);
|
|
ARegion *region_prev = CTX_wm_region(C);
|
|
|
|
if (item->wm_context != NULL) {
|
|
CTX_wm_area_set(C, item->wm_context->area);
|
|
CTX_wm_region_set(C, item->wm_context->region);
|
|
}
|
|
|
|
if (ui_popup_context_menu_for_button(C, but)) {
|
|
has_menu = true;
|
|
}
|
|
|
|
if (item->wm_context != NULL) {
|
|
CTX_wm_area_set(C, area_prev);
|
|
CTX_wm_region_set(C, region_prev);
|
|
}
|
|
}
|
|
|
|
return has_menu;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Tooltip
|
|
* \{ */
|
|
|
|
static struct ARegion *ui_search_menu_create_tooltip(struct bContext *C,
|
|
struct ARegion *region,
|
|
const rcti *UNUSED(item_rect),
|
|
void *arg,
|
|
void *active)
|
|
{
|
|
struct MenuSearch_Data *data = arg;
|
|
struct MenuSearch_Item *item = active;
|
|
|
|
memset(&data->context_menu_data, 0x0, sizeof(data->context_menu_data));
|
|
uiBut *but = &data->context_menu_data.but;
|
|
uiBlock *block = &data->context_menu_data.block;
|
|
unit_m4(block->winmat);
|
|
block->aspect = 1;
|
|
|
|
but->block = block;
|
|
|
|
/* Place the fake button at the cursor so the tool-tip is places properly. */
|
|
float tip_init[2];
|
|
const wmEvent *event = CTX_wm_window(C)->eventstate;
|
|
tip_init[0] = event->x;
|
|
tip_init[1] = event->y - (UI_UNIT_Y / 2);
|
|
ui_window_to_block_fl(region, block, &tip_init[0], &tip_init[1]);
|
|
|
|
but->rect.xmin = tip_init[0];
|
|
but->rect.xmax = tip_init[0];
|
|
but->rect.ymin = tip_init[1];
|
|
but->rect.ymax = tip_init[1];
|
|
|
|
if (menu_items_to_ui_button(item, but)) {
|
|
ScrArea *area_prev = CTX_wm_area(C);
|
|
ARegion *region_prev = CTX_wm_region(C);
|
|
|
|
if (item->wm_context != NULL) {
|
|
CTX_wm_area_set(C, item->wm_context->area);
|
|
CTX_wm_region_set(C, item->wm_context->region);
|
|
}
|
|
|
|
ARegion *region_tip = UI_tooltip_create_from_button(C, region, but, false);
|
|
|
|
if (item->wm_context != NULL) {
|
|
CTX_wm_area_set(C, area_prev);
|
|
CTX_wm_region_set(C, region_prev);
|
|
}
|
|
return region_tip;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \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);
|
|
/* When run from top-bar scan all areas in the current window. */
|
|
const bool include_all_areas = (area && (area->spacetype == SPACE_TOPBAR));
|
|
struct MenuSearch_Data *data = menu_items_from_ui_create(
|
|
C, win, area, region, include_all_areas);
|
|
UI_but_func_search_set(but,
|
|
/* Generic callback. */
|
|
ui_searchbox_create_menu,
|
|
menu_search_update_fn,
|
|
data,
|
|
false,
|
|
menu_search_arg_free_fn,
|
|
menu_search_exec_fn,
|
|
NULL);
|
|
|
|
UI_but_func_search_set_context_menu(but, ui_search_menu_create_context_menu);
|
|
UI_but_func_search_set_tooltip(but, ui_search_menu_create_tooltip);
|
|
UI_but_func_search_set_sep_string(but, MENU_SEP);
|
|
}
|
|
|
|
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
|
|
|
|
/** \} */
|