1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software Foundation,
14 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15 *
16 * The Original Code is Copyright (C) 2009 Blender Foundation.
17 * All rights reserved.
18 */
19
20 /** \file
21 * \ingroup edinterface
22 */
23
24 #include <ctype.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28
29 #include "DNA_object_types.h"
30 #include "DNA_screen_types.h"
31
32 #include "BLI_alloca.h"
33 #include "BLI_listbase.h"
34 #include "BLI_math.h"
35 #include "BLI_string.h"
36 #include "BLI_utildefines.h"
37
38 #include "BLT_translation.h"
39
40 #include "BKE_lib_id.h"
41 #include "BKE_report.h"
42
43 #include "MEM_guardedalloc.h"
44
45 #include "RNA_access.h"
46
47 #include "UI_interface.h"
48 #include "UI_interface_icons.h"
49 #include "UI_resources.h"
50
51 #include "WM_api.h"
52 #include "WM_types.h"
53
54 #include "interface_intern.h"
55
56 /*************************** RNA Utilities ******************************/
57
uiDefAutoButR(uiBlock * block,PointerRNA * ptr,PropertyRNA * prop,int index,const char * name,int icon,int x1,int y1,int x2,int y2)58 uiBut *uiDefAutoButR(uiBlock *block,
59 PointerRNA *ptr,
60 PropertyRNA *prop,
61 int index,
62 const char *name,
63 int icon,
64 int x1,
65 int y1,
66 int x2,
67 int y2)
68 {
69 uiBut *but = NULL;
70
71 switch (RNA_property_type(prop)) {
72 case PROP_BOOLEAN: {
73 if (RNA_property_array_check(prop) && index == -1) {
74 return NULL;
75 }
76
77 if (icon && name && name[0] == '\0') {
78 but = uiDefIconButR_prop(block,
79 UI_BTYPE_ICON_TOGGLE,
80 0,
81 icon,
82 x1,
83 y1,
84 x2,
85 y2,
86 ptr,
87 prop,
88 index,
89 0,
90 0,
91 -1,
92 -1,
93 NULL);
94 }
95 else if (icon) {
96 but = uiDefIconTextButR_prop(block,
97 UI_BTYPE_ICON_TOGGLE,
98 0,
99 icon,
100 name,
101 x1,
102 y1,
103 x2,
104 y2,
105 ptr,
106 prop,
107 index,
108 0,
109 0,
110 -1,
111 -1,
112 NULL);
113 }
114 else {
115 but = uiDefButR_prop(block,
116 UI_BTYPE_CHECKBOX,
117 0,
118 name,
119 x1,
120 y1,
121 x2,
122 y2,
123 ptr,
124 prop,
125 index,
126 0,
127 0,
128 -1,
129 -1,
130 NULL);
131 }
132 break;
133 }
134 case PROP_INT:
135 case PROP_FLOAT: {
136 if (RNA_property_array_check(prop) && index == -1) {
137 if (ELEM(RNA_property_subtype(prop), PROP_COLOR, PROP_COLOR_GAMMA)) {
138 but = uiDefButR_prop(
139 block, UI_BTYPE_COLOR, 0, name, x1, y1, x2, y2, ptr, prop, -1, 0, 0, 0, 0, NULL);
140 }
141 else {
142 return NULL;
143 }
144 }
145 else if (RNA_property_subtype(prop) == PROP_PERCENTAGE ||
146 RNA_property_subtype(prop) == PROP_FACTOR) {
147 but = uiDefButR_prop(block,
148 UI_BTYPE_NUM_SLIDER,
149 0,
150 name,
151 x1,
152 y1,
153 x2,
154 y2,
155 ptr,
156 prop,
157 index,
158 0,
159 0,
160 -1,
161 -1,
162 NULL);
163 }
164 else {
165 but = uiDefButR_prop(
166 block, UI_BTYPE_NUM, 0, name, x1, y1, x2, y2, ptr, prop, index, 0, 0, 0, 0, NULL);
167 }
168
169 if (RNA_property_flag(prop) & PROP_TEXTEDIT_UPDATE) {
170 UI_but_flag_enable(but, UI_BUT_TEXTEDIT_UPDATE);
171 }
172 break;
173 }
174 case PROP_ENUM:
175 if (icon && name && name[0] == '\0') {
176 but = uiDefIconButR_prop(
177 block, UI_BTYPE_MENU, 0, icon, x1, y1, x2, y2, ptr, prop, index, 0, 0, -1, -1, NULL);
178 }
179 else if (icon) {
180 but = uiDefIconTextButR_prop(block,
181 UI_BTYPE_MENU,
182 0,
183 icon,
184 NULL,
185 x1,
186 y1,
187 x2,
188 y2,
189 ptr,
190 prop,
191 index,
192 0,
193 0,
194 -1,
195 -1,
196 NULL);
197 }
198 else {
199 but = uiDefButR_prop(
200 block, UI_BTYPE_MENU, 0, name, x1, y1, x2, y2, ptr, prop, index, 0, 0, -1, -1, NULL);
201 }
202 break;
203 case PROP_STRING:
204 if (icon && name && name[0] == '\0') {
205 but = uiDefIconButR_prop(
206 block, UI_BTYPE_TEXT, 0, icon, x1, y1, x2, y2, ptr, prop, index, 0, 0, -1, -1, NULL);
207 }
208 else if (icon) {
209 but = uiDefIconTextButR_prop(block,
210 UI_BTYPE_TEXT,
211 0,
212 icon,
213 name,
214 x1,
215 y1,
216 x2,
217 y2,
218 ptr,
219 prop,
220 index,
221 0,
222 0,
223 -1,
224 -1,
225 NULL);
226 }
227 else {
228 but = uiDefButR_prop(
229 block, UI_BTYPE_TEXT, 0, name, x1, y1, x2, y2, ptr, prop, index, 0, 0, -1, -1, NULL);
230 }
231
232 if (RNA_property_flag(prop) & PROP_TEXTEDIT_UPDATE) {
233 /* TEXTEDIT_UPDATE is usually used for search buttons. For these we also want
234 * the 'x' icon to clear search string, so setting VALUE_CLEAR flag, too. */
235 UI_but_flag_enable(but, UI_BUT_TEXTEDIT_UPDATE | UI_BUT_VALUE_CLEAR);
236 }
237 break;
238 case PROP_POINTER: {
239 if (icon == 0) {
240 const PointerRNA pptr = RNA_property_pointer_get(ptr, prop);
241 icon = RNA_struct_ui_icon(pptr.type ? pptr.type : RNA_property_pointer_type(ptr, prop));
242 }
243 if (icon == ICON_DOT) {
244 icon = 0;
245 }
246
247 but = uiDefIconTextButR_prop(block,
248 UI_BTYPE_SEARCH_MENU,
249 0,
250 icon,
251 name,
252 x1,
253 y1,
254 x2,
255 y2,
256 ptr,
257 prop,
258 index,
259 0,
260 0,
261 -1,
262 -1,
263 NULL);
264 break;
265 }
266 case PROP_COLLECTION: {
267 char text[256];
268 BLI_snprintf(
269 text, sizeof(text), IFACE_("%d items"), RNA_property_collection_length(ptr, prop));
270 but = uiDefBut(block, UI_BTYPE_LABEL, 0, text, x1, y1, x2, y2, NULL, 0, 0, 0, 0, NULL);
271 UI_but_flag_enable(but, UI_BUT_DISABLED);
272 break;
273 }
274 default:
275 but = NULL;
276 break;
277 }
278
279 return but;
280 }
281
282 /**
283 * \a check_prop callback filters functions to avoid drawing certain properties,
284 * in cases where PROP_HIDDEN flag can't be used for a property.
285 *
286 * \param prop_activate_init: Property to activate on initial popup (#UI_BUT_ACTIVATE_ON_INIT).
287 */
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)288 eAutoPropButsReturn uiDefAutoButsRNA(uiLayout *layout,
289 PointerRNA *ptr,
290 bool (*check_prop)(PointerRNA *ptr,
291 PropertyRNA *prop,
292 void *user_data),
293 void *user_data,
294 PropertyRNA *prop_activate_init,
295 const eButLabelAlign label_align,
296 const bool compact)
297 {
298 eAutoPropButsReturn return_info = UI_PROP_BUTS_NONE_ADDED;
299 uiLayout *col;
300 const char *name;
301
302 RNA_STRUCT_BEGIN (ptr, prop) {
303 const int flag = RNA_property_flag(prop);
304
305 if (flag & PROP_HIDDEN) {
306 continue;
307 }
308 if (check_prop && check_prop(ptr, prop, user_data) == 0) {
309 return_info |= UI_PROP_BUTS_ANY_FAILED_CHECK;
310 continue;
311 }
312
313 const PropertyType type = RNA_property_type(prop);
314 switch (label_align) {
315 case UI_BUT_LABEL_ALIGN_COLUMN:
316 case UI_BUT_LABEL_ALIGN_SPLIT_COLUMN: {
317 const bool is_boolean = (type == PROP_BOOLEAN && !RNA_property_array_check(prop));
318
319 name = RNA_property_ui_name(prop);
320
321 if (label_align == UI_BUT_LABEL_ALIGN_COLUMN) {
322 col = uiLayoutColumn(layout, true);
323
324 if (!is_boolean) {
325 uiItemL(col, name, ICON_NONE);
326 }
327 }
328 else {
329 BLI_assert(label_align == UI_BUT_LABEL_ALIGN_SPLIT_COLUMN);
330 col = uiLayoutColumn(layout, true);
331 /* Let uiItemFullR() create the split layout. */
332 uiLayoutSetPropSep(col, true);
333 }
334
335 break;
336 }
337 case UI_BUT_LABEL_ALIGN_NONE:
338 default:
339 col = layout;
340 name = NULL; /* no smart label alignment, show default name with button */
341 break;
342 }
343
344 /* Only buttons that can be edited as text. */
345 const bool use_activate_init = ((prop == prop_activate_init) &&
346 (ELEM(type, PROP_STRING, PROP_INT, PROP_FLOAT)));
347
348 if (use_activate_init) {
349 uiLayoutSetActivateInit(col, true);
350 }
351
352 uiItemFullR(col, ptr, prop, -1, 0, compact ? UI_ITEM_R_COMPACT : 0, name, ICON_NONE);
353 return_info &= ~UI_PROP_BUTS_NONE_ADDED;
354
355 if (use_activate_init) {
356 uiLayoutSetActivateInit(col, false);
357 }
358 }
359 RNA_STRUCT_END;
360
361 return return_info;
362 }
363
364 /* *** RNA collection search menu *** */
365
366 typedef struct CollItemSearch {
367 struct CollItemSearch *next, *prev;
368 void *data;
369 char *name;
370 int index;
371 int iconid;
372 bool is_id;
373 int name_prefix_offset;
374 uint has_sep_char : 1;
375 } CollItemSearch;
376
sort_search_items_list(const void * a,const void * b)377 static int sort_search_items_list(const void *a, const void *b)
378 {
379 const CollItemSearch *cis1 = a;
380 const CollItemSearch *cis2 = b;
381
382 if (BLI_strcasecmp(cis1->name, cis2->name) > 0) {
383 return 1;
384 }
385 return 0;
386 }
387
ui_rna_collection_search_update_fn(const struct bContext * C,void * arg,const char * str,uiSearchItems * items)388 void ui_rna_collection_search_update_fn(const struct bContext *C,
389 void *arg,
390 const char *str,
391 uiSearchItems *items)
392 {
393 uiRNACollectionSearch *data = arg;
394 const int flag = RNA_property_flag(data->target_prop);
395 int i = 0;
396 ListBase *items_list = MEM_callocN(sizeof(ListBase), "items_list");
397 CollItemSearch *cis;
398 const bool is_ptr_target = (RNA_property_type(data->target_prop) == PROP_POINTER);
399 /* For non-pointer properties, UI code acts entirely based on the item's name. So the name has to
400 * match the RNA name exactly. So only for pointer properties, the name can be modified to add
401 * further UI hints. */
402 const bool requires_exact_data_name = !is_ptr_target;
403 const bool skip_filter = data->search_but && !data->search_but->changed;
404 char name_buf[UI_MAX_DRAW_STR];
405 char *name;
406 bool has_id_icon = false;
407
408 /* Prepare matching all words. */
409 const size_t str_len = strlen(str);
410 const int words_max = BLI_string_max_possible_word_count(str_len);
411 int(*words)[2] = BLI_array_alloca(words, words_max);
412 const int words_len = BLI_string_find_split_words(str, str_len, ' ', words, words_max);
413
414 /* build a temporary list of relevant items first */
415 RNA_PROP_BEGIN (&data->search_ptr, itemptr, data->search_prop) {
416
417 if (flag & PROP_ID_SELF_CHECK) {
418 if (itemptr.data == data->target_ptr.owner_id) {
419 continue;
420 }
421 }
422
423 /* use filter */
424 if (is_ptr_target) {
425 if (RNA_property_pointer_poll(&data->target_ptr, data->target_prop, &itemptr) == 0) {
426 continue;
427 }
428 }
429
430 int name_prefix_offset = 0;
431 int iconid = ICON_NONE;
432 bool has_sep_char = false;
433 const bool is_id = itemptr.type && RNA_struct_is_ID(itemptr.type);
434
435 if (is_id) {
436 iconid = ui_id_icon_get(C, itemptr.data, false);
437 if (!ELEM(iconid, 0, ICON_BLANK1)) {
438 has_id_icon = true;
439 }
440
441 if (requires_exact_data_name) {
442 name = RNA_struct_name_get_alloc(&itemptr, name_buf, sizeof(name_buf), NULL);
443 }
444 else {
445 const ID *id = itemptr.data;
446 BKE_id_full_name_ui_prefix_get(
447 name_buf, itemptr.data, true, UI_SEP_CHAR, &name_prefix_offset);
448 BLI_STATIC_ASSERT(sizeof(name_buf) >= MAX_ID_FULL_NAME_UI,
449 "Name string buffer should be big enough to hold full UI ID name");
450 name = name_buf;
451 has_sep_char = (id->lib != NULL);
452 }
453 }
454 else {
455 name = RNA_struct_name_get_alloc(&itemptr, name_buf, sizeof(name_buf), NULL);
456 }
457
458 if (name) {
459 if (skip_filter ||
460 BLI_string_all_words_matched(name + name_prefix_offset, str, words, words_len)) {
461 cis = MEM_callocN(sizeof(CollItemSearch), "CollectionItemSearch");
462 cis->data = itemptr.data;
463 cis->name = BLI_strdup(name);
464 cis->index = i;
465 cis->iconid = iconid;
466 cis->is_id = is_id;
467 cis->name_prefix_offset = name_prefix_offset;
468 cis->has_sep_char = has_sep_char;
469 BLI_addtail(items_list, cis);
470 }
471 if (name != name_buf) {
472 MEM_freeN(name);
473 }
474 }
475
476 i++;
477 }
478 RNA_PROP_END;
479
480 BLI_listbase_sort(items_list, sort_search_items_list);
481
482 /* add search items from temporary list */
483 for (cis = items_list->first; cis; cis = cis->next) {
484 /* If no item has an own icon to display, libraries can use the library icons rather than the
485 * name prefix for showing the library status. */
486 int name_prefix_offset = cis->name_prefix_offset;
487 if (!has_id_icon && cis->is_id && !requires_exact_data_name) {
488 cis->iconid = UI_icon_from_library(cis->data);
489 /* No need to re-allocate, string should be shorter than before (lib status prefix is
490 * removed). */
491 BKE_id_full_name_ui_prefix_get(name_buf, cis->data, false, UI_SEP_CHAR, &name_prefix_offset);
492 BLI_assert(strlen(name_buf) <= MEM_allocN_len(cis->name));
493 strcpy(cis->name, name_buf);
494 }
495
496 if (!UI_search_item_add(items,
497 cis->name,
498 cis->data,
499 cis->iconid,
500 cis->has_sep_char ? UI_BUT_HAS_SEP_CHAR : 0,
501 name_prefix_offset)) {
502 break;
503 }
504 }
505
506 for (cis = items_list->first; cis; cis = cis->next) {
507 MEM_freeN(cis->name);
508 }
509 BLI_freelistN(items_list);
510 MEM_freeN(items_list);
511 }
512
513 /***************************** ID Utilities *******************************/
UI_icon_from_id(ID * id)514 int UI_icon_from_id(ID *id)
515 {
516 Object *ob;
517 PointerRNA ptr;
518 short idcode;
519
520 if (id == NULL) {
521 return ICON_NONE;
522 }
523
524 idcode = GS(id->name);
525
526 /* exception for objects */
527 if (idcode == ID_OB) {
528 ob = (Object *)id;
529
530 if (ob->type == OB_EMPTY) {
531 return ICON_EMPTY_DATA;
532 }
533 return UI_icon_from_id(ob->data);
534 }
535
536 /* otherwise get it through RNA, creating the pointer
537 * will set the right type, also with subclassing */
538 RNA_id_pointer_create(id, &ptr);
539
540 return (ptr.type) ? RNA_struct_ui_icon(ptr.type) : ICON_NONE;
541 }
542
543 /* see: report_type_str */
UI_icon_from_report_type(int type)544 int UI_icon_from_report_type(int type)
545 {
546 if (type & RPT_ERROR_ALL) {
547 return ICON_ERROR;
548 }
549 if (type & RPT_WARNING_ALL) {
550 return ICON_ERROR;
551 }
552 if (type & RPT_INFO_ALL) {
553 return ICON_INFO;
554 }
555 return ICON_NONE;
556 }
557
558 /********************************** Misc **************************************/
559
560 /**
561 * Returns the best "UI" precision for given floating value,
562 * so that e.g. 10.000001 rather gets drawn as '10'...
563 */
UI_calc_float_precision(int prec,double value)564 int UI_calc_float_precision(int prec, double value)
565 {
566 static const double pow10_neg[UI_PRECISION_FLOAT_MAX + 1] = {
567 1e0, 1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6};
568 static const double max_pow = 10000000.0; /* pow(10, UI_PRECISION_FLOAT_MAX) */
569
570 BLI_assert(prec <= UI_PRECISION_FLOAT_MAX);
571 BLI_assert(fabs(pow10_neg[prec] - pow(10, -prec)) < 1e-16);
572
573 /* Check on the number of decimal places need to display the number,
574 * this is so 0.00001 is not displayed as 0.00,
575 * _but_, this is only for small values si 10.0001 will not get the same treatment.
576 */
577 value = fabs(value);
578 if ((value < pow10_neg[prec]) && (value > (1.0 / max_pow))) {
579 int value_i = (int)((value * max_pow) + 0.5);
580 if (value_i != 0) {
581 const int prec_span = 3; /* show: 0.01001, 5 would allow 0.0100001 for eg. */
582 int test_prec;
583 int prec_min = -1;
584 int dec_flag = 0;
585 int i = UI_PRECISION_FLOAT_MAX;
586 while (i && value_i) {
587 if (value_i % 10) {
588 dec_flag |= 1 << i;
589 prec_min = i;
590 }
591 value_i /= 10;
592 i--;
593 }
594
595 /* even though its a small value, if the second last digit is not 0, use it */
596 test_prec = prec_min;
597
598 dec_flag = (dec_flag >> (prec_min + 1)) & ((1 << prec_span) - 1);
599
600 while (dec_flag) {
601 test_prec++;
602 dec_flag = dec_flag >> 1;
603 }
604
605 if (test_prec > prec) {
606 prec = test_prec;
607 }
608 }
609 }
610
611 CLAMP(prec, 0, UI_PRECISION_FLOAT_MAX);
612
613 return prec;
614 }
615
UI_but_online_manual_id(const uiBut * but,char * r_str,size_t maxlength)616 bool UI_but_online_manual_id(const uiBut *but, char *r_str, size_t maxlength)
617 {
618 if (but->rnapoin.owner_id && but->rnapoin.data && but->rnaprop) {
619 BLI_snprintf(r_str,
620 maxlength,
621 "%s.%s",
622 RNA_struct_identifier(but->rnapoin.type),
623 RNA_property_identifier(but->rnaprop));
624 return true;
625 }
626 if (but->optype) {
627 WM_operator_py_idname(r_str, but->optype->idname);
628 return true;
629 }
630
631 *r_str = '\0';
632 return false;
633 }
634
UI_but_online_manual_id_from_active(const struct bContext * C,char * r_str,size_t maxlength)635 bool UI_but_online_manual_id_from_active(const struct bContext *C, char *r_str, size_t maxlength)
636 {
637 uiBut *but = UI_context_active_but_get(C);
638
639 if (but) {
640 return UI_but_online_manual_id(but, r_str, maxlength);
641 }
642
643 *r_str = '\0';
644 return false;
645 }
646
647 /* -------------------------------------------------------------------- */
648 /* Modal Button Store API */
649
650 /** \name Button Store
651 *
652 * Store for modal operators & handlers to register button pointers
653 * which are maintained while drawing or NULL when removed.
654 *
655 * This is needed since button pointers are continuously freed and re-allocated.
656 *
657 * \{ */
658
659 struct uiButStore {
660 struct uiButStore *next, *prev;
661 uiBlock *block;
662 ListBase items;
663 };
664
665 struct uiButStoreElem {
666 struct uiButStoreElem *next, *prev;
667 uiBut **but_p;
668 };
669
670 /**
671 * Create a new button store, the caller must manage and run #UI_butstore_free
672 */
UI_butstore_create(uiBlock * block)673 uiButStore *UI_butstore_create(uiBlock *block)
674 {
675 uiButStore *bs_handle = MEM_callocN(sizeof(uiButStore), __func__);
676
677 bs_handle->block = block;
678 BLI_addtail(&block->butstore, bs_handle);
679
680 return bs_handle;
681 }
682
UI_butstore_free(uiBlock * block,uiButStore * bs_handle)683 void UI_butstore_free(uiBlock *block, uiButStore *bs_handle)
684 {
685 /* Workaround for button store being moved into new block,
686 * which then can't use the previous buttons state
687 * ('ui_but_update_from_old_block' fails to find a match),
688 * keeping the active button in the old block holding a reference
689 * to the button-state in the new block: see T49034.
690 *
691 * Ideally we would manage moving the 'uiButStore', keeping a correct state.
692 * All things considered this is the most straightforward fix - Campbell.
693 */
694 if (block != bs_handle->block && bs_handle->block != NULL) {
695 block = bs_handle->block;
696 }
697
698 BLI_freelistN(&bs_handle->items);
699 BLI_assert(BLI_findindex(&block->butstore, bs_handle) != -1);
700 BLI_remlink(&block->butstore, bs_handle);
701
702 MEM_freeN(bs_handle);
703 }
704
UI_butstore_is_valid(uiButStore * bs)705 bool UI_butstore_is_valid(uiButStore *bs)
706 {
707 return (bs->block != NULL);
708 }
709
UI_butstore_is_registered(uiBlock * block,uiBut * but)710 bool UI_butstore_is_registered(uiBlock *block, uiBut *but)
711 {
712 LISTBASE_FOREACH (uiButStore *, bs_handle, &block->butstore) {
713 LISTBASE_FOREACH (uiButStoreElem *, bs_elem, &bs_handle->items) {
714 if (*bs_elem->but_p == but) {
715 return true;
716 }
717 }
718 }
719
720 return false;
721 }
722
UI_butstore_register(uiButStore * bs_handle,uiBut ** but_p)723 void UI_butstore_register(uiButStore *bs_handle, uiBut **but_p)
724 {
725 uiButStoreElem *bs_elem = MEM_callocN(sizeof(uiButStoreElem), __func__);
726 BLI_assert(*but_p);
727 bs_elem->but_p = but_p;
728
729 BLI_addtail(&bs_handle->items, bs_elem);
730 }
731
UI_butstore_unregister(uiButStore * bs_handle,uiBut ** but_p)732 void UI_butstore_unregister(uiButStore *bs_handle, uiBut **but_p)
733 {
734 LISTBASE_FOREACH_MUTABLE (uiButStoreElem *, bs_elem, &bs_handle->items) {
735 if (bs_elem->but_p == but_p) {
736 BLI_remlink(&bs_handle->items, bs_elem);
737 MEM_freeN(bs_elem);
738 }
739 }
740
741 BLI_assert(0);
742 }
743
744 /**
745 * Update the pointer for a registered button.
746 */
UI_butstore_register_update(uiBlock * block,uiBut * but_dst,const uiBut * but_src)747 bool UI_butstore_register_update(uiBlock *block, uiBut *but_dst, const uiBut *but_src)
748 {
749 bool found = false;
750
751 LISTBASE_FOREACH (uiButStore *, bs_handle, &block->butstore) {
752 LISTBASE_FOREACH (uiButStoreElem *, bs_elem, &bs_handle->items) {
753 if (*bs_elem->but_p == but_src) {
754 *bs_elem->but_p = but_dst;
755 found = true;
756 }
757 }
758 }
759
760 return found;
761 }
762
763 /**
764 * NULL all pointers, don't free since the owner needs to be able to inspect.
765 */
UI_butstore_clear(uiBlock * block)766 void UI_butstore_clear(uiBlock *block)
767 {
768 LISTBASE_FOREACH (uiButStore *, bs_handle, &block->butstore) {
769 bs_handle->block = NULL;
770 LISTBASE_FOREACH (uiButStoreElem *, bs_elem, &bs_handle->items) {
771 *bs_elem->but_p = NULL;
772 }
773 }
774 }
775
776 /**
777 * Map freed buttons from the old block and update pointers.
778 */
UI_butstore_update(uiBlock * block)779 void UI_butstore_update(uiBlock *block)
780 {
781 /* move this list to the new block */
782 if (block->oldblock) {
783 if (block->oldblock->butstore.first) {
784 BLI_movelisttolist(&block->butstore, &block->oldblock->butstore);
785 }
786 }
787
788 if (LIKELY(block->butstore.first == NULL)) {
789 return;
790 }
791
792 /* warning, loop-in-loop, in practice we only store <10 buttons at a time,
793 * so this isn't going to be a problem, if that changes old-new mapping can be cached first */
794 LISTBASE_FOREACH (uiButStore *, bs_handle, &block->butstore) {
795 BLI_assert((bs_handle->block == NULL) || (bs_handle->block == block) ||
796 (block->oldblock && block->oldblock == bs_handle->block));
797
798 if (bs_handle->block == block->oldblock) {
799 bs_handle->block = block;
800
801 LISTBASE_FOREACH (uiButStoreElem *, bs_elem, &bs_handle->items) {
802 if (*bs_elem->but_p) {
803 uiBut *but_new = ui_but_find_new(block, *bs_elem->but_p);
804
805 /* can be NULL if the buttons removed,
806 * note: we could allow passing in a callback when buttons are removed
807 * so the caller can cleanup */
808 *bs_elem->but_p = but_new;
809 }
810 }
811 }
812 }
813 }
814
815 /** \} */
816