user interface units, off by default.
[blender-staging.git] / source / blender / blenkernel / intern / unit.c
1 /**
2  *
3  * ***** BEGIN GPL LICENSE BLOCK *****
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software Foundation,
17  * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18  *
19  * Contributor(s): Campbell Barton
20  *
21  * ***** END GPL LICENSE BLOCK *****
22  */
23
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <ctype.h>
27 #include <string.h>
28 #include <math.h>
29
30 /* define a single unit */
31 typedef struct bUnitDef {
32         char *name;
33
34         /* optional, can be null */
35         char *name_plural;
36         char *name_short;       /* this is used for display*/
37         char *name_alt;         /* alternative name */
38         
39         double mul;
40         double bias;
41 } bUnitDef;
42
43 /* define a single unit */
44 typedef struct bUnitCollection {
45         struct bUnitDef *units;
46         int def;                                        /* default unit, use for 0.0, or none given */
47         int flag;                                       /* options for this system */
48 } bUnitCollection;
49
50 static struct bUnitDef buMetricLenDef[] = {
51         {"kilometer", "kilometers",             "km", NULL,     1000.0, 0.0},
52         {"meter", "meters",                             "m",  NULL,     1.0, 0.0},
53         {"centimeter", "centimeters",   "cm", NULL,     0.01, 0.0},
54         {"millimeter", "millimeters",   "mm", NULL,     0.001, 0.0},
55         {"micrometer", "micrometers",   "um", "┬Ám",    0.000001, 0.0}, // micron too?
56         {"nanometer", "nanometers",             "nm", NULL,     0.000000001, 0.0},
57         {"picometer", "picometers",             "pm", NULL,     0.000000000001, 0.0},
58         {NULL, NULL, NULL,      NULL, 0.0, 0.0}
59 };
60 static struct bUnitCollection buMetricLenCollecton = {buMetricLenDef, 1, 0};
61
62 #define IMPERIAL_DEFAULT 3 /* inch */
63 static struct bUnitDef buImperialLenDef[] = {
64         {"mile", "miles",                               "mi", "m",      1609.344, 0.0},
65         {"yard", "yards",                               "yd", NULL,     0.9144, 0.0},
66         {"foot", "feet",                                "'", "ft",      0.3048, 0.0},
67         {"inch", "inches",                              "\"", "in",     0.0254, 0.0},
68         {"thou", "thous",                               "mil", NULL,0.0000254, 0.0},
69         {NULL, NULL, NULL, NULL, 0.0, 0.0}
70 };
71 static struct bUnitCollection buImperialLenCollecton = {buImperialLenDef, 2, 0};
72
73 static struct bUnitCollection *bUnitSystems[][8] = {
74         {0,&buMetricLenCollecton, 0,0,0,0,0,0}, /* metric */
75         {0,&buImperialLenCollecton, 0,0,0,0,0,0}, /* imperial */
76         {0,0,0,0,0,0,0,0}
77 };
78
79 /* internal, has some option not exposed */
80 static bUnitCollection *unit_get_system(int system, int type)
81 {
82         return bUnitSystems[system-1][type];
83 }
84
85 static bUnitDef *unit_best_fit(double value, bUnitCollection *usys, bUnitDef *unit_start)
86 {
87         bUnitDef *unit;
88         double value_abs= value>0.0?value:-value;
89
90         for(unit= unit_start ? unit_start:usys->units; unit->name; unit++)
91                 if (value_abs >= unit->mul)
92                         return unit;
93
94         return &usys->units[usys->def];
95 }
96
97 /* convert into 2 units and 2 values for "2ft, 3inch" syntax */
98 static void unit_dual_convert(double value, bUnitCollection *usys,
99                 bUnitDef **unit_a, bUnitDef **unit_b, double *value_a, double *value_b)
100 {
101         bUnitDef *unit= unit_best_fit(value, usys, NULL);
102
103         *value_a= floor(value/unit->mul) * unit->mul;
104         *value_b= value - (*value_a);
105
106         *unit_a=        unit;
107         *unit_b=        unit_best_fit(*value_b, usys, *unit_a);
108 }
109
110 static int unit_as_string(char *str, double value, int prec, bUnitCollection *usys,
111                 /* non exposed options */
112                 bUnitDef *unit, char pad)
113 {
114         double value_conv;
115         int len, i;
116         
117         if(unit) {
118                 /* use unit without finding the best one */
119         }
120         else if(value == 0.0) {
121                 /* use the default units since there is no way to convert */
122                 unit= &usys->units[usys->def];
123         }
124         else {
125                 unit= unit_best_fit(value, usys, NULL);
126         }
127
128         value_conv= value/unit->mul;
129
130         /* Convert to a string */
131         {
132                 char conv_str[5] = {'%', '.', '0'+prec, 'f', '\0'}; /* "%.2f" when prec is 2, must be under 10 */
133                 len= sprintf(str, conv_str, (float)value_conv);
134         }
135         
136         
137         /* Add unit prefix and strip zeros */
138         {
139                 /* replace trailing zero's with spaces 
140                  * so the number is less complicated but allignment in a button wont
141                  * jump about while dragging */
142                 int j;
143                 i= len-1;
144
145         
146                 while(i>0 && str[i]=='0') { /* 4.300 -> 4.3 */
147                         str[i--]= pad;
148                 }
149                 
150                 if(i>0 && str[i]=='.') { /* 10. -> 10 */
151                         str[i--]= pad;
152                 }
153                 
154                 /* Now add the suffix */
155                 i++;
156                 j=0;
157                 while(unit->name_short[j]) {
158                         str[i++]= unit->name_short[j++];
159                 }
160
161                 if(pad) {
162                         /* this loop only runs if so many zeros were removed that
163                          * the unit name only used padded chars,
164                          * In that case add padding for the name. */
165
166                         while(i<=len+j) {
167                                 str[i++]= pad;
168                         }
169                 }
170                 
171                 /* terminate no matter whats done with padding above */
172                 str[i] = '\0';
173         }
174
175         return i;
176 }
177
178 /* Used for drawing number buttons, try keep fast */
179 void bUnit_AsString(char *str, double value, int prec, int system, int type, int split, int pad)
180 {
181         bUnitCollection *usys = unit_get_system(system, type);
182
183         if(split) {
184                 int i;
185                 bUnitDef *unit_a, *unit_b;
186                 double value_a, value_b;
187
188                 unit_dual_convert(value, usys,          &unit_a, &unit_b, &value_a, &value_b);
189
190                 /* check the 2 is a smaller unit */
191                 if(unit_b > unit_a) {
192                         i= unit_as_string(str, value_a, prec, usys,    unit_a, '\0');
193                         i= strlen(str);
194                         str[i++]= ',';
195                         str[i++]= ' ';
196
197                         /* use low precision since this is a smaller unit */
198                         unit_as_string(str+i, value_b, prec?1:0, usys, unit_b, '\0');
199                         return;
200                 }
201         }
202
203         unit_as_string(str, value, prec, usys,    NULL, pad?' ':'\0');
204 }
205
206
207 static int unit_scale_str(char *str, char *str_tmp, double scale_pref, bUnitDef *unit, char *replace_str)
208 {
209         char *str_found;
210         int change= 0;
211
212         if(replace_str && (str_found= strstr(str, replace_str))) {
213                 /* previous char cannot be a letter */
214                 if (str_found == str || isalpha(*(str_found-1))==0) {
215                         int len_name = strlen(replace_str);
216
217                         /* next char cannot be alphanum */
218                         if (!isalpha(*(str_found+len_name))) {
219                                 int len= strlen(str);
220                                 int len_num= sprintf(str_tmp, "*%g", scale_pref*unit->mul);
221                                 memmove(str_found+len_num, str_found+len_name, (len+1)-(int)((str_found+len_name)-str)); /* may grow or shrink the string, 1+ to copy the string terminator */
222                                 memcpy(str_found, str_tmp, len_num); /* without the string terminator */
223                                 change= 1;
224                         }
225                 }
226         }
227         return change;
228 }
229
230 static int unit_replace(char *str, char *str_tmp, double scale_pref, bUnitDef *unit)
231 {       
232         //unit_replace_delimit(str, str_tmp);
233         int change= 0;
234         change |= unit_scale_str(str, str_tmp, scale_pref, unit, unit->name_short);
235         change |= unit_scale_str(str, str_tmp, scale_pref, unit, unit->name_plural);
236         change |= unit_scale_str(str, str_tmp, scale_pref, unit, unit->name_alt);
237         change |= unit_scale_str(str, str_tmp, scale_pref, unit, unit->name);
238         return change;
239 }
240
241 /* make a copy of the string that replaces the units with numbers
242  * this is used before parsing
243  * This is only used when evaluating user input and can afford to be a bit slower
244  * 
245  * This is to be used before python evaluation so..
246  * 10.1km -> 10.1*1000.0
247  * ...will be resolved by python.
248  * 
249  * return true of a change was made.
250  */
251 int bUnit_ReplaceString(char *str, char *str_orig, double scale_pref, int system, int type)
252 {
253         bUnitCollection *usys = unit_get_system(system, type);
254         bUnitDef *unit;
255         char str_tmp[256];
256         int change= 0;
257         
258         strcpy(str, str_orig);
259         
260         for(unit= usys->units; unit->name; unit++) {
261                 /* incase there are multiple instances */
262                 while(unit_replace(str, str_tmp, scale_pref, unit))
263                         change= 1;
264         }
265
266         return change;
267 }