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) 2008 Blender Foundation.
17  * All rights reserved.
18  */
19 
20 /** \file
21  * \ingroup edinterface
22  *
23  * PopUp Menu Region
24  */
25 
26 #include <stdarg.h>
27 #include <stdlib.h>
28 #include <string.h>
29 
30 #include "MEM_guardedalloc.h"
31 
32 #include "DNA_userdef_types.h"
33 
34 #include "BLI_listbase.h"
35 #include "BLI_math.h"
36 
37 #include "BLI_ghash.h"
38 #include "BLI_rect.h"
39 #include "BLI_string.h"
40 #include "BLI_utildefines.h"
41 
42 #include "BKE_context.h"
43 #include "BKE_report.h"
44 #include "BKE_screen.h"
45 
46 #include "WM_api.h"
47 #include "WM_types.h"
48 
49 #include "RNA_access.h"
50 
51 #include "UI_interface.h"
52 
53 #include "BLT_translation.h"
54 
55 #include "ED_screen.h"
56 
57 #include "interface_intern.h"
58 #include "interface_regions_intern.h"
59 
60 /* -------------------------------------------------------------------- */
61 /** \name Utility Functions
62  * \{ */
63 
ui_but_menu_step_poll(const uiBut * but)64 bool ui_but_menu_step_poll(const uiBut *but)
65 {
66   BLI_assert(but->type == UI_BTYPE_MENU);
67 
68   /* currently only RNA buttons */
69   return ((but->menu_step_func != NULL) ||
70           (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_ENUM));
71 }
72 
ui_but_menu_step(uiBut * but,int direction)73 int ui_but_menu_step(uiBut *but, int direction)
74 {
75   if (ui_but_menu_step_poll(but)) {
76     if (but->menu_step_func) {
77       return but->menu_step_func(but->block->evil_C, direction, but->poin);
78     }
79 
80     const int curval = RNA_property_enum_get(&but->rnapoin, but->rnaprop);
81     return RNA_property_enum_step(
82         but->block->evil_C, &but->rnapoin, but->rnaprop, curval, direction);
83   }
84 
85   printf("%s: cannot cycle button '%s'\n", __func__, but->str);
86   return 0;
87 }
88 
ui_popup_string_hash(const char * str,const bool use_sep)89 static uint ui_popup_string_hash(const char *str, const bool use_sep)
90 {
91   /* sometimes button contains hotkey, sometimes not, strip for proper compare */
92   int hash;
93   const char *delimit = use_sep ? strrchr(str, UI_SEP_CHAR) : NULL;
94 
95   if (delimit) {
96     hash = BLI_ghashutil_strhash_n(str, delimit - str);
97   }
98   else {
99     hash = BLI_ghashutil_strhash(str);
100   }
101 
102   return hash;
103 }
104 
ui_popup_menu_hash(const char * str)105 uint ui_popup_menu_hash(const char *str)
106 {
107   return BLI_ghashutil_strhash(str);
108 }
109 
110 /* but == NULL read, otherwise set */
ui_popup_menu_memory__internal(uiBlock * block,uiBut * but)111 static uiBut *ui_popup_menu_memory__internal(uiBlock *block, uiBut *but)
112 {
113   static uint mem[256];
114   static bool first = true;
115 
116   const uint hash = block->puphash;
117   const uint hash_mod = hash & 255;
118 
119   if (first) {
120     /* init */
121     memset(mem, -1, sizeof(mem));
122     first = 0;
123   }
124 
125   if (but) {
126     /* set */
127     mem[hash_mod] = ui_popup_string_hash(but->str, but->flag & UI_BUT_HAS_SEP_CHAR);
128     return NULL;
129   }
130 
131   /* get */
132   LISTBASE_FOREACH (uiBut *, but_iter, &block->buttons) {
133     if (mem[hash_mod] ==
134         ui_popup_string_hash(but_iter->str, but_iter->flag & UI_BUT_HAS_SEP_CHAR)) {
135       return but_iter;
136     }
137   }
138 
139   return NULL;
140 }
141 
ui_popup_menu_memory_get(uiBlock * block)142 uiBut *ui_popup_menu_memory_get(uiBlock *block)
143 {
144   return ui_popup_menu_memory__internal(block, NULL);
145 }
146 
ui_popup_menu_memory_set(uiBlock * block,uiBut * but)147 void ui_popup_menu_memory_set(uiBlock *block, uiBut *but)
148 {
149   ui_popup_menu_memory__internal(block, but);
150 }
151 
152 /** \} */
153 
154 /* -------------------------------------------------------------------- */
155 /** \name Popup Menu with Callback or String
156  * \{ */
157 
158 struct uiPopupMenu {
159   uiBlock *block;
160   uiLayout *layout;
161   uiBut *but;
162   ARegion *butregion;
163 
164   int mx, my;
165   bool popup, slideout;
166 
167   uiMenuCreateFunc menu_func;
168   void *menu_arg;
169 };
170 
ui_block_func_POPUP(bContext * C,uiPopupBlockHandle * handle,void * arg_pup)171 static uiBlock *ui_block_func_POPUP(bContext *C, uiPopupBlockHandle *handle, void *arg_pup)
172 {
173   uiBlock *block;
174   uiPopupMenu *pup = arg_pup;
175   int minwidth, width, height;
176   char direction;
177   bool flip;
178 
179   if (pup->menu_func) {
180     pup->block->handle = handle;
181     pup->menu_func(C, pup->layout, pup->menu_arg);
182     pup->block->handle = NULL;
183   }
184 
185   /* Find block minimum width. */
186   if (uiLayoutGetUnitsX(pup->layout) != 0.0f) {
187     /* Use the minimum width from the layout if it's set. */
188     minwidth = uiLayoutGetUnitsX(pup->layout) * UI_UNIT_X;
189   }
190   else if (pup->but) {
191     /* minimum width to enforece */
192     if (pup->but->drawstr[0]) {
193       minwidth = BLI_rctf_size_x(&pup->but->rect);
194     }
195     else {
196       /* For buttons with no text, use the minimum (typically icon only). */
197       minwidth = UI_MENU_WIDTH_MIN;
198     }
199   }
200   else {
201     minwidth = UI_MENU_WIDTH_MIN;
202   }
203 
204   /* Find block direction. */
205   if (pup->but) {
206     if (pup->block->direction != 0) {
207       /* allow overriding the direction from menu_func */
208       direction = pup->block->direction;
209     }
210     else {
211       direction = UI_DIR_DOWN;
212     }
213   }
214   else {
215     direction = UI_DIR_DOWN;
216   }
217 
218   flip = (direction == UI_DIR_DOWN);
219 
220   block = pup->block;
221 
222   /* in some cases we create the block before the region,
223    * so we set it delayed here if necessary */
224   if (BLI_findindex(&handle->region->uiblocks, block) == -1) {
225     UI_block_region_set(block, handle->region);
226   }
227 
228   block->direction = direction;
229 
230   UI_block_layout_resolve(block, &width, &height);
231 
232   UI_block_flag_enable(block, UI_BLOCK_MOVEMOUSE_QUIT);
233 
234   if (pup->popup) {
235     int offset[2];
236 
237     uiBut *but_activate = NULL;
238     UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_NUMSELECT);
239     UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
240     UI_block_direction_set(block, direction);
241 
242     /* offset the mouse position, possibly based on earlier selection */
243     uiBut *bt;
244     if ((block->flag & UI_BLOCK_POPUP_MEMORY) && (bt = ui_popup_menu_memory_get(block))) {
245       /* position mouse on last clicked item, at 0.8*width of the
246        * button, so it doesn't overlap the text too much, also note
247        * the offset is negative because we are inverse moving the
248        * block to be under the mouse */
249       offset[0] = -(bt->rect.xmin + 0.8f * BLI_rctf_size_x(&bt->rect));
250       offset[1] = -(bt->rect.ymin + 0.5f * UI_UNIT_Y);
251 
252       if (ui_but_is_editable(bt)) {
253         but_activate = bt;
254       }
255     }
256     else {
257       /* position mouse at 0.8*width of the button and below the tile
258        * on the first item */
259       offset[0] = 0;
260       LISTBASE_FOREACH (uiBut *, but_iter, &block->buttons) {
261         offset[0] = min_ii(offset[0],
262                            -(but_iter->rect.xmin + 0.8f * BLI_rctf_size_x(&but_iter->rect)));
263       }
264 
265       offset[1] = 2.1 * UI_UNIT_Y;
266 
267       LISTBASE_FOREACH (uiBut *, but_iter, &block->buttons) {
268         if (ui_but_is_editable(but_iter)) {
269           but_activate = but_iter;
270           break;
271         }
272       }
273     }
274 
275     /* in rare cases this is needed since moving the popup
276      * to be within the window bounds may move it away from the mouse,
277      * This ensures we set an item to be active. */
278     if (but_activate) {
279       ui_but_activate_over(C, handle->region, but_activate);
280     }
281 
282     block->minbounds = minwidth;
283     UI_block_bounds_set_menu(block, 1, offset);
284   }
285   else {
286     /* for a header menu we set the direction automatic */
287     if (!pup->slideout && flip) {
288       ARegion *region = CTX_wm_region(C);
289       if (region) {
290         if (RGN_TYPE_IS_HEADER_ANY(region->regiontype)) {
291           if (RGN_ALIGN_ENUM_FROM_MASK(region->alignment) == RGN_ALIGN_BOTTOM) {
292             UI_block_direction_set(block, UI_DIR_UP);
293             UI_block_order_flip(block);
294           }
295         }
296       }
297     }
298 
299     block->minbounds = minwidth;
300     UI_block_bounds_set_text(block, 3.0f * UI_UNIT_X);
301   }
302 
303   /* if menu slides out of other menu, override direction */
304   if (pup->slideout) {
305     UI_block_direction_set(block, UI_DIR_RIGHT);
306   }
307 
308   return pup->block;
309 }
310 
ui_popup_menu_create(bContext * C,ARegion * butregion,uiBut * but,uiMenuCreateFunc menu_func,void * arg)311 uiPopupBlockHandle *ui_popup_menu_create(
312     bContext *C, ARegion *butregion, uiBut *but, uiMenuCreateFunc menu_func, void *arg)
313 {
314   wmWindow *window = CTX_wm_window(C);
315   const uiStyle *style = UI_style_get_dpi();
316   uiPopupBlockHandle *handle;
317   uiPopupMenu *pup;
318 
319   pup = MEM_callocN(sizeof(uiPopupMenu), __func__);
320   pup->block = UI_block_begin(C, NULL, __func__, UI_EMBOSS_PULLDOWN);
321   pup->block->flag |= UI_BLOCK_NUMSELECT; /* default menus to numselect */
322   pup->layout = UI_block_layout(
323       pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style);
324   pup->slideout = but ? ui_block_is_menu(but->block) : false;
325   pup->but = but;
326   uiLayoutSetOperatorContext(pup->layout, WM_OP_INVOKE_REGION_WIN);
327 
328   if (!but) {
329     /* no button to start from, means we are a popup */
330     pup->mx = window->eventstate->x;
331     pup->my = window->eventstate->y;
332     pup->popup = true;
333     pup->block->flag |= UI_BLOCK_NO_FLIP;
334   }
335   /* some enums reversing is strange, currently we have no good way to
336    * reverse some enum's but not others, so reverse all so the first menu
337    * items are always close to the mouse cursor */
338   else {
339 #if 0
340     /* if this is an rna button then we can assume its an enum
341      * flipping enums is generally not good since the order can be
342      * important T28786. */
343     if (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_ENUM) {
344       pup->block->flag |= UI_BLOCK_NO_FLIP;
345     }
346 #endif
347     if (but->context) {
348       uiLayoutContextCopy(pup->layout, but->context);
349     }
350   }
351 
352   /* menu is created from a callback */
353   pup->menu_func = menu_func;
354   pup->menu_arg = arg;
355 
356   handle = ui_popup_block_create(C, butregion, but, NULL, ui_block_func_POPUP, pup, NULL);
357 
358   if (!but) {
359     handle->popup = true;
360 
361     UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
362     WM_event_add_mousemove(window);
363   }
364 
365   MEM_freeN(pup);
366 
367   return handle;
368 }
369 
370 /** \} */
371 
372 /* -------------------------------------------------------------------- */
373 /** \name Popup Menu API with begin & end
374  * \{ */
375 
376 /**
377  * Only return handler, and set optional title.
378  * \param block_name: Assigned to uiBlock.name (useful info for debugging).
379  */
UI_popup_menu_begin_ex(bContext * C,const char * title,const char * block_name,int icon)380 uiPopupMenu *UI_popup_menu_begin_ex(bContext *C,
381                                     const char *title,
382                                     const char *block_name,
383                                     int icon)
384 {
385   const uiStyle *style = UI_style_get_dpi();
386   uiPopupMenu *pup = MEM_callocN(sizeof(uiPopupMenu), "popup menu");
387   uiBut *but;
388 
389   pup->block = UI_block_begin(C, NULL, block_name, UI_EMBOSS_PULLDOWN);
390   pup->block->flag |= UI_BLOCK_POPUP_MEMORY | UI_BLOCK_IS_FLIP;
391   pup->block->puphash = ui_popup_menu_hash(title);
392   pup->layout = UI_block_layout(
393       pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style);
394 
395   /* note, this intentionally differs from the menu & submenu default because many operators
396    * use popups like this to select one of their options -
397    * where having invoke doesn't make sense */
398   uiLayoutSetOperatorContext(pup->layout, WM_OP_EXEC_REGION_WIN);
399 
400   /* create in advance so we can let buttons point to retval already */
401   pup->block->handle = MEM_callocN(sizeof(uiPopupBlockHandle), "uiPopupBlockHandle");
402 
403   /* create title button */
404   if (title[0]) {
405     char titlestr[256];
406 
407     if (icon) {
408       BLI_snprintf(titlestr, sizeof(titlestr), " %s", title);
409       uiDefIconTextBut(pup->block,
410                        UI_BTYPE_LABEL,
411                        0,
412                        icon,
413                        titlestr,
414                        0,
415                        0,
416                        200,
417                        UI_UNIT_Y,
418                        NULL,
419                        0.0,
420                        0.0,
421                        0,
422                        0,
423                        "");
424     }
425     else {
426       but = uiDefBut(
427           pup->block, UI_BTYPE_LABEL, 0, title, 0, 0, 200, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
428       but->drawflag = UI_BUT_TEXT_LEFT;
429     }
430 
431     uiItemS(pup->layout);
432   }
433 
434   return pup;
435 }
436 
UI_popup_menu_begin(bContext * C,const char * title,int icon)437 uiPopupMenu *UI_popup_menu_begin(bContext *C, const char *title, int icon)
438 {
439   return UI_popup_menu_begin_ex(C, title, __func__, icon);
440 }
441 
442 /**
443  * Setting the button makes the popup open from the button instead of the cursor.
444  */
UI_popup_menu_but_set(uiPopupMenu * pup,struct ARegion * butregion,uiBut * but)445 void UI_popup_menu_but_set(uiPopupMenu *pup, struct ARegion *butregion, uiBut *but)
446 {
447   pup->but = but;
448   pup->butregion = butregion;
449 }
450 
451 /* set the whole structure to work */
UI_popup_menu_end(bContext * C,uiPopupMenu * pup)452 void UI_popup_menu_end(bContext *C, uiPopupMenu *pup)
453 {
454   wmWindow *window = CTX_wm_window(C);
455   uiPopupBlockHandle *menu;
456   uiBut *but = NULL;
457   ARegion *butregion = NULL;
458 
459   pup->popup = true;
460   pup->mx = window->eventstate->x;
461   pup->my = window->eventstate->y;
462 
463   if (pup->but) {
464     but = pup->but;
465     butregion = pup->butregion;
466   }
467 
468   menu = ui_popup_block_create(C, butregion, but, NULL, ui_block_func_POPUP, pup, NULL);
469   menu->popup = true;
470 
471   UI_popup_handlers_add(C, &window->modalhandlers, menu, 0);
472   WM_event_add_mousemove(window);
473 
474   MEM_freeN(pup);
475 }
476 
UI_popup_menu_end_or_cancel(bContext * C,uiPopupMenu * pup)477 bool UI_popup_menu_end_or_cancel(bContext *C, uiPopupMenu *pup)
478 {
479   if (!UI_block_is_empty_ex(pup->block, true)) {
480     UI_popup_menu_end(C, pup);
481     return true;
482   }
483   UI_block_layout_resolve(pup->block, NULL, NULL);
484   MEM_freeN(pup->block->handle);
485   UI_block_free(C, pup->block);
486   MEM_freeN(pup);
487   return false;
488 }
489 
UI_popup_menu_layout(uiPopupMenu * pup)490 uiLayout *UI_popup_menu_layout(uiPopupMenu *pup)
491 {
492   return pup->layout;
493 }
494 
495 /** \} */
496 
497 /* -------------------------------------------------------------------- */
498 /** \name Standard Popup Menus
499  * \{ */
500 
UI_popup_menu_reports(bContext * C,ReportList * reports)501 void UI_popup_menu_reports(bContext *C, ReportList *reports)
502 {
503   uiPopupMenu *pup = NULL;
504   uiLayout *layout;
505 
506   if (!CTX_wm_window(C)) {
507     return;
508   }
509 
510   LISTBASE_FOREACH (Report *, report, &reports->list) {
511     int icon;
512     const char *msg, *msg_next;
513 
514     if (report->type < reports->printlevel) {
515       continue;
516     }
517 
518     if (pup == NULL) {
519       char title[UI_MAX_DRAW_STR];
520       BLI_snprintf(title, sizeof(title), "%s: %s", IFACE_("Report"), report->typestr);
521       /* popup_menu stuff does just what we need (but pass meaningful block name) */
522       pup = UI_popup_menu_begin_ex(C, title, __func__, ICON_NONE);
523       layout = UI_popup_menu_layout(pup);
524     }
525     else {
526       uiItemS(layout);
527     }
528 
529     /* split each newline into a label */
530     msg = report->message;
531     icon = UI_icon_from_report_type(report->type);
532     do {
533       char buf[UI_MAX_DRAW_STR];
534       msg_next = strchr(msg, '\n');
535       if (msg_next) {
536         msg_next++;
537         BLI_strncpy(buf, msg, MIN2(sizeof(buf), msg_next - msg));
538         msg = buf;
539       }
540       uiItemL(layout, msg, icon);
541       icon = ICON_NONE;
542     } while ((msg = msg_next) && *msg);
543   }
544 
545   if (pup) {
546     UI_popup_menu_end(C, pup);
547   }
548 }
549 
UI_popup_menu_invoke(bContext * C,const char * idname,ReportList * reports)550 int UI_popup_menu_invoke(bContext *C, const char *idname, ReportList *reports)
551 {
552   uiPopupMenu *pup;
553   uiLayout *layout;
554   MenuType *mt = WM_menutype_find(idname, true);
555 
556   if (mt == NULL) {
557     BKE_reportf(reports, RPT_ERROR, "Menu \"%s\" not found", idname);
558     return OPERATOR_CANCELLED;
559   }
560 
561   if (WM_menutype_poll(C, mt) == false) {
562     /* cancel but allow event to pass through, just like operators do */
563     return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH);
564   }
565 
566   pup = UI_popup_menu_begin(C, IFACE_(mt->label), ICON_NONE);
567   layout = UI_popup_menu_layout(pup);
568 
569   UI_menutype_draw(C, mt, layout);
570 
571   UI_popup_menu_end(C, pup);
572 
573   return OPERATOR_INTERFACE;
574 }
575 
576 /** \} */
577 
578 /* -------------------------------------------------------------------- */
579 /** \name Popup Block API
580  * \{ */
581 
UI_popup_block_invoke_ex(bContext * C,uiBlockCreateFunc func,void * arg,void (* arg_free)(void * arg),bool can_refresh)582 void UI_popup_block_invoke_ex(
583     bContext *C, uiBlockCreateFunc func, void *arg, void (*arg_free)(void *arg), bool can_refresh)
584 {
585   wmWindow *window = CTX_wm_window(C);
586   uiPopupBlockHandle *handle;
587 
588   handle = ui_popup_block_create(C, NULL, NULL, func, NULL, arg, arg_free);
589   handle->popup = true;
590 
591   /* It can be useful to disable refresh (even though it will work)
592    * as this exists text fields which can be disruptive if refresh isn't needed. */
593   handle->can_refresh = can_refresh;
594 
595   UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
596   UI_block_active_only_flagged_buttons(C, handle->region, handle->region->uiblocks.first);
597   WM_event_add_mousemove(window);
598 }
599 
UI_popup_block_invoke(bContext * C,uiBlockCreateFunc func,void * arg,void (* arg_free)(void * arg))600 void UI_popup_block_invoke(bContext *C,
601                            uiBlockCreateFunc func,
602                            void *arg,
603                            void (*arg_free)(void *arg))
604 {
605   UI_popup_block_invoke_ex(C, func, arg, arg_free, true);
606 }
607 
UI_popup_block_ex(bContext * C,uiBlockCreateFunc func,uiBlockHandleFunc popup_func,uiBlockCancelFunc cancel_func,void * arg,wmOperator * op)608 void UI_popup_block_ex(bContext *C,
609                        uiBlockCreateFunc func,
610                        uiBlockHandleFunc popup_func,
611                        uiBlockCancelFunc cancel_func,
612                        void *arg,
613                        wmOperator *op)
614 {
615   wmWindow *window = CTX_wm_window(C);
616   uiPopupBlockHandle *handle;
617 
618   handle = ui_popup_block_create(C, NULL, NULL, func, NULL, arg, NULL);
619   handle->popup = true;
620   handle->retvalue = 1;
621   handle->can_refresh = true;
622 
623   handle->popup_op = op;
624   handle->popup_arg = arg;
625   handle->popup_func = popup_func;
626   handle->cancel_func = cancel_func;
627   // handle->opcontext = opcontext;
628 
629   UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
630   UI_block_active_only_flagged_buttons(C, handle->region, handle->region->uiblocks.first);
631   WM_event_add_mousemove(window);
632 }
633 
634 #if 0 /* UNUSED */
635 void uiPupBlockOperator(bContext *C, uiBlockCreateFunc func, wmOperator *op, int opcontext)
636 {
637   wmWindow *window = CTX_wm_window(C);
638   uiPopupBlockHandle *handle;
639 
640   handle = ui_popup_block_create(C, NULL, NULL, func, NULL, op, NULL);
641   handle->popup = 1;
642   handle->retvalue = 1;
643   handle->can_refresh = true;
644 
645   handle->popup_arg = op;
646   handle->popup_func = operator_cb;
647   handle->cancel_func = confirm_cancel_operator;
648   handle->opcontext = opcontext;
649 
650   UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
651   WM_event_add_mousemove(C);
652 }
653 #endif
654 
UI_popup_block_close(bContext * C,wmWindow * win,uiBlock * block)655 void UI_popup_block_close(bContext *C, wmWindow *win, uiBlock *block)
656 {
657   /* if loading new .blend while popup is open, window will be NULL */
658   if (block->handle) {
659     if (win) {
660       const bScreen *screen = WM_window_get_active_screen(win);
661 
662       UI_popup_handlers_remove(&win->modalhandlers, block->handle);
663       ui_popup_block_free(C, block->handle);
664 
665       /* In the case we have nested popups,
666        * closing one may need to redraw another, see: T48874 */
667       LISTBASE_FOREACH (ARegion *, region, &screen->regionbase) {
668         ED_region_tag_refresh_ui(region);
669       }
670     }
671   }
672 }
673 
UI_popup_block_name_exists(const bScreen * screen,const char * name)674 bool UI_popup_block_name_exists(const bScreen *screen, const char *name)
675 {
676   LISTBASE_FOREACH (const ARegion *, region, &screen->regionbase) {
677     LISTBASE_FOREACH (const uiBlock *, block, &region->uiblocks) {
678       if (STREQ(block->name, name)) {
679         return true;
680       }
681     }
682   }
683   return false;
684 }
685 
686 /** \} */
687