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