source: branches/stable/SL/TREEDISP/TreeCallbacks.cxx

Last change on this file was 18643, checked in by westram, 4 years ago
  • allow to 'edit bootstraps'
    • document example.
    • when a replacement results in an empty string ⇒ remove remark.
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 34.5 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : TreeCallbacks.cxx                                 //
4//   Purpose   :                                                   //
5//                                                                 //
6//   Institute of Microbiology (Technical University Munich)       //
7//   http://www.arb-home.de/                                       //
8//                                                                 //
9// =============================================================== //
10
11#include "TreeCallbacks.hxx"
12
13#include <aw_color_groups.hxx>
14#include <aw_awars.hxx>
15#include <aw_advice.hxx>
16#include <aw_msg.hxx>
17#include <aw_root.hxx>
18#include <mode_text.h>
19
20#include <cctype>
21#include <gb_aci.h>
22
23using namespace AW;
24
25// AISC_MKPT_PROMOTE:#ifndef TREEDISPLAY_HXX
26// AISC_MKPT_PROMOTE:#include <TreeDisplay.hxx>
27// AISC_MKPT_PROMOTE:#endif
28
29void nt_mode_event(UNFIXED, TREE_canvas *ntw, AWT_COMMAND_MODE mode) {
30    const char *text;
31
32    switch (mode) {
33        case AWT_MODE_ZOOM:  text = MODE_TEXT_STANDARD_ZOOMMODE(); break;
34        case AWT_MODE_EMPTY: text = MODE_TEXT_PLACEHOLDER();       break;
35
36        case AWT_MODE_SELECT: text = MODE_TEXT_1BUTTON("SELECT", "select species or open/close group");                  break;
37        case AWT_MODE_INFO:   text = MODE_TEXT_1BUTTON("INFO",   "click for info");                                      break;
38        case AWT_MODE_WWW:    text = MODE_TEXT_1BUTTON("WEB",    "Launch node dependent URL (see <Properties/WWW...>)"); break;
39
40        case AWT_MODE_SWAP:       text= MODE_TEXT_2BUTTONS("SWAP",         "swap child branches",        "flip whole subtree");          break;
41        case AWT_MODE_MARK:       text= MODE_TEXT_2BUTTONS("MARK",         "mark subtree",               "unmark subtree");              break;
42        case AWT_MODE_GROUP:      text= MODE_TEXT_2BUTTONS("GROUP",        "fold/unfold group",          "create/rename/destroy group"); break;
43        case AWT_MODE_NNI:        text= MODE_TEXT_2BUTTONS("OPTI(NNI)",    "once",                       "repeated");                    break;
44        case AWT_MODE_KERNINGHAN: text= MODE_TEXT_2BUTTONS("OPTI(KL)",     "once",                       "repeated");                    break;
45        case AWT_MODE_OPTIMIZE:   text= MODE_TEXT_2BUTTONS("OPTI(NNI&KL)", "once",                       "repeated");                    break;
46        case AWT_MODE_SETROOT:    text= MODE_TEXT_2BUTTONS("REROOT",       "set root to clicked branch", "search optimal root");         break;
47
48        case AWT_MODE_ROTATE: text = MODE_TEXT_1BUTTON_KEYS("ROTATE", "drag branch to rotate",         KEYINFO_ABORT_AND_RESET); break;
49        case AWT_MODE_SPREAD: text = MODE_TEXT_1BUTTON_KEYS("SPREAD", "drag branch to spread subtree", KEYINFO_ABORT_AND_RESET); break;
50
51        case AWT_MODE_LENGTH:    text = MODE_TEXT_2BUTTONS_KEYS("LENGTH",       "drag branch/ruler", "use discrete lengths", KEYINFO_ABORT_AND_RESET); break;
52        case AWT_MODE_MULTIFURC: text = MODE_TEXT_2BUTTONS_KEYS("MULTIFURC",    "drag branch",       "use discrete lengths", KEYINFO_ABORT_AND_RESET); break;
53        case AWT_MODE_LINE:      text = MODE_TEXT_2BUTTONS_KEYS("LINE",         "drag branch/ruler", "whole subtree",        KEYINFO_ABORT_AND_RESET); break;
54        case AWT_MODE_MOVE:      text = MODE_TEXT_2BUTTONS_KEYS("MOVE",         "drag branch/ruler", "move groupinfo only",  KEYINFO_ABORT);           break;
55        case AWT_MODE_LZOOM:     text = MODE_TEXT_2BUTTONS_KEYS("LOGICAL ZOOM", "show only subtree", "go up one step",       KEYINFO_RESET);           break;
56
57        default: text = no_mode_text_defined(); break;
58    }
59
60    td_assert(strlen(text) < AWAR_FOOTER_MAX_LEN); // text too long!
61
62    ntw->awr->awar(AWAR_FOOTER)->write_string(text);
63    ntw->set_mode(mode);
64}
65
66// ---------------------------------------
67//      Basic mark/unmark callbacks :
68
69static void count_mark_all_cb(UNFIXED, TREE_canvas *ntw) {
70    GB_push_transaction(ntw->gb_main);
71
72    GBDATA *gb_species_data = GBT_get_species_data(ntw->gb_main);
73    long    count           = GB_number_of_marked_subentries(gb_species_data);
74
75    GB_pop_transaction(ntw->gb_main);
76
77    char buf[256];
78    switch (count) {
79        case 0: strcpy(buf, "There are NO marked species"); break;
80        case 1: strcpy(buf, "There is 1 marked species"); break;
81        default: sprintf(buf, "There are %li marked species", count); break;
82    }
83    strcat(buf, ". (The number of species is displayed in the top area as well)");
84    aw_message(buf);
85}
86
87static bool species_has_alignment(GBDATA *gb_species, void *cd_use) {
88    return GBT_find_sequence(gb_species, (const char*)cd_use);
89}
90
91static bool sequence_is_partial(GBDATA *gb_species, void *cd_partial) {
92    long wanted  = (long)cd_partial;
93    td_assert(wanted == 0 || wanted == 1);
94    bool partial = GBT_is_partial(gb_species, 0, false);
95
96    return partial == wanted;
97}
98
99#define MARK_MODE_LOWER_BITS (1|2)
100#define MARK_MODE_UPPER_BITS (4|8|16)
101
102void NT_mark_all_cb(UNFIXED, TREE_canvas *ntw, int mark_mode) {
103    // Bits 0 and 1 of mark_mode:
104    //
105    // mark_mode&3  == 0 -> unmark
106    // mark_mode&3  == 1 -> mark
107    // mark_mode&3  == 2 -> toggle mark
108    //
109    // Bits 2 .. 4 of mark_mode:
110    //
111    // mark_mode&12 == 4 -> affect only full sequences
112    // mark_mode&12 == 8 -> affect only partial sequences
113    // mark_mode&12 == 16 -> affect only species with data in current alignment
114    // else -> affect all sequences
115
116    AWT_auto_refresh allowed_on(ntw);
117    GB_transaction   ta(ntw->gb_main);
118
119    switch (mark_mode&MARK_MODE_UPPER_BITS) {
120        case 0:                 // all sequences
121            GBT_mark_all(ntw->gb_main, mark_mode&MARK_MODE_LOWER_BITS);
122            break;
123        case 4:                 // full sequences only
124            GBT_mark_all_that(ntw->gb_main, mark_mode&MARK_MODE_LOWER_BITS, sequence_is_partial, (void*)NULp);
125            break;
126        case 8:                 // partial sequences only
127            GBT_mark_all_that(ntw->gb_main, mark_mode&MARK_MODE_LOWER_BITS, sequence_is_partial, (void*)1);
128            break;
129        case 16: {               // species with data in alignment only
130            char *ali = GBT_get_default_alignment(ntw->gb_main);
131            if (ali) GBT_mark_all_that(ntw->gb_main, mark_mode&MARK_MODE_LOWER_BITS, species_has_alignment, (void*)ali);
132            free(ali);
133            break;
134        }
135        default:
136            td_assert(0); // illegal mode
137            break;
138    }
139
140    ntw->request_structure_update(); // includes refresh etc
141}
142
143static void mark_tree_cb(UNFIXED, TREE_canvas *ntw, int mark_mode) {
144    GB_transaction    ta(ntw->gb_main);
145    AWT_auto_refresh  allowed_on(ntw);
146    AWT_graphic_tree *gtree     = AWT_TREE(ntw);
147    AP_tree          *tree_root = gtree->get_root_node();
148
149    switch (mark_mode&MARK_MODE_UPPER_BITS) {
150        case 0:                 // all sequences
151            gtree->mark_species_in_tree(tree_root, mark_mode&MARK_MODE_LOWER_BITS);
152            break;
153        case 4:                 // full sequences only
154            gtree->mark_species_in_tree_that(tree_root, mark_mode&MARK_MODE_LOWER_BITS, sequence_is_partial, (void*)NULp);
155            break;
156        case 8:                 // partial sequences only
157            gtree->mark_species_in_tree_that(tree_root, mark_mode&MARK_MODE_LOWER_BITS, sequence_is_partial, (void*)1);
158            break;
159        case 16: {               // species with data in alignment only
160            char *ali = GBT_get_default_alignment(ntw->gb_main);
161            if (ali) gtree->mark_species_in_tree_that(tree_root, mark_mode&MARK_MODE_LOWER_BITS, species_has_alignment, (void*)ali);
162            free(ali);
163            break;
164        }
165        default:
166            td_assert(0); // illegal mode
167            break;
168    }
169    ntw->request_structure_update();
170}
171
172struct mark_nontree_cb_data {
173    int      mark_mode_upper_bits;
174    char    *ali;               // current alignment (only if mark_mode_upper_bits == 16)
175    GB_HASH *hash;
176};
177
178static bool are_not_in_tree(GBDATA *gb_species, void *cb_data) {
179    struct mark_nontree_cb_data *data = (mark_nontree_cb_data*)cb_data;
180    bool mark_me = false;
181
182    if (GBS_read_hash(data->hash, GBT_get_name_or_description(gb_species)) == (long)gb_species) { // species is not in tree!
183        switch (data->mark_mode_upper_bits) {
184            case 0:             // all sequences
185                mark_me = true;
186                break;
187            case 4:             // full sequences only
188                mark_me = sequence_is_partial(gb_species, (void*)NULp);
189                break;
190            case 8:             // partial sequences only
191                mark_me = sequence_is_partial(gb_species, (void*)1);
192                break;
193            case 16:            // species with data in alignment only
194                mark_me = species_has_alignment(gb_species, data->ali);
195                break;
196            default:
197                td_assert(0); // illegal mode
198                break;
199        }
200    }
201
202    return mark_me;
203}
204
205static void mark_nontree_cb(UNFIXED, TREE_canvas *ntw, int mark_mode) {
206    AWT_graphic_tree            *gtree = AWT_TREE(ntw);
207    GB_transaction               ta(ntw->gb_main);
208    struct mark_nontree_cb_data  cd;
209    AWT_auto_refresh             allowed_on(ntw);
210
211    if ((mark_mode&MARK_MODE_LOWER_BITS) == 0) {   // unmark is much faster
212        cd.hash = GBT_create_marked_species_hash(ntw->gb_main); // because it only hashes marked species
213    }
214    else {
215        cd.hash = GBT_create_species_hash(ntw->gb_main); // for mark we have to hash ALL species
216    }
217
218    NT_remove_species_in_tree_from_hash(gtree->get_root_node(), cd.hash);
219
220    cd.mark_mode_upper_bits = mark_mode&MARK_MODE_UPPER_BITS;
221    cd.ali                  = cd.mark_mode_upper_bits == 16 ? GBT_get_default_alignment(ntw->gb_main) : NULp;
222
223    GBT_mark_all_that(ntw->gb_main, mark_mode&MARK_MODE_LOWER_BITS, are_not_in_tree, (void*)&cd);
224
225    free(cd.ali);
226
227    ntw->request_refresh(); // does only affect display of NDS list (if tree shown -> never changes; i.e. no structure update needed)
228}
229
230static char *create_mark_menu_entry(const char *attrib, const char *entry_template) {
231    char *entry = NULp;
232    if (attrib) {
233        bool append = attrib[0] == '-'; // if attrib starts with '-' then append (otherwise prepend)
234        if (append) ++attrib; // skip '-'
235
236        if (append) {
237            char *spaced_attrib = GBS_global_string_copy(" %s", attrib);
238            entry               = GBS_global_string_copy(entry_template, "", spaced_attrib);
239            free(spaced_attrib);
240        }
241        else {
242            char *spaced_attrib = GBS_global_string_copy("%s ", attrib);
243            entry               = GBS_global_string_copy(entry_template, spaced_attrib, "");
244
245            if (islower(entry[0])) entry[0] = toupper(entry[0]); // Caps prepended lowercase 'attrib'
246
247            free(spaced_attrib);
248        }
249    }
250    else {
251        entry = GBS_global_string_copy(entry_template, "", "");
252    }
253    return entry;
254}
255static char *create_mark_menu_id(const char *attrib, const char *id_suffix) {
256    char *id = NULp;
257    if (attrib) {
258        id = GBS_global_string_copy("%s_%s", attrib[0] == '-' ? attrib+1 : attrib, id_suffix);
259    }
260    else {
261        id = strdup(id_suffix);
262    }
263    return id;
264}
265
266static void insert_mark_topic(AW_window_menu_modes *awm, AW_active mask, const char *attrib, const char *id_suffix, const char *entry_template,
267                              const char *hotkey, const char *helpfile, const WindowCallback& wcb)
268{
269    char *entry = create_mark_menu_entry(attrib, entry_template);
270    char *id    = create_mark_menu_id(attrib, id_suffix);
271
272    awm->insert_menu_topic(id, entry, hotkey, helpfile, mask, wcb);
273
274    free(id);
275    free(entry);
276}
277
278static void insert_mark_topics(AW_window_menu_modes *awm, AW_active mask, TREE_canvas *ntw, int affect, const char *attrib) {
279    td_assert(affect == (affect&MARK_MODE_UPPER_BITS)); // only bits 2 .. 4 are allowed
280
281    insert_mark_topic(awm, mask, attrib, "mark_all",    "Mark all %sSpecies%s",            "M", "sp_mark.hlp", makeWindowCallback(NT_mark_all_cb, ntw, 1+affect));
282    insert_mark_topic(awm, mask, attrib, "unmark_all",  "Unmark all %sSpecies%s",          "U", "sp_mark.hlp", makeWindowCallback(NT_mark_all_cb, ntw, 0+affect));
283    insert_mark_topic(awm, mask, attrib, "swap_marked", "Invert marks of all %sSpecies%s", "I", "sp_mark.hlp", makeWindowCallback(NT_mark_all_cb, ntw, 2+affect));
284    awm->sep______________();
285
286    char *label = create_mark_menu_entry(attrib, "%sSpecies%s in Tree");
287
288    awm->insert_sub_menu(label, "T");
289    insert_mark_topic(awm, mask, attrib, "mark_tree",        "Mark %sSpecies%s in Tree",            "M", "sp_mark.hlp", makeWindowCallback(mark_tree_cb, ntw, 1+affect));
290    insert_mark_topic(awm, mask, attrib, "unmark_tree",      "Unmark %sSpecies%s in Tree",          "U", "sp_mark.hlp", makeWindowCallback(mark_tree_cb, ntw, 0+affect));
291    insert_mark_topic(awm, mask, attrib, "swap_marked_tree", "Invert marks of %sSpecies%s in Tree", "I", "sp_mark.hlp", makeWindowCallback(mark_tree_cb, ntw, 2+affect));
292    awm->close_sub_menu();
293
294    freeset(label, create_mark_menu_entry(attrib, "%sSpecies%s NOT in Tree"));
295
296    awm->insert_sub_menu(label, "N");
297    insert_mark_topic(awm, mask, attrib, "mark_nontree",        "Mark %sSpecies%s NOT in Tree",            "M", "sp_mark.hlp", makeWindowCallback(mark_nontree_cb, ntw, 1+affect));
298    insert_mark_topic(awm, mask, attrib, "unmark_nontree",      "Unmark %sSpecies%s NOT in Tree",          "U", "sp_mark.hlp", makeWindowCallback(mark_nontree_cb, ntw, 0+affect));
299    insert_mark_topic(awm, mask, attrib, "swap_marked_nontree", "Invert marks of %sSpecies%s NOT in Tree", "I", "sp_mark.hlp", makeWindowCallback(mark_nontree_cb, ntw, 2+affect));
300    awm->close_sub_menu();
301
302    free(label);
303}
304
305void NT_insert_mark_submenus(AW_window_menu_modes *awm, TREE_canvas *ntw, int insert_as_submenu) {
306    if (insert_as_submenu) {
307        awm->insert_sub_menu("Mark species", "M");
308    }
309
310    {
311        awm->insert_menu_topic("count_marked", "Count Marked Species", "C", "sp_count_mrk.hlp", AWM_ALL, makeWindowCallback(count_mark_all_cb, ntw));
312        awm->sep______________();
313        insert_mark_topics(awm, AWM_ALL, ntw, 0, NULp);
314        awm->sep______________();
315
316        awm->insert_sub_menu("Complete sequences", "o");
317        insert_mark_topics(awm, AWM_EXP, ntw, 4, "complete");
318        awm->close_sub_menu();
319
320        awm->insert_sub_menu("Partial sequences", "P");
321        insert_mark_topics(awm, AWM_EXP, ntw, 8, "partial");
322        awm->close_sub_menu();
323
324        awm->insert_sub_menu("Current Alignment", "A");
325        insert_mark_topics(awm, AWM_EXP, ntw, 16, "-with data");
326        awm->close_sub_menu();
327    }
328
329    if (insert_as_submenu) {
330        awm->close_sub_menu();
331    }
332}
333
334// ----------------------------------------
335//      Automated collapse/expand tree
336
337static void group_and_refold_tree(TREE_canvas *ntw, CollapseMode mode, int color_group) {
338    GB_transaction    ta(ntw->gb_main);
339    AWT_auto_refresh  allowed_on(ntw);
340    AWT_graphic_tree *agt       = AWT_TREE(ntw);
341    AP_tree          *root_node = agt->get_root_node();
342
343    agt->group_tree(root_node, mode, color_group);
344
345    agt->fast_sync_changed_folding(root_node);
346}
347
348static void collapse_all_cb     (UNFIXED, TREE_canvas *ntw) { group_and_refold_tree(ntw, COLLAPSE_ALL,      0); }
349static void collapse_terminal_cb(UNFIXED, TREE_canvas *ntw) { group_and_refold_tree(ntw, COLLAPSE_TERMINAL, 0); }
350static void expand_all_cb       (UNFIXED, TREE_canvas *ntw) { group_and_refold_tree(ntw, EXPAND_ALL,        0); }
351void NT_expand_marked_cb        (UNFIXED, TREE_canvas *ntw) { group_and_refold_tree(ntw, EXPAND_MARKED,     0); }
352static void expand_zombies_cb   (UNFIXED, TREE_canvas *ntw) { group_and_refold_tree(ntw, EXPAND_ZOMBIES,    0); }
353
354static void expand_color_cb(UNFIXED, TREE_canvas *ntw, int colornum) { group_and_refold_tree(ntw, EXPAND_COLOR, colornum); }
355
356static void insert_color_collapse_submenu(AW_window_menu_modes *awm, TREE_canvas *ntree_canvas) {
357    const int MAXLABEL = 31;
358    const int MAXENTRY = AW_COLOR_GROUP_NAME_LEN+10;
359
360    td_assert(ntree_canvas);
361
362    awm->insert_sub_menu("Expand color ...", "o");
363
364    char        label_buf[MAXLABEL+1];
365    char        entry_buf[MAXENTRY+1];
366    char hotkey[]       = "x";
367    const char *hotkeys = "AN1234567890BC"+1;
368
369    for (int i = -1; i <= AW_COLOR_GROUPS; ++i) {
370        hotkey[0]                       = hotkeys[i];
371        if (hotkey[0] == ' ') hotkey[0] = 0;
372
373        if (i) {
374            if (i<0) {
375                strcpy(label_buf, "tree_group_not_any_color");
376                strcpy(entry_buf, "Any color group");
377            }
378            else {
379                sprintf(label_buf, "tree_group_not_color_%i", i);
380
381                char   *color_group_name = AW_get_color_group_name(awm->get_root(), i);
382                size_t  len              = strlen(color_group_name);
383                if (len>AW_COLOR_GROUP_NAME_LEN) {
384                    color_group_name[AW_COLOR_GROUP_NAME_LEN] = 0; // truncate overlong colorgroup names
385                }
386                sprintf(entry_buf, "%s group '%s'", hotkey, color_group_name);
387                free(color_group_name);
388            }
389        }
390        else {
391            strcpy(label_buf, "tree_group_not_no_color");
392            strcpy(entry_buf, "No color group");
393        }
394
395        awm->insert_menu_topic(awm->local_id(label_buf), entry_buf, hotkey, "tree_group.hlp", AWM_ALL, makeWindowCallback(expand_color_cb, ntree_canvas, i));
396    }
397
398    awm->close_sub_menu();
399}
400
401void NT_insert_collapse_submenu(AW_window_menu_modes *awm, TREE_canvas *ntw) {
402    awm->insert_sub_menu("Collapse/expand groups",         "d");
403    {
404        const char *grouphelp = "tree_group.hlp";
405        awm->insert_menu_topic(awm->local_id("tree_group_all"),         "Collapse all",      "C", grouphelp, AWM_ALL, makeWindowCallback(collapse_all_cb,      ntw));
406        awm->insert_menu_topic(awm->local_id("tree_group_term_groups"), "Collapse terminal", "t", grouphelp, AWM_ALL, makeWindowCallback(collapse_terminal_cb, ntw));
407        awm->sep______________();
408        awm->insert_menu_topic(awm->local_id("tree_ungroup_all"),       "Expand all",        "E", grouphelp, AWM_ALL, makeWindowCallback(expand_all_cb,        ntw));
409        awm->insert_menu_topic(awm->local_id("tree_group_not_marked"),  "Expand marked",     "m", grouphelp, AWM_ALL, makeWindowCallback(NT_expand_marked_cb,  ntw));
410        awm->insert_menu_topic(awm->local_id("tree_ungroup_zombies"),   "Expand zombies",    "z", grouphelp, AWM_ALL, makeWindowCallback(expand_zombies_cb,    ntw));
411        awm->sep______________();
412        insert_color_collapse_submenu(awm, ntw);
413    }
414    awm->close_sub_menu();
415}
416
417// ------------------------
418//      tree sorting :
419
420GB_ERROR NT_with_displayed_tree_do(TREE_canvas *ntw, bool (*displayed_tree_cb)(TreeNode *tree, GB_ERROR& error)) {
421    // 'displayed_tree_cb' has to return true if tree was changed and needs to be saved
422
423    GB_transaction   ta(ntw->gb_main);
424    AWT_auto_refresh allowed_on(ntw);
425
426    GB_ERROR error = NULp;
427    if (displayed_tree_cb(AWT_TREE(ntw)->get_root_node(), error)) {
428        ntw->request_save_and_zoom_reset();
429    }
430    return error;
431}
432
433void NT_resort_tree_cb(UNFIXED, TREE_canvas *ntw, TreeOrder order) {
434    GB_transaction   ta(ntw->gb_main);
435    AWT_auto_refresh allowed_on(ntw);
436
437    AWT_TREE(ntw)->reorderTree(order);
438    ntw->request_save_and_zoom_reset();
439}
440
441void NT_reset_lzoom_cb(UNFIXED, TREE_canvas *ntw) {
442    GB_transaction    ta(ntw->gb_main);
443    AWT_auto_refresh  allowed_on(ntw);
444    AWT_graphic_tree *agt = AWT_TREE(ntw);
445
446    agt->set_logical_root_to(agt->get_root_node());
447    ntw->request_zoom_reset();
448}
449
450void NT_reset_pzoom_cb(UNFIXED, TREE_canvas *ntw) {
451    GB_transaction   ta(ntw->gb_main);
452    AWT_auto_refresh allowed_on(ntw);
453    ntw->request_zoom_reset();
454}
455
456void NT_set_tree_style(UNFIXED, TREE_canvas *ntw, AP_tree_display_style style) {
457    {
458        AWT_auto_refresh allowed_on(ntw);
459        AWT_TREE(ntw)->set_tree_style(style, ntw);
460        ntw->request_zoom_reset();
461    }
462    TREE_auto_jump_cb(NULp, ntw, AP_JUMP_REASON_STYLE);
463}
464
465void NT_reinit_treetype(UNFIXED, TREE_canvas *ntw) {
466    NT_set_tree_style(NULp, ntw, AWT_TREE(ntw)->get_tree_style());
467}
468
469void NT_remove_leafs(UNFIXED, TREE_canvas *ntw, AWT_RemoveType mode) {
470    GB_transaction    ta(ntw->gb_main);
471    AWT_auto_refresh  allowed_on(ntw);
472
473    AP_tree *root_node = AWT_TREE(ntw)->get_root_node();
474    if (root_node) {
475        AWT_TREE(ntw)->get_tree_root()->remove_leafs(mode);
476        ntw->request_save_and_zoom_reset();
477    }
478    else {
479        aw_message("Got no tree");
480    }
481}
482
483void NT_remove_bootstrap(UNFIXED, TREE_canvas *ntw) { // delete all bootstrap values
484    GB_transaction   ta(ntw->gb_main);
485    AWT_auto_refresh allowed_on(ntw);
486
487    AP_tree *root_node = AWT_TREE(ntw)->get_root_node();
488    if (root_node) {
489        root_node->remove_bootstrap();
490        ntw->request_save_and_zoom_reset();
491    }
492    else {
493        aw_message("Got no tree");
494    }
495}
496
497void NT_edit_bootstrap(UNFIXED, TREE_canvas *ntw) { // edit bootstrap values
498    GB_transaction   ta(ntw->gb_main);
499    AWT_auto_refresh allowed_on(ntw);
500
501    AP_tree *root_node = AWT_TREE(ntw)->get_root_node();
502    if (root_node) {
503        char *aci = aw_input("Enter ACI to apply to all branch remarks:");
504        if (aci) {
505            char *tree_name = ntw->get_awar_tree()->read_string();
506
507            GBL_env      env(ntw->gb_main, tree_name);
508            GBL_call_env callEnv(NULp, env); // do not pass any species (will only be applied to inner branches)
509
510            GB_ERROR error = root_node->apply_aci_to_remarks(aci, callEnv);
511            ntw->request_save_and_zoom_reset();
512
513            error = ta.close(error);
514            if (error) aw_message(error);
515
516            free(tree_name);
517        }
518    }
519    else {
520        aw_message("Got no tree");
521    }
522}
523
524void NT_reset_branchlengths(UNFIXED, TREE_canvas *ntw) { // set all branchlengths to tree_defaults::LENGTH
525    GB_transaction   ta(ntw->gb_main);
526    AWT_auto_refresh allowed_on(ntw);
527
528    AP_tree *root_node = AWT_TREE(ntw)->get_root_node();
529    if (root_node) {
530        root_node->reset_branchlengths();
531        ntw->request_save_and_zoom_reset();
532    }
533    else {
534        aw_message("Got no tree");
535    }
536}
537
538void NT_multifurcate_tree(TREE_canvas *ntw, const TreeNode::multifurc_limits& below) {
539    GB_transaction   ta(ntw->gb_main);
540    AWT_auto_refresh allowed_on(ntw);
541
542    TreeNode *tree = AWT_TREE(ntw)->get_root_node();
543    if (tree) {
544        tree->multifurcate_whole_tree(below);
545        ntw->request_save_and_zoom_reset();
546    }
547    else {
548        aw_message("Got no tree");
549    }
550}
551
552void NT_move_boot_branch(UNFIXED, TREE_canvas *ntw, int direction) { // copy branchlengths to bootstraps (or vice versa)
553    GB_transaction   ta(ntw->gb_main);
554    AWT_auto_refresh allowed_on(ntw);
555
556    AP_tree *root_node = AWT_TREE(ntw)->get_root_node();
557    if (root_node) {
558        if (direction == 0) root_node->bootstrap2branchlen();
559        else                root_node->branchlen2bootstrap();
560
561        ntw->request_save_and_zoom_reset();
562
563        char *adviceText = GBS_global_string_copy("Please note, that you just overwrote your existing %s.",
564                                                  direction ? "bootstrap values" : "branchlengths");
565        AW_advice(adviceText, AW_ADVICE_TOGGLE_AND_HELP, NULp, "tbl_boot2len.hlp");
566        free(adviceText);
567    }
568    else {
569        aw_message("Got no tree");
570    }
571}
572
573void NT_scale_tree(UNFIXED, TREE_canvas *ntw) { // scale branchlengths
574    char *answer = aw_input("Enter scale factor", "Scale branchlengths by factor:", "100");
575    if (answer) {
576        double   factor    = atof(answer);
577        AP_tree *root_node = AWT_TREE(ntw)->get_root_node();
578
579        if (root_node) {
580            GB_transaction   ta(ntw->gb_main);
581            AWT_auto_refresh allowed_on(ntw);
582            root_node->scale_branchlengths(factor);
583            ntw->request_save_and_zoom_reset();
584        }
585        else {
586            aw_message("Got no tree");
587        }
588        free(answer);
589    }
590}
591
592inline AP_tree *common_ancestor(AP_tree *t1, AP_tree *t2) {
593    return DOWNCAST(AP_tree*, t1->ancestor_common_with(t2));
594}
595
596void NT_jump_cb(UNFIXED, TREE_canvas *ntw, AP_tree_jump_type jumpType) {
597    if (jumpType == AP_DONT_JUMP) return;
598
599    AW_window        *aww   = ntw->aww;
600    AWT_graphic_tree *gtree = AWT_TREE(ntw);
601
602    GB_transaction   ta(ntw->gb_main);
603    AWT_auto_refresh allowed_on(ntw);
604
605    AW_root    *aw_root  = aww->get_root();
606    const char *name     = aw_root->awar(AWAR_SPECIES_NAME)->read_char_pntr();
607    GBDATA     *gb_group = gtree->get_selected_group().get_group_data();
608
609    char *msg      = NULp;
610    bool  verboose = jumpType & AP_JUMP_BE_VERBOOSE;
611
612    if (name[0] || gb_group) {
613        AP_tree *found         = NULp;
614        bool     is_tree       = is_tree_style(gtree->get_tree_style());
615        bool     jump_to_group = gb_group && is_tree; // @@@ prefer group atm
616
617        if (is_tree) {
618            if (gtree && gtree->get_logical_root()) {
619                if (jump_to_group) {
620                    found = gtree->locate_selected_group(gtree->get_logical_root());
621                    td_assert(found && found->is_clade());
622                }
623                else {
624                    found = gtree->get_logical_root()->findLeafNamed(name);
625                }
626
627                if (!found && gtree->is_logically_zoomed()) {
628                    if (jump_to_group) {
629                        found = gtree->locate_selected_group(gtree->get_root_node());
630                    }
631                    else {
632                        found = gtree->get_root_node()->findLeafNamed(name);
633                    }
634
635                    if (found) { // species/group is invisible because it is outside logically zoomed tree
636                        if (jumpType & AP_JUMP_LOGICAL_UNZOOM) {
637                            gtree->set_logical_root_to(common_ancestor(found, gtree->get_logical_root()));
638                            ntw->request_resize();
639                        }
640                        else {
641                            if (verboose) {
642                                if (jump_to_group) {
643                                    msg = GBS_global_string_copy("Group '%s' is outside logical zoomed subtree", gtree->get_selected_group().get_name());
644                                }
645                                else {
646                                    msg = GBS_global_string_copy("Species '%s' is outside logical zoomed subtree", name);
647                                }
648                            }
649
650                            found = NULp;
651                        }
652                    }
653                }
654
655                if (found && !(jumpType&AP_JUMP_AUTO_UNFOLD) && found->is_inside_folded_group()) {
656                    found = NULp; // => just undo auto-unfolding
657                }
658                gtree->auto_unfold(found);
659            }
660        }
661
662        if (found || !is_tree) {
663            bool is_IRS  = gtree->get_tree_style() == AP_TREE_IRS;
664            bool repeat  = is_IRS;
665            bool do_jump = true;
666
667            ntw->sync_DB_model_and_view(false); // sync w/o refresh
668
669            while (do_jump) {
670                do_jump = false;
671
672                AW_device_size *device = aww->get_size_device(AW_MIDDLE_AREA);
673                device->set_filter(AW_SIZE|AW_SIZE_UNSCALED);
674                device->reset();
675                ntw->init_device(device);
676                ntw->gfx->show(device);
677
678                const AW_screen_area& screen = device->get_area_size();
679
680                const Position& cursor = jump_to_group ? gtree->get_group_cursor() : gtree->get_cursor();
681                if (are_distinct(Origin, cursor)) {
682                    Position S = device->transform(cursor);
683
684                    int scroll_x = 0;
685                    int scroll_y = 0;
686
687                    bool do_vcenter = jumpType & AP_JUMP_FORCE_VCENTER;
688                    bool do_hcenter = jumpType & AP_JUMP_FORCE_HCENTER;
689
690                    if (!do_vcenter) {
691                        if (is_IRS) {
692                            // attempt to center IRS tree vertically often fails (too complicated to predict)
693                            // => force into center-half of screen to reduce error rate
694                            int border = screen.b/10;
695                            do_vcenter = S.ypos()<border || S.ypos()>(screen.b-border);
696                        }
697                        else {
698                            do_vcenter = S.ypos()<0.0 || S.ypos()>screen.b; // center if outside viewport
699                        }
700                    }
701
702                    if (do_vcenter) {
703                        scroll_y = (int)(S.ypos() - screen.b*(is_IRS ? .6 : .5)); // position a bit below vertical center for IRS tree
704
705                        if (!scroll_y && (jumpType & AP_JUMP_ALLOW_HCENTER)) { // allow horizontal centering if vertical has no effect
706                            do_hcenter = true;
707                        }
708                    }
709
710                    if (do_hcenter) {
711                        scroll_x = (int) (S.xpos() - screen.r * (is_tree ? .5 : .02));
712                    }
713                    else { // keep visible
714                        if (S.xpos()<0.0) {
715                            double relPos = 0;
716                            switch (gtree->get_tree_style()) {
717                                case AP_TREE_NORMAL:
718                                case AP_TREE_IRS:      relPos = .1; break;
719                                case AP_TREE_RADIAL:   relPos = .5; break;
720                                case AP_LIST_NDS:
721                                case AP_LIST_SIMPLE:   relPos = .02; break;
722                            }
723                            scroll_x = (int)(S.xpos() - screen.r * relPos);
724                        }
725                        else if (S.xpos()>screen.r) {
726                            scroll_x = (int)(S.xpos() - screen.r * .5);
727                        }
728                    }
729
730                    if (scroll_x || scroll_y) ntw->scroll(scroll_x, scroll_y);
731                    if (repeat) {
732                        // reposition jump in IRS tree (reduces jump failure rate)
733                        repeat  = false;
734                        do_jump = true;
735                    }
736                }
737                else {
738                    td_assert(!is_tree); // jumped-to position should have been found
739                    if (verboose) msg = GBS_global_string_copy("Species '%s' is no member of this list", name);
740                }
741            }
742        }
743
744        if (!found && is_tree && verboose && !msg) {
745            td_assert(!jump_to_group); // need special handling
746            msg = GBS_global_string_copy("Species '%s' is no member of this %s", name, gtree->get_tree_style() == AP_LIST_NDS ? "list" : "tree");
747        }
748
749    }
750    else { // select "no species" and "no group"
751        gtree->auto_unfold(NULp); // undo auto-unfolding
752        if (verboose) {
753            msg = strdup("Neither species nor group selected");
754        }
755    }
756
757    if (gtree) gtree->exports.request_refresh(); // always do refresh to show change of selected species
758
759    if (msg) {
760        td_assert(verboose);
761        aw_message(msg);
762        free(msg);
763    }
764}
765
766void TREE_auto_jump_cb(UNFIXED, TREE_canvas *ntw, AP_tree_jump_reason cause) {
767    /*! jump to species when tree/treemode/species changes
768     * @param cause reason why auto-jump was triggered
769     */
770
771    AWT_auto_refresh allowed_on(ntw);
772
773    bool tree_change = cause == AP_JUMP_REASON_TREE || cause == AP_JUMP_REASON_STYLE;
774
775    const char        *awar_name = tree_change ? AWAR_DTREE_AUTO_JUMP_TREE : AWAR_DTREE_AUTO_JUMP;
776    AW_root           *awr       = ntw->aww->get_root();
777    AP_tree_jump_type  jump_type = AP_tree_jump_type(awr->awar(awar_name)->read_int());
778
779    if (jump_type == AP_DONT_JUMP) {
780        ntw->request_refresh();
781    }
782    else {
783        bool auto_unfold           = awr->awar(AWAR_DTREE_AUTO_UNFOLD)->read_int();
784        if (auto_unfold) jump_type = AP_tree_jump_type(jump_type|AP_JUMP_AUTO_UNFOLD);
785        NT_jump_cb(NULp, ntw, jump_type);
786    }
787}
788
789inline const char *plural(int val) {
790    return "s"+(val == 1);
791}
792
793void NT_reload_tree_event(AW_root *, TREE_canvas *ntw, bool unzoom_and_expose) {
794    GB_push_transaction(ntw->gb_main);
795
796    {
797        AWT_auto_refresh update(ntw);
798
799        AWT_graphic_tree *agt   = ntw->get_graphic_tree();
800        AP_tree_root     *troot = agt->get_tree_root();
801
802        if (!troot->get_gb_tree()) { // happens at initial startup (=first load) and after a shown tree gets renamed
803            // forget all about old tree
804            agt->forget_auto_unfolded();
805            agt->deselect_group();
806        }
807        else {
808            agt->auto_unfold(NULp);     // undo auto-unfolding before tree-change (otherwise temp. unfolding remains saved in DB)
809        }
810        update.suppress_update_and_refresh(); // suppress update (speed)
811    }
812
813    char     *tree_name = ntw->get_awar_tree()->read_string();
814    GB_ERROR  error     = ntw->gfx->load_from_DB(ntw->gb_main, tree_name);
815    if (error) {
816        aw_message(error);
817    }
818    else {
819        int zombies, duplicates;
820        DOWNCAST(AWT_graphic_tree*, ntw->gfx)->get_zombies_and_duplicates(zombies, duplicates);
821
822        if (zombies || duplicates) {
823            const char *msg = NULp;
824            if (duplicates) {
825                if (zombies) msg = GBS_global_string("%i zombie%s and %i duplicate%s", zombies, plural(zombies), duplicates, plural(duplicates));
826                else msg         = GBS_global_string("%i duplicate%s", duplicates, plural(duplicates));
827            }
828            else {
829                td_assert(zombies);
830                msg = GBS_global_string("%i zombie%s", zombies, plural(zombies));
831            }
832            aw_message(GBS_global_string("%s in '%s'", msg, tree_name));
833        }
834    }
835    free(tree_name);
836    if (unzoom_and_expose) {
837        AWT_auto_refresh allowed_on(ntw);
838        ntw->request_zoom_reset();
839    }
840    GB_pop_transaction(ntw->gb_main);
841}
842
843static void tree_recompute_cb(UNFIXED, AWT_canvas *ntw) {
844    AWT_auto_refresh allowed_on(ntw);
845    ntw->request_structure_update();
846}
847
848void TREE_GC_changed_cb(GcChange whatChanged, AWT_canvas *ntw) {
849    AWT_auto_refresh allowed_on(ntw);
850
851    if (whatChanged == GC_COLOR_GROUP_USE_CHANGED) {
852        tree_recompute_cb(NULp, ntw);
853    }
854    else {
855        AWT_GC_changed_cb(whatChanged, ntw);
856    }
857}
858
859void NT_remove_species_in_tree_from_hash(AP_tree *tree, GB_HASH *hash) {
860    if (!tree) return;
861    if (tree->is_leaf() && tree->name) {
862        GBS_write_hash(hash, tree->name, 0); // delete species in hash table
863    }
864    else {
865        NT_remove_species_in_tree_from_hash(tree->get_leftson(), hash);
866        NT_remove_species_in_tree_from_hash(tree->get_rightson(), hash);
867    }
868}
869
Note: See TracBrowser for help on using the repository browser.