From 45dbc38a8b156a6e704575f4935c7f3f9cf96e5d Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 1 Jun 2020 08:22:27 -0400 Subject: Fix T74552: Distribute negatives in number input This applies a relatively simple solution for fixing some unintuitive cases in unit handling. Currently entering -1m50cm evaluates to -0.5m, and similarly 1'6" evaulates to just half a foot. So effectively there's an implied + just between the numbers, which is quite confusing. This works by adding parentheses so the negative distributes to the block of values before the next operator. For example: | Before | After | | `-1m50cm + 1m -2m50cm` | `-(1m50cm) + 1m -(2m50cm)` | | `-4m + 0.5 / -1.1` | `-(4m) + 0.5 / -(1.1)` | | `-1'6"` | `-(1'6")` | | `-1e-2cm` | `-(1e-2cm) ` | Reviewed By: campbellbarton Differential Revision: https://developer.blender.org/D7813 --- source/blender/blenkernel/intern/unit.c | 113 ++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) (limited to 'source/blender') diff --git a/source/blender/blenkernel/intern/unit.c b/source/blender/blenkernel/intern/unit.c index 3f44c13ba19..77e51f9d8c0 100644 --- a/source/blender/blenkernel/intern/unit.c +++ b/source/blender/blenkernel/intern/unit.c @@ -713,6 +713,116 @@ static bool ch_is_op(char op) } } +/** + * Helper function for #unit_distribute_negatives to find the next negative to distribute. + * + * \note This unecessarily skips the next space if it comes right after the "-" + * just to make a more predictable output. + */ +static char *find_next_negative(const char *str, const char *remaining_str) +{ + char *str_found = strstr(remaining_str, "-"); + + if (str_found == NULL) { + return NULL; + } + + /* Don't use the "-" from scientific notation, but make sure we can look backwards first. */ + if ((str_found != str) && (*(str_found - 1) == 'e' || *(str_found - 1) == 'E')) { + return find_next_negative(str, str_found + 1); + } + + if (*(str_found + 1) == ' ') { + str_found++; + } + + return str_found + 1; +} + +/** + * Helper function for #unit_distribute_negatives to find the next operation, including "-". + * + * \note This unecessarily skips the space before the operation character + * just to make a more predictable output. + */ +static char *find_next_op(const char *str, char *remaining_str, int len_max) +{ + int i; + bool scientific_notation = false; + for (i = 0; i < len_max; i++) { + if (remaining_str[i] == '\0') { + return remaining_str + i; + } + + if (ch_is_op(remaining_str[i])) { + if (scientific_notation) { + scientific_notation = false; + continue; + } + + /* Make sure we don't look backwards before the start of the string. */ + if (remaining_str != str && i != 0) { + /* Check for scientific notation. */ + if (remaining_str[i - 1] == 'e' || remaining_str[i - 1] == 'E') { + scientific_notation = true; + continue; + } + + /* Return position before a space character. */ + if (remaining_str[i - 1] == ' ') { + i--; + } + } + + return remaining_str + i; + } + } + BLI_assert(!"String should be NULL terminated"); + return remaining_str + i; +} + +/** + * Put parentheses around blocks of values after negative signs to get rid of an implied "+" + * between numbers without an operation between them. For example: + * + * "-1m50cm + 1 - 2m50cm" -> "-(1m50cm) + 1 - (2m50cm)" + */ +static bool unit_distribute_negatives(char *str, const int len_max) +{ + bool changed = false; + + char *remaining_str = str; + int remaining_str_len = len_max; + int ofs = 0; + while ((remaining_str = find_next_negative(str, remaining_str)) != NULL) { + ofs = (int)(remaining_str - str); + + /* Exit early in the unlikely situation that we've run out of length to add the parentheses. */ + remaining_str_len = len_max - ofs; + if (remaining_str_len <= 2) { + return changed; + } + + changed = true; + + /* Add '(', shift the following characters to the right to make space. */ + memmove(remaining_str + 1, remaining_str, remaining_str_len - 1); + *remaining_str = '('; + + /* Add the ')' before the next operation or at the end. */ + remaining_str = find_next_op(str, remaining_str + 1, remaining_str_len); + memmove(remaining_str + 1, remaining_str, remaining_str_len - 3); + *remaining_str = ')'; + + /* Only move forward by 1 even though we added two characters. Minus signs need to be able to + * apply to the next block of values too. */ + remaining_str += 1; + remaining_str_len -= 1; + } + + return changed; +} + static int unit_scale_str(char *str, int len_max, char *str_tmp, @@ -896,6 +1006,9 @@ bool bUnit_ReplaceString( char str_tmp[TEMP_STR_SIZE]; bool changed = false; + /* Fix cases like "-1m50cm" which would evaluate to -0.5m without this. */ + changed |= unit_distribute_negatives(str, len_max); + /* Try to find a default unit from current or previous string. */ default_unit = unit_detect_from_str(usys, str, str_prev); -- cgit v1.2.3