840 lines
25 KiB
C
840 lines
25 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.
|
|
*
|
|
* The Original Code is Copyright (C) 2009 Blender Foundation.
|
|
* All rights reserved.
|
|
*/
|
|
|
|
/** \file
|
|
* \ingroup edinterface
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "DNA_object_types.h"
|
|
#include "DNA_screen_types.h"
|
|
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_math.h"
|
|
#include "BLI_string.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "BLT_translation.h"
|
|
|
|
#include "BKE_lib_id.h"
|
|
#include "BKE_report.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "RNA_access.h"
|
|
|
|
#include "UI_interface.h"
|
|
#include "UI_interface_icons.h"
|
|
#include "UI_resources.h"
|
|
|
|
#include "WM_api.h"
|
|
#include "WM_types.h"
|
|
|
|
#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;
|
|
}
|
|
return ui_str_has_word_prefix(match + 1, needle, needle_len);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*************************** RNA Utilities ******************************/
|
|
|
|
uiBut *uiDefAutoButR(uiBlock *block,
|
|
PointerRNA *ptr,
|
|
PropertyRNA *prop,
|
|
int index,
|
|
const char *name,
|
|
int icon,
|
|
int x1,
|
|
int y1,
|
|
int x2,
|
|
int y2)
|
|
{
|
|
uiBut *but = NULL;
|
|
|
|
switch (RNA_property_type(prop)) {
|
|
case PROP_BOOLEAN: {
|
|
if (RNA_property_array_check(prop) && index == -1) {
|
|
return NULL;
|
|
}
|
|
|
|
if (icon && name && name[0] == '\0') {
|
|
but = uiDefIconButR_prop(block,
|
|
UI_BTYPE_ICON_TOGGLE,
|
|
0,
|
|
icon,
|
|
x1,
|
|
y1,
|
|
x2,
|
|
y2,
|
|
ptr,
|
|
prop,
|
|
index,
|
|
0,
|
|
0,
|
|
-1,
|
|
-1,
|
|
NULL);
|
|
}
|
|
else if (icon) {
|
|
but = uiDefIconTextButR_prop(block,
|
|
UI_BTYPE_ICON_TOGGLE,
|
|
0,
|
|
icon,
|
|
name,
|
|
x1,
|
|
y1,
|
|
x2,
|
|
y2,
|
|
ptr,
|
|
prop,
|
|
index,
|
|
0,
|
|
0,
|
|
-1,
|
|
-1,
|
|
NULL);
|
|
}
|
|
else {
|
|
but = uiDefButR_prop(block,
|
|
UI_BTYPE_CHECKBOX,
|
|
0,
|
|
name,
|
|
x1,
|
|
y1,
|
|
x2,
|
|
y2,
|
|
ptr,
|
|
prop,
|
|
index,
|
|
0,
|
|
0,
|
|
-1,
|
|
-1,
|
|
NULL);
|
|
}
|
|
break;
|
|
}
|
|
case PROP_INT:
|
|
case PROP_FLOAT: {
|
|
if (RNA_property_array_check(prop) && index == -1) {
|
|
if (ELEM(RNA_property_subtype(prop), PROP_COLOR, PROP_COLOR_GAMMA)) {
|
|
but = uiDefButR_prop(
|
|
block, UI_BTYPE_COLOR, 0, name, x1, y1, x2, y2, ptr, prop, -1, 0, 0, 0, 0, NULL);
|
|
}
|
|
else {
|
|
return NULL;
|
|
}
|
|
}
|
|
else if (RNA_property_subtype(prop) == PROP_PERCENTAGE ||
|
|
RNA_property_subtype(prop) == PROP_FACTOR) {
|
|
but = uiDefButR_prop(block,
|
|
UI_BTYPE_NUM_SLIDER,
|
|
0,
|
|
name,
|
|
x1,
|
|
y1,
|
|
x2,
|
|
y2,
|
|
ptr,
|
|
prop,
|
|
index,
|
|
0,
|
|
0,
|
|
-1,
|
|
-1,
|
|
NULL);
|
|
}
|
|
else {
|
|
but = uiDefButR_prop(
|
|
block, UI_BTYPE_NUM, 0, name, x1, y1, x2, y2, ptr, prop, index, 0, 0, -1, -1, NULL);
|
|
}
|
|
|
|
if (RNA_property_flag(prop) & PROP_TEXTEDIT_UPDATE) {
|
|
UI_but_flag_enable(but, UI_BUT_TEXTEDIT_UPDATE);
|
|
}
|
|
break;
|
|
}
|
|
case PROP_ENUM:
|
|
if (icon && name && name[0] == '\0') {
|
|
but = uiDefIconButR_prop(
|
|
block, UI_BTYPE_MENU, 0, icon, x1, y1, x2, y2, ptr, prop, index, 0, 0, -1, -1, NULL);
|
|
}
|
|
else if (icon) {
|
|
but = uiDefIconTextButR_prop(block,
|
|
UI_BTYPE_MENU,
|
|
0,
|
|
icon,
|
|
NULL,
|
|
x1,
|
|
y1,
|
|
x2,
|
|
y2,
|
|
ptr,
|
|
prop,
|
|
index,
|
|
0,
|
|
0,
|
|
-1,
|
|
-1,
|
|
NULL);
|
|
}
|
|
else {
|
|
but = uiDefButR_prop(
|
|
block, UI_BTYPE_MENU, 0, name, x1, y1, x2, y2, ptr, prop, index, 0, 0, -1, -1, NULL);
|
|
}
|
|
break;
|
|
case PROP_STRING:
|
|
if (icon && name && name[0] == '\0') {
|
|
but = uiDefIconButR_prop(
|
|
block, UI_BTYPE_TEXT, 0, icon, x1, y1, x2, y2, ptr, prop, index, 0, 0, -1, -1, NULL);
|
|
}
|
|
else if (icon) {
|
|
but = uiDefIconTextButR_prop(block,
|
|
UI_BTYPE_TEXT,
|
|
0,
|
|
icon,
|
|
name,
|
|
x1,
|
|
y1,
|
|
x2,
|
|
y2,
|
|
ptr,
|
|
prop,
|
|
index,
|
|
0,
|
|
0,
|
|
-1,
|
|
-1,
|
|
NULL);
|
|
}
|
|
else {
|
|
but = uiDefButR_prop(
|
|
block, UI_BTYPE_TEXT, 0, name, x1, y1, x2, y2, ptr, prop, index, 0, 0, -1, -1, NULL);
|
|
}
|
|
|
|
if (RNA_property_flag(prop) & PROP_TEXTEDIT_UPDATE) {
|
|
/* TEXTEDIT_UPDATE is usually used for search buttons. For these we also want
|
|
* the 'x' icon to clear search string, so setting VALUE_CLEAR flag, too. */
|
|
UI_but_flag_enable(but, UI_BUT_TEXTEDIT_UPDATE | UI_BUT_VALUE_CLEAR);
|
|
}
|
|
break;
|
|
case PROP_POINTER: {
|
|
if (icon == 0) {
|
|
PointerRNA pptr = RNA_property_pointer_get(ptr, prop);
|
|
icon = RNA_struct_ui_icon(pptr.type ? pptr.type : RNA_property_pointer_type(ptr, prop));
|
|
}
|
|
if (icon == ICON_DOT) {
|
|
icon = 0;
|
|
}
|
|
|
|
but = uiDefIconTextButR_prop(block,
|
|
UI_BTYPE_SEARCH_MENU,
|
|
0,
|
|
icon,
|
|
name,
|
|
x1,
|
|
y1,
|
|
x2,
|
|
y2,
|
|
ptr,
|
|
prop,
|
|
index,
|
|
0,
|
|
0,
|
|
-1,
|
|
-1,
|
|
NULL);
|
|
break;
|
|
}
|
|
case PROP_COLLECTION: {
|
|
char text[256];
|
|
BLI_snprintf(
|
|
text, sizeof(text), IFACE_("%d items"), RNA_property_collection_length(ptr, prop));
|
|
but = uiDefBut(block, UI_BTYPE_LABEL, 0, text, x1, y1, x2, y2, NULL, 0, 0, 0, 0, NULL);
|
|
UI_but_flag_enable(but, UI_BUT_DISABLED);
|
|
break;
|
|
}
|
|
default:
|
|
but = NULL;
|
|
break;
|
|
}
|
|
|
|
return but;
|
|
}
|
|
|
|
/**
|
|
* \a check_prop callback filters functions to avoid drawing certain properties,
|
|
* in cases where PROP_HIDDEN flag can't be used for a property.
|
|
*
|
|
* \param prop_activate_init: Property to activate on initial popup (#UI_BUT_ACTIVATE_ON_INIT).
|
|
*/
|
|
eAutoPropButsReturn uiDefAutoButsRNA(uiLayout *layout,
|
|
PointerRNA *ptr,
|
|
bool (*check_prop)(PointerRNA *ptr,
|
|
PropertyRNA *prop,
|
|
void *user_data),
|
|
void *user_data,
|
|
PropertyRNA *prop_activate_init,
|
|
const eButLabelAlign label_align,
|
|
const bool compact)
|
|
{
|
|
eAutoPropButsReturn return_info = UI_PROP_BUTS_NONE_ADDED;
|
|
uiLayout *col;
|
|
const char *name;
|
|
|
|
RNA_STRUCT_BEGIN (ptr, prop) {
|
|
const int flag = RNA_property_flag(prop);
|
|
|
|
if (flag & PROP_HIDDEN) {
|
|
continue;
|
|
}
|
|
if (check_prop && check_prop(ptr, prop, user_data) == 0) {
|
|
return_info |= UI_PROP_BUTS_ANY_FAILED_CHECK;
|
|
continue;
|
|
}
|
|
|
|
const PropertyType type = RNA_property_type(prop);
|
|
switch (label_align) {
|
|
case UI_BUT_LABEL_ALIGN_COLUMN:
|
|
case UI_BUT_LABEL_ALIGN_SPLIT_COLUMN: {
|
|
const bool is_boolean = (type == PROP_BOOLEAN && !RNA_property_array_check(prop));
|
|
|
|
name = RNA_property_ui_name(prop);
|
|
|
|
if (label_align == UI_BUT_LABEL_ALIGN_COLUMN) {
|
|
col = uiLayoutColumn(layout, true);
|
|
|
|
if (!is_boolean) {
|
|
uiItemL(col, name, ICON_NONE);
|
|
}
|
|
}
|
|
else {
|
|
BLI_assert(label_align == UI_BUT_LABEL_ALIGN_SPLIT_COLUMN);
|
|
col = uiLayoutColumn(layout, true);
|
|
/* Let uiItemFullR() create the split layout. */
|
|
uiLayoutSetPropSep(col, true);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case UI_BUT_LABEL_ALIGN_NONE:
|
|
default:
|
|
col = layout;
|
|
name = NULL; /* no smart label alignment, show default name with button */
|
|
break;
|
|
}
|
|
|
|
/* Only buttons that can be edited as text. */
|
|
const bool use_activate_init = ((prop == prop_activate_init) &&
|
|
(ELEM(type, PROP_STRING, PROP_INT, PROP_FLOAT)));
|
|
|
|
if (use_activate_init) {
|
|
uiLayoutSetActivateInit(col, true);
|
|
}
|
|
|
|
uiItemFullR(col, ptr, prop, -1, 0, compact ? UI_ITEM_R_COMPACT : 0, name, ICON_NONE);
|
|
return_info &= ~UI_PROP_BUTS_NONE_ADDED;
|
|
|
|
if (use_activate_init) {
|
|
uiLayoutSetActivateInit(col, false);
|
|
}
|
|
}
|
|
RNA_STRUCT_END;
|
|
|
|
return return_info;
|
|
}
|
|
|
|
/* *** RNA collection search menu *** */
|
|
|
|
typedef struct CollItemSearch {
|
|
struct CollItemSearch *next, *prev;
|
|
void *data;
|
|
char *name;
|
|
int index;
|
|
int iconid;
|
|
bool is_id;
|
|
int name_prefix_offset;
|
|
uint has_sep_char : 1;
|
|
} CollItemSearch;
|
|
|
|
static int sort_search_items_list(const void *a, const void *b)
|
|
{
|
|
const CollItemSearch *cis1 = a;
|
|
const CollItemSearch *cis2 = b;
|
|
|
|
if (BLI_strcasecmp(cis1->name, cis2->name) > 0) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void ui_rna_collection_search_update_fn(const struct bContext *C,
|
|
void *arg,
|
|
const char *str,
|
|
uiSearchItems *items)
|
|
{
|
|
uiRNACollectionSearch *data = arg;
|
|
const int flag = RNA_property_flag(data->target_prop);
|
|
int i = 0;
|
|
ListBase *items_list = MEM_callocN(sizeof(ListBase), "items_list");
|
|
CollItemSearch *cis;
|
|
const bool is_ptr_target = (RNA_property_type(data->target_prop) == PROP_POINTER);
|
|
/* For non-pointer properties, UI code acts entirely based on the item's name. So the name has to
|
|
* match the RNA name exactly. So only for pointer properties, the name can be modified to add
|
|
* further UI hints. */
|
|
const bool requires_exact_data_name = !is_ptr_target;
|
|
const bool skip_filter = data->search_but && !data->search_but->changed;
|
|
char name_buf[UI_MAX_DRAW_STR];
|
|
char *name;
|
|
bool has_id_icon = false;
|
|
|
|
/* build a temporary list of relevant items first */
|
|
RNA_PROP_BEGIN (&data->search_ptr, itemptr, data->search_prop) {
|
|
|
|
if (flag & PROP_ID_SELF_CHECK) {
|
|
if (itemptr.data == data->target_ptr.owner_id) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* use filter */
|
|
if (is_ptr_target) {
|
|
if (RNA_property_pointer_poll(&data->target_ptr, data->target_prop, &itemptr) == 0) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
int name_prefix_offset = 0;
|
|
int iconid = ICON_NONE;
|
|
bool has_sep_char = false;
|
|
bool is_id = itemptr.type && RNA_struct_is_ID(itemptr.type);
|
|
|
|
if (is_id) {
|
|
iconid = ui_id_icon_get(C, itemptr.data, false);
|
|
if (!ELEM(iconid, 0, ICON_BLANK1)) {
|
|
has_id_icon = true;
|
|
}
|
|
|
|
if (requires_exact_data_name) {
|
|
name = RNA_struct_name_get_alloc(&itemptr, name_buf, sizeof(name_buf), NULL);
|
|
}
|
|
else {
|
|
const ID *id = itemptr.data;
|
|
BKE_id_full_name_ui_prefix_get(
|
|
name_buf, itemptr.data, true, UI_SEP_CHAR, &name_prefix_offset);
|
|
BLI_STATIC_ASSERT(sizeof(name_buf) >= MAX_ID_FULL_NAME_UI,
|
|
"Name string buffer should be big enough to hold full UI ID name");
|
|
name = name_buf;
|
|
has_sep_char = (id->lib != NULL);
|
|
}
|
|
}
|
|
else {
|
|
name = RNA_struct_name_get_alloc(&itemptr, name_buf, sizeof(name_buf), NULL);
|
|
}
|
|
|
|
if (name) {
|
|
if (skip_filter || BLI_strcasestr(name + name_prefix_offset, str)) {
|
|
cis = MEM_callocN(sizeof(CollItemSearch), "CollectionItemSearch");
|
|
cis->data = itemptr.data;
|
|
cis->name = BLI_strdup(name);
|
|
cis->index = i;
|
|
cis->iconid = iconid;
|
|
cis->is_id = is_id;
|
|
cis->name_prefix_offset = name_prefix_offset;
|
|
cis->has_sep_char = has_sep_char;
|
|
BLI_addtail(items_list, cis);
|
|
}
|
|
if (name != name_buf) {
|
|
MEM_freeN(name);
|
|
}
|
|
}
|
|
|
|
i++;
|
|
}
|
|
RNA_PROP_END;
|
|
|
|
BLI_listbase_sort(items_list, sort_search_items_list);
|
|
|
|
/* add search items from temporary list */
|
|
for (cis = items_list->first; cis; cis = cis->next) {
|
|
/* If no item has an own icon to display, libraries can use the library icons rather than the
|
|
* name prefix for showing the library status. */
|
|
int name_prefix_offset = cis->name_prefix_offset;
|
|
if (!has_id_icon && cis->is_id) {
|
|
cis->iconid = UI_library_icon_get(cis->data);
|
|
/* No need to re-allocate, string should be shorter than before (lib status prefix is
|
|
* removed). */
|
|
BKE_id_full_name_ui_prefix_get(name_buf, cis->data, false, UI_SEP_CHAR, &name_prefix_offset);
|
|
BLI_assert(strlen(name_buf) <= MEM_allocN_len(cis->name));
|
|
strcpy(cis->name, name_buf);
|
|
}
|
|
|
|
if (!UI_search_item_add(items,
|
|
cis->name,
|
|
cis->data,
|
|
cis->iconid,
|
|
cis->has_sep_char ? UI_BUT_HAS_SEP_CHAR : 0,
|
|
name_prefix_offset)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (cis = items_list->first; cis; cis = cis->next) {
|
|
MEM_freeN(cis->name);
|
|
}
|
|
BLI_freelistN(items_list);
|
|
MEM_freeN(items_list);
|
|
}
|
|
|
|
/***************************** ID Utilities *******************************/
|
|
int UI_icon_from_id(ID *id)
|
|
{
|
|
Object *ob;
|
|
PointerRNA ptr;
|
|
short idcode;
|
|
|
|
if (id == NULL) {
|
|
return ICON_NONE;
|
|
}
|
|
|
|
idcode = GS(id->name);
|
|
|
|
/* exception for objects */
|
|
if (idcode == ID_OB) {
|
|
ob = (Object *)id;
|
|
|
|
if (ob->type == OB_EMPTY) {
|
|
return ICON_EMPTY_DATA;
|
|
}
|
|
return UI_icon_from_id(ob->data);
|
|
}
|
|
|
|
/* otherwise get it through RNA, creating the pointer
|
|
* will set the right type, also with subclassing */
|
|
RNA_id_pointer_create(id, &ptr);
|
|
|
|
return (ptr.type) ? RNA_struct_ui_icon(ptr.type) : ICON_NONE;
|
|
}
|
|
|
|
/* see: report_type_str */
|
|
int UI_icon_from_report_type(int type)
|
|
{
|
|
if (type & RPT_ERROR_ALL) {
|
|
return ICON_ERROR;
|
|
}
|
|
if (type & RPT_WARNING_ALL) {
|
|
return ICON_ERROR;
|
|
}
|
|
if (type & RPT_INFO_ALL) {
|
|
return ICON_INFO;
|
|
}
|
|
return ICON_NONE;
|
|
}
|
|
|
|
/********************************** Misc **************************************/
|
|
|
|
/**
|
|
* Returns the best "UI" precision for given floating value,
|
|
* so that e.g. 10.000001 rather gets drawn as '10'...
|
|
*/
|
|
int UI_calc_float_precision(int prec, double value)
|
|
{
|
|
static const double pow10_neg[UI_PRECISION_FLOAT_MAX + 1] = {
|
|
1e0, 1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6};
|
|
static const double max_pow = 10000000.0; /* pow(10, UI_PRECISION_FLOAT_MAX) */
|
|
|
|
BLI_assert(prec <= UI_PRECISION_FLOAT_MAX);
|
|
BLI_assert(fabs(pow10_neg[prec] - pow(10, -prec)) < 1e-16);
|
|
|
|
/* Check on the number of decimal places need to display the number,
|
|
* this is so 0.00001 is not displayed as 0.00,
|
|
* _but_, this is only for small values si 10.0001 will not get the same treatment.
|
|
*/
|
|
value = fabs(value);
|
|
if ((value < pow10_neg[prec]) && (value > (1.0 / max_pow))) {
|
|
int value_i = (int)((value * max_pow) + 0.5);
|
|
if (value_i != 0) {
|
|
const int prec_span = 3; /* show: 0.01001, 5 would allow 0.0100001 for eg. */
|
|
int test_prec;
|
|
int prec_min = -1;
|
|
int dec_flag = 0;
|
|
int i = UI_PRECISION_FLOAT_MAX;
|
|
while (i && value_i) {
|
|
if (value_i % 10) {
|
|
dec_flag |= 1 << i;
|
|
prec_min = i;
|
|
}
|
|
value_i /= 10;
|
|
i--;
|
|
}
|
|
|
|
/* even though its a small value, if the second last digit is not 0, use it */
|
|
test_prec = prec_min;
|
|
|
|
dec_flag = (dec_flag >> (prec_min + 1)) & ((1 << prec_span) - 1);
|
|
|
|
while (dec_flag) {
|
|
test_prec++;
|
|
dec_flag = dec_flag >> 1;
|
|
}
|
|
|
|
if (test_prec > prec) {
|
|
prec = test_prec;
|
|
}
|
|
}
|
|
}
|
|
|
|
CLAMP(prec, 0, UI_PRECISION_FLOAT_MAX);
|
|
|
|
return prec;
|
|
}
|
|
|
|
bool UI_but_online_manual_id(const uiBut *but, char *r_str, size_t maxlength)
|
|
{
|
|
if (but->rnapoin.owner_id && but->rnapoin.data && but->rnaprop) {
|
|
BLI_snprintf(r_str,
|
|
maxlength,
|
|
"%s.%s",
|
|
RNA_struct_identifier(but->rnapoin.type),
|
|
RNA_property_identifier(but->rnaprop));
|
|
return true;
|
|
}
|
|
if (but->optype) {
|
|
WM_operator_py_idname(r_str, but->optype->idname);
|
|
return true;
|
|
}
|
|
|
|
*r_str = '\0';
|
|
return false;
|
|
}
|
|
|
|
bool UI_but_online_manual_id_from_active(const struct bContext *C, char *r_str, size_t maxlength)
|
|
{
|
|
uiBut *but = UI_context_active_but_get(C);
|
|
|
|
if (but) {
|
|
return UI_but_online_manual_id(but, r_str, maxlength);
|
|
}
|
|
|
|
*r_str = '\0';
|
|
return false;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Modal Button Store API */
|
|
|
|
/** \name Button Store
|
|
*
|
|
* Store for modal operators & handlers to register button pointers
|
|
* which are maintained while drawing or NULL when removed.
|
|
*
|
|
* This is needed since button pointers are continuously freed and re-allocated.
|
|
*
|
|
* \{ */
|
|
|
|
struct uiButStore {
|
|
struct uiButStore *next, *prev;
|
|
uiBlock *block;
|
|
ListBase items;
|
|
};
|
|
|
|
struct uiButStoreElem {
|
|
struct uiButStoreElem *next, *prev;
|
|
uiBut **but_p;
|
|
};
|
|
|
|
/**
|
|
* Create a new button store, the caller must manage and run #UI_butstore_free
|
|
*/
|
|
uiButStore *UI_butstore_create(uiBlock *block)
|
|
{
|
|
uiButStore *bs_handle = MEM_callocN(sizeof(uiButStore), __func__);
|
|
|
|
bs_handle->block = block;
|
|
BLI_addtail(&block->butstore, bs_handle);
|
|
|
|
return bs_handle;
|
|
}
|
|
|
|
void UI_butstore_free(uiBlock *block, uiButStore *bs_handle)
|
|
{
|
|
/* Workaround for button store being moved into new block,
|
|
* which then can't use the previous buttons state
|
|
* ('ui_but_update_from_old_block' fails to find a match),
|
|
* keeping the active button in the old block holding a reference
|
|
* to the button-state in the new block: see T49034.
|
|
*
|
|
* Ideally we would manage moving the 'uiButStore', keeping a correct state.
|
|
* All things considered this is the most straightforward fix - Campbell.
|
|
*/
|
|
if (block != bs_handle->block && bs_handle->block != NULL) {
|
|
block = bs_handle->block;
|
|
}
|
|
|
|
BLI_freelistN(&bs_handle->items);
|
|
BLI_assert(BLI_findindex(&block->butstore, bs_handle) != -1);
|
|
BLI_remlink(&block->butstore, bs_handle);
|
|
|
|
MEM_freeN(bs_handle);
|
|
}
|
|
|
|
bool UI_butstore_is_valid(uiButStore *bs)
|
|
{
|
|
return (bs->block != NULL);
|
|
}
|
|
|
|
bool UI_butstore_is_registered(uiBlock *block, uiBut *but)
|
|
{
|
|
uiButStore *bs_handle;
|
|
|
|
for (bs_handle = block->butstore.first; bs_handle; bs_handle = bs_handle->next) {
|
|
uiButStoreElem *bs_elem;
|
|
|
|
for (bs_elem = bs_handle->items.first; bs_elem; bs_elem = bs_elem->next) {
|
|
if (*bs_elem->but_p == but) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void UI_butstore_register(uiButStore *bs_handle, uiBut **but_p)
|
|
{
|
|
uiButStoreElem *bs_elem = MEM_callocN(sizeof(uiButStoreElem), __func__);
|
|
BLI_assert(*but_p);
|
|
bs_elem->but_p = but_p;
|
|
|
|
BLI_addtail(&bs_handle->items, bs_elem);
|
|
}
|
|
|
|
void UI_butstore_unregister(uiButStore *bs_handle, uiBut **but_p)
|
|
{
|
|
uiButStoreElem *bs_elem, *bs_elem_next;
|
|
|
|
for (bs_elem = bs_handle->items.first; bs_elem; bs_elem = bs_elem_next) {
|
|
bs_elem_next = bs_elem->next;
|
|
if (bs_elem->but_p == but_p) {
|
|
BLI_remlink(&bs_handle->items, bs_elem);
|
|
MEM_freeN(bs_elem);
|
|
}
|
|
}
|
|
|
|
BLI_assert(0);
|
|
}
|
|
|
|
/**
|
|
* Update the pointer for a registered button.
|
|
*/
|
|
bool UI_butstore_register_update(uiBlock *block, uiBut *but_dst, const uiBut *but_src)
|
|
{
|
|
uiButStore *bs_handle;
|
|
bool found = false;
|
|
|
|
for (bs_handle = block->butstore.first; bs_handle; bs_handle = bs_handle->next) {
|
|
uiButStoreElem *bs_elem;
|
|
for (bs_elem = bs_handle->items.first; bs_elem; bs_elem = bs_elem->next) {
|
|
if (*bs_elem->but_p == but_src) {
|
|
*bs_elem->but_p = but_dst;
|
|
found = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
/**
|
|
* NULL all pointers, don't free since the owner needs to be able to inspect.
|
|
*/
|
|
void UI_butstore_clear(uiBlock *block)
|
|
{
|
|
uiButStore *bs_handle;
|
|
|
|
for (bs_handle = block->butstore.first; bs_handle; bs_handle = bs_handle->next) {
|
|
uiButStoreElem *bs_elem;
|
|
|
|
bs_handle->block = NULL;
|
|
|
|
for (bs_elem = bs_handle->items.first; bs_elem; bs_elem = bs_elem->next) {
|
|
*bs_elem->but_p = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Map freed buttons from the old block and update pointers.
|
|
*/
|
|
void UI_butstore_update(uiBlock *block)
|
|
{
|
|
uiButStore *bs_handle;
|
|
|
|
/* move this list to the new block */
|
|
if (block->oldblock) {
|
|
if (block->oldblock->butstore.first) {
|
|
BLI_movelisttolist(&block->butstore, &block->oldblock->butstore);
|
|
}
|
|
}
|
|
|
|
if (LIKELY(block->butstore.first == NULL)) {
|
|
return;
|
|
}
|
|
|
|
/* warning, loop-in-loop, in practice we only store <10 buttons at a time,
|
|
* so this isn't going to be a problem, if that changes old-new mapping can be cached first */
|
|
for (bs_handle = block->butstore.first; bs_handle; bs_handle = bs_handle->next) {
|
|
|
|
BLI_assert((bs_handle->block == NULL) || (bs_handle->block == block) ||
|
|
(block->oldblock && block->oldblock == bs_handle->block));
|
|
|
|
if (bs_handle->block == block->oldblock) {
|
|
uiButStoreElem *bs_elem;
|
|
|
|
bs_handle->block = block;
|
|
|
|
for (bs_elem = bs_handle->items.first; bs_elem; bs_elem = bs_elem->next) {
|
|
if (*bs_elem->but_p) {
|
|
uiBut *but_new = ui_but_find_new(block, *bs_elem->but_p);
|
|
|
|
/* can be NULL if the buttons removed,
|
|
* note: we could allow passing in a callback when buttons are removed
|
|
* so the caller can cleanup */
|
|
*bs_elem->but_p = but_new;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** \} */
|