ARB
field_shader.cxx
Go to the documentation of this file.
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 {
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) {
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 
136  FieldReader reader[3];
137  int dim;
138 
139 public:
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 
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)
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)
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 {
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) {
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 
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 
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 
357  init_config_definition(cdef);
358  return cdef.read();
359 }
360 
361 void ItemFieldShader::load_or_reset_config(const char *cfgstr) {
363  init_config_definition(cdef);
364 
365  if (cfgstr) cdef.write(cfgstr);
366  else cdef.reset();
367 }
368 
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);
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>
455  T min, max;
456 
457 public:
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 
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 
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 
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 // --------------------------------------------------------------------------------
GBDATA * GB_open(const char *path, const char *opent)
Definition: ad_load.cxx:1363
bool is_single_value() const
void load_or_reset_config(const char *cfgstr) OVERRIDE
ShaderPluginPtr makeItemFieldShader(BoundItemSel &itemtype)
void add(const char *awar_name, const char *config_name)
long GB_read_int(GBDATA *gbd)
Definition: arbdb.cxx:699
GB_ERROR GB_add_hierarchy_callback(GBDATA *gb_main, const char *db_path, GB_CB_TYPE type, const DatabaseCallback &dbcb)
Definition: ad_cb.cxx:432
static char * y[maxsp+1]
return string(buffer, length)
void scan_value_range_cb(int dim)
GBDATA * GB_searchOrCreate_string(GBDATA *gb_container, const char *fieldpath, const char *default_value)
Definition: adquery.cxx:546
static ShadedValue make(float f)
Definition: ValueTuple.cxx:510
GBDATA * get_any_item() const
Definition: items.cxx:14
#define AWAR_FIELD(dim)
const char * make_limit_string(bool use_float, float f, int i)
ValueTuple::ShadedValue ShadedValue
Definition: item_shader.h:172
char * GB_read_as_string(GBDATA *gbd)
Definition: arbdb.cxx:1030
#define AWAR_VALUE_MIN(dim)
long read_int() const
Definition: AW_awar.cxx:187
float calc_value(GBDATA *gb_item) const
const char * GBS_global_string(const char *templat,...)
Definition: arb_msg.cxx:204
STL namespace.
void AW_POPDOWN(AW_window *window)
Definition: AW_window.cxx:52
bool isNull() const
test if SmartPtr is NULp
Definition: smartptr.h:248
FieldReader(const char *fieldname_, const char *aci_, float minVal, float maxVal)
T get_max() const
#define ARRAY_ELEMS(array)
Definition: arb_defs.h:19
void setNull()
set SmartPtr to NULp
Definition: smartptr.h:251
#define NO_FIELD_SELECTED
GB_ERROR GB_incur_error_if(bool error_may_occur)
Definition: arb_msg.h:56
AW_awar * add_callback(const RootCallback &cb)
Definition: AW_awar.cxx:234
struct Unfixed_cb_parameter * UNFIXED
Definition: cb_base.h:15
void create_itemfield_selection_button(AW_window *aws, const FieldSelDef &selDef, const char *at)
void init_specific_awars(AW_root *awr) OVERRIDE
const char * read_char_pntr() const
Definition: AW_awar.cxx:171
GB_ERROR GB_await_error()
Definition: arb_msg.cxx:353
static AW_root * SINGLETON
Definition: aw_root.hxx:102
bool may_read() const
WindowCallback makeHelpCallback(const char *helpfile)
Definition: aw_window.hxx:106
#define TEST_EXPECT(cond)
Definition: test_unit.h:1312
Definition: arbdb.h:67
ShadedValue shade(GBDATA *gb_item) const OVERRIDE
GB_TYPES GB_read_type(GBDATA *gbd)
Definition: arbdb.cxx:1617
bool seen() const
CONSTEXPR_INLINE bool is_inf(const T &n)
Definition: arbtools.h:177
void track(T val)
Generic smart pointer.
Definition: smartptr.h:149
#define AWAR_VALUE_MAX(dim)
char * store_config() const OVERRIDE
static void scan_value_range_cb(AW_window *, ItemFieldShader *shader, int dim)
T get_min() const
#define TEST_REJECT(cond)
Definition: test_unit.h:1314
#define TEST_REJECT_NULL(n)
Definition: test_unit.h:1309
static void error(const char *msg)
Definition: mkptypes.cxx:96
int get_dimension() const
GBDATA * GB_searchOrCreate_int(GBDATA *gb_container, const char *fieldpath, long default_value)
Definition: adquery.cxx:569
float GB_read_float(GBDATA *gbd)
Definition: arbdb.cxx:714
int get_dimension() const OVERRIDE
static void setup_changed_cb(AW_root *, ItemFieldShader *shader)
AW_awar * awar(const char *awar)
Definition: AW_root.cxx:554
Definition: arbdb.h:86
#define AWAR_DIM_ACTIVE(dim)
AW_awar * awar_int(const char *var_name, long default_value=0, AW_default default_file=AW_ROOT_DEFAULT)
Definition: AW_root.cxx:580
GB_ERROR GB_remove_hierarchy_callback(GBDATA *gb_main, const char *db_path, GB_CB_TYPE type, const DatabaseCallback &dbcb)
Definition: ad_cb.cxx:440
#define AWAR_ACI(dim)
void add_reader(const FieldReader &added)
bool customizable() const OVERRIDE
GBDATA * GB_searchOrCreate_float(GBDATA *gb_container, const char *fieldpath, float default_value)
Definition: adquery.cxx:591
#define OVERRIDE
Definition: cxxforward.h:93
void customize(AW_root *awr) OVERRIDE
static ShadedValue undefined()
Definition: ValueTuple.cxx:507
ItemFieldShader(const BoundItemSel &itemtype_)
GBDATA * gb_main
Definition: items.h:85
const char * GBS_static_string(const char *str)
Definition: arb_msg.cxx:213
void trigger_reshade_if_active_cb(ReshadeMode mode)
Definition: item_shader.h:361
const char * GB_get_db_path(GBDATA *gbd)
Definition: adTest.cxx:14
void aw_message(const char *msg)
Definition: AW_status.cxx:932
GBDATA * GBT_find_or_create_species(GBDATA *gb_main, const char *name, bool markCreated)
Definition: aditem.cxx:61
#define NULp
Definition: cxxforward.h:97
void track(const char *str)
GB_ERROR write_string(const char *aw_string)
NOT4PERL char * GB_command_interpreter_in_env(const char *str, const char *commands, const GBL_call_env &callEnv)
Definition: gb_aci.cxx:361
GB_TYPES
Definition: arbdb.h:62
Definition: trnsprob.h:20
GB_transaction ta(gb_var)
GBDATA * gb_main
Definition: adname.cxx:33
AW_awar * awar_string(const char *var_name, const char *default_value="", AW_default default_file=AW_ROOT_DEFAULT)
Definition: AW_root.cxx:570
GBDATA * GB_search(GBDATA *gbd, const char *fieldpath, GB_TYPES create)
Definition: adquery.cxx:531
ShadedValue calc_value(GBDATA *gb_item) const
int get_max_dimension() const
#define min(a, b)
Definition: f2c.h:153
set< string > FieldSet
#define TEST_EXPECT_EQUAL(expr, want)
Definition: test_unit.h:1283
GBDATA * GB_entry(GBDATA *father, const char *key)
Definition: adquery.cxx:334
void aw_message_if(GB_ERROR error)
Definition: aw_msg.hxx:21
CONSTEXPR long FIELD_FILTER_STRING_READABLE
Definition: item_sel_list.h:47
char * GBS_global_string_copy(const char *templat,...)
Definition: arb_msg.cxx:195
void GB_close(GBDATA *gbd)
Definition: arbdb.cxx:625
void track(const char *str)
void write(const char *cfgStr) const
Definition: arbdb.h:66
#define is_assert(cond)
Definition: item_shader.cxx:14
#define max(a, b)
Definition: f2c.h:154
GB_write_int const char s
Definition: AW_awar.cxx:156