dcab175c733db319933c52c2ffa43ba0a720f037
[blender.git] / source / blender / editors / interface / interface_regions.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_regions.c
27  *  \ingroup edinterface
28  */
29
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 #include "BLI_blenlib.h"
43 #include "BLI_utildefines.h"
44 #include "BLI_dynstr.h"
45 #include "BLI_ghash.h"
46
47 #include "BKE_context.h"
48 #include "BKE_screen.h"
49
50 #include "WM_api.h"
51 #include "WM_types.h"
52 #include "wm_draw.h"
53 #include "wm_subwindow.h"
54 #include "wm_window.h"
55
56 #include "RNA_access.h"
57
58 #include "BIF_gl.h"
59
60 #include "UI_interface.h"
61 #include "UI_interface_icons.h"
62 #include "UI_view2d.h"
63
64 #include "BLF_api.h"
65 #include "BLF_translation.h"
66
67 #include "ED_screen.h"
68
69 #include "interface_intern.h"
70
71 #define MENU_SEPR_HEIGHT    6
72 #define B_NOP               -1
73 #define MENU_SHADOW_SIDE    8
74 #define MENU_SHADOW_BOTTOM  10
75 #define MENU_TOP            8
76
77 /*********************** Menu Data Parsing ********************* */
78
79 typedef struct MenuEntry {
80         const char *str;
81         int retval;
82         int icon;
83         int sepr;
84 } MenuEntry;
85
86 typedef struct MenuData {
87         const char *instr;
88         const char *title;
89         int titleicon;
90         
91         MenuEntry *items;
92         int nitems, itemssize;
93 } MenuData;
94
95 static MenuData *menudata_new(const char *instr)
96 {
97         MenuData *md = MEM_mallocN(sizeof(*md), "MenuData");
98
99         md->instr = instr;
100         md->title = NULL;
101         md->titleicon = 0;
102         md->items = NULL;
103         md->nitems = md->itemssize = 0;
104         
105         return md;
106 }
107
108 static void menudata_set_title(MenuData *md, const char *title, int titleicon)
109 {
110         if (!md->title)
111                 md->title = title;
112         if (!md->titleicon)
113                 md->titleicon = titleicon;
114 }
115
116 static void menudata_add_item(MenuData *md, const char *str, int retval, int icon, int sepr)
117 {
118         if (md->nitems == md->itemssize) {
119                 int nsize = md->itemssize ? (md->itemssize << 1) : 1;
120                 MenuEntry *oitems = md->items;
121                 
122                 md->items = MEM_mallocN(nsize * sizeof(*md->items), "md->items");
123                 if (oitems) {
124                         memcpy(md->items, oitems, md->nitems * sizeof(*md->items));
125                         MEM_freeN(oitems);
126                 }
127                 
128                 md->itemssize = nsize;
129         }
130         
131         md->items[md->nitems].str = str;
132         md->items[md->nitems].retval = retval;
133         md->items[md->nitems].icon = icon;
134         md->items[md->nitems].sepr = sepr;
135         md->nitems++;
136 }
137
138 static void menudata_free(MenuData *md)
139 {
140         MEM_freeN((void *)md->instr);
141         if (md->items) {
142                 MEM_freeN(md->items);
143         }
144         MEM_freeN(md);
145 }
146
147 /**
148  * Parse menu description strings, string is of the
149  * form "[sss%t|]{(sss[%xNN]|), (%l|), (sss%l|)}", ssss%t indicates the
150  * menu title, sss or sss%xNN indicates an option,
151  * if %xNN is given then NN is the return value if
152  * that option is selected otherwise the return value
153  * is the index of the option (starting with 1). %l
154  * indicates a seperator, sss%l indicates a label and
155  * new column.
156  *
157  * \param str String to be parsed.
158  * \retval new menudata structure, free with menudata_free()
159  */
160 static MenuData *decompose_menu_string(const char *str)
161 {
162         char *instr = BLI_strdup(str);
163         MenuData *md = menudata_new(instr);
164         const char *nitem = NULL;
165         char *s = instr;
166         int nicon = 0, nretval = 1, nitem_is_title = 0, nitem_is_sepr = 0;
167         
168         while (1) {
169                 char c = *s;
170
171                 if (c == '%') {
172                         if (s[1] == 'x') {
173                                 nretval = atoi(s + 2);
174
175                                 *s = '\0';
176                                 s++;
177                         }
178                         else if (s[1] == 't') {
179                                 nitem_is_title = (s != instr); /* check for empty title */
180
181                                 *s = '\0';
182                                 s++;
183                         }
184                         else if (s[1] == 'l') {
185                                 nitem_is_sepr = 1;
186                                 if (!nitem) nitem = "";
187
188                                 *s = '\0';
189                                 s++;
190                         }
191                         else if (s[1] == 'i') {
192                                 nicon = atoi(s + 2);
193                                 
194                                 *s = '\0';
195                                 s++;
196                         }
197                 }
198                 else if (c == '|' || c == '\n' || c == '\0') {
199                         if (nitem) {
200                                 *s = '\0';
201
202                                 if (nitem_is_title) {
203                                         menudata_set_title(md, nitem, nicon);
204                                         nitem_is_title = 0;
205                                 }
206                                 else if (nitem_is_sepr) {
207                                         /* prevent separator to get a value */
208                                         menudata_add_item(md, nitem, -1, nicon, 1);
209                                         nretval = md->nitems + 1;
210                                         nitem_is_sepr = 0;
211                                 }
212                                 else {
213                                         menudata_add_item(md, nitem, nretval, nicon, 0);
214                                         nretval = md->nitems + 1;
215                                 } 
216                                 
217                                 nitem = NULL;
218                                 nicon = 0;
219                         }
220                         
221                         if (c == '\0') {
222                                 break;
223                         }
224                 }
225                 else if (!nitem) {
226                         nitem = s;
227                 }
228
229                 s++;
230         }
231         
232         return md;
233 }
234
235 void ui_set_name_menu(uiBut *but, int value)
236 {
237         MenuData *md;
238         int i;
239         
240         md = decompose_menu_string(but->str);
241         for (i = 0; i < md->nitems; i++) {
242                 if (md->items[i].retval == value) {
243                         BLI_strncpy(but->drawstr, md->items[i].str, sizeof(but->drawstr));
244                         break;
245                 }
246         }
247         
248         menudata_free(md);
249 }
250
251 int ui_step_name_menu(uiBut *but, int step)
252 {
253         MenuData *md;
254         int value = ui_get_but_val(but);
255         int i;
256         
257         md = decompose_menu_string(but->str);
258         for (i = 0; i < md->nitems; i++)
259                 if (md->items[i].retval == value)
260                         break;
261         
262         if (step == 1) {
263                 /* skip separators */
264                 for (; i < md->nitems - 1; i++) {
265                         if (md->items[i + 1].retval != -1) {
266                                 value = md->items[i + 1].retval;
267                                 break;
268                         }
269                 }
270         }
271         else {
272                 if (i > 0) {
273                         /* skip separators */
274                         for (; i > 0; i--) {
275                                 if (md->items[i - 1].retval != -1) {
276                                         value = md->items[i - 1].retval;
277                                         break;
278                                 }
279                         }
280                 }
281         }
282         
283         menudata_free(md);
284                 
285         return value;
286 }
287
288
289 /******************** Creating Temporary regions ******************/
290
291 static ARegion *ui_add_temporary_region(bScreen *sc)
292 {
293         ARegion *ar;
294
295         ar = MEM_callocN(sizeof(ARegion), "area region");
296         BLI_addtail(&sc->regionbase, ar);
297
298         ar->regiontype = RGN_TYPE_TEMPORARY;
299         ar->alignment = RGN_ALIGN_FLOAT;
300
301         return ar;
302 }
303
304 static void ui_remove_temporary_region(bContext *C, bScreen *sc, ARegion *ar)
305 {
306         if (CTX_wm_window(C))
307                 wm_draw_region_clear(CTX_wm_window(C), ar);
308
309         ED_region_exit(C, ar);
310         BKE_area_region_free(NULL, ar);     /* NULL: no spacetype */
311         BLI_freelinkN(&sc->regionbase, ar);
312 }
313
314 /************************* Creating Tooltips **********************/
315
316 typedef enum {
317         UI_TIP_LC_MAIN,
318         UI_TIP_LC_NORMAL,
319         UI_TIP_LC_PYTHON,
320         UI_TIP_LC_ALERT,
321         UI_TIP_LC_SUBMENU
322 } uiTooltipLineColor;
323 #define UI_TIP_LC_MAX 5
324
325 #define MAX_TOOLTIP_LINES 8
326 typedef struct uiTooltipData {
327         rcti bbox;
328         uiFontStyle fstyle;
329         char lines[MAX_TOOLTIP_LINES][512];
330         uiTooltipLineColor color_id[MAX_TOOLTIP_LINES];
331         int totline;
332         int toth, spaceh, lineh;
333 } uiTooltipData;
334
335 static void rgb_tint(float col[3],
336                      float h, float h_strength,
337                      float v, float v_strength)
338 {
339         float col_hsv_from[3];
340         float col_hsv_to[3];
341
342         rgb_to_hsv_v(col, col_hsv_from);
343
344         col_hsv_to[0] = h;
345         col_hsv_to[1] = h_strength;
346         col_hsv_to[2] = (col_hsv_from[2] * (1.0f - v_strength)) + (v * v_strength);
347
348         hsv_to_rgb_v(col_hsv_to, col);
349 }
350
351 static void ui_tooltip_region_draw_cb(const bContext *UNUSED(C), ARegion *ar)
352 {
353         uiTooltipData *data = ar->regiondata;
354         uiWidgetColors *theme = ui_tooltip_get_theme();
355         rcti bbox = data->bbox;
356         float tip_colors[UI_TIP_LC_MAX][3];
357
358         float *main_color    = tip_colors[UI_TIP_LC_MAIN]; /* the color from the theme */
359         float *normal_color  = tip_colors[UI_TIP_LC_NORMAL];
360         float *python_color  = tip_colors[UI_TIP_LC_PYTHON];
361         float *alert_color   = tip_colors[UI_TIP_LC_ALERT];
362         float *submenu_color = tip_colors[UI_TIP_LC_SUBMENU];
363
364         float background_color[3];
365         float tone_bg;
366         int i;
367
368         /* draw background */
369         ui_draw_tooltip_background(UI_GetStyle(), NULL, &bbox);
370
371         /* set background_color */
372         rgb_uchar_to_float(background_color, (const unsigned char *)theme->inner);
373
374         /* calculate normal_color */
375         rgb_uchar_to_float(main_color, (const unsigned char *)theme->text);
376         copy_v3_v3(normal_color, main_color);
377         copy_v3_v3(python_color, main_color);
378         copy_v3_v3(alert_color, main_color);
379         copy_v3_v3(submenu_color, main_color);
380
381         /* find the brightness difference between background and text colors */
382         
383         tone_bg = rgb_to_grayscale(background_color);
384         /* tone_fg = rgb_to_grayscale(main_color); */
385
386         rgb_tint(normal_color, 0.0f, 0.0f, tone_bg, 0.3f);   /* a shade darker (to bg) */
387         rgb_tint(python_color, 0.666f, 0.25f, tone_bg, 0.3f); /* blue */
388         rgb_tint(alert_color, 0.0f, 0.8f, tone_bg, 0.1f);    /* bright red */
389         rgb_tint(submenu_color, 0.0f, 0.0f, tone_bg, 0.3f);  /* a shade darker (to bg) */
390
391         /* draw text */
392         uiStyleFontSet(&data->fstyle);
393
394         bbox.ymax = bbox.ymax - 0.5f * ((bbox.ymax - bbox.ymin) - data->toth);
395         bbox.ymin = bbox.ymax - data->lineh;
396
397         for (i = 0; i < data->totline; i++) {
398                 glColor3fv(tip_colors[data->color_id[i]]);
399                 uiStyleFontDraw(&data->fstyle, &bbox, data->lines[i]);
400                 bbox.ymin -= data->lineh + data->spaceh;
401                 bbox.ymax -= data->lineh + data->spaceh;
402         }
403 }
404
405 static void ui_tooltip_region_free_cb(ARegion *ar)
406 {
407         uiTooltipData *data;
408
409         data = ar->regiondata;
410         MEM_freeN(data);
411         ar->regiondata = NULL;
412 }
413
414 ARegion *ui_tooltip_create(bContext *C, ARegion *butregion, uiBut *but)
415 {
416         uiStyle *style = UI_GetStyle();
417         static ARegionType type;
418         ARegion *ar;
419         uiTooltipData *data;
420 /*      IDProperty *prop;*/
421         char buf[512];
422         float fonth, fontw, aspect = but->block->aspect;
423         int winx, winy, ofsx, ofsy, w, h, a;
424         rctf rect_fl;
425         rcti rect_i;
426
427         const int nbr_info = 6;
428         uiStringInfo but_tip = {BUT_GET_TIP, NULL};
429         uiStringInfo enum_label = {BUT_GET_RNAENUM_LABEL, NULL};
430         uiStringInfo enum_tip = {BUT_GET_RNAENUM_TIP, NULL};
431         uiStringInfo op_keymap = {BUT_GET_OP_KEYMAP, NULL};
432         uiStringInfo rna_struct = {BUT_GET_RNASTRUCT_IDENTIFIER, NULL};
433         uiStringInfo rna_prop = {BUT_GET_RNAPROP_IDENTIFIER, NULL};
434
435         if (but->flag & UI_BUT_NO_TOOLTIP)
436                 return NULL;
437
438         /* create tooltip data */
439         data = MEM_callocN(sizeof(uiTooltipData), "uiTooltipData");
440
441         uiButGetStrInfo(C, but, nbr_info, &but_tip, &enum_label, &enum_tip, &op_keymap, &rna_struct, &rna_prop);
442
443         /* special case, enum rna buttons only have enum item description,
444          * use general enum description too before the specific one */
445 #if 0
446         if (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_ENUM) {
447                 const char *descr = RNA_property_description(but->rnaprop);
448                 if (descr && descr[0]) {
449                         BLI_strncpy(data->lines[data->totline], descr, sizeof(data->lines[0]));
450                         data->color_id[data->totline] = UI_TIP_LC_MAIN;
451                         data->totline++;
452                 }
453
454                 if (ELEM(but->type, ROW, MENU)) {
455                         EnumPropertyItem *item;
456                         int totitem, free;
457                         int value = (but->type == ROW) ? (int)but->hardmax : (int)ui_get_but_val(but);
458
459                         RNA_property_enum_items_gettexted(C, &but->rnapoin, but->rnaprop, &item, &totitem, &free);
460
461                         for (i = 0; i < totitem; i++) {
462                                 if (item[i].identifier[0] && item[i].value == value) {
463                                         if (item[i].description && item[i].description[0]) {
464                                                 BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), "%s: %s",
465                                                              item[i].name, item[i].description);
466                                                 data->color_id[data->totline] = UI_TIP_LC_SUBMENU;
467                                                 data->totline++;
468                                         }
469                                         break;
470                                 }
471                         }
472
473                         if (free) {
474                                 MEM_freeN(item);
475                         }
476                 }
477         }
478         
479         if (but->tip && but->tip[0] != '\0') {
480                 BLI_strncpy(data->lines[data->totline], but->tip, sizeof(data->lines[0]));
481                 data->color_id[data->totline] = UI_TIP_LC_MAIN;
482                 data->totline++;
483         }
484 #else
485         /* Tip */
486         if (but_tip.strinfo) {
487                 BLI_strncpy(data->lines[data->totline], but_tip.strinfo, sizeof(data->lines[0]));
488                 data->color_id[data->totline] = UI_TIP_LC_MAIN;
489                 data->totline++;
490         }
491         /* Enum item label & tip */
492         if (enum_label.strinfo && enum_tip.strinfo) {
493                 BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]),
494                              "%s: %s", enum_label.strinfo, enum_tip.strinfo);
495                 data->color_id[data->totline] = UI_TIP_LC_SUBMENU;
496                 data->totline++;
497         }
498 #endif
499
500 #if 0
501         if (but->optype && !(but->block->flag & UI_BLOCK_LOOP)) {
502                 /* operator keymap (not menus, they already have it) */
503                 prop = (but->opptr) ? but->opptr->data : NULL;
504
505                 if (WM_key_event_operator_string(C, but->optype->idname, but->opcontext, prop, TRUE,
506                                                  buf, sizeof(buf)))
507                 {
508                         BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Shortcut: %s"), buf);
509                         data->color_id[data->totline] = UI_TIP_LC_NORMAL;
510                         data->totline++;
511                 }
512         }
513 #else
514         /* Op shortcut */
515         if (op_keymap.strinfo) {
516                 BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Shortcut: %s"), op_keymap.strinfo);
517                 data->color_id[data->totline] = UI_TIP_LC_NORMAL;
518                 data->totline++;
519         }
520 #endif
521
522         if (ELEM3(but->type, TEX, IDPOIN, SEARCH_MENU)) {
523                 /* full string */
524                 ui_get_but_string(but, buf, sizeof(buf));
525                 if (buf[0]) {
526                         BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Value: %s"), buf);
527                         data->color_id[data->totline] = UI_TIP_LC_NORMAL;
528                         data->totline++;
529                 }
530         }
531
532         if (but->rnaprop) {
533                 int unit_type = uiButGetUnitType(but);
534                 
535                 if (unit_type == PROP_UNIT_ROTATION) {
536                         if (RNA_property_type(but->rnaprop) == PROP_FLOAT) {
537                                 float value = RNA_property_array_check(but->rnaprop) ?
538                                                   RNA_property_float_get_index(&but->rnapoin, but->rnaprop, but->rnaindex) :
539                                                   RNA_property_float_get(&but->rnapoin, but->rnaprop);
540                                 BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Radians: %f"), value);
541                                 data->color_id[data->totline] = UI_TIP_LC_NORMAL;
542                                 data->totline++;
543                         }
544                 }
545                 
546                 if (but->flag & UI_BUT_DRIVEN) {
547                         if (ui_but_anim_expression_get(but, buf, sizeof(buf))) {
548                                 /* expression */
549                                 BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Expression: %s"), buf);
550                                 data->color_id[data->totline] = UI_TIP_LC_NORMAL;
551                                 data->totline++;
552                         }
553                 }
554 #if 0
555                 /* rna info */
556                 if ((U.flag & USER_TOOLTIPS_PYTHON) == 0) {
557                         BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Python: %s.%s"),
558                                      RNA_struct_identifier(but->rnapoin.type), RNA_property_identifier(but->rnaprop));
559                         data->color_id[data->totline] = UI_TIP_LC_PYTHON;
560                         data->totline++;
561                 }
562 #endif
563                 if (but->rnapoin.id.data) {
564                         ID *id = but->rnapoin.id.data;
565                         if (id->lib && id->lib->name) {
566                                 BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Library: %s"), id->lib->name);
567                                 data->color_id[data->totline] = UI_TIP_LC_NORMAL;
568                                 data->totline++;
569                         }
570                 }
571         }
572         else if (but->optype) {
573                 PointerRNA *opptr;
574                 char *str;
575                 opptr = uiButGetOperatorPtrRNA(but); /* allocated when needed, the button owns it */
576
577                 /* so the context is passed to itemf functions (some py itemf functions use it) */
578                 WM_operator_properties_sanitize(opptr, FALSE);
579
580                 str = WM_operator_pystring(C, but->optype, opptr, 0);
581
582                 /* operator info */
583                 if ((U.flag & USER_TOOLTIPS_PYTHON) == 0) {
584                         BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Python: %s"), str);
585                         data->color_id[data->totline] = UI_TIP_LC_PYTHON;
586                         data->totline++;
587                 }
588
589                 MEM_freeN(str);
590
591                 /* second check if we are disabled - why */
592                 if (but->flag & UI_BUT_DISABLED) {
593                         const char *poll_msg;
594                         CTX_wm_operator_poll_msg_set(C, NULL);
595                         WM_operator_poll_context(C, but->optype, but->opcontext);
596                         poll_msg = CTX_wm_operator_poll_msg_get(C);
597                         if (poll_msg) {
598                                 BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Disabled: %s"), poll_msg);
599                                 data->color_id[data->totline] = UI_TIP_LC_ALERT; /* alert */
600                                 data->totline++;
601                         }
602                 }
603         }
604 #if 0
605         else if (ELEM(but->type, MENU, PULLDOWN)) {
606                 if ((U.flag & USER_TOOLTIPS_PYTHON) == 0) {
607                         MenuType *mt = uiButGetMenuType(but);
608                         if (mt) {
609                                 BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Python: %s"), mt->idname);
610                                 data->color_id[data->totline] = UI_TIP_LC_PYTHON;
611                                 data->totline++;
612                         }
613                 }
614         }
615 #else
616         if ((U.flag & USER_TOOLTIPS_PYTHON) == 0 && !but->optype && rna_struct.strinfo) {
617                 if (rna_prop.strinfo)
618                         /* Struct and prop */
619                         BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]),
620                                      TIP_("Python: %s.%s"), rna_struct.strinfo, rna_prop.strinfo);
621                 else
622                         /* Only struct (e.g. menus) */
623                         BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Python: %s"), rna_struct.strinfo);
624                 data->color_id[data->totline] = UI_TIP_LC_PYTHON;
625                 data->totline++;
626         }
627 #endif
628
629         /* Free strinfo's... */
630         if (but_tip.strinfo)
631                 MEM_freeN(but_tip.strinfo);
632         if (enum_label.strinfo)
633                 MEM_freeN(enum_label.strinfo);
634         if (enum_tip.strinfo)
635                 MEM_freeN(enum_tip.strinfo);
636         if (op_keymap.strinfo)
637                 MEM_freeN(op_keymap.strinfo);
638         if (rna_struct.strinfo)
639                 MEM_freeN(rna_struct.strinfo);
640         if (rna_prop.strinfo)
641                 MEM_freeN(rna_prop.strinfo);
642
643         assert(data->totline < MAX_TOOLTIP_LINES);
644         
645         if (data->totline == 0) {
646                 MEM_freeN(data);
647                 return NULL;
648         }
649
650         /* create area region */
651         ar = ui_add_temporary_region(CTX_wm_screen(C));
652
653         memset(&type, 0, sizeof(ARegionType));
654         type.draw = ui_tooltip_region_draw_cb;
655         type.free = ui_tooltip_region_free_cb;
656         ar->type = &type;
657         
658         /* set font, get bb */
659         data->fstyle = style->widget; /* copy struct */
660         data->fstyle.align = UI_STYLE_TEXT_CENTER;
661         uiStyleFontSet(&data->fstyle);
662
663         /* these defines may need to be tweaked depending on font */
664 #define TIP_MARGIN_Y 2
665 #define TIP_BORDER_X 16.0f
666 #define TIP_BORDER_Y 6.0f
667
668         h = BLF_height_max(data->fstyle.uifont_id);
669
670         for (a = 0, fontw = 0, fonth = 0; a < data->totline; a++) {
671                 w = BLF_width(data->fstyle.uifont_id, data->lines[a]);
672                 fontw = MAX2(fontw, w);
673                 fonth += (a == 0) ? h : h + TIP_MARGIN_Y;
674         }
675
676         fontw *= aspect;
677
678         ar->regiondata = data;
679
680         data->toth = fonth;
681         data->lineh = h;
682         data->spaceh = TIP_MARGIN_Y;
683
684
685         /* compute position */
686         ofsx = (but->block->panel) ? but->block->panel->ofsx : 0;
687         ofsy = (but->block->panel) ? but->block->panel->ofsy : 0;
688
689         rect_fl.xmin = (but->rect.xmin + but->rect.xmax) * 0.5f + ofsx - (TIP_BORDER_X * aspect);
690         rect_fl.xmax = rect_fl.xmin + fontw + (TIP_BORDER_X * aspect);
691         rect_fl.ymax = but->rect.ymin + ofsy - (TIP_BORDER_Y * aspect);
692         rect_fl.ymin = rect_fl.ymax - fonth * aspect - (TIP_BORDER_Y * aspect);
693         
694 #undef TIP_MARGIN_Y
695 #undef TIP_BORDER_X
696 #undef TIP_BORDER_Y
697
698         /* copy to int, gets projected if possible too */
699         BLI_rcti_rctf_copy(&rect_i, &rect_fl);
700         
701         if (butregion) {
702                 /* XXX temp, region v2ds can be empty still */
703                 if (butregion->v2d.cur.xmin != butregion->v2d.cur.xmax) {
704                         UI_view2d_to_region_no_clip(&butregion->v2d, rect_fl.xmin, rect_fl.ymin, &rect_i.xmin, &rect_i.ymin);
705                         UI_view2d_to_region_no_clip(&butregion->v2d, rect_fl.xmax, rect_fl.ymax, &rect_i.xmax, &rect_i.ymax);
706                 }
707
708                 BLI_rcti_translate(&rect_i, butregion->winrct.xmin, butregion->winrct.ymin);
709         }
710
711         wm_window_get_size(CTX_wm_window(C), &winx, &winy);
712
713         if (rect_i.xmax > winx) {
714                 /* super size */
715                 if (rect_i.xmax > winx + rect_i.xmin) {
716                         rect_i.xmax = winx;
717                         rect_i.xmin = 0;
718                 }
719                 else {
720                         rect_i.xmin -= rect_i.xmax - winx;
721                         rect_i.xmax = winx;
722                 }
723         }
724         /* ensure at least 5 px above screen bounds
725          * 25 is just a guess to be above the menu item */
726         if (rect_i.ymin < 5) {
727                 rect_i.ymax += (-rect_i.ymin) + 30;
728                 rect_i.ymin = 30;
729         }
730
731         /* widget rect, in region coords */
732         data->bbox.xmin = MENU_SHADOW_SIDE;
733         data->bbox.xmax = rect_i.xmax - rect_i.xmin + MENU_SHADOW_SIDE;
734         data->bbox.ymin = MENU_SHADOW_BOTTOM;
735         data->bbox.ymax = rect_i.ymax - rect_i.ymin + MENU_SHADOW_BOTTOM;
736         
737         /* region bigger for shadow */
738         ar->winrct.xmin = rect_i.xmin - MENU_SHADOW_SIDE;
739         ar->winrct.xmax = rect_i.xmax + MENU_SHADOW_SIDE;
740         ar->winrct.ymin = rect_i.ymin - MENU_SHADOW_BOTTOM;
741         ar->winrct.ymax = rect_i.ymax + MENU_TOP;
742
743         /* adds subwindow */
744         ED_region_init(C, ar);
745         
746         /* notify change and redraw */
747         ED_region_tag_redraw(ar);
748
749         return ar;
750 }
751
752 void ui_tooltip_free(bContext *C, ARegion *ar)
753 {
754         ui_remove_temporary_region(C, CTX_wm_screen(C), ar);
755 }
756
757
758 /************************* Creating Search Box **********************/
759
760 struct uiSearchItems {
761         int maxitem, totitem, maxstrlen;
762         
763         int offset, offset_i; /* offset for inserting in array */
764         int more;  /* flag indicating there are more items */
765         
766         char **names;
767         void **pointers;
768         int *icons;
769
770         AutoComplete *autocpl;
771         void *active;
772 };
773
774 typedef struct uiSearchboxData {
775         rcti bbox;
776         uiFontStyle fstyle;
777         uiSearchItems items;
778         int active;     /* index in items array */
779         int noback;     /* when menu opened with enough space for this */
780         int preview;    /* draw thumbnail previews, rather than list */
781         int prv_rows, prv_cols;
782 } uiSearchboxData;
783
784 #define SEARCH_ITEMS    10
785
786 /* exported for use by search callbacks */
787 /* returns zero if nothing to add */
788 int uiSearchItemAdd(uiSearchItems *items, const char *name, void *poin, int iconid)
789 {
790         /* hijack for autocomplete */
791         if (items->autocpl) {
792                 autocomplete_do_name(items->autocpl, name);
793                 return 1;
794         }
795         
796         /* hijack for finding active item */
797         if (items->active) {
798                 if (poin == items->active)
799                         items->offset_i = items->totitem;
800                 items->totitem++;
801                 return 1;
802         }
803         
804         if (items->totitem >= items->maxitem) {
805                 items->more = 1;
806                 return 0;
807         }
808         
809         /* skip first items in list */
810         if (items->offset_i > 0) {
811                 items->offset_i--;
812                 return 1;
813         }
814         
815         if (items->names)
816                 BLI_strncpy(items->names[items->totitem], name, items->maxstrlen);
817         if (items->pointers)
818                 items->pointers[items->totitem] = poin;
819         if (items->icons)
820                 items->icons[items->totitem] = iconid;
821         
822         items->totitem++;
823         
824         return 1;
825 }
826
827 int uiSearchBoxhHeight(void)
828 {
829         return SEARCH_ITEMS * UI_UNIT_Y + 2 * MENU_TOP;
830 }
831
832 /* ar is the search box itself */
833 static void ui_searchbox_select(bContext *C, ARegion *ar, uiBut *but, int step)
834 {
835         uiSearchboxData *data = ar->regiondata;
836         
837         /* apply step */
838         data->active += step;
839         
840         if (data->items.totitem == 0)
841                 data->active = 0;
842         else if (data->active > data->items.totitem) {
843                 if (data->items.more) {
844                         data->items.offset++;
845                         data->active = data->items.totitem;
846                         ui_searchbox_update(C, ar, but, 0);
847                 }
848                 else
849                         data->active = data->items.totitem;
850         }
851         else if (data->active < 1) {
852                 if (data->items.offset) {
853                         data->items.offset--;
854                         data->active = 1;
855                         ui_searchbox_update(C, ar, but, 0);
856                 }
857                 else if (data->active < 0)
858                         data->active = 0;
859         }
860         
861         ED_region_tag_redraw(ar);
862 }
863
864 static void ui_searchbox_butrect(rcti *rect, uiSearchboxData *data, int itemnr)
865 {
866         /* thumbnail preview */
867         if (data->preview) {
868                 int buth = (data->bbox.ymax - data->bbox.ymin - 2 * MENU_TOP) / data->prv_rows;
869                 int butw = (data->bbox.xmax - data->bbox.xmin) / data->prv_cols;
870                 int row, col;
871                 
872                 *rect = data->bbox;
873                 
874                 col = itemnr % data->prv_cols;
875                 row = itemnr / data->prv_cols;
876                 
877                 rect->xmin += col * butw;
878                 rect->xmax = rect->xmin + butw;
879                 
880                 rect->ymax = data->bbox.ymax - MENU_TOP - (row * buth);
881                 rect->ymin = rect->ymax - buth;
882         }
883         /* list view */
884         else {
885                 int buth = (data->bbox.ymax - data->bbox.ymin - 2 * MENU_TOP) / SEARCH_ITEMS;
886                 
887                 *rect = data->bbox;
888                 rect->xmin = data->bbox.xmin + 3.0f;
889                 rect->xmax = data->bbox.xmax - 3.0f;
890                 
891                 rect->ymax = data->bbox.ymax - MENU_TOP - itemnr * buth;
892                 rect->ymin = rect->ymax - buth;
893         }
894         
895 }
896
897 /* x and y in screencoords */
898 int ui_searchbox_inside(ARegion *ar, int x, int y)
899 {
900         uiSearchboxData *data = ar->regiondata;
901         
902         return(BLI_in_rcti(&data->bbox, x - ar->winrct.xmin, y - ar->winrct.ymin));
903 }
904
905 /* string validated to be of correct length (but->hardmax) */
906 void ui_searchbox_apply(uiBut *but, ARegion *ar)
907 {
908         uiSearchboxData *data = ar->regiondata;
909
910         but->func_arg2 = NULL;
911         
912         if (data->active) {
913                 char *name = data->items.names[data->active - 1];
914                 char *cpoin = strchr(name, '|');
915                 
916                 if (cpoin) cpoin[0] = 0;
917                 BLI_strncpy(but->editstr, name, data->items.maxstrlen);
918                 if (cpoin) cpoin[0] = '|';
919                 
920                 but->func_arg2 = data->items.pointers[data->active - 1];
921         }
922 }
923
924 void ui_searchbox_event(bContext *C, ARegion *ar, uiBut *but, wmEvent *event)
925 {
926         uiSearchboxData *data = ar->regiondata;
927         
928         switch (event->type) {
929                 case WHEELUPMOUSE:
930                 case UPARROWKEY:
931                         ui_searchbox_select(C, ar, but, -1);
932                         break;
933                 case WHEELDOWNMOUSE:
934                 case DOWNARROWKEY:
935                         ui_searchbox_select(C, ar, but, 1);
936                         break;
937                 case MOUSEMOVE:
938                         if (BLI_in_rcti(&ar->winrct, event->x, event->y)) {
939                                 rcti rect;
940                                 int a;
941                                 
942                                 for (a = 0; a < data->items.totitem; a++) {
943                                         ui_searchbox_butrect(&rect, data, a);
944                                         if (BLI_in_rcti(&rect, event->x - ar->winrct.xmin, event->y - ar->winrct.ymin)) {
945                                                 if (data->active != a + 1) {
946                                                         data->active = a + 1;
947                                                         ui_searchbox_select(C, ar, but, 0);
948                                                         break;
949                                                 }
950                                         }
951                                 }
952                         }
953                         break;
954         }
955 }
956
957 /* ar is the search box itself */
958 void ui_searchbox_update(bContext *C, ARegion *ar, uiBut *but, int reset)
959 {
960         uiSearchboxData *data = ar->regiondata;
961         
962         /* reset vars */
963         data->items.totitem = 0;
964         data->items.more = 0;
965         if (reset == 0) {
966                 data->items.offset_i = data->items.offset;
967         }
968         else {
969                 data->items.offset_i = data->items.offset = 0;
970                 data->active = 0;
971                 
972                 /* handle active */
973                 if (but->search_func && but->func_arg2) {
974                         data->items.active = but->func_arg2;
975                         but->search_func(C, but->search_arg, but->editstr, &data->items);
976                         data->items.active = NULL;
977                         
978                         /* found active item, calculate real offset by centering it */
979                         if (data->items.totitem) {
980                                 /* first case, begin of list */
981                                 if (data->items.offset_i < data->items.maxitem) {
982                                         data->active = data->items.offset_i + 1;
983                                         data->items.offset_i = 0;
984                                 }
985                                 else {
986                                         /* second case, end of list */
987                                         if (data->items.totitem - data->items.offset_i <= data->items.maxitem) {
988                                                 data->active = 1 + data->items.offset_i - data->items.totitem + data->items.maxitem;
989                                                 data->items.offset_i = data->items.totitem - data->items.maxitem;
990                                         }
991                                         else {
992                                                 /* center active item */
993                                                 data->items.offset_i -= data->items.maxitem / 2;
994                                                 data->active = 1 + data->items.maxitem / 2;
995                                         }
996                                 }
997                         }
998                         data->items.offset = data->items.offset_i;
999                         data->items.totitem = 0;
1000                 }
1001         }
1002         
1003         /* callback */
1004         if (but->search_func)
1005                 but->search_func(C, but->search_arg, but->editstr, &data->items);
1006         
1007         /* handle case where editstr is equal to one of items */
1008         if (reset && data->active == 0) {
1009                 int a;
1010                 
1011                 for (a = 0; a < data->items.totitem; a++) {
1012                         char *cpoin = strchr(data->items.names[a], '|');
1013                         
1014                         if (cpoin) cpoin[0] = 0;
1015                         if (0 == strcmp(but->editstr, data->items.names[a]))
1016                                 data->active = a + 1;
1017                         if (cpoin) cpoin[0] = '|';
1018                 }
1019                 if (data->items.totitem == 1 && but->editstr[0])
1020                         data->active = 1;
1021         }
1022
1023         /* validate selected item */
1024         ui_searchbox_select(C, ar, but, 0);
1025         
1026         ED_region_tag_redraw(ar);
1027 }
1028
1029 void ui_searchbox_autocomplete(bContext *C, ARegion *ar, uiBut *but, char *str)
1030 {
1031         uiSearchboxData *data = ar->regiondata;
1032
1033         if (str[0]) {
1034                 data->items.autocpl = autocomplete_begin(str, ui_get_but_string_max_length(but));
1035
1036                 but->search_func(C, but->search_arg, but->editstr, &data->items);
1037
1038                 autocomplete_end(data->items.autocpl, str);
1039                 data->items.autocpl = NULL;
1040         }
1041 }
1042
1043 static void ui_searchbox_region_draw_cb(const bContext *UNUSED(C), ARegion *ar)
1044 {
1045         uiSearchboxData *data = ar->regiondata;
1046         
1047         /* pixel space */
1048         wmOrtho2(-0.01f, ar->winx - 0.01f, -0.01f, ar->winy - 0.01f);
1049
1050         if (!data->noback)
1051                 ui_draw_search_back(NULL, NULL, &data->bbox);  /* style not used yet */
1052         
1053         /* draw text */
1054         if (data->items.totitem) {
1055                 rcti rect;
1056                 int a;
1057                 
1058                 if (data->preview) {
1059                         /* draw items */
1060                         for (a = 0; a < data->items.totitem; a++) {
1061                                 ui_searchbox_butrect(&rect, data, a);
1062                                 
1063                                 /* widget itself */
1064                                 if (data->preview)
1065                                         ui_draw_preview_item(&data->fstyle, &rect, data->items.names[a], data->items.icons[a], (a + 1) == data->active ? UI_ACTIVE : 0);
1066                                 else 
1067                                         ui_draw_menu_item(&data->fstyle, &rect, data->items.names[a], data->items.icons[a], (a + 1) == data->active ? UI_ACTIVE : 0);
1068                         }
1069                         
1070                         /* indicate more */
1071                         if (data->items.more) {
1072                                 ui_searchbox_butrect(&rect, data, data->items.maxitem - 1);
1073                                 glEnable(GL_BLEND);
1074                                 UI_icon_draw(rect.xmax - 18, rect.ymin - 7, ICON_TRIA_DOWN);
1075                                 glDisable(GL_BLEND);
1076                         }
1077                         if (data->items.offset) {
1078                                 ui_searchbox_butrect(&rect, data, 0);
1079                                 glEnable(GL_BLEND);
1080                                 UI_icon_draw(rect.xmin, rect.ymax - 9, ICON_TRIA_UP);
1081                                 glDisable(GL_BLEND);
1082                         }
1083                         
1084                 }
1085                 else {
1086                         /* draw items */
1087                         for (a = 0; a < data->items.totitem; a++) {
1088                                 ui_searchbox_butrect(&rect, data, a);
1089                                 
1090                                 /* widget itself */
1091                                 ui_draw_menu_item(&data->fstyle, &rect, data->items.names[a], data->items.icons[a], (a + 1) == data->active ? UI_ACTIVE : 0);
1092                                 
1093                         }
1094                         /* indicate more */
1095                         if (data->items.more) {
1096                                 ui_searchbox_butrect(&rect, data, data->items.maxitem - 1);
1097                                 glEnable(GL_BLEND);
1098                                 UI_icon_draw((rect.xmax - rect.xmin) / 2, rect.ymin - 9, ICON_TRIA_DOWN);
1099                                 glDisable(GL_BLEND);
1100                         }
1101                         if (data->items.offset) {
1102                                 ui_searchbox_butrect(&rect, data, 0);
1103                                 glEnable(GL_BLEND);
1104                                 UI_icon_draw((rect.xmax - rect.xmin) / 2, rect.ymax - 7, ICON_TRIA_UP);
1105                                 glDisable(GL_BLEND);
1106                         }
1107                 }
1108         }
1109 }
1110
1111 static void ui_searchbox_region_free_cb(ARegion *ar)
1112 {
1113         uiSearchboxData *data = ar->regiondata;
1114         int a;
1115
1116         /* free search data */
1117         for (a = 0; a < data->items.maxitem; a++) {
1118                 MEM_freeN(data->items.names[a]);
1119         }
1120         MEM_freeN(data->items.names);
1121         MEM_freeN(data->items.pointers);
1122         MEM_freeN(data->items.icons);
1123         
1124         MEM_freeN(data);
1125         ar->regiondata = NULL;
1126 }
1127
1128 ARegion *ui_searchbox_create(bContext *C, ARegion *butregion, uiBut *but)
1129 {
1130         uiStyle *style = UI_GetStyle();
1131         static ARegionType type;
1132         ARegion *ar;
1133         uiSearchboxData *data;
1134         float aspect = but->block->aspect;
1135         rctf rect_fl;
1136         rcti rect_i;
1137         int winx, winy, ofsx, ofsy;
1138         int i;
1139         
1140         /* create area region */
1141         ar = ui_add_temporary_region(CTX_wm_screen(C));
1142         
1143         memset(&type, 0, sizeof(ARegionType));
1144         type.draw = ui_searchbox_region_draw_cb;
1145         type.free = ui_searchbox_region_free_cb;
1146         ar->type = &type;
1147         
1148         /* create searchbox data */
1149         data = MEM_callocN(sizeof(uiSearchboxData), "uiSearchboxData");
1150
1151         /* set font, get bb */
1152         data->fstyle = style->widget; /* copy struct */
1153         data->fstyle.align = UI_STYLE_TEXT_CENTER;
1154         ui_fontscale(&data->fstyle.points, aspect);
1155         uiStyleFontSet(&data->fstyle);
1156         
1157         ar->regiondata = data;
1158         
1159         /* special case, hardcoded feature, not draw backdrop when called from menus,
1160          * assume for design that popup already added it */
1161         if (but->block->flag & UI_BLOCK_LOOP)
1162                 data->noback = 1;
1163         
1164         if (but->a1 > 0 && but->a2 > 0) {
1165                 data->preview = 1;
1166                 data->prv_rows = but->a1;
1167                 data->prv_cols = but->a2;
1168         }
1169         
1170         /* compute position */
1171         if (but->block->flag & UI_BLOCK_LOOP) {
1172                 /* this case is search menu inside other menu */
1173                 /* we copy region size */
1174
1175                 ar->winrct = butregion->winrct;
1176                 
1177                 /* widget rect, in region coords */
1178                 data->bbox.xmin = MENU_SHADOW_SIDE;
1179                 data->bbox.xmax = (ar->winrct.xmax - ar->winrct.xmin) - MENU_SHADOW_SIDE;
1180                 data->bbox.ymin = MENU_SHADOW_BOTTOM;
1181                 data->bbox.ymax = (ar->winrct.ymax - ar->winrct.ymin) - MENU_SHADOW_BOTTOM;
1182                 
1183                 /* check if button is lower half */
1184                 if (but->rect.ymax < (but->block->rect.ymin + but->block->rect.ymax) / 2) {
1185                         data->bbox.ymin += (but->rect.ymax - but->rect.ymin);
1186                 }
1187                 else {
1188                         data->bbox.ymax -= (but->rect.ymax - but->rect.ymin);
1189                 }
1190         }
1191         else {
1192                 rect_fl.xmin = but->rect.xmin - 5;   /* align text with button */
1193                 rect_fl.xmax = but->rect.xmax + 5;   /* symmetrical */
1194                 rect_fl.ymax = but->rect.ymin;
1195                 rect_fl.ymin = rect_fl.ymax - uiSearchBoxhHeight();
1196
1197                 ofsx = (but->block->panel) ? but->block->panel->ofsx : 0;
1198                 ofsy = (but->block->panel) ? but->block->panel->ofsy : 0;
1199
1200                 BLI_rctf_translate(&rect_fl, ofsx, ofsy);
1201         
1202                 /* minimal width */
1203                 if (rect_fl.xmax - rect_fl.xmin < 150) {
1204                         rect_fl.xmax = rect_fl.xmin + 150;  /* XXX arbitrary */
1205                 }
1206                 
1207                 /* copy to int, gets projected if possible too */
1208                 BLI_rcti_rctf_copy(&rect_i, &rect_fl);
1209                 
1210                 if (butregion->v2d.cur.xmin != butregion->v2d.cur.xmax) {
1211                         UI_view2d_to_region_no_clip(&butregion->v2d, rect_fl.xmin, rect_fl.ymin, &rect_i.xmin, &rect_i.ymin);
1212                         UI_view2d_to_region_no_clip(&butregion->v2d, rect_fl.xmax, rect_fl.ymax, &rect_i.xmax, &rect_i.ymax);
1213                 }
1214
1215                 BLI_rcti_translate(&rect_i, butregion->winrct.xmin, butregion->winrct.ymin);
1216
1217                 wm_window_get_size(CTX_wm_window(C), &winx, &winy);
1218                 
1219                 if (rect_i.xmax > winx) {
1220                         /* super size */
1221                         if (rect_i.xmax > winx + rect_i.xmin) {
1222                                 rect_i.xmax = winx;
1223                                 rect_i.xmin = 0;
1224                         }
1225                         else {
1226                                 rect_i.xmin -= rect_i.xmax - winx;
1227                                 rect_i.xmax = winx;
1228                         }
1229                 }
1230
1231                 if (rect_i.ymin < 0) {
1232                         int newy1;
1233                         UI_view2d_to_region_no_clip(&butregion->v2d, 0, but->rect.ymax + ofsy, NULL, &newy1);
1234                         newy1 += butregion->winrct.ymin;
1235
1236                         rect_i.ymax = rect_i.ymax - rect_i.ymin + newy1;
1237                         rect_i.ymin = newy1;
1238                 }
1239
1240                 /* widget rect, in region coords */
1241                 data->bbox.xmin = MENU_SHADOW_SIDE;
1242                 data->bbox.xmax = rect_i.xmax - rect_i.xmin + MENU_SHADOW_SIDE;
1243                 data->bbox.ymin = MENU_SHADOW_BOTTOM;
1244                 data->bbox.ymax = rect_i.ymax - rect_i.ymin + MENU_SHADOW_BOTTOM;
1245                 
1246                 /* region bigger for shadow */
1247                 ar->winrct.xmin = rect_i.xmin - MENU_SHADOW_SIDE;
1248                 ar->winrct.xmax = rect_i.xmax + MENU_SHADOW_SIDE;
1249                 ar->winrct.ymin = rect_i.ymin - MENU_SHADOW_BOTTOM;
1250                 ar->winrct.ymax = rect_i.ymax;
1251         }
1252         
1253         /* adds subwindow */
1254         ED_region_init(C, ar);
1255         
1256         /* notify change and redraw */
1257         ED_region_tag_redraw(ar);
1258         
1259         /* prepare search data */
1260         if (data->preview) {
1261                 data->items.maxitem = data->prv_rows * data->prv_cols;
1262         }
1263         else {
1264                 data->items.maxitem = SEARCH_ITEMS;
1265         }
1266         data->items.maxstrlen = but->hardmax;
1267         data->items.totitem = 0;
1268         data->items.names = MEM_callocN(data->items.maxitem * sizeof(void *), "search names");
1269         data->items.pointers = MEM_callocN(data->items.maxitem * sizeof(void *), "search pointers");
1270         data->items.icons = MEM_callocN(data->items.maxitem * sizeof(int), "search icons");
1271         for (i = 0; i < data->items.maxitem; i++)
1272                 data->items.names[i] = MEM_callocN(but->hardmax + 1, "search pointers");
1273         
1274         return ar;
1275 }
1276
1277 void ui_searchbox_free(bContext *C, ARegion *ar)
1278 {
1279         ui_remove_temporary_region(C, CTX_wm_screen(C), ar);
1280 }
1281
1282 /* sets red alert if button holds a string it can't find */
1283 /* XXX weak: search_func adds all partial matches... */
1284 void ui_but_search_test(uiBut *but)
1285 {
1286         uiSearchItems *items;
1287         int x1;
1288
1289         /* possibly very large lists (such as ID datablocks) only
1290          * only validate string RNA buts (not pointers) */
1291         if (but->rnaprop && RNA_property_type(but->rnaprop) != PROP_STRING) {
1292                 return;
1293         }
1294
1295         items = MEM_callocN(sizeof(uiSearchItems), "search items");
1296
1297         /* setup search struct */
1298         items->maxitem = 10;
1299         items->maxstrlen = 256;
1300         items->names = MEM_callocN(items->maxitem * sizeof(void *), "search names");
1301         for (x1 = 0; x1 < items->maxitem; x1++)
1302                 items->names[x1] = MEM_callocN(but->hardmax + 1, "search names");
1303         
1304         but->search_func(but->block->evil_C, but->search_arg, but->drawstr, items);
1305         
1306         /* only redalert when we are sure of it, this can miss cases when >10 matches */
1307         if (items->totitem == 0)
1308                 uiButSetFlag(but, UI_BUT_REDALERT);
1309         else if (items->more == 0) {
1310                 for (x1 = 0; x1 < items->totitem; x1++)
1311                         if (strcmp(but->drawstr, items->names[x1]) == 0)
1312                                 break;
1313                 if (x1 == items->totitem)
1314                         uiButSetFlag(but, UI_BUT_REDALERT);
1315         }
1316         
1317         for (x1 = 0; x1 < items->maxitem; x1++) {
1318                 MEM_freeN(items->names[x1]);
1319         }
1320         MEM_freeN(items->names);
1321         MEM_freeN(items);
1322 }
1323
1324
1325 /************************* Creating Menu Blocks **********************/
1326
1327 /* position block relative to but, result is in window space */
1328 static void ui_block_position(wmWindow *window, ARegion *butregion, uiBut *but, uiBlock *block)
1329 {
1330         uiBut *bt;
1331         uiSafetyRct *saferct;
1332         rctf butrct;
1333         /*float aspect;*/ /*UNUSED*/
1334         int xsize, ysize, xof = 0, yof = 0, center;
1335         short dir1 = 0, dir2 = 0;
1336         
1337         /* transform to window coordinates, using the source button region/block */
1338         butrct = but->rect;
1339
1340         ui_block_to_window_fl(butregion, but->block, &butrct.xmin, &butrct.ymin);
1341         ui_block_to_window_fl(butregion, but->block, &butrct.xmax, &butrct.ymax);
1342
1343         /* calc block rect */
1344         if (block->rect.xmin == 0.0f && block->rect.xmax == 0.0f) {
1345                 if (block->buttons.first) {
1346                         BLI_rctf_init_minmax(&block->rect);
1347
1348                         for (bt = block->buttons.first; bt; bt = bt->next) {
1349                                 BLI_rctf_union(&block->rect, &bt->rect);
1350                         }
1351                 }
1352                 else {
1353                         /* we're nice and allow empty blocks too */
1354                         block->rect.xmin = block->rect.ymin = 0;
1355                         block->rect.xmax = block->rect.ymax = 20;
1356                 }
1357         }
1358         
1359         /* aspect = (float)(block->rect.xmax - block->rect.xmin + 4);*/ /*UNUSED*/
1360         ui_block_to_window_fl(butregion, but->block, &block->rect.xmin, &block->rect.ymin);
1361         ui_block_to_window_fl(butregion, but->block, &block->rect.xmax, &block->rect.ymax);
1362
1363         //block->rect.xmin -= 2.0; block->rect.ymin -= 2.0;
1364         //block->rect.xmax += 2.0; block->rect.ymax += 2.0;
1365         
1366         xsize = block->rect.xmax - block->rect.xmin + 4;  /* 4 for shadow */
1367         ysize = block->rect.ymax - block->rect.ymin + 4;
1368         /* aspect /= (float)xsize;*/ /*UNUSED*/
1369
1370         {
1371                 int left = 0, right = 0, top = 0, down = 0;
1372                 int winx, winy;
1373                 // int offscreen;
1374
1375                 wm_window_get_size(window, &winx, &winy);
1376
1377                 if (block->direction & UI_CENTER) center = ysize / 2;
1378                 else center = 0;
1379                 
1380                 /* check if there's space at all */
1381                 if (butrct.xmin - xsize > 0.0f) left = 1;
1382                 if (butrct.xmax + xsize < winx) right = 1;
1383                 if (butrct.ymin - ysize + center > 0.0f) down = 1;
1384                 if (butrct.ymax + ysize - center < winy) top = 1;
1385
1386                 if (top == 0 && down == 0) {
1387                         if (butrct.ymin - ysize < winy - butrct.ymax - ysize)
1388                                 top = 1;
1389                         else
1390                                 down = 1;
1391                 }
1392                 
1393                 dir1 = block->direction & UI_DIRECTION;
1394
1395                 /* secundary directions */
1396                 if (dir1 & (UI_TOP | UI_DOWN)) {
1397                         if (dir1 & UI_LEFT) dir2 = UI_LEFT;
1398                         else if (dir1 & UI_RIGHT) dir2 = UI_RIGHT;
1399                         dir1 &= (UI_TOP | UI_DOWN);
1400                 }
1401
1402                 if (dir2 == 0) if (dir1 == UI_LEFT || dir1 == UI_RIGHT) dir2 = UI_DOWN;
1403                 if (dir2 == 0) if (dir1 == UI_TOP || dir1 == UI_DOWN) dir2 = UI_LEFT;
1404                 
1405                 /* no space at all? don't change */
1406                 if (left || right) {
1407                         if (dir1 == UI_LEFT && left == 0) dir1 = UI_RIGHT;
1408                         if (dir1 == UI_RIGHT && right == 0) dir1 = UI_LEFT;
1409                         /* this is aligning, not append! */
1410                         if (dir2 == UI_LEFT && right == 0) dir2 = UI_RIGHT;
1411                         if (dir2 == UI_RIGHT && left == 0) dir2 = UI_LEFT;
1412                 }
1413                 if (down || top) {
1414                         if (dir1 == UI_TOP && top == 0) dir1 = UI_DOWN;
1415                         if (dir1 == UI_DOWN && down == 0) dir1 = UI_TOP;
1416                         if (dir2 == UI_TOP && top == 0) dir2 = UI_DOWN;
1417                         if (dir2 == UI_DOWN && down == 0) dir2 = UI_TOP;
1418                 }
1419
1420                 if (dir1 == UI_LEFT) {
1421                         xof = butrct.xmin - block->rect.xmax;
1422                         if (dir2 == UI_TOP) yof = butrct.ymin - block->rect.ymin - center;
1423                         else yof = butrct.ymax - block->rect.ymax + center;
1424                 }
1425                 else if (dir1 == UI_RIGHT) {
1426                         xof = butrct.xmax - block->rect.xmin;
1427                         if (dir2 == UI_TOP) yof = butrct.ymin - block->rect.ymin - center;
1428                         else yof = butrct.ymax - block->rect.ymax + center;
1429                 }
1430                 else if (dir1 == UI_TOP) {
1431                         yof = butrct.ymax - block->rect.ymin;
1432                         if (dir2 == UI_RIGHT) xof = butrct.xmax - block->rect.xmax;
1433                         else xof = butrct.xmin - block->rect.xmin;
1434                         // changed direction? 
1435                         if ((dir1 & block->direction) == 0) {
1436                                 if (block->direction & UI_SHIFT_FLIPPED)
1437                                         xof += dir2 == UI_LEFT ? 25 : -25;
1438                                 uiBlockFlipOrder(block);
1439                         }
1440                 }
1441                 else if (dir1 == UI_DOWN) {
1442                         yof = butrct.ymin - block->rect.ymax;
1443                         if (dir2 == UI_RIGHT) xof = butrct.xmax - block->rect.xmax;
1444                         else xof = butrct.xmin - block->rect.xmin;
1445                         // changed direction?
1446                         if ((dir1 & block->direction) == 0) {
1447                                 if (block->direction & UI_SHIFT_FLIPPED)
1448                                         xof += dir2 == UI_LEFT ? 25 : -25;
1449                                 uiBlockFlipOrder(block);
1450                         }
1451                 }
1452
1453                 /* and now we handle the exception; no space below or to top */
1454                 if (top == 0 && down == 0) {
1455                         if (dir1 == UI_LEFT || dir1 == UI_RIGHT) {
1456                                 // align with bottom of screen 
1457                                 // yof= ysize; (not with menu scrolls)
1458                         }
1459                 }
1460                 
1461                 /* or no space left or right */
1462                 if (left == 0 && right == 0) {
1463                         if (dir1 == UI_TOP || dir1 == UI_DOWN) {
1464                                 // align with left size of screen 
1465                                 xof = -block->rect.xmin + 5;
1466                         }
1467                 }
1468                 
1469                 // apply requested offset in the block
1470                 xof += block->xofs / block->aspect;
1471                 yof += block->yofs / block->aspect;
1472 #if 0
1473                 /* clamp to window bounds, could be made into an option if its ever annoying */
1474                 if (     (offscreen = (block->rect.ymin + yof)) < 0) yof -= offscreen;   /* bottom */
1475                 else if ((offscreen = (block->rect.ymax + yof) - winy) > 0) yof -= offscreen;  /* top */
1476                 if (     (offscreen = (block->rect.xmin + xof)) < 0) xof -= offscreen;   /* left */
1477                 else if ((offscreen = (block->rect.xmax + xof) - winx) > 0) xof -= offscreen;  /* right */
1478 #endif
1479         }
1480         
1481         /* apply offset, buttons in window coords */
1482         
1483         for (bt = block->buttons.first; bt; bt = bt->next) {
1484                 ui_block_to_window_fl(butregion, but->block, &bt->rect.xmin, &bt->rect.ymin);
1485                 ui_block_to_window_fl(butregion, but->block, &bt->rect.xmax, &bt->rect.ymax);
1486
1487                 BLI_rctf_translate(&bt->rect, xof, yof);
1488
1489                 bt->aspect = 1.0f;
1490                 /* ui_check_but recalculates drawstring size in pixels */
1491                 ui_check_but(bt);
1492         }
1493         
1494         BLI_rctf_translate(&block->rect, xof, yof);
1495
1496         /* safety calculus */
1497         if (but) {
1498                 float midx = (butrct.xmin + butrct.xmax) / 2.0f;
1499                 float midy = (butrct.ymin + butrct.ymax) / 2.0f;
1500                 
1501                 /* when you are outside parent button, safety there should be smaller */
1502                 
1503                 /* parent button to left */
1504                 if (midx < block->rect.xmin) block->safety.xmin = block->rect.xmin - 3;
1505                 else block->safety.xmin = block->rect.xmin - 40;
1506                 /* parent button to right */
1507                 if (midx > block->rect.xmax) block->safety.xmax = block->rect.xmax + 3;
1508                 else block->safety.xmax = block->rect.xmax + 40;
1509
1510                 /* parent button on bottom */
1511                 if (midy < block->rect.ymin) block->safety.ymin = block->rect.ymin - 3;
1512                 else block->safety.ymin = block->rect.ymin - 40;
1513                 /* parent button on top */
1514                 if (midy > block->rect.ymax) block->safety.ymax = block->rect.ymax + 3;
1515                 else block->safety.ymax = block->rect.ymax + 40;
1516
1517                 /* exception for switched pulldowns... */
1518                 if (dir1 && (dir1 & block->direction) == 0) {
1519                         if (dir2 == UI_RIGHT) block->safety.xmax = block->rect.xmax + 3;
1520                         if (dir2 == UI_LEFT) block->safety.xmin = block->rect.xmin - 3;
1521                 }
1522                 block->direction = dir1;
1523         }
1524         else {
1525                 block->safety.xmin = block->rect.xmin - 40;
1526                 block->safety.ymin = block->rect.ymin - 40;
1527                 block->safety.xmax = block->rect.xmax + 40;
1528                 block->safety.ymax = block->rect.ymax + 40;
1529         }
1530
1531         /* keep a list of these, needed for pulldown menus */
1532         saferct = MEM_callocN(sizeof(uiSafetyRct), "uiSafetyRct");
1533         saferct->parent = butrct;
1534         saferct->safety = block->safety;
1535         BLI_freelistN(&block->saferct);
1536         if (but)
1537                 BLI_duplicatelist(&block->saferct, &but->block->saferct);
1538         BLI_addhead(&block->saferct, saferct);
1539 }
1540
1541 static void ui_block_region_draw(const bContext *C, ARegion *ar)
1542 {
1543         uiBlock *block;
1544
1545         for (block = ar->uiblocks.first; block; block = block->next)
1546                 uiDrawBlock(C, block);
1547 }
1548
1549 static void ui_popup_block_clip(wmWindow *window, uiBlock *block)
1550 {
1551         int winx, winy;
1552         
1553         wm_window_get_size(window, &winx, &winy);
1554         
1555         if (block->rect.xmin < MENU_SHADOW_SIDE)
1556                 block->rect.xmin = MENU_SHADOW_SIDE;
1557         if (block->rect.xmax > winx - MENU_SHADOW_SIDE)
1558                 block->rect.xmax = winx - MENU_SHADOW_SIDE;
1559         
1560         if (block->rect.ymin < MENU_SHADOW_BOTTOM)
1561                 block->rect.ymin = MENU_SHADOW_BOTTOM;
1562         if (block->rect.ymax > winy - MENU_TOP)
1563                 block->rect.ymax = winy - MENU_TOP;
1564 }
1565
1566 void ui_popup_block_scrolltest(uiBlock *block)
1567 {
1568         uiBut *bt;
1569         /* Knowing direction is necessary for multi-column menus... */
1570         int is_flip = (block->direction & UI_TOP) && !(block->flag & UI_BLOCK_NO_FLIP);
1571         
1572         block->flag &= ~(UI_BLOCK_CLIPBOTTOM | UI_BLOCK_CLIPTOP);
1573         
1574         for (bt = block->buttons.first; bt; bt = bt->next)
1575                 bt->flag &= ~UI_SCROLLED;
1576         
1577         if (block->buttons.first == block->buttons.last)
1578                 return;
1579         
1580         /* mark buttons that are outside boundary and the ones next to it for arrow(s) */
1581         for (bt = block->buttons.first; bt; bt = bt->next) {
1582                 if (bt->rect.ymin < block->rect.ymin) {
1583                         bt->flag |= UI_SCROLLED;
1584                         block->flag |= UI_BLOCK_CLIPBOTTOM;
1585                         /* make space for arrow */
1586                         if (bt->rect.ymax < block->rect.ymin + 10) {
1587                                 if (is_flip && bt->next && bt->next->rect.ymin > bt->rect.ymin)
1588                                         bt->next->flag |= UI_SCROLLED;
1589                                 else if (!is_flip && bt->prev && bt->prev->rect.ymin > bt->rect.ymin)
1590                                         bt->prev->flag |= UI_SCROLLED;
1591                         }
1592                 }
1593                 if (bt->rect.ymax > block->rect.ymax) {
1594                         bt->flag |= UI_SCROLLED;
1595                         block->flag |= UI_BLOCK_CLIPTOP;
1596                         /* make space for arrow */
1597                         if (bt->rect.ymin > block->rect.ymax - 10) {
1598                                 if (!is_flip && bt->next && bt->next->rect.ymax < bt->rect.ymax)
1599                                         bt->next->flag |= UI_SCROLLED;
1600                                 else if (is_flip && bt->prev && bt->prev->rect.ymax < bt->rect.ymax)
1601                                         bt->prev->flag |= UI_SCROLLED;
1602                         }
1603                 }
1604         }
1605 }
1606
1607 uiPopupBlockHandle *ui_popup_block_create(bContext *C, ARegion *butregion, uiBut *but,
1608                                           uiBlockCreateFunc create_func, uiBlockHandleCreateFunc handle_create_func,
1609                                           void *arg)
1610 {
1611         wmWindow *window = CTX_wm_window(C);
1612         static ARegionType type;
1613         ARegion *ar;
1614         uiBlock *block;
1615         uiPopupBlockHandle *handle;
1616         uiSafetyRct *saferct;
1617
1618         /* create handle */
1619         handle = MEM_callocN(sizeof(uiPopupBlockHandle), "uiPopupBlockHandle");
1620
1621         /* store context for operator */
1622         handle->ctx_area = CTX_wm_area(C);
1623         handle->ctx_region = CTX_wm_region(C);
1624         
1625         /* create area region */
1626         ar = ui_add_temporary_region(CTX_wm_screen(C));
1627         handle->region = ar;
1628
1629         memset(&type, 0, sizeof(ARegionType));
1630         type.draw = ui_block_region_draw;
1631         ar->type = &type;
1632
1633         UI_add_region_handlers(&ar->handlers);
1634
1635         /* create ui block */
1636         if (create_func)
1637                 block = create_func(C, handle->region, arg);
1638         else
1639                 block = handle_create_func(C, handle, arg);
1640         
1641         if (block->handle) {
1642                 memcpy(block->handle, handle, sizeof(uiPopupBlockHandle));
1643                 MEM_freeN(handle);
1644                 handle = block->handle;
1645         }
1646         else
1647                 block->handle = handle;
1648
1649         ar->regiondata = handle;
1650
1651         if (!block->endblock)
1652                 uiEndBlock(C, block);
1653
1654         /* if this is being created from a button */
1655         if (but) {
1656                 if (ELEM(but->type, BLOCK, PULLDOWN))
1657                         block->xofs = -2;   /* for proper alignment */
1658
1659                 block->aspect = but->block->aspect;
1660
1661                 ui_block_position(window, butregion, but, block);
1662         }
1663         else {
1664                 /* keep a list of these, needed for pulldown menus */
1665                 saferct = MEM_callocN(sizeof(uiSafetyRct), "uiSafetyRct");
1666                 saferct->safety = block->safety;
1667                 BLI_addhead(&block->saferct, saferct);
1668                 block->flag |= UI_BLOCK_POPUP | UI_BLOCK_NUMSELECT;
1669         }
1670
1671         /* clip block with window boundary */
1672         ui_popup_block_clip(window, block);
1673         
1674         /* the block and buttons were positioned in window space as in 2.4x, now
1675          * these menu blocks are regions so we bring it back to region space.
1676          * additionally we add some padding for the menu shadow or rounded menus */
1677         ar->winrct.xmin = block->rect.xmin - MENU_SHADOW_SIDE;
1678         ar->winrct.xmax = block->rect.xmax + MENU_SHADOW_SIDE;
1679         ar->winrct.ymin = block->rect.ymin - MENU_SHADOW_BOTTOM;
1680         ar->winrct.ymax = block->rect.ymax + MENU_TOP;
1681         
1682         ui_block_translate(block, -ar->winrct.xmin, -ar->winrct.ymin);
1683         
1684         block->flag |= UI_BLOCK_LOOP;
1685
1686         /* adds subwindow */
1687         ED_region_init(C, ar);
1688
1689         /* checks which buttons are visible, sets flags to prevent draw (do after region init) */
1690         ui_popup_block_scrolltest(block);
1691         
1692         /* get winmat now that we actually have the subwindow */
1693         wmSubWindowSet(window, ar->swinid);
1694         
1695         wm_subwindow_getmatrix(window, ar->swinid, block->winmat);
1696         
1697         /* notify change and redraw */
1698         ED_region_tag_redraw(ar);
1699
1700         return handle;
1701 }
1702
1703 void ui_popup_block_free(bContext *C, uiPopupBlockHandle *handle)
1704 {
1705         ui_remove_temporary_region(C, CTX_wm_screen(C), handle->region);
1706         
1707         if (handle->scrolltimer)
1708                 WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), handle->scrolltimer);
1709         
1710         MEM_freeN(handle);
1711 }
1712
1713 /***************************** Menu Button ***************************/
1714
1715 static void ui_block_func_MENUSTR(bContext *UNUSED(C), uiLayout *layout, void *arg_str)
1716 {
1717         uiBlock *block = uiLayoutGetBlock(layout);
1718         uiPopupBlockHandle *handle = block->handle;
1719         uiLayout *split, *column = NULL;
1720         uiBut *bt;
1721         MenuData *md;
1722         MenuEntry *entry;
1723         const char *instr = arg_str;
1724         int columns, rows, a, b;
1725         int column_start = 0, column_end = 0;
1726
1727         uiBlockSetFlag(block, UI_BLOCK_MOVEMOUSE_QUIT);
1728         
1729         /* compute menu data */
1730         md = decompose_menu_string(instr);
1731
1732         /* columns and row estimation */
1733         columns = (md->nitems + 20) / 20;
1734         if (columns < 1)
1735                 columns = 1;
1736         if (columns > 8)
1737                 columns = (md->nitems + 25) / 25;
1738
1739         rows = md->nitems / columns;
1740         if (rows < 1)
1741                 rows = 1;
1742         while (rows * columns < md->nitems)
1743                 rows++;
1744
1745         /* create title */
1746         if (md->title) {
1747                 if (md->titleicon) {
1748                         uiItemL(layout, md->title, md->titleicon);
1749                 }
1750                 else {
1751                         uiItemL(layout, md->title, ICON_NONE);
1752                         bt = block->buttons.last;
1753                         bt->flag = UI_TEXT_LEFT;
1754                 }
1755         }
1756
1757         /* inconsistent, but menus with labels do not look good flipped */
1758         entry = md->items;
1759         for (a = 0; a < md->nitems; a++, entry++) {
1760                 if (entry->sepr && entry->str[0]) {
1761                         block->flag |= UI_BLOCK_NO_FLIP;
1762                         break;
1763                 }
1764         }
1765
1766         /* create items */
1767         split = uiLayoutSplit(layout, 0.0f, FALSE);
1768
1769         for (a = 0; a < md->nitems; a++) {
1770                 if (a == column_end) {
1771                         /* start new column, and find out where it ends in advance, so we
1772                          * can flip the order of items properly per column */
1773                         column_start = a;
1774                         column_end = md->nitems;
1775
1776                         for (b = a + 1; b < md->nitems; b++) {
1777                                 entry = &md->items[b];
1778
1779                                 /* new column on N rows or on separation label */
1780                                 if (((b - a) % rows == 0) || (entry->sepr && entry->str[0])) {
1781                                         column_end = b;
1782                                         break;
1783                                 }
1784                         }
1785
1786                         column = uiLayoutColumn(split, FALSE);
1787                 }
1788
1789                 if (block->flag & UI_BLOCK_NO_FLIP)
1790                         entry = &md->items[a];
1791                 else
1792                         entry = &md->items[column_start + column_end - 1 - a];
1793
1794                 if (entry->sepr) {
1795                         uiItemL(column, entry->str, entry->icon);
1796                         bt = block->buttons.last;
1797                         bt->flag = UI_TEXT_LEFT;
1798                 }
1799                 else if (entry->icon) {
1800                         uiDefIconTextButF(block, BUTM | FLO, B_NOP, entry->icon, entry->str, 0, 0,
1801                                           UI_UNIT_X * 5, UI_UNIT_Y, &handle->retvalue, (float) entry->retval, 0.0, 0, 0, "");
1802                 }
1803                 else {
1804                         uiDefButF(block, BUTM | FLO, B_NOP, entry->str, 0, 0,
1805                                   UI_UNIT_X * 5, UI_UNIT_X, &handle->retvalue, (float) entry->retval, 0.0, 0, 0, "");
1806                 }
1807         }
1808         
1809         menudata_free(md);
1810 }
1811
1812 void ui_block_func_ICONROW(bContext *UNUSED(C), uiLayout *layout, void *arg_but)
1813 {
1814         uiBlock *block = uiLayoutGetBlock(layout);
1815         uiPopupBlockHandle *handle = block->handle;
1816         uiBut *but = arg_but;
1817         int a;
1818         
1819         uiBlockSetFlag(block, UI_BLOCK_MOVEMOUSE_QUIT);
1820         
1821         for (a = (int)but->hardmin; a <= (int)but->hardmax; a++)
1822                 uiDefIconButF(block, BUTM | FLO, B_NOP, but->icon + (a - but->hardmin), 0, 0, UI_UNIT_X * 5, UI_UNIT_Y,
1823                               &handle->retvalue, (float)a, 0.0, 0, 0, "");
1824 }
1825
1826 void ui_block_func_ICONTEXTROW(bContext *UNUSED(C), uiLayout *layout, void *arg_but)
1827 {
1828         uiBlock *block = uiLayoutGetBlock(layout);
1829         uiPopupBlockHandle *handle = block->handle;
1830         uiBut *but = arg_but, *bt;
1831         MenuData *md;
1832         MenuEntry *entry;
1833         int a;
1834         
1835         uiBlockSetFlag(block, UI_BLOCK_MOVEMOUSE_QUIT);
1836
1837         md = decompose_menu_string(but->str);
1838
1839         /* title */
1840         if (md->title) {
1841                 bt = uiDefBut(block, LABEL, 0, md->title, 0, 0, UI_UNIT_X * 5, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
1842                 bt->flag = UI_TEXT_LEFT;
1843         }
1844
1845         /* loop through the menu options and draw them out with icons & text labels */
1846         for (a = 0; a < md->nitems; a++) {
1847                 entry = &md->items[md->nitems - a - 1];
1848
1849                 if (entry->sepr)
1850                         uiItemS(layout);
1851                 else
1852                         uiDefIconTextButF(block, BUTM | FLO, B_NOP, (short)((but->icon) + (entry->retval - but->hardmin)), entry->str,
1853                                           0, 0, UI_UNIT_X * 5, UI_UNIT_Y, &handle->retvalue, (float) entry->retval, 0.0, 0, 0, "");
1854         }
1855
1856         menudata_free(md);
1857 }
1858
1859 #if 0
1860 static void ui_warp_pointer(int x, int y)
1861 {
1862         /* XXX 2.50 which function to use for this? */
1863         /* OSX has very poor mouse-warp support, it sends events;
1864          * this causes a menu being pressed immediately ... */
1865 #  ifndef __APPLE__
1866         warp_pointer(x, y);
1867 #  endif
1868 }
1869 #endif
1870
1871 /********************* Color Button ****************/
1872
1873 /* picker sizes S hsize, F full size, D spacer, B button/pallette height  */
1874 #define SPICK   110.0
1875 #define FPICK   180.0
1876 #define DPICK   6.0
1877 #define BPICK   24.0
1878
1879 /* for picker, while editing hsv */
1880 void ui_set_but_hsv(uiBut *but)
1881 {
1882         float col[3];
1883         float *hsv = ui_block_hsv_get(but->block);
1884         
1885         hsv_to_rgb_v(hsv, col);
1886         ui_set_but_vectorf(but, col);
1887 }
1888
1889 /* also used by small picker, be careful with name checks below... */
1890 static void ui_update_block_buts_rgb(uiBlock *block, const float rgb[3])
1891 {
1892         uiBut *bt;
1893         float *hsv = ui_block_hsv_get(block);
1894         
1895         /* this is to keep the H and S value when V is equal to zero
1896          * and we are working in HSV mode, of course!
1897          */
1898         rgb_to_hsv_compat_v(rgb, hsv);
1899         
1900         /* this updates button strings, is hackish... but button pointers are on stack of caller function */
1901         for (bt = block->buttons.first; bt; bt = bt->next) {
1902                 if (bt->rnaprop) {
1903                         
1904                         ui_set_but_vectorf(bt, rgb);
1905                         
1906                 }
1907                 else if (strcmp(bt->str, "Hex: ") == 0) {
1908                         float rgb_gamma[3];
1909                         double intpart;
1910                         char col[16];
1911                         
1912                         /* Hex code is assumed to be in sRGB space (coming from other applications, web, etc) */
1913                         
1914                         if (block->color_profile == BLI_PR_NONE) {
1915                                 copy_v3_v3(rgb_gamma, rgb);
1916                         }
1917                         else {
1918                                 /* make an sRGB version, for Hex code */
1919                                 linearrgb_to_srgb_v3_v3(rgb_gamma, rgb);
1920                         }
1921                         
1922                         if (rgb_gamma[0] > 1.0f) rgb_gamma[0] = modf(rgb_gamma[0], &intpart);
1923                         if (rgb_gamma[1] > 1.0f) rgb_gamma[1] = modf(rgb_gamma[1], &intpart);
1924                         if (rgb_gamma[2] > 1.0f) rgb_gamma[2] = modf(rgb_gamma[2], &intpart);
1925
1926                         BLI_snprintf(col, sizeof(col), "%02X%02X%02X",
1927                                      FTOCHAR(rgb_gamma[0]), FTOCHAR(rgb_gamma[1]), FTOCHAR(rgb_gamma[2]));
1928                         
1929                         strcpy(bt->poin, col);
1930                 }
1931                 else if (bt->str[1] == ' ') {
1932                         if (bt->str[0] == 'R') {
1933                                 ui_set_but_val(bt, rgb[0]);
1934                         }
1935                         else if (bt->str[0] == 'G') {
1936                                 ui_set_but_val(bt, rgb[1]);
1937                         }
1938                         else if (bt->str[0] == 'B') {
1939                                 ui_set_but_val(bt, rgb[2]);
1940                         }
1941                         else if (bt->str[0] == 'H') {
1942                                 ui_set_but_val(bt, hsv[0]);
1943                         }
1944                         else if (bt->str[0] == 'S') {
1945                                 ui_set_but_val(bt, hsv[1]);
1946                         }
1947                         else if (bt->str[0] == 'V') {
1948                                 ui_set_but_val(bt, hsv[2]);
1949                         }
1950                 }               
1951
1952                 ui_check_but(bt);
1953         }
1954 }
1955
1956 static void do_picker_rna_cb(bContext *UNUSED(C), void *bt1, void *UNUSED(arg))
1957 {
1958         uiBut *but = (uiBut *)bt1;
1959         uiPopupBlockHandle *popup = but->block->handle;
1960         PropertyRNA *prop = but->rnaprop;
1961         PointerRNA ptr = but->rnapoin;
1962         float rgb[4];
1963         
1964         if (prop) {
1965                 RNA_property_float_get_array(&ptr, prop, rgb);
1966                 ui_update_block_buts_rgb(but->block, rgb);
1967         }
1968         
1969         if (popup)
1970                 popup->menuretval = UI_RETURN_UPDATE;
1971 }
1972
1973 static void do_hsv_rna_cb(bContext *UNUSED(C), void *bt1, void *UNUSED(arg))
1974 {
1975         uiBut *but = (uiBut *)bt1;
1976         uiPopupBlockHandle *popup = but->block->handle;
1977         float rgb[3];
1978         float *hsv = ui_block_hsv_get(but->block);
1979         
1980         hsv_to_rgb_v(hsv, rgb);
1981         
1982         ui_update_block_buts_rgb(but->block, rgb);
1983         
1984         if (popup)
1985                 popup->menuretval = UI_RETURN_UPDATE;
1986 }
1987
1988 static void do_hex_rna_cb(bContext *UNUSED(C), void *bt1, void *hexcl)
1989 {
1990         uiBut *but = (uiBut *)bt1;
1991         uiPopupBlockHandle *popup = but->block->handle;
1992         char *hexcol = (char *)hexcl;
1993         float rgb[3];
1994         
1995         hex_to_rgb(hexcol, rgb, rgb + 1, rgb + 2);
1996         
1997         /* Hex code is assumed to be in sRGB space (coming from other applications, web, etc) */
1998         if (but->block->color_profile != BLI_PR_NONE) {
1999                 /* so we need to linearise it for Blender */
2000                 srgb_to_linearrgb_v3_v3(rgb, rgb);
2001         }
2002         
2003         ui_update_block_buts_rgb(but->block, rgb);
2004         
2005         if (popup)
2006                 popup->menuretval = UI_RETURN_UPDATE;
2007 }
2008
2009 static void close_popup_cb(bContext *UNUSED(C), void *bt1, void *UNUSED(arg))
2010 {
2011         uiBut *but = (uiBut *)bt1;
2012         uiPopupBlockHandle *popup = but->block->handle;
2013         
2014         if (popup)
2015                 popup->menuretval = UI_RETURN_OK;
2016 }
2017
2018 static void picker_new_hide_reveal(uiBlock *block, short colormode)
2019 {
2020         uiBut *bt;
2021         
2022         /* tag buttons */
2023         for (bt = block->buttons.first; bt; bt = bt->next) {
2024                 
2025                 if (bt->type == LABEL) {
2026                         if (bt->str[1] == 'G') {
2027                                 if (colormode == 2) bt->flag &= ~UI_HIDDEN;
2028                                 else bt->flag |= UI_HIDDEN;
2029                         }
2030                 }
2031                 
2032                 if (bt->type == NUMSLI || bt->type == TEX) {
2033                         if (bt->str[1] == 'e') {
2034                                 if (colormode == 2) bt->flag &= ~UI_HIDDEN;
2035                                 else bt->flag |= UI_HIDDEN;
2036                         }
2037                         else if (ELEM3(bt->str[0], 'R', 'G', 'B')) {
2038                                 if (colormode == 0) bt->flag &= ~UI_HIDDEN;
2039                                 else bt->flag |= UI_HIDDEN;
2040                         }
2041                         else if (ELEM3(bt->str[0], 'H', 'S', 'V')) {
2042                                 if (colormode == 1) bt->flag &= ~UI_HIDDEN;
2043                                 else bt->flag |= UI_HIDDEN;
2044                         }
2045                 }
2046         }
2047 }
2048
2049 static void do_picker_new_mode_cb(bContext *UNUSED(C), void *bt1, void *UNUSED(arg))
2050 {
2051         uiBut *bt = bt1;
2052         short colormode = ui_get_but_val(bt);
2053         picker_new_hide_reveal(bt->block, colormode);
2054 }
2055
2056 /* picker sizes S hsize, F full size, D spacer, B button/pallette height  */
2057 #define SPICK1  150.0
2058 #define DPICK1  6.0
2059
2060 #define PICKER_H    150
2061 #define PICKER_W    150
2062 #define PICKER_SPACE    6
2063 #define PICKER_BAR      14
2064
2065 #define PICKER_TOTAL_W  (PICKER_W + PICKER_SPACE + PICKER_BAR)
2066
2067 static void circle_picker(uiBlock *block, PointerRNA *ptr, PropertyRNA *prop)
2068 {
2069         uiBut *bt;
2070         
2071         /* HS circle */
2072         bt = uiDefButR_prop(block, HSVCIRCLE, 0, "", 0, 0, PICKER_H, PICKER_W, ptr, prop, 0, 0.0, 0.0, 0, 0, "Color");
2073         uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
2074         
2075         /* value */
2076         bt = uiDefButR_prop(block, HSVCUBE, 0, "", PICKER_W + PICKER_SPACE, 0, PICKER_BAR, PICKER_H, ptr, prop, 0, 0.0, 0.0, UI_GRAD_V_ALT, 0, "Value");
2077         uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
2078 }
2079
2080
2081 static void square_picker(uiBlock *block, PointerRNA *ptr, PropertyRNA *prop, int type)
2082 {
2083         uiBut *bt;
2084         int bartype = type + 3;
2085         
2086         /* HS square */
2087         bt = uiDefButR_prop(block, HSVCUBE, 0, "",   0, PICKER_BAR + PICKER_SPACE, PICKER_TOTAL_W, PICKER_H, ptr, prop, 0, 0.0, 0.0, type, 0, "Color");
2088         uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
2089         
2090         /* value */
2091         bt = uiDefButR_prop(block, HSVCUBE, 0, "",       0, 0, PICKER_TOTAL_W, PICKER_BAR, ptr, prop, 0, 0.0, 0.0, bartype, 0, "Value");
2092         uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
2093 }
2094
2095
2096 /* a HS circle, V slider, rgb/hsv/hex sliders */
2097 static void uiBlockPicker(uiBlock *block, float rgba[4], PointerRNA *ptr, PropertyRNA *prop)
2098 {
2099         static short colormode = 0;  /* temp? 0=rgb, 1=hsv, 2=hex */
2100         uiBut *bt;
2101         int width, butwidth;
2102         static char tip[50];
2103         static char hexcol[128];
2104         float rgb_gamma[3];
2105         float min, max, step, precision;
2106         float *hsv = ui_block_hsv_get(block);
2107         
2108         ui_block_hsv_get(block);
2109         
2110         width = PICKER_TOTAL_W;
2111         butwidth = width - UI_UNIT_X - 10;
2112         
2113         /* existence of profile means storage is in linear color space, with display correction */
2114         /* XXX That tip message is not use anywhere! */
2115         if (block->color_profile == BLI_PR_NONE) {
2116                 BLI_strncpy(tip, N_("Value in Display Color Space"), sizeof(tip));
2117                 copy_v3_v3(rgb_gamma, rgba);
2118         }
2119         else {
2120                 BLI_strncpy(tip, N_("Value in Linear RGB Color Space"), sizeof(tip));
2121                 /* make an sRGB version, for Hex code */
2122                 linearrgb_to_srgb_v3_v3(rgb_gamma, rgba);
2123         }
2124         
2125         /* sneaky way to check for alpha */
2126         rgba[3] = FLT_MAX;
2127
2128         RNA_property_float_ui_range(ptr, prop, &min, &max, &step, &precision);
2129         RNA_property_float_get_array(ptr, prop, rgba);
2130
2131         switch (U.color_picker_type) {
2132                 case USER_CP_CIRCLE:
2133                         circle_picker(block, ptr, prop);
2134                         break;
2135                 case USER_CP_SQUARE_SV:
2136                         square_picker(block, ptr, prop, UI_GRAD_SV);
2137                         break;
2138                 case USER_CP_SQUARE_HS:
2139                         square_picker(block, ptr, prop, UI_GRAD_HS);
2140                         break;
2141                 case USER_CP_SQUARE_HV:
2142                         square_picker(block, ptr, prop, UI_GRAD_HV);
2143                         break;
2144         }
2145         
2146         /* mode */
2147         uiBlockBeginAlign(block);
2148         bt = uiDefButS(block, ROW, 0, IFACE_("RGB"), 0, -30, width / 3, UI_UNIT_Y, &colormode, 0.0, 0.0, 0, 0, "");
2149         uiButSetFunc(bt, do_picker_new_mode_cb, bt, NULL);
2150         bt = uiDefButS(block, ROW, 0, IFACE_("HSV"), width / 3, -30, width / 3, UI_UNIT_Y, &colormode, 0.0, 1.0, 0, 0, "");
2151         uiButSetFunc(bt, do_picker_new_mode_cb, bt, NULL);
2152         bt = uiDefButS(block, ROW, 0, IFACE_("Hex"), 2 * width / 3, -30, width / 3, UI_UNIT_Y, &colormode, 0.0, 2.0, 0, 0, "");
2153         uiButSetFunc(bt, do_picker_new_mode_cb, bt, NULL);
2154         uiBlockEndAlign(block);
2155
2156         bt = uiDefIconButO(block, BUT, "UI_OT_eyedropper", WM_OP_INVOKE_DEFAULT, ICON_EYEDROPPER, butwidth + 10, -60, UI_UNIT_X, UI_UNIT_Y, NULL);
2157         uiButSetFunc(bt, close_popup_cb, bt, NULL);
2158         
2159         /* RGB values */
2160         uiBlockBeginAlign(block);
2161         bt = uiDefButR_prop(block, NUMSLI, 0, IFACE_("R "),  0, -60, butwidth, UI_UNIT_Y, ptr, prop, 0, 0.0, 0.0, 0, 3, TIP_("Red"));
2162         uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
2163         bt = uiDefButR_prop(block, NUMSLI, 0, IFACE_("G "),  0, -80, butwidth, UI_UNIT_Y, ptr, prop, 1, 0.0, 0.0, 0, 3, TIP_("Green"));
2164         uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
2165         bt = uiDefButR_prop(block, NUMSLI, 0, IFACE_("B "),  0, -100, butwidth, UI_UNIT_Y, ptr, prop, 2, 0.0, 0.0, 0, 3, TIP_("Blue"));
2166         uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
2167
2168         /* could use uiItemFullR(col, ptr, prop, -1, 0, UI_ITEM_R_EXPAND|UI_ITEM_R_SLIDER, "", ICON_NONE);
2169          * but need to use uiButSetFunc for updating other fake buttons */
2170         
2171         /* HSV values */
2172         uiBlockBeginAlign(block);
2173         bt = uiDefButF(block, NUMSLI, 0, IFACE_("H "),   0, -60, butwidth, UI_UNIT_Y, hsv, 0.0, 1.0, 10, 3, TIP_("Hue"));
2174         uiButSetFunc(bt, do_hsv_rna_cb, bt, hsv);
2175         bt = uiDefButF(block, NUMSLI, 0, IFACE_("S "),   0, -80, butwidth, UI_UNIT_Y, hsv + 1, 0.0, 1.0, 10, 3, TIP_("Saturation"));
2176         uiButSetFunc(bt, do_hsv_rna_cb, bt, hsv);
2177         bt = uiDefButF(block, NUMSLI, 0, IFACE_("V "),   0, -100, butwidth, UI_UNIT_Y, hsv + 2, 0.0, max, 10, 3, TIP_("Value"));
2178         uiButSetFunc(bt, do_hsv_rna_cb, bt, hsv);
2179         uiBlockEndAlign(block);
2180
2181         if (rgba[3] != FLT_MAX) {
2182                 bt = uiDefButR_prop(block, NUMSLI, 0, IFACE_("A "),  0, -120, butwidth, UI_UNIT_Y, ptr, prop, 3, 0.0, 0.0, 0, 0, TIP_("Alpha"));
2183                 uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
2184         }
2185         else {
2186                 rgba[3] = 1.0f;
2187         }
2188
2189         BLI_snprintf(hexcol, sizeof(hexcol), "%02X%02X%02X", FTOCHAR(rgb_gamma[0]), FTOCHAR(rgb_gamma[1]), FTOCHAR(rgb_gamma[2]));
2190
2191         bt = uiDefBut(block, TEX, 0, IFACE_("Hex: "), 0, -60, butwidth, UI_UNIT_Y, hexcol, 0, 8, 0, 0, TIP_("Hex triplet for color (#RRGGBB)"));
2192         uiButSetFunc(bt, do_hex_rna_cb, bt, hexcol);
2193         uiDefBut(block, LABEL, 0, IFACE_("(Gamma Corrected)"), 0, -80, butwidth, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
2194
2195         rgb_to_hsv_v(rgba, hsv);
2196
2197         picker_new_hide_reveal(block, colormode);
2198 }
2199
2200
2201 static int ui_picker_small_wheel_cb(const bContext *UNUSED(C), uiBlock *block, wmEvent *event)
2202 {
2203         float add = 0.0f;
2204         
2205         if (event->type == WHEELUPMOUSE)
2206                 add = 0.05f;
2207         else if (event->type == WHEELDOWNMOUSE)
2208                 add = -0.05f;
2209         
2210         if (add != 0.0f) {
2211                 uiBut *but;
2212                 
2213                 for (but = block->buttons.first; but; but = but->next) {
2214                         if (but->type == HSVCUBE && but->active == NULL) {
2215                                 uiPopupBlockHandle *popup = block->handle;
2216                                 float rgb[3];
2217                                 float *hsv = ui_block_hsv_get(block);
2218                                 
2219                                 ui_get_but_vectorf(but, rgb);
2220                                 
2221                                 rgb_to_hsv_compat_v(rgb, hsv);
2222                                 hsv[2] = CLAMPIS(hsv[2] + add, 0.0f, 1.0f);
2223                                 hsv_to_rgb_v(hsv, rgb);
2224
2225                                 ui_set_but_vectorf(but, rgb);
2226                                 
2227                                 ui_update_block_buts_rgb(block, rgb);
2228                                 if (popup)
2229                                         popup->menuretval = UI_RETURN_UPDATE;
2230                                 
2231                                 return 1;
2232                         }
2233                 }
2234         }
2235         return 0;
2236 }
2237
2238 uiBlock *ui_block_func_COL(bContext *C, uiPopupBlockHandle *handle, void *arg_but)
2239 {
2240         uiBut *but = arg_but;
2241         uiBlock *block;
2242         
2243         block = uiBeginBlock(C, handle->region, __func__, UI_EMBOSS);
2244         
2245         if (but->rnaprop) {
2246                 if (RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
2247                         block->color_profile = BLI_PR_NONE;
2248                 }
2249         }
2250         
2251         uiBlockSetFlag(block, UI_BLOCK_MOVEMOUSE_QUIT);
2252         
2253         copy_v3_v3(handle->retvec, but->editvec);
2254         
2255         uiBlockPicker(block, handle->retvec, &but->rnapoin, but->rnaprop);
2256         
2257         block->flag = UI_BLOCK_LOOP | UI_BLOCK_REDRAW | UI_BLOCK_KEEP_OPEN | UI_BLOCK_OUT_1;
2258         uiBoundsBlock(block, 10);
2259         
2260         block->block_event_func = ui_picker_small_wheel_cb;
2261         
2262         /* and lets go */
2263         block->direction = UI_TOP;
2264         
2265         return block;
2266 }
2267
2268 /************************ Popup Menu Memory ****************************/
2269
2270 static int ui_popup_string_hash(char *str)
2271 {
2272         /* sometimes button contains hotkey, sometimes not, strip for proper compare */
2273         int hash;
2274         char *delimit = strchr(str, '|');
2275
2276         if (delimit) *delimit = 0;
2277         hash = BLI_ghashutil_strhash(str);
2278         if (delimit) *delimit = '|';
2279
2280         return hash;
2281 }
2282
2283 static int ui_popup_menu_hash(const char *str)
2284 {
2285         return BLI_ghashutil_strhash(str);
2286 }
2287
2288 /* but == NULL read, otherwise set */
2289 uiBut *ui_popup_menu_memory(uiBlock *block, uiBut *but)
2290 {
2291         static int mem[256], first = 1;
2292         int hash = block->puphash;
2293         
2294         if (first) {
2295                 /* init */
2296                 memset(mem, -1, sizeof(mem));
2297                 first = 0;
2298         }
2299
2300         if (but) {
2301                 /* set */
2302                 mem[hash & 255] = ui_popup_string_hash(but->str);
2303                 return NULL;
2304         }
2305         else {
2306                 /* get */
2307                 for (but = block->buttons.first; but; but = but->next)
2308                         if (ui_popup_string_hash(but->str) == mem[hash & 255])
2309                                 return but;
2310
2311                 return NULL;
2312         }
2313 }
2314
2315 /******************** Popup Menu with callback or string **********************/
2316
2317 struct uiPopupMenu {
2318         uiBlock *block;
2319         uiLayout *layout;
2320         uiBut *but;
2321
2322         int mx, my, popup, slideout;
2323         int startx, starty, maxrow;
2324
2325         uiMenuCreateFunc menu_func;
2326         void *menu_arg;
2327 };
2328
2329 static uiBlock *ui_block_func_POPUP(bContext *C, uiPopupBlockHandle *handle, void *arg_pup)
2330 {
2331         uiBlock *block;
2332         uiBut *bt;
2333         uiPopupMenu *pup = arg_pup;
2334         int offset[2], direction, minwidth, width, height, flip;
2335
2336         if (pup->menu_func) {
2337                 pup->block->handle = handle;
2338                 pup->menu_func(C, pup->layout, pup->menu_arg);
2339                 pup->block->handle = NULL;
2340         }
2341
2342         if (pup->but) {
2343                 /* minimum width to enforece */
2344                 minwidth = pup->but->rect.xmax - pup->but->rect.xmin;
2345
2346                 if (pup->but->type == PULLDOWN || pup->but->menu_create_func) {
2347                         direction = UI_DOWN;
2348                         flip = 1;
2349                 }
2350                 else {
2351                         direction = UI_TOP;
2352                         flip = 0;
2353                 }
2354         }
2355         else {
2356                 minwidth = 50;
2357                 direction = UI_DOWN;
2358                 flip = 1;
2359         }
2360
2361         block = pup->block;
2362         
2363         /* in some cases we create the block before the region,
2364          * so we set it delayed here if necessary */
2365         if (BLI_findindex(&handle->region->uiblocks, block) == -1)
2366                 uiBlockSetRegion(block, handle->region);
2367
2368         block->direction = direction;
2369
2370         uiBlockLayoutResolve(block, &width, &height);
2371
2372         uiBlockSetFlag(block, UI_BLOCK_MOVEMOUSE_QUIT);
2373         
2374         if (pup->popup) {
2375                 uiBlockSetFlag(block, UI_BLOCK_LOOP | UI_BLOCK_REDRAW | UI_BLOCK_NUMSELECT | UI_BLOCK_RET_1);
2376                 uiBlockSetDirection(block, direction);
2377
2378                 /* offset the mouse position, possibly based on earlier selection */
2379                 if ((block->flag & UI_BLOCK_POPUP_MEMORY) &&
2380                     (bt = ui_popup_menu_memory(block, NULL)))
2381                 {
2382                         /* position mouse on last clicked item, at 0.8*width of the
2383                          * button, so it doesn't overlap the text too much, also note
2384                          * the offset is negative because we are inverse moving the
2385                          * block to be under the mouse */
2386                         offset[0] = -(bt->rect.xmin + 0.8f * (bt->rect.xmax - bt->rect.xmin));
2387                         offset[1] = -(bt->rect.ymin + 0.5f * UI_UNIT_Y);
2388                 }
2389                 else {
2390                         /* position mouse at 0.8*width of the button and below the tile
2391                          * on the first item */
2392                         offset[0] = 0;
2393                         for (bt = block->buttons.first; bt; bt = bt->next)
2394                                 offset[0] = mini(offset[0], -(bt->rect.xmin + 0.8f * (bt->rect.xmax - bt->rect.xmin)));
2395
2396                         offset[1] = 1.5 * UI_UNIT_Y;
2397                 }
2398
2399                 block->minbounds = minwidth;
2400                 uiMenuPopupBoundsBlock(block, 1, offset[0], offset[1]);
2401         }
2402         else {
2403                 /* for a header menu we set the direction automatic */
2404                 if (!pup->slideout && flip) {
2405                         ScrArea *sa = CTX_wm_area(C);
2406                         if (sa && sa->headertype == HEADERDOWN) {
2407                                 ARegion *ar = CTX_wm_region(C);
2408                                 if (ar && ar->regiontype == RGN_TYPE_HEADER) {
2409                                         uiBlockSetDirection(block, UI_TOP);
2410                                         uiBlockFlipOrder(block);
2411                                 }
2412                         }
2413                 }
2414
2415                 block->minbounds = minwidth;
2416                 uiTextBoundsBlock(block, 50);
2417         }
2418
2419         /* if menu slides out of other menu, override direction */
2420         if (pup->slideout)
2421                 uiBlockSetDirection(block, UI_RIGHT);
2422
2423         uiEndBlock(C, block);
2424
2425         return pup->block;
2426 }
2427
2428 uiPopupBlockHandle *ui_popup_menu_create(bContext *C, ARegion *butregion, uiBut *but,
2429                                          uiMenuCreateFunc menu_func, void *arg, char *str)
2430 {
2431         wmWindow *window = CTX_wm_window(C);
2432         uiStyle *style = UI_GetStyle();
2433         uiPopupBlockHandle *handle;
2434         uiPopupMenu *pup;
2435         pup = MEM_callocN(sizeof(uiPopupMenu), __func__);
2436         pup->block = uiBeginBlock(C, NULL, __func__, UI_EMBOSSP);
2437         pup->layout = uiBlockLayout(pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, style);
2438         pup->slideout = (but && (but->block->flag & UI_BLOCK_LOOP));
2439         pup->but = but;
2440         uiLayoutSetOperatorContext(pup->layout, WM_OP_INVOKE_REGION_WIN);
2441
2442         if (!but) {
2443                 /* no button to start from, means we are a popup */
2444                 pup->mx = window->eventstate->x;
2445                 pup->my = window->eventstate->y;
2446                 pup->popup = 1;
2447                 pup->block->flag |= UI_BLOCK_NO_FLIP;
2448         }
2449         /* some enums reversing is strange, currently we have no good way to
2450          * reverse some enum's but not others, so reverse all so the first menu
2451          * items are always close to the mouse cursor */
2452         else {
2453 #if 0
2454                 /* if this is an rna button then we can assume its an enum
2455                  * flipping enums is generally not good since the order can be
2456                  * important [#28786] */
2457                 if (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_ENUM) {
2458                         pup->block->flag |= UI_BLOCK_NO_FLIP;
2459                 }
2460 #endif
2461                 if (but->context)
2462                         uiLayoutContextCopy(pup->layout, but->context);
2463         }
2464
2465         if (str) {
2466                 /* menu is created from a string */
2467                 pup->menu_func = ui_block_func_MENUSTR;
2468                 pup->menu_arg = str;
2469         }
2470         else {
2471                 /* menu is created from a callback */
2472                 pup->menu_func = menu_func;
2473                 pup->menu_arg = arg;
2474         }
2475         
2476         handle = ui_popup_block_create(C, butregion, but, NULL, ui_block_func_POPUP, pup);
2477
2478         if (!but) {
2479                 handle->popup = 1;
2480
2481                 UI_add_popup_handlers(C, &window->modalhandlers, handle);
2482                 WM_event_add_mousemove(C);
2483         }
2484         
2485         MEM_freeN(pup);
2486
2487         return handle;
2488 }
2489
2490 /******************** Popup Menu API with begin and end ***********************/
2491
2492 /* only return handler, and set optional title */
2493 uiPopupMenu *uiPupMenuBegin(bContext *C, const char *title, int icon)
2494 {
2495         uiStyle *style = UI_GetStyle();
2496         uiPopupMenu *pup = MEM_callocN(sizeof(uiPopupMenu), "popup menu");
2497         uiBut *but;
2498         
2499         pup->block = uiBeginBlock(C, NULL, __func__, UI_EMBOSSP);
2500         pup->block->flag |= UI_BLOCK_POPUP_MEMORY;
2501         pup->block->puphash = ui_popup_menu_hash(title);
2502         pup->layout = uiBlockLayout(pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, style);
2503         uiLayoutSetOperatorContext(pup->layout, WM_OP_EXEC_REGION_WIN);
2504
2505         /* create in advance so we can let buttons point to retval already */
2506         pup->block->handle = MEM_callocN(sizeof(uiPopupBlockHandle), "uiPopupBlockHandle");
2507         
2508         /* create title button */
2509         if (title && title[0]) {
2510                 char titlestr[256];
2511                 
2512                 if (icon) {
2513                         BLI_snprintf(titlestr, sizeof(titlestr), " %s", title);
2514                         uiDefIconTextBut(pup->block, LABEL, 0, icon, titlestr, 0, 0, 200, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
2515                 }
2516                 else {
2517                         but = uiDefBut(pup->block, LABEL, 0, title, 0, 0, 200, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
2518                         but->flag = UI_TEXT_LEFT;
2519                 }
2520         }
2521
2522         return pup;
2523 }
2524
2525 /* set the whole structure to work */
2526 void uiPupMenuEnd(bContext *C, uiPopupMenu *pup)
2527 {
2528         wmWindow *window = CTX_wm_window(C);
2529         uiPopupBlockHandle *menu;
2530         
2531         pup->popup = 1;
2532         pup->mx = window->eventstate->x;
2533         pup->my = window->eventstate->y;
2534         
2535         menu = ui_popup_block_create(C, NULL, NULL, NULL, ui_block_func_POPUP, pup);
2536         menu->popup = 1;
2537         
2538         UI_add_popup_handlers(C, &window->modalhandlers, menu);
2539         WM_event_add_mousemove(C);
2540         
2541         MEM_freeN(pup);
2542 }
2543
2544 uiLayout *uiPupMenuLayout(uiPopupMenu *pup)
2545 {
2546         return pup->layout;
2547 }
2548
2549 /*************************** Standard Popup Menus ****************************/
2550
2551 static void operator_name_cb(bContext *C, void *arg, int retval)
2552 {
2553         const char *opname = arg;
2554
2555         if (opname && retval > 0)
2556                 WM_operator_name_call(C, opname, WM_OP_EXEC_DEFAULT, NULL);
2557 }
2558
2559 static void operator_cb(bContext *C, void *arg, int retval)
2560 {
2561         wmOperator *op = arg;
2562         
2563         if (op && retval > 0)
2564                 WM_operator_call(C, op);
2565         else
2566                 WM_operator_free(op);
2567 }
2568
2569 static void confirm_cancel_operator(void *opv)
2570 {
2571         WM_operator_free(opv);
2572 }
2573
2574 static void vconfirm_opname(bContext *C, const char *opname, const char *title, const char *itemfmt, va_list ap)
2575 {
2576         uiPopupBlockHandle *handle;
2577         char *s, buf[512];
2578
2579         s = buf;
2580         if (title) s += sprintf(s, "%s%%t|", title);
2581         vsnprintf(s, sizeof(buf) - (s - buf), itemfmt, ap);
2582         buf[sizeof(buf) - 1] = '\0';
2583
2584         handle = ui_popup_menu_create(C, NULL, NULL, NULL, NULL, buf);
2585
2586         handle->popup_func = operator_name_cb;
2587         handle->popup_arg = (void *)opname;
2588 }
2589
2590 static void confirm_operator(bContext *C, wmOperator *op, const char *title, const char *item)
2591 {
2592         uiPopupBlockHandle *handle;
2593         char *s, buf[512];
2594         
2595         s = buf;
2596         if (title) s += BLI_snprintf(s, sizeof(buf), "%s%%t|%s", title, item);
2597         (void)s;
2598         
2599         handle = ui_popup_menu_create(C, NULL, NULL, NULL, NULL, buf);
2600
2601         handle->popup_func = operator_cb;
2602         handle->popup_arg = op;
2603         handle->cancel_func = confirm_cancel_operator;
2604 }
2605
2606 void uiPupMenuOkee(bContext *C, const char *opname, const char *str, ...)
2607 {
2608         va_list ap;
2609         char titlestr[256];
2610
2611         BLI_snprintf(titlestr, sizeof(titlestr), "OK? %%i%d", ICON_QUESTION);
2612
2613         va_start(ap, str);
2614         vconfirm_opname(C, opname, titlestr, str, ap);
2615         va_end(ap);
2616 }
2617
2618 /* note, only call this is the file exists,
2619  * the case where the file does not exist so can be saved without a
2620  * popup must be checked for already, since saving from here
2621  * will free the operator which will break invoke().
2622  * The operator state for this is implicitly OPERATOR_RUNNING_MODAL */
2623 void uiPupMenuSaveOver(bContext *C, wmOperator *op, const char *filename)
2624 {
2625         confirm_operator(C, op, "Save Over?", filename);
2626 }
2627
2628 void uiPupMenuNotice(bContext *C, const char *str, ...)
2629 {
2630         va_list ap;
2631
2632         va_start(ap, str);
2633         vconfirm_opname(C, NULL, NULL, str, ap);
2634         va_end(ap);
2635 }
2636
2637 void uiPupMenuError(bContext *C, const char *str, ...)
2638 {
2639         va_list ap;
2640         char nfmt[256];
2641         char titlestr[256];
2642
2643         BLI_snprintf(titlestr, sizeof(titlestr), "Error %%i%d", ICON_ERROR);
2644
2645         BLI_strncpy(nfmt, str, sizeof(nfmt));
2646
2647         va_start(ap, str);
2648         vconfirm_opname(C, NULL, titlestr, nfmt, ap);
2649         va_end(ap);
2650 }
2651
2652 void uiPupMenuReports(bContext *C, ReportList *reports)
2653 {
2654         Report *report;
2655         DynStr *ds;
2656         char *str;
2657
2658         if (!reports || !reports->list.first)
2659                 return;
2660         if (!CTX_wm_window(C))
2661                 return;
2662
2663         ds = BLI_dynstr_new();
2664
2665         for (report = reports->list.first; report; report = report->next) {
2666                 if (report->type < reports->printlevel)
2667                         ;  /* pass */
2668                 else if (report->type >= RPT_ERROR)
2669                         BLI_dynstr_appendf(ds, "Error %%i%d%%t|%s", ICON_ERROR, report->message);
2670                 else if (report->type >= RPT_WARNING)
2671                         BLI_dynstr_appendf(ds, "Warning %%i%d%%t|%s", ICON_ERROR, report->message);
2672                 else if (report->type >= RPT_INFO)
2673                         BLI_dynstr_appendf(ds, "Info %%i%d%%t|%s", ICON_INFO, report->message);
2674         }
2675
2676         str = BLI_dynstr_get_cstring(ds);
2677         if (str[0] != '\0')
2678                 ui_popup_menu_create(C, NULL, NULL, NULL, NULL, str);
2679         MEM_freeN(str);
2680
2681         BLI_dynstr_free(ds);
2682 }
2683
2684 void uiPupMenuInvoke(bContext *C, const char *idname)
2685 {
2686         uiPopupMenu *pup;
2687         uiLayout *layout;
2688         Menu menu;
2689         MenuType *mt = WM_menutype_find(idname, TRUE);
2690
2691         if (mt == NULL) {
2692                 printf("%s: named menu \"%s\" not found\n", __func__, idname);
2693                 return;
2694         }
2695
2696         if (mt->poll && mt->poll(C, mt) == 0)
2697                 return;
2698
2699         pup = uiPupMenuBegin(C, IFACE_(mt->label), ICON_NONE);
2700         layout = uiPupMenuLayout(pup);
2701
2702         menu.layout = layout;
2703         menu.type = mt;
2704
2705         mt->draw(C, &menu);
2706
2707         uiPupMenuEnd(C, pup);
2708 }
2709
2710
2711 /*************************** Popup Block API **************************/
2712
2713 void uiPupBlockO(bContext *C, uiBlockCreateFunc func, void *arg, const char *opname, int opcontext)
2714 {
2715         wmWindow *window = CTX_wm_window(C);
2716         uiPopupBlockHandle *handle;
2717         
2718         handle = ui_popup_block_create(C, NULL, NULL, func, NULL, arg);
2719         handle->popup = 1;
2720         handle->optype = (opname) ? WM_operatortype_find(opname, 0) : NULL;
2721         handle->opcontext = opcontext;
2722         
2723         UI_add_popup_handlers(C, &window->modalhandlers, handle);
2724         WM_event_add_mousemove(C);
2725 }
2726
2727 void uiPupBlock(bContext *C, uiBlockCreateFunc func, void *arg)
2728 {
2729         uiPupBlockO(C, func, arg, NULL, 0);
2730 }
2731
2732 void uiPupBlockEx(bContext *C, uiBlockCreateFunc func, uiBlockHandleFunc popup_func, uiBlockCancelFunc cancel_func, void *arg)
2733 {
2734         wmWindow *window = CTX_wm_window(C);
2735         uiPopupBlockHandle *handle;
2736         
2737         handle = ui_popup_block_create(C, NULL, NULL, func, NULL, arg);
2738         handle->popup = 1;
2739         handle->retvalue = 1;
2740
2741         handle->popup_arg = arg;
2742         handle->popup_func = popup_func;
2743         handle->cancel_func = cancel_func;
2744         // handle->opcontext = opcontext;
2745         
2746         UI_add_popup_handlers(C, &window->modalhandlers, handle);
2747         WM_event_add_mousemove(C);
2748 }
2749
2750 #if 0 /* UNUSED */
2751 void uiPupBlockOperator(bContext *C, uiBlockCreateFunc func, wmOperator *op, int opcontext)
2752 {
2753         wmWindow *window = CTX_wm_window(C);
2754         uiPopupBlockHandle *handle;
2755         
2756         handle = ui_popup_block_create(C, NULL, NULL, func, NULL, op);
2757         handle->popup = 1;
2758         handle->retvalue = 1;
2759
2760         handle->popup_arg = op;
2761         handle->popup_func = operator_cb;
2762         handle->cancel_func = confirm_cancel_operator;
2763         handle->opcontext = opcontext;
2764         
2765         UI_add_popup_handlers(C, &window->modalhandlers, handle);
2766         WM_event_add_mousemove(C);
2767 }
2768 #endif
2769
2770 void uiPupBlockClose(bContext *C, uiBlock *block)
2771 {
2772         if (block->handle) {
2773                 wmWindow *win = CTX_wm_window(C);
2774
2775                 /* if loading new .blend while popup is open, window will be NULL */
2776                 if (win) {
2777                         UI_remove_popup_handlers(&win->modalhandlers, block->handle);
2778                         ui_popup_block_free(C, block->handle);
2779                 }
2780         }
2781 }
2782
2783 float *ui_block_hsv_get(uiBlock *block)
2784 {
2785         return block->_hsv;
2786 }