4a051b9a4e8e5b75673456d5153e6843e0a93f5a
[blender.git] / source / blender / editors / interface / interface_utils.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2009 Blender Foundation.
19  * All rights reserved.
20  *
21  * Contributor(s): Blender Foundation
22  *
23  * ***** END GPL LICENSE BLOCK *****
24  */
25
26 /** \file blender/editors/interface/interface_utils.c
27  *  \ingroup edinterface
28  */
29
30
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <assert.h>
35
36 #include "DNA_object_types.h"
37 #include "DNA_screen_types.h"
38
39 #include "BLI_utildefines.h"
40 #include "BLI_math.h"
41 #include "BLI_string.h"
42 #include "BLI_listbase.h"
43
44 #include "BLT_translation.h"
45
46 #include "BKE_library.h"
47 #include "BKE_report.h"
48
49 #include "MEM_guardedalloc.h"
50
51 #include "RNA_access.h"
52
53 #include "UI_interface.h"
54 #include "UI_resources.h"
55
56 #include "WM_api.h"
57 #include "WM_types.h"
58
59 #include "interface_intern.h"
60
61
62 /*************************** RNA Utilities ******************************/
63
64 uiBut *uiDefAutoButR(uiBlock *block, PointerRNA *ptr, PropertyRNA *prop, int index, const char *name, int icon, int x1, int y1, int x2, int y2)
65 {
66         uiBut *but = NULL;
67
68         switch (RNA_property_type(prop)) {
69                 case PROP_BOOLEAN:
70                 {
71                         int arraylen = RNA_property_array_length(ptr, prop);
72
73                         if (arraylen && index == -1)
74                                 return NULL;
75
76                         if (icon && name && name[0] == '\0')
77                                 but = uiDefIconButR_prop(block, UI_BTYPE_ICON_TOGGLE, 0, icon, x1, y1, x2, y2, ptr, prop, index, 0, 0, -1, -1, NULL);
78                         else if (icon)
79                                 but = uiDefIconTextButR_prop(block, UI_BTYPE_ICON_TOGGLE, 0, icon, name, x1, y1, x2, y2, ptr, prop, index, 0, 0, -1, -1, NULL);
80                         else
81                                 but = uiDefButR_prop(block, UI_BTYPE_CHECKBOX, 0, name, x1, y1, x2, y2, ptr, prop, index, 0, 0, -1, -1, NULL);
82                         break;
83                 }
84                 case PROP_INT:
85                 case PROP_FLOAT:
86                 {
87                         int arraylen = RNA_property_array_length(ptr, prop);
88
89                         if (arraylen && index == -1) {
90                                 if (ELEM(RNA_property_subtype(prop), PROP_COLOR, PROP_COLOR_GAMMA)) {
91                                         but = uiDefButR_prop(block, UI_BTYPE_COLOR, 0, name, x1, y1, x2, y2, ptr, prop, -1, 0, 0, -1, -1, NULL);
92                                 }
93                                 else {
94                                         return NULL;
95                                 }
96                         }
97                         else if (RNA_property_subtype(prop) == PROP_PERCENTAGE || RNA_property_subtype(prop) == PROP_FACTOR)
98                                 but = uiDefButR_prop(block, UI_BTYPE_NUM_SLIDER, 0, name, x1, y1, x2, y2, ptr, prop, index, 0, 0, -1, -1, NULL);
99                         else
100                                 but = uiDefButR_prop(block, UI_BTYPE_NUM, 0, name, x1, y1, x2, y2, ptr, prop, index, 0, 0, -1, -1, NULL);
101
102                         if (RNA_property_flag(prop) & PROP_TEXTEDIT_UPDATE) {
103                                 UI_but_flag_enable(but, UI_BUT_TEXTEDIT_UPDATE);
104                         }
105                         break;
106                 }
107                 case PROP_ENUM:
108                         if (icon && name && name[0] == '\0')
109                                 but = uiDefIconButR_prop(block, UI_BTYPE_MENU, 0, icon, x1, y1, x2, y2, ptr, prop, index, 0, 0, -1, -1, NULL);
110                         else if (icon)
111                                 but = uiDefIconTextButR_prop(block, UI_BTYPE_MENU, 0, icon, NULL, x1, y1, x2, y2, ptr, prop, index, 0, 0, -1, -1, NULL);
112                         else
113                                 but = uiDefButR_prop(block, UI_BTYPE_MENU, 0, name, x1, y1, x2, y2, ptr, prop, index, 0, 0, -1, -1, NULL);
114                         break;
115                 case PROP_STRING:
116                         if (icon && name && name[0] == '\0')
117                                 but = uiDefIconButR_prop(block, UI_BTYPE_TEXT, 0, icon, x1, y1, x2, y2, ptr, prop, index, 0, 0, -1, -1, NULL);
118                         else if (icon)
119                                 but = uiDefIconTextButR_prop(block, UI_BTYPE_TEXT, 0, icon, name, x1, y1, x2, y2, ptr, prop, index, 0, 0, -1, -1, NULL);
120                         else
121                                 but = uiDefButR_prop(block, UI_BTYPE_TEXT, 0, name, x1, y1, x2, y2, ptr, prop, index, 0, 0, -1, -1, NULL);
122
123                         if (RNA_property_flag(prop) & PROP_TEXTEDIT_UPDATE) {
124                                 /* TEXTEDIT_UPDATE is usually used for search buttons. For these we also want
125                                  * the 'x' icon to clear search string, so setting VALUE_CLEAR flag, too. */
126                                 UI_but_flag_enable(but, UI_BUT_TEXTEDIT_UPDATE | UI_BUT_VALUE_CLEAR);
127                         }
128                         break;
129                 case PROP_POINTER:
130                 {
131                         if (icon == 0) {
132                                 PointerRNA pptr = RNA_property_pointer_get(ptr, prop);
133                                 icon = RNA_struct_ui_icon(pptr.type ? pptr.type : RNA_property_pointer_type(ptr, prop));
134                         }
135                         if (icon == ICON_DOT)
136                                 icon = 0;
137
138                         but = uiDefIconTextButR_prop(block, UI_BTYPE_SEARCH_MENU, 0, icon, name, x1, y1, x2, y2, ptr, prop, index, 0, 0, -1, -1, NULL);
139                         break;
140                 }
141                 case PROP_COLLECTION:
142                 {
143                         char text[256];
144                         BLI_snprintf(text, sizeof(text), IFACE_("%d items"), RNA_property_collection_length(ptr, prop));
145                         but = uiDefBut(block, UI_BTYPE_LABEL, 0, text, x1, y1, x2, y2, NULL, 0, 0, 0, 0, NULL);
146                         UI_but_flag_enable(but, UI_BUT_DISABLED);
147                         break;
148                 }
149                 default:
150                         but = NULL;
151                         break;
152         }
153
154         return but;
155 }
156
157 /**
158  * \a check_prop callback filters functions to avoid drawing certain properties,
159  * in cases where PROP_HIDDEN flag can't be used for a property.
160  */
161 eAutoPropButsReturn uiDefAutoButsRNA(
162         uiLayout *layout, PointerRNA *ptr,
163         bool (*check_prop)(PointerRNA *ptr, PropertyRNA *prop, void *user_data), void *user_data,
164         const eButLabelAlign label_align, const bool compact)
165 {
166         eAutoPropButsReturn return_info = UI_PROP_BUTS_NONE_ADDED;
167         uiLayout *split, *col;
168         int flag;
169         const char *name;
170
171         RNA_STRUCT_BEGIN (ptr, prop)
172         {
173                 flag = RNA_property_flag(prop);
174
175                 if (flag & PROP_HIDDEN) {
176                         continue;
177                 }
178                 if (check_prop && check_prop(ptr, prop, user_data) == 0) {
179                         return_info |= UI_PROP_BUTS_ANY_FAILED_CHECK;
180                         continue;
181                 }
182
183                 switch (label_align) {
184                         case UI_BUT_LABEL_ALIGN_COLUMN:
185                         case UI_BUT_LABEL_ALIGN_SPLIT_COLUMN:
186                         {
187                                 PropertyType type = RNA_property_type(prop);
188                                 const bool is_boolean = (type == PROP_BOOLEAN && !RNA_property_array_check(prop));
189
190                                 name = RNA_property_ui_name(prop);
191
192                                 if (label_align == UI_BUT_LABEL_ALIGN_COLUMN) {
193                                         col = uiLayoutColumn(layout, true);
194
195                                         if (!is_boolean)
196                                                 uiItemL(col, name, ICON_NONE);
197                                 }
198                                 else {
199                                         BLI_assert(label_align == UI_BUT_LABEL_ALIGN_SPLIT_COLUMN);
200                                         split = uiLayoutSplit(layout, 0.5f, false);
201
202                                         col = uiLayoutColumn(split, false);
203                                         uiItemL(col, (is_boolean) ? "" : name, ICON_NONE);
204                                         col = uiLayoutColumn(split, false);
205                                 }
206
207                                 /* may meed to add more cases here.
208                                  * don't override enum flag names */
209
210                                 /* name is shown above, empty name for button below */
211                                 name = (flag & PROP_ENUM_FLAG || is_boolean) ? NULL : "";
212
213                                 break;
214                         }
215                         case UI_BUT_LABEL_ALIGN_NONE:
216                         default:
217                                 col = layout;
218                                 name = NULL; /* no smart label alignment, show default name with button */
219                                 break;
220                 }
221
222                 uiItemFullR(col, ptr, prop, -1, 0, compact ? UI_ITEM_R_COMPACT : 0, name, ICON_NONE);
223                 return_info &= ~UI_PROP_BUTS_NONE_ADDED;
224         }
225         RNA_STRUCT_END;
226
227         return return_info;
228 }
229
230 /* *** RNA collection search menu *** */
231
232 typedef struct CollItemSearch {
233         struct CollItemSearch *next, *prev;
234         void *data;
235         char *name;
236         int index;
237         int iconid;
238 } CollItemSearch;
239
240 static int sort_search_items_list(const void *a, const void *b)
241 {
242         const CollItemSearch *cis1 = a;
243         const CollItemSearch *cis2 = b;
244
245         if (BLI_strcasecmp(cis1->name, cis2->name) > 0)
246                 return 1;
247         else
248                 return 0;
249 }
250
251 void ui_rna_collection_search_cb(const struct bContext *C, void *arg, const char *str, uiSearchItems *items)
252 {
253         uiRNACollectionSearch *data = arg;
254         char *name;
255         int i = 0, iconid = 0, flag = RNA_property_flag(data->target_prop);
256         ListBase *items_list = MEM_callocN(sizeof(ListBase), "items_list");
257         CollItemSearch *cis;
258         const bool skip_filter = !(data->but_changed && *data->but_changed);
259
260         /* build a temporary list of relevant items first */
261         RNA_PROP_BEGIN (&data->search_ptr, itemptr, data->search_prop)
262         {
263
264                 if (flag & PROP_ID_SELF_CHECK)
265                         if (itemptr.data == data->target_ptr.id.data)
266                                 continue;
267
268                 /* use filter */
269                 if (RNA_property_type(data->target_prop) == PROP_POINTER) {
270                         if (RNA_property_pointer_poll(&data->target_ptr, data->target_prop, &itemptr) == 0)
271                                 continue;
272                 }
273
274                 iconid = 0;
275                 if (itemptr.type && RNA_struct_is_ID(itemptr.type)) {
276                         name = MEM_malloc_arrayN(MAX_ID_NAME + 1, sizeof(*name), __func__);
277                         BKE_id_ui_prefix(name, itemptr.data);
278                         iconid = ui_id_icon_get(C, itemptr.data, false);
279                 }
280                 else {
281                         name = RNA_struct_name_get_alloc(&itemptr, NULL, 0, NULL); /* could use the string length here */
282                 }
283
284                 if (name) {
285                         if (skip_filter || BLI_strcasestr(name, str)) {
286                                 cis = MEM_callocN(sizeof(CollItemSearch), "CollectionItemSearch");
287                                 cis->data = itemptr.data;
288                                 cis->name = name;  /* Still ownership of that memory. */
289                                 cis->index = i;
290                                 cis->iconid = iconid;
291                                 BLI_addtail(items_list, cis);
292                         }
293                         else {
294                                 MEM_freeN(name);
295                         }
296                 }
297
298                 i++;
299         }
300         RNA_PROP_END;
301
302         BLI_listbase_sort(items_list, sort_search_items_list);
303
304         /* add search items from temporary list */
305         for (cis = items_list->first; cis; cis = cis->next) {
306                 if (UI_search_item_add(items, cis->name, cis->data, cis->iconid) == false) {
307                         break;
308                 }
309         }
310
311         for (cis = items_list->first; cis; cis = cis->next) {
312                 MEM_freeN(cis->name);
313         }
314         BLI_freelistN(items_list);
315         MEM_freeN(items_list);
316 }
317
318
319 /***************************** ID Utilities *******************************/
320 int UI_icon_from_id(ID *id)
321 {
322         Object *ob;
323         PointerRNA ptr;
324         short idcode;
325
326         if (id == NULL)
327                 return ICON_NONE;
328
329         idcode = GS(id->name);
330
331         /* exception for objects */
332         if (idcode == ID_OB) {
333                 ob = (Object *)id;
334
335                 if (ob->type == OB_EMPTY)
336                         return ICON_EMPTY_DATA;
337                 else
338                         return UI_icon_from_id(ob->data);
339         }
340
341         /* otherwise get it through RNA, creating the pointer
342          * will set the right type, also with subclassing */
343         RNA_id_pointer_create(id, &ptr);
344
345         return (ptr.type) ? RNA_struct_ui_icon(ptr.type) : ICON_NONE;
346 }
347
348 /* see: report_type_str */
349 int UI_icon_from_report_type(int type)
350 {
351         if (type & RPT_ERROR_ALL)
352                 return ICON_ERROR;
353         else if (type & RPT_WARNING_ALL)
354                 return ICON_ERROR;
355         else if (type & RPT_INFO_ALL)
356                 return ICON_INFO;
357         else
358                 return ICON_NONE;
359 }
360
361 /********************************** Misc **************************************/
362
363 /**
364  * Returns the best "UI" precision for given floating value, so that e.g. 10.000001 rather gets drawn as '10'...
365  */
366 int UI_calc_float_precision(int prec, double value)
367 {
368         static const double pow10_neg[UI_PRECISION_FLOAT_MAX + 1] = {1e0, 1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6};
369         static const double max_pow = 10000000.0;  /* pow(10, UI_PRECISION_FLOAT_MAX) */
370
371         BLI_assert(prec <= UI_PRECISION_FLOAT_MAX);
372         BLI_assert(fabs(pow10_neg[prec] - pow(10, -prec)) < 1e-16);
373
374         /* check on the number of decimal places need to display the number, this is so 0.00001 is not displayed as 0.00,
375          * _but_, this is only for small values si 10.0001 will not get the same treatment.
376          */
377         value = ABS(value);
378         if ((value < pow10_neg[prec]) && (value > (1.0 / max_pow))) {
379                 int value_i = (int)((value * max_pow) + 0.5);
380                 if (value_i != 0) {
381                         const int prec_span = 3; /* show: 0.01001, 5 would allow 0.0100001 for eg. */
382                         int test_prec;
383                         int prec_min = -1;
384                         int dec_flag = 0;
385                         int i = UI_PRECISION_FLOAT_MAX;
386                         while (i && value_i) {
387                                 if (value_i % 10) {
388                                         dec_flag |= 1 << i;
389                                         prec_min = i;
390                                 }
391                                 value_i /= 10;
392                                 i--;
393                         }
394
395                         /* even though its a small value, if the second last digit is not 0, use it */
396                         test_prec = prec_min;
397
398                         dec_flag = (dec_flag >> (prec_min + 1)) & ((1 << prec_span) - 1);
399
400                         while (dec_flag) {
401                                 test_prec++;
402                                 dec_flag = dec_flag >> 1;
403                         }
404
405                         if (test_prec > prec) {
406                                 prec = test_prec;
407                         }
408                 }
409         }
410
411         CLAMP(prec, 0, UI_PRECISION_FLOAT_MAX);
412
413         return prec;
414 }
415
416 bool UI_but_online_manual_id(const uiBut *but, char *r_str, size_t maxlength)
417 {
418         if (but->rnapoin.id.data && but->rnapoin.data && but->rnaprop) {
419                 BLI_snprintf(
420                         r_str, maxlength, "%s.%s", RNA_struct_identifier(but->rnapoin.type),
421                         RNA_property_identifier(but->rnaprop));
422                 return true;
423         }
424         else if (but->optype) {
425                 WM_operator_py_idname(r_str, but->optype->idname);
426                 return true;
427         }
428
429         *r_str = '\0';
430         return false;
431 }
432
433 bool UI_but_online_manual_id_from_active(const struct bContext *C, char *r_str, size_t maxlength)
434 {
435         uiBut *but = UI_context_active_but_get(C);
436
437         if (but) {
438                 return UI_but_online_manual_id(but, r_str, maxlength);
439         }
440
441         *r_str = '\0';
442         return false;
443 }
444
445
446 /* -------------------------------------------------------------------- */
447 /* Modal Button Store API */
448
449 /** \name Button Store
450  *
451  * Store for modal operators & handlers to register button pointers
452  * which are maintained while drawing or NULL when removed.
453  *
454  * This is needed since button pointers are continuously freed and re-allocated.
455  *
456  * \{ */
457
458 struct uiButStore {
459         struct uiButStore *next, *prev;
460         uiBlock *block;
461         ListBase items;
462 };
463
464 struct uiButStoreElem {
465         struct uiButStoreElem *next, *prev;
466         uiBut **but_p;
467 };
468
469 /**
470  * Create a new button store, the caller must manage and run #UI_butstore_free
471  */
472 uiButStore *UI_butstore_create(uiBlock *block)
473 {
474         uiButStore *bs_handle = MEM_callocN(sizeof(uiButStore), __func__);
475
476         bs_handle->block = block;
477         BLI_addtail(&block->butstore, bs_handle);
478
479         return bs_handle;
480 }
481
482 void UI_butstore_free(uiBlock *block, uiButStore *bs_handle)
483 {
484         /* Workaround for button store being moved into new block,
485          * which then can't use the previous buttons state ('ui_but_update_from_old_block' fails to find a match),
486          * keeping the active button in the old block holding a reference to the button-state in the new block: see T49034.
487          *
488          * Ideally we would manage moving the 'uiButStore', keeping a correct state.
489          * All things considered this is the most straightforward fix - Campbell.
490          */
491         if (block != bs_handle->block && bs_handle->block != NULL) {
492                 block = bs_handle->block;
493         }
494
495         BLI_freelistN(&bs_handle->items);
496         BLI_remlink(&block->butstore, bs_handle);
497
498         MEM_freeN(bs_handle);
499 }
500
501 bool UI_butstore_is_valid(uiButStore *bs)
502 {
503         return (bs->block != NULL);
504 }
505
506 bool UI_butstore_is_registered(uiBlock *block, uiBut *but)
507 {
508         uiButStore *bs_handle;
509
510         for (bs_handle = block->butstore.first; bs_handle; bs_handle = bs_handle->next) {
511                 uiButStoreElem *bs_elem;
512
513                 for (bs_elem = bs_handle->items.first; bs_elem; bs_elem = bs_elem->next) {
514                         if (*bs_elem->but_p == but) {
515                                 return true;
516                         }
517                 }
518         }
519
520         return false;
521 }
522
523 void UI_butstore_register(uiButStore *bs_handle, uiBut **but_p)
524 {
525         uiButStoreElem *bs_elem = MEM_callocN(sizeof(uiButStoreElem), __func__);
526         BLI_assert(*but_p);
527         bs_elem->but_p = but_p;
528
529         BLI_addtail(&bs_handle->items, bs_elem);
530
531 }
532
533 void UI_butstore_unregister(uiButStore *bs_handle, uiBut **but_p)
534 {
535         uiButStoreElem *bs_elem, *bs_elem_next;
536
537         for (bs_elem = bs_handle->items.first; bs_elem; bs_elem = bs_elem_next) {
538                 bs_elem_next = bs_elem->next;
539                 if (bs_elem->but_p == but_p) {
540                         BLI_remlink(&bs_handle->items, bs_elem);
541                         MEM_freeN(bs_elem);
542                 }
543         }
544
545         BLI_assert(0);
546 }
547
548 /**
549  * Update the pointer for a registered button.
550  */
551 bool UI_butstore_register_update(uiBlock *block, uiBut *but_dst, const uiBut *but_src)
552 {
553         uiButStore *bs_handle;
554         bool found = false;
555
556         for (bs_handle = block->butstore.first; bs_handle; bs_handle = bs_handle->next) {
557                 uiButStoreElem *bs_elem;
558                 for (bs_elem = bs_handle->items.first; bs_elem; bs_elem = bs_elem->next) {
559                         if (*bs_elem->but_p == but_src) {
560                                 *bs_elem->but_p = but_dst;
561                                 found = true;
562                         }
563                 }
564         }
565
566         return found;
567 }
568
569 /**
570  * NULL all pointers, don't free since the owner needs to be able to inspect.
571  */
572 void UI_butstore_clear(uiBlock *block)
573 {
574         uiButStore *bs_handle;
575
576         for (bs_handle = block->butstore.first; bs_handle; bs_handle = bs_handle->next) {
577                 uiButStoreElem *bs_elem;
578
579                 bs_handle->block = NULL;
580
581                 for (bs_elem = bs_handle->items.first; bs_elem; bs_elem = bs_elem->next) {
582                         *bs_elem->but_p = NULL;
583                 }
584         }
585 }
586
587 /**
588  * Map freed buttons from the old block and update pointers.
589  */
590 void UI_butstore_update(uiBlock *block)
591 {
592         uiButStore *bs_handle;
593
594         /* move this list to the new block */
595         if (block->oldblock) {
596                 if (block->oldblock->butstore.first) {
597                         block->butstore = block->oldblock->butstore;
598                         BLI_listbase_clear(&block->oldblock->butstore);
599                 }
600         }
601
602         if (LIKELY(block->butstore.first == NULL))
603                 return;
604
605         /* warning, loop-in-loop, in practice we only store <10 buttons at a time,
606          * so this isn't going to be a problem, if that changes old-new mapping can be cached first */
607         for (bs_handle = block->butstore.first; bs_handle; bs_handle = bs_handle->next) {
608
609                 BLI_assert((bs_handle->block == NULL) ||
610                            (bs_handle->block == block) ||
611                            (block->oldblock && block->oldblock == bs_handle->block));
612
613                 if (bs_handle->block == block->oldblock) {
614                         uiButStoreElem *bs_elem;
615
616                         bs_handle->block = block;
617
618                         for (bs_elem = bs_handle->items.first; bs_elem; bs_elem = bs_elem->next) {
619                                 if (*bs_elem->but_p) {
620                                         uiBut *but_new = ui_but_find_new(block, *bs_elem->but_p);
621
622                                         /* can be NULL if the buttons removed,
623                                          * note: we could allow passing in a callback when buttons are removed
624                                          * so the caller can cleanup */
625                                         *bs_elem->but_p = but_new;
626                                 }
627                         }
628                 }
629         }
630 }
631
632 /** \} */