source: branches/species/SL/SPECSEL/selection_admin.cxx

Last change on this file was 19705, checked in by westram, 3 days ago
  • add alias_remote_command
  • use to change a macro id in selection admin, while providing backward compatibility.
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 35.6 KB
Line 
1// ========================================================= //
2//                                                           //
3//   File      : selection_admin.cxx                         //
4//   Purpose   : species selection admin                     //
5//                                                           //
6//   Coded by Ralf Westram (coder@reallysoft.de) in Feb 26   //
7//   http://www.arb-home.de/                                 //
8//                                                           //
9// ========================================================= //
10
11#include "selection_admin.h"
12#include <sel_boxes.hxx>
13
14#include <aw_select.hxx>
15#include <aw_root.hxx>
16#include <aw_awar.hxx>
17#include <aw_awar_defs.hxx>
18#include <aw_msg.hxx>
19
20#include <arb_global_defs.h>
21#include <arb_strarray.h>
22
23#include <ad_config.h>
24#include <arbdbt.h>
25#include <RegExpr.hxx>
26
27#include <TreeNode.h>
28#include <modules.hxx>
29
30using namespace std;
31
32// -----------------------------
33//      class Store_species
34
35class Store_species : virtual Noncopyable {
36    // stores an amount of species:
37    TreeNode *node;
38    Store_species *next;
39public:
40    Store_species(TreeNode *aNode) {
41        node = aNode;
42        next = NULp;
43    }
44    ~Store_species();
45
46    Store_species* add(Store_species *list) {
47        arb_assert(!next);
48        next = list;
49        return this;
50    }
51
52    Store_species* remove() {
53        Store_species *follower = next;
54        next = NULp;
55        return follower;
56    }
57
58    TreeNode *getNode() const { return node; }
59
60    void call(void (*aPizza)(TreeNode*)) const;
61};
62
63Store_species::~Store_species() {
64    delete next;
65}
66
67void Store_species::call(void (*aPizza)(TreeNode*)) const {
68    aPizza(node);
69    if (next) next->call(aPizza);
70}
71
72// -----------------------------
73
74static void unmark_species(TreeNode *node) {
75    arb_assert(node);
76    arb_assert(node->gb_node);
77    arb_assert(GB_read_flag(node->gb_node)!=0);
78    GB_write_flag(node->gb_node, 0);
79}
80
81static void mark_species(TreeNode *node, Store_species **extra_marked_species) {
82    arb_assert(node);
83    arb_assert(node->gb_node);
84    arb_assert(GB_read_flag(node->gb_node)==0);
85    GB_write_flag(node->gb_node, 1);
86
87    *extra_marked_species = (new Store_species(node))->add(*extra_marked_species);
88}
89
90static TreeNode *rightmost_leaf(TreeNode *node) {
91    arb_assert(node);
92    while (!node->is_leaf()) {
93        node = node->get_rightson();
94        arb_assert(node);
95    }
96    return node;
97}
98
99static TreeNode *left_neighbour_leaf(TreeNode *node) {
100    if (node) {
101        TreeNode *father = node->get_father();
102        while (father) {
103            if (father->rightson==node) {
104                node = rightmost_leaf(father->get_leftson());
105                arb_assert(node->is_leaf());
106                if (!node->gb_node) { // Zombie
107                    node = left_neighbour_leaf(node);
108                }
109                return node;
110            }
111            node = father;
112            father = node->get_father();
113        }
114    }
115    return NULp;
116}
117
118const char CFG_SEP = 1;
119
120static int nt_build_conf_string_rek(GB_HASH         *used,
121                                    TreeNode        *tree,
122                                    GBS_strstruct&   memfile,
123                                    Store_species  **extra_marked_species,
124                                    int              use_species_aside,
125                                    int             *auto_mark,
126                                    int              marked_at_left,
127                                    int             *marked_at_right)
128{
129    /*! Builds a configuration string from a tree.
130     *
131     * @param used                      all species inserted by this function are stored here
132     * @param tree                      used for group information
133     * @param memfile                   generated configuration string is stored here
134     * @param extra_marked_species      all extra marked species are inserted here
135     * @param use_species_aside         number of species to mark left and right of marked species
136     * @param auto_mark                 number species to extra-mark (if not already marked)
137     * @param marked_at_left            number of species which were marked (looking to left)
138     * @param marked_at_right           number of species which are marked (when returning from recursion)
139     *
140     * @return the number of marked species
141     *
142     * --------------------------------------------------
143     * Format of configuration string : [Part]+ \0
144     *
145     * Part : '\A' ( Group | Species | Sai )
146     *
147     * Group : ( OpenedGroup | ClosedGroup )
148     * OpenedGroup : 'G' GroupDef
149     * ClosedGroup : 'F' GroupDef
150     * GroupDef : 'groupname' [PART]* EndGroup
151     * EndGroup : '\AE'
152     *
153     * SPECIES : 'L' 'speciesname'
154     * SAI : 'S' 'sainame'
155     *
156     * \0 : ASCII 0 (eos)
157     * \A : ASCII 1
158     */
159
160    if (!tree) return 0;
161    if (tree->is_leaf()) {
162        if (!tree->gb_node) {
163            UNCOVERED();
164            *marked_at_right = marked_at_left;
165            return 0;   // Zombie
166        }
167
168        if (!GB_read_flag(tree->gb_node)) { // unmarked species
169            if (*auto_mark) {
170                (*auto_mark)--;
171                mark_species(tree, extra_marked_species);
172            }
173            else {
174                *marked_at_right = 0;
175                return 0;
176            }
177        }
178        else { // marked species
179            if (marked_at_left<use_species_aside) {
180                // on the left side there are not as many marked species as needed!
181
182                arb_assert(marked_at_left>=0);
183
184                TreeNode *leaf_at_left = tree;
185                int       step_over    = marked_at_left+1; // step over myself
186                int       then_mark    = use_species_aside-marked_at_left;
187
188                while (step_over--) { // step over self and over any adjacent, marked species
189                    leaf_at_left = left_neighbour_leaf(leaf_at_left);
190                }
191
192                Store_species *marked_back = NULp;
193                while (leaf_at_left && then_mark--) { // then additionally mark some species
194                    if (GB_read_flag(leaf_at_left->gb_node) == 0) { // if they are not marked yet
195                        mark_species(leaf_at_left, extra_marked_species);
196                        marked_back = (new Store_species(leaf_at_left))->add(marked_back);
197                    }
198                    leaf_at_left = left_neighbour_leaf(leaf_at_left);
199                }
200
201                while (marked_back) {
202                    memfile.put(CFG_SEP);
203                    memfile.put('L');
204                    memfile.cat(marked_back->getNode()->name);
205                    GBS_write_hash(used, marked_back->getNode()->name, 1);      // Mark species
206
207                    Store_species *rest = marked_back->remove();
208                    delete marked_back;
209                    marked_back = rest;
210                }
211
212                marked_at_left = use_species_aside;
213            }
214            // now use_species_aside species to left are marked!
215            *auto_mark = use_species_aside;
216        }
217
218        memfile.put(CFG_SEP);
219        memfile.put('L');
220        memfile.cat(tree->name);
221        GBS_write_hash(used, tree->name, 1);    // Mark species
222
223        *marked_at_right = marked_at_left+1;
224        return 1;
225    }
226
227    const size_t oldpos = memfile.get_position();
228    if (tree->gb_node && tree->name) {      // but we are a group
229        GBDATA *gb_grouped = GB_entry(tree->gb_node, "grouped");
230        memfile.put(CFG_SEP);
231        if (gb_grouped && GB_read_byte(gb_grouped)) {
232            memfile.put('F');
233        }
234        else {
235            memfile.put('G');
236        }
237
238        memfile.cat(tree->name);
239    }
240
241    int  right_of_leftson;
242    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);
243    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);
244
245    if (tree->gb_node && tree->name) {      // but we are a group
246        memfile.put(CFG_SEP);
247        memfile.put('E');        // Group end indicated by 'E'
248    }
249
250    if (!nspecies) {
251        const size_t newpos = memfile.get_position();
252        memfile.cut_tail(newpos-oldpos); // delete group info
253    }
254    return nspecies;
255}
256
257struct SAI_string_builder {
258    GBS_strstruct&  sai_middle;
259    const char     *last_group_name;
260};
261
262static void nt_build_sai_string_by_hash(const char *key, long /*val*/, void *cd_sai_builder) {
263    SAI_string_builder *sai_builder = (SAI_string_builder*)cd_sai_builder;
264
265    const char *sep = strchr(key, 1);
266    if (sep) {
267        GBS_strstruct& sai_middle      = sai_builder->sai_middle;
268        const char    *last_group_name = sai_builder->last_group_name;
269
270        if (!last_group_name || strncmp(key, last_group_name, sep-key)) { // new group
271            if (last_group_name) {
272                sai_middle.put(CFG_SEP);
273                sai_middle.put('E');             // End of old group
274            }
275            sai_middle.put(CFG_SEP);
276            sai_middle.cat("FSAI:");
277            sai_middle.ncat(key, sep-key);
278            sai_builder->last_group_name = key;
279        }
280        sai_middle.put(CFG_SEP);
281        sai_middle.put('S');
282        sai_middle.cat(sep+1);
283    }
284}
285
286static void nt_build_sai_string(GBDATA *gb_main, const char *topAreaSaiList, GBS_strstruct& topfile, GBS_strstruct& middlefile) {
287    // collect all Sais,
288    // place some SAI in top area (those listed in 'toparea_SAIs'. SAI-groups will be ignored here)
289    // rest of SAI goes into middle area (SAI-groups respected here)
290
291    GBDATA *gb_sai_data = GBT_get_SAI_data(gb_main);
292    if (gb_sai_data) {
293        GB_HASH *hash = GBS_create_hash(GB_number_of_subentries(gb_sai_data), GB_IGNORE_CASE);
294
295        ConstStrArray topAreaSai;
296        GBT_split_string(topAreaSai, topAreaSaiList, ",;: \t", SPLIT_DROPEMPTY);
297
298        for (GBDATA *gb_sai = GBT_first_SAI_rel_SAI_data(gb_sai_data); gb_sai; gb_sai = GBT_next_SAI(gb_sai)) {
299            GBDATA *gb_name = GB_search(gb_sai, "name", GB_FIND);
300            if (gb_name) {
301                char *name = GB_read_string(gb_name);
302
303                bool wantedInTop = false;
304                for (unsigned s = 0; !wantedInTop && s<topAreaSai.size(); ++s) {
305                    wantedInTop = strcmp(name, topAreaSai[s]) == 0;
306                }
307
308                if (!wantedInTop) {
309                    GBDATA *gb_gn = GB_search(gb_sai, "sai_group", GB_FIND);
310                    char   *gn;
311
312                    if (gb_gn)  gn = GB_read_string(gb_gn);
313                    else        gn = ARB_strdup("SAI's");
314
315                    char *cn = new char[strlen(gn) + strlen(name) + 2];
316                    sprintf(cn, "%s%c%s", gn, 1, name);
317                    GBS_write_hash(hash, cn, 1);
318                    delete [] cn;
319                    free(gn);
320                }
321                free(name);
322            }
323        }
324
325        // add top area SAIs in defined order:
326        for (unsigned s = 0; s<topAreaSai.size(); ++s) {
327            GBDATA *gb_sai = GBT_find_SAI_rel_SAI_data(gb_sai_data, topAreaSai[s]);
328            if (gb_sai) {
329                topfile.put(CFG_SEP);
330                topfile.put('S');
331                topfile.cat(topAreaSai[s]);
332            }
333        }
334
335        // open surrounding SAI-group:
336        middlefile.put(CFG_SEP);
337        middlefile.cat("GSAI-Maingroup");
338
339        SAI_string_builder sai_builder = { middlefile, NULp };
340        GBS_hash_do_const_sorted_loop(hash, nt_build_sai_string_by_hash, GBS_HCF_sortedByKey, &sai_builder);
341        if (sai_builder.last_group_name) {
342            middlefile.put(CFG_SEP);
343            middlefile.put('E');             // End of old group
344        }
345
346        // close surrounding SAI-group:
347        middlefile.put(CFG_SEP);
348        middlefile.put('E');
349
350        GBS_free_hash(hash);
351    }
352}
353
354static void nt_build_conf_marked(GBDATA *gb_main, GB_HASH *used, GBS_strstruct& file) {
355    file.put(CFG_SEP);
356    file.cat("FMore Sequences");
357
358    for (GBDATA *gb_species = GBT_first_marked_species(gb_main);
359         gb_species;
360         gb_species = GBT_next_marked_species(gb_species))
361    {
362        char *name = GBT_read_string(gb_species, "name");
363        if (!GBS_read_hash(used, name)) {
364            file.put(CFG_SEP);
365            file.put('L');
366            file.cat(name);
367        }
368        free(name);
369    }
370
371    file.put(CFG_SEP);
372    file.put('E');   // Group end indicated by 'E'
373}
374
375void extract_species_selection(GBDATA *gb_main, const char *selectionName, SelectionExtractType ext_type) {
376    GB_transaction  ta(gb_main);
377    AW_root        *aw_root = AW_root::SINGLETON;
378
379    if (strcmp(selectionName, NO_CONFIG_SELECTED) == 0) {
380        aw_message("Please select a configuration");
381    }
382    else {
383        GB_ERROR   error = NULp;
384        GBT_config cfg(gb_main, selectionName, error);
385
386        if (!error) {
387            size_t  unknown_species = 0;
388            bool    refresh         = false;
389
390            GB_HASH *was_marked = NULp; // only used for SELECTION_COMBINE
391
392            switch (ext_type) {
393                case SELECTION_EXTRACT: // unmark all
394                    GBT_mark_all(gb_main, 0);
395                    refresh = true;
396                    break;
397
398                case SELECTION_COMBINE: // store all marked species in hash and unmark them
399                    was_marked = GBT_create_marked_species_hash(gb_main);
400                    GBT_mark_all(gb_main, 0);
401                    refresh    = GBS_hash_elements(was_marked);
402                    break;
403
404                default:
405                    break;
406            }
407
408            for (int area = 0; area<=1 && !error; ++area) {
409                GBT_config_parser cparser(cfg, area);
410
411                while (1) {
412                    const GBT_config_item& citem = cparser.nextItem(error);
413                    if (error || citem.type == CI_END_OF_CONFIG) break;
414
415                    if (citem.type == CI_SPECIES) {
416                        GBDATA *gb_species = GBT_find_species(gb_main, citem.name);
417
418                        if (gb_species) {
419                            int oldmark = GB_read_flag(gb_species);
420                            int newmark = oldmark;
421                            switch (ext_type) {
422                                case SELECTION_EXTRACT:
423                                case SELECTION_MARK:     newmark = 1; break;
424                                case SELECTION_UNMARK:   newmark = 0; break;
425                                case SELECTION_INVERT:   newmark = !oldmark; break;
426                                case SELECTION_COMBINE: {
427                                    arb_assert(!oldmark); // should have been unmarked above
428                                    newmark = GBS_read_hash(was_marked, citem.name); // mark if was_marked
429                                    break;
430                                }
431                                default: arb_assert(0); break;
432                            }
433                            if (newmark != oldmark) {
434                                GB_write_flag(gb_species, newmark);
435                                refresh = true;
436                            }
437                        }
438                        else {
439                            unknown_species++;
440                        }
441                    }
442                }
443            }
444
445            if (was_marked) GBS_free_hash(was_marked);
446            if (unknown_species>0 && !error) error = GBS_global_string("configuration '%s' contains %zu unknown species", selectionName, unknown_species);
447            if (refresh) aw_root->awar(AWAR_TREE_REFRESH)->touch();
448        }
449        aw_message_if(error);
450    }
451}
452
453static void nt_extract_configuration(UNFIXED, const SelectionAdmin *selection, SelectionExtractType ext_type) {
454    GBDATA  *gb_main = selection->get_gb_main();
455    AW_root *aw_root = AW_root::SINGLETON;
456
457    char *selectionName = aw_root->awar(selection->get_selection_awarname())->read_string();
458    extract_species_selection(gb_main, selectionName, ext_type);
459    free(selectionName);
460}
461
462static void nt_delete_configuration(AW_window *aww, AW_DB_selection *dbsel, const SelectionAdmin *selection) {
463    GBDATA         *gb_main = selection->get_gb_main();
464    GB_transaction  ta(gb_main);
465
466    AW_awar *awar_selected    = aww->get_root()->awar(selection->get_selection_awarname());
467    char    *name             = awar_selected->read_string();
468    GBDATA  *gb_configuration = GBT_find_configuration(gb_main, name);
469
470    if (gb_configuration) {
471        dbsel->get_sellist()->move_selection(1);
472
473        GB_ERROR error = GB_delete(gb_configuration);
474        error          = ta.close(error);
475        if (error) {
476            aw_message(error);
477        }
478        else {
479            selection->speciesSelection_deleted_cb(name);
480        }
481    }
482    free(name);
483}
484
485GB_ERROR create_species_selection(const SelectionAdmin& selection, const char *conf_name, int use_species_aside, SelectionCreation creation) {
486    GB_ERROR error = NULp;
487
488    if (!conf_name || !conf_name[0]) error = "no config name given";
489    else {
490        if (use_species_aside==-1) {
491            static int last_used_species_aside = 3;
492            {
493                const char *val                    = GBS_global_string("%i", last_used_species_aside);
494                char       *use_species            = aw_input("How many extra species to view aside marked:", val);
495                if (use_species) use_species_aside = atoi(use_species);
496                free(use_species);
497            }
498
499            if (use_species_aside<1) error = "illegal number of 'species aside'";
500            else last_used_species_aside = use_species_aside; // remember for next time
501        }
502
503        if (!error) {
504            AW_root  *awr     = AW_root::SINGLETON;
505            GBDATA   *gb_main = selection.get_gb_main();
506            TreeNode *tree    = selection.get_tree_root(); // nullable
507
508            GB_transaction ta(gb_main);  // open close transaction
509
510            GBT_config newcfg;
511            {
512                GB_HASH       *used = GBS_create_hash(GBT_get_species_count(gb_main), GB_MIND_CASE);
513                GBS_strstruct  topfile(1000);
514                GBS_strstruct  midfile(10000);
515                {
516                    GBS_strstruct middlefile(10000);
517                    nt_build_sai_string(gb_main, selection.get_toparea_SAIs(), topfile, midfile);
518
519                    if (use_species_aside) {
520                        Store_species *extra_marked_species = NULp;
521                        int            auto_mark            = 0;
522                        int            marked_at_right;
523
524                        nt_build_conf_string_rek(used, tree, middlefile, &extra_marked_species, use_species_aside, &auto_mark, use_species_aside, &marked_at_right);
525                        if (extra_marked_species) {
526                            extra_marked_species->call(unmark_species);
527                            delete extra_marked_species;
528                        }
529                    }
530                    else {
531                        int dummy_1=0, dummy_2;
532                        nt_build_conf_string_rek(used, tree, middlefile, NULp, 0, &dummy_1, 0, &dummy_2);
533                    }
534                    nt_build_conf_marked(gb_main, used, midfile);
535                    midfile.ncat(middlefile.get_data(), middlefile.get_position());
536                }
537
538                newcfg.set_definition(GBT_config::TOP_AREA,    topfile.release());
539                newcfg.set_definition(GBT_config::MIDDLE_AREA, midfile.release());
540
541                GBS_free_hash(used);
542            }
543
544            GBT_config previous(gb_main, conf_name, error);
545            error = NULp; // ignore
546
547            const char *prevComment         = NULp; // old or fixed comment
548            const char *comment             = NULp;
549            bool        warnIfSavingDefault = true;
550            switch (creation) {
551                case BY_CALLING_THE_EDITOR: { // always saves DEFAULT_CONFIGURATION!
552                    prevComment         = "This configuration will be OVERWRITTEN each time\nARB_EDIT4 is started w/o specifying a config!\n---";
553                    comment             = "created for ARB_EDIT4";
554                    warnIfSavingDefault = false;
555                    break;
556                }
557                case FROM_MANAGER: {
558                    if (previous.exists()) {
559                        prevComment = previous.get_comment();
560                        comment     = "updated manually";
561                    }
562                    else {
563                        prevComment = awr->awar(selection.get_selectionComment_awarname())->read_char_pntr();
564                        comment     = "created manually";
565                    }
566                    break;
567                }
568                case FROM_IMPORTER: {
569                    arb_assert(!previous.exists());
570                    comment = "created by importer";
571                    break;
572                }
573            }
574
575            if (prevComment && !prevComment[0]) prevComment = NULp; // handle empty comment like "no comment"
576
577            arb_assert(implicated(prevComment, comment));
578            if (comment) {
579                // annotate comment with treename
580                if (tree) {
581                    const char *treename = selection.get_name_of_tree();
582                    comment = GBS_global_string("%s (tree=%s)", comment, treename);
583                }
584                else {
585                    comment = GBS_global_string("%s (no tree)", comment);
586                }
587                char *dated = GBS_log_action_to(prevComment, comment, true);
588                newcfg.set_comment(dated);
589                free(dated);
590            }
591
592            error = newcfg.save(gb_main, conf_name, warnIfSavingDefault);
593            awr->awar(selection.get_selection_awarname())->touch(); // refreshes comment field
594        }
595    }
596
597    return error;
598}
599
600static void nt_store_configuration(AW_window*, const SelectionAdmin *selection) {
601    const char *cfgName = AW_root::SINGLETON->awar(selection->get_selection_awarname())->read_char_pntr();
602    GB_ERROR    err     = create_species_selection(*selection, cfgName, 0, FROM_MANAGER);
603    aw_message_if(err);
604}
605
606static void nt_rename_configuration(AW_window *aww, const SelectionAdmin *selection) {
607    AW_awar *awar_curr_cfg = aww->get_root()->awar(selection->get_selection_awarname());
608
609    char *old_name = awar_curr_cfg->read_string();
610    char *new_name = aw_input("Rename selection", "Enter the new name of the selection", old_name);
611
612    if (new_name) {
613        GB_ERROR  err     = NULp;
614        GBDATA   *gb_main = selection->get_gb_main();
615
616        {
617            GB_transaction ta(gb_main);
618
619            GBDATA *gb_existing_cfg  = GBT_find_configuration(gb_main, new_name);
620            if (gb_existing_cfg) err = GBS_global_string("There is already a selection named '%s'", new_name);
621            else {
622                GBDATA *gb_old_cfg = GBT_find_configuration(gb_main, old_name);
623                if (gb_old_cfg) {
624                    GBDATA *gb_name = GB_entry(gb_old_cfg, "name");
625                    if (gb_name) {
626                        err = GB_write_string(gb_name, new_name);
627                        if (!err) awar_curr_cfg->write_string(new_name);
628                    }
629                    else err = "Selection has no name";
630                }
631                else err = "Can't find that selection";
632            }
633            err = ta.close(err);
634        }
635
636        if (err) {
637            aw_message(err);
638        }
639        else {
640            arb_assert(GB_get_transaction_level(gb_main) == 0); // otherwise callback below behaves wrong
641            selection->speciesSelection_renamed_cb(old_name, new_name);
642        }
643        free(new_name);
644    }
645    free(old_name);
646}
647
648#pragma GCC diagnostic push
649#if (GCC_VERSION_CODE<700)
650#pragma GCC diagnostic ignored "-Wstrict-overflow" // gcc 6.x produces a bogus overflow warning (gcc 7.x is smart enough)
651#endif
652
653static GB_ERROR swap_configs(GBDATA *gb_main, StrArray& config, int i1, int i2) {
654    GB_ERROR error = NULp;
655
656    if (i1>i2) swap(i1, i2); // otherwise overwrite below does not work
657    arb_assert(i1<i2 && i1>=0 && i2<int(config.size()));
658
659    GBT_config c1(gb_main, config[i1], error);
660    if (!error) {
661        GBT_config c2(gb_main, config[i2], error);
662        if (!error) error = c1.saveAsOver(gb_main, config[i1], config[i2], false);
663        if (!error) error = c2.saveAsOver(gb_main, config[i2], config[i1], false);
664        if (!error) config.swap(i1, i2);
665    }
666    return error;
667}
668
669#pragma GCC diagnostic pop
670
671static void reorder_configs_cb(AW_window *aww, awt_reorder_mode mode, AW_DB_selection *sel) {
672    AW_root           *awr         = aww->get_root();
673    AW_selection_list *sellist     = sel->get_sellist();
674    AW_awar           *awar_config = awr->awar(sellist->get_awar_name());
675    const char        *selected    = awar_config->read_char_pntr();
676
677    if (selected && selected[0]) {
678        int source_idx = sellist->get_index_of(AW_scalar(selected));
679        int target_idx = -1;
680        switch (mode) {
681            case ARM_TOP:    target_idx = 0;            break;
682            case ARM_UP:     target_idx = source_idx-1; break;
683            case ARM_DOWN:   target_idx = source_idx+1; break;
684            case ARM_BOTTOM: target_idx = -1;           break;
685        }
686
687        int entries = sellist->size();
688        target_idx  = (target_idx+entries)%entries;
689
690        {
691            GBDATA         *gb_main = sel->get_gb_main();
692            GB_transaction  ta(gb_main);
693
694            StrArray config;
695            sellist->to_array(config, true);
696
697            GB_ERROR error = NULp;
698            if (source_idx<target_idx) {
699                for (int i = source_idx+1; i<=target_idx; ++i) {
700                    swap_configs(gb_main, config, i-1, i);
701                }
702            }
703            else if (source_idx>target_idx) {
704                for (int i = source_idx-1; i>=target_idx; --i) {
705                    swap_configs(gb_main, config, i+1, i);
706                }
707            }
708
709            error = ta.close(error);
710            aw_message_if(error);
711        }
712        awar_config->touch();
713    }
714}
715
716static void clear_comment_cb(AW_window *aww, const SelectionAdmin *selection) {
717    AW_awar *awar_comment = aww->get_root()->awar(selection->get_selectionComment_awarname());
718    char    *comment      = awar_comment->read_string();
719
720    ConstStrArray line;
721    GBT_splitNdestroy_string(line, comment, '\n');
722
723    bool    removedDatedLines = false;
724    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)
725    for (int i = line.size()-1; i >= 0; --i) {
726        const RegMatch *match = datedLine.match(line[i]);
727        arb_assert(implicated(!match, !datedLine.has_failed())); // assert RegExpr compiles
728        if (match && match->didMatch()) {
729            line.safe_remove(i);
730            removedDatedLines = true;
731        }
732    }
733
734    if (!removedDatedLines) line.clear(); // erase all
735
736    comment = GBT_join_strings(line, '\n');
737    awar_comment->write_string(comment);
738}
739
740AW_window *create_species_selection_window(AW_root *root, const SelectionAdmin *selection) {
741    AW_window_simple *aws = new AW_window_simple;
742
743    aws->init(root, GBS_global_string("SPECIES_SELECTIONS_%s", selection->get_macro_suffix()), "Species Selections");
744    aws->load_xfig("nt_selection.fig");
745
746    aws->at("close");
747    aws->callback(AW_POPDOWN);
748    aws->create_button("CLOSE", "CLOSE", "C");
749
750    aws->at("help");
751    aws->callback(makeHelpCallback("species_configs.hlp"));
752    aws->create_button("HELP", "HELP", "H");
753
754    aws->at("name");
755    aws->create_input_field(selection->get_selection_awarname());
756
757    aws->at("comment");
758    aws->create_text_field(selection->get_selectionComment_awarname());
759
760    aws->at("clr");
761    aws->callback(makeWindowCallback(clear_comment_cb, selection));
762    aws->create_autosize_button("CLEAR", "Clear", "l");
763
764    aws->at("list");
765    AW_DB_selection *dbsel = awt_create_CONFIG_selection_list(selection->get_gb_main(), aws, selection->get_selection_awarname());
766
767    aws->button_length(8);
768
769    aws->at("store");
770    aws->callback(makeWindowCallback(nt_store_configuration, selection));
771    {
772        const char *new_id = "STORE";
773        aws->create_button(new_id, "STORE", "S");
774
775        // provide intermediate backward compatibility for old, unwanted ID:
776        const char *old_id = GBS_global_string("STORE_%s", selection->get_macro_suffix());
777        aws->alias_remote_command(old_id, new_id);
778    }
779
780    aws->at("extract");
781    aws->callback(makeWindowCallback(nt_extract_configuration, selection, SELECTION_EXTRACT));
782    aws->create_button("EXTRACT", "EXTRACT", "E");
783
784    aws->at("mark");
785    aws->callback(makeWindowCallback(nt_extract_configuration, selection, SELECTION_MARK));
786    aws->create_button("MARK", "MARK", "M");
787
788    aws->at("unmark");
789    aws->callback(makeWindowCallback(nt_extract_configuration, selection, SELECTION_UNMARK));
790    aws->create_button("UNMARK", "UNMARK", "U");
791
792    aws->at("invert");
793    aws->callback(makeWindowCallback(nt_extract_configuration, selection, SELECTION_INVERT));
794    aws->create_button("INVERT", "INVERT", "I");
795
796    aws->at("combine");
797    aws->callback(makeWindowCallback(nt_extract_configuration, selection, SELECTION_COMBINE));
798    aws->create_button("COMBINE", "COMBINE", "C");
799
800    aws->at("delete");
801    aws->callback(makeWindowCallback(nt_delete_configuration, dbsel, selection));
802    aws->create_button("DELETE", "DELETE", "D");
803
804    aws->at("rename");
805    aws->callback(makeWindowCallback(nt_rename_configuration, selection));
806    aws->create_button("RENAME", "RENAME", "R");
807
808    aws->button_length(0);
809    aws->at("sort");
810    awt_create_order_buttons(aws, reorder_configs_cb, dbsel);
811
812    return aws;
813}
814
815// --------------------------------------------------------------------------------
816
817#ifdef UNIT_TESTS
818#ifndef TEST_UNIT_H
819#include <test_unit.h>
820#endif
821
822static bool fold_group(TreeNode *tree, const char *groupName) {
823    if (!tree->is_leaf()) {
824        if (tree->is_normal_group()) {
825            arb_assert(tree->name);
826            fprintf(stderr, "group='%s' groupName='%s'\n", tree->name, groupName);
827            if (strcmp(tree->name, groupName) == 0) {
828                arb_assert(tree->gb_node);
829                GBDATA *gb_grouped = GB_entry(tree->gb_node, "grouped");
830                if (!gb_grouped) {
831                    gb_grouped = GB_create(tree->gb_node, "grouped", GB_BYTE);
832                }
833                TEST_REJECT_NULL(gb_grouped);
834                if (GB_read_byte(gb_grouped) == 0) {
835                    TEST_EXPECT_NO_ERROR(GB_write_byte(gb_grouped, 1));
836                    return true;
837                }
838            }
839        }
840        return
841            fold_group(tree->get_leftson(), groupName) ||
842            fold_group(tree->get_rightson(), groupName);
843    }
844    return false;
845}
846
847void TEST_build_conf_string() {
848    GB_shell shell;
849    GBDATA   *gb_main = GB_open("TEST_trees.arb", "r");
850
851    {
852        GB_transaction ta(gb_main);
853
854        TreeNode *tree = GBT_read_tree(gb_main, "tree_groups", new SimpleRoot);
855
856        GBS_strstruct  memfile(500);
857        Store_species *extra_marked_species = NULp;
858
859        TEST_REJECT_NULL(tree);
860        TEST_EXPECT_NO_ERROR(GBT_link_tree(tree, gb_main, false, NULp, NULp));
861
862        int auto_mark = 0;
863        int dummy;
864        int marked;
865
866        // test result with no species marked:
867        GBT_mark_all(gb_main, 0);
868        TEST_EXPECT_ZERO(GBT_count_marked_species(gb_main));
869        {
870            GB_HASH *used = GBS_create_hash(100, GB_IGNORE_CASE);
871
872            memfile.erase();
873            marked = nt_build_conf_string_rek(used, tree, memfile, &extra_marked_species, 0, &auto_mark, 0, &dummy);
874            TEST_EXPECT_EQUAL(marked, 0);
875            TEST_EXPECT_EQUAL(memfile.get_data(), "");
876            TEST_EXPECT_NULL(extra_marked_species);
877
878            TEST_EXPECT_EQUAL(GBS_hash_elements(used), 0);
879            GBS_free_hash(used);
880        }
881
882        // mark some species:
883        TEST_EXPECT_NO_ERROR(GBT_restore_marked_species(gb_main, "CloButy2;CloPaste;CorAquat;CurCitre;CloTyro4"));
884        TEST_EXPECT_EQUAL(GBT_count_marked_species(gb_main), 5);
885
886        {
887            GB_HASH *used = GBS_create_hash(100, GB_IGNORE_CASE);
888
889            memfile.erase();
890            marked = nt_build_conf_string_rek(used, tree, memfile, &extra_marked_species, 0, &auto_mark, 0, &dummy);
891            TEST_EXPECT_EQUAL(marked, 5); // ------------------------------------- v 'G' indicates the open group
892            TEST_EXPECT_EQUAL(memfile.get_data(), "\1Gupper\1LCloButy2\1E\1Glower\1Glow1\1LCurCitre\1LCorAquat\1LCloPaste\1E\1Glow2\1LCloTyro4\1E\1E");
893            TEST_EXPECT_NULL(extra_marked_species);
894
895            TEST_EXPECT_EQUAL(GBS_hash_elements(used), 5);
896            GBS_free_hash(used);
897        }
898
899        // test with closed groups:
900        {
901            GB_HASH *used = GBS_create_hash(100, GB_IGNORE_CASE);
902
903            TEST_EXPECT(fold_group(tree, "low1"));
904
905            memfile.erase();
906            marked = nt_build_conf_string_rek(used, tree, memfile, &extra_marked_species, 0, &auto_mark, 0, &dummy);
907            TEST_EXPECT_EQUAL(marked, 5); // ------------------------------------- v 'F' indicates the closed group
908            TEST_EXPECT_EQUAL(memfile.get_data(), "\1Gupper\1LCloButy2\1E\1Glower\1Flow1\1LCurCitre\1LCorAquat\1LCloPaste\1E\1Glow2\1LCloTyro4\1E\1E");
909            TEST_EXPECT_NULL(extra_marked_species);
910
911            TEST_EXPECT_EQUAL(GBS_hash_elements(used), 5);
912            GBS_free_hash(used);
913        }
914
915        // test with marking 2 species aside:
916        {
917            GB_HASH *used = GBS_create_hash(100, GB_IGNORE_CASE);
918
919            memfile.erase();
920            marked = nt_build_conf_string_rek(used, tree, memfile, &extra_marked_species, 2, &auto_mark, 0, &dummy);
921            TEST_EXPECT_EQUAL(marked, 11);
922            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");
923
924            TEST_REJECT_NULL(extra_marked_species);
925            TEST_EXPECT_EQUAL(GBT_count_marked_species(gb_main), 15); // 5 species were marked before + marked 5*2 neighbors
926            extra_marked_species->call(unmark_species);
927            delete extra_marked_species;
928            TEST_EXPECT_EQUAL(GBT_count_marked_species(gb_main), 5);  // 5 previously marked species
929
930            TEST_EXPECT_EQUAL(GBS_hash_elements(used), 15);
931            GBS_free_hash(used);
932        }
933
934        // test nt_build_conf_marked:
935        {
936            GB_HASH *used = GBS_create_hash(100, GB_IGNORE_CASE);
937
938            memfile.erase();
939            nt_build_conf_marked(gb_main, used, memfile);
940            TEST_EXPECT_EQUAL(memfile.get_data(), "\1FMore Sequences\1LCorAquat\1LCurCitre\1LCloButy2\1LCloPaste\1LCloTyro4\1E");
941
942            // exclude 2 species (simulates that they are already in "normal" config)
943            GBS_write_hash(used, "CurCitre", 1);
944            GBS_write_hash(used, "CloPaste", 1);
945            memfile.erase();
946            nt_build_conf_marked(gb_main, used, memfile);
947            TEST_EXPECT_EQUAL(memfile.get_data(), "\1FMore Sequences\1LCorAquat\1LCloButy2\1LCloTyro4\1E");
948
949            GBS_free_hash(used);
950        }
951
952        // test nt_build_sai_string:
953        {
954            GBS_strstruct topfile(500);
955
956            memfile.erase();
957            nt_build_sai_string(gb_main, "HELIX_NR;HELIX;dummy", topfile, memfile);
958
959            TEST_EXPECT_EQUAL(topfile.get_data(), "\1SHELIX_NR\1SHELIX");
960            TEST_EXPECT_EQUAL(memfile.get_data(), "\1GSAI-Maingroup\1FSAI:SAI's\1SPOS_VAR_BY_PARSIMONY\1E\1FSAI:special\1SECOLI\1E\1E");
961        }
962
963        destroy(tree);
964    }
965
966    GB_close(gb_main);
967}
968
969#endif // UNIT_TESTS
970
971// --------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.