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

Last change on this file was 16936, checked in by westram, 6 years ago
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 29.5 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    const bool FALLBACK2DEFAULT = !allowNewFields;
472
473    Itemfield_Selection *itemSel;
474    {
475        AW_selection_list   *sellist = aw_popup->create_selection_list(get_field_awarname(), 1, 1, FALLBACK2DEFAULT);
476        itemSel = def.build_sel(sellist);
477        itemSel->refresh();
478    }
479
480    aw_popup->at("close");
481    aw_popup->callback(AW_POPDOWN);
482    aw_popup->create_button("CLOSE", "CLOSE", "C");
483
484    AW_awar *awar_field = awr->awar(get_field_awarname()); // user-awar
485    if (allowNewFields) {
486        aw_popup->at("help");
487        aw_popup->callback(makeHelpCallback("field_sel_new.hlp"));
488        aw_popup->create_button("HELP", "HELP", "H");
489
490        long allowedTypes  = def.get_type_filter();
491        int  possibleTypes = 0;
492        int  firstType     = -1;
493        for (unsigned i = 0; i<ARRAY_ELEMS(creatable); ++i) {
494            if (allowedTypes & (1<<creatable[i].type)) {
495                possibleTypes++;
496                if (firstType == -1) firstType = creatable[i].type;
497            }
498        }
499        it_assert(possibleTypes>0);
500
501        aw_popup->at("name");
502        aw_popup->create_input_field(get_field_awarname(), FIELDNAME_VISIBLE_CHARS);
503
504        // show type selector even if only one type selectable (layout- and informative-purposes)
505        aw_popup->at("type");
506        aw_popup->create_toggle_field(get_type_awarname(), NULp, NULp);
507        for (unsigned i = 0; i<ARRAY_ELEMS(creatable); ++i) {
508            if (allowedTypes & (1<<creatable[i].type)) {
509                aw_popup->insert_toggle(creatable[i].label, creatable[i].mnemonic, int(creatable[i].type));
510            }
511        }
512        if (get_unmated_type() == GB_NONE) { // type may vary -> allowed it to be undefined
513            aw_popup->insert_toggle("<undefined>", "", GB_NONE);
514        }
515        aw_popup->update_toggle_field();
516    }
517    else {
518        awar_field->add_callback(makeRootCallback(awt_auto_popdown_cb, &*aw_popup));
519    }
520}
521
522void RegFieldSelection::popup_window(AW_root *awr) {
523    if (!aw_popup) create_window(awr);
524
525    it_assert(aw_popup);
526    aw_popup->recalc_pos_atShow(AW_REPOS_TO_MOUSE); // always popup at current mouse-position (i.e. directly above the button)
527    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
528
529    aw_popup->activate();
530}
531
532static void popup_field_selection(AW_window *aw_parent, RegFieldSelection *fsel) {
533    fsel->popup_window(aw_parent->get_root());
534}
535
536void create_itemfield_selection_button(AW_window *aws, const FieldSelDef& selDef, const char *at) {
537    /*! Create a button that pops up a window allowing to select an item-field.
538     * Field may exist or not. Allows to specify type of the new field in the latter case.
539     *
540     * @param aws         parent window
541     * @param selDef      specifies details of field selection
542     * @param at          xfig label (ignored if NULp)
543     *
544     * The length of the button is hardcoded and depends on whether new fields are possible or not.
545     * Nevertheless you may override its size by defining an at-label with 'to:'-position in xfig.
546     *
547     * At start of field-writing code:
548     * - use prepare_and_get_selected_itemfield() to create the changekey-info (needed for new fields only)
549     *
550     * For each item written to:
551     * - use GBT_searchOrCreate_itemfield_according_to_changekey() to create the field
552     * - use GB_write_lossless_...() to write its value
553     */
554    if (at) aws->at(at);
555
556    RegFieldSelection& fsel = RegFieldSelection::registrate(aws->get_root(), selDef);
557
558    int old_button_length = aws->get_button_length();
559    aws->button_length(FIELDNAME_VISIBLE_CHARS+(selDef.new_fields_allowed() ? NEWFIELD_EXTRA_SPACE : 0));
560    aws->callback(makeWindowCallback(popup_field_selection, &fsel));
561
562    char *id = GBS_string_eval(selDef.get_awarname().c_str(), "/=_");
563    aws->create_button(GBS_global_string("select_%s", id), selDef.new_fields_allowed() ? fsel.get_button_awarname() : fsel.get_field_awarname());
564    free(id);
565
566    aws->button_length(old_button_length);
567}
568
569Itemfield_Selection *create_itemfield_selection_list(AW_window *aws, const FieldSelDef& selDef, const char *at) {
570    /*! Create a selection list allowing to select an item-field.
571     * Similar to create_itemfield_selection_button().
572     *
573     * Differences:
574     * - returns Itemfield_Selection -> allows to query list of fields (e.g. used in "Reorder fields")
575     * - does not allow to select a new, not yet existing field
576     */
577    if (at) aws->at(at);
578
579    const bool FALLBACK2DEFAULT            = true;
580    it_assert(selDef.new_fields_allowed() == false); // to allow creation of new fields -> use create_itemfield_selection_button
581
582    RegFieldSelection::registrate(aws->get_root(), selDef); // to avoid that awar is misused for multiple incompatible field-selections
583
584    AW_selection_list   *sellist   = aws->create_selection_list(selDef.get_awarname().c_str(), FALLBACK2DEFAULT);
585    Itemfield_Selection *selection = selDef.build_sel(sellist);
586    selection->refresh();
587    return selection;
588}
589
590// --------------------------------------------------------------------------------
591
592#ifdef UNIT_TESTS
593#ifndef TEST_UNIT_H
594#include <test_unit.h>
595#endif
596
597void TEST_lossless_conversion() {
598    GB_shell  shell;
599    GBDATA   *gb_main = GB_open("no.arb", "c");
600
601    {
602        GB_transaction ta(gb_main);
603
604        const long tested_filter[] = {
605            FIELD_FILTER_INT_WRITEABLE,
606            FIELD_FILTER_BYTE_WRITEABLE,
607            FIELD_FILTER_FLOAT_WRITEABLE,
608        };
609
610        for (unsigned f = 0; f<ARRAY_ELEMS(tested_filter); ++f) {
611            const long filter = tested_filter[f];
612
613            for (GB_TYPES type = GB_BYTE; type<=GB_STRING; type = GB_TYPES(type+1)) {
614                if (type == GB_POINTER) continue;
615
616                TEST_ANNOTATE(GBS_global_string("type=%i", int(type)));
617                GBDATA *gb_field = GB_create(gb_main, "any", type);
618                TEST_REJECT_NULL(gb_field);
619
620                if (filter & (1<<type)) {
621                    switch (filter) {
622                        case FIELD_FILTER_INT_WRITEABLE: {
623                            const int I_min(-2147483648);    // 32bit int min ..
624                            const int I_max(2147483647);     // .. and max
625
626                            int tested_int_value[] = { I_min, -1, 0, 1, I_max-1, I_max };
627                            for (unsigned i = 0; i<ARRAY_ELEMS(tested_int_value); ++i) {
628                                int written = tested_int_value[i];
629
630                                TEST_ANNOTATE(GBS_global_string("type=%i INT written=%i", int(type), written));
631                                TEST_EXPECT_NO_ERROR(GB_write_lossless_int(gb_field, written));
632
633                                GB_ERROR error = NULp;
634                                int      read  = GB_read_lossless_int(gb_field, error);
635
636                                TEST_EXPECT_NO_ERROR(error);
637                                TEST_EXPECT_EQUAL(written, read);
638                            }
639                            break;
640                        }
641                        case FIELD_FILTER_BYTE_WRITEABLE: {
642                            uint8_t tested_byte_value[] = { 0, 1, 127, 128, 255 };
643                            for (unsigned i = 0; i<ARRAY_ELEMS(tested_byte_value); ++i) {
644                                uint8_t written = tested_byte_value[i];
645
646                                TEST_ANNOTATE(GBS_global_string("type=%i BYTE written=%u", int(type), written));
647                                TEST_EXPECT_NO_ERROR(GB_write_lossless_byte(gb_field, written));
648
649                                GB_ERROR error = NULp;
650                                uint8_t  read  = GB_read_lossless_byte(gb_field, error);
651
652                                TEST_EXPECT_NO_ERROR(error);
653                                TEST_EXPECT_EQUAL(written, read);
654                            }
655                            break;
656                        }
657                        case FIELD_FILTER_FLOAT_WRITEABLE: {
658                            float tested_double_value[] = {
659                                0.0, 1.0, 0.5,
660                                1/3.0,
661                                3.123456789,
662                                1234567891.,
663                                123456789.1,
664                                12345678.91,
665                                1234567.891,
666                                123456.7891,
667                                12345.67891,
668                                1234.567891,
669                                123.4567891,
670                                12.34567891,
671                                1.234567891,
672                                .1234567891,
673                                .0123456789,
674                                .00123456789,
675                                .000123456789,
676                                .0000123456789,
677                                .00000123456789,
678                                .000000123456789,
679                                .0000000123456789,
680                                .00000000123456789,
681                                .000000000123456789,
682                                .0000000000123456789,
683                                .00000000000000000000123456789,
684                                .000000000000000000000000000000123456789,
685                                .0000000000000000000000000000000000000000123456789,
686                                .00000000000000000000000000000000000000000000000000123456789,
687                                .000000000000000000000000000000000000000000000000000000000000123456789,
688                                123456789.123456,
689                                123456789.123456789,
690                                M_PI,
691                                123456789.3,
692                            };
693
694                            for (unsigned i = 0; i<ARRAY_ELEMS(tested_double_value); ++i) {
695                                float written = tested_double_value[i];
696
697                                TEST_ANNOTATE(GBS_global_string("type=%i FLOAT written=%f", int(type), written));
698                                TEST_EXPECT_NO_ERROR(GB_write_lossless_float(gb_field, written));
699
700                                double EPSILON = written*0.000001; // choose epsilon value depending on written value
701                                if (EPSILON<=0.0) { // avoid zero epsilon value
702                                    EPSILON = 0.000000001;
703                                }
704
705                                GB_ERROR error = NULp;
706                                float    read  = GB_read_lossless_float(gb_field, error);
707
708                                TEST_EXPECT_NO_ERROR(error);
709                                TEST_EXPECT_SIMILAR(written, read, EPSILON);
710                            }
711                            break;
712                        }
713                        default: it_assert(0); break; // missing test for filter
714                    }
715                }
716                else {
717                    // test that GB_write_lossless_... does fail for other types:
718                    switch (filter) {
719                        case FIELD_FILTER_INT_WRITEABLE:
720                            TEST_EXPECT_ERROR_CONTAINS(GB_write_lossless_int(gb_field, 4711), "Cannot use");
721                            break;
722                        case FIELD_FILTER_BYTE_WRITEABLE:
723                            TEST_EXPECT_ERROR_CONTAINS(GB_write_lossless_byte(gb_field, 128), "Cannot use");
724                            break;
725                        case FIELD_FILTER_FLOAT_WRITEABLE:
726                            TEST_EXPECT_ERROR_CONTAINS(GB_write_lossless_float(gb_field, M_PI), "Cannot use");
727                            break;
728                        default: it_assert(0); break; // missing test for filter
729                    }
730                }
731            }
732        }
733    }
734
735    GB_close(gb_main);
736}
737
738#endif // UNIT_TESTS
739
740// --------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.