source: branches/ali/SL/ITEMS/item_sel_list.cxx

Last change on this file was 18979, checked in by westram, 3 years ago
  • fix use of empty labels:
    • AW_window::label no longer
      • accepts empty labels.
      • allows to overwrite existing labels (assume this happens by mistake).
    • create_toggle_field:
      • assert label is used (in flavor with label-parameter)
      • remove empty and NULp labels passed (from callers).
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 29.4 KB
Line 
1// ==================================================================== //
2//                                                                      //
3//   File      : item_sel_list.cxx                                      //
4//   Purpose   : selection lists for items (ItemSelector)               //
5//                                                                      //
6//                                                                      //
7// Coded by Ralf Westram (coder@reallysoft.de) in May 2005              //
8// Copyright Department of Microbiology (Technical University Munich)   //
9//                                                                      //
10// Visit our web site at: http://www.arb-home.de/                       //
11//                                                                      //
12// ==================================================================== //
13
14#include "item_sel_list.h"
15
16#include <arbdbt.h>
17#include <ad_cb.h>
18
19#include <arb_defs.h>
20#include <arb_global_defs.h>
21
22#include <aw_root.hxx>
23#include <aw_awar.hxx>
24#include <aw_msg.hxx>
25#include <awt_sel_boxes.hxx>
26
27#include <map>
28
29using std::string;
30using std::map;
31
32Itemfield_Selection::Itemfield_Selection(AW_selection_list *sellist_,
33                                         GBDATA            *gb_key_data,
34                                         long               type_filter_,
35                                         SelectableFields   field_filter_,
36                                         ItemSelector&      selector_) :
37    AW_DB_selection(sellist_, gb_key_data),
38    type_filter(type_filter_),
39    field_filter(field_filter_),
40    selector(selector_)
41{
42    it_assert(&selector);
43}
44
45void Itemfield_Selection::fill() {
46    if (field_filter & SF_PSEUDO) {
47        insert_plain(PSEUDO_FIELD_ANY_FIELD);
48        insert_plain(PSEUDO_FIELD_ALL_FIELDS);
49        insert_plain(PSEUDO_FIELD_ANY_FIELD_REC);
50        insert_plain(PSEUDO_FIELD_ALL_FIELDS_REC);
51    }
52
53    GBDATA         *gb_key_data = get_gbd();
54    GB_transaction  ta(gb_key_data);
55
56    for (GBDATA *gb_key = GB_entry(gb_key_data, CHANGEKEY); gb_key; gb_key = GB_nextEntry(gb_key)) {
57        GBDATA   *gb_key_type = GB_entry(gb_key, CHANGEKEY_TYPE);
58        GB_TYPES  key_type    = GB_TYPES(GB_read_int(gb_key_type));
59
60        if (shall_display_type(key_type)) {
61            GBDATA *gb_key_name = GB_entry(gb_key, CHANGEKEY_NAME);
62
63            if (gb_key_name) {
64                const char *name = GB_read_char_pntr(gb_key_name);
65                if (!name) {
66                    fprintf(stderr, "WARNING: can't read key name (Reason: %s)", GB_await_error());
67                    name = "<unnamedKey?>";
68                }
69
70                long       *hiddenPtr = GBT_read_int(gb_key, CHANGEKEY_HIDDEN);
71                const char *display   = NULp;
72
73                if (!hiddenPtr) {               // it's an older db version w/o hidden flag -> add it
74                    GB_ERROR error = GBT_write_int(gb_key, CHANGEKEY_HIDDEN, 0); // default is "not hidden"
75                    if (error) GB_warningf("WARNING: can't create " CHANGEKEY_HIDDEN " (Reason: %s)\n", error);
76
77                    static long not_hidden = 0;
78                    hiddenPtr              = &not_hidden;
79                }
80
81                if (*hiddenPtr) {               // hidden ?
82                    if (field_filter & SF_HIDDEN) { // show hidden fields ?
83                        display = GBS_global_string("[hidden] %s", name);
84                    }
85                }
86                else display = name;
87
88
89                if (display) {
90                    if (field_filter & SF_SHOW_TYPE) { // prefix type-char
91                        display = GBS_global_string("%c: %s", GB_type_2_char(key_type), display);
92                    }
93                    insert(display, name);
94                }
95            }
96        }
97    }
98
99    if (!get_sellist()->get_default_value()) {
100        insert_default((field_filter & SF_ALLOW_NEW) ? "<no or new field>" : "<no field>", NO_FIELD_SELECTED);
101    }
102}
103
104Itemfield_Selection *FieldSelDef::build_sel(AW_selection_list *from_sellist) const {
105    GBDATA *gb_key_data;
106    {
107        GB_transaction ta(gb_main);
108        gb_key_data = GB_search(gb_main, selector->change_key_path, GB_CREATE_CONTAINER);
109    }
110    return new Itemfield_Selection(from_sellist, gb_key_data, type_filter, field_filter, *selector);
111}
112
113const int FIELDNAME_VISIBLE_CHARS = 20;
114const int NEWFIELD_EXTRA_SPACE    = 13; // max: " (new STRING)"
115
116static struct {
117    GB_TYPES    type;
118    const char *label;
119    const char *mnemonic;
120    const char *typeName;
121} creatable[] = {
122    { GB_INT,    "Rounded numerical", "i", "INT" },
123    { GB_FLOAT,  "Floating-point n.", "F", "FLOAT" },
124    { GB_STRING, "Ascii text",        "s", "STRING" }
125    // keep in sync with ../DB_UI/ui_species.cxx@FIELD_TYPE_DESCRIPTIONS
126};
127
128class RegFieldSelection;
129
130typedef map<string, RegFieldSelection> FieldSelectionRegistry;
131
132class RegFieldSelection {
133    FieldSelDef              def;
134    RefPtr<AW_window_simple> aw_popup;
135
136    static FieldSelectionRegistry registry;
137    static MutableItemSelector    NULL_selector;
138
139public:
140    bool inAwarChange;
141
142    RegFieldSelection() :
143        def("dummy", NULp, NULL_selector, 0),
144        aw_popup(NULp)
145    {}
146    RegFieldSelection(const FieldSelDef& def_) :
147        def(def_),
148        aw_popup(NULp),
149        inAwarChange(false)
150    {}
151
152    const FieldSelDef& get_def() const { return def; }
153
154    bool new_fields_allowed() const { return def.new_fields_allowed(); }
155
156    const char *get_field_awarname()  const { return def.get_awarname().c_str(); }
157    const char *get_button_awarname() const { return GBS_global_string("tmp/%s/button", get_field_awarname()); }
158    const char *get_type_awarname()   const { return GBS_global_string("%s_type",   get_field_awarname()); }
159
160    void popup_window(AW_root *awr);
161    void popdown() { if (aw_popup) aw_popup->hide(); }
162    void create_window(AW_root *awr);
163    void init_awars(AW_root *awr);
164
165    GB_TYPES get_keytype(const char *fieldName) const {
166        /*! detect type of (defined) key
167         * @param fieldName name of field
168         * @return GB_NONE for undefined key or type of defined key
169        */
170        GB_TYPES        definedType = GB_NONE;
171        GBDATA         *gb_main     = def.get_gb_main();
172        GB_transaction  ta(gb_main);
173        GBDATA         *gb_key      = GBT_get_changekey(gb_main, fieldName, def.get_itemtype().change_key_path);
174
175        if (gb_key) {
176            long *elem_type = GBT_read_int(gb_key, CHANGEKEY_TYPE);
177            if (elem_type) definedType = GB_TYPES(*elem_type);
178        }
179        return definedType;
180    }
181
182    GB_TYPES get_selected_type(AW_root *awr) {
183        return GB_TYPES(awr->awar(get_type_awarname())->read_int());
184    }
185    GB_TYPES get_unmated_type() {
186        // if exactly ONE type is allowed -> return that type
187        // return GB_NONE otherwise
188        long allowed = def.get_type_filter();
189
190        for (unsigned i = 0; i<ARRAY_ELEMS(creatable); ++i) {
191            long typeFlag = 1<<creatable[i].type;
192            if (allowed & typeFlag) {
193                if (allowed == typeFlag) return creatable[i].type; // exactly one type allowed
194                break; // multiple types allowed
195            }
196        }
197        return GB_NONE;
198    }
199
200    void update_button_text(AW_root *awr) const;
201
202    // -----------------
203    // static interface:
204    static RegFieldSelection& registrate(AW_root *awr, const FieldSelDef& def);
205    static RegFieldSelection *find(const string& awar_name) {
206        FieldSelectionRegistry::iterator found = registry.find(awar_name);
207        return found == registry.end() ? NULp : &found->second;
208    }
209    static void update_buttons();
210};
211
212FieldSelectionRegistry RegFieldSelection::registry;
213MutableItemSelector    RegFieldSelection::NULL_selector;
214
215const char *get_itemfield_type_awarname(const char *itemfield_awarname) {
216    /*! returns the corresponding type-awar for an itemfield_awarname.
217     * Only returns an awarname if
218     * - itemfield_awarname was used in create_itemfield_selection_button and
219     * - new fields are allowed there
220     * returns NULp otherwise.
221     */
222
223    RegFieldSelection *registered = RegFieldSelection::find(itemfield_awarname);
224    if (registered && registered->new_fields_allowed()) {
225        return registered->get_type_awarname();
226    }
227    return NULp;
228}
229
230const char *prepare_and_get_selected_itemfield(AW_root *awr, const char *awar_name, GBDATA *gb_main, const ItemSelector& itemtype, FailIfField failIf) {
231    /*! Reads awar used in create_itemfield_selection_button().
232     *
233     * If the user selected to create a new itemfield, the changekey is created.
234     *
235     * @param awr             app root
236     * @param awar_name       name of awar used for create_itemfield_selection_button()
237     * @param gb_main         database
238     * @param itemtype        item type
239     * @param failIf          toggles various error conditions (defaults to FIF_STANDARD)
240     *
241     * @return name of the itemfield or NULp
242     *
243     * When NULp is returned
244     * - an error IS ALWAYS exported if (failIf&FIF_NO_FIELD_SELECTED), which is included in FIF_STANDARD!
245     * - an error MAY BE    exported otherwise (use GB_have_error() in that case)
246     */
247
248    it_assert(!GB_have_error());
249
250    RegFieldSelection *selected = RegFieldSelection::find(awar_name);
251    GB_ERROR           error    = NULp;
252    const char        *value    = NULp;
253
254    if (!selected) {
255        error = GBS_global_string("Awar '%s' is not registered as field selection", awar_name);
256    }
257    else {
258        AW_awar    *awar_field  = awr->awar(selected->get_field_awarname());
259        const char *field       = awar_field->read_char_pntr();
260        const char *kindOfField = selected->get_def().get_described_field().c_str();
261
262        if (!field[0]) field = NO_FIELD_SELECTED;
263
264        if (strcmp(field, NO_FIELD_SELECTED) == 0) {
265            if (failIf & FIF_NO_FIELD_SELECTED) {
266                error = GBS_global_string("Please select a %s", kindOfField);
267            }
268        }
269        else if (strcmp(field, "name") == 0 && (failIf & FIF_NAME_SELECTED)) {
270            error = GBS_global_string("You may not select 'name' as %s.", kindOfField); // protect user from overwriting the species ID
271        }
272        else { // an allowed fieldname is selected
273            GB_TYPES type = selected->get_keytype(field);
274
275            if (type == GB_NONE) { // missing field
276                if (selected->new_fields_allowed()) { // allowed to create?
277                    error = GB_check_hkey(field);
278                    if (!error) {
279                        GB_TYPES wantedType = selected->get_selected_type(awr);
280                        if (wantedType == GB_NONE) {
281                            error = GBS_global_string("Please select the datatype for the new %s '%s'", kindOfField, field);
282                        }
283                        else {
284                            error = GBT_add_new_changekey_to_keypath(gb_main, field, wantedType, itemtype.change_key_path);
285                        }
286                    }
287                }
288                else {
289                    error = GBS_global_string("Selected %s '%s' is not defined (logical error)", kindOfField, field); // should not occur!
290                }
291            }
292            else { // existing field
293                bool typeAllowed = selected->get_def().get_type_filter() & (1<<type);
294                if (!typeAllowed) {
295                    // There are two known ways this situation can be reached:
296                    // 1. select a new key; create key via search tool using a type not allowed here
297                    // 2. specify a key with disallowed type as new key name
298                    error = GBS_global_string("Selected field '%s' has unwanted type (%i)\nPlease select the %s again.", field, int(type), kindOfField);
299                }
300            }
301            if (!error) value = field;
302        }
303    }
304
305    if (error) {
306        GB_export_error(error);
307        value = NULp;
308    }
309    return value;
310}
311
312bool FieldSelDef::matches4reuse(const FieldSelDef& other) const {
313    return
314        (awar_name == other.awar_name)     && // shall use same awar,
315        (&selector == &other.selector)     && // identical itemtype,
316        (gb_main == other.gb_main)         && // same database,
317        (type_filter == other.type_filter) && // same type-filter and
318        (field_filter == other.field_filter); // same field-filter.
319}
320
321void RegFieldSelection::update_button_text(AW_root *awr) const {
322    it_assert(new_fields_allowed());
323
324    AW_awar    *awar_button = awr->awar(get_button_awarname());
325    const char *fieldName   = awr->awar(get_field_awarname())->read_char_pntr();
326
327    if (strcmp(fieldName, NO_FIELD_SELECTED) == 0 || !fieldName[0]) {
328        awar_button->write_string("<no field>");
329    }
330    else {
331        GB_TYPES type   = get_keytype(fieldName);
332        bool     exists = type != GB_NONE;
333
334        if (!exists) {
335            type = GB_TYPES(awr->awar(get_type_awarname())->read_int());
336        }
337
338        const char *typeName = NULp;
339        for (unsigned i = 0; i<ARRAY_ELEMS(creatable) && !typeName; ++i) {
340            if (creatable[i].type == type) typeName = creatable[i].typeName;
341        }
342
343        if (typeName) {
344            awar_button->write_string(GBS_global_string(exists ? "%s (%s)" : "%s (new %s)", fieldName, typeName));
345        }
346        else {
347            awar_button->write_string(GBS_global_string("<undefined> '%s'", fieldName));
348        }
349    }
350}
351
352static void fieldname_changed_cb(AW_root *awr, RegFieldSelection *fsel) {
353    if (!fsel->inAwarChange) {
354        LocallyModify<bool> avoidRecursion(fsel->inAwarChange, true);
355
356        AW_awar  *awar_type = awr->awar(fsel->get_type_awarname());
357        AW_awar  *awar_name = awr->awar(fsel->get_field_awarname());
358        GB_TYPES  defined   = fsel->get_keytype(awar_name->read_char_pntr());
359
360        if (defined) {
361            awar_type->write_int(defined);
362            fsel->popdown();
363        }
364        else {
365            // if allowed type of field is determined -> set it
366            // otherwise set type to 'undefined'
367            GB_TYPES determined = fsel->get_unmated_type();
368            awar_type->write_int(determined);
369            if (determined != GB_NONE) fsel->popdown();
370        }
371
372        fsel->update_button_text(awr);
373    }
374}
375
376static bool fieldtype_change_warning = true;
377static void fieldtype_changed_cb(AW_root *awr, RegFieldSelection *fsel) {
378    if (!fsel->inAwarChange) {
379        LocallyModify<bool> avoidRecursion(fsel->inAwarChange, true);
380
381        AW_awar  *awar_name = awr->awar(fsel->get_field_awarname());
382        GB_TYPES  defined   = fsel->get_keytype(awar_name->read_char_pntr());
383
384        if (defined != GB_NONE) {
385            AW_awar  *awar_type = awr->awar(fsel->get_type_awarname());
386            GB_TYPES  selected  = GB_TYPES(awar_type->read_int());
387
388            if (selected != defined) {
389                awar_type->write_int(defined);
390                if (fieldtype_change_warning) {
391                    aw_message("You cannot change the type of an existing field");
392                }
393            }
394        }
395
396        fsel->update_button_text(awr);
397    }
398}
399
400void RegFieldSelection::init_awars(AW_root *awr) {
401    if (new_fields_allowed()) {
402        // extra awars needed for popup-style + callbacks
403        awr->awar_string(get_button_awarname(), "?", AW_ROOT_DEFAULT);
404        AW_awar *awar_type = awr->awar_int(get_type_awarname(), GB_NONE, AW_ROOT_DEFAULT);
405        AW_awar *awar_name = awr->awar(get_field_awarname());
406
407        awar_type->add_callback(makeRootCallback(fieldtype_changed_cb, this));
408        awar_name->add_callback(makeRootCallback(fieldname_changed_cb, this));
409
410        LocallyModify<bool> dontWarn(fieldtype_change_warning, false);
411        awar_type->touch(); // refresh button-text (do NOT use awar_name to do that!)
412    }
413}
414
415void RegFieldSelection::update_buttons() {
416    for (FieldSelectionRegistry::const_iterator reg = registry.begin(); reg != registry.end(); ++reg) {
417        const RegFieldSelection& fsel = reg->second;
418        if (fsel.new_fields_allowed()) {
419            fsel.update_button_text(AW_root::SINGLETON);
420        }
421    }
422}
423
424RegFieldSelection& RegFieldSelection::registrate(AW_root *awr, const FieldSelDef& def) {
425    const string&      field_awarname = def.get_awarname();
426    RegFieldSelection *found          = find(field_awarname);
427
428    if (found) {
429        bool compatible = !found->get_def().matches4reuse(def);
430        if (!compatible) {
431            aw_message(GBS_global_string("Incompatible field-selections defined for awar '%s'", field_awarname.c_str()));
432
433            it_assert(0);
434            // to use multiple field selections on the same awar, you need to use similar parameters!
435            // (otherwise the user might outsmart the defined restrictions by using the other field-selector)
436        }
437    }
438    else {
439        registry[field_awarname] = RegFieldSelection(def);
440
441        found = find(field_awarname);
442        found->init_awars(awr);
443
444        GBDATA *gb_main = def.get_gb_main();
445        GB_transaction ta(gb_main);
446        GBDATA *gb_key_data = GB_search(gb_main, def.get_itemtype().change_key_path, GB_FIND);
447        if (gb_key_data) {
448            aw_message_if(GB_ensure_callback(gb_key_data, GB_CB_CHANGED, makeDatabaseCallback(RegFieldSelection::update_buttons)));
449        }
450    }
451    it_assert(found);
452    return *found;
453}
454
455void RegFieldSelection::create_window(AW_root *awr) {
456    it_assert(!aw_popup);
457
458    aw_popup = new AW_window_simple;
459
460    const bool allowNewFields = new_fields_allowed();
461
462    {
463        const char *format = allowNewFields ? "Select or create a new %s" : "Select the %s";
464        const char *title  = GBS_global_string(format, def.get_described_field().c_str());
465        aw_popup->init(awr, "SELECT_FIELD", title);
466    }
467    if (allowNewFields) aw_popup->load_xfig("awt/field_sel_new.fig"); // Do not DRY (ressource checker!)
468    else                aw_popup->load_xfig("awt/field_sel.fig");
469
470    aw_popup->at("sel");
471    Itemfield_Selection *itemSel;
472    {
473        AW_selection_list   *sellist = aw_popup->create_selection_list(get_field_awarname(), 1, 1);
474        itemSel = def.build_sel(sellist);
475        itemSel->refresh();
476    }
477
478    aw_popup->at("close");
479    aw_popup->callback(AW_POPDOWN);
480    aw_popup->create_button("CLOSE", "CLOSE", "C");
481
482    AW_awar *awar_field = awr->awar(get_field_awarname()); // user-awar
483    if (allowNewFields) {
484        aw_popup->at("help");
485        aw_popup->callback(makeHelpCallback("field_sel_new.hlp"));
486        aw_popup->create_button("HELP", "HELP", "H");
487
488        long allowedTypes  = def.get_type_filter();
489        int  possibleTypes = 0;
490        int  firstType     = -1;
491        for (unsigned i = 0; i<ARRAY_ELEMS(creatable); ++i) {
492            if (allowedTypes & (1<<creatable[i].type)) {
493                possibleTypes++;
494                if (firstType == -1) firstType = creatable[i].type;
495            }
496        }
497        it_assert(possibleTypes>0);
498
499        aw_popup->at("name");
500        aw_popup->create_input_field(get_field_awarname(), FIELDNAME_VISIBLE_CHARS);
501
502        // show type selector even if only one type selectable (layout- and informative-purposes)
503        aw_popup->at("type");
504        aw_popup->create_toggle_field(get_type_awarname());
505        for (unsigned i = 0; i<ARRAY_ELEMS(creatable); ++i) {
506            if (allowedTypes & (1<<creatable[i].type)) {
507                aw_popup->insert_toggle(creatable[i].label, creatable[i].mnemonic, int(creatable[i].type));
508            }
509        }
510        if (get_unmated_type() == GB_NONE) { // type may vary -> allowed it to be undefined
511            aw_popup->insert_toggle("<undefined>", "", GB_NONE);
512        }
513        aw_popup->update_toggle_field();
514    }
515    else {
516        awar_field->add_callback(makeRootCallback(awt_auto_popdown_cb, &*aw_popup));
517    }
518}
519
520void RegFieldSelection::popup_window(AW_root *awr) {
521    if (!aw_popup) create_window(awr);
522
523    it_assert(aw_popup);
524    aw_popup->recalc_pos_atShow(AW_REPOS_TO_MOUSE); // always popup at current mouse-position (i.e. directly above the button)
525    aw_popup->recalc_size_atShow(AW_RESIZE_USER);   // if user changes the size of any field-selection popup, that size will be used for future popups
526
527    aw_popup->activate();
528}
529
530static void popup_field_selection(AW_window *aw_parent, RegFieldSelection *fsel) {
531    fsel->popup_window(aw_parent->get_root());
532}
533
534void create_itemfield_selection_button(AW_window *aws, const FieldSelDef& selDef, const char *at) {
535    /*! Create a button that pops up a window allowing to select an item-field.
536     * Field may exist or not. Allows to specify type of the new field in the latter case.
537     *
538     * @param aws         parent window
539     * @param selDef      specifies details of field selection
540     * @param at          xfig label (ignored if NULp)
541     *
542     * The length of the button is hardcoded and depends on whether new fields are possible or not.
543     * Nevertheless you may override its size by defining an at-label with 'to:'-position in xfig.
544     *
545     * At start of field-writing code:
546     * - use prepare_and_get_selected_itemfield() to create the changekey-info (needed for new fields only)
547     *
548     * For each item written to:
549     * - use GBT_searchOrCreate_itemfield_according_to_changekey() to create the field
550     * - use GB_write_lossless_...() to write its value
551     */
552    if (at) aws->at(at);
553
554    RegFieldSelection& fsel = RegFieldSelection::registrate(aws->get_root(), selDef);
555
556    int old_button_length = aws->get_button_length();
557    aws->button_length(FIELDNAME_VISIBLE_CHARS+(selDef.new_fields_allowed() ? NEWFIELD_EXTRA_SPACE : 0));
558    aws->callback(makeWindowCallback(popup_field_selection, &fsel));
559
560    char *id = GBS_string_eval(selDef.get_awarname().c_str(), "/=_");
561    aws->create_button(GBS_global_string("select_%s", id), selDef.new_fields_allowed() ? fsel.get_button_awarname() : fsel.get_field_awarname());
562    free(id);
563
564    aws->button_length(old_button_length);
565}
566
567Itemfield_Selection *create_itemfield_selection_list(AW_window *aws, const FieldSelDef& selDef, const char *at) {
568    /*! Create a selection list allowing to select an item-field.
569     * Similar to create_itemfield_selection_button().
570     *
571     * Differences:
572     * - returns Itemfield_Selection -> allows to query list of fields (e.g. used in "Reorder fields")
573     * - does not allow to select a new, not yet existing field
574     */
575    if (at) aws->at(at);
576
577    it_assert(selDef.new_fields_allowed() == false); // to allow creation of new fields -> use create_itemfield_selection_button
578
579    RegFieldSelection::registrate(aws->get_root(), selDef); // to avoid that awar is misused for multiple incompatible field-selections
580
581    AW_selection_list   *sellist   = aws->create_selection_list(selDef.get_awarname().c_str());
582    Itemfield_Selection *selection = selDef.build_sel(sellist);
583    selection->refresh();
584    return selection;
585}
586
587// --------------------------------------------------------------------------------
588
589#ifdef UNIT_TESTS
590#ifndef TEST_UNIT_H
591#include <test_unit.h>
592#endif
593
594void TEST_lossless_conversion() {
595    GB_shell  shell;
596    GBDATA   *gb_main = GB_open("no.arb", "c");
597
598    {
599        GB_transaction ta(gb_main);
600
601        const long tested_filter[] = {
602            FIELD_FILTER_INT_WRITEABLE,
603            FIELD_FILTER_BYTE_WRITEABLE,
604            FIELD_FILTER_FLOAT_WRITEABLE,
605        };
606
607        for (unsigned f = 0; f<ARRAY_ELEMS(tested_filter); ++f) {
608            const long filter = tested_filter[f];
609
610            for (GB_TYPES type = GB_BYTE; type<=GB_STRING; type = GB_TYPES(type+1)) {
611                if (type == GB_POINTER) continue;
612
613                TEST_ANNOTATE(GBS_global_string("type=%i", int(type)));
614                GBDATA *gb_field = GB_create(gb_main, "any", type);
615                TEST_REJECT_NULL(gb_field);
616
617                if (filter & (1<<type)) {
618                    switch (filter) {
619                        case FIELD_FILTER_INT_WRITEABLE: {
620                            const int I_min(-2147483648);    // 32bit int min ..
621                            const int I_max(2147483647);     // .. and max
622
623                            int tested_int_value[] = { I_min, -1, 0, 1, I_max-1, I_max };
624                            for (unsigned i = 0; i<ARRAY_ELEMS(tested_int_value); ++i) {
625                                int written = tested_int_value[i];
626
627                                TEST_ANNOTATE(GBS_global_string("type=%i INT written=%i", int(type), written));
628                                TEST_EXPECT_NO_ERROR(GB_write_lossless_int(gb_field, written));
629
630                                GB_ERROR error = NULp;
631                                int      read  = GB_read_lossless_int(gb_field, error);
632
633                                TEST_EXPECT_NO_ERROR(error);
634                                TEST_EXPECT_EQUAL(written, read);
635                            }
636                            break;
637                        }
638                        case FIELD_FILTER_BYTE_WRITEABLE: {
639                            uint8_t tested_byte_value[] = { 0, 1, 127, 128, 255 };
640                            for (unsigned i = 0; i<ARRAY_ELEMS(tested_byte_value); ++i) {
641                                uint8_t written = tested_byte_value[i];
642
643                                TEST_ANNOTATE(GBS_global_string("type=%i BYTE written=%u", int(type), written));
644                                TEST_EXPECT_NO_ERROR(GB_write_lossless_byte(gb_field, written));
645
646                                GB_ERROR error = NULp;
647                                uint8_t  read  = GB_read_lossless_byte(gb_field, error);
648
649                                TEST_EXPECT_NO_ERROR(error);
650                                TEST_EXPECT_EQUAL(written, read);
651                            }
652                            break;
653                        }
654                        case FIELD_FILTER_FLOAT_WRITEABLE: {
655                            float tested_double_value[] = {
656                                0.0, 1.0, 0.5,
657                                1/3.0,
658                                3.123456789,
659                                1234567891.,
660                                123456789.1,
661                                12345678.91,
662                                1234567.891,
663                                123456.7891,
664                                12345.67891,
665                                1234.567891,
666                                123.4567891,
667                                12.34567891,
668                                1.234567891,
669                                .1234567891,
670                                .0123456789,
671                                .00123456789,
672                                .000123456789,
673                                .0000123456789,
674                                .00000123456789,
675                                .000000123456789,
676                                .0000000123456789,
677                                .00000000123456789,
678                                .000000000123456789,
679                                .0000000000123456789,
680                                .00000000000000000000123456789,
681                                .000000000000000000000000000000123456789,
682                                .0000000000000000000000000000000000000000123456789,
683                                .00000000000000000000000000000000000000000000000000123456789,
684                                .000000000000000000000000000000000000000000000000000000000000123456789,
685                                123456789.123456,
686                                123456789.123456789,
687                                M_PI,
688                                123456789.3,
689                            };
690
691                            for (unsigned i = 0; i<ARRAY_ELEMS(tested_double_value); ++i) {
692                                float written = tested_double_value[i];
693
694                                TEST_ANNOTATE(GBS_global_string("type=%i FLOAT written=%f", int(type), written));
695                                TEST_EXPECT_NO_ERROR(GB_write_lossless_float(gb_field, written));
696
697                                double EPSILON = written*0.000001; // choose epsilon value depending on written value
698                                if (EPSILON<=0.0) { // avoid zero epsilon value
699                                    EPSILON = 0.000000001;
700                                }
701
702                                GB_ERROR error = NULp;
703                                float    read  = GB_read_lossless_float(gb_field, error);
704
705                                TEST_EXPECT_NO_ERROR(error);
706                                TEST_EXPECT_SIMILAR(written, read, EPSILON);
707                            }
708                            break;
709                        }
710                        default: it_assert(0); break; // missing test for filter
711                    }
712                }
713                else {
714                    // test that GB_write_lossless_... does fail for other types:
715                    switch (filter) {
716                        case FIELD_FILTER_INT_WRITEABLE:
717                            TEST_EXPECT_ERROR_CONTAINS(GB_write_lossless_int(gb_field, 4711), "Cannot use");
718                            break;
719                        case FIELD_FILTER_BYTE_WRITEABLE:
720                            TEST_EXPECT_ERROR_CONTAINS(GB_write_lossless_byte(gb_field, 128), "Cannot use");
721                            break;
722                        case FIELD_FILTER_FLOAT_WRITEABLE:
723                            TEST_EXPECT_ERROR_CONTAINS(GB_write_lossless_float(gb_field, M_PI), "Cannot use");
724                            break;
725                        default: it_assert(0); break; // missing test for filter
726                    }
727                }
728            }
729        }
730    }
731
732    GB_close(gb_main);
733}
734
735#endif // UNIT_TESTS
736
737// --------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.