1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software Foundation,
14 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15 */
16
17 /** \file
18 * \ingroup edutil
19 */
20
21 #include "MEM_guardedalloc.h"
22
23 #include "BLI_math.h"
24 #include "BLI_string.h"
25 #include "BLI_string_cursor_utf8.h"
26 #include "BLI_string_utf8.h"
27 #include "BLI_utildefines.h"
28
29 #include "BLT_translation.h"
30
31 #include "BKE_context.h"
32 #include "BKE_scene.h"
33 #include "BKE_unit.h"
34
35 #include "DNA_scene_types.h"
36
37 #include "WM_api.h"
38 #include "WM_types.h"
39
40 #ifdef WITH_PYTHON
41 # include "BPY_extern_run.h"
42 #endif
43
44 #include "ED_numinput.h"
45 #include "UI_interface.h"
46
47 /* Numeric input which isn't allowing full numeric editing. */
48 #define USE_FAKE_EDIT
49
50 /**
51 * #NumInput.flag
52 * (1 << 8) and below are reserved for public flags!
53 */
54 enum {
55 /** Enable full editing, with units and math operators support. */
56 NUM_EDIT_FULL = (1 << 9),
57 #ifdef USE_FAKE_EDIT
58 /** Fake edited state (temp, avoids issue with backspace). */
59 NUM_FAKE_EDITED = (1 << 10),
60 #endif
61 };
62
63 /* NumInput.val_flag[] */
64 enum {
65 /* (1 << 8) and below are reserved for public flags! */
66 /** User has edited this value somehow. */
67 NUM_EDITED = (1 << 9),
68 /** Current expression for this value is invalid. */
69 NUM_INVALID = (1 << 10),
70 #ifdef USE_FAKE_EDIT
71 /** Current expression's result has to be negated. */
72 NUM_NEGATE = (1 << 11),
73 /** Current expression's result has to be inverted. */
74 NUM_INVERSE = (1 << 12),
75 #endif
76 };
77
78 /* ************************** Functions *************************** */
79
80 /* ************************** NUMINPUT **************************** */
81
initNumInput(NumInput * n)82 void initNumInput(NumInput *n)
83 {
84 n->idx_max = 0;
85 n->unit_sys = USER_UNIT_NONE;
86 copy_vn_i(n->unit_type, NUM_MAX_ELEMENTS, B_UNIT_NONE);
87 n->unit_use_radians = false;
88
89 n->flag = 0;
90 copy_vn_short(n->val_flag, NUM_MAX_ELEMENTS, 0);
91 zero_v3(n->val);
92 copy_vn_fl(n->val_org, NUM_MAX_ELEMENTS, 0.0f);
93 copy_vn_fl(n->val_inc, NUM_MAX_ELEMENTS, 1.0f);
94
95 n->idx = 0;
96 n->str[0] = '\0';
97 n->str_cur = 0;
98 }
99
100 /* str must be NUM_STR_REP_LEN * (idx_max + 1) length. */
outputNumInput(NumInput * n,char * str,UnitSettings * unit_settings)101 void outputNumInput(NumInput *n, char *str, UnitSettings *unit_settings)
102 {
103 short j;
104 const int ln = NUM_STR_REP_LEN;
105 int prec = 2; /* draw-only, and avoids too much issues with radian->degrees conversion. */
106
107 for (j = 0; j <= n->idx_max; j++) {
108 /* if AFFECTALL and no number typed and cursor not on number, use first number */
109 const short i = (n->flag & NUM_AFFECT_ALL && n->idx != j && !(n->val_flag[j] & NUM_EDITED)) ?
110 0 :
111 j;
112
113 /* Use scale_length if needed! */
114 const float fac = (float)BKE_scene_unit_scale(unit_settings, n->unit_type[j], 1.0);
115
116 if (n->val_flag[i] & NUM_EDITED) {
117 /* Get the best precision, allows us to draw '10.0001' as '10' instead! */
118 prec = UI_calc_float_precision(prec, (double)n->val[i]);
119 if (i == n->idx) {
120 const char *heading_exp = "", *trailing_exp = "";
121 char before_cursor[NUM_STR_REP_LEN];
122 char val[16];
123
124 #ifdef USE_FAKE_EDIT
125 if (n->val_flag[i] & NUM_NEGATE) {
126 heading_exp = (n->val_flag[i] & NUM_INVERSE) ? "-1/(" : "-(";
127 trailing_exp = ")";
128 }
129 else if (n->val_flag[i] & NUM_INVERSE) {
130 heading_exp = "1/(";
131 trailing_exp = ")";
132 }
133 #endif
134
135 if (n->val_flag[i] & NUM_INVALID) {
136 BLI_strncpy(val, "Invalid", sizeof(val));
137 }
138 else {
139 BKE_unit_value_as_string_adaptive(val,
140 sizeof(val),
141 (double)(n->val[i] * fac),
142 prec,
143 n->unit_sys,
144 n->unit_type[i],
145 true,
146 false);
147 }
148
149 /* +1 because of trailing '\0' */
150 BLI_strncpy(before_cursor, n->str, n->str_cur + 1);
151 BLI_snprintf(&str[j * ln],
152 ln,
153 "[%s%s|%s%s] = %s",
154 heading_exp,
155 before_cursor,
156 &n->str[n->str_cur],
157 trailing_exp,
158 val);
159 }
160 else {
161 const char *cur = (i == n->idx) ? "|" : "";
162 if (n->unit_use_radians && n->unit_type[i] == B_UNIT_ROTATION) {
163 /* Radian exception... */
164 BLI_snprintf(&str[j * ln], ln, "%s%.6gr%s", cur, n->val[i], cur);
165 }
166 else {
167 char tstr[NUM_STR_REP_LEN];
168 BKE_unit_value_as_string_adaptive(
169 tstr, ln, (double)n->val[i], prec, n->unit_sys, n->unit_type[i], true, false);
170 BLI_snprintf(&str[j * ln], ln, "%s%s%s", cur, tstr, cur);
171 }
172 }
173 }
174 else {
175 const char *cur = (i == n->idx) ? "|" : "";
176 BLI_snprintf(&str[j * ln], ln, "%sNONE%s", cur, cur);
177 }
178 /* We might have cut some multi-bytes utf8 chars
179 * (e.g. trailing '°' of degrees values can become only 'A')... */
180 BLI_utf8_invalid_strip(&str[j * ln], strlen(&str[j * ln]));
181 }
182 }
183
hasNumInput(const NumInput * n)184 bool hasNumInput(const NumInput *n)
185 {
186 short i;
187
188 #ifdef USE_FAKE_EDIT
189 if (n->flag & NUM_FAKE_EDITED) {
190 return true;
191 }
192 #endif
193
194 for (i = 0; i <= n->idx_max; i++) {
195 if (n->val_flag[i] & NUM_EDITED) {
196 return true;
197 }
198 }
199
200 return false;
201 }
202
203 /**
204 * \warning \a vec must be set beforehand otherwise we risk uninitialized vars.
205 */
applyNumInput(NumInput * n,float * vec)206 bool applyNumInput(NumInput *n, float *vec)
207 {
208 short i, j;
209 float val;
210
211 if (hasNumInput(n)) {
212 for (j = 0; j <= n->idx_max; j++) {
213 #ifdef USE_FAKE_EDIT
214 if (n->flag & NUM_FAKE_EDITED) {
215 val = n->val[j];
216 }
217 else
218 #endif
219 {
220 /* if AFFECTALL and no number typed and cursor not on number, use first number */
221 i = (n->flag & NUM_AFFECT_ALL && n->idx != j && !(n->val_flag[j] & NUM_EDITED)) ? 0 : j;
222 val = (!(n->val_flag[i] & NUM_EDITED) && n->val_flag[i] & NUM_NULL_ONE) ? 1.0f : n->val[i];
223
224 if (n->val_flag[i] & NUM_NO_NEGATIVE && val < 0.0f) {
225 val = 0.0f;
226 }
227 if (n->val_flag[i] & NUM_NO_FRACTION && val != floorf(val)) {
228 val = floorf(val + 0.5f);
229 if (n->val_flag[i] & NUM_NO_ZERO && val == 0.0f) {
230 val = 1.0f;
231 }
232 }
233 else if (n->val_flag[i] & NUM_NO_ZERO && val == 0.0f) {
234 val = 0.0001f;
235 }
236 }
237 vec[j] = val;
238 }
239 #ifdef USE_FAKE_EDIT
240 n->flag &= ~NUM_FAKE_EDITED;
241 #endif
242 return true;
243 }
244
245 /* Else, we set the 'org' values for numinput! */
246 for (j = 0; j <= n->idx_max; j++) {
247 n->val[j] = n->val_org[j] = vec[j];
248 }
249 return false;
250 }
251
value_to_editstr(NumInput * n,int idx)252 static void value_to_editstr(NumInput *n, int idx)
253 {
254 const int prec = 6; /* editing, higher precision needed. */
255 n->str_cur = BKE_unit_value_as_string_adaptive(n->str,
256 NUM_STR_REP_LEN,
257 (double)n->val[idx],
258 prec,
259 n->unit_sys,
260 n->unit_type[idx],
261 true,
262 false);
263 }
264
editstr_insert_at_cursor(NumInput * n,const char * buf,const int buf_len)265 static bool editstr_insert_at_cursor(NumInput *n, const char *buf, const int buf_len)
266 {
267 int cur = n->str_cur;
268 int len = strlen(&n->str[cur]) + 1; /* +1 for the trailing '\0'. */
269 int n_cur = cur + buf_len;
270
271 if (n_cur + len >= NUM_STR_REP_LEN) {
272 return false;
273 }
274
275 memmove(&n->str[n_cur], &n->str[cur], len);
276 memcpy(&n->str[cur], buf, sizeof(char) * buf_len);
277
278 n->str_cur = n_cur;
279 return true;
280 }
281
user_string_to_number(bContext * C,const char * str,const UnitSettings * unit,int type,const char * error_prefix,double * r_value)282 bool user_string_to_number(bContext *C,
283 const char *str,
284 const UnitSettings *unit,
285 int type,
286 const char *error_prefix,
287 double *r_value)
288 {
289 #ifdef WITH_PYTHON
290 double unit_scale = BKE_scene_unit_scale(unit, type, 1.0);
291 if (BKE_unit_string_contains_unit(str, type)) {
292 char str_unit_convert[256];
293 BLI_strncpy(str_unit_convert, str, sizeof(str_unit_convert));
294 BKE_unit_replace_string(
295 str_unit_convert, sizeof(str_unit_convert), str, unit_scale, unit->system, type);
296
297 return BPY_run_string_as_number(C, NULL, str_unit_convert, error_prefix, r_value);
298 }
299
300 int success = BPY_run_string_as_number(C, NULL, str, error_prefix, r_value);
301 *r_value = BKE_unit_apply_preferred_unit(unit, type, *r_value);
302 *r_value /= unit_scale;
303 return success;
304
305 #else
306 UNUSED_VARS(C, unit, type);
307 *r_value = atof(str);
308 return true;
309 #endif
310 }
311
editstr_is_simple_numinput(const char ascii)312 static bool editstr_is_simple_numinput(const char ascii)
313 {
314 if (ascii >= '0' && ascii <= '9') {
315 return true;
316 }
317 if (ascii == '.') {
318 return true;
319 }
320 return false;
321 }
322
handleNumInput(bContext * C,NumInput * n,const wmEvent * event)323 bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event)
324 {
325 const char *utf8_buf = NULL;
326 char ascii[2] = {'\0', '\0'};
327 bool updated = false;
328 short idx = n->idx, idx_max = n->idx_max;
329 short dir = STRCUR_DIR_NEXT, mode = STRCUR_JUMP_NONE;
330 int cur;
331
332 #ifdef USE_FAKE_EDIT
333 if (U.flag & USER_FLAG_NUMINPUT_ADVANCED)
334 #endif
335 {
336 if ((event->ctrl == 0) && (event->alt == 0) && (event->ascii != '\0') &&
337 strchr("01234567890@%^&*-+/{}()[]<>.|", event->ascii)) {
338 if (!(n->flag & NUM_EDIT_FULL)) {
339 n->flag |= NUM_EDITED;
340 n->flag |= NUM_EDIT_FULL;
341 n->val_flag[idx] |= NUM_EDITED;
342 }
343 }
344 }
345
346 #ifdef USE_FAKE_EDIT
347 /* XXX Hack around keyboards without direct access to '=' nor '*'... */
348 if (ELEM(event->ascii, '=', '*')) {
349 if (!(n->flag & NUM_EDIT_FULL)) {
350 n->flag |= NUM_EDIT_FULL;
351 n->val_flag[idx] |= NUM_EDITED;
352 return true;
353 }
354 if (event->ctrl) {
355 n->flag &= ~NUM_EDIT_FULL;
356 return true;
357 }
358 }
359 #endif
360
361 switch (event->type) {
362 case EVT_MODAL_MAP:
363 if (ELEM(event->val, NUM_MODAL_INCREMENT_UP, NUM_MODAL_INCREMENT_DOWN)) {
364 n->val[idx] += (event->val == NUM_MODAL_INCREMENT_UP) ? n->val_inc[idx] : -n->val_inc[idx];
365 value_to_editstr(n, idx);
366 n->val_flag[idx] |= NUM_EDITED;
367 updated = true;
368 }
369 else {
370 /* might be a char too... */
371 utf8_buf = event->utf8_buf;
372 ascii[0] = event->ascii;
373 }
374 break;
375 case EVT_BACKSPACEKEY:
376 /* Part specific to backspace... */
377 if (!(n->val_flag[idx] & NUM_EDITED)) {
378 copy_v3_v3(n->val, n->val_org);
379 n->val_flag[0] &= ~NUM_EDITED;
380 n->val_flag[1] &= ~NUM_EDITED;
381 n->val_flag[2] &= ~NUM_EDITED;
382 #ifdef USE_FAKE_EDIT
383 n->flag |= NUM_FAKE_EDITED;
384 #else
385 n->flag |= NUM_EDIT_FULL;
386 #endif
387 updated = true;
388 break;
389 }
390 else if (event->shift || !n->str[0]) {
391 n->val[idx] = n->val_org[idx];
392 n->val_flag[idx] &= ~NUM_EDITED;
393 n->str[0] = '\0';
394 n->str_cur = 0;
395 updated = true;
396 break;
397 }
398 /* Else, common behavior with DELKEY,
399 * only difference is remove char(s) before/after the cursor. */
400 dir = STRCUR_DIR_PREV;
401 ATTR_FALLTHROUGH;
402 case EVT_DELKEY:
403 if ((n->val_flag[idx] & NUM_EDITED) && n->str[0]) {
404 int t_cur = cur = n->str_cur;
405 if (event->ctrl) {
406 mode = STRCUR_JUMP_DELIM;
407 }
408 BLI_str_cursor_step_utf8(n->str, strlen(n->str), &t_cur, dir, mode, true);
409 if (t_cur != cur) {
410 if (t_cur < cur) {
411 SWAP(int, t_cur, cur);
412 n->str_cur = cur;
413 }
414 /* +1 for trailing '\0'. */
415 memmove(&n->str[cur], &n->str[t_cur], strlen(&n->str[t_cur]) + 1);
416 updated = true;
417 }
418 if (!n->str[0]) {
419 n->val[idx] = n->val_org[idx];
420 }
421 }
422 else {
423 return false;
424 }
425 break;
426 case EVT_LEFTARROWKEY:
427 dir = STRCUR_DIR_PREV;
428 ATTR_FALLTHROUGH;
429 case EVT_RIGHTARROWKEY:
430 cur = n->str_cur;
431 if (event->ctrl) {
432 mode = STRCUR_JUMP_DELIM;
433 }
434 BLI_str_cursor_step_utf8(n->str, strlen(n->str), &cur, dir, mode, true);
435 if (cur != n->str_cur) {
436 n->str_cur = cur;
437 return true;
438 }
439 return false;
440 case EVT_HOMEKEY:
441 if (n->str[0]) {
442 n->str_cur = 0;
443 return true;
444 }
445 return false;
446 case EVT_ENDKEY:
447 if (n->str[0]) {
448 n->str_cur = strlen(n->str);
449 return true;
450 }
451 return false;
452 case EVT_TABKEY:
453 #ifdef USE_FAKE_EDIT
454 n->val_flag[idx] &= ~(NUM_NEGATE | NUM_INVERSE);
455 #endif
456
457 idx = (idx + idx_max + (event->ctrl ? 0 : 2)) % (idx_max + 1);
458 n->idx = idx;
459 if (n->val_flag[idx] & NUM_EDITED) {
460 value_to_editstr(n, idx);
461 }
462 else {
463 n->str[0] = '\0';
464 n->str_cur = 0;
465 }
466 return true;
467 case EVT_PADPERIOD:
468 case EVT_PERIODKEY:
469 /* Force numdot, some OSs/countries generate a comma char in this case,
470 * sic... (T37992) */
471 ascii[0] = '.';
472 utf8_buf = ascii;
473 break;
474 #if 0
475 /* Those keys are not directly accessible in all layouts,
476 * preventing to generate matching events.
477 * So we use a hack (ascii value) instead, see below.
478 */
479 case EQUALKEY:
480 case PADASTERKEY:
481 if (!(n->flag & NUM_EDIT_FULL)) {
482 n->flag |= NUM_EDIT_FULL;
483 n->val_flag[idx] |= NUM_EDITED;
484 return true;
485 }
486 else if (event->ctrl) {
487 n->flag &= ~NUM_EDIT_FULL;
488 return true;
489 }
490 break;
491 #endif
492
493 #ifdef USE_FAKE_EDIT
494 case EVT_PADMINUS:
495 case EVT_MINUSKEY:
496 if (event->ctrl || !(n->flag & NUM_EDIT_FULL)) {
497 n->val_flag[idx] ^= NUM_NEGATE;
498 updated = true;
499 }
500 break;
501 case EVT_PADSLASHKEY:
502 case EVT_SLASHKEY:
503 if (event->ctrl || !(n->flag & NUM_EDIT_FULL)) {
504 n->val_flag[idx] ^= NUM_INVERSE;
505 updated = true;
506 }
507 break;
508 #endif
509 case EVT_CKEY:
510 if (event->ctrl) {
511 /* Copy current str to the copypaste buffer. */
512 WM_clipboard_text_set(n->str, 0);
513 updated = true;
514 }
515 break;
516 case EVT_VKEY:
517 if (event->ctrl) {
518 /* extract the first line from the clipboard */
519 int pbuf_len;
520 char *pbuf = WM_clipboard_text_get_firstline(false, &pbuf_len);
521
522 if (pbuf) {
523 const bool success = editstr_insert_at_cursor(n, pbuf, pbuf_len);
524
525 MEM_freeN(pbuf);
526 if (!success) {
527 return false;
528 }
529
530 n->val_flag[idx] |= NUM_EDITED;
531 }
532 updated = true;
533 }
534 break;
535 default:
536 break;
537 }
538
539 if (!updated && !utf8_buf && (event->utf8_buf[0] || event->ascii)) {
540 utf8_buf = event->utf8_buf;
541 ascii[0] = event->ascii;
542 }
543
544 /* Up to this point, if we have a ctrl modifier, skip.
545 * This allows to still access most of modals' shortcuts even in numinput mode.
546 */
547 if (!updated && event->ctrl) {
548 return false;
549 }
550
551 if ((!utf8_buf || !utf8_buf[0]) && ascii[0]) {
552 /* Fallback to ascii. */
553 utf8_buf = ascii;
554 }
555
556 if (utf8_buf && utf8_buf[0]) {
557 if (!(n->flag & NUM_EDIT_FULL)) {
558 /* In simple edit mode, we only keep a few chars as valid! */
559 /* no need to decode unicode, ascii is first char only */
560 if (!editstr_is_simple_numinput(utf8_buf[0])) {
561 return false;
562 }
563 }
564
565 if (!editstr_insert_at_cursor(n, utf8_buf, BLI_str_utf8_size(utf8_buf))) {
566 return false;
567 }
568
569 n->val_flag[idx] |= NUM_EDITED;
570 }
571 else if (!updated) {
572 return false;
573 }
574
575 /* At this point, our value has changed, try to interpret it with python
576 * (if str is not empty!). */
577 if (n->str[0]) {
578 const float val_prev = n->val[idx];
579 Scene *sce = CTX_data_scene(C);
580
581 double val;
582 int success = user_string_to_number(
583 C, n->str, &sce->unit, n->unit_type[idx], IFACE_("Numeric input evaluation"), &val);
584
585 if (success) {
586 n->val[idx] = (float)val;
587 n->val_flag[idx] &= ~NUM_INVALID;
588 }
589 else {
590 n->val_flag[idx] |= NUM_INVALID;
591 }
592
593 #ifdef USE_FAKE_EDIT
594 if (n->val_flag[idx] & NUM_NEGATE) {
595 n->val[idx] = -n->val[idx];
596 }
597 if (n->val_flag[idx] & NUM_INVERSE) {
598 val = n->val[idx];
599 /* If we invert on radians when user is in degrees,
600 * you get unexpected results... See T53463. */
601 if (!n->unit_use_radians && n->unit_type[idx] == B_UNIT_ROTATION) {
602 val = RAD2DEG(val);
603 }
604 val = 1.0 / val;
605 if (!n->unit_use_radians && n->unit_type[idx] == B_UNIT_ROTATION) {
606 val = DEG2RAD(val);
607 }
608 n->val[idx] = (float)val;
609 }
610 #endif
611
612 if (UNLIKELY(!isfinite(n->val[idx]))) {
613 n->val[idx] = val_prev;
614 n->val_flag[idx] |= NUM_INVALID;
615 }
616 }
617
618 /* REDRAW SINCE NUMBERS HAVE CHANGED */
619 return true;
620 }
621