source: branches/items/SL/NDS/nds.cxx

Last change on this file was 18734, checked in by westram, 3 years ago
  • fix NDEBUG warnings:
    • conditionally compile functions used only with assertions.
    • NodeTextBuilder is Noncopyable!
    • declare NDS_Labeler final.
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 36.4 KB
Line 
1// ================================================================ //
2//                                                                  //
3//   File      : AWT_nds.cxx                                        //
4//   Purpose   :                                                    //
5//                                                                  //
6//   Institute of Microbiology (Technical University Munich)        //
7//   http://www.arb-home.de/                                        //
8//                                                                  //
9// ================================================================ //
10
11#include "nds.h"
12#include <awt_config_manager.hxx>
13#include <awt_sel_boxes.hxx>
14
15#include <aw_awar.hxx>
16#include <aw_file.hxx>
17#include <aw_msg.hxx>
18#include <aw_root.hxx>
19#include <aw_select.hxx>
20
21#include <TreeNode.h>
22#include <items.h>
23#include <item_sel_list.h>
24#include <gb_aci.h>
25
26#include <arb_msg_fwd.h>
27#include <arb_global_defs.h>
28
29#define nds_assert(cond) arb_assert(cond)
30
31#define NDS_PER_PAGE 10         // number of NDS definitions on each config-page
32#define NDS_PAGES     6         // how many config-pages (each has NDS_PER_PAGE definitions)
33
34#define NDS_COUNT (NDS_PER_PAGE*NDS_PAGES)    // overall number of NDS definitions
35
36#define AWAR_NDS_USE_ALL "arb_presets/all"
37#define AWAR_NDS_PAGE    "arb_presets/page"
38
39#if defined(DEBUG)
40#define NDS_STRING_SIZE 200
41#else
42#define NDS_STRING_SIZE 4000
43#endif // DEBUG
44
45class NodeTextBuilder : virtual Noncopyable {
46    char  buf[NDS_STRING_SIZE]; // buffer used to generate output
47    char *bp;
48    int   space_left;
49
50    long count;
51    int  show_errors;           // how many errors to show
52
53    long  lengths[NDS_COUNT];   // length of generated string
54    char *fieldname[NDS_COUNT]; // database field name (if empty -> no field; may be hierarchical, see 'rek')
55    bool  rek[NDS_COUNT];       // 1->key is hierarchical (e.g. 'ali_16s/data')
56    char *parsing[NDS_COUNT];   // ACI/SRT program
57    bool  at_group[NDS_COUNT];  // whether string shall appear at group NDS entries
58    bool  at_leaf[NDS_COUNT];   // whether string shall appear at leaf NDS entries
59
60    void init_buffer() {
61        bp         = buf;
62        space_left = NDS_STRING_SIZE-1;
63    }
64    char *get_buffer() {
65        bp[0] = 0;
66        return buf;
67    }
68
69    void insert_overflow_warning() {
70        nds_assert(space_left >= 0); // <0 means 'already warned'
71        while (space_left) {
72            *bp++ = ' ';
73            space_left--;
74        }
75
76        static const char *warning  = "..<truncated>";
77        static int         warn_len = 0;
78        if (!warn_len) warn_len = strlen(warning);
79
80        strcpy(buf+(NDS_STRING_SIZE-warn_len-1), warning);
81        space_left = -1;
82    }
83
84    void append(char c) {
85        if (space_left >= 1) {
86            *bp++ = c;
87            space_left--;
88        }
89        else if (!space_left) {
90            insert_overflow_warning();
91        }
92    }
93    void append(const char *str, int length = -1) {
94        nds_assert(str);
95        if (length == -1) length = strlen(str);
96        nds_assert(int(strlen(str)) == length);
97
98        if (space_left >= length) {
99            strcpy(bp, str);
100            bp         += length;
101            space_left -= length;
102        }
103        else { // space_left < length
104            if (space_left >= 0) {
105                if (space_left>0) {
106                    memcpy(bp, str, space_left);
107                    bp         += space_left;
108                    space_left  = 0;
109                }
110                insert_overflow_warning();
111            }
112        }
113    }
114
115    __ATTR__FORMAT(2) void appendf(const char *format, ...) { FORWARD_FORMATTED(append, format); }
116
117public:
118
119    NodeTextBuilder()
120        : count(-1)
121    {
122        for (int i = 0; i<NDS_COUNT; ++i) {
123            fieldname[i] = NULp;
124            parsing[i]   = NULp;
125        }
126        init_buffer();
127    }
128    ~NodeTextBuilder();
129
130    void init(GBDATA *gb_main);
131    const char *work(GBDATA *gb_main, GBDATA *gbd, NDS_Type mode, TreeNode *species, const char *tree_name, bool forceGroup);
132};
133
134inline const char *viewkeyAwarName(int i, const char *name) {
135    nds_assert(i >= 0 && i < NDS_PER_PAGE);
136    return GBS_global_string("tmp/viewkeys/viewkey_%i/%s", i, name);
137}
138
139inline AW_awar *viewkeyAwar(AW_root *aw_root, AW_default awdef, int i, const char *name, bool string_awar) {
140    const char *awar_name = viewkeyAwarName(i, name);
141    AW_awar    *awar      = NULp;
142    if (string_awar) awar = aw_root->awar_string(awar_name, "", awdef);
143    else        awar      = aw_root->awar_int(awar_name, 0, awdef);
144    return awar;
145}
146
147static void map_viewkey(AW_root *aw_root, AW_default awdef, int i, GBDATA *gb_viewkey) {
148    // maps one NDS key data to one line of the config window
149
150    GBDATA *gb_key_text = GB_entry(gb_viewkey, "key_text");
151    GBDATA *gb_pars     = GB_entry(gb_viewkey, "pars");
152    GBDATA *gb_len1     = GB_entry(gb_viewkey, "len1");
153    GBDATA *gb_group    = GB_entry(gb_viewkey, "group");
154    GBDATA *gb_leaf     = GB_entry(gb_viewkey, "leaf");
155
156    nds_assert(gb_key_text);
157    nds_assert(gb_pars);
158    nds_assert(gb_len1);
159    nds_assert(gb_group);
160    nds_assert(gb_leaf);
161
162    AW_awar *Awar;
163    Awar = viewkeyAwar(aw_root, awdef, i, "key_text", true); Awar->map(gb_key_text);
164    Awar = viewkeyAwar(aw_root, awdef, i, "pars",     true); Awar->map(gb_pars);
165    Awar = viewkeyAwar(aw_root, awdef, i, "len1",     false); Awar->map(gb_len1); Awar->set_minmax(0, 1000000);
166    Awar = viewkeyAwar(aw_root, awdef, i, "group",    false); Awar->map(gb_group);
167    Awar = viewkeyAwar(aw_root, awdef, i, "leaf",     false); Awar->map(gb_leaf);
168}
169
170static void map_viewkeys(AW_root *aw_root, /*AW_default*/ GBDATA *awdef, GBDATA *gb_main) {
171    // map visible viewkeys to internal db entries
172    static bool  initialized        = false;
173    AW_awar     *awar_selected_page = NULp;
174
175    if (!initialized) {
176        awar_selected_page = aw_root->awar_int(AWAR_NDS_PAGE, 0, gb_main);
177        awar_selected_page->add_callback(makeRootCallback(map_viewkeys, awdef, gb_main)); // bind to self
178        initialized = true;
179    }
180    else {
181        awar_selected_page = aw_root->awar(AWAR_NDS_PAGE);
182    }
183
184    int page = awar_selected_page->read_int();
185#if defined(DEBUG)
186    printf("map_viewkeys to page %i\n", page);
187#endif // DEBUG
188    if (page<NDS_PAGES) {
189        GB_transaction ta(gb_main);
190
191        GBDATA *gb_arb_presets = GB_search(gb_main, "arb_presets", GB_CREATE_CONTAINER);
192        GBDATA *gb_viewkey     = NULp;
193
194        int i1 = page*NDS_PER_PAGE;
195        int i2 = i1+NDS_PER_PAGE-1;
196
197        for (int i = 0; i <= i2; i++) {
198            gb_viewkey = gb_viewkey ? GB_nextEntry(gb_viewkey) : GB_entry(gb_arb_presets, "viewkey");
199            nds_assert(i<NDS_COUNT);
200            nds_assert(gb_viewkey);
201            if (i >= i1) map_viewkey(aw_root, awdef, i-i1, gb_viewkey);
202        }
203    }
204}
205
206void NDS_create_vars(AW_root *aw_root, AW_default awdef, GBDATA *gb_main, bool force_reinit) {
207    GB_push_transaction(gb_main);
208
209    GBDATA *gb_viewkey     = NULp;
210    GBDATA *gb_arb_presets = GB_search(gb_main, "arb_presets", GB_CREATE_CONTAINER);
211
212    for (int i = 0; i<NDS_COUNT; ++i) {
213        gb_viewkey = gb_viewkey ? GB_nextEntry(gb_viewkey) : GB_entry(gb_arb_presets, "viewkey");
214
215        if (!gb_viewkey) {
216            gb_viewkey = GB_create_container(gb_arb_presets, "viewkey");
217        }
218        else if (force_reinit) {
219            for (GBDATA *gb_child = GB_child(gb_viewkey); gb_child; ) {
220                GBDATA *gb_next_child = GB_nextChild(gb_child);
221                GB_delete(gb_child);
222                gb_child = gb_next_child;
223            }
224        }
225
226        {
227            int  group             = 0;
228            int  leaf              = 0;
229            bool was_group_name    = false;
230            int  default_len       = 30;
231
232            GBDATA *gb_key_text = GB_entry(gb_viewkey, "key_text");
233            if (!gb_key_text) {
234                gb_key_text = GB_create(gb_viewkey, "key_text", GB_STRING);
235                const char *wanted = "";
236                switch (i) {
237                    case 0: wanted = "name"; default_len = 12; leaf = 1; break;
238                    case 1: wanted = "full_name"; leaf = 1; break;
239                    case 2: wanted = ""; was_group_name = true; break;
240                    case 3: wanted = "acc"; default_len = 20; leaf = 1; break;
241                    case 4: wanted = "date"; break;
242                }
243                GB_write_string(gb_key_text, wanted);
244            }
245
246            if (strcmp(GB_read_char_pntr(gb_key_text), "group_name") == 0) {
247                GB_write_string(gb_key_text, "");
248                was_group_name = true; // means: change group/leaf + add 'taxonomy(1)' to ACI
249            }
250
251            GB_searchOrCreate_int(gb_viewkey, "len1", default_len);
252            GBDATA *gb_pars = GB_searchOrCreate_string(gb_viewkey, "pars", "");
253
254            if (was_group_name) {
255                group = 1;
256                leaf  = 0;
257
258                const char *pars = GB_read_char_pntr(gb_pars);
259
260                if (pars[0] == 0) pars        = "taxonomy(1)"; // empty ACI/SRT
261                else if (pars[0] == ':') pars = GBS_global_string("taxonomy(1)|%s", pars); // was an SRT -> unsure what to do
262                else if (pars[0] == '|') pars = GBS_global_string("taxonomy(1)%s", pars); // was an ACI -> prefix taxonomy
263                else pars                     = GBS_global_string("taxonomy(1)|%s", pars); // other ACIs -> same
264
265                GB_write_string(gb_pars, pars);
266            }
267
268            {
269                GBDATA *gb_flag1 = GB_entry(gb_viewkey, "flag1");
270                if (gb_flag1) {
271                    if (GB_read_int(gb_flag1)) { // obsolete
272                        leaf = 1;
273                    }
274                    GB_ERROR error = GB_delete(gb_flag1);
275                    if (error) aw_message(error);
276                }
277            }
278
279            {
280                GBDATA *gb_inherit = GB_entry(gb_viewkey, "inherit");
281                if (gb_inherit) { // 'inherit' is old NDS style -> convert & delete
282                    if (was_group_name && GB_read_int(gb_inherit)) leaf = 1;
283                    GB_ERROR error = GB_delete(gb_inherit);
284                    if (error) aw_message(error);
285                }
286            }
287
288            GB_searchOrCreate_int(gb_viewkey, "group", group);
289            GB_searchOrCreate_int(gb_viewkey, "leaf", leaf);
290        }
291    }
292
293    aw_root->awar_int(AWAR_NDS_USE_ALL, 1, gb_main);
294
295    GB_pop_transaction(gb_main);
296
297    map_viewkeys(aw_root, awdef, gb_main); // call once
298}
299
300static const char *script_part_of(const char *predef_entry) {
301    const char *numsign = strchr(predef_entry, '#');
302    return numsign ? numsign+1 : predef_entry;
303}
304
305static bool in_pre_update = false;
306
307static void awt_pre_to_view(AW_root *aw_root) {
308    if (!in_pre_update) {
309        LocallyModify<bool> dontRecurse(in_pre_update, true);
310
311        const char *sel_predef_entry = aw_root->awar(AWAR_SELECT_ACISRT_PRE)->read_char_pntr();
312        aw_root->awar(AWAR_SELECT_ACISRT)->write_string(script_part_of(sel_predef_entry));
313    }
314}
315static void awt_select_pre_from_view(AW_root *aw_root, AW_selection_list *programs) {
316    if (!in_pre_update) {
317        LocallyModify<bool> dontRecurse(in_pre_update, true);
318
319        const char *currScript  = aw_root->awar(AWAR_SELECT_ACISRT)->read_char_pntr();
320        const char *foundPredef = NULL;
321
322        for (AW_selection_list_iterator piter(programs); piter && !foundPredef; ++piter) { // search entry pre-defining current script
323            const char *predef = piter.get_value()->get_string();
324            const char *script = script_part_of(predef);
325
326            if (strcmp(script, currScript) == 0) {
327                foundPredef = predef;
328            }
329        }
330
331        // select script pre-defining current ACI (or select default if modified)
332        aw_root->awar(AWAR_SELECT_ACISRT_PRE)->write_string(foundPredef ? foundPredef : currScript);
333    }
334}
335
336void NDS_popup_select_srtaci_window(AW_window *aww, const char *acisrt_awarname) {
337    static AW_window *win = NULp;
338
339    AW_root *aw_root = aww->get_root();
340
341    if (!win) {
342        AW_awar *awar_curr_aci = aw_root->awar_string(AWAR_SELECT_ACISRT);
343        AW_awar *awar_sel_aci  = aw_root->awar_string(AWAR_SELECT_ACISRT_PRE);
344
345        AW_window_simple *aws = new AW_window_simple;
346        aws->init(aw_root, "SRT_ACI_SELECT", "SRT_ACI_SELECT");
347        aws->load_xfig("awt/srt_select.fig");
348
349        aws->button_length(13);
350        aws->callback(AW_POPDOWN);
351        aws->at("close");
352        aws->create_button("CLOSE", "CLOSE", "C");
353
354        aws->callback(makeHelpCallback("acisrt.hlp"));
355        aws->at("help");
356        aws->create_button("HELP", "HELP", "H");
357
358        aws->at("box");
359        AW_selection_list *programs = aws->create_selection_list(AWAR_SELECT_ACISRT_PRE, true);
360        GB_ERROR           error;
361        {
362            StorableSelectionList storable_sellist(TypedSelectionList("sellst", programs, "SRT/ACI scripts", "srt_aci"));
363            error = storable_sellist.load(GB_path_in_ARBLIB("sellists/srt_aci*.sellst"), false);
364        }
365        if (error) aw_message(error);
366
367        aws->at("field");
368        aws->create_text_field(AWAR_SELECT_ACISRT);
369
370        awar_sel_aci->add_callback(awt_pre_to_view);
371        awar_curr_aci->add_callback(makeRootCallback(awt_select_pre_from_view, programs));
372
373        win = aws;
374    }
375
376    aw_root->awar(AWAR_SELECT_ACISRT)->map(acisrt_awarname);
377    win->activate();
378}
379
380static void nds_init_config(AWT_config_definition& cdef) {
381    for (int i = 0; i<NDS_PER_PAGE; ++i) {
382        cdef.add(viewkeyAwarName(i, "leaf"), "leaf", i);
383        cdef.add(viewkeyAwarName(i, "group"), "group", i);
384        cdef.add(viewkeyAwarName(i, "key_text"), "key_text", i);
385        cdef.add(viewkeyAwarName(i, "len1"), "len1", i);
386        cdef.add(viewkeyAwarName(i, "pars"), "pars", i);
387    }
388}
389
390static char *nds_store_config() {
391    AWT_config_definition cdef;
392    nds_init_config(cdef);
393    return cdef.read();
394}
395
396static void nds_restore_config(const char *stored, GBDATA *gb_main) {
397    // if stored == NULp -> reset
398
399    AWT_config_definition cdef;
400    nds_init_config(cdef);
401
402    if (stored) { // restore
403        AWT_config parsedCfg(stored);
404        if (parsedCfg.has_entry("inherit0")) {
405            aw_message("Converting stored config to new NDS format -- consider saving it again.");
406            // Note: The conversion applied here is also done in NDS_create_vars()
407
408            GB_ERROR error = NULp;
409
410            for (int i = 0; !error && i<NDS_COUNT; ++i) {
411                bool was_group_name = false;
412                {
413                    const char *key_text_key = GBS_global_string("key_text%i", i);
414                    const char *key_text     = parsedCfg.get_entry(key_text_key);
415                    if (strcmp(key_text, "group_name") == 0) {
416                        was_group_name = true;
417                        parsedCfg.set_entry(key_text_key, "");
418                    }
419                }
420
421                bool leaf    = false;
422                bool group   = false;
423                int  inherit = 0;
424
425                {
426                    const char *inherit_key   = GBS_global_string("inherit%i", i);
427                    const char *inherit_value = parsedCfg.get_entry(inherit_key);
428
429                    if (inherit_value) {
430                        inherit = atoi(inherit_value);
431                        parsedCfg.delete_entry(inherit_key);
432                    }
433                    else {
434                        error = GB_export_errorf("Expected entry '%s' in saved config", inherit_key);
435                    }
436                }
437
438                if (was_group_name) {
439                    if (!error) {
440                        leaf  = inherit;
441                        group = true;
442
443                        char       *aci_key = GBS_global_string_copy("pars%i", i);
444                        const char *aci     = parsedCfg.get_entry(aci_key);
445                        char       *new_aci = NULp;
446
447                        if      (aci[0] == 0)   { new_aci = strdup("taxonomy(1)"); }
448                        else if (aci[0] == '|') { new_aci = GBS_global_string_copy("taxonomy(1)%s", aci); }
449                        else                    { new_aci = GBS_global_string_copy("taxonomy(1)|%s", aci); }
450
451                        parsedCfg.set_entry(aci_key, new_aci);
452
453                        free(new_aci);
454                        free(aci_key);
455                    }
456                }
457                else {
458                    leaf = true;
459                }
460
461                if (!error) {
462                    const char *flag1_key   = GBS_global_string("active%i", i);
463                    const char *flag1_value = parsedCfg.get_entry(flag1_key);
464                    if (flag1_value) {
465                        int flag1 = atoi(flag1_value);
466                        if (flag1 == 0) { leaf = group = false; }
467                        parsedCfg.delete_entry(flag1_key);
468                    }
469                    else {
470                        error = GB_export_errorf("Expected entry '%s' in saved config", flag1_key);
471                    }
472                }
473
474                if (!error) {
475                    const char *leaf_key  = GBS_global_string("leaf%i", i);
476                    parsedCfg.set_entry(leaf_key, GBS_global_string("%i", int(leaf)));
477                    const char *group_key = GBS_global_string("group%i", i);
478                    parsedCfg.set_entry(group_key, GBS_global_string("%i", int(group)));
479                }
480            }
481
482            if (!error) {
483                char *converted_cfg_str = parsedCfg.config_string();
484                cdef.write(converted_cfg_str);
485                free(converted_cfg_str);
486            }
487            else {
488                aw_message(error);
489            }
490        }
491        else {
492            cdef.write(stored);
493        }
494    }
495    else { // reset to factory defaults
496        cdef.reset(); // AWAR values are just empty here
497        NDS_create_vars(AW_root::SINGLETON, AW_ROOT_DEFAULT, gb_main, true); // => reinit NDS
498    }
499}
500
501AW_window *NDS_create_window(AW_root *aw_root, GBDATA *gb_main) {
502    static AW_window_simple *aws = NULp;
503    if (!aws) {
504        aws = new AW_window_simple;
505        aws->init(aw_root, "NDS_PROPS", "NDS");
506        aws->load_xfig("awt/nds.fig");
507        aws->auto_space(10, 5);
508
509        aws->callback(AW_POPDOWN);
510        aws->at("close");
511        aws->create_button("CLOSE", "CLOSE", "C");
512
513        aws->at("help");
514        aws->callback(makeHelpCallback("props_nds.hlp"));
515        aws->create_button("HELP", "HELP", "H");
516
517        aws->at("page");
518        aws->create_option_menu(AWAR_NDS_PAGE, true);
519        for (int p = 0; p < NDS_PAGES; p++) {
520            const char *text = GBS_global_string("Entries %i - %i", p*NDS_PER_PAGE+1, (p+1)*NDS_PER_PAGE);
521            aws->insert_option(text, "", p);
522        }
523        aws->update_option_menu();
524
525        aws->at("use");
526        aws->create_option_menu(AWAR_NDS_USE_ALL, true);
527        aws->insert_default_option("Use all entries",          "", 1);
528        aws->insert_option        ("Only use visible entries", "", 0);
529        aws->update_option_menu();
530
531        aws->at("config");
532        AWT_insert_config_manager(aws, AW_ROOT_DEFAULT, "nds", makeStoreConfigCallback(nds_store_config), makeRestoreConfigCallback(nds_restore_config, gb_main));
533
534        // --------------------
535
536        aws->button_length(13);
537        int dummy, closey;
538        aws->at_newline();
539        aws->get_at_position(&dummy, &closey);
540
541        aws->create_button(NULp, "K");
542
543        aws->at_newline();
544
545        int leafx, groupx, fieldx, columnx, srtx, srtux;
546
547        aws->auto_space(10, 0);
548
549        int i;
550        for (i=0; i<NDS_PER_PAGE; i++) {
551            aws->get_at_position(&leafx, &dummy);
552            aws->create_toggle(viewkeyAwarName(i, "leaf"));
553
554            aws->get_at_position(&groupx, &dummy);
555            aws->create_toggle(viewkeyAwarName(i, "group"));
556
557            aws->get_at_position(&fieldx, &dummy);
558            {
559                const char *awar_name = viewkeyAwarName(i, "key_text");
560                create_itemfield_selection_button(aws, FieldSelDef(awar_name, gb_main, SPECIES_get_selector(), FIELD_FILTER_NDS, "display-field"), NULp);
561            }
562
563            aws->get_at_position(&columnx, &dummy);
564            aws->create_input_field(viewkeyAwarName(i, "len1"), 4);
565
566            aws->get_at_position(&srtx, &dummy);
567            {
568                char *awar_name = strdup(viewkeyAwarName(i, "pars"));
569
570                aws->button_length(0);
571                aws->callback(makeWindowCallback(NDS_popup_select_srtaci_window, awar_name)); // awar_name belongs to cbs now
572                {
573                    char *button_id = GBS_global_string_copy("SELECT_SRTACI_%i", i+1);
574                    aws->create_button(button_id, "S");
575                    free(button_id);
576                }
577
578                aws->get_at_position(&srtux, &dummy);
579                aws->at_attach_to(true, false, -7, 30);
580                aws->create_input_field(awar_name, 40);
581            }
582
583            aws->at_unattach();
584            aws->at_newline();
585        }
586
587        aws->at(leafx, closey);
588
589        aws->at_x(leafx);
590        aws->create_button(NULp, "LEAF");
591        aws->at_x(groupx);
592        aws->create_button(NULp, "GRP.");
593
594        aws->at_x(fieldx);
595        aws->create_button(NULp, "FIELD");
596
597        aws->at_x(columnx);
598        aws->create_button(NULp, "WIDTH");
599
600        aws->at_x(srtx);
601        aws->create_button(NULp, "SRT");
602
603        aws->at_x(srtux);
604        aws->create_button(NULp, "ACI/SRT PROGRAM");
605    }
606
607    return aws;
608}
609
610
611
612void NodeTextBuilder::init(GBDATA *gb_main) {
613    GBDATA *gb_arb_presets    = GB_search(gb_main, "arb_presets", GB_CREATE_CONTAINER);
614    bool    only_visible_page = false;
615    int     page              = 0;
616    {
617        int all = *GBT_readOrCreate_int(gb_arb_presets, "all", 1);
618        if (!all) {
619            page              = *GBT_readOrCreate_int(gb_arb_presets, "page", 0);
620            only_visible_page = true;
621        }
622    }
623
624    count   = 0;
625    int idx = 0;
626    for (GBDATA *gbz = GB_entry(gb_arb_presets, "viewkey"); gbz; gbz = GB_nextEntry(gbz), ++idx) {
627        bool use = !only_visible_page || (idx/NDS_PER_PAGE) == page;
628        if (use) {
629            // wanted NDS entry?
630            bool atLeaf  = *GBT_read_int(gbz, "leaf");
631            bool atGroup = *GBT_read_int(gbz, "group");
632
633            if (atLeaf || atGroup) {
634                GBDATA *gb_keyname = GB_entry(gbz, "key_text");
635                char   *keyname    = GB_read_string(gb_keyname);
636
637                if (keyname[0] && strcmp(keyname, NO_FIELD_SELECTED) == 0) {
638                    freeset(keyname, strdup("")); // NDS code interprets empty keyname as "no field"
639                }
640                freeset(fieldname[count], keyname);
641
642                rek[count]      = bool(GB_first_non_key_char(keyname));
643                lengths[count]  = *GBT_read_int(gbz, "len1");
644                at_leaf[count]  = atLeaf;
645                at_group[count] = atGroup;
646
647                GBDATA *gbe = GB_entry(gbz, "pars");
648                freenull(parsing[count]);
649                if (gbe && GB_read_string_count(gbe)>1) parsing[count] = GB_read_string(gbe);
650                count++;
651            }
652        }
653    }
654
655    show_errors = 10;
656}
657
658NodeTextBuilder::~NodeTextBuilder() {
659    for (int i = 0; i<count; ++i) {
660        freenull(fieldname[i]);
661        freenull(parsing[i]);
662    }
663}
664
665static char *quoted_if_containing_separator(const char *text, char separator) {
666    bool contains_separator = strchr(text, separator);
667    if (!contains_separator) return NULp;
668    return GBS_global_string_copy("\"%s\"", text);
669}
670
671const char *NodeTextBuilder::work(GBDATA *gb_main, GBDATA *gbd, NDS_Type mode, TreeNode *species, const char *tree_name, bool forceGroup) {
672    // @@@ change result into SizedCstr? (to speed up display)
673
674    nds_assert(count>=0); // initialized?
675    init_buffer();
676
677    if (!gbd) {
678        if (!species) return "<internal error: no tree-node, no db-entry>";
679        if (!species->name) return "<internal error: species w/o name>";
680        appendf("<%s>", species->name); // zombie
681    }
682    else {
683        bool field_was_printed = false;
684        bool is_leaf           = !forceGroup && (species ? species->is_leaf() : true);
685
686        for (int i = 0; i < count; i++) {
687            if (is_leaf) { if (!at_leaf[i]) continue; }
688            else         { if (!at_group[i]) continue; }
689
690            char *str        = NULp;  // the generated string
691            bool  apply_aci  = false; // whether aci shall be applied
692            bool  align_left = true;  // otherwise align right
693
694            {
695                const char *field_output = "";
696                const char *field_name   = fieldname[i];
697
698                if (field_name[0] == 0) { // empty field_name (or NO_FIELD_SELECTED) -> only do ACI/SRT
699                    apply_aci = true;
700                }
701                else { // non-empty field_name
702                    GBDATA *gbe;
703                    if (rek[i]) {       // hierarchical key
704                        gbe = GB_search(gbd, field_name, GB_FIND);
705                    }
706                    else {              // flat entry
707                        gbe = GB_entry(gbd, field_name);
708                    }
709
710                    // silently ignore missing fields (and leave apply_aci false!)
711                    if (gbe) {
712                        apply_aci = true;
713                        switch (GB_read_type(gbe)) {
714                            case GB_INT: field_output  = GBS_global_string("%li", GB_read_int(gbe)); align_left = false; break;
715                            case GB_BYTE: field_output = GBS_global_string("%i", GB_read_byte(gbe)); align_left = false; break;
716
717                            case GB_FLOAT: {
718                                const char *format = "%5.4f";
719                                if (mode == NDS_OUTPUT_TAB_SEPARATED) { // '.' -> ','
720                                    char *dotted  = GBS_global_string_copy(format, GB_read_float(gbe));
721                                    char *dot     = strchr(dotted, '.');
722                                    if (dot) *dot = ',';
723                                    field_output  = GBS_static_string(dotted);
724                                    free(dotted);
725                                }
726                                else {
727                                    field_output = GBS_global_string(format, GB_read_float(gbe));
728                                }
729                                align_left = false;
730                                break;
731                            }
732                            case GB_STRING:
733                                field_output = GB_read_char_pntr(gbe);
734                                if (!field_output) field_output="<read error>";
735                                break;
736
737                            default: {
738                                char *as_string = GB_read_as_string(gbe);
739                                field_output    = GBS_static_string(as_string);
740                                free(as_string);
741                            }
742                        }
743                    }
744                    else {
745                        if (GB_have_error()) {
746                            field_output = GB_await_error();
747                        }
748                    }
749                }
750                str = strdup(field_output);
751            }
752
753            // apply ACI/SRT program
754
755            GB_ERROR error = NULp;
756            if (apply_aci) {
757                const char *aci = parsing[i];
758                if (aci) {
759                    GBL_env      env(gb_main, tree_name);  // @@@ pass from caller?
760                    GBL_call_env callEnv(gbd, env);
761
762                    char *aci_result            = GB_command_interpreter_in_env(str, aci, callEnv);
763                    if (!aci_result) aci_result = GBS_global_string_copy("<error: %s>", GB_await_error());
764                    freeset(str, aci_result);
765                }
766            }
767
768            NDS_mask_nonprintable_chars(str);
769
770            // quote string, if it contains separator
771            {
772                char *quoted = NULp;
773                switch (mode) {
774                    case NDS_OUTPUT_COMMA_SEPARATED:
775                        quoted = quoted_if_containing_separator(str, ',');
776                        break;
777
778                    case NDS_OUTPUT_TAB_SEPARATED:
779                        quoted = quoted_if_containing_separator(str, '\t');
780                        break;
781
782                    case NDS_OUTPUT_LEAFTEXT:
783                    case NDS_OUTPUT_SPACE_PADDED:
784                        break;
785                }
786
787                if (quoted) freeset(str, quoted);
788            }
789
790
791            bool skip_display = (mode == NDS_OUTPUT_LEAFTEXT && str[0] == 0);
792            if (!skip_display) {
793                switch (mode) {
794                    case NDS_OUTPUT_LEAFTEXT:
795                        if (!field_was_printed) break; // no comma no space if nothing printed yet
796                        append(','); // separate single fields by comma in compressed mode
797                        // fall-through
798                    case NDS_OUTPUT_SPACE_PADDED: // fixed column width output (for office calc)
799                        if (!field_was_printed) break; // no space if nothing printed yet
800                        append(' '); // print at least one space if not using tabs
801                        break;
802
803                    case NDS_OUTPUT_COMMA_SEPARATED:
804                        if (i != 0) append(','); // CSV output (for office calc)
805                        break;
806
807                    case NDS_OUTPUT_TAB_SEPARATED:
808                        if (i != 0) append('\t'); // tabbed output (for office calc)
809                        break;
810                }
811
812                field_was_printed = true;
813
814                int str_len = strlen(str);
815                switch (mode) {
816                    case NDS_OUTPUT_TAB_SEPARATED:
817                    case NDS_OUTPUT_COMMA_SEPARATED:
818                        append(str, str_len);
819                        break;
820
821                    case NDS_OUTPUT_LEAFTEXT:
822                    case NDS_OUTPUT_SPACE_PADDED: {
823                        int nds_len = lengths[i];
824                        if (str_len>nds_len) { // string is too long -> shorten
825                            str[nds_len] = 0;
826                            str_len      = nds_len;
827                        }
828
829                        if (mode == NDS_OUTPUT_SPACE_PADDED) { // may need alignment
830                            const char *spaced = GBS_global_string((align_left ? "%-*s" : "%*s"), nds_len, str);
831                            append(spaced, nds_len);
832                        }
833                        else {
834                            nds_assert(mode == NDS_OUTPUT_LEAFTEXT);
835                            append(str, str_len);
836                        }
837                    }
838                }
839            }
840
841            // show first XXX errors
842            if (error && show_errors>0) {
843                show_errors--;
844                aw_message(error);
845            }
846
847            free(str);
848        }
849    }
850
851    return get_buffer();
852}
853
854NodeTextBuilder& NDS_Labeler::theBuilder(GBDATA *gb_main) const {
855    if (!builder) {
856        builder = new NodeTextBuilder;
857        builder->init(gb_main);
858    }
859    return *builder;
860}
861
862NDS_Labeler::NDS_Labeler(NDS_Type type_) :
863    type(type_),
864    builder(NULp)
865{
866}
867NDS_Labeler::~NDS_Labeler() {
868    delete builder;
869}
870
871const char *NDS_Labeler::generate(GBDATA *gb_main, GBDATA *gbd, TreeNode *species, const char *tree_name) const {
872    return theBuilder(gb_main).work(gb_main, gbd, get_NDS_Type(), species, tree_name, false);
873}
874const char *NDS_Labeler::groupLabel(GBDATA *gb_main, GBDATA *gbd, TreeNode *species, const char *tree_name) const {
875    // forces group nodes (even if 'species' is a leaf)
876    return theBuilder(gb_main).work(gb_main, gbd, get_NDS_Type(), species, tree_name, true);
877}
878
879
880static const char *createReplaceTable() {
881    static char replaceTable[256];
882
883    for (int i = 0; i<32; ++i)   replaceTable[i] = '?';
884    for (int i = 32; i<256; ++i) replaceTable[i] = i;
885
886    const char LFREP = '#';
887
888    replaceTable['\n'] = LFREP;
889    replaceTable['\r'] = LFREP;
890    replaceTable['\t'] = ' ';
891
892    return replaceTable;
893}
894
895char *NDS_mask_nonprintable_chars(char * const str) {
896    // mask nonprintable characters in result of NDS.
897    //
898    // modifies and returns 'str'
899    //
900    // background: gtk renders LFs as such (i.e. renders multiple lines),
901    //             motif printed a small box (i.e. rendered all text in one line)
902
903    static const char *replaceTable = createReplaceTable();
904    for (int i = 0; str[i]; ++i) {
905        str[i] = replaceTable[safeCharIndex(str[i])];
906    }
907    return str;
908}
909
910// --------------------------------------------------------------------------------
911
912#ifdef UNIT_TESTS
913#ifndef TEST_UNIT_H
914#include <test_unit.h>
915#endif
916
917#define TEST_EXPECT_MASK_NONPRINTABLE(i,o) do { \
918        char *masked = strdup(i);               \
919        NDS_mask_nonprintable_chars(masked);    \
920        TEST_EXPECT_EQUAL(masked,o);            \
921        free(masked);                           \
922    } while (0)
923
924void TEST_mask_nds() {
925    TEST_EXPECT_MASK_NONPRINTABLE("plain text",     "plain text");
926    TEST_EXPECT_MASK_NONPRINTABLE("with\nLF",       "with#LF");
927    TEST_EXPECT_MASK_NONPRINTABLE("with\rLF",       "with#LF");
928    TEST_EXPECT_MASK_NONPRINTABLE("with\r\nLF",     "with##LF");
929    TEST_EXPECT_MASK_NONPRINTABLE("tab\tseparated", "tab separated");
930    TEST_EXPECT_MASK_NONPRINTABLE("\t\n\t\n",       " # #");
931}
932
933#define TEST_EXPECT_NDS_EQUALS(specName,labeler,expected_NDS) do {              \
934        GBDATA *gb_species  = GBT_find_species(gb_main, specName);              \
935        TEST_REJECT_NULL(gb_species);                                           \
936                                                                                \
937        const char *nds = labeler.generate(gb_main, gb_species, NULp, NULp);    \
938        TEST_EXPECT_EQUAL(nds, expected_NDS);                                   \
939    } while(0)
940
941#define TEST_EXPECT_NDS_EQUALS__BROKEN(specName,labeler,expected_NDS) do {      \
942        GBDATA *gb_species  = GBT_find_species(gb_main, specName);              \
943        TEST_REJECT_NULL(gb_species);                                           \
944                                                                                \
945        const char *nds = labeler.generate(gb_main, gb_species, NULp, NULp);    \
946        TEST_EXPECT_EQUAL__BROKEN(nds, expected_NDS);                           \
947    } while(0)
948
949void TEST_nds() {
950    GB_shell    shell;
951    const char *testDB  = "display/nds.arb"; // NDS definitions are in ../../UNIT_TESTER/run/display/nds.arb@arb_presets
952    GBDATA     *gb_main = GB_open(testDB, "r");
953
954    TEST_REJECT_NULL(gb_main);
955
956    {
957        GB_transaction ta(gb_main);
958
959        NDS_Labeler leaftext_labeler(NDS_OUTPUT_LEAFTEXT);
960        NDS_Labeler space_padded_labeler(NDS_OUTPUT_SPACE_PADDED);
961        NDS_Labeler tab_separated_labeler(NDS_OUTPUT_TAB_SEPARATED);
962        NDS_Labeler comma_separated_labeler(NDS_OUTPUT_COMMA_SEPARATED);
963
964        TEST_EXPECT_NDS_EQUALS("MycChlor", leaftext_labeler,        "'MycChlor', Mycobacterium #phenolicus, acc=X79094");
965        TEST_EXPECT_NDS_EQUALS("MycChlor", space_padded_labeler,    "'MycChlor'   Mycobacterium #phenolicus      acc=X79094          ");
966        TEST_EXPECT_NDS_EQUALS("MycChlor", tab_separated_labeler,   "'MycChlor'\tMycobacterium #phenolicus\tacc=X79094");
967        TEST_EXPECT_NDS_EQUALS("MycChlor", comma_separated_labeler, "'MycChlor',Mycobacterium #phenolicus,acc=X79094");
968
969        TEST_EXPECT_NDS_EQUALS("ActUtahe", leaftext_labeler,        "'ActUtahe', Act;ino planes uta,hen.sis#, acc=X80823");
970        TEST_EXPECT_NDS_EQUALS("ActUtahe", space_padded_labeler,    "'ActUtahe'   Act;ino planes uta,hen.sis#    acc=X80823          ");
971        TEST_EXPECT_NDS_EQUALS("ActUtahe", tab_separated_labeler,   "'ActUtahe'\tAct;ino planes uta,hen.sis#\tacc=X80823");
972        TEST_EXPECT_NDS_EQUALS("ActUtahe", comma_separated_labeler, "'ActUtahe',\"Act;ino planes uta,hen.sis#\",acc=X80823");     // quote 2nd value (cause it contains a comma)
973
974        TEST_EXPECT_NDS_EQUALS("StpGrise", leaftext_labeler,        "'StpGrise', Strepto s griseus, acc=M76388 X55435 X6");       // acc truncated!
975        TEST_EXPECT_NDS_EQUALS("StpGrise", space_padded_labeler,    "'StpGrise'   Strepto s griseus              acc=M76388 X55435 X6");
976        TEST_EXPECT_NDS_EQUALS("StpGrise", tab_separated_labeler,   "'StpGrise'\tStrepto s griseus\tacc=M76388 X55435 X61478");   // acc not truncated here
977        TEST_EXPECT_NDS_EQUALS("StpGrise", comma_separated_labeler, "'StpGrise',Strepto s griseus,acc=M76388 X55435 X61478");
978    }
979
980    GB_close(gb_main);
981}
982
983#endif // UNIT_TESTS
984
985// --------------------------------------------------------------------------------
986
987
988
989
990
Note: See TracBrowser for help on using the repository browser.