From 82704ac3edf0e37d9eea2860bc565bcf521ae593 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 7 May 2020 23:16:22 +1000 Subject: [PATCH] UI: support context menu in menu search popup --- source/blender/editors/include/UI_interface.h | 5 + source/blender/editors/interface/interface.c | 6 + .../editors/interface/interface_handlers.c | 15 +++ .../editors/interface/interface_intern.h | 4 +- .../interface/interface_region_search.c | 30 ++++- .../interface_template_search_menu.c | 104 +++++++++++++++++- .../windowmanager/intern/wm_operators.c | 2 +- 7 files changed, 161 insertions(+), 5 deletions(-) diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index 4f010156c17..2ea03fa5bc2 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -512,6 +512,10 @@ typedef void (*uiButSearchUpdateFn)(const struct bContext *C, const char *str, uiSearchItems *items); typedef void (*uiButSearchArgFreeFn)(void *arg); +typedef bool (*uiButSearchContextMenuFn)(struct bContext *C, + void *arg, + void *active, + const struct wmEvent *event); /* Must return allocated string. */ typedef char *(*uiButToolTipFunc)(struct bContext *C, void *argN, const char *tip); @@ -1579,6 +1583,7 @@ void UI_but_func_search_set(uiBut *but, uiButSearchArgFreeFn search_arg_free_fn, uiButHandleFunc handle_fn, void *active); +void UI_but_func_search_set_context_menu(uiBut *but, uiButSearchContextMenuFn context_menu_fn); void UI_but_func_search_set_sep_string(uiBut *but, const char *search_sep_string); /* height in pixels, it's using hardcoded values still */ diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c index 5c38ee1ee08..93caa97db73 100644 --- a/source/blender/editors/interface/interface.c +++ b/source/blender/editors/interface/interface.c @@ -6409,6 +6409,12 @@ void UI_but_func_search_set(uiBut *but, } } +void UI_but_func_search_set_context_menu(uiBut *but, uiButSearchContextMenuFn context_menu_fn) +{ + struct uiButSearchData *search = but->search; + search->context_menu_fn = context_menu_fn; +} + void UI_but_func_search_set_sep_string(uiBut *but, const char *search_sep_string) { struct uiButSearchData *search = but->search; diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 42cb8c566dd..a976c5fa334 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -3473,6 +3473,16 @@ static void ui_do_but_textedit( case RIGHTMOUSE: case EVT_ESCKEY: if (event->val == KM_PRESS) { + /* Support search context menu. */ + if (event->type == RIGHTMOUSE) { + if (data->searchbox) { + if (ui_searchbox_event(C, data->searchbox, but, event)) { + /* Only break if the event was handled. */ + break; + } + } + } + #ifdef WITH_INPUT_IME /* skips button handling since it is not wanted */ if (is_ime_composing) { @@ -9333,6 +9343,11 @@ static int ui_handle_menu_button(bContext *C, const wmEvent *event, uiPopupBlock if (event->val == KM_RELEASE) { /* pass, needed so we can exit active menu-items when click-dragging out of them */ } + else if (but->type == UI_BTYPE_SEARCH_MENU) { + /* Pass, needed so search popup can have RMB context menu. + * This may be useful for other interactions which happen in the search popup + * without being directly over the search button. */ + } else if (!ui_block_is_menu(but->block) || ui_block_is_pie_menu(but->block)) { /* pass, skip for dialogs */ } diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index da7cbc8638b..b69d8fb7245 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -150,6 +150,8 @@ struct uiButSearchData { uiButSearchUpdateFn update_fn; void *arg; uiButSearchArgFreeFn arg_free_fn; + uiButSearchContextMenuFn context_menu_fn; + const char *sep_string; }; @@ -658,7 +660,7 @@ bool ui_searchbox_inside(struct ARegion *region, int x, int y); int ui_searchbox_find_index(struct ARegion *region, const char *name); void ui_searchbox_update(struct bContext *C, struct ARegion *region, uiBut *but, const bool reset); int ui_searchbox_autocomplete(struct bContext *C, struct ARegion *region, uiBut *but, char *str); -void ui_searchbox_event(struct bContext *C, +bool ui_searchbox_event(struct bContext *C, struct ARegion *region, uiBut *but, const struct wmEvent *event); diff --git a/source/blender/editors/interface/interface_region_search.c b/source/blender/editors/interface/interface_region_search.c index e7b90f9654f..cefb584eed4 100644 --- a/source/blender/editors/interface/interface_region_search.c +++ b/source/blender/editors/interface/interface_region_search.c @@ -153,7 +153,8 @@ bool UI_search_item_add(uiSearchItems *items, const char *name, void *poin, int /* Limit flags that can be set so flags such as 'UI_SELECT' aren't accidentally set * which will cause problems, add others as needed. */ - BLI_assert((state & ~(UI_BUT_DISABLED | UI_BUT_INACTIVE | UI_BUT_REDALERT)) == 0); + BLI_assert( + (state & ~(UI_BUT_DISABLED | UI_BUT_INACTIVE | UI_BUT_REDALERT | UI_BUT_HAS_SEP_CHAR)) == 0); if (items->states) { items->states[items->totitem] = state; } @@ -295,10 +296,11 @@ bool ui_searchbox_apply(uiBut *but, ARegion *region) } } -void ui_searchbox_event(bContext *C, ARegion *region, uiBut *but, const wmEvent *event) +bool ui_searchbox_event(bContext *C, ARegion *region, uiBut *but, const wmEvent *event) { uiSearchboxData *data = region->regiondata; int type = event->type, val = event->val; + bool handled = false; if (type == MOUSEPAN) { ui_pan_to_scroll(event, &type, &val); @@ -308,10 +310,32 @@ void ui_searchbox_event(bContext *C, ARegion *region, uiBut *but, const wmEvent case WHEELUPMOUSE: case EVT_UPARROWKEY: ui_searchbox_select(C, region, but, -1); + handled = true; break; case WHEELDOWNMOUSE: case EVT_DOWNARROWKEY: ui_searchbox_select(C, region, but, 1); + handled = true; + break; + case RIGHTMOUSE: + if (val) { + if (but->search->context_menu_fn) { + if (data->active != -1) { + /* Check the cursor is over the active element + * (a little confusing if this isn't the case, although it does work). */ + rcti rect; + ui_searchbox_butrect(&rect, data, data->active); + if (BLI_rcti_isect_pt( + &rect, event->x - region->winrct.xmin, event->y - region->winrct.ymin)) { + + void *active = data->items.pointers[data->active]; + if (but->search->context_menu_fn(C, but->search->arg, active, event)) { + handled = true; + } + } + } + } + } break; case MOUSEMOVE: if (BLI_rcti_isect_pt(®ion->winrct, event->x, event->y)) { @@ -325,6 +349,7 @@ void ui_searchbox_event(bContext *C, ARegion *region, uiBut *but, const wmEvent if (data->active != a) { data->active = a; ui_searchbox_select(C, region, but, 0); + handled = true; break; } } @@ -332,6 +357,7 @@ void ui_searchbox_event(bContext *C, ARegion *region, uiBut *but, const wmEvent } break; } + return handled; } /* region is the search box itself */ diff --git a/source/blender/editors/interface/interface_template_search_menu.c b/source/blender/editors/interface/interface_template_search_menu.c index 1e8d32abf67..8bd61ccd038 100644 --- a/source/blender/editors/interface/interface_template_search_menu.c +++ b/source/blender/editors/interface/interface_template_search_menu.c @@ -137,6 +137,12 @@ struct MenuSearch_Data { 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) @@ -208,7 +214,8 @@ static bool menu_items_from_ui_create_item_from_button(struct MenuSearch_Data *d /* 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->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; @@ -221,6 +228,51 @@ static bool menu_items_from_ui_create_item_from_button(struct MenuSearch_Data *d 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. */ @@ -318,6 +370,7 @@ static void menu_items_from_all_operators(bContext *C, struct MenuSearch_Data *d 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; @@ -895,6 +948,53 @@ static void menu_search_cb(const bContext *UNUSED(C), /** \} */ +/* -------------------------------------------------------------------- */ +/** \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 menu_search_context_menu_fn(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 Menu Search Template Public API * \{ */ @@ -916,6 +1016,8 @@ void UI_but_func_menu_search(uiBut *but) menu_items_from_ui_destroy, menu_call_fn, NULL); + + UI_but_func_search_set_context_menu(but, menu_search_context_menu_fn); UI_but_func_search_set_sep_string(but, MENU_SEP); } diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c index 96b2fcf92e7..ad1f5430872 100644 --- a/source/blender/windowmanager/intern/wm_operators.c +++ b/source/blender/windowmanager/intern/wm_operators.c @@ -1799,7 +1799,7 @@ static int wm_search_menu_invoke(bContext *C, wmOperator *op, const wmEvent *eve .size = {UI_searchbox_size_x() * 2, UI_searchbox_size_y()}, }; - UI_popup_block_invoke(C, wm_block_search_menu, &data, NULL); + UI_popup_block_invoke_ex(C, wm_block_search_menu, &data, NULL, false); return OPERATOR_INTERFACE; }