UI: refactor menus to remove menus encoded in strings

On every redraw a single unopened dropdown boxe would translate
and convert every EnumPropertyItem into a string,
then decode every item, and search those items to find the name of the button to draw.

Replace this with a custom menu callback for RNA enums,
tooltips for enums now show too.
This commit is contained in:
Campbell Barton 2014-02-10 12:52:35 +11:00
parent 21b60ea7e1
commit 35f62bdced
Notes: blender-bot 2023-02-14 11:12:14 +01:00
Referenced by issue #38588, Strange button labels in Render View in latest build
4 changed files with 205 additions and 375 deletions

View File

@ -48,11 +48,9 @@
#include "BLI_path_util.h"
#include "BLI_rect.h"
#include "BLI_dynstr.h"
#include "BLI_utildefines.h"
#include "BKE_context.h"
#include "BKE_library.h"
#include "BKE_unit.h"
#include "BKE_screen.h"
#include "BKE_idprop.h"
@ -82,6 +80,8 @@
#define UI_BUT_VALUE_UNSET DBL_MAX
#define UI_GET_BUT_VALUE_INIT(_but, _value) if (_value == DBL_MAX) { (_value) = ui_get_but_val(_but); } (void)0
#define B_NOP -1
/*
* a full doc with API notes can be found in bf-blender/trunk/blender/doc/guides/interface_API.txt
*
@ -670,6 +670,25 @@ static bool ui_but_update_from_old_block(const bContext *C, uiBlock *block, uiBu
ui_but_update_linklines(block, oldbut, but);
/* move/copy string from the new button to the old */
/* needed for alt+mouse wheel over enums */
if (but->str != but->strdata) {
if (oldbut->str != oldbut->strdata) {
SWAP(char *, but->str, oldbut->str);
}
else {
oldbut->str = but->str;
but->str = but->strdata;
}
}
else {
if (oldbut->str != oldbut->strdata) {
MEM_freeN(oldbut->str);
oldbut->str = oldbut->strdata;
}
BLI_strncpy(oldbut->strdata, but->strdata, sizeof(oldbut->strdata));
}
BLI_remlink(&block->buttons, but);
ui_free_but(C, but);
@ -2469,15 +2488,6 @@ void ui_check_but(uiBut *but)
/* name: */
switch (but->type) {
case MENU:
if (BLI_rctf_size_x(&but->rect) > 24.0f) {
UI_GET_BUT_VALUE_INIT(but, value);
ui_set_name_menu(but, (int)value);
}
break;
case NUM:
case NUMSLI:
@ -2966,6 +2976,127 @@ static void ui_def_but_rna__disable(uiBut *but)
but->lockstr = "";
}
static void ui_def_but_rna__menu(bContext *UNUSED(C), uiLayout *layout, void *but_p)
{
uiBlock *block = uiLayoutGetBlock(layout);
uiPopupBlockHandle *handle = block->handle;
uiBut *but = (uiBut *)but_p;
/* see comment in ui_item_enum_expand, re: uiname */
EnumPropertyItem *item, *item_array;
bool free;
uiLayout *split, *column = NULL;
int totitems = 0;
int columns, rows, a, b;
int column_start = 0, column_end = 0;
int nbr_entries_nosepr = 0;
uiBlockSetFlag(block, UI_BLOCK_MOVEMOUSE_QUIT);
RNA_property_enum_items_gettexted(block->evil_C, &but->rnapoin, but->rnaprop, &item_array, NULL, &free);
/* we dont want nested rows, cols in menus */
uiBlockSetCurLayout(block, layout);
for (item = item_array; item->identifier; item++, totitems++) {
if (!item->identifier[0]) {
/* inconsistent, but menus with labels do not look good flipped */
if (item->name) {
block->flag |= UI_BLOCK_NO_FLIP;
nbr_entries_nosepr++;
}
/* We do not want simple separators in nbr_entries_nosepr count */
continue;
}
nbr_entries_nosepr++;
}
/* Columns and row estimation. Ignore simple separators here. */
columns = (nbr_entries_nosepr + 20) / 20;
if (columns < 1)
columns = 1;
if (columns > 8)
columns = (nbr_entries_nosepr + 25) / 25;
rows = totitems / columns;
if (rows < 1)
rows = 1;
while (rows * columns < totitems)
rows++;
/* Title */
uiDefBut(block, LABEL, 0, RNA_property_ui_name(but->rnaprop),
0, 0, UI_UNIT_X * 5, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
uiItemS(layout);
/* note, item_array[...] is reversed on access */
/* create items */
split = uiLayoutSplit(layout, 0.0f, false);
for (a = 0; a < totitems; a++) {
if (a == column_end) {
/* start new column, and find out where it ends in advance, so we
* can flip the order of items properly per column */
column_start = a;
column_end = totitems;
for (b = a + 1; b < totitems; b++) {
item = &item_array[ b];
/* new column on N rows or on separation label */
if (((b - a) % rows == 0) || (!item->identifier[0] && item->name)) {
column_end = b;
break;
}
}
column = uiLayoutColumn(split, false);
}
if (block->flag & UI_BLOCK_NO_FLIP) {
item = &item_array[a];
}
else {
item = &item_array[(column_start + column_end - 1 - a)];
}
if (!item->identifier[0]) {
if (item->name) {
if (item->icon) {
uiItemL(column, item->name, item->icon);
}
else {
/* Do not use uiItemL here, as our root layout is a menu one, it will add a fake blank icon! */
uiDefBut(block, LABEL, 0, item->name, 0, 0, UI_UNIT_X * 5, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
}
}
else {
uiItemS(column);
}
}
else {
if (item->icon) {
uiDefIconTextButF(block, BUTM, B_NOP, item->icon, item->name, 0, 0,
UI_UNIT_X * 5, UI_UNIT_Y, &handle->retvalue, (float) item->value, 0.0, 0, -1, item->description);
}
else {
uiDefButF(block, BUTM, B_NOP, item->name, 0, 0,
UI_UNIT_X * 5, UI_UNIT_X, &handle->retvalue, (float) item->value, 0.0, 0, -1, item->description);
}
}
}
uiBlockSetCurLayout(block, layout);
if (free) {
MEM_freeN(item_array);
}
}
/**
* ui_def_but_rna_propname and ui_def_but_rna
* both take the same args except for propname vs prop, this is done so we can
@ -2982,6 +3113,7 @@ static uiBut *ui_def_but_rna(uiBlock *block, int type, int retval, const char *s
const PropertyType proptype = RNA_property_type(prop);
uiBut *but;
int freestr = 0, icon = 0;
uiMenuCreateFunc func = NULL;
if (ELEM3(type, COLOR, HSVCIRCLE, HSVCUBE)) {
BLI_assert(index == -1);
@ -2991,38 +3123,30 @@ static uiBut *ui_def_but_rna(uiBlock *block, int type, int retval, const char *s
if (!str) {
if (type == MENU && proptype == PROP_ENUM) {
EnumPropertyItem *item;
DynStr *dynstr;
int i, totitem, value;
int totitem, value;
bool free;
int i;
RNA_property_enum_items_gettexted(block->evil_C, ptr, prop, &item, &totitem, &free);
RNA_property_enum_items(block->evil_C, ptr, prop, &item, &totitem, &free);
value = RNA_property_enum_get(ptr, prop);
dynstr = BLI_dynstr_new();
BLI_dynstr_appendf(dynstr, "%s%%t", RNA_property_ui_name(prop));
for (i = 0; i < totitem; i++) {
if (!item[i].identifier[0]) {
if (item[i].name)
BLI_dynstr_appendf(dynstr, "|%s%%l", item[i].name);
else
BLI_dynstr_append(dynstr, "|%l");
}
else if (item[i].icon)
BLI_dynstr_appendf(dynstr, "|%s %%i%d %%x%d", item[i].name, item[i].icon, item[i].value);
else
BLI_dynstr_appendf(dynstr, "|%s %%x%d", item[i].name, item[i].value);
if (value == item[i].value)
icon = item[i].icon;
i = RNA_enum_from_value(item, value);
if (i != -1) {
str = item[i].name;
icon = item[i].icon;
}
else {
str = "";
}
str = BLI_dynstr_get_cstring(dynstr);
BLI_dynstr_free(dynstr);
if (free) {
MEM_freeN(item);
}
freestr = 1;
#ifdef WITH_INTERNATIONAL
str = CTX_IFACE_(RNA_property_translation_context(prop), str);
#endif
func = ui_def_but_rna__menu;
}
else if (ELEM(type, ROW, LISTROW) && proptype == PROP_ENUM) {
EnumPropertyItem *item, *item_array = NULL;
@ -3123,6 +3247,11 @@ static uiBut *ui_def_but_rna(uiBlock *block, int type, int retval, const char *s
but->a1 = ui_get_but_step_unit(but, but->a1);
}
if (func) {
but->menu_create_func = func;
but->poin = (char *)but;
}
if (freestr) {
MEM_freeN((void *)str);
}

View File

@ -2821,7 +2821,6 @@ static void ui_blockopen_begin(bContext *C, uiBut *but, uiHandleButtonData *data
uiBlockCreateFunc func = NULL;
uiBlockHandleCreateFunc handlefunc = NULL;
uiMenuCreateFunc menufunc = NULL;
char *menustr = NULL;
void *arg = NULL;
switch (but->type) {
@ -2837,17 +2836,9 @@ static void ui_blockopen_begin(bContext *C, uiBut *but, uiHandleButtonData *data
}
break;
case MENU:
if (but->menu_create_func) {
menufunc = but->menu_create_func;
arg = but->poin;
}
else {
data->origvalue = ui_get_but_val(but);
data->value = data->origvalue;
but->editval = &data->value;
menustr = but->str;
}
BLI_assert(but->menu_create_func);
menufunc = but->menu_create_func;
arg = but->poin;
break;
case COLOR:
ui_get_but_vectorf(but, data->origvec);
@ -2868,8 +2859,8 @@ static void ui_blockopen_begin(bContext *C, uiBut *but, uiHandleButtonData *data
if (but->block->handle)
data->menu->popup = but->block->handle->popup;
}
else if (menufunc || menustr) {
data->menu = ui_popup_menu_create(C, data->region, but, menufunc, arg, menustr);
else if (menufunc) {
data->menu = ui_popup_menu_create(C, data->region, but, menufunc, arg);
if (but->block->handle)
data->menu->popup = but->block->handle->popup;
}

View File

@ -490,11 +490,10 @@ uiPopupBlockHandle *ui_popup_block_create(struct bContext *C, struct ARegion *bu
uiBlockCreateFunc create_func, uiBlockHandleCreateFunc handle_create_func,
void *arg);
uiPopupBlockHandle *ui_popup_menu_create(struct bContext *C, struct ARegion *butregion, uiBut *but,
uiMenuCreateFunc create_func, void *arg, char *str);
uiMenuCreateFunc create_func, void *arg);
void ui_popup_block_free(struct bContext *C, uiPopupBlockHandle *handle);
void ui_set_name_menu(uiBut *but, int value);
int ui_step_name_menu(uiBut *but, int step);
struct AutoComplete;

View File

@ -72,221 +72,52 @@
#include "interface_intern.h"
#define B_NOP -1
#define MENU_TOP 8
#define MENU_PADDING (int)(0.2f * UI_UNIT_Y)
/*********************** Menu Data Parsing ********************* */
typedef struct MenuEntry {
const char *str;
int retval;
int icon;
int sepr;
} MenuEntry;
typedef struct MenuData {
const char *instr;
const char *title;
int titleicon;
MenuEntry *items;
int nitems, itemssize;
} MenuData;
static MenuData *menudata_new(const char *instr)
static int rna_property_enum_step(const bContext *C, PointerRNA *ptr, PropertyRNA *prop, int direction)
{
MenuData *md = MEM_mallocN(sizeof(*md), "MenuData");
EnumPropertyItem *item_array;
int totitem;
bool free;
int value;
int i, i_init;
int step = (direction < 0) ? -1 : 1;
int step_tot = 0;
md->instr = instr;
md->title = NULL;
md->titleicon = 0;
md->items = NULL;
md->nitems = md->itemssize = 0;
return md;
}
RNA_property_enum_items((bContext *)C, ptr, prop, &item_array, &totitem, &free);
value = RNA_property_enum_get(ptr, prop);
i = RNA_enum_from_value(item_array, value);
i_init = i;
static void menudata_set_title(MenuData *md, const char *title, int titleicon)
{
if (!md->title)
md->title = title;
if (!md->titleicon)
md->titleicon = titleicon;
}
static void menudata_add_item(MenuData *md, const char *str, int retval, int icon, int sepr)
{
if (md->nitems == md->itemssize) {
int nsize = md->itemssize ? (md->itemssize << 1) : 1;
MenuEntry *oitems = md->items;
md->items = MEM_mallocN(nsize * sizeof(*md->items), "md->items");
if (oitems) {
memcpy(md->items, oitems, md->nitems * sizeof(*md->items));
MEM_freeN(oitems);
do {
i = mod_i(i + step, totitem);
if (item_array[i].identifier[0]) {
step_tot += step;
}
md->itemssize = nsize;
} while ((i != i_init) && (step_tot != direction));
if (i != i_init) {
value = item_array[i].value;
}
md->items[md->nitems].str = str;
md->items[md->nitems].retval = retval;
md->items[md->nitems].icon = icon;
md->items[md->nitems].sepr = sepr;
md->nitems++;
}
static void menudata_free(MenuData *md)
{
MEM_freeN((void *)md->instr);
if (md->items) {
MEM_freeN(md->items);
if (free) {
MEM_freeN(item_array);
}
MEM_freeN(md);
}
/**
* Parse menu description strings, string is of the
* form "[sss%t|]{(sss[%xNN]|), (%l|), (sss%l|)}", ssss%t indicates the
* menu title, sss or sss%xNN indicates an option,
* if %xNN is given then NN is the return value if
* that option is selected otherwise the return value
* is the index of the option (starting with 1). %l
* indicates a separator, sss%l indicates a label and
* new column.
*
* \param str String to be parsed.
* \retval new menudata structure, free with menudata_free()
*/
static MenuData *decompose_menu_string(const char *str)
{
char *instr = BLI_strdup(str);
MenuData *md = menudata_new(instr);
const char *nitem = NULL;
char *s = instr;
int nicon = 0, nretval = 1, nitem_is_title = 0, nitem_is_sepr = 0;
while (1) {
char c = *s;
if (c == '%') {
if (s[1] == 'x') {
nretval = atoi(s + 2);
*s = '\0';
s++;
}
else if (s[1] == 't') {
nitem_is_title = (s != instr); /* check for empty title */
*s = '\0';
s++;
}
else if (s[1] == 'l') {
nitem_is_sepr = 1;
if (!nitem) nitem = "";
*s = '\0';
s++;
}
else if (s[1] == 'i') {
nicon = atoi(s + 2);
*s = '\0';
s++;
}
}
else if (c == UI_SEP_CHAR || c == '\n' || c == '\0') {
if (nitem) {
*s = '\0';
if (nitem_is_title) {
menudata_set_title(md, nitem, nicon);
nitem_is_title = 0;
}
else if (nitem_is_sepr) {
/* prevent separator to get a value */
menudata_add_item(md, nitem, -1, nicon, 1);
nretval = md->nitems + 1;
nitem_is_sepr = 0;
}
else {
menudata_add_item(md, nitem, nretval, nicon, 0);
nretval = md->nitems + 1;
}
nitem = NULL;
nicon = 0;
}
if (c == '\0') {
break;
}
}
else if (!nitem) {
nitem = s;
}
s++;
}
return md;
}
void ui_set_name_menu(uiBut *but, int value)
{
MenuData *md;
int i;
md = decompose_menu_string(but->str);
for (i = 0; i < md->nitems; i++) {
if (md->items[i].retval == value) {
BLI_strncpy(but->drawstr, md->items[i].str, sizeof(but->drawstr));
break;
}
}
menudata_free(md);
}
int ui_step_name_menu(uiBut *but, int step)
{
MenuData *md;
int value = ui_get_but_val(but);
int i;
md = decompose_menu_string(but->str);
for (i = 0; i < md->nitems; i++)
if (md->items[i].retval == value)
break;
if (step == 1) {
/* skip separators */
for (; i < md->nitems - 1; i++) {
if (md->items[i + 1].retval != -1) {
value = md->items[i + 1].retval;
break;
}
}
}
else {
if (i > 0) {
/* skip separators */
for (; i > 0; i--) {
if (md->items[i - 1].retval != -1) {
value = md->items[i - 1].retval;
break;
}
}
}
}
menudata_free(md);
return value;
}
int ui_step_name_menu(uiBut *but, int direction)
{
/* currenly only RNA buttons */
if ((but->rnaprop == NULL) || (RNA_property_type(but->rnaprop) != PROP_ENUM)) {
printf("%s: cannot cycle button '%s'", __func__, but->str);
return 0;
}
return rna_property_enum_step(but->block->evil_C, &but->rnapoin, but->rnaprop, direction);
}
/******************** Creating Temporary regions ******************/
@ -1793,119 +1624,6 @@ void ui_popup_block_free(bContext *C, uiPopupBlockHandle *handle)
/***************************** Menu Button ***************************/
static void ui_block_func_MENUSTR(bContext *UNUSED(C), uiLayout *layout, void *arg_str)
{
uiBlock *block = uiLayoutGetBlock(layout);
uiPopupBlockHandle *handle = block->handle;
uiLayout *split, *column = NULL;
MenuData *md;
MenuEntry *entry;
const char *instr = arg_str;
int columns, rows, a, b;
int column_start = 0, column_end = 0;
int nbr_entries_nosepr = 0;
uiBlockSetFlag(block, UI_BLOCK_MOVEMOUSE_QUIT);
/* compute menu data */
md = decompose_menu_string(instr);
/* Run some "tweaking" checks. */
entry = md->items;
for (a = 0; a < md->nitems; a++, entry++) {
if (entry->sepr) {
/* inconsistent, but menus with labels do not look good flipped */
if (entry->str[0]) {
block->flag |= UI_BLOCK_NO_FLIP;
nbr_entries_nosepr++;
}
/* We do not want simple separators in nbr_entries_nosepr count */
continue;
}
nbr_entries_nosepr++;
}
/* Columns and row estimation. Ignore simple separators here. */
columns = (nbr_entries_nosepr + 20) / 20;
if (columns < 1)
columns = 1;
if (columns > 8)
columns = (nbr_entries_nosepr + 25) / 25;
rows = md->nitems / columns;
if (rows < 1)
rows = 1;
while (rows * columns < md->nitems)
rows++;
/* create title */
if (md->title) {
if (md->titleicon) {
uiItemL(layout, md->title, md->titleicon);
}
else {
/* Do not use uiItemL here, as our root layout is a menu one, it will add a fake blank icon! */
uiDefBut(block, LABEL, 0, md->title, 0, 0, UI_UNIT_X * 5, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
}
uiItemS(layout);
}
/* create items */
split = uiLayoutSplit(layout, 0.0f, false);
for (a = 0; a < md->nitems; a++) {
if (a == column_end) {
/* start new column, and find out where it ends in advance, so we
* can flip the order of items properly per column */
column_start = a;
column_end = md->nitems;
for (b = a + 1; b < md->nitems; b++) {
entry = &md->items[b];
/* new column on N rows or on separation label */
if (((b - a) % rows == 0) || (entry->sepr && entry->str[0])) {
column_end = b;
break;
}
}
column = uiLayoutColumn(split, false);
}
if (block->flag & UI_BLOCK_NO_FLIP)
entry = &md->items[a];
else
entry = &md->items[column_start + column_end - 1 - a];
if (entry->sepr) {
if (entry->str[0]) {
if (entry->icon) {
uiItemL(column, entry->str, entry->icon);
}
else {
/* Do not use uiItemL here, as our root layout is a menu one, it will add a fake blank icon! */
uiDefBut(block, LABEL, 0, entry->str, 0, 0, UI_UNIT_X * 5, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
}
}
else {
uiItemS(column);
}
}
else if (entry->icon) {
uiDefIconTextButF(block, BUTM, B_NOP, entry->icon, entry->str, 0, 0,
UI_UNIT_X * 5, UI_UNIT_Y, &handle->retvalue, (float) entry->retval, 0.0, 0, -1, "");
}
else {
uiDefButF(block, BUTM, B_NOP, entry->str, 0, 0,
UI_UNIT_X * 5, UI_UNIT_X, &handle->retvalue, (float) entry->retval, 0.0, 0, -1, "");
}
}
menudata_free(md);
}
#if 0
static void ui_warp_pointer(int x, int y)
{
@ -2478,7 +2196,7 @@ static uiBlock *ui_block_func_POPUP(bContext *C, uiPopupBlockHandle *handle, voi
}
uiPopupBlockHandle *ui_popup_menu_create(bContext *C, ARegion *butregion, uiBut *but,
uiMenuCreateFunc menu_func, void *arg, char *str)
uiMenuCreateFunc menu_func, void *arg)
{
wmWindow *window = CTX_wm_window(C);
uiStyle *style = UI_GetStyleDraw();
@ -2516,16 +2234,9 @@ uiPopupBlockHandle *ui_popup_menu_create(bContext *C, ARegion *butregion, uiBut
uiLayoutContextCopy(pup->layout, but->context);
}
if (str) {
/* menu is created from a string */
pup->menu_func = ui_block_func_MENUSTR;
pup->menu_arg = str;
}
else {
/* menu is created from a callback */
pup->menu_func = menu_func;
pup->menu_arg = arg;
}
/* menu is created from a callback */
pup->menu_func = menu_func;
pup->menu_arg = arg;
handle = ui_popup_block_create(C, butregion, but, NULL, ui_block_func_POPUP, pup);