c69996defd7799cc1c6ffa7796366ceff2765160
[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_unit.h"
37
38 #include "DNA_scene_types.h"
39
40 #include "WM_api.h"
41 #include "WM_types.h"
42
43 #ifdef WITH_PYTHON
44 #include "BPY_extern.h"
45 #endif
46
47 #include "ED_numinput.h"
48
49
50 /* NumInput.val_flag[] */
51 enum {
52         /* (1 << 8) and below are reserved for public flags! */
53         NUM_EDITED          = (1 << 9),    /* User has edited this value somehow. */
54         NUM_INVALID         = (1 << 10),   /* Current expression for this value is invalid. */
55 };
56
57 /* ************************** Functions *************************** */
58
59 /* ************************** NUMINPUT **************************** */
60
61 void initNumInput(NumInput *n)
62 {
63         n->unit_sys = USER_UNIT_NONE;
64         n->unit_type[0] = n->unit_type[1] = n->unit_type[2] = B_UNIT_NONE;
65         n->idx = 0;
66         n->idx_max = 0;
67         n->flag = 0;
68         n->val_flag[0] = n->val_flag[1] = n->val_flag[2] = 0;
69         zero_v3(n->val_org);
70         zero_v3(n->val);
71         n->str[0] = '\0';
72         n->str_cur = 0;
73         copy_v3_fl(n->val_inc, 1.0f);
74 }
75
76 /* str must be NUM_STR_REP_LEN * (idx_max + 1) length. */
77 void outputNumInput(NumInput *n, char *str)
78 {
79         short i, j;
80         const int ln = NUM_STR_REP_LEN;
81         const int prec = 4; /* draw-only, and avoids too much issues with radian->degrees conversion. */
82
83         for (j = 0; j <= n->idx_max; j++) {
84                 /* if AFFECTALL and no number typed and cursor not on number, use first number */
85                 i = (n->flag & NUM_AFFECT_ALL && n->idx != j && !(n->val_flag[j] & NUM_EDITED)) ? 0 : j;
86
87                 if (n->val_flag[i] & NUM_EDITED) {
88                         if (i == n->idx && n->str[0]) {
89                                 char before_cursor[NUM_STR_REP_LEN];
90                                 char val[16];
91                                 if (n->val_flag[i] & NUM_INVALID) {
92                                         BLI_strncpy(val, "Invalid", sizeof(val));
93                                 }
94                                 else {
95                                         bUnit_AsString(val, sizeof(val), (double)n->val[i], prec,
96                                                        n->unit_sys, n->unit_type[i], true, false);
97                                 }
98                                 BLI_strncpy(before_cursor, n->str, n->str_cur + 1);  /* +1 because of trailing '\0' */
99                                 BLI_snprintf(&str[j * ln], ln, "[%s|%s] = %s", before_cursor, &n->str[n->str_cur], val);
100                         }
101                         else {
102                                 const char *cur = (i == n->idx) ? "|" : "";
103                                 if (n->unit_use_radians && n->unit_type[i] == B_UNIT_ROTATION) {
104                                         /* Radian exception... */
105                                         BLI_snprintf(&str[j * ln], ln, "%s%.6gr%s", cur, n->val[i], cur);
106                                 }
107                                 else {
108                                         char tstr[NUM_STR_REP_LEN];
109                                         bUnit_AsString(tstr, ln, (double)n->val[i], prec, n->unit_sys, n->unit_type[i], true, false);
110                                         BLI_snprintf(&str[j * ln], ln, "%s%s%s", cur, tstr, cur);
111                                 }
112                         }
113                 }
114                 else {
115                         const char *cur = (i == n->idx) ? "|" : "";
116                         BLI_snprintf(&str[j * ln], ln, "%sNONE%s", cur, cur);
117                 }
118         }
119 }
120
121 bool hasNumInput(const NumInput *n)
122 {
123         short i;
124
125         for (i = 0; i <= n->idx_max; i++) {
126                 if (n->val_flag[i] & NUM_EDITED) {
127                         return true;
128                 }
129         }
130
131         return false;
132 }
133
134 /**
135  * \warning \a vec must be set beforehand otherwise we risk uninitialized vars.
136  */
137 void applyNumInput(NumInput *n, float *vec)
138 {
139         short i, j;
140         float val;
141
142         if (hasNumInput(n)) {
143                 for (j = 0; j <= n->idx_max; j++) {
144                         /* if AFFECTALL and no number typed and cursor not on number, use first number */
145                         i = (n->flag & NUM_AFFECT_ALL && n->idx != j && !(n->val_flag[j] & NUM_EDITED)) ? 0 : j;
146                         val = (!(n->val_flag[i] & NUM_EDITED) && n->val_flag[i] & NUM_NULL_ONE) ? 1.0f : n->val[i];
147
148                         if (n->val_flag[i] & NUM_NO_NEGATIVE && val < 0.0f) {
149                                 val = 0.0f;
150                         }
151                         if (n->val_flag[i] & NUM_NO_ZERO && val == 0.0f) {
152                                 val = 0.0001f;
153                         }
154                         if (n->val_flag[i] & NUM_NO_FRACTION && val != floorf(val)) {
155                                 val = floorf(val + 0.5f);
156                                 if (n->val_flag[i] & NUM_NO_ZERO && val == 0.0f) {
157                                         val = 1.0f;
158                                 }
159                         }
160                         vec[j] = val;
161                 }
162         }
163 }
164
165
166 static void value_to_editstr(NumInput *n, int idx)
167 {
168         const int prec = 6; /* editing, higher precision needed. */
169         bUnit_AsString(n->str, NUM_STR_REP_LEN, (double)n->val[idx], prec,
170                        n->unit_sys, n->unit_type[idx], true, false);
171         n->str_cur = strlen(n->str);
172 }
173
174 static bool editstr_insert_at_cursor(NumInput *n, const char *buf, const int buf_len)
175 {
176         int cur = n->str_cur;
177         int len = strlen(&n->str[cur]) + 1;  /* +1 for the trailing '\0'. */
178         int n_cur = cur + buf_len;
179
180         if (n_cur + len >= NUM_STR_REP_LEN) {
181                 return false;
182         }
183
184         memmove(&n->str[n_cur], &n->str[cur], len);
185         memcpy(&n->str[cur], buf, sizeof(char) * buf_len);
186
187         n->str_cur = n_cur;
188         return true;
189 }
190
191 #define NUM_REVERSE_START "-("
192 #define NUM_REVERSE_END ")"
193 #define NUM_INVERSE_START "1/("
194 #define NUM_INVERSE_END ")"
195
196 static bool editstr_reverse_inverse_toggle(NumInput *n, const bool reverse, const bool inverse)
197 {
198         /* This function just add or remove -(...) or 1/(...) around current expression. */
199         size_t len = strlen(n->str);
200         const size_t len_rev_start = strlen(NUM_REVERSE_START);
201         const size_t len_rev_end = strlen(NUM_REVERSE_END);
202         const size_t len_inv_start = strlen(NUM_INVERSE_START);
203         const size_t len_inv_end = strlen(NUM_INVERSE_END);
204         int len_start = 0, len_end = 0;
205         size_t off_start, off_end;
206
207         bool is_reversed = ((strncmp(n->str, NUM_REVERSE_START, len_rev_start) == 0) &&
208                             (strncmp(n->str + len - len_rev_end, NUM_REVERSE_END, len_rev_end) == 0)) ||
209                            ((strncmp(n->str + len_inv_start, NUM_REVERSE_START, len_rev_start) == 0) &&
210                             (strncmp(n->str + len - len_rev_end - len_inv_end, NUM_REVERSE_END, len_rev_end) == 0));
211         bool is_inversed = ((strncmp(n->str, NUM_INVERSE_START, len_inv_start) == 0) &&
212                             (strncmp(n->str + len - len_inv_end, NUM_INVERSE_END, len_inv_end) == 0)) ||
213                            ((strncmp(n->str + len_rev_start, NUM_INVERSE_START, len_inv_start) == 0) &&
214                             (strncmp(n->str + len - len_inv_end - len_rev_end, NUM_INVERSE_END, len_inv_end) == 0));
215
216         if ((!reverse && !inverse) || n->str[0] == '\0') {
217                 return false;
218         }
219
220         if (reverse) {
221                 if (is_reversed) {
222                         len_start -= len_rev_start;
223                         len_end -= len_rev_end;
224                 }
225                 else {
226                         len_start += len_rev_start;
227                         len_end += len_rev_end;
228                 }
229         }
230         if (inverse) {
231                 if (is_inversed) {
232                         len_start -= len_inv_start;
233                         len_end -= len_inv_end;
234                 }
235                 else {
236                         len_start += len_inv_start;
237                         len_end += len_inv_end;
238                 }
239         }
240
241         if (len_start < 0) {
242                 len -= (size_t)(-(len_start + len_end));
243                 memmove(n->str, n->str + (size_t)(-len_start), len);
244         }
245         else if (len_start > 0) {
246                 if (len + len_start + len_end > sizeof(n->str)) {
247                         return false;  /* Not enough room in buffer... */
248                 }
249                 memmove(n->str + (size_t)len_start, n->str, len);
250                 len += (size_t)(len_start + len_end);
251         }
252
253         if (reverse) {
254                 is_reversed = !is_reversed;
255         }
256         if (inverse) {
257                 is_inversed = !is_inversed;
258         }
259
260         off_start = 0;
261         off_end = len;
262         if (is_reversed) {
263                 off_end -= len_rev_end;
264                 memcpy(n->str + off_start, NUM_REVERSE_START, len_rev_start);
265                 memcpy(n->str + off_end, NUM_REVERSE_END, len_rev_end);
266                 off_start += len_rev_start;
267         }
268         if (is_inversed) {
269                 off_end -= len_inv_end;
270                 memcpy(n->str + off_start, NUM_INVERSE_START, len_inv_start);
271                 memcpy(n->str + off_end, NUM_INVERSE_END, len_inv_end);
272                 off_start += len_inv_start;
273         }
274
275         n->str[len] = '\0';
276         n->str_cur += len_start;
277         return true;
278 }
279
280 #undef NUM_REVERSE_START
281 #undef NUM_REVERSE_END
282 #undef NUM_INVERSE_START
283 #undef NUM_INVERSE_END
284
285 bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event)
286 {
287         const char *utf8_buf = NULL;
288         char ascii[2] = {'\0', '\0'};
289         bool updated = false;
290         short idx = n->idx, idx_max = n->idx_max;
291         short dir = STRCUR_DIR_NEXT, mode = STRCUR_JUMP_NONE;
292         int cur;
293         double val;
294
295         switch (event->type) {
296                 case EVT_MODAL_MAP:
297                         if (ELEM(event->val, NUM_MODAL_INCREMENT_UP, NUM_MODAL_INCREMENT_DOWN)) {
298                                 n->val[idx] += (event->val == NUM_MODAL_INCREMENT_UP) ? n->val_inc[idx] : -n->val_inc[idx];
299                                 value_to_editstr(n, idx);
300                                 n->val_flag[idx] |= NUM_EDITED;
301                                 updated = true;
302                         }
303                         else {
304                                 /* might be a char too... */
305                                 utf8_buf = event->utf8_buf;
306                                 ascii[0] = event->ascii;
307                         }
308                         break;
309                 case BACKSPACEKEY:
310                         /* Part specific to backspace... */
311                         if (!(n->val_flag[idx] & NUM_EDITED)) {
312                                 copy_v3_v3(n->val, n->val_org);
313                                 n->val_flag[0] &= ~NUM_EDITED;
314                                 n->val_flag[1] &= ~NUM_EDITED;
315                                 n->val_flag[2] &= ~NUM_EDITED;
316                                 updated = true;
317                                 break;
318                         }
319                         else if (event->shift || !n->str[0]) {
320                                 n->val[idx] = n->val_org[idx];
321                                 n->val_flag[idx] &= ~NUM_EDITED;
322                                 n->str[0] = '\0';
323                                 n->str_cur = 0;
324                                 updated = true;
325                                 break;
326                         }
327                         /* Else, common behavior with DELKEY, only difference is remove char(s) before/after the cursor. */
328                         dir = STRCUR_DIR_PREV;
329                         /* fall-through */
330                 case DELKEY:
331                         if ((n->val_flag[idx] & NUM_EDITED) && n->str[0]) {
332                                 int t_cur = cur = n->str_cur;
333                                 if (event->ctrl) {
334                                         mode = STRCUR_JUMP_DELIM;
335                                 }
336                                 BLI_str_cursor_step_utf8(n->str, strlen(n->str), &t_cur, dir, mode, true);
337                                 if (t_cur != cur) {
338                                         if (t_cur < cur) {
339                                                 SWAP(int, t_cur, cur);
340                                                 n->str_cur = cur;
341                                         }
342                                         memmove(&n->str[cur], &n->str[t_cur], strlen(&n->str[t_cur]) + 1);  /* +1 for trailing '\0'. */
343                                         updated = true;
344                                 }
345                         }
346                         else {
347                                 return false;
348                         }
349                         break;
350                 case LEFTARROWKEY:
351                         dir = STRCUR_DIR_PREV;
352                         /* fall-through */
353                 case RIGHTARROWKEY:
354                         cur = n->str_cur;
355                         if (event->ctrl) {
356                                 mode = STRCUR_JUMP_DELIM;
357                         }
358                         BLI_str_cursor_step_utf8(n->str, strlen(n->str), &cur, dir, mode, true);
359                         if (cur != n->str_cur) {
360                                 n->str_cur = cur;
361                                 return true;
362                         }
363                         return false;
364                 case HOMEKEY:
365                         if (n->str[0]) {
366                                 n->str_cur = 0;
367                                 return true;
368                         }
369                         return false;
370                 case ENDKEY:
371                         if (n->str[0]) {
372                                 n->str_cur = strlen(n->str);
373                                 return true;
374                         }
375                         return false;
376                 case TABKEY:
377                         n->val_org[idx] = n->val[idx];
378
379                         idx += event->ctrl ? -1 : 1;
380                         idx %= idx_max + 1;
381                         n->idx = idx;
382                         n->val[idx] = n->val_org[idx];
383                         if (n->val_flag[idx] & NUM_EDITED) {
384                                 value_to_editstr(n, idx);
385                         }
386                         else {
387                                 n->str[0] = '\0';
388                                 n->str_cur = 0;
389                         }
390                         return true;
391                 case PADPERIOD:
392                         /* Force numdot, some OSs/countries generate a comma char in this case, sic...  (T37992) */
393                         ascii[0] = '.';
394                         utf8_buf = ascii;
395                         break;
396                 case PADMINUS:
397                         if (event->ctrl && editstr_reverse_inverse_toggle(n, true, false)) {
398                                 updated = true;
399                                 break;
400                         }
401                         /* fall-through */
402                 case PADSLASHKEY:
403                         if (event->ctrl && editstr_reverse_inverse_toggle(n, false, true)) {
404                                 updated = true;
405                                 break;
406                         }
407                         /* fall-through */
408                 case CKEY:
409                         if (event->ctrl) {
410                                 /* Copy current str to the copypaste buffer. */
411                                 WM_clipboard_text_set(n->str, 0);
412                                 updated = true;
413                                 break;
414                         }
415                         /* fall-through */
416                 case VKEY:
417                         if (event->ctrl) {
418                                 /* extract the first line from the clipboard */
419                                 char *pbuf = WM_clipboard_text_get(0);
420
421                                 if (pbuf) {
422                                         bool success;
423                                         /* Only copy string until first of this char. */
424                                         char *cr = strchr(pbuf, '\r');
425                                         char *cn = strchr(pbuf, '\n');
426                                         if (cn && cn < cr) cr = cn;
427                                         if (cr) *cr = '\0';
428
429                                         success = editstr_insert_at_cursor(n, pbuf, strlen(pbuf));
430
431                                         MEM_freeN(pbuf);
432                                         if (!success) {
433                                                 return false;
434                                         }
435
436                                         n->val_flag[idx] |= NUM_EDITED;
437                                 }
438                                 updated = true;
439                                 break;
440                         }
441                         /* fall-through */
442                 default:
443                         utf8_buf = event->utf8_buf;
444                         ascii[0] = event->ascii;
445                         break;
446         }
447
448         if (utf8_buf && !utf8_buf[0] && ascii[0]) {
449                 /* Fallback to ascii. */
450                 utf8_buf = ascii;
451         }
452
453         if (utf8_buf && utf8_buf[0]) {
454                 if (!editstr_insert_at_cursor(n, utf8_buf, BLI_str_utf8_size(utf8_buf))) {
455                         return false;
456                 }
457
458                 n->val_flag[idx] |= NUM_EDITED;
459         }
460         else if (!updated) {
461                 return false;
462         }
463
464         /* At this point, our value has changed, try to interpret it with python (if str is not empty!). */
465         if (n->str[0]) {
466 #ifdef WITH_PYTHON
467                 char str_unit_convert[NUM_STR_REP_LEN * 6];  /* Should be more than enough! */
468                 const char *default_unit = NULL;
469
470                 /* Make radian default unit when needed. */
471                 if (n->unit_use_radians && n->unit_type[idx] == B_UNIT_ROTATION)
472                         default_unit = "r";
473
474                 BLI_strncpy(str_unit_convert, n->str, sizeof(str_unit_convert));
475
476                 bUnit_ReplaceString(str_unit_convert, sizeof(str_unit_convert), default_unit, 1.0,
477                                     n->unit_sys, n->unit_type[idx]);
478
479                 /* Note: with angles, we always get values as radians here... */
480                 if (BPY_button_exec(C, str_unit_convert, &val, false) != -1) {
481                         n->val[idx] = (float)val;
482                         n->val_flag[idx] &= ~NUM_INVALID;
483                 }
484                 else {
485                         n->val_flag[idx] |= NUM_INVALID;
486                 }
487 #else  /* Very unlikely, but does not harm... */
488                 n->val[idx] = (float)atof(n->str);
489 #endif  /* WITH_PYTHON */
490         }
491
492         /* REDRAW SINCE NUMBERS HAVE CHANGED */
493         return true;
494 }