source: trunk/SL/NDS/nds.cxx

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