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

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