Cleanup: remove unused context args
[blender.git] / source / blender / editors / interface / interface_region_tooltip.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2008 Blender Foundation.
19  * All rights reserved.
20  *
21  * Contributor(s): Blender Foundation
22  *
23  * ***** END GPL LICENSE BLOCK *****
24  */
25
26 /** \file blender/editors/interface/interface_region_tooltip.c
27  *  \ingroup edinterface
28  *
29  * ToolTip Region and Construction
30  */
31
32 /* TODO(campbell):
33  * We may want to have a higher level API that initializes a timer,
34  * checks for mouse motion and clears the tool-tip afterwards.
35  * We never want multiple tool-tips at once so this could be handled on the window / window-manager level.
36  *
37  * For now it's not a priority, so leave as-is.
38  */
39
40 #include <stdarg.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <assert.h>
44
45 #include "MEM_guardedalloc.h"
46
47 #include "DNA_userdef_types.h"
48
49 #include "BLI_math.h"
50 #include "BLI_string.h"
51 #include "BLI_string_utf8.h"
52 #include "BLI_rect.h"
53 #include "BLI_utildefines.h"
54
55 #include "BKE_context.h"
56 #include "BKE_screen.h"
57
58 #include "WM_api.h"
59 #include "WM_types.h"
60
61 #include "RNA_access.h"
62
63 #include "BIF_gl.h"
64
65 #include "UI_interface.h"
66
67 #include "BLF_api.h"
68 #include "BLT_translation.h"
69
70 #include "ED_screen.h"
71
72 #include "interface_intern.h"
73 #include "interface_regions_intern.h"
74
75 #define UI_TIP_PAD_FAC      1.3f
76 #define UI_TIP_PADDING      (int)(UI_TIP_PAD_FAC * UI_UNIT_Y)
77 #define UI_TIP_MAXWIDTH     600
78
79
80 typedef struct uiTooltipFormat {
81         enum {
82                 UI_TIP_STYLE_NORMAL = 0,
83                 UI_TIP_STYLE_HEADER,
84                 UI_TIP_STYLE_MONO,
85         } style : 3;
86         enum {
87                 UI_TIP_LC_MAIN = 0,     /* primary text */
88                 UI_TIP_LC_VALUE,        /* the value of buttons (also shortcuts) */
89                 UI_TIP_LC_ACTIVE,       /* titles of active enum values */
90                 UI_TIP_LC_NORMAL,       /* regular text */
91                 UI_TIP_LC_PYTHON,       /* Python snippet */
92                 UI_TIP_LC_ALERT,        /* description of why operator can't run */
93         } color_id : 4;
94         int is_pad : 1;
95 } uiTooltipFormat;
96
97 typedef struct uiTooltipField {
98         char *text;
99         char *text_suffix;
100         struct {
101                 uint x_pos;     /* x cursor position at the end of the last line */
102                 uint lines;     /* number of lines, 1 or more with word-wrap */
103         } geom;
104         uiTooltipFormat format;
105
106 } uiTooltipField;
107
108 typedef struct uiTooltipData {
109         rcti bbox;
110         uiTooltipField *fields;
111         uint            fields_len;
112         uiFontStyle fstyle;
113         int wrap_width;
114         int toth, lineh;
115 } uiTooltipData;
116
117 #define UI_TIP_LC_MAX 6
118
119 BLI_STATIC_ASSERT(UI_TIP_LC_MAX == UI_TIP_LC_ALERT + 1, "invalid lc-max");
120 BLI_STATIC_ASSERT(sizeof(uiTooltipFormat) <= sizeof(int), "oversize");
121
122 static uiTooltipField *text_field_add_only(
123         uiTooltipData *data)
124 {
125         data->fields_len += 1;
126         data->fields = MEM_recallocN(data->fields, sizeof(*data->fields) * data->fields_len);
127         return &data->fields[data->fields_len - 1];
128 }
129
130 static uiTooltipField *text_field_add(
131         uiTooltipData *data,
132         const uiTooltipFormat *format)
133 {
134         uiTooltipField *field = text_field_add_only(data);
135         field->format = *format;
136         return field;
137 }
138
139 /* -------------------------------------------------------------------- */
140 /** \name ToolTip Callbacks (Draw & Free)
141  * \{ */
142
143 static void rgb_tint(
144         float col[3],
145         float h, float h_strength,
146         float v, float v_strength)
147 {
148         float col_hsv_from[3];
149         float col_hsv_to[3];
150
151         rgb_to_hsv_v(col, col_hsv_from);
152
153         col_hsv_to[0] = h;
154         col_hsv_to[1] = h_strength;
155         col_hsv_to[2] = (col_hsv_from[2] * (1.0f - v_strength)) + (v * v_strength);
156
157         hsv_to_rgb_v(col_hsv_to, col);
158 }
159
160 static void ui_tooltip_region_draw_cb(const bContext *UNUSED(C), ARegion *ar)
161 {
162         const float pad_px = UI_TIP_PADDING;
163         uiTooltipData *data = ar->regiondata;
164         uiWidgetColors *theme = ui_tooltip_get_theme();
165         rcti bbox = data->bbox;
166         float tip_colors[UI_TIP_LC_MAX][3];
167         unsigned char drawcol[4] = {0, 0, 0, 255}; /* to store color in while drawing (alpha is always 255) */
168
169         float *main_color    = tip_colors[UI_TIP_LC_MAIN]; /* the color from the theme */
170         float *value_color   = tip_colors[UI_TIP_LC_VALUE];
171         float *active_color  = tip_colors[UI_TIP_LC_ACTIVE];
172         float *normal_color  = tip_colors[UI_TIP_LC_NORMAL];
173         float *python_color  = tip_colors[UI_TIP_LC_PYTHON];
174         float *alert_color   = tip_colors[UI_TIP_LC_ALERT];
175
176         float background_color[3];
177         float tone_bg;
178         int i;
179
180         wmOrtho2_region_pixelspace(ar);
181
182         /* draw background */
183         ui_draw_tooltip_background(UI_style_get(), NULL, &bbox);
184
185         /* set background_color */
186         rgb_uchar_to_float(background_color, (const unsigned char *)theme->inner);
187
188         /* calculate normal_color */
189         rgb_uchar_to_float(main_color, (const unsigned char *)theme->text);
190         copy_v3_v3(active_color, main_color);
191         copy_v3_v3(normal_color, main_color);
192         copy_v3_v3(python_color, main_color);
193         copy_v3_v3(alert_color, main_color);
194         copy_v3_v3(value_color, main_color);
195
196         /* find the brightness difference between background and text colors */
197
198         tone_bg = rgb_to_grayscale(background_color);
199         /* tone_fg = rgb_to_grayscale(main_color); */
200
201         /* mix the colors */
202         rgb_tint(value_color,  0.0f, 0.0f, tone_bg, 0.2f);  /* light gray */
203         rgb_tint(active_color, 0.6f, 0.2f, tone_bg, 0.2f);  /* light blue */
204         rgb_tint(normal_color, 0.0f, 0.0f, tone_bg, 0.4f);  /* gray       */
205         rgb_tint(python_color, 0.0f, 0.0f, tone_bg, 0.5f);  /* dark gray  */
206         rgb_tint(alert_color,  0.0f, 0.8f, tone_bg, 0.1f);  /* red        */
207
208         /* draw text */
209         BLF_wordwrap(data->fstyle.uifont_id, data->wrap_width);
210         BLF_wordwrap(blf_mono_font, data->wrap_width);
211
212         bbox.xmin += 0.5f * pad_px;  /* add padding to the text */
213         bbox.ymax -= 0.25f * pad_px;
214
215         for (i = 0; i < data->fields_len; i++) {
216                 const uiTooltipField *field = &data->fields[i];
217                 const uiTooltipField *field_next = (i + 1) != data->fields_len ? &data->fields[i + 1] : NULL;
218
219                 bbox.ymin = bbox.ymax - (data->lineh * field->geom.lines);
220                 if (field->format.style == UI_TIP_STYLE_HEADER) {
221                         /* draw header and active data (is done here to be able to change color) */
222                         uiFontStyle fstyle_header = data->fstyle;
223
224                         /* override text-style */
225                         fstyle_header.shadow = 1;
226                         fstyle_header.shadowcolor = rgb_to_grayscale(tip_colors[UI_TIP_LC_MAIN]);
227                         fstyle_header.shadx = fstyle_header.shady = 0;
228                         fstyle_header.shadowalpha = 1.0f;
229                         fstyle_header.word_wrap = true;
230
231                         rgb_float_to_uchar(drawcol, tip_colors[UI_TIP_LC_MAIN]);
232                         UI_fontstyle_set(&fstyle_header);
233                         UI_fontstyle_draw(&fstyle_header, &bbox, field->text, drawcol);
234
235                         fstyle_header.shadow = 0;
236
237                         /* offset to the end of the last line */
238                         if (field->text_suffix) {
239                                 float xofs = field->geom.x_pos;
240                                 float yofs = data->lineh * (field->geom.lines - 1);
241                                 bbox.xmin += xofs;
242                                 bbox.ymax -= yofs;
243
244                                 rgb_float_to_uchar(drawcol, tip_colors[UI_TIP_LC_ACTIVE]);
245                                 UI_fontstyle_draw(&fstyle_header, &bbox, field->text_suffix, drawcol);
246
247                                 /* undo offset */
248                                 bbox.xmin -= xofs;
249                                 bbox.ymax += yofs;
250                         }
251                 }
252                 else if (field->format.style == UI_TIP_STYLE_MONO) {
253                         uiFontStyle fstyle_mono = data->fstyle;
254                         fstyle_mono.uifont_id = blf_mono_font;
255                         fstyle_mono.word_wrap = true;
256
257                         UI_fontstyle_set(&fstyle_mono);
258                         /* XXX, needed because we dont have mono in 'U.uifonts' */
259                         BLF_size(fstyle_mono.uifont_id, fstyle_mono.points * U.pixelsize, U.dpi);
260                         rgb_float_to_uchar(drawcol, tip_colors[field->format.color_id]);
261                         UI_fontstyle_draw(&fstyle_mono, &bbox, field->text, drawcol);
262                 }
263                 else {
264                         uiFontStyle fstyle_normal = data->fstyle;
265                         BLI_assert(field->format.style == UI_TIP_STYLE_NORMAL);
266                         fstyle_normal.word_wrap = true;
267
268                         /* draw remaining data */
269                         rgb_float_to_uchar(drawcol, tip_colors[field->format.color_id]);
270                         UI_fontstyle_set(&fstyle_normal);
271                         UI_fontstyle_draw(&fstyle_normal, &bbox, field->text, drawcol);
272                 }
273
274                 bbox.ymax -= data->lineh * field->geom.lines;
275
276                 if (field_next && field_next->format.is_pad) {
277                         bbox.ymax -= data->lineh * (UI_TIP_PAD_FAC - 1);
278                 }
279         }
280
281         BLF_disable(data->fstyle.uifont_id, BLF_WORD_WRAP);
282         BLF_disable(blf_mono_font, BLF_WORD_WRAP);
283 }
284
285 static void ui_tooltip_region_free_cb(ARegion *ar)
286 {
287         uiTooltipData *data;
288
289         data = ar->regiondata;
290
291         for (int  i = 0; i < data->fields_len; i++) {
292                 const uiTooltipField *field = &data->fields[i];
293                 MEM_freeN(field->text);
294                 if (field->text_suffix) {
295                         MEM_freeN(field->text_suffix);
296                 }
297         }
298         MEM_freeN(data->fields);
299         MEM_freeN(data);
300         ar->regiondata = NULL;
301 }
302
303 /** \} */
304
305 /* -------------------------------------------------------------------- */
306 /** \name ToolTip Creation
307  * \{ */
308
309 static uiTooltipData *ui_tooltip_data_from_keymap(bContext *C, wmKeyMap *keymap)
310 {
311         char buf[512];
312
313         /* create tooltip data */
314         uiTooltipData *data = MEM_callocN(sizeof(uiTooltipData), "uiTooltipData");
315
316         for (wmKeyMapItem *kmi = keymap->items.first; kmi; kmi = kmi->next) {
317                 wmOperatorType *ot = WM_operatortype_find(kmi->idname, true);
318                 if (ot != NULL) {
319                         /* Tip */
320                         {
321                                 uiTooltipField *field = text_field_add(
322                                         data, &(uiTooltipFormat){
323                                             .style = UI_TIP_STYLE_NORMAL,
324                                             .color_id = UI_TIP_LC_MAIN,
325                                             .is_pad = true,
326                                         });
327                                 field->text = BLI_strdup(ot->description[0] ? ot->description : ot->name);
328                         }
329                         /* Shortcut */
330                         {
331                                 uiTooltipField *field = text_field_add(
332                                         data, &(uiTooltipFormat){
333                                             .style = UI_TIP_STYLE_NORMAL,
334                                             .color_id = UI_TIP_LC_NORMAL,
335                                         });
336                                 bool found = false;
337                                 if (WM_keymap_item_to_string(kmi, false, buf, sizeof(buf))) {
338                                         found = true;
339                                 }
340                                 field->text = BLI_sprintfN(TIP_("Shortcut: %s"), found ? buf : "None");
341                         }
342
343                         /* Python */
344                         {
345                                 uiTooltipField *field = text_field_add(
346                                         data, &(uiTooltipFormat){
347                                             .style = UI_TIP_STYLE_NORMAL,
348                                             .color_id = UI_TIP_LC_PYTHON,
349                                         });
350                                 char *str = WM_operator_pystring_ex(C, NULL, false, false, ot, kmi->ptr);
351                                 WM_operator_pystring_abbreviate(str, 32);
352                                 field->text = BLI_sprintfN(TIP_("Python: %s"), str);
353                                 MEM_freeN(str);
354                         }
355                 }
356         }
357         if (data->fields_len == 0) {
358                 MEM_freeN(data);
359                 return NULL;
360         }
361         else {
362                 return data;
363         }
364 }
365
366 static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but)
367 {
368         uiStringInfo but_tip = {BUT_GET_TIP, NULL};
369         uiStringInfo enum_label = {BUT_GET_RNAENUM_LABEL, NULL};
370         uiStringInfo enum_tip = {BUT_GET_RNAENUM_TIP, NULL};
371         uiStringInfo op_keymap = {BUT_GET_OP_KEYMAP, NULL};
372         uiStringInfo prop_keymap = {BUT_GET_PROP_KEYMAP, NULL};
373         uiStringInfo rna_struct = {BUT_GET_RNASTRUCT_IDENTIFIER, NULL};
374         uiStringInfo rna_prop = {BUT_GET_RNAPROP_IDENTIFIER, NULL};
375
376         char buf[512];
377
378         /* create tooltip data */
379         uiTooltipData *data = MEM_callocN(sizeof(uiTooltipData), "uiTooltipData");
380
381         UI_but_string_info_get(C, but, &but_tip, &enum_label, &enum_tip, &op_keymap, &prop_keymap, &rna_struct, &rna_prop, NULL);
382
383         /* Tip */
384         if (but_tip.strinfo) {
385                 {
386                         uiTooltipField *field = text_field_add(
387                                 data, &(uiTooltipFormat){
388                                     .style = UI_TIP_STYLE_HEADER,
389                                     .color_id = UI_TIP_LC_NORMAL,
390                                 });
391                         if (enum_label.strinfo) {
392                                 field->text = BLI_sprintfN("%s:  ", but_tip.strinfo);
393                                 field->text_suffix = BLI_strdup(enum_label.strinfo);
394                         }
395                         else {
396                                 field->text = BLI_sprintfN("%s.", but_tip.strinfo);
397                         }
398                 }
399
400                 /* special case enum rna buttons */
401                 if ((but->type & UI_BTYPE_ROW) && but->rnaprop && RNA_property_flag(but->rnaprop) & PROP_ENUM_FLAG) {
402                         uiTooltipField *field = text_field_add(
403                                 data, &(uiTooltipFormat){
404                                     .style = UI_TIP_STYLE_NORMAL,
405                                     .color_id = UI_TIP_LC_NORMAL,
406                                 });
407                         field->text = BLI_strdup(IFACE_("(Shift-Click/Drag to select multiple)"));
408                 }
409
410         }
411         /* Enum field label & tip */
412         if (enum_tip.strinfo) {
413                 uiTooltipField *field = text_field_add(
414                         data, &(uiTooltipFormat){
415                             .style = UI_TIP_STYLE_NORMAL,
416                             .color_id = UI_TIP_LC_VALUE,
417                             .is_pad = true,
418                         });
419                 field->text = BLI_strdup(enum_tip.strinfo);
420         }
421
422         /* Op shortcut */
423         if (op_keymap.strinfo) {
424                 uiTooltipField *field = text_field_add(
425                         data, &(uiTooltipFormat){
426                             .style = UI_TIP_STYLE_NORMAL,
427                             .color_id = UI_TIP_LC_VALUE,
428                             .is_pad = true,
429                         });
430                 field->text = BLI_sprintfN(TIP_("Shortcut: %s"), op_keymap.strinfo);
431         }
432
433         /* Property context-toggle shortcut */
434         if (prop_keymap.strinfo) {
435                 uiTooltipField *field = text_field_add(
436                         data, &(uiTooltipFormat){
437                             .style = UI_TIP_STYLE_NORMAL,
438                             .color_id = UI_TIP_LC_VALUE,
439                             .is_pad = true,
440                         });
441                 field->text = BLI_sprintfN(TIP_("Shortcut: %s"), prop_keymap.strinfo);
442         }
443
444         if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
445                 /* better not show the value of a password */
446                 if ((but->rnaprop && (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD)) == 0) {
447                         /* full string */
448                         ui_but_string_get(but, buf, sizeof(buf));
449                         if (buf[0]) {
450                                 uiTooltipField *field = text_field_add(
451                                         data, &(uiTooltipFormat){
452                                             .style = UI_TIP_STYLE_NORMAL,
453                                             .color_id = UI_TIP_LC_VALUE,
454                                             .is_pad = true,
455                                         });
456                                 field->text = BLI_sprintfN(TIP_("Value: %s"), buf);
457                         }
458                 }
459         }
460
461         if (but->rnaprop) {
462                 int unit_type = UI_but_unit_type_get(but);
463
464                 if (unit_type == PROP_UNIT_ROTATION) {
465                         if (RNA_property_type(but->rnaprop) == PROP_FLOAT) {
466                                 float value = RNA_property_array_check(but->rnaprop) ?
467                                                   RNA_property_float_get_index(&but->rnapoin, but->rnaprop, but->rnaindex) :
468                                                   RNA_property_float_get(&but->rnapoin, but->rnaprop);
469
470                                 uiTooltipField *field = text_field_add(
471                                         data, &(uiTooltipFormat){
472                                             .style = UI_TIP_STYLE_NORMAL,
473                                             .color_id = UI_TIP_LC_VALUE,
474                                         });
475                                 field->text = BLI_sprintfN(TIP_("Radians: %f"), value);
476                         }
477                 }
478
479                 if (but->flag & UI_BUT_DRIVEN) {
480                         if (ui_but_anim_expression_get(but, buf, sizeof(buf))) {
481                                 uiTooltipField *field = text_field_add(
482                                         data, &(uiTooltipFormat){
483                                             .style = UI_TIP_STYLE_NORMAL,
484                                             .color_id = UI_TIP_LC_NORMAL,
485                                         });
486                                 field->text = BLI_sprintfN(TIP_("Expression: %s"), buf);
487                         }
488                 }
489
490                 if (but->rnapoin.id.data) {
491                         const ID *id = but->rnapoin.id.data;
492                         if (ID_IS_LINKED(id)) {
493                                 uiTooltipField *field = text_field_add(
494                                         data, &(uiTooltipFormat){
495                                             .style = UI_TIP_STYLE_NORMAL,
496                                             .color_id = UI_TIP_LC_NORMAL,
497                                         });
498                                 field->text = BLI_sprintfN(TIP_("Library: %s"), id->lib->name);
499                         }
500                 }
501         }
502         else if (but->optype) {
503                 PointerRNA *opptr;
504                 char *str;
505                 opptr = UI_but_operator_ptr_get(but); /* allocated when needed, the button owns it */
506
507                 /* so the context is passed to fieldf functions (some py fieldf functions use it) */
508                 WM_operator_properties_sanitize(opptr, false);
509
510                 str = WM_operator_pystring_ex(C, NULL, false, false, but->optype, opptr);
511
512                 /* avoid overly verbose tips (eg, arrays of 20 layers), exact limit is arbitrary */
513                 WM_operator_pystring_abbreviate(str, 32);
514
515                 /* operator info */
516                 if (U.flag & USER_TOOLTIPS_PYTHON) {
517                         uiTooltipField *field = text_field_add(
518                                 data, &(uiTooltipFormat){
519                                     .style = UI_TIP_STYLE_MONO,
520                                     .color_id = UI_TIP_LC_PYTHON,
521                                     .is_pad = true,
522                                 });
523                         field->text = BLI_sprintfN(TIP_("Python: %s"), str);
524                 }
525
526                 MEM_freeN(str);
527         }
528
529         /* button is disabled, we may be able to tell user why */
530         if (but->flag & UI_BUT_DISABLED) {
531                 const char *disabled_msg = NULL;
532
533                 /* if operator poll check failed, it can give pretty precise info why */
534                 if (but->optype) {
535                         CTX_wm_operator_poll_msg_set(C, NULL);
536                         WM_operator_poll_context(C, but->optype, but->opcontext);
537                         disabled_msg = CTX_wm_operator_poll_msg_get(C);
538                 }
539                 /* alternatively, buttons can store some reasoning too */
540                 else if (but->disabled_info) {
541                         disabled_msg = TIP_(but->disabled_info);
542                 }
543
544                 if (disabled_msg && disabled_msg[0]) {
545                         uiTooltipField *field = text_field_add(
546                                 data, &(uiTooltipFormat){
547                                     .style = UI_TIP_STYLE_NORMAL,
548                                     .color_id = UI_TIP_LC_ALERT,
549                                 });
550                         field->text = BLI_sprintfN(TIP_("Disabled: %s"), disabled_msg);
551                 }
552         }
553
554         if ((U.flag & USER_TOOLTIPS_PYTHON) && !but->optype && rna_struct.strinfo) {
555                 {
556                         uiTooltipField *field = text_field_add(
557                                 data, &(uiTooltipFormat){
558                                     .style = UI_TIP_STYLE_MONO,
559                                     .color_id = UI_TIP_LC_PYTHON,
560                                     .is_pad = true,
561                                 });
562                         if (rna_prop.strinfo) {
563                                 /* Struct and prop */
564                                 field->text = BLI_sprintfN(TIP_("Python: %s.%s"), rna_struct.strinfo, rna_prop.strinfo);
565                         }
566                         else {
567                                 /* Only struct (e.g. menus) */
568                                 field->text = BLI_sprintfN(TIP_("Python: %s"), rna_struct.strinfo);
569                         }
570                 }
571
572                 if (but->rnapoin.id.data) {
573                         uiTooltipField *field = text_field_add(
574                                 data, &(uiTooltipFormat){
575                                     .style = UI_TIP_STYLE_MONO,
576                                     .color_id = UI_TIP_LC_PYTHON,
577                                 });
578
579                         /* this could get its own 'BUT_GET_...' type */
580
581                         /* never fails */
582                         /* move ownership (no need for re-alloc) */
583                         if (but->rnaprop) {
584                                 field->text = RNA_path_full_property_py_ex(&but->rnapoin, but->rnaprop, but->rnaindex, true);
585                         }
586                         else {
587                                 field->text = RNA_path_full_struct_py(&but->rnapoin);
588                         }
589
590                 }
591         }
592
593         /* Free strinfo's... */
594         if (but_tip.strinfo)
595                 MEM_freeN(but_tip.strinfo);
596         if (enum_label.strinfo)
597                 MEM_freeN(enum_label.strinfo);
598         if (enum_tip.strinfo)
599                 MEM_freeN(enum_tip.strinfo);
600         if (op_keymap.strinfo)
601                 MEM_freeN(op_keymap.strinfo);
602         if (prop_keymap.strinfo)
603                 MEM_freeN(prop_keymap.strinfo);
604         if (rna_struct.strinfo)
605                 MEM_freeN(rna_struct.strinfo);
606         if (rna_prop.strinfo)
607                 MEM_freeN(rna_prop.strinfo);
608
609         if (data->fields_len == 0) {
610                 MEM_freeN(data);
611                 return NULL;
612         }
613         else {
614                 return data;
615         }
616 }
617
618 static uiTooltipData *ui_tooltip_data_from_manipulator(bContext *C, wmManipulator *mpr)
619 {
620         uiTooltipData *data = MEM_callocN(sizeof(uiTooltipData), "uiTooltipData");
621
622         /* TODO(campbell): a way for manipulators to have their own descriptions (low priority). */
623
624         /* Operator Actions */
625         {
626                 bool use_drag = mpr->drag_part != -1 && mpr->highlight_part != mpr->drag_part;
627
628                 const struct {
629                         int part;
630                         const char *prefix;
631                 } mpop_actions[] = {
632                         {
633                                 .part = mpr->highlight_part,
634                                 .prefix = use_drag ? TIP_("Click") : NULL,
635                         }, {
636                                 .part = use_drag ? mpr->drag_part : -1,
637                                 .prefix = use_drag ? TIP_("Drag") : NULL,
638                         },
639                 };
640
641                 for (int i = 0; i < ARRAY_SIZE(mpop_actions); i++) {
642                         wmManipulatorOpElem *mpop = (mpop_actions[i].part != -1) ? WM_manipulator_operator_get(mpr, mpop_actions[i].part) : NULL;
643                         if (mpop != NULL) {
644                                 /* Description */
645                                 const char *info = RNA_struct_ui_description(mpop->type->srna);
646                                 if (!(info && info[0])) {
647                                         info  = RNA_struct_ui_name(mpop->type->srna);
648                                 }
649
650                                 if (info && info[0]) {
651                                         char *text = NULL;
652                                         if (mpop_actions[i].prefix != NULL) {
653                                                 text = BLI_sprintfN("%s: %s", mpop_actions[i].prefix, info);
654                                         }
655                                         else {
656                                                 text = BLI_strdup(info);
657                                         }
658
659                                         if (text != NULL) {
660                                                 uiTooltipField *field = text_field_add(
661                                                         data, &(uiTooltipFormat){
662                                                             .style = UI_TIP_STYLE_HEADER,
663                                                             .color_id = UI_TIP_LC_VALUE,
664                                                             .is_pad = true,
665                                                         });
666                                                 field->text = text;
667                                         }
668                                 }
669
670                                 /* Shortcut */
671                                 {
672                                         bool found = false;
673                                         IDProperty *prop = mpop->ptr.data;
674                                         char buf[128];
675                                         if (WM_key_event_operator_string(
676                                                     C, mpop->type->idname, WM_OP_INVOKE_DEFAULT, prop, true,
677                                                     buf, ARRAY_SIZE(buf)))
678                                         {
679                                                 found = true;
680                                         }
681                                         uiTooltipField *field = text_field_add(
682                                                 data, &(uiTooltipFormat){
683                                                     .style = UI_TIP_STYLE_NORMAL,
684                                                     .color_id = UI_TIP_LC_VALUE,
685                                                     .is_pad = true,
686                                                 });
687                                         field->text = BLI_sprintfN(TIP_("Shortcut: %s"), found ? buf : "None");
688                                 }
689                         }
690                 }
691         }
692
693         /* Property Actions */
694         if (mpr->type->target_property_defs_len) {
695                 wmManipulatorProperty *mpr_prop_array = WM_manipulator_target_property_array(mpr);
696                 for (int i = 0; i < mpr->type->target_property_defs_len; i++) {
697                         /* TODO(campbell): function callback descriptions. */
698                         wmManipulatorProperty *mpr_prop = &mpr_prop_array[i];
699                         if (mpr_prop->prop != NULL) {
700                                 const char *info = RNA_property_ui_description(mpr_prop->prop);
701                                 if (info && info[0]) {
702                                         uiTooltipField *field = text_field_add(
703                                                 data, &(uiTooltipFormat){
704                                                     .style = UI_TIP_STYLE_NORMAL,
705                                                     .color_id = UI_TIP_LC_VALUE,
706                                                     .is_pad = true,
707                                                 });
708                                         field->text = BLI_strdup(info);
709                                 }
710                         }
711                 }
712         }
713
714         if (data->fields_len == 0) {
715                 MEM_freeN(data);
716                 return NULL;
717         }
718         else {
719                 return data;
720         }
721 }
722
723
724 static ARegion *ui_tooltip_create_with_data(
725         bContext *C, uiTooltipData *data,
726         const float init_position[2],
727         const float aspect)
728 {
729         const float pad_px = UI_TIP_PADDING;
730         wmWindow *win = CTX_wm_window(C);
731         const int winx = WM_window_pixels_x(win);
732         uiStyle *style = UI_style_get();
733         static ARegionType type;
734         ARegion *ar;
735         int fonth, fontw;
736         int h, i;
737         rctf rect_fl;
738         rcti rect_i;
739         int font_flag = 0;
740
741         /* create area region */
742         ar = ui_region_temp_add(CTX_wm_screen(C));
743
744         memset(&type, 0, sizeof(ARegionType));
745         type.draw = ui_tooltip_region_draw_cb;
746         type.free = ui_tooltip_region_free_cb;
747         type.regionid = RGN_TYPE_TEMPORARY;
748         ar->type = &type;
749
750         /* set font, get bb */
751         data->fstyle = style->widget; /* copy struct */
752         ui_fontscale(&data->fstyle.points, aspect);
753
754         UI_fontstyle_set(&data->fstyle);
755
756         data->wrap_width = min_ii(UI_TIP_MAXWIDTH * U.pixelsize / aspect, winx - (UI_TIP_PADDING * 2));
757
758         font_flag |= BLF_WORD_WRAP;
759         if (data->fstyle.kerning == 1) {
760                 font_flag |= BLF_KERNING_DEFAULT;
761         }
762         BLF_enable(data->fstyle.uifont_id, font_flag);
763         BLF_enable(blf_mono_font, font_flag);
764         BLF_wordwrap(data->fstyle.uifont_id, data->wrap_width);
765         BLF_wordwrap(blf_mono_font, data->wrap_width);
766
767         /* these defines tweaked depending on font */
768 #define TIP_BORDER_X (16.0f / aspect)
769 #define TIP_BORDER_Y (6.0f / aspect)
770
771         h = BLF_height_max(data->fstyle.uifont_id);
772
773         for (i = 0, fontw = 0, fonth = 0; i < data->fields_len; i++) {
774                 uiTooltipField *field = &data->fields[i];
775                 uiTooltipField *field_next = (i + 1) != data->fields_len ? &data->fields[i + 1] : NULL;
776
777                 struct ResultBLF info;
778                 int w, x_pos = 0;
779                 int font_id;
780
781                 if (field->format.style == UI_TIP_STYLE_MONO) {
782                         BLF_size(blf_mono_font, data->fstyle.points * U.pixelsize, U.dpi);
783                         font_id = blf_mono_font;
784                 }
785                 else {
786                         BLI_assert(ELEM(field->format.style, UI_TIP_STYLE_NORMAL, UI_TIP_STYLE_HEADER));
787                         font_id = data->fstyle.uifont_id;
788                 }
789                 w = BLF_width_ex(font_id, field->text, BLF_DRAW_STR_DUMMY_MAX, &info);
790
791                 /* check for suffix (enum label) */
792                 if (field->text_suffix && field->text_suffix[0]) {
793                         x_pos = info.width;
794                         w = max_ii(w, x_pos + BLF_width(font_id, field->text_suffix, BLF_DRAW_STR_DUMMY_MAX));
795                 }
796                 fontw = max_ii(fontw, w);
797
798                 fonth += h * info.lines;
799                 if (field_next && field_next->format.is_pad) {
800                         fonth += h * (UI_TIP_PAD_FAC - 1);
801                 }
802
803                 field->geom.lines = info.lines;
804                 field->geom.x_pos = x_pos;
805         }
806
807         //fontw *= aspect;
808
809         BLF_disable(data->fstyle.uifont_id, font_flag);
810         BLF_disable(blf_mono_font, font_flag);
811
812         ar->regiondata = data;
813
814         data->toth = fonth;
815         data->lineh = h;
816
817         /* compute position */
818
819         rect_fl.xmin = init_position[0] - TIP_BORDER_X;
820         rect_fl.xmax = rect_fl.xmin + fontw + pad_px;
821         rect_fl.ymax = init_position[1] - TIP_BORDER_Y;
822         rect_fl.ymin = rect_fl.ymax - fonth  - TIP_BORDER_Y;
823
824         BLI_rcti_rctf_copy(&rect_i, &rect_fl);
825
826 #undef TIP_BORDER_X
827 #undef TIP_BORDER_Y
828
829         /* clip with window boundaries */
830         if (rect_i.xmax > winx) {
831                 /* super size */
832                 if (rect_i.xmax > winx + rect_i.xmin) {
833                         rect_i.xmax = winx;
834                         rect_i.xmin = 0;
835                 }
836                 else {
837                         rect_i.xmin -= rect_i.xmax - winx;
838                         rect_i.xmax = winx;
839                 }
840         }
841         /* ensure at least 5 px above screen bounds
842          * 25 is just a guess to be above the menu item */
843         if (rect_i.ymin < 5) {
844                 rect_i.ymax += (-rect_i.ymin) + 30;
845                 rect_i.ymin = 30;
846         }
847
848         /* add padding */
849         BLI_rcti_resize(&rect_i,
850                         BLI_rcti_size_x(&rect_i) + pad_px,
851                         BLI_rcti_size_y(&rect_i) + pad_px);
852
853         /* widget rect, in region coords */
854         {
855                 const int margin = UI_POPUP_MARGIN;
856
857                 data->bbox.xmin = margin;
858                 data->bbox.xmax = BLI_rcti_size_x(&rect_i) - margin;
859                 data->bbox.ymin = margin;
860                 data->bbox.ymax = BLI_rcti_size_y(&rect_i);
861
862                 /* region bigger for shadow */
863                 ar->winrct.xmin = rect_i.xmin - margin;
864                 ar->winrct.xmax = rect_i.xmax + margin;
865                 ar->winrct.ymin = rect_i.ymin - margin;
866                 ar->winrct.ymax = rect_i.ymax + margin;
867         }
868
869         /* adds subwindow */
870         ED_region_init(ar);
871
872         /* notify change and redraw */
873         ED_region_tag_redraw(ar);
874
875         return ar;
876 }
877
878 /** \} */
879
880 /* -------------------------------------------------------------------- */
881 /** \name ToolTip Public API
882  * \{ */
883
884
885 ARegion *UI_tooltip_create_from_button(bContext *C, ARegion *butregion, uiBut *but)
886 {
887         wmWindow *win = CTX_wm_window(C);
888         /* aspect values that shrink text are likely unreadable */
889         const float aspect = min_ff(1.0f, but->block->aspect);
890         float init_position[2];
891
892         if (but->drawflag & UI_BUT_NO_TOOLTIP) {
893                 return NULL;
894         }
895         uiTooltipData *data = NULL;
896
897         /* custom tips for pre-defined operators */
898         if (but->optype) {
899                 /* TODO(campbell): we now use 'WM_OT_tool_set_by_name', this logic will be moved into the status bar. */
900                 if (false && STREQ(but->optype->idname, "WM_OT_tool_set")) {
901                         char keymap[64] = "";
902                         RNA_string_get(but->opptr, "keymap", keymap);
903                         if (keymap[0]) {
904                                 ScrArea *sa = CTX_wm_area(C);
905                                 /* It happens in rare cases, for tooltips originated from the toolbar.
906                                  * It is hard to reproduce, but it happens when the mouse is nowhere near the actual tool. */
907                                 if (sa == NULL) {
908                                         return NULL;
909                                 }
910                                 wmKeyMap *km = WM_keymap_find_all(C, keymap, sa->spacetype, RGN_TYPE_WINDOW);
911                                 if (km != NULL) {
912                                         data = ui_tooltip_data_from_keymap(C, km);
913                                 }
914                         }
915                 }
916         }
917         /* toolsystem exception */
918
919         if (data == NULL) {
920                 data = ui_tooltip_data_from_button(C, but);
921         }
922         if (data == NULL) {
923                 return NULL;
924         }
925
926         init_position[0] = BLI_rctf_cent_x(&but->rect);
927         init_position[1] = but->rect.ymin;
928
929         if (butregion) {
930                 ui_block_to_window_fl(butregion, but->block, &init_position[0], &init_position[1]);
931                 init_position[0] = win->eventstate->x;
932         }
933
934         return ui_tooltip_create_with_data(C, data, init_position, aspect);
935 }
936
937 ARegion *UI_tooltip_create_from_manipulator(bContext *C, wmManipulator *mpr)
938 {
939         wmWindow *win = CTX_wm_window(C);
940         const float aspect = 1.0f;
941         float init_position[2];
942
943         uiTooltipData *data = ui_tooltip_data_from_manipulator(C, mpr);
944         if (data == NULL) {
945                 return NULL;
946         }
947
948         init_position[0] = win->eventstate->x;
949         init_position[1] = win->eventstate->y;
950
951         return ui_tooltip_create_with_data(C, data, init_position, aspect);
952 }
953
954 void UI_tooltip_free(bContext *C, bScreen *sc, ARegion *ar)
955 {
956         ui_region_temp_remove(C, sc, ar);
957 }
958
959 /** \} */