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
This commit is contained in:
Hans Goudey 2020-06-01 08:22:27 -04:00
parent 8d670546f9
commit 45dbc38a8b
Notes: blender-bot 2023-02-14 07:30:31 +01:00
Referenced by commit e6739ed91d, Fix T82407: Negative number input gives syntax error for velocities and
Referenced by commit 9de5adc6a1, Fix: Remove debug print added mistakenly
Referenced by commit a5e9f024f2, Fix T77289: Crash when typing negative numbers
Referenced by issue #82407, Negative number input gives syntax error for velocities and accelerations
Referenced by issue #74552, Moving negative imperial units works incorrectly
1 changed files with 113 additions and 0 deletions

View File

@ -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);