d2bc8bc80c0821e0daf1abb0dfbbeedd933e84a6
[blender.git] / source / blender / editors / util / numinput.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  * Contributor(s): Jonathan Smith
19  *
20  * ***** END GPL LICENSE BLOCK *****
21  */
22
23 /** \file blender/editors/util/numinput.c
24  *  \ingroup edutil
25  */
26
27 #include "MEM_guardedalloc.h"
28
29 #include "BLI_utildefines.h"
30 #include "BLI_math.h"
31 #include "BLI_string.h"
32 #include "BLI_string_utf8.h"
33 #include "BLI_string_cursor_utf8.h"
34
35 #include "BKE_context.h"
36 #include "BKE_scene.h"
37 #include "BKE_unit.h"
38
39 #include "DNA_scene_types.h"
40
41 #include "WM_api.h"
42 #include "WM_types.h"
43
44 #ifdef WITH_PYTHON
45 #include "BPY_extern.h"
46 #endif
47
48 #include "ED_numinput.h"
49 #include "UI_interface.h"
50
51
52 /* NumInput.flag */
53 enum {
54         /* (1 << 8) and below are reserved for public flags! */
55         NUM_EDIT_FULL       = (1 << 9),   /* Enable full editing, with units and math operators support. */
56         NUM_FAKE_EDITED     = (1 << 10),  /* Fake edited state (temp, avoids issue with backspace). */
57 };
58
59 /* NumInput.val_flag[] */
60 enum {
61         /* (1 << 8) and below are reserved for public flags! */
62         NUM_EDITED          = (1 << 9),    /* User has edited this value somehow. */
63         NUM_INVALID         = (1 << 10),   /* Current expression for this value is invalid. */
64         NUM_NEGATE          = (1 << 11),   /* Current expression's result has to be negated. */
65         NUM_INVERSE         = (1 << 12),   /* Current expression's result has to be inverted. */
66 };
67
68 /* ************************** Functions *************************** */
69
70 /* ************************** NUMINPUT **************************** */
71
72 void initNumInput(NumInput *n)
73 {
74         n->idx_max = 0;
75         n->unit_sys = USER_UNIT_NONE;
76         copy_vn_i(n->unit_type, NUM_MAX_ELEMENTS, B_UNIT_NONE);
77         n->unit_use_radians = false;
78
79         n->flag = 0;
80         copy_vn_short(n->val_flag, NUM_MAX_ELEMENTS, 0);
81         zero_v3(n->val);
82         copy_vn_fl(n->val_org, NUM_MAX_ELEMENTS, 0.0f);
83         copy_vn_fl(n->val_inc, NUM_MAX_ELEMENTS, 1.0f);
84
85         n->idx = 0;
86         n->str[0] = '\0';
87         n->str_cur = 0;
88 }
89
90 /* str must be NUM_STR_REP_LEN * (idx_max + 1) length. */
91 void outputNumInput(NumInput *n, char *str, UnitSettings *unit_settings)
92 {
93         short j;
94         const int ln = NUM_STR_REP_LEN;
95         int prec = 2; /* draw-only, and avoids too much issues with radian->degrees conversion. */
96
97         for (j = 0; j <= n->idx_max; j++) {
98                 /* if AFFECTALL and no number typed and cursor not on number, use first number */
99                 const short i = (n->flag & NUM_AFFECT_ALL && n->idx != j && !(n->val_flag[j] & NUM_EDITED)) ? 0 : j;
100
101                 /* Use scale_length if needed! */
102                 const float fac = (float)BKE_scene_unit_scale(unit_settings, n->unit_type[j], 1.0);
103
104                 if (n->val_flag[i] & NUM_EDITED) {
105                         /* Get the best precision, allows us to draw '10.0001' as '10' instead! */
106                         prec = UI_calc_float_precision(prec, (double)n->val[i]);
107                         if (i == n->idx) {
108                                 const char *heading_exp = "", *trailing_exp = "";
109                                 char before_cursor[NUM_STR_REP_LEN];
110                                 char val[16];
111
112                                 if (n->val_flag[i] & NUM_NEGATE) {
113                                         heading_exp = (n->val_flag[i] & NUM_INVERSE) ? "-1/(" : "-(";
114                                         trailing_exp = ")";
115                                 }
116                                 else if (n->val_flag[i] & NUM_INVERSE) {
117                                         heading_exp = "1/(";
118                                         trailing_exp = ")";
119                                 }
120
121                                 if (n->val_flag[i] & NUM_INVALID) {
122                                         BLI_strncpy(val, "Invalid", sizeof(val));
123                                 }
124                                 else {
125                                         bUnit_AsString(val, sizeof(val), (double)(n->val[i] * fac), prec,
126                                                        n->unit_sys, n->unit_type[i], true, false);
127                                 }
128
129                                 BLI_strncpy(before_cursor, n->str, n->str_cur + 1);  /* +1 because of trailing '\0' */
130                                 BLI_snprintf(&str[j * ln], ln, "[%s%s|%s%s] = %s",
131                                              heading_exp, before_cursor, &n->str[n->str_cur], trailing_exp, val);
132                         }
133                         else {
134                                 const char *cur = (i == n->idx) ? "|" : "";
135                                 if (n->unit_use_radians && n->unit_type[i] == B_UNIT_ROTATION) {
136                                         /* Radian exception... */
137                                         BLI_snprintf(&str[j * ln], ln, "%s%.6gr%s", cur, n->val[i], cur);
138                                 }
139                                 else {
140                                         char tstr[NUM_STR_REP_LEN];
141                                         bUnit_AsString(tstr, ln, (double)n->val[i], prec, n->unit_sys, n->unit_type[i], true, false);
142                                         BLI_snprintf(&str[j * ln], ln, "%s%s%s", cur, tstr, cur);
143                                 }
144                         }
145                 }
146                 else {
147                         const char *cur = (i == n->idx) ? "|" : "";
148                         BLI_snprintf(&str[j * ln], ln, "%sNONE%s", cur, cur);
149                 }
150                 /* We might have cut some multi-bytes utf8 chars (e.g. trailing '°' of degrees values can become only 'A')... */
151                 BLI_utf8_invalid_strip(&str[j * ln], strlen(&str[j * ln]));
152         }
153 }
154
155 bool hasNumInput(const NumInput *n)
156 {
157         short i;
158
159         if (n->flag & NUM_FAKE_EDITED) {
160                 return true;
161         }
162
163         for (i = 0; i <= n->idx_max; i++) {
164                 if (n->val_flag[i] & NUM_EDITED) {
165                         return true;
166                 }
167         }
168
169         return false;
170 }
171
172 /**
173  * \warning \a vec must be set beforehand otherwise we risk uninitialized vars.
174  */
175 bool applyNumInput(NumInput *n, float *vec)
176 {
177         short i, j;
178         float val;
179
180         if (hasNumInput(n)) {
181                 for (j = 0; j <= n->idx_max; j++) {
182                         if (n->flag & NUM_FAKE_EDITED) {
183                                 val = n->val[j];
184                         }
185                         else {
186                                 /* if AFFECTALL and no number typed and cursor not on number, use first number */
187                                 i = (n->flag & NUM_AFFECT_ALL && n->idx != j && !(n->val_flag[j] & NUM_EDITED)) ? 0 : j;
188                                 val = (!(n->val_flag[i] & NUM_EDITED) && n->val_flag[i] & NUM_NULL_ONE) ? 1.0f : n->val[i];
189
190                                 if (n->val_flag[i] & NUM_NO_NEGATIVE && val < 0.0f) {
191                                         val = 0.0f;
192                                 }
193                                 if (n->val_flag[i] & NUM_NO_FRACTION && val != floorf(val)) {
194                                         val = floorf(val + 0.5f);
195                                         if (n->val_flag[i] & NUM_NO_ZERO && val == 0.0f) {
196                                                 val = 1.0f;
197                                         }
198                                 }
199                                 else if (n->val_flag[i] & NUM_NO_ZERO && val == 0.0f) {
200                                         val = 0.0001f;
201                                 }
202                         }
203                         vec[j] = val;
204                 }
205                 n->flag &= ~NUM_FAKE_EDITED;
206                 return true;
207         }
208         else {
209                 /* Else, we set the 'org' values for numinput! */
210                 for (j = 0; j <= n->idx_max; j++) {
211                         n->val[j] = n->val_org[j] = vec[j];
212                 }
213                 return false;
214         }
215 }
216
217
218 static void value_to_editstr(NumInput *n, int idx)
219 {
220         const int prec = 6; /* editing, higher precision needed. */
221         n->str_cur = bUnit_AsString(n->str, NUM_STR_REP_LEN, (double)n->val[idx], prec,
222                                     n->unit_sys, n->unit_type[idx], true, false);
223 }
224
225 static bool editstr_insert_at_cursor(NumInput *n, const char *buf, const int buf_len)
226 {
227         int cur = n->str_cur;
228         int len = strlen(&n->str[cur]) + 1;  /* +1 for the trailing '\0'. */
229         int n_cur = cur + buf_len;
230
231         if (n_cur + len >= NUM_STR_REP_LEN) {
232                 return false;
233         }
234
235         memmove(&n->str[n_cur], &n->str[cur], len);
236         memcpy(&n->str[cur], buf, sizeof(char) * buf_len);
237
238         n->str_cur = n_cur;
239         return true;
240 }
241
242 static bool editstr_is_simple_numinput(const char ascii)
243 {
244         if (ascii >= '0' && ascii <= '9') {
245                 return true;
246         }
247         else if (ascii == '.') {
248                 return true;
249         }
250         else {
251                 return false;
252         }
253 }
254
255 bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event)
256 {
257         const char *utf8_buf = NULL;
258         char ascii[2] = {'\0', '\0'};
259         bool updated = false;
260         short idx = n->idx, idx_max = n->idx_max;
261         short dir = STRCUR_DIR_NEXT, mode = STRCUR_JUMP_NONE;
262         int cur;
263
264         switch (event->type) {
265                 case EVT_MODAL_MAP:
266                         if (ELEM(event->val, NUM_MODAL_INCREMENT_UP, NUM_MODAL_INCREMENT_DOWN)) {
267                                 n->val[idx] += (event->val == NUM_MODAL_INCREMENT_UP) ? n->val_inc[idx] : -n->val_inc[idx];
268                                 value_to_editstr(n, idx);
269                                 n->val_flag[idx] |= NUM_EDITED;
270                                 updated = true;
271                         }
272                         else {
273                                 /* might be a char too... */
274                                 utf8_buf = event->utf8_buf;
275                                 ascii[0] = event->ascii;
276                         }
277                         break;
278                 case BACKSPACEKEY:
279                         /* Part specific to backspace... */
280                         if (!(n->val_flag[idx] & NUM_EDITED)) {
281                                 copy_v3_v3(n->val, n->val_org);
282                                 n->val_flag[0] &= ~NUM_EDITED;
283                                 n->val_flag[1] &= ~NUM_EDITED;
284                                 n->val_flag[2] &= ~NUM_EDITED;
285                                 n->flag |= NUM_FAKE_EDITED;
286                                 updated = true;
287                                 break;
288                         }
289                         else if (event->shift || !n->str[0]) {
290                                 n->val[idx] = n->val_org[idx];
291                                 n->val_flag[idx] &= ~NUM_EDITED;
292                                 n->str[0] = '\0';
293                                 n->str_cur = 0;
294                                 updated = true;
295                                 break;
296                         }
297                         /* Else, common behavior with DELKEY, only difference is remove char(s) before/after the cursor. */
298                         dir = STRCUR_DIR_PREV;
299                         /* fall-through */
300                 case DELKEY:
301                         if ((n->val_flag[idx] & NUM_EDITED) && n->str[0]) {
302                                 int t_cur = cur = n->str_cur;
303                                 if (event->ctrl) {
304                                         mode = STRCUR_JUMP_DELIM;
305                                 }
306                                 BLI_str_cursor_step_utf8(n->str, strlen(n->str), &t_cur, dir, mode, true);
307                                 if (t_cur != cur) {
308                                         if (t_cur < cur) {
309                                                 SWAP(int, t_cur, cur);
310                                                 n->str_cur = cur;
311                                         }
312                                         memmove(&n->str[cur], &n->str[t_cur], strlen(&n->str[t_cur]) + 1);  /* +1 for trailing '\0'. */
313                                         updated = true;
314                                 }
315                                 if (!n->str[0]) {
316                                         n->val[idx] = n->val_org[idx];
317                                 }
318                         }
319                         else {
320                                 return false;
321                         }
322                         break;
323                 case LEFTARROWKEY:
324                         dir = STRCUR_DIR_PREV;
325                         /* fall-through */
326                 case RIGHTARROWKEY:
327                         cur = n->str_cur;
328                         if (event->ctrl) {
329                                 mode = STRCUR_JUMP_DELIM;
330                         }
331                         BLI_str_cursor_step_utf8(n->str, strlen(n->str), &cur, dir, mode, true);
332                         if (cur != n->str_cur) {
333                                 n->str_cur = cur;
334                                 return true;
335                         }
336                         return false;
337                 case HOMEKEY:
338                         if (n->str[0]) {
339                                 n->str_cur = 0;
340                                 return true;
341                         }
342                         return false;
343                 case ENDKEY:
344                         if (n->str[0]) {
345                                 n->str_cur = strlen(n->str);
346                                 return true;
347                         }
348                         return false;
349                 case TABKEY:
350                         n->val_flag[idx] &= ~(NUM_NEGATE | NUM_INVERSE);
351
352                         idx = (idx + idx_max + (event->ctrl ? 0 : 2)) % (idx_max + 1);
353                         n->idx = idx;
354                         if (n->val_flag[idx] & NUM_EDITED) {
355                                 value_to_editstr(n, idx);
356                         }
357                         else {
358                                 n->str[0] = '\0';
359                                 n->str_cur = 0;
360                         }
361                         return true;
362                 case PADPERIOD:
363                 case PERIODKEY:
364                         /* Force numdot, some OSs/countries generate a comma char in this case, sic...  (T37992) */
365                         ascii[0] = '.';
366                         utf8_buf = ascii;
367                         break;
368 #if 0
369                 /* Those keys are not directly accessible in all layouts, preventing to generate matching events.
370                  * So we use a hack (ascii value) instead, see below.
371                  */
372                 case EQUALKEY:
373                 case PADASTERKEY:
374                         if (!(n->flag & NUM_EDIT_FULL)) {
375                                 n->flag |= NUM_EDIT_FULL;
376                                 n->val_flag[idx] |= NUM_EDITED;
377                                 return true;
378                         }
379                         else if (event->ctrl) {
380                                 n->flag &= ~NUM_EDIT_FULL;
381                                 return true;
382                         }
383                         break;
384 #endif
385                 case PADMINUS:
386                 case MINUSKEY:
387                         if (event->ctrl || !(n->flag & NUM_EDIT_FULL)) {
388                                 n->val_flag[idx] ^= NUM_NEGATE;
389                                 updated = true;
390                         }
391                         break;
392                 case PADSLASHKEY:
393                 case SLASHKEY:
394                         if (event->ctrl || !(n->flag & NUM_EDIT_FULL)) {
395                                 n->val_flag[idx] ^= NUM_INVERSE;
396                                 updated = true;
397                         }
398                         break;
399                 case CKEY:
400                         if (event->ctrl) {
401                                 /* Copy current str to the copypaste buffer. */
402                                 WM_clipboard_text_set(n->str, 0);
403                                 updated = true;
404                         }
405                         break;
406                 case VKEY:
407                         if (event->ctrl) {
408                                 /* extract the first line from the clipboard */
409                                 int pbuf_len;
410                                 char *pbuf = WM_clipboard_text_get_firstline(false, &pbuf_len);
411
412                                 if (pbuf) {
413                                         const bool success = editstr_insert_at_cursor(n, pbuf, pbuf_len);
414
415                                         MEM_freeN(pbuf);
416                                         if (!success) {
417                                                 return false;
418                                         }
419
420                                         n->val_flag[idx] |= NUM_EDITED;
421                                 }
422                                 updated = true;
423                         }
424                         break;
425                 default:
426                         break;
427         }
428
429         if (!updated && !utf8_buf && (event->utf8_buf[0] || event->ascii)) {
430                 utf8_buf = event->utf8_buf;
431                 ascii[0] = event->ascii;
432         }
433
434         /* XXX Hack around keyboards without direct access to '=' nor '*'... */
435         if (ELEM(ascii[0], '=', '*')) {
436                 if (!(n->flag & NUM_EDIT_FULL)) {
437                         n->flag |= NUM_EDIT_FULL;
438                         n->val_flag[idx] |= NUM_EDITED;
439                         return true;
440                 }
441                 else if (event->ctrl) {
442                         n->flag &= ~NUM_EDIT_FULL;
443                         return true;
444                 }
445         }
446
447         /* Up to this point, if we have a ctrl modifier, skip.
448          * This allows to still access most of modals' shortcuts even in numinput mode.
449          */
450         if (!updated && event->ctrl) {
451                 return false;
452         }
453
454         if ((!utf8_buf || !utf8_buf[0]) && ascii[0]) {
455                 /* Fallback to ascii. */
456                 utf8_buf = ascii;
457         }
458
459         if (utf8_buf && utf8_buf[0]) {
460                 if (!(n->flag & NUM_EDIT_FULL)) {
461                         /* In simple edit mode, we only keep a few chars as valid! */
462                         /* no need to decode unicode, ascii is first char only */
463                         if (!editstr_is_simple_numinput(utf8_buf[0])) {
464                                 return false;
465                         }
466                 }
467
468                 if (!editstr_insert_at_cursor(n, utf8_buf, BLI_str_utf8_size(utf8_buf))) {
469                         return false;
470                 }
471
472                 n->val_flag[idx] |= NUM_EDITED;
473         }
474         else if (!updated) {
475                 return false;
476         }
477
478         /* At this point, our value has changed, try to interpret it with python (if str is not empty!). */
479         if (n->str[0]) {
480 #ifdef WITH_PYTHON
481                 Scene *sce = CTX_data_scene(C);
482                 double val;
483                 char str_unit_convert[NUM_STR_REP_LEN * 6];  /* Should be more than enough! */
484                 const char *default_unit = NULL;
485
486                 /* Use scale_length if needed! */
487                 const float fac = (float)BKE_scene_unit_scale(&sce->unit, n->unit_type[idx], 1.0);
488
489                 /* Make radian default unit when needed. */
490                 if (n->unit_use_radians && n->unit_type[idx] == B_UNIT_ROTATION)
491                         default_unit = "r";
492
493                 BLI_strncpy(str_unit_convert, n->str, sizeof(str_unit_convert));
494
495                 bUnit_ReplaceString(str_unit_convert, sizeof(str_unit_convert), default_unit, fac,
496                                     n->unit_sys, n->unit_type[idx]);
497
498                 /* Note: with angles, we always get values as radians here... */
499                 if (BPY_button_exec(C, str_unit_convert, &val, false) != -1) {
500                         n->val[idx] = (float)val;
501                         n->val_flag[idx] &= ~NUM_INVALID;
502                 }
503                 else {
504                         n->val_flag[idx] |= NUM_INVALID;
505                 }
506 #else  /* Very unlikely, but does not harm... */
507                 n->val[idx] = (float)atof(n->str);
508                 (void)C;
509 #endif  /* WITH_PYTHON */
510
511                 if (n->val_flag[idx] & NUM_NEGATE) {
512                         n->val[idx] = -n->val[idx];
513                 }
514                 if (n->val_flag[idx] & NUM_INVERSE) {
515                         n->val[idx] = 1.0f / n->val[idx];
516                 }
517         }
518
519         /* REDRAW SINCE NUMBERS HAVE CHANGED */
520         return true;
521 }