Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBastien Montagne <montagne29@wanadoo.fr>2013-12-21 20:11:43 +0400
committerBastien Montagne <montagne29@wanadoo.fr>2013-12-21 20:44:48 +0400
commit87cc890aef53c4660448b1125dc0c40a187ae1f2 (patch)
tree5742d07986fa362b4f17590129dd8c5692cd51b7 /source/blender/editors/util/numinput.c
parentf72b86da8cab4bff482fc58095d14c97823fa39f (diff)
Support units in modal numinput
Summary: This completly changes the way modal numinput is handled. Now, edited expression is a string, which then gets unit- and py-evaluated to get a float value. We gain many power and flexibility, but lose a few "shortcuts" like '-' to negate, or '/' to inverse (if they are really needed, we still can add them with modifiers, like e.g. ctrl-/ or so). Features: - units (cm, ", deg, etc.). - basic operations from python/BKE_unit (+, *, **, etc.), and math constants and functions (pi, sin, etc.). - you can navigate in edited value (left/right key, ctrl to move by block) and insert/delete chars, e.g. to fix a typo without having to rewrite everything. - you can go to next/previous value with (ctrl-)TAB key. - As before, hitting backspace after having deleted all leading chars will first reset the edited value to init state, and on second press, the whole "modal numinput" editing will be cancelled, going back to usual transform with mouse. Notes: - Did not touch to how values are shown in header when modal numinput is not enabled (would do that in another commit), so this is still quite inconsistent. - Added back radian support in BKE_unit. - Added arcminute/arcsecond to BKE_unit. (those unit changes affect all angle UI controls, btw, so you can now enter radians or longitude/latitude values when in degrees units). Related to T37600. Reviewers: brecht, campbellbarton, carter2422 Reviewed By: brecht, campbellbarton, carter2422 Thanks everybody! Differential Revision: http://developer.blender.org/D61
Diffstat (limited to 'source/blender/editors/util/numinput.c')
-rw-r--r--source/blender/editors/util/numinput.c489
1 files changed, 282 insertions, 207 deletions
diff --git a/source/blender/editors/util/numinput.c b/source/blender/editors/util/numinput.c
index 0feaf936172..891051185ac 100644
--- a/source/blender/editors/util/numinput.c
+++ b/source/blender/editors/util/numinput.c
@@ -24,93 +24,97 @@
* \ingroup edutil
*/
-
-#include <math.h> /* fabs */
-#include <stdio.h> /* for size_t */
+#include "MEM_guardedalloc.h"
#include "BLI_utildefines.h"
+#include "BLI_math.h"
#include "BLI_string.h"
+#include "BLI_string_utf8.h"
+#include "BLI_string_cursor_utf8.h"
+
+#include "BKE_context.h"
+#include "BKE_unit.h"
+
+#include "DNA_scene_types.h"
+#include "WM_api.h"
#include "WM_types.h"
+#ifdef WITH_PYTHON
+#include "BPY_extern.h"
+#endif
+
#include "ED_numinput.h"
+
+/* NumInput.val_flag[] */
+enum {
+ /* (1 << 8) and below are reserved for public flags! */
+ NUM_EDITED = (1 << 9), /* User has edited this value somehow. */
+ NUM_INVALID = (1 << 10), /* Current expression for this value is invalid. */
+};
+
/* ************************** Functions *************************** */
/* ************************** NUMINPUT **************************** */
void initNumInput(NumInput *n)
{
- n->flag =
- n->idx =
- n->idx_max =
- n->inv[0] =
- n->inv[1] =
- n->inv[2] =
- n->ctrl[0] =
- n->ctrl[1] =
- n->ctrl[2] = 0;
-
- n->val[0] =
- n->val[1] =
- n->val[2] = 0.0f;
+ n->unit_sys = USER_UNIT_NONE;
+ n->unit_type[0] = n->unit_type[1] = n->unit_type[2] = B_UNIT_NONE;
+ n->idx = 0;
+ n->idx_max = 0;
+ n->flag = 0;
+ n->val_flag[0] = n->val_flag[1] = n->val_flag[2] = 0;
+ zero_v3(n->val_org);
+ zero_v3(n->val);
+ n->str[0] = '\0';
+ n->str_cur = 0;
+ copy_v3_fl(n->val_inc, 1.0f);
}
+/* str must be NUM_STR_REP_LEN * (idx_max + 1) length. */
void outputNumInput(NumInput *n, char *str)
{
- char cur;
- char inv[] = "1/";
short i, j;
const int ln = NUM_STR_REP_LEN;
+ const int prec = 4; /* draw-only, and avoids too much issues with radian->degrees conversion. */
for (j = 0; j <= n->idx_max; j++) {
/* if AFFECTALL and no number typed and cursor not on number, use first number */
- if (n->flag & NUM_AFFECT_ALL && n->idx != j && n->ctrl[j] == 0)
- i = 0;
- else
- i = j;
-
- if (n->idx != i)
- cur = ' ';
- else
- cur = '|';
-
- if (n->inv[i])
- inv[0] = '1';
- else
- inv[0] = 0;
-
- if (n->val[i] > 1e10f || n->val[i] < -1e10f)
- BLI_snprintf(&str[j * ln], ln, "%s%.4e%c", inv, n->val[i], cur);
- else
- switch (n->ctrl[i]) {
- case 0:
- BLI_snprintf(&str[j * ln], ln, "%sNONE%c", inv, cur);
- break;
- case 1:
- case -1:
- BLI_snprintf(&str[j * ln], ln, "%s%.0f%c", inv, n->val[i], cur);
- break;
- case 10:
- case -10:
- BLI_snprintf(&str[j * ln], ln, "%s%.f.%c", inv, n->val[i], cur);
- break;
- case 100:
- case -100:
- BLI_snprintf(&str[j * ln], ln, "%s%.1f%c", inv, n->val[i], cur);
- break;
- case 1000:
- case -1000:
- BLI_snprintf(&str[j * ln], ln, "%s%.2f%c", inv, n->val[i], cur);
- break;
- case 10000:
- case -10000:
- BLI_snprintf(&str[j * ln], ln, "%s%.3f%c", inv, n->val[i], cur);
- break;
- default:
- BLI_snprintf(&str[j * ln], ln, "%s%.4e%c", inv, n->val[i], cur);
- break;
+ i = (n->flag & NUM_AFFECT_ALL && n->idx != j && !(n->val_flag[j] & NUM_EDITED)) ? 0 : j;
+
+ if (n->val_flag[i] & NUM_EDITED) {
+ if (i == n->idx && n->str[0]) {
+ char before_cursor[NUM_STR_REP_LEN];
+ char val[16];
+ if (n->val_flag[i] & NUM_INVALID) {
+ BLI_strncpy(val, "Invalid", sizeof(val));
+ }
+ else {
+ bUnit_AsString(val, sizeof(val), (double)n->val[i], prec,
+ n->unit_sys, n->unit_type[i], true, false);
+ }
+ BLI_strncpy(before_cursor, n->str, n->str_cur + 1); /* +1 because of trailing '\0' */
+ BLI_snprintf(&str[j * ln], ln, "[%s|%s] = %s", before_cursor, &n->str[n->str_cur], val);
}
+ else {
+ const char *cur = (i == n->idx) ? "|" : "";
+ if (n->unit_use_radians && n->unit_type[i] == B_UNIT_ROTATION) {
+ /* Radian exception... */
+ BLI_snprintf(&str[j * ln], ln, "%s%.6gr%s", cur, n->val[i], cur);
+ }
+ else {
+ char tstr[NUM_STR_REP_LEN];
+ bUnit_AsString(tstr, ln, (double)n->val[i], prec, n->unit_sys, n->unit_type[i], true, false);
+ BLI_snprintf(&str[j * ln], ln, "%s%s%s", cur, tstr, cur);
+ }
+ }
+ }
+ else {
+ const char *cur = (i == n->idx) ? "|" : "";
+ BLI_snprintf(&str[j * ln], ln, "%sNONE%s", cur, cur);
+ }
}
}
@@ -119,8 +123,9 @@ bool hasNumInput(const NumInput *n)
short i;
for (i = 0; i <= n->idx_max; i++) {
- if (n->ctrl[i])
+ if (n->val_flag[i] & NUM_EDITED) {
return true;
+ }
}
return false;
@@ -132,177 +137,247 @@ bool hasNumInput(const NumInput *n)
void applyNumInput(NumInput *n, float *vec)
{
short i, j;
+ float val;
if (hasNumInput(n)) {
for (j = 0; j <= n->idx_max; j++) {
/* if AFFECTALL and no number typed and cursor not on number, use first number */
- if (n->flag & NUM_AFFECT_ALL && n->idx != j && n->ctrl[j] == 0)
- i = 0;
- else
- i = j;
+ i = (n->flag & NUM_AFFECT_ALL && n->idx != j && !(n->val_flag[j] & NUM_EDITED)) ? 0 : j;
+ val = (!(n->val_flag[i] & NUM_EDITED) && n->val_flag[i] & NUM_NULL_ONE) ? 1.0f : n->val[i];
- if (n->ctrl[i] == 0 && n->flag & NUM_NULL_ONE) {
- vec[j] = 1.0f;
+ if (n->val_flag[i] & NUM_NO_NEGATIVE && val < 0.0f) {
+ val = 0.0f;
}
- else if (n->val[i] == 0.0f && n->flag & NUM_NO_ZERO) {
- vec[j] = 0.0001f;
+ if (n->val_flag[i] & NUM_NO_ZERO && val == 0.0f) {
+ val = 0.0001f;
}
- else {
- if (n->inv[i]) {
- vec[j] = 1.0f / n->val[i];
- }
- else {
- vec[j] = n->val[i];
+ if (n->val_flag[i] & NUM_NO_FRACTION && val != floorf(val)) {
+ val = floorf(val + 0.5f);
+ if (n->val_flag[i] & NUM_NO_ZERO && val == 0.0f) {
+ val = 1.0f;
}
}
+ vec[j] = val;
}
}
}
-bool handleNumInput(NumInput *n, const wmEvent *event)
+
+static void value_to_editstr(NumInput *n, int idx)
{
- float Val = 0;
- short idx = n->idx, idx_max = n->idx_max;
+ const int prec = 6; /* editing, higher precision needed. */
+ bUnit_AsString(n->str, NUM_STR_REP_LEN, (double)n->val[idx], prec,
+ n->unit_sys, n->unit_type[idx], true, false);
+ n->str_cur = strlen(n->str);
+}
- if (event->type == EVT_MODAL_MAP) {
- switch (event->val) {
- case NUM_MODAL_INCREMENT_UP:
- if (!n->ctrl[idx])
- n->ctrl[idx] = 1;
+static bool editstr_insert_at_cursor(NumInput *n, const char *buf, const int buf_len)
+{
+ int cur = n->str_cur;
+ int len = strlen(&n->str[cur]) + 1; /* +1 for the trailing '\0'. */
+ int n_cur = cur + buf_len;
- n->val[idx] += n->increment;
- break;
- case NUM_MODAL_INCREMENT_DOWN:
- if (!n->ctrl[idx])
- n->ctrl[idx] = 1;
+ if (n_cur + len >= NUM_STR_REP_LEN) {
+ return false;
+ }
+
+ memmove(&n->str[n_cur], &n->str[cur], len);
+ memcpy(&n->str[cur], buf, sizeof(char) * buf_len);
+
+ n->str_cur = n_cur;
+ return true;
+}
+
+bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event)
+{
+ const char *utf8_buf = NULL;
+ char ascii[2] = {'\0', '\0'};
+ bool updated = false;
+ short idx = n->idx, idx_max = n->idx_max;
+ short dir = STRCUR_DIR_NEXT, mode = STRCUR_JUMP_NONE;
+ int cur;
+ double val;
- n->val[idx] -= n->increment;
+ switch (event->type) {
+ case EVT_MODAL_MAP:
+ if (ELEM(event->val, NUM_MODAL_INCREMENT_UP, NUM_MODAL_INCREMENT_DOWN)) {
+ n->val[idx] += (event->val == NUM_MODAL_INCREMENT_UP) ? n->val_inc[idx] : -n->val_inc[idx];
+ value_to_editstr(n, idx);
+ n->val_flag[idx] |= NUM_EDITED;
+ updated = true;
+ }
+ else {
+ /* might be a char too... */
+ utf8_buf = event->utf8_buf;
+ ascii[0] = event->ascii;
+ }
+ break;
+ case BACKSPACEKEY:
+ /* Part specific to backspace... */
+ if (!(n->val_flag[idx] & NUM_EDITED)) {
+ copy_v3_v3(n->val, n->val_org);
+ n->val_flag[0] &= ~NUM_EDITED;
+ n->val_flag[1] &= ~NUM_EDITED;
+ n->val_flag[2] &= ~NUM_EDITED;
+ updated = true;
break;
- default:
- return 0;
- }
- }
- else {
- switch (event->type) {
- case BACKSPACEKEY:
- if (n->ctrl[idx] == 0) {
- n->val[0] =
- n->val[1] =
- n->val[2] = 0.0f;
- n->ctrl[0] =
- n->ctrl[1] =
- n->ctrl[2] = 0;
- n->inv[0] =
- n->inv[1] =
- n->inv[2] = 0;
- }
- else {
- n->val[idx] = 0.0f;
- n->ctrl[idx] = 0;
- n->inv[idx] = 0;
- }
+ }
+ else if (event->shift || !n->str[0]) {
+ n->val[idx] = n->val_org[idx];
+ n->val_flag[idx] &= ~NUM_EDITED;
+ n->str[0] = '\0';
+ n->str_cur = 0;
+ updated = true;
break;
- case PERIODKEY: case PADPERIOD:
- if (n->flag & NUM_NO_FRACTION)
- return 0;
-
- switch (n->ctrl[idx]) {
- case 0:
- case 1:
- n->ctrl[idx] = 10;
- break;
- case -1:
- n->ctrl[idx] = -10;
- break;
+ }
+ /* Else, common behavior with DELKEY, only difference is remove char(s) before/after the cursor. */
+ dir = STRCUR_DIR_PREV;
+ /* fall-through */
+ case DELKEY:
+ if ((n->val_flag[idx] & NUM_EDITED) && n->str[0]) {
+ int t_cur = cur = n->str_cur;
+ if (event->ctrl) {
+ mode = STRCUR_JUMP_DELIM;
}
- break;
- case PADMINUS:
- if (event->alt)
- break;
- /* fall-through */
- case MINUSKEY:
- if (n->flag & NUM_NO_NEGATIVE)
- return 0;
-
- if (n->ctrl[idx]) {
- n->ctrl[idx] *= -1;
- n->val[idx] *= -1;
+ BLI_str_cursor_step_utf8(n->str, strlen(n->str), &t_cur, dir, mode, true);
+ if (t_cur != cur) {
+ if (t_cur < cur) {
+ SWAP(int, t_cur, cur);
+ n->str_cur = cur;
+ }
+ memmove(&n->str[cur], &n->str[t_cur], strlen(&n->str[t_cur]) + 1); /* +1 for trailing '\0'. */
+ updated = true;
}
- else
- n->ctrl[idx] = -1;
- break;
- case PADSLASHKEY: case SLASHKEY:
- if (n->flag & NUM_NO_FRACTION)
- return 0;
+ }
+ else {
+ return false;
+ }
+ break;
+ case LEFTARROWKEY:
+ dir = STRCUR_DIR_PREV;
+ /* fall-through */
+ case RIGHTARROWKEY:
+ cur = n->str_cur;
+ if (event->ctrl) {
+ mode = STRCUR_JUMP_DELIM;
+ }
+ BLI_str_cursor_step_utf8(n->str, strlen(n->str), &cur, dir, mode, true);
+ if (cur != n->str_cur) {
+ n->str_cur = cur;
+ return true;
+ }
+ return false;
+ case HOMEKEY:
+ if (n->str[0]) {
+ n->str_cur = 0;
+ return true;
+ }
+ return false;
+ case ENDKEY:
+ if (n->str[0]) {
+ n->str_cur = strlen(n->str);
+ return true;
+ }
+ return false;
+ case TABKEY:
+ n->val_org[idx] = n->val[idx];
- n->inv[idx] = !n->inv[idx];
- break;
- case TABKEY:
- if (idx_max == 0)
- return 0;
-
- idx++;
- if (idx > idx_max)
- idx = 0;
- n->idx = idx;
+ idx += event->ctrl ? -1 : 1;
+ idx %= idx_max + 1;
+ n->idx = idx;
+ n->val[idx] = n->val_org[idx];
+ if (n->val_flag[idx] & NUM_EDITED) {
+ value_to_editstr(n, idx);
+ }
+ else {
+ n->str[0] = '\0';
+ n->str_cur = 0;
+ }
+ return true;
+ case CKEY:
+ if (event->ctrl) {
+ /* Copy current str to the copypaste buffer. */
+ WM_clipboard_text_set(n->str, 0);
+ updated = true;
break;
- case PAD9: case NINEKEY:
- Val += 1.0f;
- /* fall-through */
- case PAD8: case EIGHTKEY:
- Val += 1.0f;
- /* fall-through */
- case PAD7: case SEVENKEY:
- Val += 1.0f;
- /* fall-through */
- case PAD6: case SIXKEY:
- Val += 1.0f;
- /* fall-through */
- case PAD5: case FIVEKEY:
- Val += 1.0f;
- /* fall-through */
- case PAD4: case FOURKEY:
- Val += 1.0f;
- /* fall-through */
- case PAD3: case THREEKEY:
- Val += 1.0f;
- /* fall-through */
- case PAD2: case TWOKEY:
- Val += 1.0f;
- /* fall-through */
- case PAD1: case ONEKEY:
- Val += 1.0f;
- /* fall-through */
- case PAD0: case ZEROKEY:
- if (!n->ctrl[idx])
- n->ctrl[idx] = 1;
-
- if (fabsf(n->val[idx]) > 9999999.0f) {
- /* pass */
- }
- else if (n->ctrl[idx] == 1) {
- n->val[idx] *= 10;
- n->val[idx] += Val;
- }
- else if (n->ctrl[idx] == -1) {
- n->val[idx] *= 10;
- n->val[idx] -= Val;
- }
- else {
- /* float resolution breaks when over six digits after comma */
- if (ABS(n->ctrl[idx]) < 10000000) {
- n->val[idx] += Val / (float)n->ctrl[idx];
- n->ctrl[idx] *= 10;
+ }
+ /* fall-through */
+ case VKEY:
+ if (event->ctrl) {
+ /* extract the first line from the clipboard */
+ char *pbuf = WM_clipboard_text_get(0);
+
+ if (pbuf) {
+ bool success;
+ /* Only copy string until first of this char. */
+ char *cr = strchr(pbuf, '\r');
+ char *cn = strchr(pbuf, '\n');
+ if (cn && cn < cr) cr = cn;
+ if (cr) *cr = '\0';
+
+ success = editstr_insert_at_cursor(n, pbuf, strlen(pbuf));
+
+ MEM_freeN(pbuf);
+ if (!success) {
+ return false;
}
+
+ n->val_flag[idx] |= NUM_EDITED;
}
+ updated = true;
break;
- default:
- return 0;
+ }
+ /* fall-through */
+ default:
+ utf8_buf = event->utf8_buf;
+ ascii[0] = event->ascii;
+ break;
+ }
+
+ if (utf8_buf && !utf8_buf[0] && ascii[0]) {
+ /* Fallback to ascii. */
+ utf8_buf = ascii;
+ }
+
+ if (utf8_buf && utf8_buf[0]) {
+ if (!editstr_insert_at_cursor(n, utf8_buf, BLI_str_utf8_size(utf8_buf))) {
+ return false;
+ }
+
+ n->val_flag[idx] |= NUM_EDITED;
+ }
+ else if (!updated) {
+ return false;
+ }
+
+ /* At this point, our value has changed, try to interpret it with python (if str is not empty!). */
+ if (n->str[0]) {
+#ifdef WITH_PYTHON
+ char str_unit_convert[NUM_STR_REP_LEN * 6]; /* Should be more than enough! */
+ const char *default_unit = NULL;
+
+ /* Make radian default unit when needed. */
+ if (n->unit_use_radians && n->unit_type[idx] == B_UNIT_ROTATION)
+ default_unit = "r";
+
+ BLI_strncpy(str_unit_convert, n->str, sizeof(str_unit_convert));
+
+ bUnit_ReplaceString(str_unit_convert, sizeof(str_unit_convert), default_unit, 1.0,
+ n->unit_sys, n->unit_type[idx]);
+
+ /* Note: with angles, we always get values as radians here... */
+ if (BPY_button_exec(C, str_unit_convert, &val, false) != -1) {
+ n->val[idx] = (float)val;
+ n->val_flag[idx] &= ~NUM_INVALID;
+ }
+ else {
+ n->val_flag[idx] |= NUM_INVALID;
}
+#else /* Very unlikely, but does not harm... */
+ n->val[idx] = (float)atof(n->str);
+#endif /* WITH_PYTHON */
}
-
- // printf("%f\n", n->val[idx]);
/* REDRAW SINCE NUMBERS HAVE CHANGED */
- return 1;
+ return true;
}