source: branches/properties/NTREE/NT_edconf.cxx

Last change on this file was 19365, checked in by westram, 21 months ago
  • string splitters:
    • unittests for GBT_split_string:
      • add tests for dropEmptyTokens.
      • document special behavior for empty string.
    • define enum SplitMode. use enum instead of bool param for GBT_splitNdestroy_string + GBT_split_string.
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 55.6 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : NT_edconf.cxx                                     //
4//   Purpose   :                                                   //
5//                                                                 //
6//   Institute of Microbiology (Technical University Munich)       //
7//   http://www.arb-home.de/                                       //
8//                                                                 //
9// =============================================================== //
10
11#include "NT_local.h"
12#include "ad_trees.h"
13
14#include <TreeNode.h>
15#include <TreeDisplay.hxx>
16#include <RegExpr.hxx>
17
18#include <awt_sel_boxes.hxx>
19#include <awt_misc.hxx>
20#include <awt_config_manager.hxx>
21#include <awt_modules.hxx>
22
23#include <aw_awars.hxx>
24#include <aw_root.hxx>
25#include <aw_msg.hxx>
26#include <aw_select.hxx>
27
28#include <ad_config.h>
29#include <ad_cb_prot.h>
30
31#include <arb_global_defs.h>
32
33#include <map>
34#include <set>
35#include <string>
36
37using namespace std;
38
39// AWT_canvas-local (%i=canvas-id):
40#define AWAR_CL_SELECTED_CONFIGS       "configuration_data/win%i/selected"
41#define AWAR_CL_DISPLAY_CONFIG_MARKERS "configuration_data/win%i/display"
42
43#define AWAR_CONFIG_COMMENT "tmp/configuration/comment"
44
45enum extractType {
46    CONF_EXTRACT,
47    CONF_MARK,
48    CONF_UNMARK,
49    CONF_INVERT,
50    CONF_COMBINE // logical AND
51};
52static void nt_extract_configuration(UNFIXED, extractType ext_type);
53
54typedef map<string, string> ConfigHits; // key=speciesname; value[markerIdx]==1 -> highlighted
55
56class ConfigMarkerDisplay FINAL_TYPE : public MarkerDisplay, virtual Noncopyable {
57    GBDATA                  *gb_main;
58    SmartPtr<ConstStrArray>  config; // configuration names
59    StrArray                 errors; // config load-errors
60    ConfigHits               hits;
61
62    void updateHits() {
63        flush_cache();
64        hits.clear();
65        errors.erase();
66        for (int c = 0; c<size(); ++c) {
67            GB_ERROR   error;
68            GBT_config cfg(gb_main, (*config)[c], error);
69
70            for (int area = 0; area<=1 && !error; ++area) {
71                GBT_config_parser cparser(cfg, area);
72
73                while (1) {
74                    const GBT_config_item& item = cparser.nextItem(error);
75                    if (error || item.type == CI_END_OF_CONFIG) break;
76                    if (item.type == CI_SPECIES) {
77                        ConfigHits::iterator found = hits.find(item.name);
78                        if (found == hits.end()) {
79                            string h(size(), '0');
80                            h[c]            = '1';
81                            hits[item.name] = h;
82                        }
83                        else {
84                            (found->second)[c] = '1';
85                        }
86                    }
87                }
88            }
89
90            errors.put(ARB_strdup(null2empty(error)));
91        }
92    }
93
94public:
95    ConfigMarkerDisplay(SmartPtr<ConstStrArray> config_, GBDATA *gb_main_)
96        : MarkerDisplay(config_->size()),
97          gb_main(gb_main_),
98          config(config_)
99    {
100        updateHits();
101    }
102    const char *get_marker_name(int markerIdx) const OVERRIDE {
103        const char *error = errors[markerIdx];
104        const char *name  = (*config)[markerIdx];
105        if (error && error[0]) return GBS_global_string("%s (Error: %s)", name, error);
106        return name;
107    }
108    void retrieve_marker_state(const char *speciesName, NodeMarkers& node) OVERRIDE {
109        ConfigHits::const_iterator found = hits.find(speciesName);
110        if (found != hits.end()) {
111            const string& hit = found->second;
112
113            for (int c = 0; c<size(); ++c) {
114                if (hit[c] == '1') node.incMarker(c);
115            }
116        }
117        node.incNodeSize();
118    }
119
120    void handle_click(int markerIdx, AW_MouseButton button, AWT_graphic_exports& exports) OVERRIDE {
121        if (button == AW_BUTTON_LEFT || button == AW_BUTTON_RIGHT) {
122            AW_root::SINGLETON->awar(AWAR_CONFIGURATION)->write_string(get_marker_name(markerIdx)); // select config of clicked marker
123            if (button == AW_BUTTON_RIGHT) { // extract configuration
124                nt_extract_configuration(NULp, CONF_EXTRACT);
125                exports.request_structure_update(); // needed to recalculate branch colors
126            }
127        }
128    }
129};
130
131inline bool displays_config_markers(MarkerDisplay *md) { return dynamic_cast<ConfigMarkerDisplay*>(md); }
132
133#define CONFIG_SEPARATOR "\1"
134
135inline AW_awar *get_canvas_awar(const char *awar_name_format, int canvas_id) {
136    return AW_root::SINGLETON->awar_no_error(GBS_global_string(awar_name_format, canvas_id));
137}
138inline AW_awar *get_config_awar        (int canvas_id) { return get_canvas_awar(AWAR_CL_SELECTED_CONFIGS,       canvas_id); }
139inline AW_awar *get_display_toggle_awar(int canvas_id) { return get_canvas_awar(AWAR_CL_DISPLAY_CONFIG_MARKERS, canvas_id); }
140
141static SmartPtr<ConstStrArray> get_selected_configs_from_awar(int canvas_id) {
142    // returns configs stored in awar as array (empty array if awar undefined!)
143    SmartPtr<ConstStrArray> config(new ConstStrArray);
144
145    AW_awar *awar = get_config_awar(canvas_id);
146    if (awar) {
147        char *config_str = awar->read_string();
148        GBT_splitNdestroy_string(*config, config_str, CONFIG_SEPARATOR, SPLIT_DROPEMPTY);
149    }
150
151    return config;
152}
153static void write_configs_to_awar(int canvas_id, const CharPtrArray& configs) {
154    char *config_str = GBT_join_strings(configs, CONFIG_SEPARATOR[0]);
155    AW_root::SINGLETON->awar(GBS_global_string(AWAR_CL_SELECTED_CONFIGS, canvas_id))->write_string(config_str);
156    free(config_str);
157}
158
159// --------------------------------------------------------------------------------
160
161static AW_selection *selected_configs_list[MAX_NT_WINDOWS] = { MAX_NT_WINDOWS_NULLINIT };
162static bool allow_selection2awar_update = true;
163static bool allow_to_activate_display   = false;
164
165static void init_config_awars(AW_root *root) {
166    root->awar_string(AWAR_CONFIGURATION, DEFAULT_CONFIGURATION, GLOBAL.gb_main);
167}
168static void selected_configs_awar_changed_cb(AW_root *aw_root, TREE_canvas *ntw) {
169    AWT_graphic_tree        *agt    = DOWNCAST(AWT_graphic_tree*, ntw->gfx);
170    int                      ntw_id = ntw->get_index();
171    SmartPtr<ConstStrArray>  config = get_selected_configs_from_awar(ntw_id);
172    bool                     redraw = false;
173
174    if (config->empty() || get_display_toggle_awar(ntw_id)->read_int() == 0) {
175        if (displays_config_markers(agt->get_marker_display())) { // only hide config markers
176            agt->hide_marker_display();
177            redraw = true;
178        }
179    }
180    else {
181        bool activate = allow_to_activate_display || displays_config_markers(agt->get_marker_display());
182
183        if (activate) {
184            init_config_awars(aw_root);
185            ConfigMarkerDisplay *disp = new ConfigMarkerDisplay(config, ntw->gb_main);
186            agt->set_marker_display(disp);
187            redraw = true;
188        }
189    }
190
191    if (selected_configs_list[ntw_id]) { // if configuration_marker_window has been opened
192        // update content of subset-selection (needed when reloading a config-set (not implemented yet) or after renaming a config)
193        LocallyModify<bool> avoid(allow_selection2awar_update, false);
194        awt_set_subset_selection_content(selected_configs_list[ntw_id], *config);
195    }
196
197    if (redraw) AW_root::SINGLETON->awar(AWAR_TREE_REFRESH)->touch();
198}
199
200static void selected_configs_display_awar_changed_cb(AW_root *root, TREE_canvas *ntw) {
201    LocallyModify<bool> allowInteractiveActivation(allow_to_activate_display, true);
202    selected_configs_awar_changed_cb(root, ntw);
203}
204
205static void configs_selectionlist_changed_cb(AW_selection *selected_configs, bool interactive_change, AW_CL ntw_id) {
206    if (allow_selection2awar_update) {
207        LocallyModify<bool> allowInteractiveActivation(allow_to_activate_display, interactive_change);
208
209        StrArray config;
210        selected_configs->get_values(config);
211        write_configs_to_awar(ntw_id, config);
212    }
213}
214
215static void config_modified_cb(GBDATA *gb_cfg_area) { // called with "top_area" AND "middle_area" entry!
216    static GBDATA   *gb_lastname = NULp;
217    static GB_ULONG  lastcall     = 0;
218
219    GBDATA   *gb_name  = GB_entry(GB_get_father(gb_cfg_area), "name");
220    GB_ULONG  thiscall = GB_time_of_day();
221
222    bool is_same_modification = gb_name == gb_lastname && (thiscall == lastcall || thiscall == (lastcall+1));
223    if (!is_same_modification) { // avoid duplicate check if "top_area" and "middle_area" changed (=standard case)
224        // touch all canvas-specific awars that contain 'name'
225        const char *name = GB_read_char_pntr(gb_name);
226
227        for (int canvas_id = 0; canvas_id<MAX_NT_WINDOWS; ++canvas_id) {
228            SmartPtr<ConstStrArray> config = get_selected_configs_from_awar(canvas_id);
229            for (size_t c = 0; c<config->size(); ++c) {
230                if (strcmp((*config)[c], name) == 0) {
231                    get_config_awar(canvas_id)->touch();
232                    break;
233                }
234            }
235        }
236    }
237    gb_lastname = gb_name;
238    lastcall    = thiscall;
239}
240
241#define CONFIG_BASE_PATH "/configuration_data/configuration"
242
243static void install_config_change_callbacks(GBDATA *gb_main) {
244    static bool installed = false;
245    if (!installed) {
246        DatabaseCallback dbcb = makeDatabaseCallback(config_modified_cb);
247        ASSERT_NO_ERROR(GB_add_hierarchy_callback(gb_main, CONFIG_BASE_PATH "/middle_area", GB_CB_CHANGED, dbcb));
248        ASSERT_NO_ERROR(GB_add_hierarchy_callback(gb_main, CONFIG_BASE_PATH "/top_area",    GB_CB_CHANGED, dbcb));
249
250        installed = true;
251    }
252}
253
254void NT_activate_configMarkers_display(TREE_canvas *ntw) {
255    GBDATA *gb_main = ntw->gb_main;
256
257    int      ntw_idx      = ntw->get_index();
258    AW_awar *awar_selCfgs = ntw->awr->awar_string(GBS_global_string(AWAR_CL_SELECTED_CONFIGS, ntw_idx), "", gb_main);
259    awar_selCfgs->add_callback(makeRootCallback(selected_configs_awar_changed_cb, ntw));
260
261    AW_awar *awar_dispCfgs = ntw->awr->awar_int(GBS_global_string(AWAR_CL_DISPLAY_CONFIG_MARKERS, ntw_idx), 1, gb_main);
262    awar_dispCfgs->add_callback(makeRootCallback(selected_configs_display_awar_changed_cb, ntw));
263
264    awar_selCfgs->touch(); // force initial refresh
265    install_config_change_callbacks(gb_main);
266}
267
268// define where to store config-sets (using config-manager):
269#define MANAGED_CONFIGSET_SECTION "configmarkers"
270#define MANAGED_CONFIGSET_ENTRY   "selected_configs"
271
272static void setup_configmarker_config_cb(AWT_config_definition& config, int ntw_id) {
273    AW_awar *selcfg_awar = get_config_awar(ntw_id);
274    nt_assert(selcfg_awar);
275    if (selcfg_awar) {
276        config.add(selcfg_awar->awar_name, MANAGED_CONFIGSET_ENTRY);
277    }
278}
279
280struct ConfigModifier : virtual Noncopyable {
281    virtual ~ConfigModifier() {}
282    virtual const char *modify(const char *old) const = 0;
283
284    bool modifyConfig(ConstStrArray& config) const {
285        bool changed = false;
286        for (size_t i = 0; i<config.size(); ++i) {
287            const char *newContent = modify(config[i]);
288            if (!newContent) {
289                config.remove(i);
290                changed = true;
291            }
292            else if (strcmp(newContent, config[i]) != 0) {
293                config.replace(i, newContent);
294                changed = true;
295            }
296        }
297        return changed;
298    }
299};
300class ConfigRenamer : public ConfigModifier { // derived from Noncopyable
301    const char *oldName;
302    const char *newName;
303    const char *modify(const char *name) const OVERRIDE {
304        return strcmp(name, oldName) == 0 ? newName : name;
305    }
306public:
307    ConfigRenamer(const char *oldName_, const char *newName_)
308        : oldName(oldName_),
309          newName(newName_)
310    {}
311};
312class ConfigDeleter : public ConfigModifier { // derived from Noncopyable
313    const char *toDelete;
314    const char *modify(const char *name) const OVERRIDE {
315        return strcmp(name, toDelete) == 0 ? NULp : name;
316    }
317public:
318    ConfigDeleter(const char *toDelete_)
319        : toDelete(toDelete_)
320    {}
321};
322
323static char *correct_managed_configsets_cb(const char *key, const char *value, AW_CL cl_ConfigModifier) {
324    char *modified_value = NULp;
325    if (strcmp(key, MANAGED_CONFIGSET_ENTRY) == 0) {
326        const ConfigModifier *mod = (const ConfigModifier*)cl_ConfigModifier;
327        ConstStrArray         config;
328        GBT_split_string(config, value, CONFIG_SEPARATOR, SPLIT_DROPEMPTY);
329        if (mod->modifyConfig(config)) {
330            modified_value = GBT_join_strings(config, CONFIG_SEPARATOR[0]);
331        }
332    }
333    return modified_value ? modified_value : ARB_strdup(value);
334}
335static void modify_configurations(const ConfigModifier& mod) {
336    for (int canvas_id = 0; canvas_id<MAX_NT_WINDOWS; ++canvas_id) {
337        // modify currently selected configs:
338        SmartPtr<ConstStrArray> config = get_selected_configs_from_awar(canvas_id);
339        if (mod.modifyConfig(*config)) {
340            write_configs_to_awar(canvas_id, *config);
341        }
342    }
343    // change all configuration-sets stored in config-manager (shared by all windows)
344    AWT_modify_managed_configs(GLOBAL.gb_main, MANAGED_CONFIGSET_SECTION, correct_managed_configsets_cb, AW_CL(&mod));
345}
346
347static void configuration_renamed_cb(const char *old_name, const char *new_name) { modify_configurations(ConfigRenamer(old_name, new_name)); }
348static void configuration_deleted_cb(const char *name)                           { modify_configurations(ConfigDeleter(name)); }
349
350static AW_window *create_configuration_marker_window(AW_root *root, TREE_canvas *ntw) {
351    AW_window_simple *aws = new AW_window_simple;
352
353    int ntw_id = ntw->get_index();
354    aws->init(root, GBS_global_string("MARK_CONFIGS_%i", ntw_id), "Highlight configurations in tree");
355    aws->load_xfig("mark_configs.fig");
356
357    aws->auto_space(10, 10);
358
359    aws->at("close");
360    aws->callback(AW_POPDOWN);
361    aws->create_button("CLOSE", "CLOSE", "C");
362
363    aws->at("help");
364    aws->callback(makeHelpCallback("species_configs_highlight.hlp"));
365    aws->create_button("HELP", "HELP", "H");
366
367
368    aws->at("list");
369    AW_DB_selection *all_configs = awt_create_CONFIG_selection_list(GLOBAL.gb_main, aws, AWAR_CONFIGURATION);
370    AW_selection *sub_sel;
371    {
372        LocallyModify<bool> avoid(allow_selection2awar_update, false); // avoid awar gets updated from empty sub-selectionlist
373        sub_sel = awt_create_subset_selection_list(aws, all_configs->get_sellist(), "selected", "add", "sort", false, configs_selectionlist_changed_cb, ntw->get_index());
374    }
375
376    awt_set_subset_selection_content(sub_sel, *get_selected_configs_from_awar(ntw_id));
377    selected_configs_list[ntw_id] = sub_sel;
378
379    // @@@ would like to use ntw-specific awar for this selection list (opening two lists links them)
380
381    aws->at("show");
382    aws->label("Display?");
383    aws->create_toggle(get_display_toggle_awar(ntw_id)->awar_name);
384
385    aws->at("settings");
386    aws->callback(TREE_create_marker_settings_window);
387    aws->create_autosize_button("SETTINGS", "Settings", "S");
388
389    AWT_insert_config_manager(aws, GLOBAL.gb_main, MANAGED_CONFIGSET_SECTION, makeConfigSetupCallback(setup_configmarker_config_cb, ntw_id));
390
391    return aws;
392}
393
394// -----------------------------
395//      class Store_species
396
397class Store_species : virtual Noncopyable {
398    // stores an amount of species:
399    TreeNode *node;
400    Store_species *next;
401public:
402    Store_species(TreeNode *aNode) {
403        node = aNode;
404        next = NULp;
405    }
406    ~Store_species();
407
408    Store_species* add(Store_species *list) {
409        nt_assert(!next);
410        next = list;
411        return this;
412    }
413
414    Store_species* remove() {
415        Store_species *follower = next;
416        next = NULp;
417        return follower;
418    }
419
420    TreeNode *getNode() const { return node; }
421
422    void call(void (*aPizza)(TreeNode*)) const;
423};
424
425Store_species::~Store_species() {
426    delete next;
427}
428
429void Store_species::call(void (*aPizza)(TreeNode*)) const {
430    aPizza(node);
431    if (next) next->call(aPizza);
432}
433
434static void unmark_species(TreeNode *node) {
435    nt_assert(node);
436    nt_assert(node->gb_node);
437    nt_assert(GB_read_flag(node->gb_node)!=0);
438    GB_write_flag(node->gb_node, 0);
439}
440
441static void mark_species(TreeNode *node, Store_species **extra_marked_species) {
442    nt_assert(node);
443    nt_assert(node->gb_node);
444    nt_assert(GB_read_flag(node->gb_node)==0);
445    GB_write_flag(node->gb_node, 1);
446
447    *extra_marked_species = (new Store_species(node))->add(*extra_marked_species);
448}
449
450
451
452static TreeNode *rightmost_leaf(TreeNode *node) {
453    nt_assert(node);
454    while (!node->is_leaf()) {
455        node = node->get_rightson();
456        nt_assert(node);
457    }
458    return node;
459}
460
461static TreeNode *left_neighbour_leaf(TreeNode *node) {
462    if (node) {
463        TreeNode *father = node->get_father();
464        while (father) {
465            if (father->rightson==node) {
466                node = rightmost_leaf(father->get_leftson());
467                nt_assert(node->is_leaf());
468                if (!node->gb_node) { // Zombie
469                    node = left_neighbour_leaf(node);
470                }
471                return node;
472            }
473            node = father;
474            father = node->get_father();
475        }
476    }
477    return NULp;
478}
479
480const char CFG_SEP = 1;
481
482static int nt_build_conf_string_rek(GB_HASH         *used,
483                                    TreeNode        *tree,
484                                    GBS_strstruct&   memfile,
485                                    Store_species  **extra_marked_species,
486                                    int              use_species_aside,
487                                    int             *auto_mark,
488                                    int              marked_at_left,
489                                    int             *marked_at_right)
490{
491    /*! Builds a configuration string from a tree.
492     *
493     * @param used                      all species inserted by this function are stored here
494     * @param tree                      used for group information
495     * @param memfile                   generated configuration string is stored here
496     * @param extra_marked_species      all extra marked species are inserted here
497     * @param use_species_aside         number of species to mark left and right of marked species
498     * @param auto_mark                 number species to extra-mark (if not already marked)
499     * @param marked_at_left            number of species which were marked (looking to left)
500     * @param marked_at_right           number of species which are marked (when returning from recursion)
501     *
502     * @return the number of marked species
503     *
504     * --------------------------------------------------
505     * Format of configuration string : [Part]+ \0
506     *
507     * Part : '\A' ( Group | Species | Sai )
508     *
509     * Group : ( OpenedGroup | ClosedGroup )
510     * OpenedGroup : 'G' GroupDef
511     * ClosedGroup : 'F' GroupDef
512     * GroupDef : 'groupname' [PART]* EndGroup
513     * EndGroup : '\AE'
514     *
515     * SPECIES : 'L' 'speciesname'
516     * SAI : 'S' 'sainame'
517     *
518     * \0 : ASCII 0 (eos)
519     * \A : ASCII 1
520     */
521
522    if (!tree) return 0;
523    if (tree->is_leaf()) {
524        if (!tree->gb_node) {
525            UNCOVERED();
526            *marked_at_right = marked_at_left;
527            return 0;   // Zombie
528        }
529
530        if (!GB_read_flag(tree->gb_node)) { // unmarked species
531            if (*auto_mark) {
532                (*auto_mark)--;
533                mark_species(tree, extra_marked_species);
534            }
535            else {
536                *marked_at_right = 0;
537                return 0;
538            }
539        }
540        else { // marked species
541            if (marked_at_left<use_species_aside) {
542                // on the left side there are not as many marked species as needed!
543
544                nt_assert(marked_at_left>=0);
545
546                TreeNode *leaf_at_left = tree;
547                int       step_over    = marked_at_left+1; // step over myself
548                int       then_mark    = use_species_aside-marked_at_left;
549
550                while (step_over--) { // step over self and over any adjacent, marked species
551                    leaf_at_left = left_neighbour_leaf(leaf_at_left);
552                }
553
554                Store_species *marked_back = NULp;
555                while (leaf_at_left && then_mark--) { // then additionally mark some species
556                    if (GB_read_flag(leaf_at_left->gb_node) == 0) { // if they are not marked yet
557                        mark_species(leaf_at_left, extra_marked_species);
558                        marked_back = (new Store_species(leaf_at_left))->add(marked_back);
559                    }
560                    leaf_at_left = left_neighbour_leaf(leaf_at_left);
561                }
562
563                while (marked_back) {
564                    memfile.put(CFG_SEP);
565                    memfile.put('L');
566                    memfile.cat(marked_back->getNode()->name);
567                    GBS_write_hash(used, marked_back->getNode()->name, 1);      // Mark species
568
569                    Store_species *rest = marked_back->remove();
570                    delete marked_back;
571                    marked_back = rest;
572                }
573
574                marked_at_left = use_species_aside;
575            }
576            // now use_species_aside species to left are marked!
577            *auto_mark = use_species_aside;
578        }
579
580        memfile.put(CFG_SEP);
581        memfile.put('L');
582        memfile.cat(tree->name);
583        GBS_write_hash(used, tree->name, 1);    // Mark species
584
585        *marked_at_right = marked_at_left+1;
586        return 1;
587    }
588
589    const size_t oldpos = memfile.get_position();
590    if (tree->gb_node && tree->name) {      // but we are a group
591        GBDATA *gb_grouped = GB_entry(tree->gb_node, "grouped");
592        memfile.put(CFG_SEP);
593        if (gb_grouped && GB_read_byte(gb_grouped)) {
594            memfile.put('F');
595        }
596        else {
597            memfile.put('G');
598        }
599
600        memfile.cat(tree->name);
601    }
602
603    int  right_of_leftson;
604    long nspecies=   nt_build_conf_string_rek(used, tree->get_leftson(),  memfile, extra_marked_species, use_species_aside, auto_mark, marked_at_left,   &right_of_leftson);
605    nspecies      += nt_build_conf_string_rek(used, tree->get_rightson(), memfile, extra_marked_species, use_species_aside, auto_mark, right_of_leftson, marked_at_right);
606
607    if (tree->gb_node && tree->name) {      // but we are a group
608        memfile.put(CFG_SEP);
609        memfile.put('E');        // Group end indicated by 'E'
610    }
611
612    if (!nspecies) {
613        const size_t newpos = memfile.get_position();
614        memfile.cut_tail(newpos-oldpos); // delete group info
615    }
616    return nspecies;
617}
618
619struct SAI_string_builder {
620    GBS_strstruct&  sai_middle;
621    const char     *last_group_name;
622};
623
624static void nt_build_sai_string_by_hash(const char *key, long /*val*/, void *cd_sai_builder) {
625    SAI_string_builder *sai_builder = (SAI_string_builder*)cd_sai_builder;
626
627    const char *sep = strchr(key, 1);
628    if (sep) {
629        GBS_strstruct& sai_middle      = sai_builder->sai_middle;
630        const char    *last_group_name = sai_builder->last_group_name;
631
632        if (!last_group_name || strncmp(key, last_group_name, sep-key)) { // new group
633            if (last_group_name) {
634                sai_middle.put(CFG_SEP);
635                sai_middle.put('E');             // End of old group
636            }
637            sai_middle.put(CFG_SEP);
638            sai_middle.cat("FSAI:");
639            sai_middle.ncat(key, sep-key);
640            sai_builder->last_group_name = key;
641        }
642        sai_middle.put(CFG_SEP);
643        sai_middle.put('S');
644        sai_middle.cat(sep+1);
645    }
646}
647
648static void nt_build_sai_string(GBDATA *gb_main, const char *topAreaSaiList, GBS_strstruct& topfile, GBS_strstruct& middlefile) {
649    // collect all Sais,
650    // place some SAI in top area (those listed in 'toparea_SAIs'; SAI-groups will be ignored here)
651    // rest of SAI goes into middle area (SAI-groups respected here)
652
653    GBDATA *gb_sai_data = GBT_get_SAI_data(gb_main);
654    if (gb_sai_data) {
655        GB_HASH *hash = GBS_create_hash(GB_number_of_subentries(gb_sai_data), GB_IGNORE_CASE);
656
657        ConstStrArray topAreaSai;
658        GBT_split_string(topAreaSai, topAreaSaiList, ",;: \t", SPLIT_DROPEMPTY);
659
660        for (GBDATA *gb_sai = GBT_first_SAI_rel_SAI_data(gb_sai_data); gb_sai; gb_sai = GBT_next_SAI(gb_sai)) {
661            GBDATA *gb_name = GB_search(gb_sai, "name", GB_FIND);
662            if (gb_name) {
663                char *name = GB_read_string(gb_name);
664
665                bool wantedInTop = false;
666                for (unsigned s = 0; !wantedInTop && s<topAreaSai.size(); ++s) {
667                    wantedInTop = strcmp(name, topAreaSai[s]) == 0;
668                }
669
670                if (!wantedInTop) {
671                    GBDATA *gb_gn = GB_search(gb_sai, "sai_group", GB_FIND);
672                    char   *gn;
673
674                    if (gb_gn)  gn = GB_read_string(gb_gn);
675                    else        gn = ARB_strdup("SAI's");
676
677                    char *cn = new char[strlen(gn) + strlen(name) + 2];
678                    sprintf(cn, "%s%c%s", gn, 1, name);
679                    GBS_write_hash(hash, cn, 1);
680                    delete [] cn;
681                    free(gn);
682                }
683                free(name);
684            }
685        }
686
687        // add top area SAIs in defined order:
688        for (unsigned s = 0; s<topAreaSai.size(); ++s) {
689            GBDATA *gb_sai = GBT_find_SAI_rel_SAI_data(gb_sai_data, topAreaSai[s]);
690            if (gb_sai) {
691                topfile.put(CFG_SEP);
692                topfile.put('S');
693                topfile.cat(topAreaSai[s]);
694            }
695        }
696
697        // open surrounding SAI-group:
698        middlefile.put(CFG_SEP);
699        middlefile.cat("GSAI-Maingroup");
700
701        SAI_string_builder sai_builder = { middlefile, NULp };
702        GBS_hash_do_const_sorted_loop(hash, nt_build_sai_string_by_hash, GBS_HCF_sortedByKey, &sai_builder);
703        if (sai_builder.last_group_name) {
704            middlefile.put(CFG_SEP);
705            middlefile.put('E');             // End of old group
706        }
707
708        // close surrounding SAI-group:
709        middlefile.put(CFG_SEP);
710        middlefile.put('E');
711
712        GBS_free_hash(hash);
713    }
714}
715
716static void nt_build_conf_marked(GBDATA *gb_main, GB_HASH *used, GBS_strstruct& file) {
717    file.put(CFG_SEP);
718    file.cat("FMore Sequences");
719
720    for (GBDATA *gb_species = GBT_first_marked_species(gb_main);
721         gb_species;
722         gb_species = GBT_next_marked_species(gb_species))
723    {
724        char *name = GBT_read_string(gb_species, "name");
725        if (!GBS_read_hash(used, name)) {
726            file.put(CFG_SEP);
727            file.put('L');
728            file.cat(name);
729        }
730        free(name);
731    }
732
733    file.put(CFG_SEP);
734    file.put('E');   // Group end indicated by 'E'
735}
736
737static void nt_extract_configuration(UNFIXED, extractType ext_type) {
738    GB_transaction  ta(GLOBAL.gb_main);
739    AW_root        *aw_root = AW_root::SINGLETON;
740    char           *cn      = aw_root->awar(AWAR_CONFIGURATION)->read_string();
741
742    if (strcmp(cn, NO_CONFIG_SELECTED) == 0) {
743        aw_message("Please select a configuration");
744    }
745    else {
746        GB_ERROR   error = NULp;
747        GBT_config cfg(GLOBAL.gb_main, cn, error);
748
749        if (!error) {
750            size_t  unknown_species = 0;
751            bool    refresh         = false;
752
753            GB_HASH *was_marked = NULp; // only used for CONF_COMBINE
754
755            switch (ext_type) {
756                case CONF_EXTRACT: // unmark all
757                    GBT_mark_all(GLOBAL.gb_main, 0);
758                    refresh = true;
759                    break;
760
761                case CONF_COMBINE: // store all marked species in hash and unmark them
762                    was_marked = GBT_create_marked_species_hash(GLOBAL.gb_main);
763                    GBT_mark_all(GLOBAL.gb_main, 0);
764                    refresh    = GBS_hash_elements(was_marked);
765                    break;
766
767                default:
768                    break;
769            }
770
771            for (int area = 0; area<=1 && !error; ++area) {
772                GBT_config_parser cparser(cfg, area);
773
774                while (1) {
775                    const GBT_config_item& citem = cparser.nextItem(error);
776                    if (error || citem.type == CI_END_OF_CONFIG) break;
777
778                    if (citem.type == CI_SPECIES) {
779                        GBDATA *gb_species = GBT_find_species(GLOBAL.gb_main, citem.name);
780
781                        if (gb_species) {
782                            int oldmark = GB_read_flag(gb_species);
783                            int newmark = oldmark;
784                            switch (ext_type) {
785                                case CONF_EXTRACT:
786                                case CONF_MARK:     newmark = 1; break;
787                                case CONF_UNMARK:   newmark = 0; break;
788                                case CONF_INVERT:   newmark = !oldmark; break;
789                                case CONF_COMBINE: {
790                                    nt_assert(!oldmark); // should have been unmarked above
791                                    newmark = GBS_read_hash(was_marked, citem.name); // mark if was_marked
792                                    break;
793                                }
794                                default: nt_assert(0); break;
795                            }
796                            if (newmark != oldmark) {
797                                GB_write_flag(gb_species, newmark);
798                                refresh = true;
799                            }
800                        }
801                        else {
802                            unknown_species++;
803                        }
804                    }
805                }
806            }
807
808            if (was_marked) GBS_free_hash(was_marked);
809            if (unknown_species>0 && !error) error = GBS_global_string("configuration '%s' contains %zu unknown species", cn, unknown_species);
810            if (refresh) aw_root->awar(AWAR_TREE_REFRESH)->touch();
811        }
812        aw_message_if(error);
813    }
814    free(cn);
815}
816
817static void nt_delete_configuration(AW_window *aww, AW_DB_selection *dbsel) {
818    GB_transaction ta(GLOBAL.gb_main);
819
820    AW_awar *awar_selected    = aww->get_root()->awar(AWAR_CONFIGURATION);
821    char    *name             = awar_selected->read_string();
822    GBDATA  *gb_configuration = GBT_find_configuration(GLOBAL.gb_main, name);
823
824    if (gb_configuration) {
825        dbsel->get_sellist()->move_selection(1);
826
827        GB_ERROR error = GB_delete(gb_configuration);
828        error          = ta.close(error);
829        if (error) {
830            aw_message(error);
831        }
832        else {
833            configuration_deleted_cb(name);
834        }
835    }
836    free(name);
837}
838
839enum ConfigCreation {
840    BY_CALLING_THE_EDITOR,
841    FROM_IMPORTER,
842    FROM_MANAGER,
843};
844
845static GB_ERROR nt_create_configuration(TreeNode *tree, const char *conf_name, int use_species_aside, ConfigCreation creation) {
846    GB_ERROR error = NULp;
847
848    if (!conf_name || !conf_name[0]) error = "no config name given";
849    else {
850        if (use_species_aside==-1) {
851            static int last_used_species_aside = 3;
852            {
853                const char *val                    = GBS_global_string("%i", last_used_species_aside);
854                char       *use_species            = aw_input("How many extra species to view aside marked:", val);
855                if (use_species) use_species_aside = atoi(use_species);
856                free(use_species);
857            }
858
859            if (use_species_aside<1) error = "illegal number of 'species aside'";
860            else last_used_species_aside = use_species_aside; // remember for next time
861        }
862
863        if (!error) {
864            GB_transaction  ta(GLOBAL.gb_main); // open close transaction
865            AW_root        *awr = AW_root::SINGLETON;
866
867            GBT_config newcfg;
868            {
869                GB_HASH       *used    = GBS_create_hash(GBT_get_species_count(GLOBAL.gb_main), GB_MIND_CASE);
870                GBS_strstruct topfile(1000);
871                GBS_strstruct midfile(10000);
872                {
873                    GBS_strstruct middlefile(10000);
874
875                    nt_build_sai_string(GLOBAL.gb_main, awr->awar(AWAR_TOPAREA_SAIS)->read_char_pntr(), topfile, midfile);
876
877                    if (use_species_aside) {
878                        Store_species *extra_marked_species = NULp;
879                        int            auto_mark            = 0;
880                        int            marked_at_right;
881
882                        nt_build_conf_string_rek(used, tree, middlefile, &extra_marked_species, use_species_aside, &auto_mark, use_species_aside, &marked_at_right);
883                        if (extra_marked_species) {
884                            extra_marked_species->call(unmark_species);
885                            delete extra_marked_species;
886                        }
887                    }
888                    else {
889                        int dummy_1=0, dummy_2;
890                        nt_build_conf_string_rek(used, tree, middlefile, NULp, 0, &dummy_1, 0, &dummy_2);
891                    }
892                    nt_build_conf_marked(GLOBAL.gb_main, used, midfile);
893                    midfile.ncat(middlefile.get_data(), middlefile.get_position());
894                }
895
896                newcfg.set_definition(GBT_config::TOP_AREA,    topfile.release());
897                newcfg.set_definition(GBT_config::MIDDLE_AREA, midfile.release());
898
899                GBS_free_hash(used);
900            }
901
902            GBT_config previous(GLOBAL.gb_main, conf_name, error);
903            error = NULp; // ignore
904
905            const char *prevComment         = NULp; // old or fixed comment
906            const char *comment             = NULp;
907            bool        warnIfSavingDefault = true;
908            switch (creation) {
909                case BY_CALLING_THE_EDITOR: { // always saves DEFAULT_CONFIGURATION!
910                    prevComment         = "This configuration will be OVERWRITTEN each time\nARB_EDIT4 is started w/o specifying a config!\n---";
911                    comment             = "created for ARB_EDIT4";
912                    warnIfSavingDefault = false;
913                    break;
914                }
915                case FROM_MANAGER: {
916                    if (previous.exists()) {
917                        prevComment = previous.get_comment();
918                        comment     = "updated manually";
919                    }
920                    else {
921                        prevComment                      = awr->awar(AWAR_CONFIG_COMMENT)->read_char_pntr();
922                        if (!prevComment[0]) prevComment = NULp;
923                        comment                          = "created manually";
924                    }
925                    break;
926                }
927                case FROM_IMPORTER:
928                    nt_assert(!previous.exists());
929                    comment = "created by importer";
930                    break;
931            }
932
933            nt_assert(implicated(prevComment, comment));
934            if (comment) {
935                // annotate with treename
936                const char *treename = awr->awar(AWAR_TREE_NAME)->read_char_pntr();
937                if (treename[0]) {
938                    comment = GBS_global_string("%s (tree=%s)", comment, treename);
939                }
940                else {
941                    comment = GBS_global_string("%s (no tree)", comment);
942                }
943                char *dated = GBS_log_action_to(prevComment, comment, true);
944                newcfg.set_comment(dated);
945                free(dated);
946            }
947
948            error = newcfg.save(GLOBAL.gb_main, conf_name, warnIfSavingDefault);
949            awr->awar(AWAR_CONFIGURATION)->touch(); // refreshes comment field
950        }
951    }
952
953    return error;
954}
955
956static void nt_store_configuration(AW_window*, TREE_canvas *ntw) {
957    const char *cfgName = AW_root::SINGLETON->awar(AWAR_CONFIGURATION)->read_char_pntr();
958    GB_ERROR    err     = nt_create_configuration(NT_get_tree_root_of_canvas(ntw), cfgName, 0, FROM_MANAGER);
959    aw_message_if(err);
960}
961
962static void nt_rename_configuration(AW_window *aww) {
963    AW_awar  *awar_curr_cfg = aww->get_root()->awar(AWAR_CONFIGURATION);
964    char     *old_name      = awar_curr_cfg->read_string();
965
966    {
967        char *new_name = aw_input("Rename selection", "Enter the new name of the selection", old_name);
968        if (new_name) {
969            GB_ERROR err = NULp;
970
971            {
972                GB_transaction ta(GLOBAL.gb_main);
973
974                GBDATA *gb_existing_cfg  = GBT_find_configuration(GLOBAL.gb_main, new_name);
975                if (gb_existing_cfg) err = GBS_global_string("There is already a selection named '%s'", new_name);
976                else {
977                    GBDATA *gb_old_cfg = GBT_find_configuration(GLOBAL.gb_main, old_name);
978                    if (gb_old_cfg) {
979                        GBDATA *gb_name = GB_entry(gb_old_cfg, "name");
980                        if (gb_name) {
981                            err = GB_write_string(gb_name, new_name);
982                            if (!err) awar_curr_cfg->write_string(new_name);
983                        }
984                        else err = "Selection has no name";
985                    }
986                    else err = "Can't find that selection";
987                }
988                err = ta.close(err);
989            }
990
991            if (err) {
992                aw_message(err);
993            }
994            else {
995                nt_assert(GB_get_transaction_level(GLOBAL.gb_main) == 0); // otherwise callback below behaves wrong
996                configuration_renamed_cb(old_name, new_name);
997            }
998            free(new_name);
999        }
1000    }
1001    free(old_name);
1002}
1003
1004static void selected_config_changed_cb(AW_root *root) {
1005    const char *config = root->awar(AWAR_CONFIGURATION)->read_char_pntr();
1006
1007    bool    nonexisting_config = false;
1008    GBDATA *gb_target_commment = NULp;
1009    if (config[0]) {
1010        GBDATA *gb_configuration = GBT_find_configuration(GLOBAL.gb_main, config);
1011        if (gb_configuration) {
1012            gb_target_commment = GB_entry(gb_configuration, "comment");
1013        }
1014        else {
1015            nonexisting_config = true;
1016        }
1017    }
1018
1019    AW_awar *awar_comment = root->awar(AWAR_CONFIG_COMMENT);
1020    if (gb_target_commment) {
1021        if (!awar_comment->is_mapped()) awar_comment->write_string("");
1022        awar_comment->map(gb_target_commment);
1023    }
1024    else {
1025        char *reuse_comment = nonexisting_config ? awar_comment->read_string() : ARB_strdup("");
1026        if (awar_comment->is_mapped()) {
1027            awar_comment->unmap();
1028        }
1029        awar_comment->write_string(reuse_comment);
1030        free(reuse_comment);
1031    }
1032}
1033static void config_comment_changed_cb(AW_root *root) {
1034    // called when comment-awar changes or gets re-map-ped
1035
1036    AW_awar    *awar_comment = root->awar(AWAR_CONFIG_COMMENT);
1037    const char *comment      = awar_comment->read_char_pntr();
1038
1039    const char *config           = root->awar(AWAR_CONFIGURATION)->read_char_pntr();
1040    GBDATA     *gb_configuration = config[0] ? GBT_find_configuration(GLOBAL.gb_main, config) : NULp;
1041
1042    GB_ERROR error = NULp;
1043    if (awar_comment->is_mapped()) {
1044        if (!comment[0]) { // empty existing comment
1045            nt_assert(gb_configuration);
1046            GBDATA *gb_commment = GB_entry(gb_configuration, "comment");
1047            nt_assert(gb_commment);
1048            if (gb_commment) {
1049                awar_comment->unmap();
1050                error = GB_delete(gb_commment);
1051            }
1052        }
1053    }
1054    else {
1055        if (comment[0]) { // ignore empty comment for unmapped awar
1056            if (gb_configuration) {
1057                nt_assert(!GB_entry(gb_configuration, "comment"));
1058                error = GBT_write_string(gb_configuration, "comment", comment);
1059                if (!error) {
1060                    awar_comment->write_string("");
1061                    selected_config_changed_cb(root);
1062                }
1063            }
1064            else if (!config[0]) {
1065                // do NOT warn if name field contains (not yet) existing name
1066                // (allows to edit comment while creating new config)
1067                error = "Please select an existing species selection to edit its comment";
1068            }
1069        }
1070    }
1071
1072    aw_message_if(error);
1073}
1074
1075static void init_config_admin_awars(AW_root *root) {
1076    init_config_awars(root);
1077    root->awar_string(AWAR_CONFIG_COMMENT, "", GLOBAL.gb_main)->add_callback(config_comment_changed_cb);
1078    root->awar(AWAR_CONFIGURATION)->add_callback(selected_config_changed_cb)->touch();
1079}
1080
1081#pragma GCC diagnostic push
1082#if (GCC_VERSION_CODE<700)
1083#pragma GCC diagnostic ignored "-Wstrict-overflow" // gcc 6.x produces a bogus overflow warning (gcc 7.x is smart enough)
1084#endif
1085
1086static GB_ERROR swap_configs(GBDATA *gb_main, StrArray& config, int i1, int i2) {
1087    GB_ERROR error = NULp;
1088
1089    if (i1>i2) swap(i1, i2); // otherwise overwrite below does not work
1090    nt_assert(i1<i2 && i1>=0 && i2<int(config.size()));
1091
1092    GBT_config c1(gb_main, config[i1], error);
1093    if (!error) {
1094        GBT_config c2(gb_main, config[i2], error);
1095        if (!error) error = c1.saveAsOver(gb_main, config[i1], config[i2], false);
1096        if (!error) error = c2.saveAsOver(gb_main, config[i2], config[i1], false);
1097        if (!error) config.swap(i1, i2);
1098    }
1099    return error;
1100}
1101
1102#pragma GCC diagnostic pop
1103
1104static void reorder_configs_cb(AW_window *aww, awt_reorder_mode mode, AW_DB_selection *sel) {
1105    AW_root    *awr         = aww->get_root();
1106    AW_awar    *awar_config = awr->awar(AWAR_CONFIGURATION);
1107    const char *selected    = awar_config->read_char_pntr();
1108
1109    if (selected && selected[0]) {
1110        AW_selection_list *sellist = sel->get_sellist();
1111
1112        int source_idx = sellist->get_index_of(AW_scalar(selected));
1113        int target_idx = -1;
1114        switch (mode) {
1115            case ARM_TOP:    target_idx = 0;            break;
1116            case ARM_UP:     target_idx = source_idx-1; break;
1117            case ARM_DOWN:   target_idx = source_idx+1; break;
1118            case ARM_BOTTOM: target_idx = -1;           break;
1119        }
1120
1121        int entries = sellist->size();
1122        target_idx  = (target_idx+entries)%entries;
1123
1124        {
1125            GBDATA         *gb_main = sel->get_gb_main();
1126            GB_transaction  ta(gb_main);
1127
1128            StrArray config;
1129            sellist->to_array(config, true);
1130
1131            GB_ERROR error = NULp;
1132            if (source_idx<target_idx) {
1133                for (int i = source_idx+1; i<=target_idx; ++i) {
1134                    swap_configs(gb_main, config, i-1, i);
1135                }
1136            }
1137            else if (source_idx>target_idx) {
1138                for (int i = source_idx-1; i>=target_idx; --i) {
1139                    swap_configs(gb_main, config, i+1, i);
1140                }
1141            }
1142
1143            error = ta.close(error);
1144            aw_message_if(error);
1145        }
1146        awar_config->touch();
1147    }
1148}
1149
1150static void clear_comment_cb(AW_window *aww) {
1151    AW_awar *awar_comment = aww->get_root()->awar(AWAR_CONFIG_COMMENT);
1152    char    *comment      = awar_comment->read_string();
1153
1154    ConstStrArray line;
1155    GBT_splitNdestroy_string(line, comment, '\n');
1156
1157    bool    removedDatedLines = false;
1158    RegExpr datedLine("^([A-Z][a-z]{2}\\s){2}[0-9]+\\s([0-9]{2}:){2}[0-9]{2}\\s[0-9]{4}:\\s", false); // matches lines created with GBS_log_action_to(..., stamp=true)
1159    for (int i = line.size()-1; i >= 0; --i) {
1160        const RegMatch *match = datedLine.match(line[i]);
1161        nt_assert(implicated(!match, !datedLine.has_failed())); // assert RegExpr compiles
1162        if (match && match->didMatch()) {
1163            line.safe_remove(i);
1164            removedDatedLines = true;
1165        }
1166    }
1167
1168    if (!removedDatedLines) line.clear(); // erase all
1169
1170    comment = GBT_join_strings(line, '\n');
1171    awar_comment->write_string(comment);
1172}
1173
1174static AW_window *create_configuration_admin_window(AW_root *root, TREE_canvas *ntw) {
1175    static AW_window_simple *existing_aws[MAX_NT_WINDOWS] = { MAX_NT_WINDOWS_NULLINIT };
1176
1177    int ntw_id = ntw->get_index();
1178    if (!existing_aws[ntw_id]) {
1179        init_config_admin_awars(root);
1180
1181        AW_window_simple *aws = new AW_window_simple;
1182        aws->init(root, GBS_global_string("SPECIES_SELECTIONS_%i", ntw_id), "Species Selections");
1183        aws->load_xfig("nt_selection.fig");
1184
1185        aws->at("close");
1186        aws->callback(AW_POPDOWN);
1187        aws->create_button("CLOSE", "CLOSE", "C");
1188
1189        aws->at("help");
1190        aws->callback(makeHelpCallback("species_configs.hlp"));
1191        aws->create_button("HELP", "HELP", "H");
1192
1193        aws->at("name");
1194        aws->create_input_field(AWAR_CONFIGURATION);
1195
1196        aws->at("comment");
1197        aws->create_text_field(AWAR_CONFIG_COMMENT);
1198
1199        aws->at("clr");
1200        aws->callback(clear_comment_cb);
1201        aws->create_autosize_button("CLEAR", "Clear", "l");
1202
1203        aws->at("list");
1204        AW_DB_selection *dbsel = awt_create_CONFIG_selection_list(GLOBAL.gb_main, aws, AWAR_CONFIGURATION);
1205
1206        aws->button_length(8);
1207
1208        aws->at("store");
1209        aws->callback(makeWindowCallback(nt_store_configuration, ntw));
1210        aws->create_button(GBS_global_string("STORE_%i", ntw_id), "STORE", "S");
1211
1212        aws->at("extract");
1213        aws->callback(makeWindowCallback(nt_extract_configuration, CONF_EXTRACT));
1214        aws->create_button("EXTRACT", "EXTRACT", "E");
1215
1216        aws->at("mark");
1217        aws->callback(makeWindowCallback(nt_extract_configuration, CONF_MARK));
1218        aws->create_button("MARK", "MARK", "M");
1219
1220        aws->at("unmark");
1221        aws->callback(makeWindowCallback(nt_extract_configuration, CONF_UNMARK));
1222        aws->create_button("UNMARK", "UNMARK", "U");
1223
1224        aws->at("invert");
1225        aws->callback(makeWindowCallback(nt_extract_configuration, CONF_INVERT));
1226        aws->create_button("INVERT", "INVERT", "I");
1227
1228        aws->at("combine");
1229        aws->callback(makeWindowCallback(nt_extract_configuration, CONF_COMBINE));
1230        aws->create_button("COMBINE", "COMBINE", "C");
1231
1232        aws->at("delete");
1233        aws->callback(makeWindowCallback(nt_delete_configuration, dbsel));
1234        aws->create_button("DELETE", "DELETE", "D");
1235
1236        aws->at("rename");
1237        aws->callback(nt_rename_configuration);
1238        aws->create_button("RENAME", "RENAME", "R");
1239
1240        aws->at("highlight");
1241        aws->callback(makeCreateWindowCallback(create_configuration_marker_window, ntw));
1242        aws->create_autosize_button(GBS_global_string("HIGHLIGHT_%i", ntw_id), "Highlight in tree", "t");
1243
1244        aws->button_length(0);
1245        aws->at("sort");
1246        awt_create_order_buttons(aws, reorder_configs_cb, dbsel);
1247
1248        existing_aws[ntw_id] = aws;
1249    }
1250    return existing_aws[ntw_id];
1251}
1252
1253void NT_popup_configuration_admin(AW_window *aw_main, TREE_canvas *ntw) {
1254    create_configuration_admin_window(aw_main->get_root(), ntw)->activate();
1255}
1256
1257// -----------------------------------------
1258//      various ways to start the editor
1259
1260static void nt_start_editor_on_configuration(AW_window *aww) {
1261    aww->hide();
1262
1263    const char *cfgName   = aww->get_root()->awar(AWAR_CONFIGURATION)->read_char_pntr();
1264    char       *quotedCfg = GBK_singlequote(cfgName);
1265
1266    AWT_system_cb(GBS_global_string("arb_edit4 -c %s &", quotedCfg));
1267
1268    free(quotedCfg);
1269}
1270
1271AW_window *NT_create_startEditorOnOldConfiguration_window(AW_root *awr) {
1272    static AW_window_simple *aws = NULp;
1273    if (!aws) {
1274        init_config_awars(awr);
1275
1276        aws = new AW_window_simple;
1277        aws->init(awr, "SELECT_CONFIGURATION", "SELECT A CONFIGURATION");
1278        aws->at(10, 10);
1279        aws->auto_space(0, 0);
1280        awt_create_CONFIG_selection_list(GLOBAL.gb_main, aws, AWAR_CONFIGURATION);
1281        aws->at_newline();
1282
1283        aws->callback(nt_start_editor_on_configuration);
1284        aws->create_button("START", "START");
1285
1286        aws->callback(AW_POPDOWN);
1287        aws->create_button("CLOSE", "CLOSE", "C");
1288
1289        aws->window_fit();
1290    }
1291    return aws;
1292}
1293
1294void NT_start_editor_on_tree(AW_window *aww, int use_species_aside, TREE_canvas *ntw) {
1295    init_config_awars(aww->get_root());
1296    GB_ERROR error    = nt_create_configuration(NT_get_tree_root_of_canvas(ntw), DEFAULT_CONFIGURATION, use_species_aside, BY_CALLING_THE_EDITOR);
1297    if (!error) error = GBK_system("arb_edit4 -c " DEFAULT_CONFIGURATION " &");
1298    aw_message_if(error);
1299}
1300
1301inline void nt_create_config_after_import(TREE_canvas *ntw) {
1302    init_config_awars(ntw->awr);
1303
1304    const char *dated_suffix = ARB_dateTime_suffix();
1305    char       *configName   = GBS_global_string_copy("imported_%s", dated_suffix);
1306
1307    // ensure unique config-name
1308    {
1309        int unique = 1;
1310        GB_transaction ta(ntw->gb_main);
1311        while (GBT_find_configuration(ntw->gb_main, configName)) {
1312            freeset(configName, GBS_global_string_copy("imported_%s_%i", dated_suffix, ++unique));
1313        }
1314    }
1315
1316    GB_ERROR error = nt_create_configuration(NT_get_tree_root_of_canvas(ntw), configName, 0, FROM_IMPORTER);
1317    aw_message_if(error);
1318
1319    free(configName);
1320}
1321
1322void NT_create_config_after_import(TREE_canvas *ntw, bool imported_from_scratch) {
1323    /*! create a new config after import
1324     * @param imported_from_scratch if true -> DB was created from scratch, all species in DB are marked.
1325     *                              if false -> data was imported into existing DB. Other species may be marked as well, imported species are "queried".
1326     */
1327
1328    if (imported_from_scratch) {
1329        nt_create_config_after_import(ntw);
1330    }
1331    else {
1332        GB_transaction ta(ntw->gb_main);
1333
1334        // remember marks + mark queried species:
1335        for (GBDATA *gb_species = GBT_first_species(ntw->gb_main); gb_species; gb_species = GBT_next_species(gb_species)) {
1336            GB_write_user_flag(gb_species, GB_USERFLAG_WASMARKED, GB_read_flag(gb_species));
1337            GB_write_flag(gb_species, GB_user_flag(gb_species, GB_USERFLAG_QUERY));
1338        }
1339
1340        nt_create_config_after_import(ntw);
1341
1342        // restore old marks:
1343        for (GBDATA *gb_species = GBT_first_species(ntw->gb_main); gb_species; gb_species = GBT_next_species(gb_species)) {
1344            GB_write_flag(gb_species, GB_user_flag(gb_species, GB_USERFLAG_WASMARKED));
1345            GB_clear_user_flag(gb_species, GB_USERFLAG_WASMARKED);
1346        }
1347    }
1348}
1349
1350// --------------------------------------------------------------------------------
1351
1352#ifdef UNIT_TESTS
1353#ifndef TEST_UNIT_H
1354#include <test_unit.h>
1355#endif
1356
1357static bool fold_group(TreeNode *tree, const char *groupName) {
1358    if (!tree->is_leaf()) {
1359        if (tree->is_normal_group()) {
1360            nt_assert(tree->name);
1361            fprintf(stderr, "group='%s' groupName='%s'\n", tree->name, groupName);
1362            if (strcmp(tree->name, groupName) == 0) {
1363                nt_assert(tree->gb_node);
1364                GBDATA *gb_grouped = GB_entry(tree->gb_node, "grouped");
1365                if (!gb_grouped) {
1366                    gb_grouped = GB_create(tree->gb_node, "grouped", GB_BYTE);
1367                }
1368                TEST_REJECT_NULL(gb_grouped);
1369                if (GB_read_byte(gb_grouped) == 0) {
1370                    TEST_EXPECT_NO_ERROR(GB_write_byte(gb_grouped, 1));
1371                    return true;
1372                }
1373            }
1374        }
1375        return
1376            fold_group(tree->get_leftson(), groupName) ||
1377            fold_group(tree->get_rightson(), groupName);
1378    }
1379    return false;
1380}
1381
1382void TEST_build_conf_string() {
1383    GB_shell shell;
1384    GBDATA   *gb_main = GB_open("TEST_trees.arb", "r");
1385
1386    {
1387        GB_transaction ta(gb_main);
1388
1389        TreeNode *tree = GBT_read_tree(gb_main, "tree_groups", new SimpleRoot);
1390
1391        GBS_strstruct  memfile(500);
1392        Store_species *extra_marked_species = NULp;
1393
1394        TEST_REJECT_NULL(tree);
1395        TEST_EXPECT_NO_ERROR(GBT_link_tree(tree, gb_main, false, NULp, NULp));
1396
1397        int auto_mark = 0;
1398        int dummy;
1399        int marked;
1400
1401        // test result with no species marked:
1402        GBT_mark_all(gb_main, 0);
1403        TEST_EXPECT_ZERO(GBT_count_marked_species(gb_main));
1404        {
1405            GB_HASH *used = GBS_create_hash(100, GB_IGNORE_CASE);
1406
1407            memfile.erase();
1408            marked = nt_build_conf_string_rek(used, tree, memfile, &extra_marked_species, 0, &auto_mark, 0, &dummy);
1409            TEST_EXPECT_EQUAL(marked, 0);
1410            TEST_EXPECT_EQUAL(memfile.get_data(), "");
1411            TEST_EXPECT_NULL(extra_marked_species);
1412
1413            TEST_EXPECT_EQUAL(GBS_hash_elements(used), 0);
1414            GBS_free_hash(used);
1415        }
1416
1417        // mark some species:
1418        TEST_EXPECT_NO_ERROR(GBT_restore_marked_species(gb_main, "CloButy2;CloPaste;CorAquat;CurCitre;CloTyro4"));
1419        TEST_EXPECT_EQUAL(GBT_count_marked_species(gb_main), 5);
1420
1421        {
1422            GB_HASH *used = GBS_create_hash(100, GB_IGNORE_CASE);
1423
1424            memfile.erase();
1425            marked = nt_build_conf_string_rek(used, tree, memfile, &extra_marked_species, 0, &auto_mark, 0, &dummy);
1426            TEST_EXPECT_EQUAL(marked, 5); // ------------------------------------- v 'G' indicates the open group
1427            TEST_EXPECT_EQUAL(memfile.get_data(), "\1Gupper\1LCloButy2\1E\1Glower\1Glow1\1LCurCitre\1LCorAquat\1LCloPaste\1E\1Glow2\1LCloTyro4\1E\1E");
1428            TEST_EXPECT_NULL(extra_marked_species);
1429
1430            TEST_EXPECT_EQUAL(GBS_hash_elements(used), 5);
1431            GBS_free_hash(used);
1432        }
1433
1434        // test with closed groups:
1435        {
1436            GB_HASH *used = GBS_create_hash(100, GB_IGNORE_CASE);
1437
1438            TEST_EXPECT(fold_group(tree, "low1"));
1439
1440            memfile.erase();
1441            marked = nt_build_conf_string_rek(used, tree, memfile, &extra_marked_species, 0, &auto_mark, 0, &dummy);
1442            TEST_EXPECT_EQUAL(marked, 5); // ------------------------------------- v 'F' indicates the closed group
1443            TEST_EXPECT_EQUAL(memfile.get_data(), "\1Gupper\1LCloButy2\1E\1Glower\1Flow1\1LCurCitre\1LCorAquat\1LCloPaste\1E\1Glow2\1LCloTyro4\1E\1E");
1444            TEST_EXPECT_NULL(extra_marked_species);
1445
1446            TEST_EXPECT_EQUAL(GBS_hash_elements(used), 5);
1447            GBS_free_hash(used);
1448        }
1449
1450        // test with marking 2 species aside:
1451        {
1452            GB_HASH *used = GBS_create_hash(100, GB_IGNORE_CASE);
1453
1454            memfile.erase();
1455            marked = nt_build_conf_string_rek(used, tree, memfile, &extra_marked_species, 2, &auto_mark, 0, &dummy);
1456            TEST_EXPECT_EQUAL(marked, 11);
1457            TEST_EXPECT_EQUAL(memfile.get_data(), "\1Gupper\1LCloTyro3\1LCloButyr\1LCloButy2\1LCloBifer\1LCloInnoc\1E\1Glower\1Flow1\1LCytAquat\1LCurCitre\1LCorAquat\1LCelBiazo\1LCorGluta\1LCloCarni\1LCloPaste\1E\1Glow2\1Gtwoleafs\1LCloTyrob\1LCloTyro2\1E\1LCloTyro4\1E\1E");
1458
1459            TEST_REJECT_NULL(extra_marked_species);
1460            TEST_EXPECT_EQUAL(GBT_count_marked_species(gb_main), 15); // 5 species were marked before + marked 5*2 neighbors
1461            extra_marked_species->call(unmark_species);
1462            delete extra_marked_species;
1463            TEST_EXPECT_EQUAL(GBT_count_marked_species(gb_main), 5);  // 5 previously marked species
1464
1465            TEST_EXPECT_EQUAL(GBS_hash_elements(used), 15);
1466            GBS_free_hash(used);
1467        }
1468
1469        // test nt_build_conf_marked:
1470        {
1471            GB_HASH *used = GBS_create_hash(100, GB_IGNORE_CASE);
1472
1473            memfile.erase();
1474            nt_build_conf_marked(gb_main, used, memfile);
1475            TEST_EXPECT_EQUAL(memfile.get_data(), "\1FMore Sequences\1LCorAquat\1LCurCitre\1LCloButy2\1LCloPaste\1LCloTyro4\1E");
1476
1477            // exclude 2 species (simulates that they are already in "normal" config)
1478            GBS_write_hash(used, "CurCitre", 1);
1479            GBS_write_hash(used, "CloPaste", 1);
1480            memfile.erase();
1481            nt_build_conf_marked(gb_main, used, memfile);
1482            TEST_EXPECT_EQUAL(memfile.get_data(), "\1FMore Sequences\1LCorAquat\1LCloButy2\1LCloTyro4\1E");
1483
1484            GBS_free_hash(used);
1485        }
1486
1487        // test nt_build_sai_string:
1488        {
1489            GBS_strstruct topfile(500);
1490
1491            memfile.erase();
1492            nt_build_sai_string(gb_main, "HELIX_NR;HELIX;dummy", topfile, memfile);
1493
1494            TEST_EXPECT_EQUAL(topfile.get_data(), "\1SHELIX_NR\1SHELIX");
1495            TEST_EXPECT_EQUAL(memfile.get_data(), "\1GSAI-Maingroup\1FSAI:SAI's\1SPOS_VAR_BY_PARSIMONY\1E\1FSAI:special\1SECOLI\1E\1E");
1496        }
1497
1498        destroy(tree);
1499    }
1500
1501    GB_close(gb_main);
1502}
1503
1504#endif // UNIT_TESTS
1505
1506// --------------------------------------------------------------------------------
1507
1508
Note: See TracBrowser for help on using the repository browser.