source: trunk/SL/SPECSEL/selection_admin.cxx

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