source: tags/ms_r16q2/SL/ITEMS/item_sel_list.cxx

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