1 | // ============================================================ // |
---|
2 | // // |
---|
3 | // File : field_shader.cxx // |
---|
4 | // Purpose : a shader based on 1 to 3 item fields // |
---|
5 | // // |
---|
6 | // Coded by Ralf Westram (coder@reallysoft.de) in June 2016 // |
---|
7 | // http://www.arb-home.de/ // |
---|
8 | // // |
---|
9 | // ============================================================ // |
---|
10 | |
---|
11 | #include "field_shader.h" |
---|
12 | |
---|
13 | #include <item_sel_list.h> |
---|
14 | #include <awt_config_manager.hxx> |
---|
15 | |
---|
16 | #include <aw_root.hxx> |
---|
17 | #include <aw_awar.hxx> |
---|
18 | #include <aw_msg.hxx> |
---|
19 | |
---|
20 | #include <ad_cb_prot.h> |
---|
21 | #include <gb_aci.h> |
---|
22 | |
---|
23 | #include <arb_global_defs.h> |
---|
24 | #include <arb_defs.h> |
---|
25 | |
---|
26 | #include <set> |
---|
27 | #include <limits> |
---|
28 | |
---|
29 | using namespace std; |
---|
30 | |
---|
31 | #define AWAR_DIM_ACTIVE(dim) dimension_awar(dim, "active") |
---|
32 | #define AWAR_FIELD(dim) dimension_awar(dim, "field") |
---|
33 | #define AWAR_ACI(dim) dimension_awar(dim, "aci") |
---|
34 | #define AWAR_VALUE_MIN(dim) dimension_awar(dim, "min") |
---|
35 | #define AWAR_VALUE_MAX(dim) dimension_awar(dim, "max") |
---|
36 | |
---|
37 | class FieldReader { |
---|
38 | RefPtr<const char> fieldname; |
---|
39 | RefPtr<const char> aci; // if NULp -> no (=empty) ACI |
---|
40 | |
---|
41 | bool is_hkey; // true if fieldname is hierarchical |
---|
42 | |
---|
43 | float min_value, max_value; |
---|
44 | float factor; |
---|
45 | |
---|
46 | static bool safe_atof(const char *strval, float& res) { |
---|
47 | // returns true |
---|
48 | // - if at least some characters have been converted and |
---|
49 | // - if result is not inf (e.g. if strval is "Infundibulomyces sp. NR-2006a") |
---|
50 | char *end; |
---|
51 | res = strtof(strval, &end); |
---|
52 | return |
---|
53 | end != strval && |
---|
54 | !is_inf(res); |
---|
55 | } |
---|
56 | |
---|
57 | void calc_factor() { |
---|
58 | float span = max_value-min_value; |
---|
59 | factor = span != 0.0 ? 1.0/span : 1/0.00001; |
---|
60 | } |
---|
61 | public: |
---|
62 | FieldReader() : fieldname(NULp), aci(NULp) {} |
---|
63 | |
---|
64 | FieldReader(const char *fieldname_, const char *aci_, float minVal, float maxVal) : |
---|
65 | fieldname(fieldname_), |
---|
66 | aci(aci_), |
---|
67 | is_hkey(strchr(fieldname, '/')), |
---|
68 | min_value(minVal), |
---|
69 | max_value(maxVal) |
---|
70 | { |
---|
71 | is_assert(fieldname); |
---|
72 | calc_factor(); |
---|
73 | } |
---|
74 | |
---|
75 | bool may_read() const { return fieldname; } // false -> never will produce value |
---|
76 | |
---|
77 | float calc_value(GBDATA *gb_item) const { |
---|
78 | /*! reads one field from passed item. |
---|
79 | * |
---|
80 | * Returns NAN in the following cases: |
---|
81 | * - 'this' is a null-reader |
---|
82 | * - no item passed |
---|
83 | * - field is missing |
---|
84 | * - STRING field contains no numeric data |
---|
85 | * |
---|
86 | * Otherwise the value is scaled (but not limited) to the value range. |
---|
87 | */ |
---|
88 | if (fieldname && gb_item) { |
---|
89 | GBDATA *gb_field = is_hkey |
---|
90 | ? GB_search(gb_item, fieldname, GB_FIND) |
---|
91 | : GB_entry(gb_item, fieldname); |
---|
92 | |
---|
93 | if (gb_field) { |
---|
94 | float val = 0.0; |
---|
95 | |
---|
96 | if (aci) { |
---|
97 | char *content = GB_read_as_string(gb_field); |
---|
98 | char *applied = GB_command_interpreter_in_env(content, aci, GBL_simple_call_env(gb_item)); |
---|
99 | free(content); |
---|
100 | |
---|
101 | if (!applied) { |
---|
102 | aw_message(GB_await_error()); |
---|
103 | const_cast<FieldReader*>(this)->aci = NULp; // avoid ACI gets evaluated (until changed again by user) |
---|
104 | return NAN; |
---|
105 | } |
---|
106 | |
---|
107 | bool converted = safe_atof(applied, val); |
---|
108 | free(applied); |
---|
109 | if (!converted) return NAN; |
---|
110 | } |
---|
111 | else { |
---|
112 | switch (GB_read_type(gb_field)) { |
---|
113 | case GB_INT: val = GB_read_int(gb_field); break; |
---|
114 | case GB_FLOAT: val = GB_read_float(gb_field); break; |
---|
115 | default: { |
---|
116 | char *content = GB_read_as_string(gb_field); |
---|
117 | bool converted = safe_atof(content, val); |
---|
118 | free(content); |
---|
119 | if (!converted) return NAN; |
---|
120 | break; |
---|
121 | } |
---|
122 | } |
---|
123 | } |
---|
124 | |
---|
125 | const float rval = (val-min_value)*factor; |
---|
126 | is_assert(!is_inf(rval)); // cannot make ValueTuple from inf |
---|
127 | return rval; |
---|
128 | } |
---|
129 | } |
---|
130 | return NAN; |
---|
131 | } |
---|
132 | |
---|
133 | }; |
---|
134 | |
---|
135 | class MultiFieldReader { |
---|
136 | FieldReader reader[3]; |
---|
137 | int dim; |
---|
138 | |
---|
139 | public: |
---|
140 | MultiFieldReader() : |
---|
141 | dim(0) |
---|
142 | {} |
---|
143 | |
---|
144 | void add_reader(const FieldReader& added) { |
---|
145 | if (added.may_read()) { |
---|
146 | is_assert(dim<3); // hardcoded limit |
---|
147 | reader[dim++] = added; |
---|
148 | } |
---|
149 | } |
---|
150 | int get_dimension() const { |
---|
151 | return dim; |
---|
152 | } |
---|
153 | |
---|
154 | ShadedValue calc_value(GBDATA *gb_item) const { |
---|
155 | switch (dim) { |
---|
156 | case 0: return ValueTuple::undefined(); |
---|
157 | case 1: return ValueTuple::make(reader[0].calc_value(gb_item)); |
---|
158 | case 2: return ValueTuple::make(reader[0].calc_value(gb_item), |
---|
159 | reader[1].calc_value(gb_item)); |
---|
160 | case 3: return ValueTuple::make(reader[0].calc_value(gb_item), |
---|
161 | reader[1].calc_value(gb_item), |
---|
162 | reader[2].calc_value(gb_item)); |
---|
163 | } |
---|
164 | is_assert(0); // unsupported dimension |
---|
165 | return ShadedValue(); |
---|
166 | } |
---|
167 | }; |
---|
168 | |
---|
169 | |
---|
170 | typedef set<string> FieldSet; |
---|
171 | |
---|
172 | class ItemFieldShader: public ShaderPlugin { |
---|
173 | BoundItemSel itemtype; |
---|
174 | FieldSet hcbs_installed; // list of currently installed hierarchy callbacks |
---|
175 | |
---|
176 | RefPtr<AW_window> aw_config; |
---|
177 | |
---|
178 | mutable string item_dbpath; |
---|
179 | mutable SmartPtr<MultiFieldReader> reader; |
---|
180 | |
---|
181 | const char *get_fieldname(int dim) const { |
---|
182 | // returns configured fieldname (or NULp) |
---|
183 | AW_root *awr = AW_root::SINGLETON; |
---|
184 | if (awr && awr->awar(AWAR_DIM_ACTIVE(dim))->read_int()) { |
---|
185 | const char *fname = awr->awar(AWAR_FIELD(dim))->read_char_pntr(); |
---|
186 | if (strcmp(fname, NO_FIELD_SELECTED) != 0) { |
---|
187 | return fname; |
---|
188 | } |
---|
189 | } |
---|
190 | return NULp; |
---|
191 | } |
---|
192 | |
---|
193 | static bool is_ACI(const char *aci) { |
---|
194 | return aci[0]; |
---|
195 | } |
---|
196 | |
---|
197 | const char *get_ACI(int dim) const { |
---|
198 | // returns configured ACI (or NULp) |
---|
199 | AW_root *awr = AW_root::SINGLETON; |
---|
200 | if (awr && awr->awar(AWAR_DIM_ACTIVE(dim))->read_int()) { |
---|
201 | const char *aci = awr->awar(AWAR_ACI(dim))->read_char_pntr(); |
---|
202 | if (is_ACI(aci)) return aci; |
---|
203 | } |
---|
204 | return NULp; |
---|
205 | } |
---|
206 | |
---|
207 | FieldReader get_dimension_reader(int dim) const { |
---|
208 | AW_root *awr = AW_root::SINGLETON; |
---|
209 | if (awr) { |
---|
210 | const char *fieldname = get_fieldname(dim); |
---|
211 | if (fieldname) { |
---|
212 | float minVal = atof(awr->awar(AWAR_VALUE_MIN(dim))->read_char_pntr()); |
---|
213 | float maxVal = atof(awr->awar(AWAR_VALUE_MAX(dim))->read_char_pntr()); |
---|
214 | return FieldReader(fieldname, get_ACI(dim), minVal, maxVal); |
---|
215 | } |
---|
216 | } |
---|
217 | return FieldReader(); |
---|
218 | } |
---|
219 | |
---|
220 | MultiFieldReader *make_multi_field_reader() const { |
---|
221 | MultiFieldReader *multi = new MultiFieldReader; |
---|
222 | for (int dim = 0; dim<get_max_dimension(); ++dim) { |
---|
223 | multi->add_reader(get_dimension_reader(dim)); |
---|
224 | } |
---|
225 | return multi; |
---|
226 | } |
---|
227 | |
---|
228 | MultiFieldReader& get_field_reader() const { |
---|
229 | if (reader.isNull()) reader = make_multi_field_reader(); |
---|
230 | return *reader; |
---|
231 | } |
---|
232 | |
---|
233 | bool knows_item_dbpath() const { |
---|
234 | if (item_dbpath.empty()) { |
---|
235 | GBDATA *gb_item = itemtype.get_any_item(); |
---|
236 | if (gb_item) { |
---|
237 | const char *ipath = GB_get_db_path(gb_item); |
---|
238 | if (ipath) item_dbpath = string(ipath); |
---|
239 | } |
---|
240 | } |
---|
241 | return !item_dbpath.empty(); |
---|
242 | } |
---|
243 | |
---|
244 | void update_db_callbacks(const FieldSet& wanted) { |
---|
245 | if (knows_item_dbpath()) { |
---|
246 | DatabaseCallback dbcb = makeDatabaseCallback(ItemFieldShader::field_updated_in_DB_cb, this); |
---|
247 | |
---|
248 | GB_ERROR error = NULp; |
---|
249 | GBDATA *gb_main = itemtype.gb_main; |
---|
250 | |
---|
251 | GB_transaction ta(gb_main); |
---|
252 | |
---|
253 | // uninstall unwanted callbacks: |
---|
254 | for (FieldSet::const_iterator installed = hcbs_installed.begin(); installed != hcbs_installed.end(); ++installed) { |
---|
255 | if (wanted.find(*installed) == wanted.end()) { // 'installed' is not in 'wanted' |
---|
256 | string field_db_path = item_dbpath + '/' + *installed; |
---|
257 | error = GB_remove_hierarchy_callback(gb_main, field_db_path.c_str(), GB_CB_CHANGED_OR_DELETED, dbcb); |
---|
258 | } |
---|
259 | } |
---|
260 | // install missing callbacks: |
---|
261 | for (FieldSet::const_iterator missing = wanted.begin(); missing != wanted.end(); ++missing) { |
---|
262 | if (hcbs_installed.find(*missing) == hcbs_installed.end()) { // 'missing' is not in 'hcbs_installed' |
---|
263 | string field_db_path = item_dbpath + '/' + *missing; |
---|
264 | error = GB_add_hierarchy_callback(gb_main, field_db_path.c_str(), GB_CB_CHANGED_OR_DELETED, dbcb); |
---|
265 | } |
---|
266 | } |
---|
267 | hcbs_installed = wanted; // store current state |
---|
268 | aw_message_if(error); |
---|
269 | } |
---|
270 | } |
---|
271 | void add_used_fields(FieldSet& wanted) const { |
---|
272 | const char *fname0 = get_fieldname(0); |
---|
273 | if (fname0) wanted.insert(fname0); |
---|
274 | } |
---|
275 | void setup_db_callbacks(bool install) { |
---|
276 | FieldSet wanted; |
---|
277 | if (install) add_used_fields(wanted); // otherwise uninstall all |
---|
278 | update_db_callbacks(wanted); |
---|
279 | } |
---|
280 | static void field_updated_in_DB_cb(UNFIXED, ItemFieldShader *shader) { |
---|
281 | shader->trigger_reshade_if_active_cb(SIMPLE_RESHADE); |
---|
282 | } |
---|
283 | |
---|
284 | void init_config_definition(AWT_config_definition& cdef) const; |
---|
285 | |
---|
286 | public: |
---|
287 | explicit ItemFieldShader(const BoundItemSel& itemtype_) : |
---|
288 | ShaderPlugin("field", "Database field shader"), |
---|
289 | itemtype(itemtype_), |
---|
290 | aw_config(NULp) |
---|
291 | {} |
---|
292 | |
---|
293 | ShadedValue shade(GBDATA *gb_item) const OVERRIDE { |
---|
294 | return get_field_reader().calc_value(gb_item); |
---|
295 | } |
---|
296 | |
---|
297 | int get_dimension() const OVERRIDE { |
---|
298 | // returns (current) dimension of shader-plugin |
---|
299 | return get_field_reader().get_dimension(); |
---|
300 | } |
---|
301 | int get_max_dimension() const { |
---|
302 | return 3; |
---|
303 | } |
---|
304 | void init_specific_awars(AW_root *awr) OVERRIDE; |
---|
305 | |
---|
306 | bool customizable() const OVERRIDE { return true; } |
---|
307 | void customize(AW_root *awr) OVERRIDE; |
---|
308 | |
---|
309 | char *store_config() const OVERRIDE; |
---|
310 | void load_or_reset_config(const char *cfgstr) OVERRIDE; |
---|
311 | |
---|
312 | void activate(bool on) OVERRIDE { |
---|
313 | // called with true when plugin gets activated, with false when it gets deactivated |
---|
314 | setup_db_callbacks(on); |
---|
315 | } |
---|
316 | |
---|
317 | |
---|
318 | void setup_changed_cb() { |
---|
319 | setup_db_callbacks(true); // @@@ does this also happen if plugin is NOT ACTIVE? shouldn't! |
---|
320 | |
---|
321 | reader.setNull(); |
---|
322 | trigger_reshade_if_active_cb(CHECK_DIMENSION_CHANGE); |
---|
323 | } |
---|
324 | static void setup_changed_cb(AW_root*, ItemFieldShader *shader) { |
---|
325 | shader->setup_changed_cb(); |
---|
326 | } |
---|
327 | |
---|
328 | void scan_value_range_cb(int dim); |
---|
329 | static void scan_value_range_cb(AW_window*, ItemFieldShader *shader, int dim) { shader->scan_value_range_cb(dim); } |
---|
330 | |
---|
331 | }; |
---|
332 | |
---|
333 | void ItemFieldShader::init_specific_awars(AW_root *awr) { |
---|
334 | for (int dim = 0; dim<get_max_dimension(); ++dim) { |
---|
335 | RootCallback FieldSetup_changed_cb = makeRootCallback(ItemFieldShader::setup_changed_cb, this); |
---|
336 | |
---|
337 | awr->awar_int(AWAR_DIM_ACTIVE(dim), dim == 0) ->add_callback(FieldSetup_changed_cb); |
---|
338 | awr->awar_string(AWAR_FIELD(dim), NO_FIELD_SELECTED)->add_callback(FieldSetup_changed_cb); |
---|
339 | awr->awar_string(AWAR_ACI(dim), "") ->add_callback(FieldSetup_changed_cb); |
---|
340 | awr->awar_string(AWAR_VALUE_MIN(dim), "0") ->add_callback(FieldSetup_changed_cb); |
---|
341 | awr->awar_string(AWAR_VALUE_MAX(dim), "1") ->add_callback(FieldSetup_changed_cb); |
---|
342 | } |
---|
343 | } |
---|
344 | |
---|
345 | void ItemFieldShader::init_config_definition(AWT_config_definition& cdef) const { |
---|
346 | for (int dim = 0; dim<get_max_dimension(); ++dim) { |
---|
347 | cdef.add(AWAR_DIM_ACTIVE(dim), "active", dim); |
---|
348 | cdef.add(AWAR_FIELD (dim), "field", dim); |
---|
349 | cdef.add(AWAR_ACI (dim), "aci", dim); |
---|
350 | cdef.add(AWAR_VALUE_MIN (dim), "min", dim); |
---|
351 | cdef.add(AWAR_VALUE_MAX (dim), "max", dim); |
---|
352 | } |
---|
353 | } |
---|
354 | |
---|
355 | char *ItemFieldShader::store_config() const { |
---|
356 | AWT_config_definition cdef; |
---|
357 | init_config_definition(cdef); |
---|
358 | return cdef.read(); |
---|
359 | } |
---|
360 | |
---|
361 | void ItemFieldShader::load_or_reset_config(const char *cfgstr) { |
---|
362 | AWT_config_definition cdef; |
---|
363 | init_config_definition(cdef); |
---|
364 | |
---|
365 | if (cfgstr) cdef.write(cfgstr); |
---|
366 | else cdef.reset(); |
---|
367 | } |
---|
368 | |
---|
369 | void ItemFieldShader::customize(AW_root *awr) { |
---|
370 | if (!aw_config) { |
---|
371 | AW_window_simple *aws = new AW_window_simple; |
---|
372 | { |
---|
373 | string wid = GBS_global_string("%s_cfg", get_shader_local_id()); |
---|
374 | aws->init(awr, wid.c_str(), get_description().c_str()); |
---|
375 | } |
---|
376 | |
---|
377 | aws->auto_space(5, 5); |
---|
378 | aws->button_length(8); |
---|
379 | |
---|
380 | int y0 = aws->get_at_yposition(); |
---|
381 | |
---|
382 | aws->callback(AW_POPDOWN); |
---|
383 | aws->create_button("CLOSE", "CLOSE", "O"); |
---|
384 | |
---|
385 | aws->callback(makeHelpCallback("field_shader.hlp")); |
---|
386 | aws->create_button("HELP", "HELP"); |
---|
387 | |
---|
388 | aws->at_newline(); |
---|
389 | |
---|
390 | int y = aws->get_at_yposition(); // header-position |
---|
391 | const char *header[] = { "Use", "Field", "ACI", "min", "max", }; |
---|
392 | const int HCOUNT = ARRAY_ELEMS(header); |
---|
393 | |
---|
394 | int x[HCOUNT]; |
---|
395 | x[0] = aws->get_at_xposition(); |
---|
396 | |
---|
397 | aws->at_y(y+(y-y0)); |
---|
398 | |
---|
399 | for (int dim = 0; dim<get_max_dimension(); ++dim) { |
---|
400 | aws->create_toggle(AWAR_DIM_ACTIVE(dim)); |
---|
401 | |
---|
402 | int h = 1; |
---|
403 | if (!dim) x[h++] = aws->get_at_xposition(); |
---|
404 | FieldSelDef def(AWAR_FIELD(dim), itemtype.gb_main, itemtype.selector, FIELD_FILTER_STRING_READABLE); |
---|
405 | create_itemfield_selection_button(aws, def, NULp); |
---|
406 | |
---|
407 | if (!dim) x[h++] = aws->get_at_xposition(); |
---|
408 | aws->create_input_field(AWAR_ACI(dim), 30); |
---|
409 | |
---|
410 | const int VALCOL = 9; |
---|
411 | if (!dim) x[h++] = aws->get_at_xposition(); |
---|
412 | aws->create_input_field(AWAR_VALUE_MIN(dim), VALCOL); |
---|
413 | if (!dim) x[h++] = aws->get_at_xposition(); |
---|
414 | aws->create_input_field(AWAR_VALUE_MAX(dim), VALCOL); |
---|
415 | |
---|
416 | aws->callback(makeWindowCallback(scan_value_range_cb, this, dim)); |
---|
417 | aws->create_button(GBS_global_string("SCAN%i", dim), "SCAN"); |
---|
418 | |
---|
419 | aws->at_newline(); |
---|
420 | } |
---|
421 | |
---|
422 | for (int h = 0; h<HCOUNT; ++h) { |
---|
423 | aws->at(x[h], y); |
---|
424 | aws->create_button(NULp, header[h]); |
---|
425 | } |
---|
426 | |
---|
427 | aw_config = aws; |
---|
428 | } |
---|
429 | aw_config->activate(); |
---|
430 | } |
---|
431 | |
---|
432 | inline const char *make_limit_string(bool use_float, float f, int i) { |
---|
433 | if (use_float) { |
---|
434 | // cut off trailing '.0*': |
---|
435 | char *s = GBS_global_string_copy("%f", f); |
---|
436 | char *e = strchr(s, 0)-1; |
---|
437 | while (e>s) { |
---|
438 | char c = e[0]; |
---|
439 | if (c == '.') { |
---|
440 | e[0] = 0; |
---|
441 | break; |
---|
442 | } |
---|
443 | if (c != '0') break; |
---|
444 | *e-- = 0; |
---|
445 | } |
---|
446 | const char *cs = GBS_static_string(s); |
---|
447 | free(s); |
---|
448 | return cs; |
---|
449 | } |
---|
450 | return GBS_global_string("%i", i); |
---|
451 | } |
---|
452 | |
---|
453 | template<typename T> |
---|
454 | class LimitTracker { |
---|
455 | T min, max; |
---|
456 | |
---|
457 | public: |
---|
458 | LimitTracker() : |
---|
459 | min(numeric_limits<T>::max()), |
---|
460 | max(numeric_limits<T>::min()) |
---|
461 | {} |
---|
462 | |
---|
463 | void track(T val) { |
---|
464 | min = std::min(min, val); |
---|
465 | max = std::max(max, val); |
---|
466 | } |
---|
467 | void track(const char *str); |
---|
468 | |
---|
469 | bool seen() const { return min <= max; } |
---|
470 | bool is_single_value() const { return !(min<max); } |
---|
471 | |
---|
472 | T get_min() const { return min; } |
---|
473 | T get_max() const { return max; } |
---|
474 | |
---|
475 | }; |
---|
476 | |
---|
477 | template<> void LimitTracker<int> ::track(const char *str) { track(atoi(str)); } |
---|
478 | template<> void LimitTracker<float>::track(const char *str) { track(atof(str)); } |
---|
479 | |
---|
480 | |
---|
481 | void ItemFieldShader::scan_value_range_cb(int dim) { |
---|
482 | AW_root *awr = AW_root::SINGLETON; |
---|
483 | const char *fieldname = awr->awar(AWAR_FIELD(dim))->read_char_pntr(); |
---|
484 | const char *aci = awr->awar(AWAR_ACI(dim))->read_char_pntr(); |
---|
485 | bool have_aci = is_ACI(aci); |
---|
486 | GB_ERROR error = NULp; |
---|
487 | |
---|
488 | if (strcmp(fieldname, NO_FIELD_SELECTED) == 0) { |
---|
489 | error = "Select field to scan"; |
---|
490 | } |
---|
491 | else { |
---|
492 | LimitTracker<int> ilimit; |
---|
493 | LimitTracker<float> flimit; |
---|
494 | |
---|
495 | bool seen_field = false; |
---|
496 | bool is_hierarchical = strchr(fieldname, '/'); |
---|
497 | |
---|
498 | GB_transaction ta(itemtype.gb_main); |
---|
499 | for (GBDATA *gb_cont = itemtype.get_first_item_container(NULp, QUERY_ALL_ITEMS); |
---|
500 | gb_cont && !error; |
---|
501 | gb_cont = itemtype.get_next_item_container(gb_cont, QUERY_ALL_ITEMS)) |
---|
502 | { |
---|
503 | for (GBDATA *gb_item = itemtype.get_first_item(gb_cont, QUERY_ALL_ITEMS); |
---|
504 | gb_item && !error; |
---|
505 | gb_item = itemtype.get_next_item(gb_item, QUERY_ALL_ITEMS)) |
---|
506 | { |
---|
507 | GBDATA *gb_field = is_hierarchical |
---|
508 | ? GB_search(gb_item, fieldname, GB_FIND) |
---|
509 | : GB_entry(gb_item, fieldname); |
---|
510 | |
---|
511 | error = GB_incur_error_if(!gb_field); |
---|
512 | if (gb_field) { |
---|
513 | is_assert(!error); |
---|
514 | seen_field = true; |
---|
515 | |
---|
516 | if (have_aci) { |
---|
517 | char *content = GB_read_as_string(gb_field); |
---|
518 | char *applied = GB_command_interpreter_in_env(content, aci, GBL_simple_call_env(gb_item)); |
---|
519 | |
---|
520 | if (!applied) { |
---|
521 | error = GB_await_error(); |
---|
522 | } |
---|
523 | else { |
---|
524 | ilimit.track(applied); |
---|
525 | flimit.track(applied); |
---|
526 | free(applied); |
---|
527 | } |
---|
528 | free(content); |
---|
529 | } |
---|
530 | else { |
---|
531 | GB_TYPES field_type = GB_read_type(gb_field); |
---|
532 | switch (field_type) { |
---|
533 | case GB_INT: { |
---|
534 | int i = GB_read_int(gb_field); |
---|
535 | ilimit.track(i); |
---|
536 | break; |
---|
537 | } |
---|
538 | case GB_FLOAT: { |
---|
539 | float f = GB_read_float(gb_field); |
---|
540 | flimit.track(f); |
---|
541 | break; |
---|
542 | } |
---|
543 | default: { |
---|
544 | char *s = GB_read_as_string(gb_field); |
---|
545 | ilimit.track(s); |
---|
546 | flimit.track(s); |
---|
547 | free(s); |
---|
548 | break; |
---|
549 | } |
---|
550 | } |
---|
551 | } |
---|
552 | } |
---|
553 | } |
---|
554 | } |
---|
555 | |
---|
556 | if (seen_field) { |
---|
557 | // decide whether to use float or int limits |
---|
558 | bool seen_float = flimit.seen(); |
---|
559 | bool seen_int = ilimit.seen(); |
---|
560 | |
---|
561 | if (seen_float || seen_int) { |
---|
562 | bool use_float = seen_float && (!seen_int || ilimit.get_max()<flimit.get_max()); |
---|
563 | |
---|
564 | { |
---|
565 | DelayReshade of(shader_plugged_into()); // avoid duplicate refresh |
---|
566 | awr->awar(AWAR_VALUE_MIN(dim))->write_string(make_limit_string(use_float, flimit.get_min(), ilimit.get_min())); |
---|
567 | awr->awar(AWAR_VALUE_MAX(dim))->write_string(make_limit_string(use_float, flimit.get_max(), ilimit.get_max())); |
---|
568 | } |
---|
569 | |
---|
570 | bool shading_useless = use_float ? flimit.is_single_value() : ilimit.is_single_value(); |
---|
571 | if (shading_useless) { |
---|
572 | error = GBS_global_string("Using field '%s' for shading is quite useless", fieldname); |
---|
573 | } |
---|
574 | } |
---|
575 | else { |
---|
576 | error = "Failed to scan range (no value encountered)"; |
---|
577 | } |
---|
578 | } |
---|
579 | } |
---|
580 | aw_message_if(error); |
---|
581 | } |
---|
582 | |
---|
583 | // ------------------ |
---|
584 | // factory: |
---|
585 | |
---|
586 | ShaderPluginPtr makeItemFieldShader(BoundItemSel& itemtype) { |
---|
587 | return new ItemFieldShader(itemtype); |
---|
588 | } |
---|
589 | |
---|
590 | // -------------------------------------------------------------------------------- |
---|
591 | |
---|
592 | #ifdef UNIT_TESTS |
---|
593 | #ifndef TEST_UNIT_H |
---|
594 | #include <test_unit.h> |
---|
595 | #endif |
---|
596 | |
---|
597 | #define TEST_READER_READS(reader,species,expected) TEST_EXPECT_EQUAL(ValueTuple::make((reader).calc_value(species))->inspect(), expected) |
---|
598 | #define TEST_READER_UNDEF(reader,species) TEST_READER_READS(reader, species, "<undef>") |
---|
599 | #define TEST_MULTI_READS(reader,species,expected) TEST_EXPECT_EQUAL((reader).calc_value(species)->inspect(), expected) |
---|
600 | #define TEST_MULTI_UNDEF(reader,species) TEST_MULTI_READS(reader,species, "<undef>") |
---|
601 | |
---|
602 | void TEST_FieldReader() { |
---|
603 | GB_shell shell; |
---|
604 | GBDATA *gb_main = GB_open("nosuch.arb", "c"); |
---|
605 | TEST_REJECT_NULL(gb_main); |
---|
606 | |
---|
607 | GBDATA *gb_species, *gb_species2, *gb_species_outofbounds, *gb_species_no_field; |
---|
608 | |
---|
609 | const char *FIELD_FLOAT = "float"; |
---|
610 | const char *FIELD_INT = "int"; |
---|
611 | const char *FIELD_STRING = "string"; |
---|
612 | { |
---|
613 | GB_transaction ta(gb_main); |
---|
614 | |
---|
615 | gb_species = GBT_find_or_create_species(gb_main, "test", true); TEST_REJECT_NULL(gb_species); |
---|
616 | gb_species2 = GBT_find_or_create_species(gb_main, "other", true); TEST_REJECT_NULL(gb_species2); |
---|
617 | gb_species_no_field = GBT_find_or_create_species(gb_main, "empty", true); TEST_REJECT_NULL(gb_species_no_field); |
---|
618 | gb_species_outofbounds = GBT_find_or_create_species(gb_main, "outer", true); TEST_REJECT_NULL(gb_species_outofbounds); |
---|
619 | |
---|
620 | GBDATA *gb_field; |
---|
621 | gb_field = GB_searchOrCreate_float (gb_species, FIELD_FLOAT, 0.25); TEST_REJECT_NULL(gb_field); |
---|
622 | gb_field = GB_searchOrCreate_int (gb_species, FIELD_INT, 50); TEST_REJECT_NULL(gb_field); |
---|
623 | gb_field = GB_searchOrCreate_string(gb_species, FIELD_STRING, "200 units"); TEST_REJECT_NULL(gb_field); |
---|
624 | |
---|
625 | gb_field = GB_searchOrCreate_float (gb_species2, FIELD_FLOAT, 0.9); TEST_REJECT_NULL(gb_field); |
---|
626 | gb_field = GB_searchOrCreate_int (gb_species2, FIELD_INT, 99); TEST_REJECT_NULL(gb_field); |
---|
627 | gb_field = GB_searchOrCreate_string(gb_species2, FIELD_STRING, "175.9"); TEST_REJECT_NULL(gb_field); |
---|
628 | |
---|
629 | gb_field = GB_searchOrCreate_float (gb_species_outofbounds, FIELD_FLOAT, 1.5); TEST_REJECT_NULL(gb_field); |
---|
630 | gb_field = GB_searchOrCreate_int (gb_species_outofbounds, FIELD_INT, 9999); TEST_REJECT_NULL(gb_field); |
---|
631 | gb_field = GB_searchOrCreate_string(gb_species_outofbounds, FIELD_STRING, "-12345.678"); TEST_REJECT_NULL(gb_field); |
---|
632 | } |
---|
633 | |
---|
634 | FieldReader nullReader; |
---|
635 | FieldReader missingReader("missing", NULp, 0, 1); |
---|
636 | FieldReader floatReader (FIELD_FLOAT, NULp, 1, 0); // reverse value-range! |
---|
637 | FieldReader intReader (FIELD_INT, NULp, 0, 100); |
---|
638 | FieldReader stringReader (FIELD_STRING, NULp, 100, 250); |
---|
639 | |
---|
640 | FieldReader aciReader(FIELD_STRING, "|contains(unit)", 0, 1); |
---|
641 | |
---|
642 | TEST_REJECT(nullReader.may_read()); |
---|
643 | TEST_EXPECT(missingReader.may_read()); |
---|
644 | TEST_EXPECT(stringReader.may_read()); |
---|
645 | TEST_EXPECT(aciReader.may_read()); |
---|
646 | |
---|
647 | { |
---|
648 | GB_transaction ta(gb_main); |
---|
649 | |
---|
650 | // expect undef if no species - no matter what reader is used (eg. zombie in tree) |
---|
651 | TEST_READER_UNDEF(nullReader, NULp); |
---|
652 | TEST_READER_UNDEF(missingReader, NULp); |
---|
653 | TEST_READER_UNDEF(floatReader, NULp); |
---|
654 | TEST_READER_UNDEF(intReader, NULp); |
---|
655 | TEST_READER_UNDEF(stringReader, NULp); |
---|
656 | TEST_READER_UNDEF(aciReader, NULp); |
---|
657 | |
---|
658 | TEST_READER_UNDEF(nullReader, gb_species); // null reader always undef |
---|
659 | TEST_READER_UNDEF(missingReader, gb_species); // expect undef if field is missing |
---|
660 | |
---|
661 | TEST_READER_READS(floatReader, gb_species, "(0.750)"); |
---|
662 | TEST_READER_READS(intReader, gb_species, "(0.500)"); |
---|
663 | TEST_READER_READS(stringReader, gb_species, "(0.667)"); |
---|
664 | TEST_READER_READS(aciReader, gb_species, "(5.000)"); // = position |
---|
665 | |
---|
666 | TEST_READER_READS(floatReader, gb_species2, "(0.100)"); |
---|
667 | TEST_READER_READS(intReader, gb_species2, "(0.990)"); |
---|
668 | TEST_READER_READS(stringReader, gb_species2, "(0.506)"); // 175 would be mid-range, 175.9 is a little bit above |
---|
669 | TEST_READER_READS(aciReader, gb_species2, "(0.000)"); |
---|
670 | |
---|
671 | // if values are outside of value-range -> they are scaled to range-size, but not bounded: |
---|
672 | TEST_READER_READS(floatReader, gb_species_outofbounds, "(-0.500)"); |
---|
673 | TEST_READER_READS(intReader, gb_species_outofbounds, "(99.990)"); |
---|
674 | TEST_READER_READS(stringReader, gb_species_outofbounds, "(-82.971)"); |
---|
675 | TEST_READER_READS(aciReader, gb_species_outofbounds, "(0.000)"); |
---|
676 | |
---|
677 | TEST_READER_UNDEF(floatReader, gb_species_no_field); // species is missing all fields -> always undef |
---|
678 | TEST_READER_UNDEF(intReader, gb_species_no_field); |
---|
679 | TEST_READER_UNDEF(stringReader, gb_species_no_field); |
---|
680 | TEST_READER_UNDEF(aciReader, gb_species_no_field); |
---|
681 | } |
---|
682 | |
---|
683 | MultiFieldReader multi; TEST_EXPECT_EQUAL(multi.get_dimension(), 0); |
---|
684 | multi.add_reader(nullReader); TEST_EXPECT_EQUAL(multi.get_dimension(), 0); |
---|
685 | multi.add_reader(floatReader); TEST_EXPECT_EQUAL(multi.get_dimension(), 1); |
---|
686 | |
---|
687 | { |
---|
688 | GB_transaction ta(gb_main); |
---|
689 | |
---|
690 | // only floatReader added yet -> should behave like floatReader did above: |
---|
691 | TEST_MULTI_READS(multi, gb_species, "(0.750)"); |
---|
692 | TEST_MULTI_READS(multi, gb_species2, "(0.100)"); |
---|
693 | TEST_MULTI_READS(multi, gb_species_outofbounds, "(-0.500)"); |
---|
694 | TEST_MULTI_UNDEF(multi, gb_species_no_field); |
---|
695 | } |
---|
696 | |
---|
697 | GB_close(gb_main); |
---|
698 | } |
---|
699 | |
---|
700 | #endif // UNIT_TESTS |
---|
701 | |
---|
702 | // -------------------------------------------------------------------------------- |
---|