Cleanup: right shift in interface code
[blender.git] / source / blender / editors / interface / interface_region_search.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) 2008 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_region_search.c
27  *  \ingroup edinterface
28  *
29  * Search Box Region & Interaction
30  */
31
32 #include <stdarg.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <assert.h>
36
37 #include "MEM_guardedalloc.h"
38
39 #include "DNA_userdef_types.h"
40
41 #include "BLI_math.h"
42
43 #include "BLI_string.h"
44 #include "BLI_rect.h"
45 #include "BLI_utildefines.h"
46
47 #include "BKE_context.h"
48 #include "BKE_screen.h"
49
50 #include "WM_api.h"
51 #include "WM_types.h"
52
53 #include "RNA_access.h"
54
55 #include "BIF_gl.h"
56
57 #include "UI_interface.h"
58 #include "UI_interface_icons.h"
59 #include "UI_view2d.h"
60
61 #include "BLT_translation.h"
62
63 #include "ED_screen.h"
64
65 #include "interface_intern.h"
66 #include "interface_regions_intern.h"
67
68 #define MENU_BORDER                     (int)(0.3f * U.widget_unit)
69
70 /* -------------------------------------------------------------------- */
71 /** \name Search Box Creation
72  * \{ */
73
74 struct uiSearchItems {
75         int maxitem, totitem, maxstrlen;
76
77         int offset, offset_i; /* offset for inserting in array */
78         int more;  /* flag indicating there are more items */
79
80         char **names;
81         void **pointers;
82         int *icons;
83
84         AutoComplete *autocpl;
85         void *active;
86 };
87
88 typedef struct uiSearchboxData {
89         rcti bbox;
90         uiFontStyle fstyle;
91         uiSearchItems items;
92         int active;     /* index in items array */
93         bool noback;    /* when menu opened with enough space for this */
94         bool preview;   /* draw thumbnail previews, rather than list */
95         bool use_sep;   /* use the UI_SEP_CHAR char for splitting shortcuts (good for operators, bad for data) */
96         int prv_rows, prv_cols;
97 } uiSearchboxData;
98
99 #define SEARCH_ITEMS    10
100
101 /* exported for use by search callbacks */
102 /* returns zero if nothing to add */
103 bool UI_search_item_add(uiSearchItems *items, const char *name, void *poin, int iconid)
104 {
105         /* hijack for autocomplete */
106         if (items->autocpl) {
107                 UI_autocomplete_update_name(items->autocpl, name);
108                 return true;
109         }
110
111         /* hijack for finding active item */
112         if (items->active) {
113                 if (poin == items->active)
114                         items->offset_i = items->totitem;
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         if (items->pointers)
133                 items->pointers[items->totitem] = poin;
134         if (items->icons)
135                 items->icons[items->totitem] = iconid;
136
137         items->totitem++;
138
139         return true;
140 }
141
142 int UI_searchbox_size_y(void)
143 {
144         return SEARCH_ITEMS * UI_UNIT_Y + 2 * UI_POPUP_MENU_TOP;
145 }
146
147 int UI_searchbox_size_x(void)
148 {
149         return 12 * UI_UNIT_X;
150 }
151
152 int UI_search_items_find_index(uiSearchItems *items, const char *name)
153 {
154         int i;
155         for (i = 0; i < items->totitem; i++) {
156                 if (STREQ(name, items->names[i])) {
157                         return i;
158                 }
159         }
160         return -1;
161 }
162
163 /* ar is the search box itself */
164 static void ui_searchbox_select(bContext *C, ARegion *ar, uiBut *but, int step)
165 {
166         uiSearchboxData *data = ar->regiondata;
167
168         /* apply step */
169         data->active += step;
170
171         if (data->items.totitem == 0) {
172                 data->active = -1;
173         }
174         else if (data->active >= data->items.totitem) {
175                 if (data->items.more) {
176                         data->items.offset++;
177                         data->active = data->items.totitem - 1;
178                         ui_searchbox_update(C, ar, but, false);
179                 }
180                 else {
181                         data->active = data->items.totitem - 1;
182                 }
183         }
184         else if (data->active < 0) {
185                 if (data->items.offset) {
186                         data->items.offset--;
187                         data->active = 0;
188                         ui_searchbox_update(C, ar, but, false);
189                 }
190                 else {
191                         /* only let users step into an 'unset' state for unlink buttons */
192                         data->active = (but->flag & UI_BUT_VALUE_CLEAR) ? -1 : 0;
193                 }
194         }
195
196         ED_region_tag_redraw(ar);
197 }
198
199 static void ui_searchbox_butrect(rcti *r_rect, uiSearchboxData *data, int itemnr)
200 {
201         /* thumbnail preview */
202         if (data->preview) {
203                 int butw = (BLI_rcti_size_x(&data->bbox) - 2 * MENU_BORDER) / data->prv_cols;
204                 int buth = (BLI_rcti_size_y(&data->bbox) - 2 * MENU_BORDER) / data->prv_rows;
205                 int row, col;
206
207                 *r_rect = data->bbox;
208
209                 col = itemnr % data->prv_cols;
210                 row = itemnr / data->prv_cols;
211
212                 r_rect->xmin += MENU_BORDER + (col * butw);
213                 r_rect->xmax = r_rect->xmin + butw;
214
215                 r_rect->ymax -= MENU_BORDER + (row * buth);
216                 r_rect->ymin = r_rect->ymax - buth;
217         }
218         /* list view */
219         else {
220                 int buth = (BLI_rcti_size_y(&data->bbox) - 2 * UI_POPUP_MENU_TOP) / SEARCH_ITEMS;
221
222                 *r_rect = data->bbox;
223                 r_rect->xmin = data->bbox.xmin + 3.0f;
224                 r_rect->xmax = data->bbox.xmax - 3.0f;
225
226                 r_rect->ymax = data->bbox.ymax - UI_POPUP_MENU_TOP - itemnr * buth;
227                 r_rect->ymin = r_rect->ymax - buth;
228         }
229
230 }
231
232 int ui_searchbox_find_index(ARegion *ar, const char *name)
233 {
234         uiSearchboxData *data = ar->regiondata;
235         return UI_search_items_find_index(&data->items, name);
236 }
237
238 /* x and y in screencoords */
239 bool ui_searchbox_inside(ARegion *ar, int x, int y)
240 {
241         uiSearchboxData *data = ar->regiondata;
242
243         return BLI_rcti_isect_pt(&data->bbox, x - ar->winrct.xmin, y - ar->winrct.ymin);
244 }
245
246 /* string validated to be of correct length (but->hardmax) */
247 bool ui_searchbox_apply(uiBut *but, ARegion *ar)
248 {
249         uiSearchboxData *data = ar->regiondata;
250
251         but->func_arg2 = NULL;
252
253         if (data->active != -1) {
254                 const char *name = data->items.names[data->active];
255                 const char *name_sep = data->use_sep ? strrchr(name, UI_SEP_CHAR) : NULL;
256
257                 BLI_strncpy(but->editstr, name, name_sep ? (name_sep - name) : data->items.maxstrlen);
258
259                 but->func_arg2 = data->items.pointers[data->active];
260
261                 return true;
262         }
263         else if (but->flag & UI_BUT_VALUE_CLEAR) {
264                 /* It is valid for _VALUE_CLEAR flavor to have no active element (it's a valid way to unlink). */
265                 but->editstr[0] = '\0';
266
267                 return true;
268         }
269         else {
270                 return false;
271         }
272 }
273
274 void ui_searchbox_event(bContext *C, ARegion *ar, uiBut *but, const wmEvent *event)
275 {
276         uiSearchboxData *data = ar->regiondata;
277         int type = event->type, val = event->val;
278
279         if (type == MOUSEPAN)
280                 ui_pan_to_scroll(event, &type, &val);
281
282         switch (type) {
283                 case WHEELUPMOUSE:
284                 case UPARROWKEY:
285                         ui_searchbox_select(C, ar, but, -1);
286                         break;
287                 case WHEELDOWNMOUSE:
288                 case DOWNARROWKEY:
289                         ui_searchbox_select(C, ar, but, 1);
290                         break;
291                 case MOUSEMOVE:
292                         if (BLI_rcti_isect_pt(&ar->winrct, event->x, event->y)) {
293                                 rcti rect;
294                                 int a;
295
296                                 for (a = 0; a < data->items.totitem; a++) {
297                                         ui_searchbox_butrect(&rect, data, a);
298                                         if (BLI_rcti_isect_pt(&rect, event->x - ar->winrct.xmin, event->y - ar->winrct.ymin)) {
299                                                 if (data->active != a) {
300                                                         data->active = a;
301                                                         ui_searchbox_select(C, ar, but, 0);
302                                                         break;
303                                                 }
304                                         }
305                                 }
306                         }
307                         break;
308         }
309 }
310
311 /* ar is the search box itself */
312 void ui_searchbox_update(bContext *C, ARegion *ar, uiBut *but, const bool reset)
313 {
314         uiSearchboxData *data = ar->regiondata;
315
316         /* reset vars */
317         data->items.totitem = 0;
318         data->items.more = 0;
319         if (reset == false) {
320                 data->items.offset_i = data->items.offset;
321         }
322         else {
323                 data->items.offset_i = data->items.offset = 0;
324                 data->active = -1;
325
326                 /* handle active */
327                 if (but->search_func && but->func_arg2) {
328                         data->items.active = but->func_arg2;
329                         but->search_func(C, but->search_arg, but->editstr, &data->items);
330                         data->items.active = NULL;
331
332                         /* found active item, calculate real offset by centering it */
333                         if (data->items.totitem) {
334                                 /* first case, begin of list */
335                                 if (data->items.offset_i < data->items.maxitem) {
336                                         data->active = data->items.offset_i;
337                                         data->items.offset_i = 0;
338                                 }
339                                 else {
340                                         /* second case, end of list */
341                                         if (data->items.totitem - data->items.offset_i <= data->items.maxitem) {
342                                                 data->active = data->items.offset_i - data->items.totitem + data->items.maxitem;
343                                                 data->items.offset_i = data->items.totitem - data->items.maxitem;
344                                         }
345                                         else {
346                                                 /* center active item */
347                                                 data->items.offset_i -= data->items.maxitem / 2;
348                                                 data->active = data->items.maxitem / 2;
349                                         }
350                                 }
351                         }
352                         data->items.offset = data->items.offset_i;
353                         data->items.totitem = 0;
354                 }
355         }
356
357         /* callback */
358         if (but->search_func)
359                 but->search_func(C, but->search_arg, but->editstr, &data->items);
360
361         /* handle case where editstr is equal to one of items */
362         if (reset && data->active == -1) {
363                 int a;
364
365                 for (a = 0; a < data->items.totitem; a++) {
366                         const char *name = data->items.names[a];
367                         const char *name_sep = data->use_sep ? strrchr(name, UI_SEP_CHAR) : NULL;
368                         if (STREQLEN(but->editstr, name, name_sep ? (name_sep - name) : data->items.maxstrlen)) {
369                                 data->active = a;
370                                 break;
371                         }
372                 }
373                 if (data->items.totitem == 1 && but->editstr[0])
374                         data->active = 0;
375         }
376
377         /* validate selected item */
378         ui_searchbox_select(C, ar, but, 0);
379
380         ED_region_tag_redraw(ar);
381 }
382
383 int ui_searchbox_autocomplete(bContext *C, ARegion *ar, uiBut *but, char *str)
384 {
385         uiSearchboxData *data = ar->regiondata;
386         int match = AUTOCOMPLETE_NO_MATCH;
387
388         if (str[0]) {
389                 data->items.autocpl = UI_autocomplete_begin(str, ui_but_string_get_max_length(but));
390
391                 but->search_func(C, but->search_arg, but->editstr, &data->items);
392
393                 match = UI_autocomplete_end(data->items.autocpl, str);
394                 data->items.autocpl = NULL;
395         }
396
397         return match;
398 }
399
400 static void ui_searchbox_region_draw_cb(const bContext *UNUSED(C), ARegion *ar)
401 {
402         uiSearchboxData *data = ar->regiondata;
403
404         /* pixel space */
405         wmOrtho2_region_pixelspace(ar);
406
407         if (data->noback == false)
408                 ui_draw_search_back(NULL, NULL, &data->bbox);  /* style not used yet */
409
410         /* draw text */
411         if (data->items.totitem) {
412                 rcti rect;
413                 int a;
414
415                 if (data->preview) {
416                         /* draw items */
417                         for (a = 0; a < data->items.totitem; a++) {
418                                 ui_searchbox_butrect(&rect, data, a);
419
420                                 /* widget itself */
421                                 ui_draw_preview_item(
422                                         &data->fstyle, &rect, data->items.names[a], data->items.icons[a],
423                                         (a == data->active) ? UI_ACTIVE : 0);
424                         }
425
426                         /* indicate more */
427                         if (data->items.more) {
428                                 ui_searchbox_butrect(&rect, data, data->items.maxitem - 1);
429                                 glEnable(GL_BLEND);
430                                 UI_icon_draw(rect.xmax - 18, rect.ymin - 7, ICON_TRIA_DOWN);
431                                 glDisable(GL_BLEND);
432                         }
433                         if (data->items.offset) {
434                                 ui_searchbox_butrect(&rect, data, 0);
435                                 glEnable(GL_BLEND);
436                                 UI_icon_draw(rect.xmin, rect.ymax - 9, ICON_TRIA_UP);
437                                 glDisable(GL_BLEND);
438                         }
439
440                 }
441                 else {
442                         /* draw items */
443                         for (a = 0; a < data->items.totitem; a++) {
444                                 ui_searchbox_butrect(&rect, data, a);
445
446                                 /* widget itself */
447                                 ui_draw_menu_item(
448                                         &data->fstyle, &rect, data->items.names[a], data->items.icons[a],
449                                         (a == data->active) ? UI_ACTIVE : 0, data->use_sep);
450
451                         }
452                         /* indicate more */
453                         if (data->items.more) {
454                                 ui_searchbox_butrect(&rect, data, data->items.maxitem - 1);
455                                 glEnable(GL_BLEND);
456                                 UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymin - 9, ICON_TRIA_DOWN);
457                                 glDisable(GL_BLEND);
458                         }
459                         if (data->items.offset) {
460                                 ui_searchbox_butrect(&rect, data, 0);
461                                 glEnable(GL_BLEND);
462                                 UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymax - 7, ICON_TRIA_UP);
463                                 glDisable(GL_BLEND);
464                         }
465                 }
466         }
467 }
468
469 static void ui_searchbox_region_free_cb(ARegion *ar)
470 {
471         uiSearchboxData *data = ar->regiondata;
472         int a;
473
474         /* free search data */
475         for (a = 0; a < data->items.maxitem; a++) {
476                 MEM_freeN(data->items.names[a]);
477         }
478         MEM_freeN(data->items.names);
479         MEM_freeN(data->items.pointers);
480         MEM_freeN(data->items.icons);
481
482         MEM_freeN(data);
483         ar->regiondata = NULL;
484 }
485
486 ARegion *ui_searchbox_create_generic(bContext *C, ARegion *butregion, uiBut *but)
487 {
488         wmWindow *win = CTX_wm_window(C);
489         uiStyle *style = UI_style_get();
490         static ARegionType type;
491         ARegion *ar;
492         uiSearchboxData *data;
493         float aspect = but->block->aspect;
494         rctf rect_fl;
495         rcti rect_i;
496         const int margin = UI_POPUP_MARGIN;
497         int winx /*, winy */, ofsx, ofsy;
498         int i;
499
500         /* create area region */
501         ar = ui_region_temp_add(CTX_wm_screen(C));
502
503         memset(&type, 0, sizeof(ARegionType));
504         type.draw = ui_searchbox_region_draw_cb;
505         type.free = ui_searchbox_region_free_cb;
506         type.regionid = RGN_TYPE_TEMPORARY;
507         ar->type = &type;
508
509         /* create searchbox data */
510         data = MEM_callocN(sizeof(uiSearchboxData), "uiSearchboxData");
511
512         /* set font, get bb */
513         data->fstyle = style->widget; /* copy struct */
514         data->fstyle.align = UI_STYLE_TEXT_CENTER;
515         ui_fontscale(&data->fstyle.points, aspect);
516         UI_fontstyle_set(&data->fstyle);
517
518         ar->regiondata = data;
519
520         /* special case, hardcoded feature, not draw backdrop when called from menus,
521          * assume for design that popup already added it */
522         if (but->block->flag & UI_BLOCK_SEARCH_MENU)
523                 data->noback = true;
524
525         if (but->a1 > 0 && but->a2 > 0) {
526                 data->preview = true;
527                 data->prv_rows = but->a1;
528                 data->prv_cols = but->a2;
529         }
530
531         /* only show key shortcuts when needed (not rna buttons) [#36699] */
532         if (but->rnaprop == NULL) {
533                 data->use_sep = true;
534         }
535
536         /* compute position */
537         if (but->block->flag & UI_BLOCK_SEARCH_MENU) {
538                 const int search_but_h = BLI_rctf_size_y(&but->rect) + 10;
539                 /* this case is search menu inside other menu */
540                 /* we copy region size */
541
542                 ar->winrct = butregion->winrct;
543
544                 /* widget rect, in region coords */
545                 data->bbox.xmin = margin;
546                 data->bbox.xmax = BLI_rcti_size_x(&ar->winrct) - margin;
547                 data->bbox.ymin = margin;
548                 data->bbox.ymax = BLI_rcti_size_y(&ar->winrct) - margin;
549
550                 /* check if button is lower half */
551                 if (but->rect.ymax < BLI_rctf_cent_y(&but->block->rect)) {
552                         data->bbox.ymin += search_but_h;
553                 }
554                 else {
555                         data->bbox.ymax -= search_but_h;
556                 }
557         }
558         else {
559                 const int searchbox_width = UI_searchbox_size_x();
560
561                 rect_fl.xmin = but->rect.xmin - 5;   /* align text with button */
562                 rect_fl.xmax = but->rect.xmax + 5;   /* symmetrical */
563                 rect_fl.ymax = but->rect.ymin;
564                 rect_fl.ymin = rect_fl.ymax - UI_searchbox_size_y();
565
566                 ofsx = (but->block->panel) ? but->block->panel->ofsx : 0;
567                 ofsy = (but->block->panel) ? but->block->panel->ofsy : 0;
568
569                 BLI_rctf_translate(&rect_fl, ofsx, ofsy);
570
571                 /* minimal width */
572                 if (BLI_rctf_size_x(&rect_fl) < searchbox_width) {
573                         rect_fl.xmax = rect_fl.xmin + searchbox_width;
574                 }
575
576                 /* copy to int, gets projected if possible too */
577                 BLI_rcti_rctf_copy(&rect_i, &rect_fl);
578
579                 if (butregion->v2d.cur.xmin != butregion->v2d.cur.xmax) {
580                         UI_view2d_view_to_region_rcti(&butregion->v2d, &rect_fl, &rect_i);
581                 }
582
583                 BLI_rcti_translate(&rect_i, butregion->winrct.xmin, butregion->winrct.ymin);
584
585                 winx = WM_window_pixels_x(win);
586                 // winy = WM_window_pixels_y(win);  /* UNUSED */
587                 //wm_window_get_size(win, &winx, &winy);
588
589                 if (rect_i.xmax > winx) {
590                         /* super size */
591                         if (rect_i.xmax > winx + rect_i.xmin) {
592                                 rect_i.xmax = winx;
593                                 rect_i.xmin = 0;
594                         }
595                         else {
596                                 rect_i.xmin -= rect_i.xmax - winx;
597                                 rect_i.xmax = winx;
598                         }
599                 }
600
601                 if (rect_i.ymin < 0) {
602                         int newy1 = but->rect.ymax + ofsy;
603
604                         if (butregion->v2d.cur.xmin != butregion->v2d.cur.xmax)
605                                 newy1 = UI_view2d_view_to_region_y(&butregion->v2d, newy1);
606
607                         newy1 += butregion->winrct.ymin;
608
609                         rect_i.ymax = BLI_rcti_size_y(&rect_i) + newy1;
610                         rect_i.ymin = newy1;
611                 }
612
613                 /* widget rect, in region coords */
614                 data->bbox.xmin = margin;
615                 data->bbox.xmax = BLI_rcti_size_x(&rect_i) + margin;
616                 data->bbox.ymin = margin;
617                 data->bbox.ymax = BLI_rcti_size_y(&rect_i) + margin;
618
619                 /* region bigger for shadow */
620                 ar->winrct.xmin = rect_i.xmin - margin;
621                 ar->winrct.xmax = rect_i.xmax + margin;
622                 ar->winrct.ymin = rect_i.ymin - margin;
623                 ar->winrct.ymax = rect_i.ymax;
624         }
625
626         /* adds subwindow */
627         ED_region_init(C, ar);
628
629         /* notify change and redraw */
630         ED_region_tag_redraw(ar);
631
632         /* prepare search data */
633         if (data->preview) {
634                 data->items.maxitem = data->prv_rows * data->prv_cols;
635         }
636         else {
637                 data->items.maxitem = SEARCH_ITEMS;
638         }
639         data->items.maxstrlen = but->hardmax;
640         data->items.totitem = 0;
641         data->items.names = MEM_callocN(data->items.maxitem * sizeof(void *), "search names");
642         data->items.pointers = MEM_callocN(data->items.maxitem * sizeof(void *), "search pointers");
643         data->items.icons = MEM_callocN(data->items.maxitem * sizeof(int), "search icons");
644         for (i = 0; i < data->items.maxitem; i++)
645                 data->items.names[i] = MEM_callocN(but->hardmax + 1, "search pointers");
646
647         return ar;
648 }
649
650 /**
651  * Similar to Python's `str.title` except...
652  *
653  * - we know words are upper case and ascii only.
654  * - '_' are replaces by spaces.
655  */
656 static void str_tolower_titlecaps_ascii(char *str, const size_t len)
657 {
658         size_t i;
659         bool prev_delim = true;
660
661         for (i = 0; (i < len) && str[i]; i++) {
662                 if (str[i] >= 'A' && str[i] <= 'Z') {
663                         if (prev_delim == false) {
664                                 str[i] += 'a' - 'A';
665                         }
666                 }
667                 else if (str[i] == '_') {
668                         str[i] = ' ';
669                 }
670
671                 prev_delim = ELEM(str[i], ' ') || (str[i] >= '0' && str[i] <= '9');
672         }
673
674 }
675
676 static void ui_searchbox_region_draw_cb__operator(const bContext *UNUSED(C), ARegion *ar)
677 {
678         uiSearchboxData *data = ar->regiondata;
679
680         /* pixel space */
681         wmOrtho2_region_pixelspace(ar);
682
683         if (data->noback == false)
684                 ui_draw_search_back(NULL, NULL, &data->bbox);  /* style not used yet */
685
686         /* draw text */
687         if (data->items.totitem) {
688                 rcti rect;
689                 int a;
690
691                 /* draw items */
692                 for (a = 0; a < data->items.totitem; a++) {
693                         rcti rect_pre, rect_post;
694                         ui_searchbox_butrect(&rect, data, a);
695
696                         rect_pre  = rect;
697                         rect_post = rect;
698
699                         rect_pre.xmax = rect_post.xmin = rect.xmin + ((rect.xmax - rect.xmin) / 4);
700
701                         /* widget itself */
702                         /* NOTE: i18n messages extracting tool does the same, please keep it in sync. */
703                         {
704                                 wmOperatorType *ot = data->items.pointers[a];
705
706                                 int state = (a == data->active) ? UI_ACTIVE : 0;
707                                 char  text_pre[128];
708                                 char *text_pre_p = strstr(ot->idname, "_OT_");
709                                 if (text_pre_p == NULL) {
710                                         text_pre[0] = '\0';
711                                 }
712                                 else {
713                                         int text_pre_len;
714                                         text_pre_p += 1;
715                                         text_pre_len = BLI_strncpy_rlen(
716                                                 text_pre, ot->idname, min_ii(sizeof(text_pre), text_pre_p - ot->idname));
717                                         text_pre[text_pre_len] = ':';
718                                         text_pre[text_pre_len + 1] = '\0';
719                                         str_tolower_titlecaps_ascii(text_pre, sizeof(text_pre));
720                                 }
721
722                                 rect_pre.xmax += 4;  /* sneaky, avoid showing ugly margin */
723                                 ui_draw_menu_item(
724                                         &data->fstyle, &rect_pre, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, text_pre),
725                                         data->items.icons[a], state, false);
726                                 ui_draw_menu_item(&data->fstyle, &rect_post, data->items.names[a], 0, state, data->use_sep);
727                         }
728
729                 }
730                 /* indicate more */
731                 if (data->items.more) {
732                         ui_searchbox_butrect(&rect, data, data->items.maxitem - 1);
733                         glEnable(GL_BLEND);
734                         UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymin - 9, ICON_TRIA_DOWN);
735                         glDisable(GL_BLEND);
736                 }
737                 if (data->items.offset) {
738                         ui_searchbox_butrect(&rect, data, 0);
739                         glEnable(GL_BLEND);
740                         UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymax - 7, ICON_TRIA_UP);
741                         glDisable(GL_BLEND);
742                 }
743         }
744 }
745
746 ARegion *ui_searchbox_create_operator(bContext *C, ARegion *butregion, uiBut *but)
747 {
748         ARegion *ar;
749
750         ar = ui_searchbox_create_generic(C, butregion, but);
751
752         ar->type->draw = ui_searchbox_region_draw_cb__operator;
753
754         return ar;
755 }
756
757 void ui_searchbox_free(bContext *C, ARegion *ar)
758 {
759         ui_region_temp_remove(C, CTX_wm_screen(C), ar);
760 }
761
762 /* sets red alert if button holds a string it can't find */
763 /* XXX weak: search_func adds all partial matches... */
764 void ui_but_search_refresh(uiBut *but)
765 {
766         uiSearchItems *items;
767         int x1;
768
769         /* possibly very large lists (such as ID datablocks) only
770          * only validate string RNA buts (not pointers) */
771         if (but->rnaprop && RNA_property_type(but->rnaprop) != PROP_STRING) {
772                 return;
773         }
774
775         items = MEM_callocN(sizeof(uiSearchItems), "search items");
776
777         /* setup search struct */
778         items->maxitem = 10;
779         items->maxstrlen = 256;
780         items->names = MEM_callocN(items->maxitem * sizeof(void *), "search names");
781         for (x1 = 0; x1 < items->maxitem; x1++)
782                 items->names[x1] = MEM_callocN(but->hardmax + 1, "search names");
783
784         but->search_func(but->block->evil_C, but->search_arg, but->drawstr, items);
785
786         /* only redalert when we are sure of it, this can miss cases when >10 matches */
787         if (items->totitem == 0) {
788                 UI_but_flag_enable(but, UI_BUT_REDALERT);
789         }
790         else if (items->more == 0) {
791                 if (UI_search_items_find_index(items, but->drawstr) == -1) {
792                         UI_but_flag_enable(but, UI_BUT_REDALERT);
793                 }
794         }
795
796         for (x1 = 0; x1 < items->maxitem; x1++) {
797                 MEM_freeN(items->names[x1]);
798         }
799         MEM_freeN(items->names);
800         MEM_freeN(items);
801 }
802
803 /** \} */