source: branches/ali/SL/ITEM_SHADER/field_shader.cxx

Last change on this file was 18730, checked in by westram, 3 years ago
  • remove trailing whitespace from c source.
File size: 25.6 KB
Line 
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
29using 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
37class 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    }
61public:
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
135class MultiFieldReader {
136    FieldReader reader[3];
137    int         dim;
138
139public:
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
170typedef set<string> FieldSet;
171
172class 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
286public:
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
333void 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
345void 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
355char *ItemFieldShader::store_config() const {
356    AWT_config_definition cdef;
357    init_config_definition(cdef);
358    return cdef.read();
359}
360
361void 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
369void 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
432inline 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
453template<typename T>
454class LimitTracker {
455    T min, max;
456
457public:
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
477template<> void LimitTracker<int>  ::track(const char *str) { track(atoi(str)); }
478template<> void LimitTracker<float>::track(const char *str) { track(atof(str)); }
479
480
481void 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
586ShaderPluginPtr 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
602void 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// --------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.