Blender  V3.3
interface_region_search.cc
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later
2  * Copyright 2008 Blender Foundation. All rights reserved. */
3 
10 #include <cstdarg>
11 #include <cstdlib>
12 #include <cstring>
13 
14 #include "DNA_ID.h"
15 #include "MEM_guardedalloc.h"
16 
17 #include "DNA_userdef_types.h"
18 
19 #include "BLI_math.h"
20 
21 #include "BLI_listbase.h"
22 #include "BLI_rect.h"
23 #include "BLI_string.h"
24 #include "BLI_utildefines.h"
25 
26 #include "BKE_context.h"
27 #include "BKE_screen.h"
28 
29 #include "WM_api.h"
30 #include "WM_types.h"
31 
32 #include "RNA_access.h"
33 
34 #include "UI_interface.h"
35 #include "UI_interface_icons.h"
36 #include "UI_view2d.h"
37 
38 #include "BLT_translation.h"
39 
40 #include "ED_screen.h"
41 
42 #include "GPU_state.h"
43 #include "interface_intern.h"
45 
46 #define MENU_BORDER (int)(0.3f * U.widget_unit)
47 
48 /* -------------------------------------------------------------------- */
52 struct uiSearchItems {
54 
55  int offset, offset_i; /* offset for inserting in array */
56  int more; /* flag indicating there are more items */
57 
58  char **names;
59  void **pointers;
60  int *icons;
61  int *but_flags;
63 
65  bool has_icon;
66 
68  void *active;
69 };
70 
76  int active;
78  bool noback;
80  bool preview;
88  const char *sep_string;
89 };
90 
91 #define SEARCH_ITEMS 10
92 
94  const char *name,
95  void *poin,
96  int iconid,
97  const int but_flag,
98  const uint8_t name_prefix_offset)
99 {
100  /* hijack for autocomplete */
101  if (items->autocpl) {
102  UI_autocomplete_update_name(items->autocpl, name + name_prefix_offset);
103  return true;
104  }
105 
106  if (iconid) {
107  items->has_icon = true;
108  }
109 
110  /* hijack for finding active item */
111  if (items->active) {
112  if (poin == items->active) {
113  items->offset_i = items->totitem;
114  }
115  items->totitem++;
116  return true;
117  }
118 
119  if (items->totitem >= items->maxitem) {
120  items->more = 1;
121  return false;
122  }
123 
124  /* skip first items in list */
125  if (items->offset_i > 0) {
126  items->offset_i--;
127  return true;
128  }
129 
130  if (items->names) {
131  BLI_strncpy(items->names[items->totitem], name, items->maxstrlen);
132  }
133  if (items->pointers) {
134  items->pointers[items->totitem] = poin;
135  }
136  if (items->icons) {
137  items->icons[items->totitem] = iconid;
138  }
139 
140  if (name_prefix_offset != 0) {
141  /* Lazy initialize, as this isn't used often. */
142  if (items->name_prefix_offsets == nullptr) {
144  items->maxitem * sizeof(*items->name_prefix_offsets), __func__);
145  }
146  items->name_prefix_offsets[items->totitem] = name_prefix_offset;
147  }
148 
149  /* Limit flags that can be set so flags such as 'UI_SELECT' aren't accidentally set
150  * which will cause problems, add others as needed. */
151  BLI_assert((but_flag &
153  if (items->but_flags) {
154  items->but_flags[items->totitem] = but_flag;
155  }
156 
157  items->totitem++;
158 
159  return true;
160 }
161 
163 {
165 }
166 
168 {
169  return 12 * UI_UNIT_X;
170 }
171 
172 int UI_search_items_find_index(uiSearchItems *items, const char *name)
173 {
174  if (items->name_prefix_offsets != nullptr) {
175  for (int i = 0; i < items->totitem; i++) {
176  if (STREQ(name, items->names[i] + items->name_prefix_offsets[i])) {
177  return i;
178  }
179  }
180  }
181  else {
182  for (int i = 0; i < items->totitem; i++) {
183  if (STREQ(name, items->names[i])) {
184  return i;
185  }
186  }
187  }
188  return -1;
189 }
190 
191 /* region is the search box itself */
192 static void ui_searchbox_select(bContext *C, ARegion *region, uiBut *but, int step)
193 {
194  uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
195 
196  /* apply step */
197  data->active += step;
198 
199  if (data->items.totitem == 0) {
200  data->active = -1;
201  }
202  else if (data->active >= data->items.totitem) {
203  if (data->items.more) {
204  data->items.offset++;
205  data->active = data->items.totitem - 1;
206  ui_searchbox_update(C, region, but, false);
207  }
208  else {
209  data->active = data->items.totitem - 1;
210  }
211  }
212  else if (data->active < 0) {
213  if (data->items.offset) {
214  data->items.offset--;
215  data->active = 0;
216  ui_searchbox_update(C, region, but, false);
217  }
218  else {
219  /* only let users step into an 'unset' state for unlink buttons */
220  data->active = (but->flag & UI_BUT_VALUE_CLEAR) ? -1 : 0;
221  }
222  }
223 
224  ED_region_tag_redraw(region);
225 }
226 
227 static void ui_searchbox_butrect(rcti *r_rect, uiSearchboxData *data, int itemnr)
228 {
229  /* thumbnail preview */
230  if (data->preview) {
231  const int butw = (BLI_rcti_size_x(&data->bbox) - 2 * MENU_BORDER) / data->prv_cols;
232  const int buth = (BLI_rcti_size_y(&data->bbox) - 2 * MENU_BORDER) / data->prv_rows;
233  int row, col;
234 
235  *r_rect = data->bbox;
236 
237  col = itemnr % data->prv_cols;
238  row = itemnr / data->prv_cols;
239 
240  r_rect->xmin += MENU_BORDER + (col * butw);
241  r_rect->xmax = r_rect->xmin + butw;
242 
243  r_rect->ymax -= MENU_BORDER + (row * buth);
244  r_rect->ymin = r_rect->ymax - buth;
245  }
246  /* list view */
247  else {
248  const int buth = (BLI_rcti_size_y(&data->bbox) - 2 * UI_POPUP_MENU_TOP) / SEARCH_ITEMS;
249 
250  *r_rect = data->bbox;
251  r_rect->xmin = data->bbox.xmin + 3.0f;
252  r_rect->xmax = data->bbox.xmax - 3.0f;
253 
254  r_rect->ymax = data->bbox.ymax - UI_POPUP_MENU_TOP - itemnr * buth;
255  r_rect->ymin = r_rect->ymax - buth;
256  }
257 }
258 
259 int ui_searchbox_find_index(ARegion *region, const char *name)
260 {
261  uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
262  return UI_search_items_find_index(&data->items, name);
263 }
264 
265 bool ui_searchbox_inside(ARegion *region, const int xy[2])
266 {
267  uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
268 
269  return BLI_rcti_isect_pt(&data->bbox, xy[0] - region->winrct.xmin, xy[1] - region->winrct.ymin);
270 }
271 
272 bool ui_searchbox_apply(uiBut *but, ARegion *region)
273 {
274  uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
275  uiButSearch *search_but = (uiButSearch *)but;
276 
278 
279  search_but->item_active = nullptr;
280 
281  if (data->active != -1) {
282  const char *name = data->items.names[data->active] +
283  /* Never include the prefix in the button. */
284  (data->items.name_prefix_offsets ?
285  data->items.name_prefix_offsets[data->active] :
286  0);
287 
288  const char *name_sep = data->use_shortcut_sep ? strrchr(name, UI_SEP_CHAR) : nullptr;
289 
290  /* Search button with dynamic string properties may have their own method of applying
291  * the search results, so only copy the result if there is a proper space for it. */
292  if (but->hardmax != 0) {
293  BLI_strncpy(but->editstr, name, name_sep ? (name_sep - name) + 1 : data->items.maxstrlen);
294  }
295 
296  search_but->item_active = data->items.pointers[data->active];
297 
298  return true;
299  }
300  return false;
301 }
302 
304  struct ARegion *region,
305  int *UNUSED(r_pass),
306  double *UNUSED(pass_delay),
307  bool *r_exit_on_event)
308 {
309  *r_exit_on_event = true;
310 
311  LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
312  LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
313  if (but->type != UI_BTYPE_SEARCH_MENU) {
314  continue;
315  }
316 
317  uiButSearch *search_but = (uiButSearch *)but;
318  if (!search_but->item_tooltip_fn) {
319  continue;
320  }
321 
322  ARegion *searchbox_region = UI_region_searchbox_region_get(region);
323  uiSearchboxData *data = static_cast<uiSearchboxData *>(searchbox_region->regiondata);
324 
325  BLI_assert(data->items.pointers[data->active] == search_but->item_active);
326 
327  rcti rect;
328  ui_searchbox_butrect(&rect, data, data->active);
329 
330  return search_but->item_tooltip_fn(
331  C, region, &rect, search_but->arg, search_but->item_active);
332  }
333  }
334  return nullptr;
335 }
336 
338  bContext *C, ARegion *region, uiBut *but, ARegion *butregion, const wmEvent *event)
339 {
340  uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
341  uiButSearch *search_but = (uiButSearch *)but;
342  int type = event->type, val = event->val;
343  bool handled = false;
344  bool tooltip_timer_started = false;
345 
347 
348  if (type == MOUSEPAN) {
349  ui_pan_to_scroll(event, &type, &val);
350  }
351 
352  switch (type) {
353  case WHEELUPMOUSE:
354  case EVT_UPARROWKEY:
355  ui_searchbox_select(C, region, but, -1);
356  handled = true;
357  break;
358  case WHEELDOWNMOUSE:
359  case EVT_DOWNARROWKEY:
360  ui_searchbox_select(C, region, but, 1);
361  handled = true;
362  break;
363  case RIGHTMOUSE:
364  if (val) {
365  if (search_but->item_context_menu_fn) {
366  if (data->active != -1) {
367  /* Check the cursor is over the active element
368  * (a little confusing if this isn't the case, although it does work). */
369  rcti rect;
370  ui_searchbox_butrect(&rect, data, data->active);
371  if (BLI_rcti_isect_pt(&rect,
372  event->xy[0] - region->winrct.xmin,
373  event->xy[1] - region->winrct.ymin)) {
374 
375  void *active = data->items.pointers[data->active];
376  if (search_but->item_context_menu_fn(C, search_but->arg, active, event)) {
377  handled = true;
378  }
379  }
380  }
381  }
382  }
383  break;
384  case MOUSEMOVE: {
385  bool is_inside = false;
386 
387  if (BLI_rcti_isect_pt(&region->winrct, event->xy[0], event->xy[1])) {
388  rcti rect;
389  int a;
390 
391  for (a = 0; a < data->items.totitem; a++) {
392  ui_searchbox_butrect(&rect, data, a);
393  if (BLI_rcti_isect_pt(
394  &rect, event->xy[0] - region->winrct.xmin, event->xy[1] - region->winrct.ymin)) {
395  is_inside = true;
396  if (data->active != a) {
397  data->active = a;
398  ui_searchbox_select(C, region, but, 0);
399  handled = true;
400  break;
401  }
402  }
403  }
404  }
405 
406  if (U.flag & USER_TOOLTIPS) {
407  if (is_inside) {
408  if (data->active != -1) {
410  search_but->item_active = data->items.pointers[data->active];
412  tooltip_timer_started = true;
413  }
414  }
415  }
416 
417  break;
418  }
419  }
420 
421  if (handled && (tooltip_timer_started == false)) {
422  wmWindow *win = CTX_wm_window(C);
423  WM_tooltip_clear(C, win);
424  }
425 
426  return handled;
427 }
428 
431  uiButSearch *search_but,
432  const char *str,
433  uiSearchItems *items)
434 {
435  /* While the button is in text editing mode (searchbox open), remove tooltips on every update. */
436  if (search_but->but.editstr) {
437  wmWindow *win = CTX_wm_window(C);
438  WM_tooltip_clear(C, win);
439  }
440  const bool is_first_search = !search_but->but.changed;
441  search_but->items_update_fn(C, search_but->arg, str, items, is_first_search);
442 }
443 
444 void ui_searchbox_update(bContext *C, ARegion *region, uiBut *but, const bool reset)
445 {
446  uiButSearch *search_but = (uiButSearch *)but;
447  uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
448 
450 
451  /* reset vars */
452  data->items.totitem = 0;
453  data->items.more = 0;
454  if (!reset) {
455  data->items.offset_i = data->items.offset;
456  }
457  else {
458  data->items.offset_i = data->items.offset = 0;
459  data->active = -1;
460 
461  /* On init, find and center active item. */
462  const bool is_first_search = !search_but->but.changed;
463  if (is_first_search && search_but->items_update_fn && search_but->item_active) {
464  data->items.active = search_but->item_active;
465  ui_searchbox_update_fn(C, search_but, but->editstr, &data->items);
466  data->items.active = nullptr;
467 
468  /* found active item, calculate real offset by centering it */
469  if (data->items.totitem) {
470  /* first case, begin of list */
471  if (data->items.offset_i < data->items.maxitem) {
472  data->active = data->items.offset_i;
473  data->items.offset_i = 0;
474  }
475  else {
476  /* second case, end of list */
477  if (data->items.totitem - data->items.offset_i <= data->items.maxitem) {
478  data->active = data->items.offset_i - data->items.totitem + data->items.maxitem;
479  data->items.offset_i = data->items.totitem - data->items.maxitem;
480  }
481  else {
482  /* center active item */
483  data->items.offset_i -= data->items.maxitem / 2;
484  data->active = data->items.maxitem / 2;
485  }
486  }
487  }
488  data->items.offset = data->items.offset_i;
489  data->items.totitem = 0;
490  }
491  }
492 
493  /* callback */
494  if (search_but->items_update_fn) {
495  ui_searchbox_update_fn(C, search_but, but->editstr, &data->items);
496  }
497 
498  /* handle case where editstr is equal to one of items */
499  if (reset && data->active == -1) {
500  for (int a = 0; a < data->items.totitem; a++) {
501  const char *name = data->items.names[a] +
502  /* Never include the prefix in the button. */
503  (data->items.name_prefix_offsets ? data->items.name_prefix_offsets[a] :
504  0);
505  const char *name_sep = data->use_shortcut_sep ? strrchr(name, UI_SEP_CHAR) : nullptr;
506  if (STREQLEN(but->editstr, name, name_sep ? (name_sep - name) : data->items.maxstrlen)) {
507  data->active = a;
508  break;
509  }
510  }
511  if (data->items.totitem == 1 && but->editstr[0]) {
512  data->active = 0;
513  }
514  }
515 
516  /* validate selected item */
517  ui_searchbox_select(C, region, but, 0);
518 
519  ED_region_tag_redraw(region);
520 }
521 
523 {
524  uiButSearch *search_but = (uiButSearch *)but;
525  uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
526  int match = AUTOCOMPLETE_NO_MATCH;
527 
529 
530  if (str[0]) {
532 
533  ui_searchbox_update_fn(C, search_but, but->editstr, &data->items);
534 
535  match = UI_autocomplete_end(data->items.autocpl, str);
536  data->items.autocpl = nullptr;
537  }
538 
539  return match;
540 }
541 
542 static void ui_searchbox_region_draw_fn(const bContext *C, ARegion *region)
543 {
544  uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
545 
546  /* pixel space */
548 
549  if (data->noback == false) {
550  ui_draw_widget_menu_back(&data->bbox, true);
551  }
552 
553  /* draw text */
554  if (data->items.totitem) {
555  rcti rect;
556 
557  if (data->preview) {
558  /* draw items */
559  for (int a = 0; a < data->items.totitem; a++) {
560  const int but_flag = ((a == data->active) ? UI_ACTIVE : 0) | data->items.but_flags[a];
561 
562  /* ensure icon is up-to-date */
563  ui_icon_ensure_deferred(C, data->items.icons[a], data->preview);
564 
565  ui_searchbox_butrect(&rect, data, a);
566 
567  /* widget itself */
568  ui_draw_preview_item(&data->fstyle,
569  &rect,
570  data->items.names[a],
571  data->items.icons[a],
572  but_flag,
574  }
575 
576  /* indicate more */
577  if (data->items.more) {
578  ui_searchbox_butrect(&rect, data, data->items.maxitem - 1);
580  UI_icon_draw(rect.xmax - 18, rect.ymin - 7, ICON_TRIA_DOWN);
582  }
583  if (data->items.offset) {
584  ui_searchbox_butrect(&rect, data, 0);
586  UI_icon_draw(rect.xmin, rect.ymax - 9, ICON_TRIA_UP);
588  }
589  }
590  else {
591  const int search_sep_len = data->sep_string ? strlen(data->sep_string) : 0;
592  /* draw items */
593  for (int a = 0; a < data->items.totitem; a++) {
594  const int but_flag = ((a == data->active) ? UI_ACTIVE : 0) | data->items.but_flags[a];
595  char *name = data->items.names[a];
596  int icon = data->items.icons[a];
597  char *name_sep_test = nullptr;
598 
600  if (data->use_shortcut_sep) {
601  separator_type = UI_MENU_ITEM_SEPARATOR_SHORTCUT;
602  }
603  /* Only set for displaying additional hint (e.g. library name of a linked data-block). */
604  else if (but_flag & UI_BUT_HAS_SEP_CHAR) {
605  separator_type = UI_MENU_ITEM_SEPARATOR_HINT;
606  }
607 
608  ui_searchbox_butrect(&rect, data, a);
609 
610  /* widget itself */
611  if ((search_sep_len == 0) ||
612  !(name_sep_test = strstr(data->items.names[a], data->sep_string))) {
613  if (!icon && data->items.has_icon) {
614  /* If there is any icon item, make sure all items line up. */
615  icon = ICON_BLANK1;
616  }
617 
618  /* Simple menu item. */
619  ui_draw_menu_item(&data->fstyle, &rect, name, icon, but_flag, separator_type, nullptr);
620  }
621  else {
622  /* Split menu item, faded text before the separator. */
623  char *name_sep = nullptr;
624  do {
625  name_sep = name_sep_test;
626  name_sep_test = strstr(name_sep + search_sep_len, data->sep_string);
627  } while (name_sep_test != nullptr);
628 
629  name_sep += search_sep_len;
630  const char name_sep_prev = *name_sep;
631  *name_sep = '\0';
632  int name_width = 0;
633  ui_draw_menu_item(&data->fstyle,
634  &rect,
635  name,
636  0,
637  but_flag | UI_BUT_INACTIVE,
639  &name_width);
640  *name_sep = name_sep_prev;
641  rect.xmin += name_width;
642  rect.xmin += UI_UNIT_X / 4;
643 
644  if (icon == ICON_BLANK1) {
645  icon = ICON_NONE;
646  rect.xmin -= UI_DPI_ICON_SIZE / 4;
647  }
648 
649  /* The previous menu item draws the active selection. */
651  &data->fstyle, &rect, name_sep, icon, but_flag, separator_type, nullptr);
652  }
653  }
654  /* indicate more */
655  if (data->items.more) {
656  ui_searchbox_butrect(&rect, data, data->items.maxitem - 1);
658  UI_icon_draw(BLI_rcti_size_x(&rect) / 2, rect.ymin - 9, ICON_TRIA_DOWN);
660  }
661  if (data->items.offset) {
662  ui_searchbox_butrect(&rect, data, 0);
664  UI_icon_draw(BLI_rcti_size_x(&rect) / 2, rect.ymax - 7, ICON_TRIA_UP);
666  }
667  }
668  }
669 }
670 
672 {
673  uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
674 
675  /* free search data */
676  for (int a = 0; a < data->items.maxitem; a++) {
677  MEM_freeN(data->items.names[a]);
678  }
679  MEM_freeN(data->items.names);
680  MEM_freeN(data->items.pointers);
681  MEM_freeN(data->items.icons);
682  MEM_freeN(data->items.but_flags);
683 
684  if (data->items.name_prefix_offsets != nullptr) {
685  MEM_freeN(data->items.name_prefix_offsets);
686  }
687 
688  MEM_freeN(data);
689  region->regiondata = nullptr;
690 }
691 
693  ARegion *butregion,
694  uiButSearch *search_but,
695  const bool use_shortcut_sep)
696 {
697  wmWindow *win = CTX_wm_window(C);
698  const uiStyle *style = UI_style_get();
699  uiBut *but = &search_but->but;
700  const float aspect = but->block->aspect;
701  const int margin = UI_POPUP_MARGIN;
702 
703  /* create area region */
705 
706  static ARegionType type;
707  memset(&type, 0, sizeof(ARegionType));
710  type.regionid = RGN_TYPE_TEMPORARY;
711  region->type = &type;
712 
713  /* create searchbox data */
714  uiSearchboxData *data = MEM_cnew<uiSearchboxData>(__func__);
715 
716  /* set font, get bb */
717  data->fstyle = style->widget; /* copy struct */
718  ui_fontscale(&data->fstyle.points, aspect);
719  UI_fontstyle_set(&data->fstyle);
720 
721  region->regiondata = data;
722 
723  /* special case, hardcoded feature, not draw backdrop when called from menus,
724  * assume for design that popup already added it */
725  if (but->block->flag & UI_BLOCK_SEARCH_MENU) {
726  data->noback = true;
727  }
728 
729  if (but->a1 > 0 && but->a2 > 0) {
730  data->preview = true;
731  data->prv_rows = but->a1;
732  data->prv_cols = but->a2;
733  }
734 
735  if (but->optype != nullptr || use_shortcut_sep) {
736  data->use_shortcut_sep = true;
737  }
738  data->sep_string = search_but->item_sep_string;
739 
740  /* compute position */
741  if (but->block->flag & UI_BLOCK_SEARCH_MENU) {
742  const int search_but_h = BLI_rctf_size_y(&but->rect) + 10;
743  /* this case is search menu inside other menu */
744  /* we copy region size */
745 
746  region->winrct = butregion->winrct;
747 
748  /* widget rect, in region coords */
749  data->bbox.xmin = margin;
750  data->bbox.xmax = BLI_rcti_size_x(&region->winrct) - margin;
751  data->bbox.ymin = margin;
752  data->bbox.ymax = BLI_rcti_size_y(&region->winrct) - margin;
753 
754  /* check if button is lower half */
755  if (but->rect.ymax < BLI_rctf_cent_y(&but->block->rect)) {
756  data->bbox.ymin += search_but_h;
757  }
758  else {
759  data->bbox.ymax -= search_but_h;
760  }
761  }
762  else {
763  const int searchbox_width = UI_searchbox_size_x();
764 
765  rctf rect_fl;
766  rect_fl.xmin = but->rect.xmin - 5; /* align text with button */
767  rect_fl.xmax = but->rect.xmax + 5; /* symmetrical */
768  rect_fl.ymax = but->rect.ymin;
769  rect_fl.ymin = rect_fl.ymax - UI_searchbox_size_y();
770 
771  const int ofsx = (but->block->panel) ? but->block->panel->ofsx : 0;
772  const int ofsy = (but->block->panel) ? but->block->panel->ofsy : 0;
773 
774  BLI_rctf_translate(&rect_fl, ofsx, ofsy);
775 
776  /* minimal width */
777  if (BLI_rctf_size_x(&rect_fl) < searchbox_width) {
778  rect_fl.xmax = rect_fl.xmin + searchbox_width;
779  }
780 
781  /* copy to int, gets projected if possible too */
782  rcti rect_i;
783  BLI_rcti_rctf_copy(&rect_i, &rect_fl);
784 
785  if (butregion->v2d.cur.xmin != butregion->v2d.cur.xmax) {
786  UI_view2d_view_to_region_rcti(&butregion->v2d, &rect_fl, &rect_i);
787  }
788 
789  BLI_rcti_translate(&rect_i, butregion->winrct.xmin, butregion->winrct.ymin);
790 
791  int winx = WM_window_pixels_x(win);
792  // winy = WM_window_pixels_y(win); /* UNUSED */
793  // wm_window_get_size(win, &winx, &winy);
794 
795  if (rect_i.xmax > winx) {
796  /* super size */
797  if (rect_i.xmax > winx + rect_i.xmin) {
798  rect_i.xmax = winx;
799  rect_i.xmin = 0;
800  }
801  else {
802  rect_i.xmin -= rect_i.xmax - winx;
803  rect_i.xmax = winx;
804  }
805  }
806 
807  if (rect_i.ymin < 0) {
808  int newy1 = but->rect.ymax + ofsy;
809 
810  if (butregion->v2d.cur.xmin != butregion->v2d.cur.xmax) {
811  newy1 = UI_view2d_view_to_region_y(&butregion->v2d, newy1);
812  }
813 
814  newy1 += butregion->winrct.ymin;
815 
816  rect_i.ymax = BLI_rcti_size_y(&rect_i) + newy1;
817  rect_i.ymin = newy1;
818  }
819 
820  /* widget rect, in region coords */
821  data->bbox.xmin = margin;
822  data->bbox.xmax = BLI_rcti_size_x(&rect_i) + margin;
823  data->bbox.ymin = margin;
824  data->bbox.ymax = BLI_rcti_size_y(&rect_i) + margin;
825 
826  /* region bigger for shadow */
827  region->winrct.xmin = rect_i.xmin - margin;
828  region->winrct.xmax = rect_i.xmax + margin;
829  region->winrct.ymin = rect_i.ymin - margin;
830  region->winrct.ymax = rect_i.ymax;
831  }
832 
833  /* adds subwindow */
834  ED_region_floating_init(region);
835 
836  /* notify change and redraw */
837  ED_region_tag_redraw(region);
838 
839  /* prepare search data */
840  if (data->preview) {
841  data->items.maxitem = data->prv_rows * data->prv_cols;
842  }
843  else {
844  data->items.maxitem = SEARCH_ITEMS;
845  }
846  /* In case the button's string is dynamic, make sure there are buffers available. */
847  data->items.maxstrlen = but->hardmax == 0 ? UI_MAX_NAME_STR : but->hardmax;
848  data->items.totitem = 0;
849  data->items.names = (char **)MEM_callocN(data->items.maxitem * sizeof(void *), __func__);
850  data->items.pointers = (void **)MEM_callocN(data->items.maxitem * sizeof(void *), __func__);
851  data->items.icons = (int *)MEM_callocN(data->items.maxitem * sizeof(int), __func__);
852  data->items.but_flags = (int *)MEM_callocN(data->items.maxitem * sizeof(int), __func__);
853  data->items.name_prefix_offsets = nullptr; /* Lazy initialized as needed. */
854  for (int i = 0; i < data->items.maxitem; i++) {
855  data->items.names[i] = (char *)MEM_callocN(data->items.maxstrlen + 1, __func__);
856  }
857 
858  return region;
859 }
860 
862 {
863  return ui_searchbox_create_generic_ex(C, butregion, search_but, false);
864 }
865 
872 static void str_tolower_titlecaps_ascii(char *str, const size_t len)
873 {
874  bool prev_delim = true;
875 
876  for (size_t i = 0; (i < len) && str[i]; i++) {
877  if (str[i] >= 'A' && str[i] <= 'Z') {
878  if (prev_delim == false) {
879  str[i] += 'a' - 'A';
880  }
881  }
882  else if (str[i] == '_') {
883  str[i] = ' ';
884  }
885 
886  prev_delim = ELEM(str[i], ' ') || (str[i] >= '0' && str[i] <= '9');
887  }
888 }
889 
891 {
892  uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
893 
894  /* pixel space */
896 
897  if (data->noback == false) {
898  ui_draw_widget_menu_back(&data->bbox, true);
899  }
900 
901  /* draw text */
902  if (data->items.totitem) {
903  rcti rect;
904 
905  /* draw items */
906  for (int a = 0; a < data->items.totitem; a++) {
907  rcti rect_pre, rect_post;
908  ui_searchbox_butrect(&rect, data, a);
909 
910  rect_pre = rect;
911  rect_post = rect;
912 
913  rect_pre.xmax = rect_post.xmin = rect.xmin + ((rect.xmax - rect.xmin) / 4);
914 
915  /* widget itself */
916  /* NOTE: i18n messages extracting tool does the same, please keep it in sync. */
917  {
918  const int but_flag = ((a == data->active) ? UI_ACTIVE : 0) | data->items.but_flags[a];
919 
920  wmOperatorType *ot = static_cast<wmOperatorType *>(data->items.pointers[a]);
921  char text_pre[128];
922  const char *text_pre_p = strstr(ot->idname, "_OT_");
923  if (text_pre_p == nullptr) {
924  text_pre[0] = '\0';
925  }
926  else {
927  int text_pre_len;
928  text_pre_p += 1;
929  text_pre_len = BLI_strncpy_rlen(
930  text_pre, ot->idname, min_ii(sizeof(text_pre), text_pre_p - ot->idname));
931  text_pre[text_pre_len] = ':';
932  text_pre[text_pre_len + 1] = '\0';
933  str_tolower_titlecaps_ascii(text_pre, sizeof(text_pre));
934  }
935 
936  rect_pre.xmax += 4; /* sneaky, avoid showing ugly margin */
937  ui_draw_menu_item(&data->fstyle,
938  &rect_pre,
940  data->items.icons[a],
941  but_flag,
943  nullptr);
944  ui_draw_menu_item(&data->fstyle,
945  &rect_post,
946  data->items.names[a],
947  0,
948  but_flag,
949  data->use_shortcut_sep ? UI_MENU_ITEM_SEPARATOR_SHORTCUT :
951  nullptr);
952  }
953  }
954  /* indicate more */
955  if (data->items.more) {
956  ui_searchbox_butrect(&rect, data, data->items.maxitem - 1);
958  UI_icon_draw(BLI_rcti_size_x(&rect) / 2, rect.ymin - 9, ICON_TRIA_DOWN);
960  }
961  if (data->items.offset) {
962  ui_searchbox_butrect(&rect, data, 0);
964  UI_icon_draw(BLI_rcti_size_x(&rect) / 2, rect.ymax - 7, ICON_TRIA_UP);
966  }
967  }
968 }
969 
971 {
972  ARegion *region = ui_searchbox_create_generic_ex(C, butregion, search_but, true);
973 
975 
976  return region;
977 }
978 
980 {
982 }
983 
985 {
986  /* Currently unused. */
987 }
988 
990 {
991  ARegion *region = ui_searchbox_create_generic_ex(C, butregion, search_but, true);
992 
993  if (false) {
995  }
996 
997  return region;
998 }
999 
1001 {
1002  uiBut *but = &search_but->but;
1003 
1004  /* possibly very large lists (such as ID datablocks) only
1005  * only validate string RNA buts (not pointers) */
1006  if (but->rnaprop && RNA_property_type(but->rnaprop) != PROP_STRING) {
1007  return;
1008  }
1009 
1010  uiSearchItems *items = MEM_cnew<uiSearchItems>(__func__);
1011 
1012  /* setup search struct */
1013  items->maxitem = 10;
1014  items->maxstrlen = 256;
1015  items->names = (char **)MEM_callocN(items->maxitem * sizeof(void *), __func__);
1016  for (int i = 0; i < items->maxitem; i++) {
1017  items->names[i] = (char *)MEM_callocN(but->hardmax + 1, __func__);
1018  }
1019 
1020  ui_searchbox_update_fn((bContext *)but->block->evil_C, search_but, but->drawstr, items);
1021 
1022  if (!search_but->results_are_suggestions) {
1023  /* Only red-alert when we are sure of it, this can miss cases when >10 matches. */
1024  if (items->totitem == 0) {
1026  }
1027  else if (items->more == 0) {
1028  if (UI_search_items_find_index(items, but->drawstr) == -1) {
1030  }
1031  }
1032  }
1033 
1034  for (int i = 0; i < items->maxitem; i++) {
1035  MEM_freeN(items->names[i]);
1036  }
1037  MEM_freeN(items->names);
1038  MEM_freeN(items);
1039 }
1040 
struct ScrArea * CTX_wm_area(const bContext *C)
Definition: context.c:738
struct bScreen * CTX_wm_screen(const bContext *C)
Definition: context.c:733
struct wmWindow * CTX_wm_window(const bContext *C)
Definition: context.c:723
#define BLI_assert(a)
Definition: BLI_assert.h:46
#define LISTBASE_FOREACH(type, var, list)
Definition: BLI_listbase.h:336
MINLINE int min_ii(int a, int b)
void BLI_rctf_translate(struct rctf *rect, float x, float y)
Definition: rct.c:566
BLI_INLINE int BLI_rcti_size_y(const struct rcti *rct)
Definition: BLI_rect.h:190
BLI_INLINE float BLI_rctf_cent_y(const struct rctf *rct)
Definition: BLI_rect.h:181
void BLI_rcti_translate(struct rcti *rect, int x, int y)
Definition: rct.c:559
bool BLI_rcti_isect_pt(const struct rcti *rect, int x, int y)
BLI_INLINE int BLI_rcti_size_x(const struct rcti *rct)
Definition: BLI_rect.h:186
BLI_INLINE float BLI_rctf_size_x(const struct rctf *rct)
Definition: BLI_rect.h:194
void BLI_rcti_rctf_copy(struct rcti *dst, const struct rctf *src)
BLI_INLINE float BLI_rctf_size_y(const struct rctf *rct)
Definition: BLI_rect.h:198
size_t BLI_strncpy_rlen(char *__restrict dst, const char *__restrict src, size_t maxncpy) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition: string.c:120
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t maxncpy) ATTR_NONNULL()
Definition: string.c:64
#define STREQLEN(a, b, n)
#define UNUSED(x)
#define ELEM(...)
#define STREQ(a, b)
#define CTX_IFACE_(context, msgid)
#define BLT_I18NCONTEXT_OPERATOR_DEFAULT
ID and Library types, which are fundamental for sdna.
@ RGN_TYPE_TEMPORARY
@ USER_TOOLTIPS
void ED_region_floating_init(struct ARegion *region)
Definition: area.c:2057
void ED_region_tag_redraw(struct ARegion *region)
Definition: area.c:655
_GL_VOID GLfloat value _GL_VOID_RET _GL_VOID const GLuint GLboolean *residences _GL_BOOL_RET _GL_VOID GLsizei GLfloat GLfloat GLfloat GLfloat const GLubyte *bitmap _GL_VOID_RET _GL_VOID GLenum type
@ GPU_BLEND_NONE
Definition: GPU_state.h:60
@ GPU_BLEND_ALPHA
Definition: GPU_state.h:62
void GPU_blend(eGPUBlend blend)
Definition: gpu_state.cc:39
Read Guarded memory(de)allocation.
@ PROP_STRING
Definition: RNA_types.h:62
#define C
Definition: RandGen.cpp:25
#define UI_UNIT_Y
@ UI_BUT_REDALERT
Definition: UI_interface.h:201
@ UI_BUT_DISABLED
Definition: UI_interface.h:196
@ UI_BUT_INACTIVE
Definition: UI_interface.h:203
@ UI_BUT_HAS_SEP_CHAR
Definition: UI_interface.h:222
@ UI_BUT_VALUE_CLEAR
Definition: UI_interface.h:228
void UI_fontstyle_set(const struct uiFontStyle *fs)
#define UI_SEP_CHAR
Definition: UI_interface.h:83
const struct uiStyle * UI_style_get(void)
#define AUTOCOMPLETE_NO_MATCH
AutoComplete * UI_autocomplete_begin(const char *startname, size_t maxlen)
Definition: interface.cc:4901
@ UI_STYLE_TEXT_LEFT
int UI_autocomplete_end(AutoComplete *autocpl, char *autoname)
Definition: interface.cc:4948
#define UI_DPI_ICON_SIZE
Definition: UI_interface.h:307
struct ARegion * UI_region_searchbox_region_get(const struct ARegion *button_region)
#define UI_UNIT_X
@ UI_BTYPE_SEARCH_MENU
Definition: UI_interface.h:372
#define UI_MAX_NAME_STR
Definition: UI_interface.h:92
void UI_autocomplete_update_name(AutoComplete *autocpl, const char *name)
Definition: interface.cc:4914
void UI_but_flag_enable(uiBut *but, int flag)
Definition: interface.cc:5858
@ UI_BLOCK_SEARCH_MENU
Definition: UI_interface.h:147
void UI_icon_draw(float x, float y, int icon_id)
void UI_view2d_view_to_region_rcti(const struct View2D *v2d, const struct rctf *rect_src, struct rcti *rect_dst) ATTR_NONNULL()
float UI_view2d_view_to_region_y(const struct View2D *v2d, float y)
unsigned int U
Definition: btGjkEpa3.h:78
int len
Definition: draw_manager.c:108
#define str(s)
static bool is_inside(int x, int y, int cols, int rows)
Definition: filesel.c:706
uint col
void ui_fontscale(float *points, float aspect)
Definition: interface.cc:2014
int ui_but_string_get_max_length(uiBut *but)
Definition: interface.cc:2653
void ui_pan_to_scroll(const wmEvent *event, int *type, int *val)
void ui_icon_ensure_deferred(const bContext *C, const int icon_id, const bool big)
#define UI_POPUP_MENU_TOP
void ui_draw_widget_menu_back(const rcti *rect, bool use_shadow)
#define UI_POPUP_MARGIN
void ui_draw_menu_item(const struct uiFontStyle *fstyle, rcti *rect, const char *name, int iconid, int but_flag, uiMenuItemSeparatorType separator_type, int *r_xmax)
void ui_draw_preview_item(const struct uiFontStyle *fstyle, rcti *rect, const char *name, int iconid, int but_flag, eFontStyle_Align text_align)
@ UI_ACTIVE
uiMenuItemSeparatorType
@ UI_MENU_ITEM_SEPARATOR_NONE
@ UI_MENU_ITEM_SEPARATOR_HINT
@ UI_MENU_ITEM_SEPARATOR_SHORTCUT
bool UI_search_item_add(uiSearchItems *items, const char *name, void *poin, int iconid, const int but_flag, const uint8_t name_prefix_offset)
int ui_searchbox_find_index(ARegion *region, const char *name)
void ui_searchbox_update(bContext *C, ARegion *region, uiBut *but, const bool reset)
ARegion * ui_searchbox_create_operator(bContext *C, ARegion *butregion, uiButSearch *search_but)
static void str_tolower_titlecaps_ascii(char *str, const size_t len)
void ui_but_search_refresh(uiButSearch *search_but)
#define SEARCH_ITEMS
static void ui_searchbox_butrect(rcti *r_rect, uiSearchboxData *data, int itemnr)
static void ui_searchbox_select(bContext *C, ARegion *region, uiBut *but, int step)
static struct ARegion * wm_searchbox_tooltip_init(struct bContext *C, struct ARegion *region, int *UNUSED(r_pass), double *UNUSED(pass_delay), bool *r_exit_on_event)
#define MENU_BORDER
void ui_searchbox_free(bContext *C, ARegion *region)
int UI_searchbox_size_x()
ARegion * ui_searchbox_create_generic(bContext *C, ARegion *butregion, uiButSearch *search_but)
bool ui_searchbox_apply(uiBut *but, ARegion *region)
static void ui_searchbox_update_fn(bContext *C, uiButSearch *search_but, const char *str, uiSearchItems *items)
static void ui_searchbox_region_draw_fn(const bContext *C, ARegion *region)
int UI_searchbox_size_y()
static ARegion * ui_searchbox_create_generic_ex(bContext *C, ARegion *butregion, uiButSearch *search_but, const bool use_shortcut_sep)
static void ui_searchbox_region_draw_cb__menu(const bContext *UNUSED(C), ARegion *UNUSED(region))
bool ui_searchbox_event(bContext *C, ARegion *region, uiBut *but, ARegion *butregion, const wmEvent *event)
static void ui_searchbox_region_draw_cb__operator(const bContext *UNUSED(C), ARegion *region)
ARegion * ui_searchbox_create_menu(bContext *C, ARegion *butregion, uiButSearch *search_but)
int UI_search_items_find_index(uiSearchItems *items, const char *name)
static void ui_searchbox_region_free_fn(ARegion *region)
bool ui_searchbox_inside(ARegion *region, const int xy[2])
int ui_searchbox_autocomplete(bContext *C, ARegion *region, uiBut *but, char *str)
void ui_region_temp_remove(bContext *C, bScreen *screen, ARegion *region)
ARegion * ui_region_temp_add(bScreen *screen)
ccl_gpu_kernel_postfix ccl_global float int int int int float bool reset
clear internal cached data and reset random seed
void(* MEM_freeN)(void *vmemh)
Definition: mallocn.c:27
void *(* MEM_callocN)(size_t len, const char *str)
Definition: mallocn.c:31
static unsigned a[3]
Definition: RandGen.cpp:78
static void area(int d1, int d2, int e1, int e2, float weights[2])
bool active
all scheduled work for the GPU.
PropertyType RNA_property_type(PropertyRNA *prop)
Definition: rna_access.c:1010
unsigned char uint8_t
Definition: stdint.h:78
void(* draw)(const struct bContext *C, struct ARegion *region)
Definition: BKE_screen.h:151
void * regiondata
struct ARegionType * type
ListBase uiblocks
float xmax
Definition: DNA_vec_types.h:69
float xmin
Definition: DNA_vec_types.h:69
float ymax
Definition: DNA_vec_types.h:70
float ymin
Definition: DNA_vec_types.h:70
int ymin
Definition: DNA_vec_types.h:64
int ymax
Definition: DNA_vec_types.h:64
int xmin
Definition: DNA_vec_types.h:63
int xmax
Definition: DNA_vec_types.h:63
struct Panel * panel
void * evil_C
uiButSearchUpdateFn items_update_fn
bool results_are_suggestions
const char * item_sep_string
uiButSearchTooltipFn item_tooltip_fn
uiButSearchContextMenuFn item_context_menu_fn
char * editstr
eButType type
float hardmax
bool changed
uiBlock * block
struct wmOperatorType * optype
char drawstr[UI_MAX_DRAW_STR]
struct PropertyRNA * rnaprop
AutoComplete * autocpl
uiFontStyle widget
int xy[2]
Definition: WM_types.h:682
const char * idname
Definition: WM_types.h:890
int xy[2]
Definition: wm_draw.c:135
@ MOUSEPAN
@ RIGHTMOUSE
@ EVT_DOWNARROWKEY
@ WHEELUPMOUSE
@ WHEELDOWNMOUSE
@ MOUSEMOVE
@ EVT_UPARROWKEY
wmOperatorType * ot
Definition: wm_files.c:3479
void wmOrtho2_region_pixelspace(const ARegion *region)
Definition: wm_subwindow.c:103
void WM_tooltip_clear(bContext *C, wmWindow *win)
Definition: wm_tooltip.c:80
void WM_tooltip_timer_init(bContext *C, wmWindow *win, ScrArea *area, ARegion *region, wmTooltipInitFn init)
Definition: wm_tooltip.c:62
int WM_window_pixels_x(const wmWindow *win)
Definition: wm_window.c:2076