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

Last change on this file was 18665, checked in by westram, 3 years ago
  • change many WARN_TODO triggered warnings into todo markers.
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 180.0 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : TreeDisplay.cxx                                   //
4//   Purpose   :                                                   //
5//                                                                 //
6//   Institute of Microbiology (Technical University Munich)       //
7//   http://www.arb-home.de/                                       //
8//                                                                 //
9// =============================================================== //
10
11#include "TreeDisplay.hxx"
12#include "TreeCallbacks.hxx"
13#include "GroupIterator.hxx"
14
15#include <AP_TreeColors.hxx>
16#include <AP_TreeShader.hxx>
17#include <AP_TreeSet.hxx>
18#include <nds.h>
19
20#include <awt_config_manager.hxx>
21
22#include <aw_preset.hxx>
23#include <aw_awars.hxx>
24#include <aw_msg.hxx>
25#include <aw_root.hxx>
26#include <aw_question.hxx>
27
28#include <arb_defs.h>
29#include <arb_diff.h>
30#include <arb_global_defs.h>
31#include <arb_strbuf.h>
32
33#include <ad_cb.h>
34
35#include <unistd.h>
36#include <iostream>
37#include <cfloat>
38#include <algorithm>
39
40#define RULER_LINEWIDTH "ruler/ruler_width" // line width of ruler
41#define RULER_SIZE      "ruler/size"
42
43#define DEFAULT_RULER_LINEWIDTH tree_defaults::LINEWIDTH
44#define DEFAULT_RULER_LENGTH    tree_defaults::LENGTH
45
46int TREE_canvas::count = 0;
47
48const int MARKER_COLORS = 12;
49static int MarkerGC[MARKER_COLORS] = {
50    // double rainbow
51    AWT_GC_RED,
52    AWT_GC_YELLOW,
53    AWT_GC_GREEN,
54    AWT_GC_CYAN,
55    AWT_GC_BLUE,
56    AWT_GC_MAGENTA,
57
58    AWT_GC_ORANGE,
59    AWT_GC_LAWNGREEN,
60    AWT_GC_AQUAMARIN,
61    AWT_GC_SKYBLUE,
62    AWT_GC_PURPLE,
63    AWT_GC_PINK,
64};
65
66using namespace AW;
67
68static void nocb() {}
69GraphicTreeCallback AWT_graphic_tree::group_changed_cb = makeGraphicTreeCallback(nocb);
70
71AW_gc_manager *AWT_graphic_tree::init_devices(AW_window *aww, AW_device *device, AWT_canvas* ntw) {
72    AW_gc_manager *gc_manager =
73        AW_manage_GC(aww,
74                     ntw->get_gc_base_name(),
75                     device, AWT_GC_MAX, AW_GCM_DATA_AREA,
76                     makeGcChangedCallback(TREE_GC_changed_cb, ntw),
77                     "#8ce",
78
79                     // Important note :
80                     // Many gc indices are shared between ABR_NTREE and ARB_PARSIMONY
81                     // e.g. the tree drawing routines use same gc's for drawing both trees
82                     // (check PARS_dtree.cxx AWT_graphic_parsimony::init_devices)
83                     // (keep in sync with ../../PARSIMONY/PARS_dtree.cxx@init_devices)
84
85                     // Note: in radial tree display, branches with lower gc(-index) are drawn AFTER branches
86                     //       with higher gc(-index), i.e. marked branches are drawn on top of unmarked branches.
87
88                     "Cursor$white",
89                     "Branch remarks$#3d8a99",
90                     "+-Bootstrap$#abe3ff",    "-B.(limited)$#cfe9ff",
91                     "-IRS group box$#000",
92                     "Marked$#ffe560",
93                     "Some marked$#d9c45c",
94                     "Not marked$#5d5d5d",
95                     "Zombies etc.$#7aa3cc",
96
97                     "+-None (black)$#000000", "-All (white)$#ffffff",
98
99                     "+-P1(red)$#ff0000",        "+-P2(green)$#00ff00",    "-P3(blue)$#0000ff",
100                     "+-P4(orange)$#ffd060",     "+-P5(aqua)$#40ffc0",     "-P6(purple)$#c040ff",
101                     "+-P7(1&2,yellow)$#ffff00", "+-P8(2&3,cyan)$#00ffff", "-P9(3&1,magenta)$#ff00ff",
102                     "+-P10(lawn)$#c0ff40",      "+-P11(skyblue)$#40c0ff", "-P12(pink)$#f030b0",
103
104                     "&color_groups", // use color groups
105
106                     // color ranges:
107                     "*Linear,linear:+-lower$#a00,-upper$#0a0",
108                     "*Rainbow,cyclic:+-col1$#a00,-col2$#990,"
109                     /*           */ "+-col3$#0a0,-col4$#0aa,"
110                     /*           */ "+-col5$#00a,-col6$#b0b",
111                     "*Planar,planar:+-off$#000,-dim1$#a00,"
112                     /*          */ "-dim2$#0a0",
113                     "*Spatial,spatial:+-off$#000,-dim1$#a00,"
114                     /*            */ "+-dim2$#0a0,-dim3$#00a",
115
116                     NULp);
117
118    return gc_manager;
119}
120
121void AWT_graphic_tree::mark_species_in_tree(AP_tree *at, int mark_mode) {
122    /*
123      mode      does
124
125      0         unmark all
126      1         mark all
127      2         invert all marks
128    */
129
130    if (!at) return;
131
132    GB_transaction ta(tree_static->get_gb_main());
133    if (at->is_leaf()) {
134        if (at->gb_node) {      // not a zombie
135            switch (mark_mode) {
136                case 0: GB_write_flag(at->gb_node, 0); break;
137                case 1: GB_write_flag(at->gb_node, 1); break;
138                case 2: GB_write_flag(at->gb_node, !GB_read_flag(at->gb_node)); break;
139                default: td_assert(0);
140            }
141        }
142    }
143
144    mark_species_in_tree(at->get_leftson(), mark_mode);
145    mark_species_in_tree(at->get_rightson(), mark_mode);
146}
147
148void AWT_graphic_tree::mark_species_in_tree_that(AP_tree *at, int mark_mode, bool (*condition)(GBDATA*, void*), void *cd) {
149    /*
150      mark_mode does
151
152      0         unmark all
153      1         mark all
154      2         invert all marks
155
156      marks are only changed for those species for that condition() != 0
157    */
158
159    if (!at) return;
160
161    GB_transaction ta(tree_static->get_gb_main());
162    if (at->is_leaf()) {
163        if (at->gb_node) {      // not a zombie
164            int oldMark = GB_read_flag(at->gb_node);
165            if (oldMark != mark_mode && condition(at->gb_node, cd)) {
166                switch (mark_mode) {
167                    case 0: GB_write_flag(at->gb_node, 0); break;
168                    case 1: GB_write_flag(at->gb_node, 1); break;
169                    case 2: GB_write_flag(at->gb_node, !oldMark); break;
170                    default: td_assert(0);
171                }
172            }
173        }
174    }
175
176    mark_species_in_tree_that(at->get_leftson(), mark_mode, condition, cd);
177    mark_species_in_tree_that(at->get_rightson(), mark_mode, condition, cd);
178}
179
180
181// same as mark_species_in_tree but works on rest of tree
182void AWT_graphic_tree::mark_species_in_rest_of_tree(AP_tree *at, int mark_mode) {
183    if (at) {
184        AP_tree *pa = at->get_father();
185        if (pa) {
186            GB_transaction ta(tree_static->get_gb_main());
187
188            mark_species_in_tree(at->get_brother(), mark_mode);
189            mark_species_in_rest_of_tree(pa, mark_mode);
190        }
191    }
192}
193
194// same as mark_species_in_tree_that but works on rest of tree
195void AWT_graphic_tree::mark_species_in_rest_of_tree_that(AP_tree *at, int mark_mode, bool (*condition)(GBDATA*, void*), void *cd) {
196    if (at) {
197        AP_tree *pa = at->get_father();
198        if (pa) {
199            GB_transaction ta(tree_static->get_gb_main());
200
201            mark_species_in_tree_that(at->get_brother(), mark_mode, condition, cd);
202            mark_species_in_rest_of_tree_that(pa, mark_mode, condition, cd);
203        }
204    }
205}
206
207bool AWT_graphic_tree::tree_has_marks(AP_tree *at) {
208    if (!at) return false;
209
210    if (at->is_leaf()) {
211        if (!at->gb_node) return false; // zombie
212        int marked = GB_read_flag(at->gb_node);
213        return marked;
214    }
215
216    return tree_has_marks(at->get_leftson()) || tree_has_marks(at->get_rightson());
217}
218
219bool AWT_graphic_tree::rest_tree_has_marks(AP_tree *at) {
220    if (!at) return false;
221
222    AP_tree *pa = at->get_father();
223    if (!pa) return false;
224
225    return tree_has_marks(at->get_brother()) || rest_tree_has_marks(pa);
226}
227
228struct AWT_graphic_tree_group_state {
229    int closed, opened;
230    int closed_terminal, opened_terminal;
231    int closed_with_marked, opened_with_marked;
232    int marked_in_groups, marked_outside_groups;
233
234    void clear() {
235        closed                = 0;
236        opened                = 0;
237        closed_terminal       = 0;
238        opened_terminal       = 0;
239        closed_with_marked    = 0;
240        opened_with_marked    = 0;
241        marked_in_groups      = 0;
242        marked_outside_groups = 0;
243    }
244
245    AWT_graphic_tree_group_state() { clear(); }
246
247    bool has_groups() const { return closed+opened; }
248    int marked() const { return marked_in_groups+marked_outside_groups; }
249
250    bool all_opened() const { return closed == 0 && opened>0; }
251    bool all_closed() const { return opened == 0 && closed>0; }
252    bool all_terminal_closed() const { return opened_terminal == 0 && closed_terminal == closed; }
253    bool all_marked_opened() const { return marked_in_groups > 0 && closed_with_marked == 0; }
254
255    CollapseMode next_expand_mode() const {
256        if (closed_with_marked) return EXPAND_MARKED;
257        return EXPAND_ALL;
258    }
259
260    CollapseMode next_collapse_mode() const {
261        if (all_terminal_closed()) return COLLAPSE_ALL;
262        return COLLAPSE_TERMINAL;
263    }
264};
265
266void AWT_graphic_tree::detect_group_state(AP_tree *at, AWT_graphic_tree_group_state *state, AP_tree *skip_this_son) {
267    if (!at) return;
268    if (at->is_leaf()) {
269        if (at->gb_node && GB_read_flag(at->gb_node)) state->marked_outside_groups++; // count marks
270        return;                 // leafs never get grouped
271    }
272
273    if (at->is_normal_group()) {
274        AWT_graphic_tree_group_state sub_state;
275        if (at->leftson != skip_this_son) detect_group_state(at->get_leftson(), &sub_state, skip_this_son);
276        if (at->rightson != skip_this_son) detect_group_state(at->get_rightson(), &sub_state, skip_this_son);
277
278        if (at->gr.grouped) {   // a closed group
279            state->closed++;
280            if (!sub_state.has_groups()) state->closed_terminal++;
281            if (sub_state.marked()) state->closed_with_marked++;
282            state->closed += sub_state.opened;
283        }
284        else { // an open group
285            state->opened++;
286            if (!sub_state.has_groups()) state->opened_terminal++;
287            if (sub_state.marked()) state->opened_with_marked++;
288            state->opened += sub_state.opened;
289        }
290
291        state->marked_in_groups += sub_state.marked();
292
293        state->closed             += sub_state.closed;
294        state->opened_terminal    += sub_state.opened_terminal;
295        state->closed_terminal    += sub_state.closed_terminal;
296        state->opened_with_marked += sub_state.opened_with_marked;
297        state->closed_with_marked += sub_state.closed_with_marked;
298    }
299    else { // not a group
300        if (at->leftson != skip_this_son) detect_group_state(at->get_leftson(), state, skip_this_son);
301        if (at->rightson != skip_this_son) detect_group_state(at->get_rightson(), state, skip_this_son);
302    }
303}
304
305void AWT_graphic_tree::group_rest_tree(AP_tree *at, CollapseMode mode, int color_group) {
306    if (at) {
307        AP_tree *pa = at->get_father();
308        if (pa) {
309            group_tree(at->get_brother(), mode, color_group);
310            group_rest_tree(pa, mode, color_group);
311        }
312    }
313}
314
315bool AWT_graphic_tree::group_tree(AP_tree *at, CollapseMode mode, int color_group) {
316    /*! collapse/expand subtree according to mode (and color_group)
317     * Run on father! (why?)
318     * @return true if subtree shall expand
319     */
320
321    if (!at) return true;
322
323    GB_transaction ta(tree_static->get_gb_main());
324
325    bool expand_me = false;
326    if (at->is_leaf()) {
327        if (mode & EXPAND_ALL) expand_me = true;
328        else if (at->gb_node) { // linked leaf
329            if (mode & EXPAND_MARKED) { // do not group marked
330                if (GB_read_flag(at->gb_node)) expand_me = true;
331            }
332            if (!expand_me && (mode & EXPAND_COLOR)) { // do not group specified color_group
333                int my_color_group = GBT_get_color_group(at->gb_node);
334
335                expand_me =
336                    my_color_group == color_group || // specific or no color
337                    (my_color_group != 0 && color_group == -1); // any color
338            }
339        }
340        else { // zombie
341            expand_me = mode & EXPAND_ZOMBIES;
342        }
343    }
344    else { // inner node
345        at->gr.grouped = false; // expand during descend (important because keeled group may fold 'at' from inside recursion!)
346
347        expand_me = group_tree(at->get_leftson(), mode, color_group);
348        expand_me = group_tree(at->get_rightson(), mode, color_group) || expand_me;
349
350        if (!expand_me) { // no son requests to be expanded
351            if (at->is_normal_group()) {
352                at->gr.grouped = true; // group me
353                if (mode & COLLAPSE_TERMINAL) expand_me = true; // do not group non-terminal groups (upwards)
354            }
355            if (at->is_keeled_group()) {
356                at->get_father()->gr.grouped = true; // group "keeled"-me
357                if (mode & COLLAPSE_TERMINAL) expand_me = true; // do not group non-terminal groups (upwards)
358            }
359        }
360    }
361    return expand_me;
362}
363
364void AWT_graphic_tree::reorderTree(TreeOrder mode) {
365    AP_tree *at = get_root_node();
366    if (at) {
367        at->reorder_tree(mode);
368        exports.request_save();
369    }
370}
371
372BootstrapConfig::BootstrapConfig() :
373    circle_filter(AW_SCREEN|AW_PRINTER|AW_SIZE_UNSCALED),
374    text_filter  (AW_SCREEN|AW_CLICK|AW_CLICK_DROP|AW_PRINTER|AW_SIZE_UNSCALED)
375{
376}
377
378void BootstrapConfig::display_remark(AW_device *device, const char *remark, const AW::Position& center, double blen, double bdist, const AW::Position& textpos, AW_pos alignment) const {
379    double         dboot;
380    GBT_RemarkType type = parse_remark(remark, dboot);
381
382    bool is_bootstrap = false;
383    switch (type) {
384        case REMARK_BOOTSTRAP:
385            is_bootstrap = true;
386            break;
387
388        case REMARK_NONE:
389            td_assert(show_100_if_empty); // otherwise method should not have been called
390
391            is_bootstrap = true;
392            dboot        = 100.0;
393            break;
394
395        case REMARK_OTHER:
396            break;
397    }
398
399    int bootstrap = is_bootstrap ? int(dboot) : -1;
400
401    bool        show = true;
402    const char *text = NULp;
403
404    if (is_bootstrap) {
405        if (!show_boots || // hide bootstrap when disabled,
406            bootstrap<bootstrap_min || // when outside of displayed range or
407            bootstrap>bootstrap_max ||
408            blen == 0.0) // when branch is part of a multifurcation (i.e. "does not exist")
409        {
410            show = false;
411        }
412        else {
413            static GBS_strstruct buf(10);
414            buf.erase();
415
416            if (bootstrap<1) {
417                buf.put('<');
418                bootstrap = 1;
419            }
420
421            if (style == BS_FLOAT) {
422                buf.nprintf(4, "%4.2f", double(bootstrap/100.0));
423            }
424            else {
425                buf.nprintf(3, "%i", bootstrap);
426                if (style == BS_PERCENT) {
427                    buf.put('%');
428                }
429            }
430            text = buf.get_data();
431        }
432    }
433    else { // non-bootstrap remark (always shown)
434        text = remark;
435    }
436
437    if (show_circle && is_bootstrap && show) {
438        double radius = .01 * bootstrap; // bootstrap values are given in % (0..100)
439
440        if (radius < .1) radius = .1;
441
442        radius  = 1.0 / sqrt(radius); // -> bootstrap->radius : 100% -> 1, 0% -> inf
443        radius -= 1.0;                // -> bootstrap->radius : 100% -> 0, 0% -> inf
444
445        radius *= zoom_factor * 2;
446
447        // Note : radius goes against infinite, if bootstrap values go towards zero
448        //        For this reason we limit radius here:
449
450        int gc = AWT_GC_BOOTSTRAP;
451        if (radius > max_radius) {
452            radius = max_radius;
453            gc     = AWT_GC_BOOTSTRAP_LIMITED; // draw limited bootstraps in different color
454        }
455
456        const double radiusx        = radius * blen;     // multiply with length of branch (and zoomfactor)
457        const bool   circleTooSmall = radiusx<0 || nearlyZero(radiusx);
458        if (!circleTooSmall) {
459            double radiusy;
460            if (elipsoid) {
461                radiusy = bdist;
462                if (radiusy > radiusx) radiusy = radiusx;
463            }
464            else {
465                radiusy = radiusx;
466            }
467
468            device->set_grey_level(gc, fill_level);
469            device->circle(gc, AW::FillStyle::SHADED_WITH_BORDER, center, Vector(radiusx, radiusy), circle_filter);
470        }
471    }
472
473    if (show) {
474        td_assert(text);
475        device->text(AWT_GC_BRANCH_REMARK, text, textpos, alignment, text_filter);
476    }
477}
478
479void BootstrapConfig::display_node_remark(AW_device *device, const AP_tree *at, const AW::Position& center, double blen, double bdist, AW::RoughDirection textArea) const {
480    td_assert(!at->is_leaf()); // leafs (should) have no remarks
481
482    AW_pos   alignment = (textArea & D_WEST) ? 1.0 : 0.0;
483    Position textpos(center);
484    textpos.movey(scaled_remark_ascend*((textArea & D_SOUTH) ? 1.2 : ((textArea & D_NORTH) ? -0.1 : 0.5)));
485
486    display_remark(device, at->get_remark(), center, blen, bdist, textpos, alignment);
487}
488
489static void AWT_graphic_tree_root_changed(void *cd, AP_tree *old, AP_tree *newroot) {
490    AWT_graphic_tree *agt = (AWT_graphic_tree*)cd;
491    if (agt->get_logical_root() == old || agt->get_logical_root()->is_inside(old)) {
492        agt->set_logical_root_to(newroot);
493    }
494}
495
496static void AWT_graphic_tree_node_deleted(void *cd, AP_tree *del) {
497    AWT_graphic_tree *agt = (AWT_graphic_tree*)cd;
498    if (agt->get_logical_root() == del) {
499        agt->set_logical_root_to(agt->get_root_node());
500    }
501    if (agt->get_root_node() == del) {
502        agt->set_logical_root_to(NULp);
503    }
504}
505
506GB_ERROR AWT_graphic_tree::create_group(AP_tree *at) {
507    GB_ERROR error = NULp;
508
509    if (at->has_group_info()) {
510        // only happens for nodes representing a keeled group
511        td_assert(at->keelTarget());
512        error = GBS_global_string("Cannot create group at position of keeled group '%s'", at->name);
513    }
514    else {
515        char *gname = aw_input("Enter name of new group");
516        if (gname && gname[0]) {
517            GBDATA         *gb_tree  = tree_static->get_gb_tree();
518            GBDATA         *gb_mainT = GB_get_root(gb_tree);
519            GB_transaction  ta(gb_mainT);
520
521            GBDATA *gb_node     = GB_create_container(gb_tree, "node");
522            if (!gb_node) error = GB_await_error();
523
524            if (at->gb_node) {                                     // already have existing node info (e.g. for linewidth)
525                if (!error) error = GB_copy_dropProtectMarksAndTempstate(gb_node, at->gb_node); // copy existing node and ..
526                if (!error) error = GB_delete(at->gb_node);        // .. delete old one (to trigger invalidation of taxonomy-cache)
527            }
528
529            if (!error) error = GBT_write_int(gb_node, "id", 0); // force re-creation of node-id
530
531            if (!error) {
532                error = GBT_write_name_to_groupData(gb_node, true, gname, true);
533            }
534            if (!error) exports.request_save();
535            if (!error) {
536                at->gb_node = gb_node;
537                at->name    = gname;
538
539                at->setKeeledState(0); // new group is always unkeeled
540            }
541            error = ta.close(error);
542        }
543    }
544
545    return error;
546}
547
548void AWT_graphic_tree::toggle_group(AP_tree *at) {
549    GB_ERROR error = NULp;
550
551    if (at->is_leaf()) {
552        error = "Please select an inner node to create a group";
553    }
554    else if (at->is_clade()) { // existing group
555        bool     keeled = at->is_keeled_group(); // -> prefer keeled over normal group
556        AP_tree *gat    = keeled ? at->get_father() : at; // node linked to group
557
558        const char *msg = GBS_global_string("What to do with group '%s'?", gat->name);
559
560        switch (aw_question(NULp, msg, "KeelOver,Rename,Destroy,Cancel" + (keeled ? 0 : 9)) - (keeled ? 1 : 0)) {
561            case -1: { // keel over
562                td_assert(keeled);
563                dislocate_selected_group();
564                gat->unkeelGroup();
565
566                // need to flush taxonomy (otherwise group is displayed with leading '!'):
567                GBDATA *gb_gname = GB_entry(gat->gb_node, "group_name");
568                td_assert(gb_gname);
569                GB_touch(gb_gname);
570
571                exports.request_save();
572                break;
573            }
574            case 0: { // rename
575                char *new_gname = aw_input("Rename group", "Change group name:", gat->name);
576                if (new_gname) {
577                    error = GBT_write_name_to_groupData(gat->gb_node, true, new_gname, true);
578                    if (!error) {
579                        freeset(gat->name, new_gname);
580                        select_group(at);
581                        exports.request_save();
582                    }
583                }
584                break;
585            }
586
587            case 1: // destroy
588                if (selected_group.at_node(at)) deselect_group(); // deselect group only if selected group gets destroyed
589
590                gat->gr.grouped = false;
591                gat->name       = NULp;
592                error           = GB_delete(gat->gb_node);        // ODD: expecting this to also destroy linewidth, rot and spread - but it doesn't!
593                gat->gb_node    = NULp;
594
595                if (!error) exports.request_save();      // ODD: even when commenting out this line info is not deleted
596                break;
597
598            case 2:  break; // cancel
599            default: td_assert(0); break;
600        }
601    }
602    else {
603        error = create_group(at); // create new group
604        if (!error && at->has_group_info()) {
605            at->gr.grouped = true;
606            select_group(at);
607        }
608    }
609
610    if (error) aw_message(error);
611}
612
613class Dragged : public AWT_command_data {
614    /*! Is dragged and can be dropped.
615     * Knows how to indicate dragging.
616     */
617    AWT_graphic_exports& exports;
618
619protected:
620    AWT_graphic_exports& get_exports() { return exports; }
621
622public:
623    enum DragAction { DRAGGING, DROPPED };
624
625    Dragged(AWT_graphic_exports& exports_) : exports(exports_) {}
626
627    static bool valid_drag_device(AW_device *device) { return device->type() == AW_DEVICE_SCREEN; }
628
629    virtual void draw_drag_indicator(AW_device *device, int drag_gc) const = 0;
630    virtual void perform(DragAction action, const AW_clicked_element *target, const Position& mousepos) = 0;
631    virtual void abort() = 0;
632
633    void do_drag(const AW_clicked_element *drag_target, const Position& mousepos) {
634        perform(DRAGGING, drag_target, mousepos);
635    }
636    void do_drop(const AW_clicked_element *drop_target, const Position& mousepos) {
637        perform(DROPPED, drop_target, mousepos);
638    }
639
640    void hide_drag_indicator(AW_device *device, int drag_gc) const {
641        // hide by XOR-drawing
642        draw_drag_indicator(device, drag_gc);
643    }
644};
645
646bool AWT_graphic_tree::warn_inappropriate_mode(AWT_COMMAND_MODE mode) {
647    if (mode == AWT_MODE_ROTATE || mode == AWT_MODE_SPREAD) {
648        if (tree_style != AP_TREE_RADIAL) {
649            aw_message("Please select the radial tree display mode to use this command");
650            return true;
651        }
652    }
653    return false;
654}
655
656inline bool is_cursor_keycode(AW_key_code kcode) {
657    return
658        kcode == AW_KEY_UP ||
659        kcode == AW_KEY_DOWN ||
660        kcode == AW_KEY_LEFT ||
661        kcode == AW_KEY_RIGHT ||
662        kcode == AW_KEY_HOME ||
663        kcode == AW_KEY_END;
664}
665
666AP_tree *AWT_graphic_tree::find_selected_node() const {
667    /*! search node of selected species
668     *  @return found node (NULp if none selected)
669     */
670    AP_tree *node = selSpec.get_node(); // node stored by last refresh
671    if (!node && displayed_root && species_name[0]) {
672        node = displayed_root->findLeafNamed(species_name);
673    }
674    return node;
675}
676
677AP_tree *AWT_graphic_tree::find_selected_group() {
678    /*! search root-node of selected group
679     * @return found node (NULp if none selected)
680     */
681
682    // @@@ could use selGroup to speed up search (if selected group was already drawn)
683    AP_tree *node = NULp;
684    if (selected_group.is_valid()) {
685        if (!selected_group.is_located()) {
686            selected_group.locate(get_root_node());
687        }
688        node = selected_group.get_node();
689    }
690    return node;
691}
692
693
694static GBDATA *brute_force_find_next_species(GBDATA *gb_main, GBDATA *gb_sel, bool marked_only, bool upwards) {
695    if (upwards) {
696        if (gb_sel && marked_only && !GB_read_flag(gb_sel)) gb_sel = GBT_next_marked_species(gb_sel);
697
698        GBDATA *gb_prev = marked_only ? GBT_first_marked_species(gb_main) : GBT_first_species(gb_main);
699        while (gb_prev) {
700            GBDATA *gb_next = marked_only ? GBT_next_marked_species(gb_prev) : GBT_next_species(gb_prev);
701            if (gb_next == gb_sel) {
702                return gb_prev;
703            }
704            gb_prev = gb_next;
705        }
706        return gb_sel ? brute_force_find_next_species(gb_main, NULp, marked_only, upwards) : NULp;
707    }
708
709    // downwards
710    GBDATA *gb_found = NULp;
711    if (marked_only) {
712        if (gb_sel) gb_found = GBT_next_marked_species(gb_sel);
713        if (!gb_found) gb_found = GBT_first_marked_species(gb_main);
714    }
715    else {
716        if (gb_sel) gb_found = GBT_next_species(gb_sel);
717        if (!gb_found) gb_found = GBT_first_species(gb_main);
718    }
719    return gb_found;
720}
721
722class AP_tree_folding {
723    AP_tree_set unfolded; // nodes which have been unfolded
724
725    static void need_update(AP_tree*& subtree, AP_tree *node) {
726        if (subtree) {
727            subtree = DOWNCAST(AP_tree*, node->ancestor_common_with(subtree));
728        }
729        else {
730            subtree = node;
731        }
732    }
733
734public:
735
736    AP_tree *unfold(const AP_tree_set& want_unfolded) { // set has to contain all parent group-nodes as well (use collect_enclosing_groups)
737        AP_tree_set  keep_unfolded;
738        AP_tree     *affected_subtree = NULp;
739        for (AP_tree_set_citer g = want_unfolded.begin(); g != want_unfolded.end(); ++g) {
740            AP_tree *node = *g;
741            td_assert(node->has_group_info()); // ensure keeled groups add the parent node (where their group-info is stored!)
742            if (node->gr.grouped) {
743                node->gr.grouped = false;  // auto-unfold
744                need_update(affected_subtree, node);
745                keep_unfolded.insert(node);
746            }
747        }
748
749        for (AP_tree_set_iter g = unfolded.begin(); g != unfolded.end(); ++g) {
750            AP_tree *node = *g;
751            td_assert(node->has_group_info());
752            if (want_unfolded.find(node) == want_unfolded.end()) {
753                node->gr.grouped = true; // auto-refold
754                need_update(affected_subtree, node);
755            }
756            else {
757                keep_unfolded.insert(node); // auto-refold later
758            }
759        }
760        unfolded = keep_unfolded;
761        return affected_subtree;
762    }
763
764    bool is_auto_unfolded() const { return !unfolded.empty(); }
765    void forget() { unfolded.clear(); }
766
767    bool node_is_auto_unfolded(AP_tree *node) const {
768        return unfolded.find(node) != unfolded.end();
769    }
770};
771
772void AWT_graphic_tree::auto_unfold(AP_tree *want_visible) {
773    AP_tree_set parentGroups;
774    if (want_visible) collect_enclosing_groups(want_visible, parentGroups);
775
776    AP_tree *outdated_subtree = autoUnfolded->unfold(parentGroups);
777    if (outdated_subtree) {
778        fast_sync_changed_folding(outdated_subtree);
779    }
780}
781
782void AWT_graphic_tree::forget_auto_unfolded() {
783    autoUnfolded->forget();
784}
785
786bool AWT_graphic_tree::handle_cursor(AW_key_code kcode, AW_key_mod mod) {
787    td_assert(is_cursor_keycode(kcode));
788
789    bool handled = false;
790    if (!displayed_root) return false;
791
792    if (mod == AW_KEYMODE_NONE || mod == AW_KEYMODE_SHIFT || mod == AW_KEYMODE_CONTROL) { // jump next/prev (marked) species
793        if (kcode != AW_KEY_LEFT && kcode != AW_KEY_RIGHT) { // cursor left/right not defined
794            GBDATA  *gb_jump_to   = NULp;
795            bool     marked_only  = (mod == AW_KEYMODE_CONTROL);
796
797            bool upwards         = false;
798            bool ignore_selected = false;
799
800            switch (kcode) {
801                case AW_KEY_HOME: ignore_selected = true; // fall-through
802                case AW_KEY_DOWN: upwards = false; break;
803                case AW_KEY_END:  ignore_selected = true; // fall-through
804                case AW_KEY_UP:   upwards = true; break;
805                default: break;
806            }
807
808            if (is_tree_style(tree_style)) {
809                bool     descent_folded = marked_only || (mod == AW_KEYMODE_SHIFT);
810                AP_tree *sel_node       = NULp;
811
812                bool at_group = false;
813                if (!ignore_selected) {
814                    sel_node = find_selected_node();
815                    if (!sel_node) {
816                        sel_node = find_selected_group();
817                        at_group = sel_node;
818                    }
819                }
820
821                ARB_edge edge =
822                    sel_node
823                    ? (at_group
824                       ? (upwards
825                          ? ARB_edge(sel_node, sel_node->get_rightson()).previous()
826                          : ARB_edge(sel_node->get_leftson(), sel_node))
827                       : leafEdge(sel_node))
828                    : rootEdge(get_tree_root());
829
830                // limit edge iteration (to avoid deadlock, e.g. if all species are inside folded groups)
831                int      maxIter      = ARB_edge::iteration_count(get_root_node()->gr.leaf_sum)+2;
832                AP_tree *jump_to_node = NULp;
833
834                while (!jump_to_node && maxIter-->0) {
835                    edge = upwards ? edge.next() : edge.previous();
836                    if (edge.is_edge_to_leaf()) {
837                        AP_tree *leaf = DOWNCAST(AP_tree*, edge.dest());
838                        if (leaf->gb_node                                                    &&       // skip zombies
839                            (marked_only ? leaf->gr.mark_sum                                          // skip unmarked leafs
840                             : implicated(!descent_folded, !leaf->is_inside_folded_group())) &&       // skip folded leafs if !descent_folded
841                            implicated(is_logically_zoomed(), displayed_root->is_ancestor_of(leaf))) // stay inside logically zoomed subtree
842                        {
843                            jump_to_node = leaf;
844                        }
845                    }
846                    // @@@ optimize: no need to descend into unmarked subtrees (if marked_only)
847                    // @@@ optimize: no need to descend into folded subtrees (if !marked_only)
848                }
849
850                if (jump_to_node) {
851                    // perform auto-unfolding unconditionally here
852                    // (jump_to_node will only point to a hidden node here, if auto-unfolding shall happen)
853                    auto_unfold(jump_to_node);
854                    if (jump_to_node->is_leaf()) gb_jump_to = jump_to_node->gb_node; // select node (done below)
855                }
856            }
857            else {
858                if (nds_only_marked) marked_only      = true;
859                if (!species_name[0]) ignore_selected = true;
860
861                GBDATA *gb_sel = ignore_selected ? NULp : GBT_find_species(gb_main, species_name);
862                gb_jump_to = brute_force_find_next_species(gb_main, gb_sel, marked_only, upwards);
863            }
864
865            if (gb_jump_to) {
866                GB_transaction ta(gb_main);
867                map_viewer_cb(gb_jump_to, ADMVT_SELECT);
868                handled = true;
869            }
870        }
871    }
872    else if (mod == AW_KEYMODE_ALT) { // jump to groups
873        if (is_tree_style(tree_style)) {
874            AP_tree *start_node           = find_selected_group();
875            bool     started_from_species = false;
876
877            if (!start_node) { // if no group is selected => start at selected species
878                start_node           = find_selected_node();
879                started_from_species = start_node;
880            }
881
882            // if nothing selected -> 'iter' will point to first group (deepest one)
883            GroupIterator  iter(start_node ? start_node : get_root_node());
884            AP_tree       *unfold_to = NULp;
885
886            bool at_target = false;
887            if (started_from_species) {
888                AP_tree *parentGroup = DOWNCAST(AP_tree*, start_node->find_parent_clade());
889
890                if (parentGroup) {
891                    iter      = GroupIterator(parentGroup);
892                    at_target = (kcode == AW_KEY_LEFT || kcode == AW_KEY_RIGHT);
893                }
894                else {
895                    at_target = true; // stick with default group
896                }
897            }
898
899            while (!at_target) {
900                AW_key_code  inject_kcode      = AW_KEY_NONE;
901                int          start_clade_level = iter.get_clade_level();
902                AP_tree     *start_group       = iter.node(); // abort iteration (handles cases where only 1 group is found)
903
904                switch (kcode) {
905                    case AW_KEY_UP:
906                        do {
907                            iter.previous();
908                            if (iter.node() == start_group) break;
909                        }
910                        while (iter.get_clade_level() > start_clade_level);
911
912                        if (iter.get_clade_level() < start_clade_level) {
913                            iter         = GroupIterator(start_group);
914                            inject_kcode = AW_KEY_END;
915                        }
916                        break;
917
918                    case AW_KEY_DOWN:
919                        if (start_node) { // otherwise iterator already points to wanted node
920                            do {
921                                iter.next();
922                                if (iter.node() == start_group) break;
923                            }
924                            while (iter.get_clade_level() > start_clade_level);
925
926                            if (iter.get_clade_level() < start_clade_level) {
927                                iter         = GroupIterator(start_group);
928                                inject_kcode = AW_KEY_HOME;
929                            }
930                        }
931                        break;
932
933                    case AW_KEY_HOME: {
934                        AP_tree *parent = DOWNCAST(AP_tree*, iter.node()->find_parent_clade());
935                        if (parent) {
936                            iter         = GroupIterator(parent);
937                            inject_kcode = AW_KEY_RIGHT;
938                        }
939                        else {
940                            iter = GroupIterator(get_root_node());
941                        }
942                        break;
943                    }
944                    case AW_KEY_END: {
945                        AP_tree *last_visited = NULp;
946                        do {
947                            if (iter.get_clade_level() == start_clade_level) {
948                                last_visited = iter.node();
949                            }
950                            iter.next();
951                            if (iter.node() == start_group) break;
952                        }
953                        while (iter.get_clade_level() >= start_clade_level);
954
955                        td_assert(last_visited);
956                        iter = GroupIterator(last_visited);
957                        break;
958                    }
959                    case AW_KEY_LEFT: {
960                        // first  keypress: fold if auto-unfolded
961                        // second keypress: select parent
962                        bool refoldFirst = autoUnfolded && autoUnfolded->node_is_auto_unfolded(start_group);
963                        if (!refoldFirst) {
964                            AP_tree *parent  = DOWNCAST(AP_tree*, iter.node()->find_parent_clade());
965                            if (parent) iter = GroupIterator(parent);
966                        }
967                        // else just dont move (will refold group the group)
968                        break;
969                    }
970                    case AW_KEY_RIGHT: {
971                        iter.next();
972                        if (iter.node()->find_parent_clade() != start_group) { // has no child group =>
973                            iter      = GroupIterator(start_group); // set back ..
974                            unfold_to = start_group->get_leftson(); // .. and temp. show group content
975                        }
976                        break;
977                    }
978
979                    default:
980                        td_assert(0); // avoid
981                        break;
982                }
983
984                if (inject_kcode == AW_KEY_NONE) at_target = true;
985                else                             kcode     = inject_kcode; // simulate keystroke:
986            }
987
988            if (iter.valid()) {
989                AP_tree *jump_to = iter.node();
990                select_group(jump_to);
991                auto_unfold(unfold_to ? unfold_to : jump_to);
992#if defined(DEBUG)
993                fprintf(stderr, "selected group '%s' (clade-level=%i)\n", jump_to->get_group_name(), jump_to->calc_clade_level());
994#endif
995            }
996            else {
997                deselect_group();
998            }
999            exports.request_refresh();
1000        }
1001    }
1002
1003    return handled;
1004}
1005
1006void AWT_graphic_tree::toggle_folding_at(AP_tree *at, bool force_jump) {
1007    if (at && !at->is_leaf() && at->is_clade()) {
1008        bool     wasFolded  = at->is_folded_group();
1009        AP_tree *top_change = NULp;
1010
1011        if (at->is_normal_group()) {
1012            top_change = at;
1013            top_change->gr.grouped = !wasFolded;
1014        }
1015        if (at->is_keeled_group()) {
1016            top_change = at->get_father();
1017            top_change->gr.grouped = !wasFolded;
1018        }
1019
1020        td_assert(top_change);
1021
1022        if (!force_jump) {
1023            select_group(at);
1024        }
1025        fast_sync_changed_folding(top_change);
1026        if (force_jump) {
1027            deselect_group();
1028            select_group(at);
1029        }
1030    }
1031}
1032
1033void AWT_graphic_tree::handle_key(AW_device *device, AWT_graphic_event& event) {
1034    //! handles AW_Event_Type = AW_Keyboard_Press.
1035
1036    td_assert(event.type() == AW_Keyboard_Press);
1037
1038    if (event.key_code() == AW_KEY_NONE) return;
1039    if (event.key_code() == AW_KEY_ASCII && event.key_char() == 0) return;
1040
1041#if defined(DEBUG) && 0
1042    printf("key_char=%i (=%c)\n", int(event.key_char()), event.key_char());
1043#endif // DEBUG
1044
1045    // ------------------------
1046    //      drag&drop keys
1047
1048    if (event.key_code() == AW_KEY_ESCAPE) {
1049        AWT_command_data *cmddata = get_command_data();
1050        if (cmddata) {
1051            Dragged *dragging = dynamic_cast<Dragged*>(cmddata);
1052            if (dragging) {
1053                dragging->hide_drag_indicator(device, drag_gc);
1054                dragging->abort(); // abort what ever we did
1055                store_command_data(NULp);
1056            }
1057        }
1058    }
1059
1060    // ----------------------------------------
1061    //      commands independent of tree :
1062
1063    bool handled = true;
1064    switch (event.key_char()) {
1065        case 9: {     // Ctrl-i = invert all
1066            GBT_mark_all(gb_main, 2);
1067            exports.request_structure_update();
1068            break;
1069        }
1070        case 13: {     // Ctrl-m = mark/unmark all
1071            int mark   = !GBT_first_marked_species(gb_main); // no species marked -> mark all
1072            GBT_mark_all(gb_main, mark);
1073            exports.request_structure_update();
1074            break;
1075        }
1076        case ' ': { // Space = toggle mark(s) of selected species/group
1077            if (species_name[0]) {
1078                GB_transaction  ta(gb_main);
1079                GBDATA         *gb_species = GBT_find_species(gb_main, species_name);
1080                if (gb_species) {
1081                    GB_write_flag(gb_species, !GB_read_flag(gb_species));
1082                    exports.request_structure_update();
1083                }
1084            }
1085            else {
1086                AP_tree *subtree = find_selected_group();
1087                if (subtree) {
1088                    mark_species_in_tree(subtree, !tree_has_marks(subtree));
1089                    exports.request_structure_update();
1090                }
1091            }
1092            break;
1093        }
1094        default: handled = false; break;
1095    }
1096
1097    if (!handled) {
1098        handled = true;
1099        switch (event.key_code()) {
1100            case AW_KEY_RETURN: // Return = fold/unfold selected group
1101                toggle_folding_at(find_selected_group(), true);
1102                break;
1103            default: handled = false; break;
1104        }
1105    }
1106
1107    // -------------------------
1108    //      cursor movement
1109    if (!handled && is_cursor_keycode(event.key_code())) {
1110        handled = handle_cursor(event.key_code(), event.key_modifier());
1111    }
1112
1113    if (!handled) {
1114        // handle key events specific to pointed-to tree-element
1115        ClickedTarget pointed(this, event.best_click());
1116
1117        if (pointed.species()) {
1118            handled = true;
1119            switch (event.key_char()) {
1120                case 'i':
1121                case 'm': {     // i/m = mark/unmark species
1122                    GB_write_flag(pointed.species(), 1-GB_read_flag(pointed.species()));
1123                    exports.request_structure_update();
1124                    break;
1125                }
1126                case 'I': {     // I = invert all but current
1127                    int mark = GB_read_flag(pointed.species());
1128                    GBT_mark_all(gb_main, 2);
1129                    GB_write_flag(pointed.species(), mark);
1130                    exports.request_structure_update();
1131                    break;
1132                }
1133                case 'M': {     // M = mark/unmark all but current
1134                    int mark = GB_read_flag(pointed.species());
1135                    GB_write_flag(pointed.species(), 0); // unmark current
1136                    GBT_mark_all(gb_main, !GBT_first_marked_species(gb_main));
1137                    GB_write_flag(pointed.species(), mark); // restore mark of current
1138                    exports.request_structure_update();
1139                    break;
1140                }
1141                default: handled = false; break;
1142            }
1143        }
1144
1145        if (!handled && event.key_char() == '0') {
1146            // handle reset-key promised by
1147            // - KEYINFO_ABORT_AND_RESET (AWT_MODE_ROTATE, AWT_MODE_LENGTH, AWT_MODE_MULTIFURC, AWT_MODE_LINE, AWT_MODE_SPREAD)
1148            // - KEYINFO_RESET (AWT_MODE_LZOOM)
1149
1150            if (event.cmd() == AWT_MODE_LZOOM) {
1151                set_logical_root_to(get_root_node());
1152                exports.request_zoom_reset();
1153            }
1154            else if (pointed.is_ruler()) {
1155                GBDATA *gb_tree = tree_static->get_gb_tree();
1156                td_assert(gb_tree);
1157
1158                switch (event.cmd()) {
1159                    case AWT_MODE_ROTATE: break; // ruler has no rotation
1160                    case AWT_MODE_SPREAD: break; // ruler has no spread
1161                    case AWT_MODE_LENGTH: {
1162                        GB_transaction ta(gb_tree);
1163                        GBDATA *gb_ruler_size = GB_searchOrCreate_float(gb_tree, RULER_SIZE, DEFAULT_RULER_LENGTH);
1164                        GB_write_float(gb_ruler_size, DEFAULT_RULER_LENGTH);
1165                        exports.request_structure_update();
1166                        break;
1167                    }
1168                    case AWT_MODE_LINE: {
1169                        GB_transaction ta(gb_tree);
1170                        GBDATA *gb_ruler_width = GB_searchOrCreate_int(gb_tree, RULER_LINEWIDTH, DEFAULT_RULER_LINEWIDTH);
1171                        GB_write_int(gb_ruler_width, DEFAULT_RULER_LINEWIDTH);
1172                        exports.request_structure_update();
1173                        break;
1174                    }
1175                    default: break;
1176                }
1177            }
1178            else if (pointed.node()) {
1179                if (warn_inappropriate_mode(event.cmd())) return;
1180                switch (event.cmd()) {
1181                    case AWT_MODE_ROTATE:    pointed.node()->reset_subtree_angles();         exports.request_save(); break;
1182                    case AWT_MODE_LENGTH:    pointed.node()->set_branchlength_unrooted(0.0); exports.request_save(); break;
1183                    case AWT_MODE_MULTIFURC: pointed.node()->multifurcate();                 exports.request_save(); break;
1184                    case AWT_MODE_LINE:      pointed.node()->reset_subtree_linewidths();     exports.request_save(); break;
1185                    case AWT_MODE_SPREAD:    pointed.node()->reset_subtree_spreads();        exports.request_save(); break;
1186                    default: break;
1187                }
1188            }
1189            return;
1190        }
1191
1192        if (!handled && pointed.node()) {
1193            handled = true;
1194            switch (event.key_char()) {
1195                case 'm': {     // m = mark/unmark (sub)tree
1196                    mark_species_in_tree(pointed.node(), !tree_has_marks(pointed.node()));
1197                    exports.request_structure_update();
1198                    break;
1199                }
1200                case 'M': {     // M = mark/unmark all but (sub)tree
1201                    mark_species_in_rest_of_tree(pointed.node(), !rest_tree_has_marks(pointed.node()));
1202                    exports.request_structure_update();
1203                    break;
1204                }
1205
1206                case 'i': {     // i = invert (sub)tree
1207                    mark_species_in_tree(pointed.node(), 2);
1208                    exports.request_structure_update();
1209                    break;
1210                }
1211                case 'I': {     // I = invert all but (sub)tree
1212                    mark_species_in_rest_of_tree(pointed.node(), 2);
1213                    exports.request_structure_update();
1214                    break;
1215                }
1216                case 'c':
1217                case 'x': {
1218                    AWT_graphic_tree_group_state  state;
1219                    AP_tree                      *at = pointed.node();
1220
1221                    detect_group_state(at, &state, NULp);
1222
1223                    if (!state.has_groups()) { // somewhere inside group
1224do_parent :
1225                        at  = at->get_father();
1226                        while (at) {
1227                            if (at->is_normal_group()) break;
1228                            at = at->get_father();
1229                        }
1230
1231                        if (at) {
1232                            state.clear();
1233                            detect_group_state(at, &state, NULp);
1234                        }
1235                    }
1236
1237                    if (at) {
1238                        CollapseMode next_group_mode;
1239
1240                        if (event.key_char() == 'x') {  // expand
1241                            next_group_mode = state.next_expand_mode();
1242                        }
1243                        else { // collapse
1244                            if (state.all_closed()) goto do_parent;
1245                            next_group_mode = state.next_collapse_mode();
1246                        }
1247
1248                        group_tree(at, next_group_mode, 0);
1249                        fast_sync_changed_folding(at);
1250                    }
1251                    break;
1252                }
1253                case 'C':
1254                case 'X': {
1255                    AP_tree *root_node = pointed.node();
1256                    while (root_node->father) root_node = root_node->get_father(); // search father
1257
1258                    td_assert(root_node);
1259
1260                    AWT_graphic_tree_group_state state;
1261                    detect_group_state(root_node, &state, pointed.node());
1262
1263                    CollapseMode next_group_mode;
1264                    if (event.key_char() == 'X') {  // expand
1265                        next_group_mode = state.next_expand_mode();
1266                    }
1267                    else { // collapse
1268                        next_group_mode = state.next_collapse_mode();
1269                    }
1270
1271                    group_rest_tree(pointed.node(), next_group_mode, 0);
1272                    fast_sync_changed_folding(root_node);
1273
1274                    break;
1275                }
1276                default: handled = false; break;
1277            }
1278        }
1279    }
1280}
1281
1282static bool command_on_GBDATA(GBDATA *gbd, const AWT_graphic_event& event, AD_map_viewer_cb map_viewer_cb) {
1283    // modes listed here are available in ALL tree-display-modes (i.e. as well in listmode)
1284
1285    bool refresh = false;
1286
1287    if (event.type() == AW_Mouse_Press && event.button() != AW_BUTTON_MIDDLE) {
1288        AD_MAP_VIEWER_TYPE selectType = ADMVT_NONE;
1289
1290        switch (event.cmd()) {
1291            case AWT_MODE_MARK: // see also .@OTHER_MODE_MARK_HANDLER
1292                switch (event.button()) {
1293                    case AW_BUTTON_LEFT:
1294                        GB_write_flag(gbd, 1);
1295                        selectType = ADMVT_SELECT;
1296                        break;
1297                    case AW_BUTTON_RIGHT:
1298                        GB_write_flag(gbd, 0);
1299                        break;
1300                    default:
1301                        break;
1302                }
1303                refresh = true;
1304                break;
1305
1306            case AWT_MODE_WWW:  selectType = ADMVT_WWW;    break;
1307            case AWT_MODE_INFO: selectType = ADMVT_INFO;   break;
1308            default:            selectType = ADMVT_SELECT; break;
1309        }
1310
1311        if (selectType != ADMVT_NONE) {
1312            map_viewer_cb(gbd, selectType);
1313            refresh = true;
1314        }
1315    }
1316
1317    return refresh;
1318}
1319
1320class ClickedElement {
1321    /*! Stores a copy of AW_clicked_element.
1322     * Used as Drag&Drop source and target.
1323     */
1324    AW_clicked_element *elem;
1325
1326public:
1327    ClickedElement(const AW_clicked_element& e) : elem(e.clone()) {}
1328    ClickedElement(const ClickedElement& other) : elem(other.element()->clone()) {}
1329    DECLARE_ASSIGNMENT_OPERATOR(ClickedElement);
1330    ~ClickedElement() { delete elem; }
1331
1332    const AW_clicked_element *element() const { return elem; }
1333
1334    bool operator == (const ClickedElement& other) const { return *element() == *other.element(); }
1335    bool operator != (const ClickedElement& other) const { return !(*this == other); }
1336};
1337
1338class DragNDrop : public Dragged {
1339    ClickedElement Drag, Drop;
1340
1341    virtual void perform_drop() = 0;
1342
1343    void drag(const AW_clicked_element *drag_target)  {
1344        Drop = drag_target ? *drag_target : Drag;
1345    }
1346    void drop(const AW_clicked_element *drop_target) {
1347        drag(drop_target);
1348        perform_drop();
1349    }
1350
1351    void perform(DragAction action, const AW_clicked_element *target, const Position&) FINAL_OVERRIDE {
1352        switch (action) {
1353            case DRAGGING: drag(target); break;
1354            case DROPPED:  drop(target); break;
1355        }
1356    }
1357
1358    void abort() OVERRIDE {
1359        perform(DROPPED, Drag.element(), Position()); // drop dragged element onto itself to abort
1360    }
1361
1362protected:
1363    const AW_clicked_element *source_element() const { return Drag.element(); }
1364    const AW_clicked_element *dest_element() const { return Drop.element(); }
1365
1366public:
1367    DragNDrop(const AW_clicked_element *dragFrom, AWT_graphic_exports& exports_) :
1368        Dragged(exports_),
1369        Drag(*dragFrom),
1370        Drop(Drag)
1371    {}
1372
1373    void draw_drag_indicator(AW_device *device, int drag_gc) const FINAL_OVERRIDE {
1374        td_assert(valid_drag_device(device));
1375        source_element()->indicate_selected(device, drag_gc);
1376        if (Drag != Drop) {
1377            dest_element()->indicate_selected(device, drag_gc);
1378            device->line(drag_gc, source_element()->get_connecting_line(*dest_element()));
1379        }
1380    }
1381};
1382
1383class BranchMover : public DragNDrop {
1384    AW_MouseButton    button;
1385    AWT_graphic_tree& agt;
1386
1387    void perform_drop() OVERRIDE {
1388        ClickedTarget source(source_element());
1389        ClickedTarget dest(dest_element());
1390
1391        if (source.node() && dest.node() && source.node() != dest.node()) {
1392            GB_ERROR  error   = NULp;
1393            GBDATA   *gb_node = source.node()->gb_node;
1394            agt.deselect_group();
1395            switch (button) {
1396                case AW_BUTTON_LEFT:
1397                    error = source.node()->cantMoveNextTo(dest.node());
1398                    if (!error) source.node()->moveNextTo(dest.node(), dest.get_rel_attach());
1399                    break;
1400
1401                case AW_BUTTON_RIGHT:
1402                    error = source.node()->move_group_to(dest.node());
1403                    break;
1404                default:
1405                    td_assert(0);
1406                    break;
1407            }
1408
1409            if (error) {
1410                aw_message(error);
1411            }
1412            else {
1413                get_exports().request_save();
1414                bool group_moved = !source.node()->is_leaf() && source.node()->is_normal_group();
1415                if (group_moved) agt.select_group(gb_node);
1416            }
1417        }
1418        else {
1419#if defined(DEBUG)
1420            if (!source.node()) printf("No source.node\n");
1421            if (!dest.node()) printf("No dest.node\n");
1422            if (dest.node() == source.node()) printf("source==dest\n");
1423#endif
1424        }
1425    }
1426
1427public:
1428    BranchMover(const AW_clicked_element *dragFrom, AW_MouseButton button_, AWT_graphic_tree& agt_) :
1429        DragNDrop(dragFrom, agt_.exports),
1430        button(button_),
1431        agt(agt_)
1432    {}
1433};
1434
1435
1436class Scaler : public Dragged {
1437    Position mouse_start; // screen-coordinates
1438    Position last_drag_pos;
1439    double unscale;
1440
1441    virtual void draw_scale_indicator(const AW::Position& drag_pos, AW_device *device, int drag_gc) const = 0;
1442    virtual void do_scale(const Position& drag_pos) = 0;
1443
1444    void perform(DragAction action, const AW_clicked_element *, const Position& mousepos) FINAL_OVERRIDE {
1445        switch (action) {
1446            case DRAGGING:
1447                last_drag_pos = mousepos;
1448                FALLTHROUGH; // aka instantly apply drop-action while dragging
1449            case DROPPED:
1450                do_scale(mousepos);
1451                break;
1452        }
1453    }
1454    void abort() OVERRIDE {
1455        perform(DROPPED, NULp, mouse_start); // drop exactly where dragging started
1456    }
1457
1458
1459protected:
1460    const Position& startpos() const { return mouse_start; }
1461    Vector scaling(const Position& current) const { return Vector(mouse_start, current)*unscale; } // returns world-coordinates
1462
1463public:
1464    Scaler(const Position& start, double unscale_, AWT_graphic_exports& exports_)
1465        : Dragged(exports_),
1466          mouse_start(start),
1467          last_drag_pos(start),
1468          unscale(unscale_)
1469    {
1470        td_assert(!is_nan_or_inf(unscale));
1471    }
1472
1473    void draw_drag_indicator(AW_device *device, int drag_gc) const FINAL_OVERRIDE {
1474        draw_scale_indicator(last_drag_pos, device, drag_gc);
1475    }
1476};
1477
1478inline double discrete_value(double analog_value, int discretion_factor) {
1479    // discretion_factor:
1480    //     10 -> 1st digit behind dot
1481    //    100 -> 2nd ------- " ------
1482    //   1000 -> 3rd ------- " ------
1483
1484    if (analog_value<0.0) return -discrete_value(-analog_value, discretion_factor);
1485    return int(analog_value*discretion_factor+0.5)/double(discretion_factor);
1486}
1487
1488class DB_scalable {
1489    //! a DB entry scalable by dragging
1490    GBDATA   *gbd;
1491    GB_TYPES  type;
1492    float     min;
1493    float     max;
1494    int       discretion_factor;
1495    bool      inversed;
1496
1497    static CONSTEXPR double INTSCALE = 100.0;
1498
1499    void init() {
1500        min = -DBL_MAX;
1501        max =  DBL_MAX;
1502
1503        discretion_factor = 0;
1504        inversed          = false;
1505    }
1506
1507public:
1508    DB_scalable() : gbd(NULp), type(GB_NONE) { init(); }
1509    DB_scalable(GBDATA *gbd_) : gbd(gbd_), type(GB_read_type(gbd)) { init(); }
1510
1511    GBDATA *data() { return gbd; }
1512
1513    float read() {
1514        float res = 0.0;
1515        switch (type) {
1516            case GB_FLOAT: res = GB_read_float(gbd);        break;
1517            case GB_INT:   res = GB_read_int(gbd)/INTSCALE; break;
1518            default: break;
1519        }
1520        return inversed ? -res : res;
1521    }
1522    bool write(float val) {
1523        float old = read();
1524
1525        if (inversed) val = -val;
1526
1527        val = val<=min ? min : (val>=max ? max : val);
1528        val = discretion_factor ? discrete_value(val, discretion_factor) : val;
1529
1530        switch (type) {
1531            case GB_FLOAT:
1532                GB_write_float(gbd, val);
1533                break;
1534            case GB_INT:
1535                GB_write_int(gbd, int(val*INTSCALE+0.5));
1536                break;
1537            default: break;
1538        }
1539
1540        return old != read();
1541    }
1542
1543    void set_discretion_factor(int df) { discretion_factor = df; }
1544    void set_min(float val) { min = (type == GB_INT) ? val*INTSCALE : val; }
1545    void set_max(float val) { max = (type == GB_INT) ? val*INTSCALE : val; }
1546    void inverse() { inversed = !inversed; }
1547};
1548
1549class RulerScaler : public Scaler { // derived from Noncopyable
1550    Position    awar_start;
1551    DB_scalable x, y; // DB entries scaled by x/y movement
1552
1553    GBDATA *gbdata() {
1554        GBDATA *gbd   = x.data();
1555        if (!gbd) gbd = y.data();
1556        td_assert(gbd);
1557        return gbd;
1558    }
1559
1560    Position read_pos() { return Position(x.read(), y.read()); }
1561    bool write_pos(Position p) {
1562        bool xchanged = x.write(p.xpos());
1563        bool ychanged = y.write(p.ypos());
1564        return xchanged || ychanged;
1565    }
1566
1567    void draw_scale_indicator(const AW::Position& , AW_device *, int) const {}
1568    void do_scale(const Position& drag_pos) {
1569        GB_transaction ta(gbdata());
1570        if (write_pos(awar_start+scaling(drag_pos))) get_exports().request_refresh();
1571    }
1572public:
1573    RulerScaler(const Position& start, double unscale_, const DB_scalable& xs, const DB_scalable& ys, AWT_graphic_exports& exports_)
1574        : Scaler(start, unscale_, exports_),
1575          x(xs),
1576          y(ys)
1577    {
1578        GB_transaction ta(gbdata());
1579        awar_start = read_pos();
1580    }
1581};
1582
1583static void text_near_head(AW_device *device, int gc, const LineVector& line, const char *text) {
1584    // @@@ should keep a little distance between the line-head and the text (depending on line orientation)
1585    Position at = line.head();
1586    device->text(gc, text, at);
1587}
1588
1589enum ScaleMode { SCALE_LENGTH, SCALE_LENGTH_PRESERVING, SCALE_SPREAD };
1590
1591class BranchScaler : public Scaler { // derived from Noncopyable
1592    ScaleMode  mode;
1593    AP_tree   *node;
1594
1595    float start_val;   // length or spread
1596    bool  zero_val_removed;
1597
1598    LineVector branch;
1599    Position   attach; // point on 'branch' (next to click position)
1600
1601    int discretion_factor;  // !=0 = > scale to discrete values
1602
1603    bool allow_neg_val;
1604
1605    float get_val() const {
1606        switch (mode) {
1607            case SCALE_LENGTH_PRESERVING:
1608            case SCALE_LENGTH: return node->get_branchlength_unrooted();
1609            case SCALE_SPREAD: return node->gr.spread;
1610        }
1611        td_assert(0);
1612        return 0.0;
1613    }
1614    void set_val(float val) {
1615        switch (mode) {
1616            case SCALE_LENGTH_PRESERVING: node->set_branchlength_preserving(val); break;
1617            case SCALE_LENGTH: node->set_branchlength_unrooted(val); break;
1618            case SCALE_SPREAD: node->gr.spread = val; break;
1619        }
1620    }
1621
1622    void init_discretion_factor(bool discrete) {
1623        if (start_val != 0 && discrete) {
1624            discretion_factor = 10;
1625            while ((start_val*discretion_factor)<1) {
1626                discretion_factor *= 10;
1627            }
1628        }
1629        else {
1630            discretion_factor = 0;
1631        }
1632    }
1633
1634    Position get_dragged_attach(const AW::Position& drag_pos) const {
1635        // return dragged position of 'attach'
1636        Vector moved      = scaling(drag_pos);
1637        Vector attach2tip = branch.head()-attach;
1638
1639        if (attach2tip.length()>0) {
1640            Vector   moveOnBranch = orthogonal_projection(moved, attach2tip);
1641            return attach+moveOnBranch;
1642        }
1643        Vector attach2base = branch.start()-attach;
1644        if (attach2base.length()>0) {
1645            Vector moveOnBranch = orthogonal_projection(moved, attach2base);
1646            return attach+moveOnBranch;
1647        }
1648        return Position(); // no position
1649    }
1650
1651
1652    void draw_scale_indicator(const AW::Position& drag_pos, AW_device *device, int drag_gc) const {
1653        td_assert(valid_drag_device(device));
1654        Position attach_dragged = get_dragged_attach(drag_pos);
1655        if (attach_dragged.valid()) {
1656            Position   drag_world = device->rtransform(drag_pos);
1657            LineVector to_dragged(attach_dragged, drag_world);
1658            LineVector to_start(attach, -to_dragged.line_vector());
1659
1660            device->set_line_attributes(drag_gc, 1, AW_SOLID);
1661
1662            device->line(drag_gc, to_start);
1663            device->line(drag_gc, to_dragged);
1664
1665            text_near_head(device, drag_gc, to_start,   GBS_global_string("old=%.3f", start_val));
1666            text_near_head(device, drag_gc, to_dragged, GBS_global_string("new=%.3f", get_val()));
1667        }
1668
1669        device->set_line_attributes(drag_gc, 3, AW_SOLID);
1670        device->line(drag_gc, branch);
1671    }
1672
1673    void do_scale(const Position& drag_pos) {
1674        double oldval = get_val();
1675
1676        if (start_val == 0.0) { // can't scale
1677            if (!zero_val_removed) {
1678                switch (mode) {
1679                    case SCALE_LENGTH:
1680                    case SCALE_LENGTH_PRESERVING:
1681                        set_val(tree_defaults::LENGTH); // fake branchlength (can't scale zero-length branches)
1682                        aw_message("Cannot scale zero sized branches\nBranchlength has been set to 0.1\nNow you may scale the branch");
1683                        break;
1684                    case SCALE_SPREAD:
1685                        set_val(tree_defaults::SPREAD); // reset spread (can't scale unspreaded branches)
1686                        aw_message("Cannot spread unspreaded branches\nSpreading has been set to 1.0\nNow you may spread the branch"); // @@@ clumsy
1687                        break;
1688                }
1689                zero_val_removed = true;
1690            }
1691        }
1692        else {
1693            Position attach_dragged = get_dragged_attach(drag_pos);
1694            if (attach_dragged.valid()) {
1695                Vector to_attach(branch.start(), attach);
1696                Vector to_attach_dragged(branch.start(), attach_dragged);
1697
1698                double tal = to_attach.length();
1699                double tdl = to_attach_dragged.length();
1700
1701                if (tdl>0.0 && tal>0.0) {
1702                    bool   negate = are_antiparallel(to_attach, to_attach_dragged);
1703                    double scale  = tdl/tal * (negate ? -1 : 1);
1704
1705                    float val = start_val * scale;
1706                    if (val<0.0) {
1707                        if (node->is_leaf() || !allow_neg_val) {
1708                            val = 0.0; // do NOT accept negative values
1709                        }
1710                    }
1711                    if (discretion_factor) {
1712                        val = discrete_value(val, discretion_factor);
1713                    }
1714                    set_val(NONAN(val));
1715                }
1716            }
1717        }
1718
1719        if (oldval != get_val()) {
1720            get_exports().request_save();
1721        }
1722    }
1723
1724public:
1725
1726    BranchScaler(ScaleMode mode_, AP_tree *node_, const LineVector& branch_, const Position& attach_, const Position& start, double unscale_, bool discrete, bool allow_neg_values_, AWT_graphic_exports& exports_)
1727        : Scaler(start, unscale_, exports_),
1728          mode(mode_),
1729          node(node_),
1730          start_val(get_val()),
1731          zero_val_removed(false),
1732          branch(branch_),
1733          attach(attach_),
1734          allow_neg_val(allow_neg_values_)
1735    {
1736        init_discretion_factor(discrete);
1737    }
1738};
1739
1740class BranchLinewidthScaler : public Scaler, virtual Noncopyable {
1741    AP_tree *node;
1742    int      start_width;
1743    bool     wholeSubtree;
1744
1745public:
1746    BranchLinewidthScaler(AP_tree *node_, const Position& start, bool wholeSubtree_, AWT_graphic_exports& exports_)
1747        : Scaler(start, 0.1, exports_), // 0.1 = > change linewidth dragpixel/10
1748          node(node_),
1749          start_width(node->get_linewidth()),
1750          wholeSubtree(wholeSubtree_)
1751    {}
1752
1753    void draw_scale_indicator(const AW::Position& , AW_device *, int) const OVERRIDE {}
1754    void do_scale(const Position& drag_pos) OVERRIDE {
1755        Vector moved = scaling(drag_pos);
1756        double ymove = -moved.y();
1757        int    old   = node->get_linewidth();
1758
1759        int width = start_width + ymove;
1760        if (width<tree_defaults::LINEWIDTH) width = tree_defaults::LINEWIDTH;
1761
1762        if (width != old) {
1763            if (wholeSubtree) {
1764                node->set_linewidth_recursive(width);
1765            }
1766            else {
1767                node->set_linewidth(width);
1768            }
1769            get_exports().request_save();
1770        }
1771    }
1772};
1773
1774class BranchRotator FINAL_TYPE : public Dragged, virtual Noncopyable {
1775    AW_device  *device;
1776    AP_tree    *node;
1777    LineVector  clicked_branch;
1778    float       orig_angle;      // of node
1779    Position    hinge;
1780    Position    mousepos_world;
1781
1782    void perform(DragAction, const AW_clicked_element *, const Position& mousepos) OVERRIDE {
1783        mousepos_world = device->rtransform(mousepos);
1784
1785        double prev_angle = node->get_angle();
1786
1787        Angle current(hinge, mousepos_world);
1788        Angle orig(clicked_branch.line_vector());
1789        Angle diff = current-orig;
1790
1791        node->set_angle(orig_angle + diff.radian());
1792
1793        if (node->get_angle() != prev_angle) get_exports().request_save();
1794    }
1795
1796    void abort() OVERRIDE {
1797        node->set_angle(orig_angle);
1798        get_exports().request_save();
1799    }
1800
1801public:
1802    BranchRotator(AW_device *device_, AP_tree *node_, const LineVector& clicked_branch_, const Position& mousepos, AWT_graphic_exports& exports_)
1803        : Dragged(exports_),
1804          device(device_),
1805          node(node_),
1806          clicked_branch(clicked_branch_),
1807          orig_angle(node->get_angle()),
1808          hinge(clicked_branch.start()),
1809          mousepos_world(device->rtransform(mousepos))
1810    {
1811        td_assert(valid_drag_device(device));
1812    }
1813
1814    void draw_drag_indicator(AW_device *IF_DEBUG(same_device), int drag_gc) const OVERRIDE {
1815        td_assert(valid_drag_device(same_device));
1816        td_assert(device == same_device);
1817
1818        device->line(drag_gc, clicked_branch);
1819        device->line(drag_gc, LineVector(hinge, mousepos_world));
1820        device->circle(drag_gc, AW::FillStyle::EMPTY, hinge, device->rtransform(Vector(5, 5)));
1821    }
1822};
1823
1824inline Position calc_text_coordinates_near_tip(AW_device *device, int gc, const Position& pos, const Angle& orientation, AW_pos& alignment, double dist_factor = 1.0) {
1825    /*! calculates text coordinates for text placed at the tip of a vector
1826     * @param device      output device
1827     * @param gc          context
1828     * @param pos         tip of the vector (world coordinates)
1829     * @param orientation orientation of the vector (towards its tip)
1830     * @param alignment   result param (alignment for call to text())
1831     * @param dist_factor normally 1.0 (smaller => text nearer towards 'pos')
1832     */
1833    const AW_font_limits& charLimits = device->get_font_limits(gc, 'A');
1834
1835    const double text_height = charLimits.get_height() * device->get_unscale();
1836    const double dist        = text_height * dist_factor;
1837
1838    Vector shift = orientation.normal();
1839    // use sqrt of sin(=y) to move text faster between positions below and above branch:
1840    shift.sety(shift.y()>0 ? sqrt(shift.y()) : -sqrt(-shift.y()));
1841
1842    Position near = pos + dist*shift;
1843    near.movey(.3*text_height); // @@@ just a hack. fix.
1844
1845    alignment = .5 - .5*orientation.cos();
1846
1847    return near;
1848}
1849
1850inline Position calc_text_coordinates_aside_line(AW_device *device, int gc, const Position& pos, Angle orientation, bool right, AW_pos& alignment, double dist_factor = 1.0) {
1851    /*! calculates text coordinates for text placed aside of a vector
1852     * @param device      output device
1853     * @param gc          context
1854     * @param pos         position on the vector, e.g. center of vector (world coordinates)
1855     * @param orientation orientation of the vector (towards its tip)
1856     * @param right       true -> on right side of vector (otherwise on left side)
1857     * @param alignment   result param (alignment for call to text())
1858     * @param dist_factor normally 1.0 (smaller => text nearer towards 'pos')
1859     */
1860
1861    return calc_text_coordinates_near_tip(device, gc, pos, right ? orientation.rotate90deg() : orientation.rotate270deg(), alignment, dist_factor);
1862}
1863
1864class MarkerIdentifier : public Dragged, virtual Noncopyable {
1865    AW_clicked_element *marker; // maybe box, line or text!
1866    Position            click;
1867    std::string         name;
1868
1869    void draw_drag_indicator(AW_device *device, int drag_gc) const OVERRIDE {
1870        Position  click_world = device->rtransform(click);
1871        Rectangle bbox        = marker->get_bounding_box();
1872        Position  center      = bbox.centroid();
1873
1874        Vector toClick(center, click_world);
1875        {
1876            double minLen = Vector(center, bbox.nearest_corner(click_world)).length();
1877            if (toClick.length()<minLen) toClick.set_length(minLen);
1878        }
1879        LineVector toHead(center, 1.5*toClick);
1880
1881        marker->indicate_selected(device, drag_gc);
1882        device->line(drag_gc, toHead);
1883
1884        Angle    orientation(toHead.line_vector());
1885        AW_pos   alignment;
1886        Position textPos = calc_text_coordinates_near_tip(device, drag_gc, toHead.head(), Angle(toHead.line_vector()), alignment);
1887
1888        device->text(drag_gc, name.c_str(), textPos, alignment);
1889    }
1890    void perform(DragAction, const AW_clicked_element*, const Position& mousepos) OVERRIDE {
1891        click = mousepos;
1892        get_exports().request_refresh();
1893    }
1894    void abort() OVERRIDE {
1895        get_exports().request_refresh();
1896    }
1897
1898public:
1899    MarkerIdentifier(const AW_clicked_element *marker_, const Position& start, const char *name_, AWT_graphic_exports& exports_)
1900        : Dragged(exports_),
1901          marker(marker_->clone()),
1902          click(start),
1903          name(name_)
1904    {
1905        get_exports().request_refresh();
1906    }
1907    ~MarkerIdentifier() {
1908        delete marker;
1909    }
1910
1911};
1912
1913static AW_device_click::ClickPreference preferredForCommand(AWT_COMMAND_MODE mode) {
1914    // return preferred click target for tree-display
1915    // (Note: not made this function a member of AWT_graphic_event,
1916    //  since modes are still reused in other ARB applications,
1917    //  e.g. AWT_MODE_ROTATE in SECEDIT)
1918
1919    switch (mode) {
1920        case AWT_MODE_LENGTH:
1921        case AWT_MODE_MULTIFURC:
1922        case AWT_MODE_SPREAD:
1923            return AW_device_click::PREFER_LINE;
1924
1925        default:
1926            return AW_device_click::PREFER_NEARER;
1927    }
1928}
1929
1930void AWT_graphic_tree::handle_command(AW_device *device, AWT_graphic_event& event) {
1931    td_assert(event.button()!=AW_BUTTON_MIDDLE); // shall be handled by caller
1932
1933    if (!tree_static) return;                      // no tree -> no commands
1934
1935    if (event.type() == AW_Keyboard_Release) return;
1936    if (event.type() == AW_Keyboard_Press) return handle_key(device, event);
1937
1938    // @@@ move code below into separate member function handle_mouse()
1939
1940    if (event.button() != AW_BUTTON_LEFT && event.button() != AW_BUTTON_RIGHT) return; // nothing else is currently handled here
1941
1942    ClickedTarget clicked(this, event.best_click(preferredForCommand(event.cmd())));
1943    // Note: during drag/release 'clicked'
1944    //       - contains drop-target (only if AWT_graphic::drag_target_detection is requested)
1945    //       - no longer contains initially clicked element (in all other modes)
1946    // see also ../../AWT/AWT_canvas.cxx@motion_event
1947
1948    if (clicked.species()) {
1949        if (command_on_GBDATA(clicked.species(), event, map_viewer_cb)) {
1950            exports.request_refresh();
1951        }
1952        return;
1953    }
1954
1955    if (!tree_static->get_root_node()) return; // no tree -> no commands
1956
1957    const Position&  mousepos = event.position();
1958
1959    // -------------------------------------
1960    //      generic drag & drop handler
1961    {
1962        AWT_command_data *cmddata = get_command_data();
1963        if (cmddata) {
1964            Dragged *dragging = dynamic_cast<Dragged*>(cmddata);
1965            if (dragging) {
1966                dragging->hide_drag_indicator(device, drag_gc);
1967                if (event.type() == AW_Mouse_Press) {
1968                    // mouse pressed while dragging (e.g. press other button)
1969                    dragging->abort(); // abort what ever we did
1970                    store_command_data(NULp);
1971                }
1972                else {
1973                    switch (event.type()) {
1974                        case AW_Mouse_Drag:
1975                            dragging->do_drag(clicked.element(), mousepos);
1976                            dragging->draw_drag_indicator(device, drag_gc);
1977                            break;
1978
1979                        case AW_Mouse_Release:
1980                            dragging->do_drop(clicked.element(), mousepos);
1981                            store_command_data(NULp);
1982                            break;
1983                        default:
1984                            break;
1985                    }
1986                }
1987                return;
1988            }
1989        }
1990    }
1991
1992    if (event.type() != AW_Mouse_Press) return; // no drag/drop handling below!
1993
1994    if (clicked.is_ruler()) {
1995        DB_scalable  xdata;
1996        DB_scalable  ydata;
1997        double       unscale = device->get_unscale();
1998        GBDATA      *gb_tree = tree_static->get_gb_tree();
1999
2000        switch (event.cmd()) {
2001            case AWT_MODE_LENGTH:
2002            case AWT_MODE_MULTIFURC: { // scale ruler
2003                xdata = GB_searchOrCreate_float(gb_tree, RULER_SIZE, DEFAULT_RULER_LENGTH);
2004
2005                double rel  = clicked.get_rel_attach();
2006                if (tree_style == AP_TREE_IRS) {
2007                    unscale /= (rel-1)*irs_tree_ruler_scale_factor; // ruler has opposite orientation in IRS mode
2008                }
2009                else {
2010                    unscale /= rel;
2011                }
2012
2013                if (event.button() == AW_BUTTON_RIGHT) xdata.set_discretion_factor(10);
2014                xdata.set_min(0.01);
2015                break;
2016            }
2017            case AWT_MODE_LINE: // scale ruler linewidth
2018                ydata = GB_searchOrCreate_int(gb_tree, RULER_LINEWIDTH, DEFAULT_RULER_LINEWIDTH);
2019                ydata.set_min(0);
2020                ydata.inverse();
2021                break;
2022
2023            default: { // move ruler or ruler text
2024                bool isText = clicked.is_text();
2025                xdata = GB_searchOrCreate_float(gb_tree, ruler_awar(isText ? "text_x" : "ruler_x"), 0.0);
2026                ydata = GB_searchOrCreate_float(gb_tree, ruler_awar(isText ? "text_y" : "ruler_y"), 0.0);
2027                break;
2028            }
2029        }
2030        if (!is_nan_or_inf(unscale)) {
2031            store_command_data(new RulerScaler(mousepos, unscale, xdata, ydata, exports));
2032        }
2033        return;
2034    }
2035
2036    if (clicked.is_marker()) {
2037        if (clicked.element()->get_distance() <= 3) { // accept 3 pixel distance
2038            display_markers->handle_click(clicked.get_markerindex(), event.button(), exports);
2039            if (event.button() == AW_BUTTON_LEFT) {
2040                const char *name = display_markers->get_marker_name(clicked.get_markerindex());
2041                store_command_data(new MarkerIdentifier(clicked.element(), mousepos, name, exports));
2042            }
2043        }
2044        return;
2045    }
2046
2047    if (warn_inappropriate_mode(event.cmd())) {
2048        return;
2049    }
2050
2051    switch (event.cmd()) {
2052        // -----------------------------
2053        //      two point commands:
2054
2055        case AWT_MODE_MOVE:
2056            if (clicked.node() && clicked.node()->father) {
2057                drag_target_detection(true);
2058                BranchMover *mover = new BranchMover(clicked.element(), event.button(), *this);
2059                store_command_data(mover);
2060                mover->draw_drag_indicator(device, drag_gc);
2061            }
2062            break;
2063
2064        case AWT_MODE_LENGTH:
2065        case AWT_MODE_MULTIFURC:
2066            if (clicked.node() && clicked.is_branch()) {
2067                bool allow_neg_branches = aw_root->awar(AWAR_EXPERT)->read_int();
2068                bool discrete_lengths   = event.button() == AW_BUTTON_RIGHT;
2069
2070                const AW_clicked_line *cl = dynamic_cast<const AW_clicked_line*>(clicked.element());
2071                td_assert(cl);
2072
2073                ScaleMode     mode   = event.cmd() == AWT_MODE_LENGTH ? SCALE_LENGTH : SCALE_LENGTH_PRESERVING;
2074                BranchScaler *scaler = new BranchScaler(mode, clicked.node(), cl->get_line(), clicked.element()->get_attach_point(), mousepos, device->get_unscale(), discrete_lengths, allow_neg_branches, exports);
2075
2076                store_command_data(scaler);
2077                scaler->draw_drag_indicator(device, drag_gc);
2078            }
2079            break;
2080
2081        case AWT_MODE_ROTATE:
2082            if (clicked.node()) {
2083                BranchRotator *rotator = NULp;
2084                if (clicked.is_branch()) {
2085                    const AW_clicked_line *cl = dynamic_cast<const AW_clicked_line*>(clicked.element());
2086                    td_assert(cl);
2087                    rotator = new BranchRotator(device, clicked.node(), cl->get_line(), mousepos, exports);
2088                }
2089                else { // rotate branches inside a folded group (allows to modify size of group triangle)
2090                    const AW_clicked_polygon *poly = dynamic_cast<const AW_clicked_polygon*>(clicked.element());
2091                    if (poly) {
2092                        int                 npos;
2093                        const AW::Position *pos = poly->get_polygon(npos);
2094
2095                        if (npos == 3) { // only makes sense in radial mode (which uses triangles)
2096                            LineVector left(pos[0], pos[1]);
2097                            LineVector right(pos[0], pos[2]);
2098
2099                            Position mousepos_world = device->rtransform(mousepos);
2100
2101                            if (Distance(mousepos_world, left) < Distance(mousepos_world, right)) {
2102                                rotator = new BranchRotator(device, clicked.node()->get_leftson(), left, mousepos, exports);
2103                            }
2104                            else {
2105                                rotator = new BranchRotator(device, clicked.node()->get_rightson(), right, mousepos, exports);
2106                            }
2107                        }
2108                    }
2109                }
2110                if (rotator) {
2111                    store_command_data(rotator);
2112                    rotator->draw_drag_indicator(device, drag_gc);
2113                }
2114            }
2115            break;
2116
2117        case AWT_MODE_LINE:
2118            if (clicked.node()) {
2119                BranchLinewidthScaler *widthScaler = new BranchLinewidthScaler(clicked.node(), mousepos, event.button() == AW_BUTTON_RIGHT, exports);
2120                store_command_data(widthScaler);
2121                widthScaler->draw_drag_indicator(device, drag_gc);
2122            }
2123            break;
2124
2125        case AWT_MODE_SPREAD:
2126            if (clicked.node() && clicked.is_branch()) {
2127                const AW_clicked_line *cl = dynamic_cast<const AW_clicked_line*>(clicked.element());
2128                td_assert(cl);
2129                BranchScaler *spreader = new BranchScaler(SCALE_SPREAD, clicked.node(), cl->get_line(), clicked.element()->get_attach_point(), mousepos, device->get_unscale(), false, false, exports);
2130                store_command_data(spreader);
2131                spreader->draw_drag_indicator(device, drag_gc);
2132            }
2133            break;
2134
2135        // -----------------------------
2136        //      one point commands:
2137
2138        case AWT_MODE_LZOOM:
2139            switch (event.button()) {
2140                case AW_BUTTON_LEFT:
2141                    if (clicked.node()) {
2142                        set_logical_root_to(clicked.node());
2143                        exports.request_zoom_reset();
2144                    }
2145                    break;
2146                case AW_BUTTON_RIGHT:
2147                    if (displayed_root->father) {
2148                        set_logical_root_to(displayed_root->get_father());
2149                        exports.request_zoom_reset();
2150                    }
2151                    break;
2152
2153                default: td_assert(0); break;
2154            }
2155            break;
2156
2157act_like_group :
2158        case AWT_MODE_GROUP:
2159            if (clicked.node()) {
2160                switch (event.button()) {
2161                    case AW_BUTTON_LEFT:
2162                        toggle_folding_at(clicked.node(), false);
2163                        break;
2164                    case AW_BUTTON_RIGHT:
2165                        if (tree_static->get_gb_tree()) {
2166                            toggle_group(clicked.node());
2167                        }
2168                        break;
2169                    default: td_assert(0); break;
2170                }
2171            }
2172            break;
2173
2174        case AWT_MODE_SETROOT:
2175            switch (event.button()) {
2176                case AW_BUTTON_LEFT:
2177                    if (clicked.node()) {
2178                        clicked.node()->set_root();
2179                        dislocate_selected_group();
2180                    }
2181                    break;
2182                case AW_BUTTON_RIGHT:
2183                    tree_static->find_innermost_edge().set_root();
2184                    dislocate_selected_group();
2185                    break;
2186                default: td_assert(0); break;
2187            }
2188            exports.request_save_and_zoom_reset();
2189            break;
2190
2191        case AWT_MODE_SWAP:
2192            if (clicked.node()) {
2193                switch (event.button()) {
2194                    case AW_BUTTON_LEFT:  clicked.node()->swap_sons(); break;
2195                    case AW_BUTTON_RIGHT: clicked.node()->rotate_subtree();     break;
2196                    default: td_assert(0); break;
2197                }
2198                exports.request_save();
2199            }
2200            break;
2201
2202        case AWT_MODE_MARK: // see also .@OTHER_MODE_MARK_HANDLER
2203            if (clicked.node()) {
2204                GB_transaction ta(tree_static->get_gb_main());
2205
2206                switch (event.button()) {
2207                    case AW_BUTTON_LEFT:  mark_species_in_tree(clicked.node(), 1); break;
2208                    case AW_BUTTON_RIGHT: mark_species_in_tree(clicked.node(), 0); break;
2209                    default: td_assert(0); break;
2210                }
2211                tree_static->update_timers(); // do not reload the tree
2212                exports.request_structure_update();
2213            }
2214            break;
2215
2216        case AWT_MODE_NONE:
2217        case AWT_MODE_SELECT:
2218            if (clicked.node()) {
2219                GB_transaction ta(tree_static->get_gb_main());
2220                exports.request_refresh(); // No refresh needed !! AD_map_viewer will do the refresh (needed by arb_pars)
2221                map_viewer_cb(clicked.node()->gb_node, ADMVT_SELECT);
2222
2223                if (event.button() == AW_BUTTON_LEFT) goto act_like_group; // now do the same like in AWT_MODE_GROUP
2224            }
2225            break;
2226
2227        // now handle all modes which only act on tips (aka species) and
2228        // shall perform identically in tree- and list-modes
2229
2230        case AWT_MODE_INFO:
2231        case AWT_MODE_WWW: {
2232            if (clicked.node() && clicked.node()->gb_node) {
2233                if (command_on_GBDATA(clicked.node()->gb_node, event, map_viewer_cb)) {
2234                    exports.request_refresh();
2235                }
2236            }
2237            break;
2238        }
2239        default:
2240            break;
2241    }
2242}
2243
2244void AWT_graphic_tree::set_tree_style(AP_tree_display_style style, AWT_canvas *ntw) {
2245    if (is_list_style(style)) {
2246        if (tree_style == style) { // we are already in wanted view
2247            nds_only_marked = !nds_only_marked; // -> toggle between 'marked' and 'all'
2248        }
2249        else {
2250            nds_only_marked = false; // default to all
2251        }
2252    }
2253    tree_style = style;
2254    apply_zoom_settings_for_treetype(ntw); // sets default padding
2255
2256    exports.fit_mode  = AWT_FIT_LARGER;
2257    exports.zoom_mode = AWT_ZOOM_BOTH;
2258
2259    exports.dont_scroll = 0;
2260
2261    switch (style) {
2262        case AP_TREE_RADIAL:
2263            break;
2264
2265        case AP_LIST_SIMPLE:
2266        case AP_LIST_NDS:
2267            exports.fit_mode  = AWT_FIT_NEVER;
2268            exports.zoom_mode = AWT_ZOOM_NEVER;
2269
2270            break;
2271
2272        case AP_TREE_IRS:    // folded dendrogram
2273            exports.fit_mode    = AWT_FIT_X;
2274            exports.zoom_mode   = AWT_ZOOM_X;
2275            exports.dont_scroll = 1;
2276            break;
2277
2278        case AP_TREE_NORMAL: // normal dendrogram
2279            exports.fit_mode  = AWT_FIT_X;
2280            exports.zoom_mode = AWT_ZOOM_X;
2281            break;
2282    }
2283}
2284
2285static void tree_change_ignore_cb(AWT_graphic_tree*) {}
2286static GraphicTreeCallback treeChangeIgnore_cb = makeGraphicTreeCallback(tree_change_ignore_cb);
2287
2288AWT_graphic_tree::AWT_graphic_tree(AW_root *aw_root_, GBDATA *gb_main_, AD_map_viewer_cb map_viewer_cb_) :
2289    AWT_graphic(),
2290    species_name(NULp),
2291    baselinewidth(1),
2292    tree_proto(NULp),
2293    link_to_database(false),
2294    group_style(GS_TRAPEZE),
2295    line_filter         (AW_SCREEN|AW_CLICK|AW_TRACK|AW_CLICK_DROP|AW_PRINTER|AW_SIZE),          // horizontal lines (ie. lines towards leafs in dendro-view; all lines in radial view)
2296    vert_line_filter    (AW_SCREEN|AW_CLICK|AW_CLICK_DROP|AW_PRINTER),                           // vertical lines (in dendro view; @@@ should be used in IRS as well!)
2297    mark_filter         (AW_SCREEN|AW_CLICK|AW_TRACK|AW_CLICK_DROP|AW_PRINTER_EXT),              // diamond at open group (dendro+radial); boxes at marked species (all views); origin (radial view); cursor box (all views); group-handle (IRS)
2298    group_bracket_filter(AW_SCREEN|AW_CLICK|AW_CLICK_DROP|AW_PRINTER|AW_SIZE_UNSCALED),
2299    leaf_text_filter    (AW_SCREEN|AW_CLICK|AW_TRACK|AW_CLICK_DROP|AW_PRINTER|AW_SIZE_UNSCALED), // text at leafs (all views but IRS? @@@ should be used in IRS as well)
2300    group_text_filter   (AW_SCREEN|AW_CLICK|AW_CLICK_DROP|AW_PRINTER|AW_SIZE_UNSCALED),
2301    other_text_filter   (AW_SCREEN|AW_PRINTER|AW_SIZE_UNSCALED),
2302    ruler_filter        (AW_SCREEN|AW_CLICK|AW_PRINTER),                                         // appropriate size-filter added manually in code
2303    root_filter         (AW_SCREEN|AW_PRINTER_EXT),                                              // unused (@@@ should be used for radial root)
2304    marker_filter       (AW_SCREEN|AW_CLICK|AW_PRINTER_EXT|AW_SIZE_UNSCALED),                    // species markers (eg. visualizing configs)
2305    group_info_pos(GIP_SEPARATED),
2306    group_count_mode(GCM_MEMBERS),
2307    branch_style(BS_RECTANGULAR),
2308    display_markers(NULp),
2309    map_viewer_cb(map_viewer_cb_),
2310    cmd_data(NULp),
2311    tree_static(NULp),
2312    displayed_root(NULp),
2313    tree_changed_cb(treeChangeIgnore_cb),
2314    autoUnfolded(new AP_tree_folding),
2315    aw_root(aw_root_),
2316    gb_main(gb_main_),
2317    nds_only_marked(false)
2318{
2319    td_assert(gb_main);
2320    set_tree_style(AP_TREE_NORMAL, NULp);
2321}
2322
2323AWT_graphic_tree::~AWT_graphic_tree() {
2324    delete cmd_data;
2325    free(species_name);
2326    destroy(tree_proto);
2327    delete tree_static;
2328    delete display_markers;
2329    delete autoUnfolded;
2330}
2331
2332AP_tree_root *AWT_graphic_tree::create_tree_root(AliView *aliview, AP_sequence *seq_prototype, bool insert_delete_cbs) {
2333    return new AP_tree_root(aliview, seq_prototype, insert_delete_cbs, &groupScale);
2334}
2335
2336void AWT_graphic_tree::init(AliView *aliview, AP_sequence *seq_prototype, bool link_to_database_, bool insert_delete_cbs) {
2337    tree_static      = create_tree_root(aliview, seq_prototype, insert_delete_cbs);
2338    td_assert(!insert_delete_cbs || link_to_database); // inserting delete callbacks w/o linking to DB has no effect!
2339    link_to_database = link_to_database_;
2340}
2341
2342void AWT_graphic_tree::unload() {
2343    forget_auto_unfolded();
2344    if (display_markers) display_markers->flush_cache();
2345    deselect_group();
2346    destroy(tree_static->get_root_node());
2347    displayed_root = NULp;
2348}
2349
2350GB_ERROR AWT_graphic_tree::load_from_DB(GBDATA *, const char *name) {
2351    GB_ERROR error = NULp;
2352
2353    if (!name) { // happens in error-case (called by AWT_graphic::update_DB_and_model_as_requested to load previous state)
2354        if (tree_static) {
2355            name = tree_static->get_tree_name();
2356            td_assert(name);
2357        }
2358        else {
2359            error = "Please select a tree (name lost)";
2360        }
2361    }
2362
2363    if (!error) {
2364        if (name[0] == 0 || strcmp(name, NO_TREE_SELECTED) == 0) {
2365            unload();
2366            zombies    = 0;
2367            duplicates = 0;
2368        }
2369        else {
2370            GBDATA *gb_group = get_selected_group().get_group_data(); // remember selected group
2371            freenull(tree_static->gone_tree_name);
2372            {
2373                char   *name_dup = strdup(name); // name might be freed by unload()
2374                unload();
2375                error            = tree_static->loadFromDB(name_dup);
2376                free(name_dup);
2377            }
2378
2379            if (!error && link_to_database) {
2380                error = tree_static->linkToDB(&zombies, &duplicates);
2381            }
2382
2383            if (error) {
2384                destroy(tree_static->get_root_node());
2385            }
2386            else {
2387                displayed_root = get_root_node();
2388                read_tree_settings();
2389                get_root_node()->compute_tree();
2390
2391                td_assert(!display_markers || display_markers->cache_is_flushed());
2392
2393                tree_static->set_root_changed_callback(AWT_graphic_tree_root_changed, this);
2394                tree_static->set_node_deleted_callback(AWT_graphic_tree_node_deleted, this);
2395            }
2396            select_group(gb_group);
2397        }
2398    }
2399
2400    tree_changed_cb(this);
2401    return error;
2402}
2403
2404GB_ERROR AWT_graphic_tree::save_to_DB(GBDATA * /* dummy */, const char * /* name */) {
2405    GB_ERROR error = NULp;
2406    if (get_root_node()) {
2407        error = tree_static->saveToDB();
2408        if (display_markers) display_markers->flush_cache();
2409    }
2410    else if (tree_static && tree_static->get_tree_name()) {
2411        if (tree_static->gb_tree_gone) {
2412            td_assert(!tree_static->gone_tree_name);
2413            tree_static->gone_tree_name = strdup(tree_static->get_tree_name());
2414
2415            GB_transaction ta(gb_main);
2416            error = GB_delete(tree_static->gb_tree_gone);
2417            error = ta.close(error);
2418
2419            if (!error) {
2420                aw_message(GBS_global_string("Tree '%s' lost all leafs and has been deleted", tree_static->get_tree_name()));
2421
2422                // @@@ TODO: somehow update selected tree
2423
2424                // solution: currently selected tree (in NTREE, maybe also in PARSIMONY)
2425                // needs to add a delete callback on treedata in DB
2426            }
2427
2428            tree_static->gb_tree_gone = NULp; // do not delete twice
2429        }
2430    }
2431    tree_changed_cb(this);
2432    return error;
2433}
2434
2435void AWT_graphic_tree::check_for_DB_update(GBDATA *) {
2436    td_assert(exports.flags_writeable()); // otherwise fails on requests below
2437
2438    if (tree_static) {
2439        AP_tree_root *troot = get_tree_root();
2440        if (troot) {
2441            GB_transaction ta(gb_main);
2442
2443            AP_UPDATE_FLAGS flags = troot->check_update();
2444            switch (flags) {
2445                case AP_UPDATE_OK:
2446                case AP_UPDATE_ERROR:
2447                    break;
2448
2449                case AP_UPDATE_RELOADED: {
2450                    const char *name = tree_static->get_tree_name();
2451                    if (name) {
2452                        GB_ERROR error = load_from_DB(gb_main, name);
2453                        if (error) aw_message(error);
2454                        else exports.request_resize();
2455                    }
2456                    break;
2457                }
2458                case AP_UPDATE_RELINKED: {
2459                    AP_tree *tree_root = get_root_node();
2460                    if (tree_root) {
2461                        GB_ERROR error = tree_root->relink();
2462                        if (error) aw_message(error);
2463                        else exports.request_structure_update();
2464                    }
2465                    break;
2466                }
2467            }
2468        }
2469    }
2470}
2471
2472void AWT_graphic_tree::notify_synchronized(GBDATA *) {
2473    if (get_tree_root()) get_tree_root()->update_timers();
2474}
2475
2476void AWT_graphic_tree::summarizeGroupMarkers(AP_tree *at, NodeMarkers& markers) {
2477    /*! summarizes matches of each probe for subtree 'at' in result param 'matches'
2478     * uses pcoll.cache to avoid repeated calculations
2479     */
2480    td_assert(display_markers);
2481    td_assert(markers.getNodeSize() == 0);
2482    if (at->is_leaf()) {
2483        if (at->name) {
2484            display_markers->retrieve_marker_state(at->name, markers);
2485        }
2486    }
2487    else {
2488        if (at->is_clade()) {
2489            const NodeMarkers *cached = display_markers->read_cache(at);
2490            if (cached) {
2491                markers = *cached;
2492                return;
2493            }
2494        }
2495
2496        summarizeGroupMarkers(at->get_leftson(), markers);
2497        NodeMarkers rightMarkers(display_markers->size());
2498        summarizeGroupMarkers(at->get_rightson(), rightMarkers);
2499        markers.add(rightMarkers);
2500
2501        if (at->is_clade()) {
2502            display_markers->write_cache(at, markers);
2503        }
2504    }
2505}
2506
2507class MarkerXPos {
2508    double Width;
2509    double Offset;
2510    int    markers;
2511public:
2512
2513    static int marker_width;
2514
2515    MarkerXPos(AW_pos scale, int markers_)
2516        : Width((marker_width-1) / scale),
2517          Offset(marker_width / scale),
2518          markers(markers_)
2519    {}
2520
2521    double width() const  { return Width; }
2522    double offset() const { return Offset; }
2523
2524    double leftx  (int markerIdx) const { return (markerIdx - markers - 1.0) * offset(); }
2525    double centerx(int markerIdx) const { return leftx(markerIdx) + width()/2; }
2526};
2527
2528int MarkerXPos::marker_width = 3;
2529
2530class MarkerPosition : public MarkerXPos {
2531    double y1, y2;
2532public:
2533    MarkerPosition(AW_pos scale, int markers_, double y1_, double y2_)
2534        : MarkerXPos(scale, markers_),
2535          y1(y1_),
2536          y2(y2_)
2537    {}
2538
2539    Position pos(int markerIdx) const { return Position(leftx(markerIdx), y1); }
2540    Vector size() const { return Vector(width(), y2-y1); }
2541};
2542
2543
2544void AWT_graphic_tree::drawMarker(const class MarkerPosition& marker, const bool partial, const int markerIdx) {
2545    td_assert(display_markers);
2546
2547    const int gc = MarkerGC[markerIdx % MARKER_COLORS];
2548
2549    if (partial) disp_device->set_grey_level(gc, marker_greylevel);
2550    disp_device->box(gc, partial ? AW::FillStyle::SHADED : AW::FillStyle::SOLID, marker.pos(markerIdx), marker.size(), marker_filter);
2551}
2552
2553void AWT_graphic_tree::detectAndDrawMarkers(AP_tree *at, const double y1, const double y2) {
2554    td_assert(display_markers);
2555
2556    if (disp_device->type() != AW_DEVICE_SIZE) {
2557        // Note: extra device scaling (needed to show flags) is done by drawMarkerNames
2558
2559        int            numMarkers = display_markers->size();
2560        MarkerPosition flag(disp_device->get_scale(), numMarkers, y1, y2);
2561        NodeMarkers    markers(numMarkers);
2562
2563        summarizeGroupMarkers(at, markers);
2564
2565        if (markers.getNodeSize()>0) {
2566            AW_click_cd clickflag(disp_device, 0, CL_FLAG);
2567            for (int markerIdx = 0 ; markerIdx < numMarkers ; markerIdx++) {
2568                if (markers.markerCount(markerIdx) > 0) {
2569                    bool draw    = at->is_leaf();
2570                    bool partial = false;
2571
2572                    if (!draw) { // group
2573                        td_assert(at->is_clade());
2574                        double markRate = markers.getMarkRate(markerIdx);
2575                        if (markRate>=groupThreshold.partiallyMarked && markRate>0.0) {
2576                            draw    = true;
2577                            partial = markRate<groupThreshold.marked;
2578                        }
2579                    }
2580
2581                    if (draw) {
2582                        clickflag.set_cd1(markerIdx);
2583                        drawMarker(flag, partial, markerIdx);
2584                    }
2585                }
2586            }
2587        }
2588    }
2589}
2590
2591void AWT_graphic_tree::drawMarkerNames(Position& Pen) {
2592    td_assert(display_markers);
2593
2594    int        numMarkers = display_markers->size();
2595    MarkerXPos flag(disp_device->get_scale(), numMarkers);
2596
2597    if (disp_device->type() != AW_DEVICE_SIZE) {
2598        Position pl1(flag.centerx(numMarkers-1), Pen.ypos()); // upper point of thin line
2599        Pen.movey(scaled_branch_distance);
2600        Position pl2(pl1.xpos(), Pen.ypos()); // lower point of thin line
2601
2602        Vector sizeb(flag.width(), scaled_branch_distance); // size of boxes
2603        Vector b2t(2*flag.offset(), scaled_branch_distance); // offset box->text
2604        Vector toNext(-flag.offset(), scaled_branch_distance); // offset to next box
2605
2606        Rectangle mbox(Position(flag.leftx(numMarkers-1), pl2.ypos()), sizeb); // the marker box
2607
2608        AW_click_cd clickflag(disp_device, 0, CL_FLAG);
2609
2610        for (int markerIdx = numMarkers - 1 ; markerIdx >= 0 ; markerIdx--) {
2611            const char *markerName = display_markers->get_marker_name(markerIdx);
2612            if (markerName) {
2613                int gc = MarkerGC[markerIdx % MARKER_COLORS];
2614
2615                clickflag.set_cd1(markerIdx);
2616
2617                disp_device->line(gc, pl1, pl2, marker_filter);
2618                disp_device->box(gc, AW::FillStyle::SOLID, mbox, marker_filter);
2619                disp_device->text(gc, markerName, mbox.upper_left_corner()+b2t, 0, marker_filter);
2620            }
2621
2622            pl1.movex(toNext.x());
2623            pl2.move(toNext);
2624            mbox.move(toNext);
2625        }
2626
2627        Pen.movey(scaled_branch_distance * (numMarkers+2));
2628    }
2629    else { // just reserve space on size device
2630        Pen.movey(scaled_branch_distance * (numMarkers+3));
2631        Position leftmost(flag.leftx(0), Pen.ypos());
2632        disp_device->line(AWT_GC_CURSOR, Pen, leftmost, marker_filter);
2633    }
2634}
2635
2636void AWT_graphic_tree::pixel_box(int gc, const AW::Position& pos, int pixel_width, AW::FillStyle filled) {
2637    double diameter = disp_device->rtransform_pixelsize(pixel_width);
2638    Vector diagonal(diameter, diameter);
2639
2640    td_assert(!filled.is_shaded()); // the pixel box is either filled or empty! (by design)
2641    if (filled.somehow()) disp_device->set_grey_level(gc, group_greylevel); // @@@ should not be needed here, but changes test-results (xfig-shading need fixes anyway)
2642    else                  disp_device->set_line_attributes(gc, 1, AW_SOLID);
2643    disp_device->box(gc, filled, pos-0.5*diagonal, diagonal, mark_filter);
2644}
2645
2646void AWT_graphic_tree::diamond(int gc, const Position& posIn, int pixel_radius) {
2647    // filled box with one corner down
2648    Position spos = disp_device->transform(posIn);
2649    Vector   hor  = Vector(pixel_radius, 0);
2650    Vector   ver  = Vector(0, pixel_radius);
2651
2652    Position corner[4] = {
2653        disp_device->rtransform(spos+hor),
2654        disp_device->rtransform(spos+ver),
2655        disp_device->rtransform(spos-hor),
2656        disp_device->rtransform(spos-ver),
2657    };
2658
2659    disp_device->polygon(gc, AW::FillStyle::SOLID, 4, corner, mark_filter);
2660}
2661
2662void AWT_graphic_tree::show_dendrogram(AP_tree *at, Position& Pen, DendroSubtreeLimits& limits) {
2663    /*! show dendrogram of subtree
2664     * @param at       the subtree to show
2665     * @param Pen      upper left corner of subtree area (eg. equals position of mark-box for tips).
2666     *                 Is modified and points to next subtree-area afterwards (Y only, X is undef!)
2667     * @param limits   reports dimension of painted subtree (output parameter)
2668     */
2669
2670    if (disp_device->type() != AW_DEVICE_SIZE) { // tree below cliprect bottom can be cut
2671        Position p(0, Pen.ypos() - scaled_branch_distance *2.0);
2672        Position s = disp_device->transform(p);
2673
2674        bool   is_clipped = false;
2675        double offset     = 0.0;
2676        if (disp_device->is_below_clip(s.ypos())) {
2677            offset     = scaled_branch_distance;
2678            is_clipped = true;
2679        }
2680        else {
2681            p.sety(Pen.ypos() + scaled_branch_distance *(at->gr.view_sum+2));
2682            s = disp_device->transform(p);
2683
2684            if (disp_device->is_above_clip(s.ypos())) {
2685                offset     = scaled_branch_distance*at->gr.view_sum;
2686                is_clipped = true;
2687            }
2688        }
2689
2690        if (is_clipped) {
2691            limits.x_right  = Pen.xpos();
2692            limits.y_branch = Pen.ypos();
2693            Pen.movey(offset);
2694            limits.y_top    = limits.y_bot = Pen.ypos();
2695            return;
2696        }
2697    }
2698
2699    AW_click_cd cd(disp_device, (AW_CL)at, CL_NODE);
2700    if (at->is_leaf()) {
2701        if (at->gb_node && GB_read_flag(at->gb_node)) {
2702            set_line_attributes_for(at);
2703            filled_box(at->gr.gc, Pen, NT_BOX_WIDTH);
2704        }
2705
2706        int  gc       = at->gr.gc;
2707        bool is_group = false;
2708
2709        if (at->hasName(species_name)) {
2710            selSpec = PaintedNode(Pen, at);
2711        }
2712        if (at->is_keeled_group()) { // keeled groups may appear at leafs!
2713            is_group         = true;
2714            bool is_selected = selected_group.at_node(at);
2715            if (is_selected) {
2716                selGroup = PaintedNode(Pen, at);
2717                gc       = int(AWT_GC_CURSOR);
2718            }
2719        }
2720
2721        if ((at->name || is_group) && (disp_device->get_filter() & leaf_text_filter)) {
2722            // display text
2723            const AW_font_limits&  charLimits = disp_device->get_font_limits(gc, 'A');
2724
2725            double   unscale = disp_device->get_unscale();
2726            Position textPos = Pen + 0.5*Vector((charLimits.width+NT_BOX_WIDTH)*unscale, scaled_font.ascent);
2727
2728            if (display_markers) {
2729                detectAndDrawMarkers(at, Pen.ypos() - scaled_branch_distance * 0.495, Pen.ypos() + scaled_branch_distance * 0.495);
2730            }
2731
2732            const char *data = make_node_text_nds(this->gb_main, at->gb_node, NDS_OUTPUT_LEAFTEXT, at, tree_static->get_tree_name());
2733            if (is_group) {
2734                static SmartCharPtr buf;
2735
2736                buf = strdup(data);
2737
2738                const GroupInfo& info = get_group_info(at, GI_COMBINED, false); // retrieves group info for leaf!
2739
2740                buf  = GBS_global_string_copy("%s (=%s)", &*buf, info.name);
2741                data = &*buf;
2742            }
2743
2744            SizedCstr sdata(data);
2745
2746            disp_device->text(gc, sdata, textPos, 0.0, leaf_text_filter);
2747            double textsize = disp_device->get_string_size(gc, sdata) * unscale;
2748
2749            limits.x_right = textPos.xpos() + textsize;
2750        }
2751        else {
2752            limits.x_right = Pen.xpos();
2753        }
2754
2755        limits.y_top = limits.y_bot = limits.y_branch = Pen.ypos();
2756        Pen.movey(scaled_branch_distance);
2757    }
2758
2759    //   s0-------------n0
2760    //   |
2761    //   attach (to father)
2762    //   |
2763    //   s1------n1
2764
2765    else if (at->is_folded_group()) {
2766        double height     = scaled_branch_distance * at->gr.view_sum;
2767        double box_height = height-scaled_branch_distance;
2768
2769        Position s0(Pen);
2770        Position s1(s0);  s1.movey(box_height);
2771        Position n0(s0);  n0.movex(at->gr.max_tree_depth);
2772        Position n1(s1);  n1.movex(at->gr.min_tree_depth);
2773
2774        set_line_attributes_for(at);
2775
2776        if (display_markers) {
2777            detectAndDrawMarkers(at, s0.ypos(), s1.ypos());
2778        }
2779
2780        disp_device->set_grey_level(at->gr.gc, group_greylevel);
2781
2782        bool      is_selected = selected_group.at_node(at);
2783        const int group_gc    = is_selected ? int(AWT_GC_CURSOR) : at->gr.gc;
2784
2785        Position   s_attach; // parent attach point
2786        LineVector g_diag;   // diagonal line at right side of group ("short side" -> "long side", ie. pointing rightwards)
2787        {
2788            Position group[4] = { s0, s1, n1, n0 }; // init with long side at top (=traditional orientation)
2789
2790            bool flip = false;
2791            switch (group_orientation) {
2792                case GO_TOP:      flip = false; break;
2793                case GO_BOTTOM:   flip = true; break;
2794                case GO_EXTERIOR: flip = at->is_lower_son(); break;
2795                case GO_INTERIOR: flip = at->is_upper_son(); break;
2796            }
2797            if (flip) { // flip triangle/trapeze vertically
2798                double x2 = group[2].xpos();
2799                group[2].setx(group[3].xpos());
2800                group[3].setx(x2);
2801                g_diag = LineVector(group[3], group[2]); // n0 -> n1
2802            }
2803            else {
2804                g_diag = LineVector(group[2], group[3]); // n1 -> n0
2805            }
2806
2807            s_attach = s1+(flip ? 1.0-attach_group : attach_group)*(s0-s1);
2808
2809            if (group_style == GS_TRIANGLE) {
2810                group[1] = s_attach;
2811                disp_device->polygon(at->gr.gc, AW::FillStyle::SHADED_WITH_BORDER, 3, group+1, line_filter);
2812                if (is_selected) disp_device->polygon(group_gc, AW::FillStyle::EMPTY, 3, group+1, line_filter);
2813            }
2814            else {
2815                td_assert(group_style == GS_TRAPEZE); // traditional style
2816                disp_device->polygon(at->gr.gc, AW::FillStyle::SHADED_WITH_BORDER, 4, group, line_filter);
2817                if (is_selected) disp_device->polygon(at->gr.gc, AW::FillStyle::EMPTY, 4, group, line_filter);
2818            }
2819        }
2820
2821        if (is_selected) selGroup = PaintedNode(s_attach, at);
2822
2823        limits.x_right = n0.xpos();
2824
2825        if (disp_device->get_filter() & group_text_filter) {
2826            const GroupInfo&      info       = get_group_info(at, group_info_pos == GIP_SEPARATED ? GI_SEPARATED : GI_COMBINED, group_info_pos == GIP_OVERLAYED);
2827            const AW_font_limits& charLimits = disp_device->get_font_limits(group_gc, 'A');
2828
2829            const double text_ascent = charLimits.ascent * disp_device->get_unscale();
2830            const double char_width  = charLimits.width * disp_device->get_unscale();
2831
2832            if (info.name) { // attached info
2833
2834                Position textPos;
2835
2836                const double gy           = g_diag.line_vector().y();
2837                const double group_height = fabs(gy);
2838
2839                if (group_height<=text_ascent) {
2840                    textPos = Position(g_diag.head().xpos(), g_diag.centroid().ypos()+text_ascent*0.5);
2841                }
2842                else {
2843                    Position pmin(g_diag.start()); // text position at short side of polygon (=leftmost position)
2844                    Position pmax(g_diag.head());  // text position at long  side of polygon (=rightmost position)
2845
2846                    const double shift_right = g_diag.line_vector().x() * text_ascent / group_height; // rightward shift needed at short side (to avoid overlap with group polygon)
2847
2848                    if (gy < 0.0) { // long side at top
2849                        pmin.movex(shift_right);
2850                        pmax.movey(text_ascent);
2851                    }
2852                    else { // long side at bottom
2853                        pmin.move(Vector(shift_right, text_ascent));
2854                    }
2855
2856                    textPos = pmin + 0.125*(pmax-pmin);
2857                }
2858
2859                textPos.movex(char_width);
2860
2861                SizedCstr infoName(info.name, info.name_len);
2862                disp_device->text(group_gc, infoName, textPos, 0.0, group_text_filter);
2863
2864                double textsize = disp_device->get_string_size(group_gc, infoName) * disp_device->get_unscale();
2865                limits.x_right  = std::max(limits.x_right, textPos.xpos()+textsize);
2866            }
2867
2868            if (info.count) { // overlayed info
2869                SizedCstr    infoCount(info.count, info.count_len);
2870                const double textsize = disp_device->get_string_size(group_gc, infoCount) * disp_device->get_unscale();
2871                Position     countPos;
2872                if (group_style == GS_TRIANGLE) {
2873                    countPos = s_attach + Vector(g_diag.centroid()-s_attach)*0.666 + Vector(-textsize, text_ascent)*0.5;
2874                }
2875                else {
2876                    countPos = s_attach + Vector(char_width, 0.5*text_ascent);
2877                }
2878                disp_device->text(group_gc, infoCount, countPos, 0.0, group_text_filter);
2879
2880                limits.x_right  = std::max(limits.x_right, countPos.xpos()+textsize);
2881            }
2882        }
2883
2884        limits.y_top    = s0.ypos();
2885        limits.y_bot    = s1.ypos();
2886        limits.y_branch = s_attach.ypos();
2887
2888        Pen.movey(height);
2889    }
2890    else { // furcation
2891        bool      is_group    = at->is_clade();
2892        bool      is_selected = is_group && selected_group.at_node(at);
2893        const int group_gc    = is_selected ? int(AWT_GC_CURSOR) : at->gr.gc;
2894
2895        Position s0(Pen);
2896
2897        Pen.movex(at->leftlen);
2898        Position n0(Pen);
2899
2900        show_dendrogram(at->get_leftson(), Pen, limits); // re-use limits for left branch
2901
2902        n0.sety(limits.y_branch);
2903        s0.sety(limits.y_branch);
2904
2905        Pen.setx(s0.xpos());
2906        Position subtree_border(Pen); subtree_border.movey(- .5*scaled_branch_distance); // attach point centered between both subtrees
2907        Pen.movex(at->rightlen);
2908        Position n1(Pen);
2909        {
2910            DendroSubtreeLimits right_lim;
2911            show_dendrogram(at->get_rightson(), Pen, right_lim);
2912            n1.sety(right_lim.y_branch);
2913            limits.combine(right_lim);
2914        }
2915
2916        Position s1(s0.xpos(), n1.ypos());
2917
2918        // calculate attach-point:
2919        Position attach = centroid(s0, s1);
2920        {
2921            Vector shift_by_size(ZeroVector);
2922            Vector shift_by_len(ZeroVector);
2923            int    nonZero = 0;
2924
2925            if (attach_size != 0.0) {
2926                ++nonZero;
2927                shift_by_size = -attach_size * (subtree_border-attach);
2928            }
2929
2930            if (attach_len != 0.0) {
2931                Position barycenter;
2932                if (nearlyZero(at->leftlen)) {
2933                    if (nearlyZero(at->rightlen)) {
2934                        barycenter = attach;
2935                    }
2936                    else {
2937                        barycenter = s1; // at(!) right branch
2938                    }
2939                }
2940                else {
2941                    if (nearlyZero(at->rightlen)) {
2942                        barycenter = s0; // at(!) left branch
2943                    }
2944                    else {
2945                        double sum = at->leftlen + at->rightlen;
2946                        double fraction;
2947                        Vector big2small;
2948                        if (at->leftlen < at->rightlen) {
2949                            fraction   = at->leftlen/sum;
2950                            big2small  = s0-s1;
2951                        }
2952                        else {
2953                            fraction = at->rightlen/sum;
2954                            big2small = s1-s0;
2955                        }
2956                        barycenter = attach-big2small/2+big2small*fraction;
2957                    }
2958                }
2959
2960                Vector shift_to_barycenter = barycenter-attach;
2961                shift_by_len               = shift_to_barycenter*attach_len;
2962
2963                ++nonZero;
2964            }
2965
2966            if (nonZero>1) {
2967                double sum    = fabs(attach_size) + fabs(attach_len);
2968                double f_size = fabs(attach_size)/sum;
2969                double f_len  = fabs(attach_len)/sum;
2970
2971                attach += f_size * shift_by_size;
2972                attach += f_len  * shift_by_len;
2973            }
2974            else {
2975                attach += shift_by_size;
2976                attach += shift_by_len;
2977            }
2978        }
2979
2980        if (is_group && show_brackets) {
2981            double                unscale          = disp_device->get_unscale();
2982            const AW_font_limits& charLimits       = disp_device->get_font_limits(group_gc, 'A');
2983            double                half_text_ascent = charLimits.ascent * unscale * 0.5;
2984
2985            double x1 = limits.x_right + scaled_branch_distance*0.1;
2986            double x2 = x1 + scaled_branch_distance * 0.3;
2987            double y1 = limits.y_top - half_text_ascent * 0.5;
2988            double y2 = limits.y_bot + half_text_ascent * 0.5;
2989
2990            Rectangle bracket(Position(x1, y1), Position(x2, y2));
2991
2992            set_line_attributes_for(at);
2993
2994            disp_device->line(group_gc, bracket.upper_edge(), group_bracket_filter);
2995            disp_device->line(group_gc, bracket.lower_edge(), group_bracket_filter);
2996            disp_device->line(group_gc, bracket.right_edge(), group_bracket_filter);
2997
2998            limits.x_right = x2;
2999
3000            if (disp_device->get_filter() & group_text_filter) {
3001                LineVector worldBracket = disp_device->transform(bracket.right_edge());
3002                LineVector clippedWorldBracket;
3003
3004                bool visible = disp_device->clip(worldBracket, clippedWorldBracket);
3005                if (visible) {
3006                    const GroupInfo& info = get_group_info(at, GI_SEPARATED_PARENTIZED);
3007
3008                    if (info.name || info.count) {
3009                        LineVector clippedBracket = disp_device->rtransform(clippedWorldBracket);
3010
3011                        if (info.name) {
3012                            Position namePos = clippedBracket.centroid()+Vector(half_text_ascent, -0.2*half_text_ascent); // originally y-offset was half_text_ascent (w/o counter shown)
3013                            SizedCstr infoName(info.name, info.name_len);
3014                            disp_device->text(group_gc, infoName, namePos, 0.0, group_text_filter);
3015                            if (info.name_len>=info.count_len) {
3016                                double textsize = disp_device->get_string_size(group_gc, infoName) * unscale;
3017                                limits.x_right  = namePos.xpos() + textsize;
3018                            }
3019                        }
3020
3021                        if (info.count) {
3022                            Position countPos = clippedBracket.centroid()+Vector(half_text_ascent, 2.2*half_text_ascent);
3023                            SizedCstr infoCount(info.count, info.count_len);
3024                            disp_device->text(group_gc, infoCount, countPos, 0.0, group_text_filter);
3025                            if (info.count_len>info.name_len) {
3026                                double textsize = disp_device->get_string_size(group_gc, infoCount) * unscale;
3027                                limits.x_right  = countPos.xpos() + textsize;
3028                            }
3029                        }
3030                    }
3031                }
3032            }
3033        }
3034
3035        for (int right = 0; right<2; ++right) {
3036            const Position& n = right ? n1 : n0; // node-position
3037            const Position& s = right ? s1 : s0; // upper/lower corner of rectangular branch
3038
3039            AP_tree *son;
3040            GBT_LEN  len;
3041            if (right) {
3042                son = at->get_rightson();
3043                len = at->rightlen;
3044            }
3045            else {
3046                son = at->get_leftson();
3047                len = at->leftlen;
3048            }
3049
3050            AW_click_cd cds(disp_device, (AW_CL)son, CL_NODE);
3051
3052            set_line_attributes_for(son);
3053            unsigned int gc = son->gr.gc;
3054
3055            if (branch_style == BS_RECTANGULAR) {
3056                draw_branch_line(gc, s, n, line_filter);
3057                draw_branch_line(gc, attach, s, vert_line_filter);
3058            }
3059            else {
3060                td_assert(branch_style == BS_DIAGONAL);
3061                draw_branch_line(gc, attach, n, line_filter);
3062            }
3063
3064            if (bconf.shall_show_remark_for(son)) {
3065                if (son->is_son_of_root()) {
3066                    if (right) {            // only draw once
3067                        AW_click_cd cdr(disp_device, 0, CL_ROOTNODE);
3068                        len += at->leftlen; // sum up length of both sons of root
3069                        bconf.display_node_remark(disp_device, son, attach, len, scaled_branch_distance, D_EAST);
3070                    }
3071                }
3072                else {
3073                    bconf.display_node_remark(disp_device, son, n, len, scaled_branch_distance, right ? D_SOUTH_WEST : D_NORTH_WEST); // leftson is_upper_son
3074                }
3075            }
3076        }
3077        if (is_group) {
3078            diamond(group_gc, attach, NT_DIAMOND_RADIUS);
3079            if (is_selected) selGroup = PaintedNode(attach, at);
3080        }
3081        limits.y_branch = attach.ypos();
3082    }
3083}
3084
3085struct Subinfo { // subtree info (used to implement branch draw precedence)
3086    AP_tree *at;
3087    double   pc; // percent of space (depends on # of species in subtree)
3088    Angle    orientation;
3089    double   len;
3090};
3091
3092void AWT_graphic_tree::show_radial_tree(AP_tree *at, const AW::Position& base, const AW::Position& tip, const AW::Angle& orientation, const double tree_spread) {
3093    AW_click_cd cd(disp_device, (AW_CL)at, CL_NODE);
3094    set_line_attributes_for(at);
3095    draw_branch_line(at->gr.gc, base, tip, line_filter);
3096
3097    if (at->is_leaf()) { // draw leaf node
3098        if (at->gb_node && GB_read_flag(at->gb_node)) { // draw mark box
3099            filled_box(at->gr.gc, tip, NT_BOX_WIDTH);
3100        }
3101
3102        if (at->name && (disp_device->get_filter() & leaf_text_filter)) {
3103            if (at->hasName(species_name)) selSpec = PaintedNode(tip, at);
3104
3105            AW_pos   alignment;
3106            Position textpos = calc_text_coordinates_near_tip(disp_device, at->gr.gc, tip, orientation, alignment);
3107
3108            const char *data =  make_node_text_nds(this->gb_main, at->gb_node, NDS_OUTPUT_LEAFTEXT, at, tree_static->get_tree_name());
3109            disp_device->text(at->gr.gc, data,
3110                              textpos,
3111                              alignment,
3112                              leaf_text_filter);
3113        }
3114    }
3115    else if (at->is_folded_group()) { // draw folded group
3116        bool      is_selected = at->name && selected_group.at_node(at); // @@@ superfluous+wrong test for group (at->name)
3117        const int group_gc    = is_selected ? int(AWT_GC_CURSOR) : at->gr.gc;
3118
3119        if (is_selected) selGroup = PaintedNode(tip, at);
3120
3121        Position corner[3];
3122        corner[0] = tip;
3123        {
3124            Angle left(orientation.radian() + 0.25*tree_spread + at->gr.left_angle);
3125            corner[1] = tip + left.normal()*at->gr.min_tree_depth;
3126        }
3127        {
3128            Angle right(orientation.radian() - 0.25*tree_spread + at->gr.right_angle);
3129            corner[2] = tip + right.normal()*at->gr.max_tree_depth;
3130        }
3131
3132        disp_device->set_grey_level(at->gr.gc, group_greylevel);
3133        disp_device->polygon(at->gr.gc, AW::FillStyle::SHADED_WITH_BORDER, 3, corner, line_filter);
3134        if (group_gc != int(at->gr.gc)) {
3135            disp_device->polygon(group_gc, AW::FillStyle::EMPTY, 3, corner, line_filter);
3136        }
3137
3138        if (disp_device->get_filter() & group_text_filter) {
3139            const GroupInfo& info = get_group_info(at, group_info_pos == GIP_SEPARATED ? GI_SEPARATED : GI_COMBINED, group_info_pos == GIP_OVERLAYED);
3140            if (info.name) {
3141                Angle toText = orientation;
3142                toText.rotate90deg();
3143
3144                AW_pos   alignment;
3145                Position textpos = calc_text_coordinates_near_tip(disp_device, group_gc, corner[1], toText, alignment);
3146
3147                disp_device->text(group_gc, SizedCstr(info.name, info.name_len), textpos, alignment, group_text_filter);
3148            }
3149            if (info.count) {
3150                Vector v01 = corner[1]-corner[0];
3151                Vector v02 = corner[2]-corner[0];
3152
3153                Position incircleCenter = corner[0] + (v01*v02.length() + v02*v01.length()) / (v01.length()+v02.length()+Distance(v01.endpoint(), v02.endpoint()));
3154
3155                disp_device->text(group_gc, SizedCstr(info.count, info.count_len), incircleCenter, 0.5, group_text_filter);
3156            }
3157        }
3158    }
3159    else { // draw subtrees
3160        bool is_selected = at->name && selected_group.at_node(at); // @@@ wrong test for group (at->name)
3161        if (is_selected) selGroup = PaintedNode(tip, at);
3162
3163        Subinfo sub[2];
3164        sub[0].at = at->get_leftson();
3165        sub[1].at = at->get_rightson();
3166
3167        sub[0].pc = sub[0].at->gr.view_sum / (double)at->gr.view_sum;
3168        sub[1].pc = 1.0-sub[0].pc;
3169
3170        sub[0].orientation = Angle(orientation.radian() + sub[1].pc*0.5*tree_spread + at->gr.left_angle);
3171        sub[1].orientation = Angle(orientation.radian() - sub[0].pc*0.5*tree_spread + at->gr.right_angle);
3172
3173        sub[0].len = at->leftlen;
3174        sub[1].len = at->rightlen;
3175
3176        if (sub[0].at->gr.gc < sub[1].at->gr.gc) {
3177            std::swap(sub[0], sub[1]); // swap branch draw order (branches with lower gc are drawn on top of branches with higher gc)
3178        }
3179
3180        for (int s = 0; s<2; ++s) {
3181            show_radial_tree(sub[s].at,
3182                             tip,
3183                             tip + sub[s].len * sub[s].orientation.normal(),
3184                             sub[s].orientation,
3185                             sub[s].at->is_leaf() ? 1.0 : tree_spread * sub[s].pc * sub[s].at->gr.spread);
3186        }
3187        for (int s = 0; s<2; ++s) {
3188            AP_tree *son = sub[s].at;
3189            if (bconf.shall_show_remark_for(son)) {
3190                AW_click_cd sub_cd(disp_device, (AW_CL)son, CL_NODE);
3191
3192                td_assert(!son->is_leaf());
3193                if (son->is_son_of_root()) {
3194                    if (s) { // only at one son
3195                        AW_click_cd cdr(disp_device, 0, CL_ROOTNODE);
3196                        AW_pos      alignment;
3197                        Position    text_pos = calc_text_coordinates_aside_line(disp_device, AWT_GC_BRANCH_REMARK, tip, sub[s].orientation, true, alignment, 1.0);
3198
3199                        bconf.display_remark(disp_device, son->get_remark(), tip, sub[0].len+sub[1].len, 0, text_pos, alignment);
3200                    }
3201                }
3202                else {
3203                    Position sub_branch_center = tip + (sub[s].len*.5) * sub[s].orientation.normal();
3204
3205                    AW_pos   alignment;
3206                    Position text_pos = calc_text_coordinates_aside_line(disp_device, AWT_GC_BRANCH_REMARK, sub_branch_center, sub[s].orientation, true, alignment, 0.5);
3207                    bconf.display_remark(disp_device, son->get_remark(), sub_branch_center, sub[s].len, 0, text_pos, alignment);
3208                }
3209            }
3210        }
3211
3212        if (at->is_clade()) {
3213            const int group_gc = selected_group.at_node(at) ? int(AWT_GC_CURSOR) : at->gr.gc;
3214            diamond(group_gc, tip, NT_DIAMOND_RADIUS);
3215        }
3216    }
3217}
3218
3219const char *AWT_graphic_tree::ruler_awar(const char *name) {
3220    // return "ruler/TREETYPE/name" (path to entry below tree)
3221    const char *tree_awar = NULp;
3222    switch (tree_style) {
3223        case AP_TREE_NORMAL:
3224            tree_awar = "LIST";
3225            break;
3226        case AP_TREE_RADIAL:
3227            tree_awar = "RADIAL";
3228            break;
3229        case AP_TREE_IRS:
3230            tree_awar = "IRS";
3231            break;
3232        case AP_LIST_SIMPLE:
3233        case AP_LIST_NDS:
3234            // rulers not allowed in these display modes
3235            td_assert(0); // should not be called
3236            break;
3237    }
3238
3239    static char awar_name[256];
3240    sprintf(awar_name, "ruler/%s/%s", tree_awar, name);
3241    return awar_name;
3242}
3243
3244void AWT_graphic_tree::show_ruler(AW_device *device, int gc) {
3245    GBDATA *gb_tree = tree_static->get_gb_tree();
3246    if (!gb_tree) return; // no tree -> no ruler
3247
3248    bool mode_has_ruler = ruler_awar(NULp);
3249    if (mode_has_ruler) {
3250        GB_transaction ta(gb_tree);
3251
3252        float ruler_size = *GBT_readOrCreate_float(gb_tree, RULER_SIZE, DEFAULT_RULER_LENGTH);
3253        float ruler_y    = 0.0;
3254
3255        const char *awar = ruler_awar("ruler_y");
3256        if (!GB_search(gb_tree, awar, GB_FIND)) {
3257            if (device->type() == AW_DEVICE_SIZE) {
3258                AW_world world;
3259                DOWNCAST(AW_device_size*, device)->get_size_information(&world);
3260                ruler_y = world.b * 1.3;
3261            }
3262        }
3263
3264        double half_ruler_width = ruler_size*0.5;
3265
3266        float ruler_add_y  = 0.0;
3267        float ruler_add_x  = 0.0;
3268        switch (tree_style) {
3269            case AP_TREE_IRS:
3270                // scale is different for IRS tree -> adjust:
3271                half_ruler_width *= irs_tree_ruler_scale_factor;
3272                ruler_y     = 0;
3273                ruler_add_y = this->list_tree_ruler_y;
3274                ruler_add_x = -half_ruler_width;
3275                break;
3276            case AP_TREE_NORMAL:
3277                ruler_y     = 0;
3278                ruler_add_y = this->list_tree_ruler_y;
3279                ruler_add_x = half_ruler_width;
3280                break;
3281            default:
3282                break;
3283        }
3284        ruler_y = ruler_add_y + *GBT_readOrCreate_float(gb_tree, awar, ruler_y);
3285
3286        float ruler_x = 0.0;
3287        ruler_x       = ruler_add_x + *GBT_readOrCreate_float(gb_tree, ruler_awar("ruler_x"), ruler_x);
3288
3289        td_assert(!is_nan_or_inf(ruler_x));
3290
3291        float ruler_text_x = 0.0;
3292        ruler_text_x       = *GBT_readOrCreate_float(gb_tree, ruler_awar("text_x"), ruler_text_x);
3293
3294        td_assert(!is_nan_or_inf(ruler_text_x));
3295
3296        float ruler_text_y = 0.0;
3297        ruler_text_y       = *GBT_readOrCreate_float(gb_tree, ruler_awar("text_y"), ruler_text_y);
3298
3299        td_assert(!is_nan_or_inf(ruler_text_y));
3300
3301        int ruler_width = *GBT_readOrCreate_int(gb_tree, RULER_LINEWIDTH, DEFAULT_RULER_LINEWIDTH);
3302
3303        device->set_line_attributes(gc, ruler_width+baselinewidth, AW_SOLID);
3304
3305        AW_click_cd cd(device, 0, CL_RULER);
3306        device->line(gc,
3307                     ruler_x - half_ruler_width, ruler_y,
3308                     ruler_x + half_ruler_width, ruler_y,
3309                     this->ruler_filter|AW_SIZE);
3310
3311        char ruler_text[20];
3312        sprintf(ruler_text, "%4.2f", ruler_size);
3313        device->text(gc, ruler_text,
3314                     ruler_x + ruler_text_x,
3315                     ruler_y + ruler_text_y,
3316                     0.5,
3317                     this->ruler_filter|AW_SIZE_UNSCALED);
3318    }
3319}
3320
3321struct Column : virtual Noncopyable {
3322    char   *text;
3323    size_t  len;
3324    double  print_width;
3325    bool    is_numeric; // also true for empty text
3326
3327    Column() : text(NULp) {}
3328    ~Column() { free(text); }
3329
3330    void init(const char *text_, AW_device& device, int gc) {
3331        len         = strlen(text_);
3332        text        = ARB_strduplen(text_, len);
3333        print_width = device.get_string_size(gc, SizedCstr(text, len));
3334        is_numeric  = (strspn(text, "0123456789.") == len);
3335    }
3336};
3337
3338class ListDisplayRow : virtual Noncopyable {
3339    GBDATA *gb_species;
3340    AW_pos  y_position;
3341    int     gc;
3342    size_t  part_count;                             // NDS columns
3343    Column *column;
3344
3345public:
3346    ListDisplayRow(GBDATA *gb_main, GBDATA *gb_species_, AW_pos y_position_, int gc_, AW_device& device, bool use_nds, const char *tree_name)
3347        : gb_species(gb_species_),
3348          y_position(y_position_),
3349          gc(gc_)
3350    {
3351        const char *nds = use_nds
3352                          ? make_node_text_nds(gb_main, gb_species, NDS_OUTPUT_TAB_SEPARATED, NULp, tree_name)
3353                          : GBT_get_name_or_description(gb_species);
3354
3355        ConstStrArray parts;
3356        GBT_split_string(parts, nds, "\t", false);
3357        part_count = parts.size();
3358
3359        column = new Column[part_count];
3360        for (size_t i = 0; i<part_count; ++i) {
3361            column[i].init(parts[i], device, gc);
3362        }
3363    }
3364
3365    ~ListDisplayRow() { delete [] column; }
3366
3367    size_t get_part_count() const { return part_count; }
3368    const Column& get_column(size_t p) const {
3369        td_assert(p<part_count);
3370        return column[p];
3371    }
3372    double get_print_width(size_t p) const { return get_column(p).print_width; }
3373    const char *get_text(size_t p, size_t& len) const {
3374        const Column& col = get_column(p);
3375        len = col.len;
3376        return col.text;
3377    }
3378    int get_gc() const { return gc; }
3379    double get_ypos() const { return y_position; }
3380    GBDATA *get_species() const { return gb_species; }
3381};
3382
3383void AWT_graphic_tree::show_nds_list(GBDATA *, bool use_nds) {
3384    AW_pos y_position = scaled_branch_distance;
3385    AW_pos x_position = NT_SELECTED_WIDTH * disp_device->get_unscale();
3386
3387    disp_device->text(nds_only_marked ? AWT_GC_ALL_MARKED : AWT_GC_CURSOR,
3388                      GBS_global_string("%s of %s species", use_nds ? "NDS List" : "Simple list", nds_only_marked ? "marked" : "all"),
3389                      (AW_pos) x_position, (AW_pos) 0,
3390                      (AW_pos) 0, other_text_filter);
3391
3392    double max_x         = 0;
3393    double text_y_offset = scaled_font.ascent*.5;
3394
3395    GBDATA *selected_species;
3396    {
3397        GBDATA *selected_name = GB_find_string(GBT_get_species_data(gb_main), "name", this->species_name, GB_IGNORE_CASE, SEARCH_GRANDCHILD);
3398        selected_species      = selected_name ? GB_get_father(selected_name) : NULp;
3399    }
3400
3401    const char *tree_name = tree_static ? tree_static->get_tree_name() : NULp;
3402
3403    AW_pos y1, y2;
3404    {
3405        const AW_screen_area& clip_rect = disp_device->get_cliprect();
3406
3407        AW_pos Y1 = clip_rect.t;
3408        AW_pos Y2 = clip_rect.b;
3409
3410        AW_pos x;
3411        disp_device->rtransform(0, Y1, x, y1);
3412        disp_device->rtransform(0, Y2, x, y2);
3413    }
3414
3415    y1 -= 2*scaled_branch_distance;                 // add two lines for safety
3416    y2 += 2*scaled_branch_distance;
3417
3418    size_t           displayed_rows = (y2-y1)/scaled_branch_distance+1;
3419    ListDisplayRow **row            = new ListDisplayRow*[displayed_rows];
3420
3421    size_t species_count = 0;
3422    size_t max_parts     = 0;
3423
3424    GBDATA *gb_species = nds_only_marked ? GBT_first_marked_species(gb_main) : GBT_first_species(gb_main);
3425    if (gb_species) {
3426        int skip_over = (y1-y_position)/scaled_branch_distance-2;
3427        if (skip_over>0) {
3428            gb_species  = nds_only_marked
3429                          ? GB_following_marked(gb_species, "species", skip_over-1)
3430                          : GB_followingEntry(gb_species, skip_over-1);
3431            y_position += skip_over*scaled_branch_distance;
3432        }
3433    }
3434
3435    const AP_TreeShader *shader = AP_tree::get_tree_shader();
3436    const_cast<AP_TreeShader*>(shader)->update_settings();
3437
3438    for (; gb_species; gb_species = nds_only_marked ? GBT_next_marked_species(gb_species) : GBT_next_species(gb_species)) {
3439        y_position += scaled_branch_distance;
3440
3441        if (gb_species == selected_species) selSpec = PaintedNode(Position(0, y_position), NULp);
3442
3443        if (y_position>y1) {
3444            if (y_position>y2) break;           // no need to examine rest of species
3445
3446            bool is_marked = nds_only_marked || GB_read_flag(gb_species);
3447            if (is_marked) {
3448                disp_device->set_line_attributes(AWT_GC_ALL_MARKED, baselinewidth, AW_SOLID);
3449                filled_box(AWT_GC_ALL_MARKED, Position(0, y_position), NT_BOX_WIDTH);
3450            }
3451
3452            bool colorize_marked = is_marked && !nds_only_marked; // do not use mark-color if only showing marked
3453
3454            int gc = shader->calc_leaf_GC(gb_species, colorize_marked);
3455            if (gc == AWT_GC_NONE_MARKED && shader->does_shade()) { // may show shaded color
3456                gc = shader->to_GC(shader->calc_shaded_leaf_GC(gb_species));
3457            }
3458
3459            ListDisplayRow *curr = new ListDisplayRow(gb_main, gb_species, y_position+text_y_offset, gc, *disp_device, use_nds, tree_name);
3460            max_parts            = std::max(max_parts, curr->get_part_count());
3461            row[species_count++] = curr;
3462        }
3463    }
3464
3465    td_assert(species_count <= displayed_rows);
3466
3467    // calculate column offsets and detect column alignment
3468    double *max_part_width = new double[max_parts];
3469    bool   *align_right    = new bool[max_parts];
3470
3471    for (size_t p = 0; p<max_parts; ++p) {
3472        max_part_width[p] = 0;
3473        align_right[p]    = true;
3474    }
3475
3476    for (size_t s = 0; s<species_count; ++s) {
3477        size_t parts = row[s]->get_part_count();
3478        for (size_t p = 0; p<parts; ++p) {
3479            const Column& col = row[s]->get_column(p);
3480            max_part_width[p] = std::max(max_part_width[p], col.print_width);
3481            align_right[p]    = align_right[p] && col.is_numeric;
3482        }
3483    }
3484
3485    double column_space = scaled_branch_distance;
3486
3487    double *part_x_pos = new double[max_parts];
3488    for (size_t p = 0; p<max_parts; ++p) {
3489        part_x_pos[p]  = x_position;
3490        x_position    += max_part_width[p]+column_space;
3491    }
3492    max_x = x_position;
3493
3494    // draw
3495
3496    for (size_t s = 0; s<species_count; ++s) {
3497        const ListDisplayRow& Row = *row[s];
3498
3499        size_t parts = Row.get_part_count();
3500        int    gc    = Row.get_gc();
3501        AW_pos y     = Row.get_ypos();
3502
3503        GBDATA      *gb_sp = Row.get_species();
3504        AW_click_cd  cd(disp_device, (AW_CL)gb_sp, CL_SPECIES);
3505
3506        for (size_t p = 0; p<parts; ++p) {
3507            const Column& col = Row.get_column(p);
3508
3509            AW_pos x               = part_x_pos[p];
3510            if (align_right[p]) x += max_part_width[p] - col.print_width;
3511
3512            disp_device->text(gc, SizedCstr(col.text, col.len), x, y, 0.0, leaf_text_filter);
3513        }
3514    }
3515
3516    delete [] part_x_pos;
3517    delete [] align_right;
3518    delete [] max_part_width;
3519
3520    for (size_t s = 0; s<species_count; ++s) delete row[s];
3521    delete [] row;
3522
3523    disp_device->invisible(Origin);  // @@@ remove when size-dev works
3524    disp_device->invisible(Position(max_x, y_position+scaled_branch_distance));  // @@@ remove when size-dev works
3525}
3526
3527void AWT_graphic_tree::read_tree_settings() {
3528    scaled_branch_distance = aw_root->awar(AWAR_DTREE_VERICAL_DIST)->read_float();       // not final value!
3529    group_greylevel        = aw_root->awar(AWAR_DTREE_GREY_LEVEL)->read_int() * 0.01;
3530    baselinewidth          = aw_root->awar(AWAR_DTREE_BASELINEWIDTH)->read_int();
3531    group_count_mode       = GroupCountMode(aw_root->awar(AWAR_DTREE_GROUPCOUNTMODE)->read_int());
3532    group_info_pos         = GroupInfoPosition(aw_root->awar(AWAR_DTREE_GROUPINFOPOS)->read_int());
3533    show_brackets          = aw_root->awar(AWAR_DTREE_SHOW_BRACKETS)->read_int();
3534    groupScale.pow         = aw_root->awar(AWAR_DTREE_GROUP_DOWNSCALE)->read_float();
3535    groupScale.linear      = aw_root->awar(AWAR_DTREE_GROUP_SCALE)->read_float();
3536    group_style            = GroupStyle(aw_root->awar(AWAR_DTREE_GROUP_STYLE)->read_int());
3537    group_orientation      = GroupOrientation(aw_root->awar(AWAR_DTREE_GROUP_ORIENT)->read_int());
3538    branch_style           = BranchStyle(aw_root->awar(AWAR_DTREE_BRANCH_STYLE)->read_int());
3539    attach_size            = aw_root->awar(AWAR_DTREE_ATTACH_SIZE)->read_float();
3540    attach_len             = aw_root->awar(AWAR_DTREE_ATTACH_LEN)->read_float();
3541    attach_group           = (aw_root->awar(AWAR_DTREE_ATTACH_GROUP)->read_float()+1)/2; // projection: [-1 .. 1] -> [0 .. 1]
3542
3543    bconf.show_boots    = aw_root->awar(AWAR_DTREE_BOOTSTRAP_SHOW)->read_int();
3544    bconf.bootstrap_min = aw_root->awar(AWAR_DTREE_BOOTSTRAP_MIN)->read_int();
3545    bconf.bootstrap_max = aw_root->awar(AWAR_DTREE_BOOTSTRAP_MAX)->read_int();
3546    bconf.style         = BootstrapStyle(aw_root->awar(AWAR_DTREE_BOOTSTRAP_STYLE)->read_int());
3547    bconf.zoom_factor   = aw_root->awar(AWAR_DTREE_CIRCLE_ZOOM)->read_float();
3548    bconf.max_radius    = aw_root->awar(AWAR_DTREE_CIRCLE_LIMIT)->read_float();
3549    bconf.show_circle   = aw_root->awar(AWAR_DTREE_CIRCLE_SHOW)->read_int();
3550    bconf.fill_level    = aw_root->awar(AWAR_DTREE_CIRCLE_FILL)->read_int() * 0.01;
3551    bconf.elipsoid      = aw_root->awar(AWAR_DTREE_CIRCLE_ELLIPSE)->read_int();
3552
3553    freeset(species_name, aw_root->awar(AWAR_SPECIES_NAME)->read_string());
3554
3555    if (display_markers) {
3556        groupThreshold.marked          = aw_root->awar(AWAR_DTREE_GROUP_MARKED_THRESHOLD)->read_float() * 0.01;
3557        groupThreshold.partiallyMarked = aw_root->awar(AWAR_DTREE_GROUP_PARTIALLY_MARKED_THRESHOLD)->read_float() * 0.01;
3558        MarkerXPos::marker_width       = aw_root->awar(AWAR_DTREE_MARKER_WIDTH)->read_int();
3559        marker_greylevel               = aw_root->awar(AWAR_DTREE_PARTIAL_GREYLEVEL)->read_int() * 0.01;
3560    }
3561}
3562
3563void AWT_graphic_tree::apply_zoom_settings_for_treetype(AWT_canvas *ntw) {
3564    exports.set_standard_default_padding();
3565
3566    if (ntw) {
3567        bool zoom_fit_text       = false;
3568        int  left_padding  = 0;
3569        int  right_padding = 0;
3570
3571        switch (tree_style) {
3572            case AP_TREE_RADIAL:
3573                zoom_fit_text = aw_root->awar(AWAR_DTREE_RADIAL_ZOOM_TEXT)->read_int();
3574                left_padding  = aw_root->awar(AWAR_DTREE_RADIAL_XPAD)->read_int();
3575                right_padding = left_padding;
3576                break;
3577
3578            case AP_TREE_NORMAL:
3579            case AP_TREE_IRS:
3580                zoom_fit_text = aw_root->awar(AWAR_DTREE_DENDRO_ZOOM_TEXT)->read_int();
3581                left_padding  = STANDARD_PADDING;
3582                right_padding = aw_root->awar(AWAR_DTREE_DENDRO_XPAD)->read_int();
3583                break;
3584
3585            default :
3586                break;
3587        }
3588
3589        exports.set_default_padding(STANDARD_PADDING, STANDARD_PADDING, left_padding, right_padding);
3590
3591        ntw->set_consider_text_for_zoom_reset(zoom_fit_text);
3592    }
3593}
3594
3595void AWT_graphic_tree::show(AW_device *device) {
3596    read_tree_settings();
3597    bconf.update_empty_branch_behavior(get_tree_root());
3598
3599    disp_device = device;
3600    disp_device->reset_style();
3601
3602    {
3603        const AW_font_limits& charLimits  = disp_device->get_font_limits(AWT_GC_ALL_MARKED, 0);
3604        scaled_font.init(charLimits, device->get_unscale());
3605    }
3606    {
3607        const AW_font_limits&  remarkLimits = disp_device->get_font_limits(AWT_GC_BRANCH_REMARK, 0);
3608        AWT_scaled_font_limits scaledRemarkLimits;
3609        scaledRemarkLimits.init(remarkLimits, device->get_unscale());
3610        bconf.scaled_remark_ascend          = scaledRemarkLimits.ascent;
3611    }
3612    scaled_branch_distance *= scaled_font.height;
3613
3614    make_node_text_init(gb_main);
3615
3616    selSpec  = PaintedNode(); // not painted yet
3617    selGroup = PaintedNode(); // not painted yet
3618
3619    if (!displayed_root && is_tree_style(tree_style)) { // if there is no tree, but display style needs tree
3620        static const char *no_tree_text[] = {
3621            "No tree (selected)",
3622            "",
3623            "In the top area you may click on",
3624            "- the listview-button to see a plain list of species",
3625            "- the tree-selection-button to select a tree",
3626            NULp
3627        };
3628
3629        Position p0(0, -3*scaled_branch_distance);
3630        Position cursor = p0;
3631        for (int i = 0; no_tree_text[i]; ++i) {
3632            cursor.movey(scaled_branch_distance);
3633            device->text(AWT_GC_CURSOR, no_tree_text[i], cursor);
3634        }
3635        device->line(AWT_GC_CURSOR, p0, cursor);
3636        selSpec = PaintedNode(cursor, NULp);
3637    }
3638    else {
3639        double   range_display_size  = scaled_branch_distance;
3640        bool     allow_range_display = true;
3641        Position range_origin        = Origin;
3642
3643        switch (tree_style) {
3644            case AP_TREE_NORMAL: {
3645                DendroSubtreeLimits limits;
3646                Position            pen(0, 0.05);
3647
3648                show_dendrogram(displayed_root, pen, limits);
3649
3650                int rulerOffset = 2;
3651                if (display_markers) {
3652                    drawMarkerNames(pen);
3653                    ++rulerOffset;
3654                }
3655                list_tree_ruler_y = pen.ypos() + double(rulerOffset) * scaled_branch_distance;
3656                break;
3657            }
3658            case AP_TREE_RADIAL: {
3659                LocallyModify<bool> onlyUseCircles(bconf.elipsoid, false); // radial tree never shows bootstrap circles as ellipsoids
3660
3661                {
3662                    AW_click_cd cdr(device, 0, CL_ROOTNODE);
3663                    empty_box(displayed_root->gr.gc, Origin, NT_ROOT_WIDTH);
3664                }
3665                show_radial_tree(displayed_root, Origin, Origin, Eastwards, 2*M_PI);
3666
3667                range_display_size  = 3.0/AW_PLANAR_COLORS;
3668                range_origin       += Vector(-range_display_size*AW_PLANAR_COLORS/2, -range_display_size*AW_PLANAR_COLORS/2);
3669                break;
3670            }
3671            case AP_TREE_IRS:
3672                show_irs_tree(displayed_root, scaled_branch_distance);
3673                break;
3674
3675            case AP_LIST_NDS:       // this is the list all/marked species mode (no tree)
3676                show_nds_list(gb_main, true);
3677                break;
3678
3679            case AP_LIST_SIMPLE:    // simple list of names (used at startup only)
3680                // don't see why we need to draw ANY tree at startup -> disabled
3681                // show_nds_list(gb_main, false);
3682                allow_range_display = false;
3683                break;
3684        }
3685        if (selSpec.was_displayed()) {
3686            AP_tree     *selNode = selSpec.get_node();
3687            AW_click_cd  cd(device, AW_CL(selNode ? selNode->gb_node : NULp), CL_SPECIES);
3688            empty_box(AWT_GC_CURSOR, selSpec.get_pos(), NT_SELECTED_WIDTH);
3689        }
3690        if (is_tree_style(tree_style)) show_ruler(disp_device, AWT_GC_CURSOR);
3691
3692        if (allow_range_display) {
3693            AW_displayColorRange(disp_device, AWT_GC_FIRST_RANGE_COLOR, range_origin, range_display_size, range_display_size);
3694        }
3695    }
3696
3697    if (cmd_data && Dragged::valid_drag_device(disp_device)) {
3698        Dragged *dragging = dynamic_cast<Dragged*>(cmd_data);
3699        if (dragging) {
3700            // if tree is redisplayed while dragging, redraw the drag indicator.
3701            // (happens in modes which modify the tree during drag, e.g. when scaling branches)
3702            dragging->draw_drag_indicator(disp_device, drag_gc);
3703        }
3704    }
3705
3706    disp_device = NULp;
3707}
3708
3709inline unsigned percentMarked(const AP_tree_members& gr) {
3710    double   percent = double(gr.mark_sum)/gr.leaf_sum;
3711    unsigned pc      = unsigned(percent*100.0+0.5);
3712
3713    if (pc == 0) {
3714        td_assert(gr.mark_sum>0); // otherwise function should not be called
3715        pc = 1; // do not show '0%' for range ]0.0 .. 0.05[
3716    }
3717    else if (pc == 100) {
3718        if (gr.mark_sum<gr.leaf_sum) {
3719            pc = 99; // do not show '100%' for range [0.95 ... 1.0[
3720        }
3721    }
3722    return pc;
3723}
3724
3725const GroupInfo& AWT_graphic_tree::get_group_info(AP_tree *at, GroupInfoMode mode, bool swap) const {
3726    static GroupInfo info = { NULp, NULp, 0, 0 };
3727
3728    info.name = NULp;
3729    if (at->father) {
3730        static SmartCharPtr copy;
3731        if (!at->is_leaf() && at->is_normal_group()) {
3732            if (at->is_keeled_group()) { // keeled + named
3733                info.name = make_group_text_nds(gb_main, at->gb_node, NDS_OUTPUT_LEAFTEXT, at, tree_static->get_tree_name()); // normal
3734                copy = strdup(info.name);
3735                info.name = make_group_text_nds(gb_main, at->father->gb_node, NDS_OUTPUT_LEAFTEXT, at, tree_static->get_tree_name()); // keeled
3736
3737                copy = GBS_global_string_copy("%s = %s", &*copy, info.name);
3738                info.name = &*copy;
3739            }
3740            else { // only named group
3741                info.name = make_group_text_nds(gb_main, at->gb_node, NDS_OUTPUT_LEAFTEXT, at, tree_static->get_tree_name());
3742            }
3743        }
3744        else if (at->is_keeled_group()) {
3745            info.name = make_group_text_nds(gb_main, at->father->gb_node, NDS_OUTPUT_LEAFTEXT, at, tree_static->get_tree_name());
3746        }
3747#if defined(ASSERTION_USED)
3748        else {
3749            td_assert(0); // why was get_group_info called?
3750        }
3751#endif
3752    }
3753    else {
3754        if (at->gb_node) {
3755            td_assert(0); // if this never happens -> remove case
3756            info.name = tree_static->get_tree_name();
3757        }
3758    }
3759    if (info.name && !info.name[0]) info.name = NULp;
3760    info.name_len = info.name ? strlen(info.name) : 0;
3761
3762    static char countBuf[50];
3763    countBuf[0] = 0;
3764
3765    GroupCountMode count_mode = group_count_mode;
3766
3767    if (!at->gr.mark_sum) { // do not display zero marked
3768        switch (count_mode) {
3769            case GCM_NONE:
3770            case GCM_MEMBERS: break; // unchanged
3771
3772            case GCM_PERCENT:
3773            case GCM_MARKED: count_mode = GCM_NONE; break; // completely skip
3774
3775            case GCM_BOTH:
3776            case GCM_BOTH_PC: count_mode = GCM_MEMBERS; break; // fallback to members-only
3777        }
3778    }
3779
3780    switch (count_mode) {
3781        case GCM_NONE:    break;
3782        case GCM_MEMBERS: sprintf(countBuf, "%u",      at->gr.leaf_sum);                        break;
3783        case GCM_MARKED:  sprintf(countBuf, "%u",      at->gr.mark_sum);                        break;
3784        case GCM_BOTH:    sprintf(countBuf, "%u/%u",   at->gr.mark_sum, at->gr.leaf_sum);       break;
3785        case GCM_PERCENT: sprintf(countBuf, "%u%%",    percentMarked(at->gr));                  break;
3786        case GCM_BOTH_PC: sprintf(countBuf, "%u%%/%u", percentMarked(at->gr), at->gr.leaf_sum); break;
3787    }
3788
3789    if (countBuf[0]) {
3790        info.count     = countBuf;
3791        info.count_len = strlen(info.count);
3792
3793        bool parentize = mode != GI_SEPARATED;
3794        if (parentize) {
3795            memmove(countBuf+1, countBuf, info.count_len);
3796            countBuf[0] = '(';
3797            strcpy(countBuf+info.count_len+1, ")");
3798            info.count_len += 2;
3799        }
3800    }
3801    else {
3802        info.count     = NULp;
3803        info.count_len = 0;
3804    }
3805
3806    if (mode == GI_COMBINED) {
3807        if (info.name) {
3808            if (info.count) {
3809                info.name      = GBS_global_string("%s %s", info.name, info.count);
3810                info.name_len += info.count_len+1;
3811
3812                info.count     = NULp;
3813                info.count_len = 0;
3814            }
3815        }
3816        else if (info.count) {
3817            swap = !swap;
3818        }
3819    }
3820
3821    if (swap) {
3822        std::swap(info.name, info.count);
3823        std::swap(info.name_len, info.count_len);
3824    }
3825
3826    return info;
3827}
3828
3829void AWT_graphic_tree::install_tree_changed_callback(const GraphicTreeCallback& gtcb) {
3830    /*! install a callback called whenever
3831     *  - topology changes (either by DB-change or by GUI command),
3832     *  - logical zoom changes or
3833     *  - a different tree gets displayed.
3834     */
3835    td_assert(tree_changed_cb == treeChangeIgnore_cb);
3836    tree_changed_cb = gtcb;
3837}
3838void AWT_graphic_tree::uninstall_tree_changed_callback() {
3839    td_assert(!(tree_changed_cb == treeChangeIgnore_cb));
3840    tree_changed_cb = treeChangeIgnore_cb;
3841}
3842
3843void AWT_graphic_tree::fast_sync_changed_folding(AP_tree *parent_of_all_changes) {
3844    // Does work normally done by [save_to_DB + update_structure],
3845    // but works only correctly if nothing but folding has changed.
3846
3847    if (!exports.needs_save()) {
3848        // td_assert(!exports.needs_structure_update()); // if that happens -> do what????
3849#if defined(ASSERTION_USED)
3850        bool needed_structure_update = exports.needs_structure_update();
3851#endif
3852        if (display_markers) display_markers->flush_cache();
3853        parent_of_all_changes->recompute_and_write_folding();
3854
3855        td_assert(needed_structure_update == exports.needs_structure_update()); // structure update gets delayed (@@@ not correct, but got no idea how to fix it correctly)
3856        exports.request_resize();
3857        notify_synchronized(NULp); // avoid reload
3858    }
3859}
3860
3861AWT_graphic_tree *NT_generate_tree(AW_root *root, GBDATA *gb_main, AD_map_viewer_cb map_viewer_cb) {
3862    AWT_graphic_tree *apdt = new AWT_graphic_tree(root, gb_main, map_viewer_cb);
3863    apdt->init(new AliView(gb_main), NULp, true, false); // tree w/o sequence data
3864    return apdt;
3865}
3866
3867static void markerThresholdChanged_cb(AW_root *root, bool partChanged) {
3868    static bool avoid_recursion = false;
3869    if (!avoid_recursion) {
3870        LocallyModify<bool> flag(avoid_recursion, true);
3871
3872        AW_awar *awar_marked     = root->awar(AWAR_DTREE_GROUP_MARKED_THRESHOLD);
3873        AW_awar *awar_partMarked = root->awar(AWAR_DTREE_GROUP_PARTIALLY_MARKED_THRESHOLD);
3874
3875        float marked     = awar_marked->read_float();
3876        float partMarked = awar_partMarked->read_float();
3877
3878        if (partMarked>marked) { // unwanted state
3879            if (partChanged) {
3880                awar_marked->write_float(partMarked);
3881            }
3882            else {
3883                awar_partMarked->write_float(marked);
3884            }
3885        }
3886        root->awar(AWAR_TREE_REFRESH)->touch();
3887    }
3888}
3889
3890void TREE_create_awars(AW_root *aw_root, AW_default db) {
3891    aw_root->awar_int  (AWAR_DTREE_BASELINEWIDTH,   1)  ->set_minmax (1,    10);
3892    aw_root->awar_float(AWAR_DTREE_VERICAL_DIST,    1.0)->set_minmax (0.01, 30);
3893    aw_root->awar_int  (AWAR_DTREE_BRANCH_STYLE,    BS_RECTANGULAR);
3894    aw_root->awar_float(AWAR_DTREE_ATTACH_SIZE,    -1.0)->set_minmax (-1.0,  1.0);
3895    aw_root->awar_float(AWAR_DTREE_ATTACH_LEN,      0.0)->set_minmax (-1.0,  1.0);
3896    aw_root->awar_float(AWAR_DTREE_ATTACH_GROUP,    0.0)->set_minmax (-1.0,  1.0);
3897    aw_root->awar_float(AWAR_DTREE_GROUP_DOWNSCALE, 0.33)->set_minmax(0.0,  1.0);
3898    aw_root->awar_float(AWAR_DTREE_GROUP_SCALE,     1.0)->set_minmax (0.01, 10.0);
3899
3900    aw_root->awar_int(AWAR_DTREE_AUTO_JUMP,      AP_JUMP_KEEP_VISIBLE);
3901    aw_root->awar_int(AWAR_DTREE_AUTO_JUMP_TREE, AP_JUMP_FORCE_VCENTER);
3902    aw_root->awar_int(AWAR_DTREE_AUTO_UNFOLD,    1);
3903
3904    aw_root->awar_int(AWAR_DTREE_GROUPCOUNTMODE, GCM_MEMBERS);
3905    aw_root->awar_int(AWAR_DTREE_GROUPINFOPOS,   GIP_SEPARATED);
3906
3907    aw_root->awar_int(AWAR_DTREE_SHOW_BRACKETS,  1);
3908
3909    aw_root->awar_int  (AWAR_DTREE_BOOTSTRAP_SHOW,  1);
3910    aw_root->awar_int  (AWAR_DTREE_BOOTSTRAP_STYLE, BS_PERCENT);
3911    aw_root->awar_int  (AWAR_DTREE_BOOTSTRAP_MIN,   0)->set_minmax(0,100);
3912    aw_root->awar_int  (AWAR_DTREE_BOOTSTRAP_MAX,   99)->set_minmax(0,100);
3913    aw_root->awar_int  (AWAR_DTREE_CIRCLE_SHOW,     0);
3914    aw_root->awar_int  (AWAR_DTREE_CIRCLE_ELLIPSE,  1);
3915    aw_root->awar_int  (AWAR_DTREE_CIRCLE_FILL,     50)->set_minmax(0, 100); // draw bootstrap circles 50% greyscaled
3916    aw_root->awar_float(AWAR_DTREE_CIRCLE_ZOOM,     1.0)->set_minmax(0.01, 30);
3917    aw_root->awar_float(AWAR_DTREE_CIRCLE_LIMIT,    2.0)->set_minmax(0.01, 30);
3918
3919    aw_root->awar_int(AWAR_DTREE_GROUP_STYLE,  GS_TRAPEZE);
3920    aw_root->awar_int(AWAR_DTREE_GROUP_ORIENT, GO_TOP);
3921
3922    aw_root->awar_int(AWAR_DTREE_GREY_LEVEL, 20)->set_minmax(0, 100);
3923
3924    aw_root->awar_int(AWAR_DTREE_RADIAL_ZOOM_TEXT, 0);
3925    aw_root->awar_int(AWAR_DTREE_RADIAL_XPAD,      150)->set_minmax(-100, 2000);
3926    aw_root->awar_int(AWAR_DTREE_DENDRO_ZOOM_TEXT, 0);
3927    aw_root->awar_int(AWAR_DTREE_DENDRO_XPAD,      300)->set_minmax(-100, 2000);
3928
3929    aw_root->awar_int  (AWAR_DTREE_MARKER_WIDTH,                     3)    ->set_minmax(1, 20);
3930    aw_root->awar_int  (AWAR_DTREE_PARTIAL_GREYLEVEL,                37)   ->set_minmax(0, 100);
3931    aw_root->awar_float(AWAR_DTREE_GROUP_MARKED_THRESHOLD,           100.0)->set_minmax(0, 100);
3932    aw_root->awar_float(AWAR_DTREE_GROUP_PARTIALLY_MARKED_THRESHOLD, 0.0)  ->set_minmax(0, 100);
3933
3934    aw_root->awar_int(AWAR_TREE_REFRESH,   0, db);
3935    aw_root->awar_int(AWAR_TREE_RECOMPUTE, 0, db);
3936}
3937
3938static void TREE_recompute_and_resize_cb(UNFIXED, TREE_canvas *ntw) {
3939    AWT_auto_refresh  allowed_on(ntw);
3940    AWT_graphic_tree *gt   = DOWNCAST(AWT_graphic_tree*, ntw->gfx);
3941    AP_tree          *root = gt->get_root_node();
3942    if (root) {
3943        gt->read_tree_settings(); // update settings for group-scaling
3944        ntw->request_structure_update();
3945    }
3946    ntw->request_resize();
3947}
3948static void TREE_resize_cb(UNFIXED, TREE_canvas *ntw) {
3949    AWT_auto_refresh allowed_on(ntw);
3950    ntw->request_resize();
3951}
3952
3953static void bootstrap_range_changed_cb(AW_root *awr, TREE_canvas *ntw, int upper_changed) {
3954    // couple limits of bootstrap range
3955    static bool in_recursion = false;
3956    if (!in_recursion) {
3957        LocallyModify<bool> avoid(in_recursion, true);
3958
3959        AW_awar *alower = awr->awar(AWAR_DTREE_BOOTSTRAP_MIN);
3960        AW_awar *aupper = awr->awar(AWAR_DTREE_BOOTSTRAP_MAX);
3961
3962        int rlower = alower->read_int();
3963        int rupper = aupper->read_int();
3964
3965        if (rlower>rupper) { // need correction
3966            if (upper_changed) {
3967                alower->write_int(rupper);
3968            }
3969            else {
3970                aupper->write_int(rlower);
3971            }
3972        }
3973
3974        AWT_auto_refresh allowed_on(ntw);
3975        ntw->request_refresh();
3976    }
3977}
3978
3979void TREE_install_update_callbacks(TREE_canvas *ntw) {
3980    // install all callbacks needed to make the tree-display update properly
3981
3982    AW_root *awr = ntw->awr;
3983
3984    // bind to all options available in 'Tree options'
3985    RootCallback expose_cb = makeRootCallback(AWT_expose_cb, static_cast<AWT_canvas*>(ntw));
3986    awr->awar(AWAR_DTREE_BASELINEWIDTH)  ->add_callback(expose_cb);
3987    awr->awar(AWAR_DTREE_BOOTSTRAP_SHOW) ->add_callback(expose_cb);
3988    awr->awar(AWAR_DTREE_BOOTSTRAP_STYLE)->add_callback(expose_cb);
3989    awr->awar(AWAR_DTREE_CIRCLE_SHOW)    ->add_callback(expose_cb);
3990    awr->awar(AWAR_DTREE_CIRCLE_FILL)    ->add_callback(expose_cb);
3991    awr->awar(AWAR_DTREE_SHOW_BRACKETS)  ->add_callback(expose_cb);
3992    awr->awar(AWAR_DTREE_CIRCLE_ZOOM)    ->add_callback(expose_cb);
3993    awr->awar(AWAR_DTREE_CIRCLE_LIMIT)   ->add_callback(expose_cb);
3994    awr->awar(AWAR_DTREE_CIRCLE_ELLIPSE) ->add_callback(expose_cb);
3995    awr->awar(AWAR_DTREE_GROUP_STYLE)    ->add_callback(expose_cb);
3996    awr->awar(AWAR_DTREE_GROUP_ORIENT)   ->add_callback(expose_cb);
3997    awr->awar(AWAR_DTREE_GREY_LEVEL)     ->add_callback(expose_cb);
3998    awr->awar(AWAR_DTREE_GROUPCOUNTMODE) ->add_callback(expose_cb);
3999    awr->awar(AWAR_DTREE_GROUPINFOPOS)   ->add_callback(expose_cb);
4000    awr->awar(AWAR_DTREE_BRANCH_STYLE)   ->add_callback(expose_cb);
4001    awr->awar(AWAR_DTREE_ATTACH_SIZE)    ->add_callback(expose_cb);
4002    awr->awar(AWAR_DTREE_ATTACH_LEN)     ->add_callback(expose_cb);
4003    awr->awar(AWAR_DTREE_ATTACH_GROUP)   ->add_callback(expose_cb);
4004
4005    awr->awar(AWAR_DTREE_BOOTSTRAP_MIN)  ->add_callback(makeRootCallback(bootstrap_range_changed_cb, ntw, 0));
4006    awr->awar(AWAR_DTREE_BOOTSTRAP_MAX)  ->add_callback(makeRootCallback(bootstrap_range_changed_cb, ntw, 1));
4007
4008    RootCallback reinit_treetype_cb = makeRootCallback(NT_reinit_treetype, ntw);
4009    awr->awar(AWAR_DTREE_RADIAL_ZOOM_TEXT)->add_callback(reinit_treetype_cb);
4010    awr->awar(AWAR_DTREE_RADIAL_XPAD)     ->add_callback(reinit_treetype_cb);
4011    awr->awar(AWAR_DTREE_DENDRO_ZOOM_TEXT)->add_callback(reinit_treetype_cb);
4012    awr->awar(AWAR_DTREE_DENDRO_XPAD)     ->add_callback(reinit_treetype_cb);
4013
4014    RootCallback resize_cb = makeRootCallback(TREE_resize_cb, ntw);
4015    awr->awar(AWAR_DTREE_VERICAL_DIST)->add_callback(resize_cb);
4016
4017    RootCallback recompute_and_resize_cb = makeRootCallback(TREE_recompute_and_resize_cb, ntw);
4018    awr->awar(AWAR_DTREE_GROUP_SCALE)    ->add_callback(recompute_and_resize_cb);
4019    awr->awar(AWAR_DTREE_GROUP_DOWNSCALE)->add_callback(recompute_and_resize_cb);
4020
4021    // global refresh trigger (used where a refresh is/was missing)
4022    awr->awar(AWAR_TREE_REFRESH)->add_callback(expose_cb);
4023    awr->awar(AWAR_TREE_RECOMPUTE)->add_callback(recompute_and_resize_cb);
4024
4025    // refresh on NDS changes
4026    GBDATA *gb_arb_presets = GB_search(ntw->gb_main, "arb_presets", GB_CREATE_CONTAINER);
4027    GB_add_callback(gb_arb_presets, GB_CB_CHANGED, makeDatabaseCallback(AWT_expose_cb, static_cast<AWT_canvas*>(ntw)));
4028
4029    // track selected species (autoscroll)
4030    awr->awar(AWAR_SPECIES_NAME)->add_callback(makeRootCallback(TREE_auto_jump_cb, ntw, AP_JUMP_REASON_SPECIES));
4031
4032    // refresh on changes of marker display settings
4033    awr->awar(AWAR_DTREE_MARKER_WIDTH)                    ->add_callback(expose_cb);
4034    awr->awar(AWAR_DTREE_PARTIAL_GREYLEVEL)               ->add_callback(expose_cb);
4035    awr->awar(AWAR_DTREE_GROUP_MARKED_THRESHOLD)          ->add_callback(makeRootCallback(markerThresholdChanged_cb,  false));
4036    awr->awar(AWAR_DTREE_GROUP_PARTIALLY_MARKED_THRESHOLD)->add_callback(makeRootCallback(markerThresholdChanged_cb,  true));
4037}
4038
4039static void tree_insert_jump_option_menu(AW_window *aws, const char *label, const char *awar_name) {
4040    aws->label(label);
4041    aws->create_option_menu(awar_name, true);
4042    aws->insert_default_option("do nothing",        "n", AP_DONT_JUMP);
4043    aws->insert_option        ("keep visible",      "k", AP_JUMP_KEEP_VISIBLE);
4044    aws->insert_option        ("center vertically", "v", AP_JUMP_FORCE_VCENTER);
4045    aws->insert_option        ("center",            "c", AP_JUMP_FORCE_CENTER);
4046    aws->update_option_menu();
4047    aws->at_newline();
4048}
4049
4050static AWT_config_mapping_def tree_setting_config_mapping[] = {
4051
4052    // main tree settings:
4053    { AWAR_DTREE_BASELINEWIDTH,    "line_width" },
4054    { AWAR_DTREE_BRANCH_STYLE,     "branch_style" },
4055    { AWAR_DTREE_SHOW_BRACKETS,    "show_brackets" },
4056    { AWAR_DTREE_GROUP_STYLE,      "group_style" },
4057    { AWAR_DTREE_GROUP_ORIENT,     "group_orientation" },
4058    { AWAR_DTREE_GREY_LEVEL,       "grey_level" },
4059    { AWAR_DTREE_GROUPCOUNTMODE,   "group_countmode" },
4060    { AWAR_DTREE_GROUPINFOPOS,     "group_infopos" },
4061    { AWAR_DTREE_VERICAL_DIST,     "vert_dist" },
4062    { AWAR_DTREE_GROUP_SCALE,      "group_scale" },
4063    { AWAR_DTREE_GROUP_DOWNSCALE,  "group_downscale" },
4064    { AWAR_DTREE_AUTO_JUMP,        "auto_jump" },
4065    { AWAR_DTREE_AUTO_JUMP_TREE,   "auto_jump_tree" },
4066    { AWAR_DTREE_AUTO_UNFOLD,      "auto_unfold" },
4067
4068    // bootstrap sub window:
4069    { AWAR_DTREE_BOOTSTRAP_SHOW,   "show_bootstrap" },
4070    { AWAR_DTREE_BOOTSTRAP_MIN,    "bootstrap_min" },
4071    { AWAR_DTREE_BOOTSTRAP_MAX,    "bootstrap_max" },
4072    { AWAR_DTREE_BOOTSTRAP_STYLE,  "bootstrap_style" },
4073    { AWAR_DTREE_CIRCLE_SHOW,      "show_circle" },
4074    { AWAR_DTREE_CIRCLE_FILL,      "fill_circle" },
4075    { AWAR_DTREE_CIRCLE_ELLIPSE,   "use_ellipse" },
4076    { AWAR_DTREE_CIRCLE_ZOOM,      "circle_zoom" },
4077    { AWAR_DTREE_CIRCLE_LIMIT,     "circle_limit" },
4078
4079    // expert settings:
4080    { AWAR_DTREE_ATTACH_SIZE,      "attach_size" },
4081    { AWAR_DTREE_ATTACH_LEN,       "attach_len" },
4082    { AWAR_DTREE_ATTACH_GROUP,     "attach_group" },
4083    { AWAR_DTREE_DENDRO_ZOOM_TEXT, "dendro_zoomtext" },
4084    { AWAR_DTREE_DENDRO_XPAD,      "dendro_xpadding" },
4085    { AWAR_DTREE_RADIAL_ZOOM_TEXT, "radial_zoomtext" },
4086    { AWAR_DTREE_RADIAL_XPAD,      "radial_xpadding" },
4087
4088    { NULp, NULp }
4089};
4090
4091static const int SCALER_WIDTH = 250; // pixel
4092static const int LABEL_WIDTH  = 30;  // char
4093
4094static void insert_section_header(AW_window *aws, const char *title) {
4095    char *button_text = GBS_global_string_copy("%*s%s ]", LABEL_WIDTH+1, "[ ", title);
4096    aws->create_autosize_button(NULp, button_text);
4097    aws->at_newline();
4098    free(button_text);
4099}
4100
4101static AW_window *create_tree_expert_settings_window(AW_root *aw_root) {
4102    static AW_window_simple *aws = NULp;
4103    if (!aws) {
4104        aws = new AW_window_simple;
4105        aws->init(aw_root, "TREE_EXPERT_SETUP", "Expert tree settings");
4106
4107        aws->at(5, 5);
4108        aws->auto_space(5, 5);
4109        aws->label_length(LABEL_WIDTH);
4110        aws->button_length(8);
4111
4112        aws->callback(AW_POPDOWN);
4113        aws->create_button("CLOSE", "CLOSE", "C");
4114        aws->callback(makeHelpCallback("nt_tree_settings_expert.hlp"));
4115        aws->create_button("HELP", "HELP", "H");
4116        aws->at_newline();
4117
4118        insert_section_header(aws, "parent attach position");
4119
4120        aws->label("Attach by size");
4121        aws->create_input_field_with_scaler(AWAR_DTREE_ATTACH_SIZE, 4, SCALER_WIDTH);
4122        aws->at_newline();
4123
4124        aws->label("Attach by len");
4125        aws->create_input_field_with_scaler(AWAR_DTREE_ATTACH_LEN, 4, SCALER_WIDTH);
4126        aws->at_newline();
4127
4128        aws->label("Attach (at groups)");
4129        aws->create_input_field_with_scaler(AWAR_DTREE_ATTACH_GROUP, 4, SCALER_WIDTH);
4130        aws->at_newline();
4131
4132        insert_section_header(aws, "text zooming / padding");
4133
4134        const int PAD_SCALER_WIDTH = SCALER_WIDTH-39;
4135
4136        aws->label("Text zoom/pad (dendro)");
4137        aws->create_toggle(AWAR_DTREE_DENDRO_ZOOM_TEXT);
4138        aws->create_input_field_with_scaler(AWAR_DTREE_DENDRO_XPAD, 4, PAD_SCALER_WIDTH);
4139        aws->at_newline();
4140
4141        aws->label("Text zoom/pad (radial)");
4142        aws->create_toggle(AWAR_DTREE_RADIAL_ZOOM_TEXT);
4143        aws->create_input_field_with_scaler(AWAR_DTREE_RADIAL_XPAD, 4, PAD_SCALER_WIDTH);
4144        aws->at_newline();
4145
4146        aws->window_fit();
4147    }
4148    return aws;
4149}
4150
4151static AW_window *create_tree_bootstrap_settings_window(AW_root *aw_root) {
4152    static AW_window_simple *aws = NULp;
4153    if (!aws) {
4154        aws = new AW_window_simple;
4155        aws->init(aw_root, "TREE_BOOT_SETUP", "Bootstrap display settings");
4156
4157        aws->at(5, 5);
4158        aws->auto_space(5, 5);
4159        aws->label_length(LABEL_WIDTH);
4160        aws->button_length(8);
4161
4162        aws->callback(AW_POPDOWN);
4163        aws->create_button("CLOSE", "CLOSE", "C");
4164        aws->callback(makeHelpCallback("nt_tree_settings_bootstrap.hlp"));
4165        aws->create_button("HELP", "HELP", "H");
4166        aws->at_newline();
4167
4168        insert_section_header(aws, "visibility");
4169
4170        aws->label("Show bootstraps");
4171        aws->create_toggle(AWAR_DTREE_BOOTSTRAP_SHOW);
4172        aws->at_newline();
4173
4174        aws->label("Hide bootstraps below");
4175        aws->create_input_field_with_scaler(AWAR_DTREE_BOOTSTRAP_MIN, 4, SCALER_WIDTH);
4176        aws->at_newline();
4177
4178        aws->label("Hide bootstraps above");
4179        aws->create_input_field_with_scaler(AWAR_DTREE_BOOTSTRAP_MAX, 4, SCALER_WIDTH);
4180        aws->at_newline();
4181
4182        insert_section_header(aws, "style");
4183
4184        aws->label("Bootstrap style");
4185        aws->create_option_menu(AWAR_DTREE_BOOTSTRAP_STYLE, true);
4186        aws->insert_default_option("percent%", "p", BS_PERCENT);
4187        aws->insert_option        ("percent",  "c", BS_PERCENT_NOSIGN);
4188        aws->insert_option        ("float",    "f", BS_FLOAT);
4189        aws->update_option_menu();
4190        aws->at_newline();
4191
4192        insert_section_header(aws, "circles");
4193
4194        aws->label("Show bootstrap circles");
4195        aws->create_toggle(AWAR_DTREE_CIRCLE_SHOW);
4196        aws->at_newline();
4197
4198        aws->label("Greylevel of circles (%)");
4199        aws->create_input_field_with_scaler(AWAR_DTREE_CIRCLE_FILL, 4, SCALER_WIDTH);
4200        aws->at_newline();
4201
4202        aws->label("Use ellipses");
4203        aws->create_toggle(AWAR_DTREE_CIRCLE_ELLIPSE);
4204        aws->at_newline();
4205
4206        aws->label("Bootstrap circle zoom factor");
4207        aws->create_input_field_with_scaler(AWAR_DTREE_CIRCLE_ZOOM, 4, SCALER_WIDTH);
4208        aws->at_newline();
4209
4210        aws->label("Bootstrap radius limit");
4211        aws->create_input_field_with_scaler(AWAR_DTREE_CIRCLE_LIMIT, 4, SCALER_WIDTH, AW_SCALER_EXP_LOWER);
4212        aws->at_newline();
4213
4214        aws->window_fit();
4215    }
4216    return aws;
4217}
4218
4219AW_window *TREE_create_settings_window(AW_root *aw_root) {
4220    static AW_window_simple *aws = NULp;
4221    if (!aws) {
4222        aws = new AW_window_simple;
4223        aws->init(aw_root, "TREE_SETUP", "Tree settings");
4224        aws->load_xfig("awt/tree_settings.fig");
4225
4226        aws->at("close");
4227        aws->auto_space(5, 5);
4228        aws->label_length(LABEL_WIDTH);
4229        aws->button_length(8);
4230
4231        aws->callback(AW_POPDOWN);
4232        aws->create_button("CLOSE", "CLOSE", "C");
4233        aws->callback(makeHelpCallback("nt_tree_settings.hlp"));
4234        aws->create_button("HELP", "HELP", "H");
4235
4236        aws->at("button");
4237
4238        insert_section_header(aws, "branches");
4239
4240        aws->label("Base line width");
4241        aws->create_input_field_with_scaler(AWAR_DTREE_BASELINEWIDTH, 4, SCALER_WIDTH);
4242        aws->at_newline();
4243
4244        aws->label("Branch style");
4245        aws->create_option_menu(AWAR_DTREE_BRANCH_STYLE, true);
4246        aws->insert_default_option("Rectangular",     "R", BS_RECTANGULAR);
4247        aws->insert_option        ("Diagonal",        "D", BS_DIAGONAL);
4248        aws->update_option_menu();
4249        aws->at_newline();
4250
4251        insert_section_header(aws, "groups");
4252
4253        aws->label("Show group brackets");
4254        aws->create_toggle(AWAR_DTREE_SHOW_BRACKETS);
4255        aws->at_newline();
4256
4257        aws->label("Group style");
4258        aws->create_option_menu(AWAR_DTREE_GROUP_STYLE, true);
4259        aws->insert_default_option("Trapeze",  "z", GS_TRAPEZE);
4260        aws->insert_option        ("Triangle", "i", GS_TRIANGLE);
4261        aws->update_option_menu();
4262        aws->create_option_menu(AWAR_DTREE_GROUP_ORIENT, true);
4263        aws->insert_default_option("Top",      "T", GO_TOP);
4264        aws->insert_option        ("Bottom",   "B", GO_BOTTOM);
4265        aws->insert_option        ("Interior", "I", GO_INTERIOR);
4266        aws->insert_option        ("Exterior", "E", GO_EXTERIOR);
4267        aws->update_option_menu();
4268        aws->at_newline();
4269
4270        aws->label("Greylevel of groups (%)");
4271        aws->create_input_field_with_scaler(AWAR_DTREE_GREY_LEVEL, 4, SCALER_WIDTH);
4272        aws->at_newline();
4273
4274        aws->label("Show group counter");
4275        aws->create_option_menu(AWAR_DTREE_GROUPCOUNTMODE, true);
4276        aws->insert_default_option("None",            "N", GCM_NONE);
4277        aws->insert_option        ("Members",         "M", GCM_MEMBERS);
4278        aws->insert_option        ("Marked",          "a", GCM_MARKED);
4279        aws->insert_option        ("Marked/Members",  "b", GCM_BOTH);
4280        aws->insert_option        ("%Marked",         "%", GCM_PERCENT);
4281        aws->insert_option        ("%Marked/Members", "e", GCM_BOTH_PC);
4282        aws->update_option_menu();
4283        aws->at_newline();
4284
4285        aws->label("Group counter position");
4286        aws->create_option_menu(AWAR_DTREE_GROUPINFOPOS, true);
4287        aws->insert_default_option("Attached",  "A", GIP_ATTACHED);
4288        aws->insert_option        ("Overlayed", "O", GIP_OVERLAYED);
4289        aws->insert_option        ("Separated", "a", GIP_SEPARATED);
4290        aws->update_option_menu();
4291        aws->at_newline();
4292
4293        insert_section_header(aws, "vertical scaling");
4294
4295        aws->label("Vertical distance");
4296        aws->create_input_field_with_scaler(AWAR_DTREE_VERICAL_DIST, 4, SCALER_WIDTH, AW_SCALER_EXP_LOWER);
4297        aws->at_newline();
4298
4299        aws->label("Vertical group scaling");
4300        aws->create_input_field_with_scaler(AWAR_DTREE_GROUP_SCALE, 4, SCALER_WIDTH);
4301        aws->at_newline();
4302
4303        aws->label("'Biggroup' scaling");
4304        aws->create_input_field_with_scaler(AWAR_DTREE_GROUP_DOWNSCALE, 4, SCALER_WIDTH);
4305        aws->at_newline();
4306
4307        insert_section_header(aws, "auto focus");
4308
4309        tree_insert_jump_option_menu(aws, "On species change", AWAR_DTREE_AUTO_JUMP);
4310        tree_insert_jump_option_menu(aws, "On tree change",    AWAR_DTREE_AUTO_JUMP_TREE);
4311
4312        aws->label("Auto unfold selected species?");
4313        aws->create_toggle(AWAR_DTREE_AUTO_UNFOLD);
4314
4315        // complete top area of window
4316
4317        aws->at("config");
4318        AWT_insert_config_manager(aws, AW_ROOT_DEFAULT, "tree_settings", tree_setting_config_mapping);
4319
4320        aws->button_length(19);
4321
4322        aws->at("bv");
4323        aws->create_toggle(AWAR_DTREE_BOOTSTRAP_SHOW);
4324
4325        aws->at("bootstrap");
4326        aws->callback(create_tree_bootstrap_settings_window);
4327        aws->create_button("bootstrap", "Bootstrap settings", "B");
4328
4329        aws->at("expert");
4330        aws->callback(create_tree_expert_settings_window);
4331        aws->create_button("expert", "Expert settings", "E");
4332    }
4333    return aws;
4334}
4335
4336// --------------------------------------------------------------------------------
4337
4338AW_window *TREE_create_marker_settings_window(AW_root *root) {
4339    static AW_window_simple *aws = NULp;
4340
4341    if (!aws) {
4342        aws = new AW_window_simple;
4343
4344        aws->init(root, "MARKER_SETTINGS", "Tree marker settings");
4345
4346        aws->auto_space(10, 10);
4347
4348        aws->callback(AW_POPDOWN);
4349        aws->create_button("CLOSE", "CLOSE", "C");
4350        aws->callback(makeHelpCallback("nt_tree_marker_settings.hlp"));
4351        aws->create_button("HELP", "HELP", "H");
4352        aws->at_newline();
4353
4354        const int FIELDSIZE  = 5;
4355        const int SCALERSIZE = 250;
4356        aws->label_length(35);
4357
4358        aws->label("Group marked threshold");
4359        aws->create_input_field_with_scaler(AWAR_DTREE_GROUP_MARKED_THRESHOLD, FIELDSIZE, SCALERSIZE);
4360
4361        aws->at_newline();
4362
4363        aws->label("Group partially marked threshold");
4364        aws->create_input_field_with_scaler(AWAR_DTREE_GROUP_PARTIALLY_MARKED_THRESHOLD, FIELDSIZE, SCALERSIZE);
4365
4366        aws->at_newline();
4367
4368        aws->label("Marker width");
4369        aws->create_input_field_with_scaler(AWAR_DTREE_MARKER_WIDTH, FIELDSIZE, SCALERSIZE);
4370
4371        aws->at_newline();
4372
4373        aws->label("Partial marker greylevel");
4374        aws->create_input_field_with_scaler(AWAR_DTREE_PARTIAL_GREYLEVEL, FIELDSIZE, SCALERSIZE);
4375
4376        aws->at_newline();
4377    }
4378
4379    return aws;
4380}
4381
4382// --------------------------------------------------------------------------------
4383
4384#ifdef UNIT_TESTS
4385#include <test_unit.h>
4386#include <../../WINDOW/aw_common.hxx>
4387
4388static void fake_AD_map_viewer_cb(GBDATA *, AD_MAP_VIEWER_TYPE) {}
4389
4390static AW_rgb colors_def[] = {
4391    AW_NO_COLOR, AW_NO_COLOR, AW_NO_COLOR, AW_NO_COLOR, AW_NO_COLOR, AW_NO_COLOR,
4392    0x30b0e0,
4393    0xff8800, // AWT_GC_CURSOR
4394    0xa3b3cf, // AWT_GC_BRANCH_REMARK
4395    0x53d3ff, // AWT_GC_BOOTSTRAP
4396    0x808080, // AWT_GC_BOOTSTRAP_LIMITED
4397    0x000000, // AWT_GC_IRS_GROUP_BOX
4398    0xf0c000, // AWT_GC_ALL_MARKED
4399    0xbb8833, // AWT_GC_SOME_MARKED
4400    0x622300, // AWT_GC_NONE_MARKED
4401    0x977a0e, // AWT_GC_ONLY_ZOMBIES
4402
4403    0x000000, // AWT_GC_BLACK
4404    0x808080, // AWT_GC_WHITE
4405
4406    0xff0000, // AWT_GC_RED
4407    0x00ff00, // AWT_GC_GREEN
4408    0x0000ff, // AWT_GC_BLUE
4409
4410    0xc0ff40, // AWT_GC_ORANGE
4411    0x40c0ff, // AWT_GC_AQUAMARIN
4412    0xf030b0, // AWT_GC_PURPLE
4413
4414    0xffff00, // AWT_GC_YELLOW
4415    0x00ffff, // AWT_GC_CYAN
4416    0xff00ff, // AWT_GC_MAGENTA
4417
4418    0xc0ff40, // AWT_GC_LAWNGREEN
4419    0x40c0ff, // AWT_GC_SKYBLUE
4420    0xf030b0, // AWT_GC_PINK
4421
4422    0xd50000, // AWT_GC_FIRST_COLOR_GROUP
4423    0x00c0a0,
4424    0x00ff77,
4425    0xc700c7,
4426    0x0000ff,
4427    0xffce5b,
4428    0xab2323,
4429    0x008888,
4430    0x008800,
4431    0x880088,
4432    0x000088,
4433    0x888800,
4434    AW_NO_COLOR
4435};
4436static AW_rgb *fcolors       = colors_def;
4437static AW_rgb *dcolors       = colors_def;
4438static long    dcolors_count = ARRAY_ELEMS(colors_def);
4439
4440class fake_AW_GC : public AW_GC {
4441    void wm_set_foreground_color(AW_rgb /*col*/) OVERRIDE {  }
4442    void wm_set_function(AW_function /*mode*/) OVERRIDE { td_assert(0); }
4443    void wm_set_lineattributes(short /*lwidth*/, AW_linestyle /*lstyle*/) OVERRIDE {}
4444    void wm_set_font(AW_font /*font_nr*/, int size, int */*found_size*/) OVERRIDE {
4445        unsigned int i;
4446        for (i = AW_FONTINFO_CHAR_ASCII_MIN; i <= AW_FONTINFO_CHAR_ASCII_MAX; i++) {
4447            set_char_size(i, size, 0, size-2); // good fake size for Courier 8pt
4448        }
4449    }
4450public:
4451    fake_AW_GC(AW_common *common_) : AW_GC(common_) {}
4452    int get_available_fontsizes(AW_font /*font_nr*/, int */*available_sizes*/) const OVERRIDE {
4453        td_assert(0);
4454        return 0;
4455    }
4456};
4457
4458struct fake_AW_common : public AW_common {
4459    fake_AW_common()
4460        : AW_common(fcolors, dcolors, dcolors_count)
4461    {
4462        for (int gc = 0; gc < dcolors_count-AW_STD_COLOR_IDX_MAX; ++gc) { // gcs used in this example
4463            new_gc(gc);
4464            AW_GC *gcm = map_mod_gc(gc);
4465            gcm->set_line_attributes(1, AW_SOLID);
4466            gcm->set_function(AW_COPY);
4467            gcm->set_font(12, 8, NULp); // 12 is Courier (use monospaced here, cause font limits are faked)
4468
4469            gcm->set_fg_color(colors_def[gc+AW_STD_COLOR_IDX_MAX]);
4470        }
4471    }
4472    ~fake_AW_common() OVERRIDE {}
4473
4474    virtual AW_GC *create_gc() {
4475        return new fake_AW_GC(this);
4476    }
4477};
4478
4479class fake_AWT_graphic_tree FINAL_TYPE : public AWT_graphic_tree {
4480    int         var_mode; // current range: [0..3]
4481    double      att_size, att_len;
4482    BranchStyle bstyle;
4483
4484    void read_tree_settings() OVERRIDE {
4485        scaled_branch_distance = 1.0; // not final value!
4486
4487        // var_mode is in range [0..3]
4488        // it is used to vary tree settings such that many different combinations get tested
4489
4490        static const double group_attach[] = { 0.5, 1.0, 0.0, };
4491        static const double tested_greylevel[] = { 0.0, 0.25, 0.75, 1.0 };
4492
4493        int mvar_mode = var_mode+int(tree_style)+int(group_style); // [0..3] + [0..5] + [0..1] = [0..9]
4494
4495        groupScale.pow    = .33;
4496        groupScale.linear = (tree_style == AP_TREE_RADIAL) ? 7.0 : 1.0;
4497        group_greylevel   = tested_greylevel[mvar_mode%4];
4498
4499        baselinewidth     = (var_mode == 3)+1;
4500        show_brackets     = (var_mode != 2);
4501        group_style       = ((var_mode%2) == (bstyle == BS_DIAGONAL)) ? GS_TRAPEZE : GS_TRIANGLE;
4502        group_orientation = GroupOrientation((var_mode+1)%4);
4503        attach_size       = att_size;
4504        attach_len        = att_len;
4505        attach_group      = group_attach[var_mode%3];
4506        branch_style      = bstyle;
4507
4508        bconf.zoom_factor = 1.3;
4509        bconf.max_radius  = 1.95;
4510        bconf.show_circle = var_mode%3;
4511        bconf.fill_level  = 1.0 - group_greylevel; // for bootstrap circles use inverse shading of groups
4512
4513        bconf.elipsoid      = var_mode%2;
4514        bconf.show_boots    = !(var_mode == 0 && bstyle == BS_DIAGONAL);        // hide BS in dendro_MN_diagonal.fig
4515        bconf.bootstrap_min = var_mode<2    ?   0 :  5;                         // remove BS<5%  for var_mode 0,1
4516        bconf.bootstrap_max = !(var_mode%2) ? 100 : 95;                         // remove BS>95% for var_mode 0,2
4517        if (var_mode != ((bconf.show_circle+branch_style)%3) && bconf.bootstrap_max == 100) {
4518            bconf.bootstrap_max = 99; // hide 100% branches in those figs previously excluded via 'auto_add_100'
4519        }
4520
4521        bconf.style = (mvar_mode%4) ? BS_PERCENT : (int(group_style)%2 ? BS_PERCENT_NOSIGN : BS_FLOAT);
4522
4523        group_info_pos = GIP_SEPARATED;
4524        switch (var_mode) {
4525            case 2: group_info_pos = GIP_ATTACHED;  break;
4526            case 3: group_info_pos = GIP_OVERLAYED; break;
4527        }
4528
4529        switch (var_mode) {
4530            case 0: group_count_mode = GCM_MEMBERS; break;
4531            case 1: group_count_mode = GCM_NONE;  break;
4532            case 2: group_count_mode = (tree_style%2) ? GCM_MARKED : GCM_PERCENT;  break;
4533            case 3: group_count_mode = (tree_style%2) ? GCM_BOTH   : GCM_BOTH_PC;  break;
4534        }
4535    }
4536
4537public:
4538    fake_AWT_graphic_tree(GBDATA *gbmain, const char *selected_species)
4539        : AWT_graphic_tree(NULp, gbmain, fake_AD_map_viewer_cb),
4540          var_mode(0),
4541          att_size(0),
4542          att_len(0),
4543          bstyle(BS_RECTANGULAR)
4544    {
4545        species_name      = strdup(selected_species);
4546        exports.modifying = 1; // hack to workaround need for AWT_auto_refresh
4547    }
4548
4549    void set_var_mode(int mode) { var_mode = mode; }
4550    void set_attach(double asize, double alen) { att_size = asize; att_len  = alen; }
4551    void set_branchstyle(BranchStyle bstyle_) { bstyle = bstyle_; }
4552
4553    void test_show_tree(AW_device *device, bool force_compute) {
4554        if (force_compute) {
4555            // force reload and recompute (otherwise changing groupScale.linear has DELAYED effect)
4556            read_tree_settings();
4557            get_root_node()->compute_tree();
4558        }
4559        check_for_DB_update(get_gbmain()); // hack to workaround need for AWT_auto_refresh
4560        show(device);
4561    }
4562
4563    void test_print_tree(AW_device_print *print_device, AP_tree_display_style style, bool show_handles) {
4564        const int      SCREENSIZE = 541; // use a prime as screensize to reduce rounding errors
4565        AW_device_size size_device(print_device->get_common());
4566
4567        size_device.reset();
4568        size_device.zoom(1.0);
4569        size_device.set_filter(AW_SIZE|AW_SIZE_UNSCALED);
4570        test_show_tree(&size_device, true);
4571
4572        Rectangle drawn = size_device.get_size_information();
4573
4574        td_assert(drawn.surface() >= 0.0);
4575
4576        double zoomx = SCREENSIZE/drawn.width();
4577        double zoomy = SCREENSIZE/drawn.height();
4578        double zoom  = 0.0;
4579
4580        switch (style) {
4581            case AP_LIST_SIMPLE:
4582            case AP_TREE_RADIAL:
4583                zoom = std::max(zoomx, zoomy);
4584                break;
4585
4586            case AP_TREE_NORMAL:
4587            case AP_TREE_IRS:
4588                zoom = zoomx;
4589                break;
4590
4591            case AP_LIST_NDS:
4592                zoom = 1.0;
4593                break;
4594        }
4595
4596        if (!nearlyEqual(zoom, 1.0)) {
4597            // recalculate size
4598            size_device.restart_tracking();
4599            size_device.reset();
4600            size_device.zoom(zoom);
4601            size_device.set_filter(AW_SIZE|AW_SIZE_UNSCALED);
4602            test_show_tree(&size_device, false);
4603        }
4604
4605        drawn = size_device.get_size_information();
4606
4607        const AW_borders& text_overlap = size_device.get_unscaleable_overlap();
4608        Rectangle         drawn_text   = size_device.get_size_information_inclusive_text();
4609
4610        int            EXTRA = SCREENSIZE*0.05;
4611        AW_screen_area clipping;
4612
4613        clipping.l = 0; clipping.r = drawn.width()+text_overlap.l+text_overlap.r + 2*EXTRA;
4614        clipping.t = 0; clipping.b = drawn.height()+text_overlap.t+text_overlap.b + 2*EXTRA;
4615
4616        print_device->get_common()->set_screen(clipping);
4617        print_device->set_filter(AW_PRINTER|(show_handles ? AW_PRINTER_EXT : 0));
4618        print_device->reset();
4619
4620        print_device->zoom(zoom);
4621
4622        Rectangle drawn_world      = print_device->rtransform(drawn);
4623        Rectangle drawn_text_world = print_device->rtransform(drawn_text);
4624
4625        Vector extra_shift  = Vector(EXTRA, EXTRA);
4626        Vector corner_shift = -Vector(drawn.upper_left_corner());
4627        Vector text_shift = Vector(text_overlap.l, text_overlap.t);
4628
4629        Vector offset(extra_shift+corner_shift+text_shift);
4630        print_device->set_offset(offset/(zoom*zoom)); // dont really understand this, but it does the right shift
4631
4632        test_show_tree(print_device, false);
4633        print_device->box(AWT_GC_CURSOR,        AW::FillStyle::EMPTY, drawn_world);
4634        print_device->box(AWT_GC_IRS_GROUP_BOX, AW::FillStyle::EMPTY, drawn_text_world);
4635    }
4636};
4637
4638void fake_AW_init_color_groups();
4639void AW_init_color_groups(AW_root *awr, AW_default def);
4640
4641static bool replaceFirstRemark(TreeNode *node, const char *oldRem, const char *newRem) {
4642    if (!node->is_leaf()) {
4643        const char *rem = node->get_remark();
4644        if (rem && strcmp(rem, oldRem) == 0) {
4645            node->set_remark(newRem);
4646            return true;
4647        }
4648
4649        return
4650            replaceFirstRemark(node->get_leftson(), oldRem, newRem) ||
4651            replaceFirstRemark(node->get_rightson(), oldRem, newRem);
4652    }
4653    return false;
4654}
4655
4656void TEST_treeDisplay() {
4657    GB_shell  shell;
4658    GBDATA   *gb_main = GB_open("../../demo.arb", "r");
4659
4660    fake_AWT_graphic_tree agt(gb_main, "OctSprin");
4661    fake_AW_common        fake_common;
4662
4663    AW_device_print print_dev(&fake_common);
4664    AW_init_color_group_defaults(NULp);
4665    fake_AW_init_color_groups();
4666
4667    agt.init(new AliView(gb_main), NULp, true, false);
4668
4669    {
4670        GB_transaction ta(gb_main);
4671        ASSERT_RESULT(const char *, NULp, agt.load_from_DB(NULp, "tree_test")); // calls compute_tree once
4672    }
4673
4674    const char *spoolnameof[] = {
4675        "dendro",
4676        "radial",
4677        "irs",
4678        "nds",
4679        NULp, // "simple", (too simple, need no test)
4680    };
4681
4682    // modify some tree comments
4683    {
4684        AP_tree *rootNode = agt.get_root_node();
4685        TEST_EXPECT(replaceFirstRemark(rootNode, "97%", "hello")); // is displayed when bootstrap display is OFF (e.g. in dendro_MN_diagonal.fig)
4686        TEST_EXPECT(replaceFirstRemark(rootNode, "44%", "100%"));
4687
4688        GB_transaction ta(gb_main);
4689        ASSERT_RESULT(const char *, NULp, agt.save_to_DB(NULp, "tree_test"));
4690    }
4691
4692    for (int show_handles = 0; show_handles <= 1; ++show_handles) {
4693        for (int color = 0; color <= 1; ++color) {
4694            print_dev.set_color_mode(color);
4695            // for (int istyle = AP_TREE_NORMAL; istyle <= AP_LIST_SIMPLE; ++istyle) {
4696            for (int istyle = AP_LIST_SIMPLE; istyle >= AP_TREE_NORMAL; --istyle) {
4697                AP_tree_display_style style = AP_tree_display_style(istyle);
4698                if (spoolnameof[style]) {
4699                    char *spool_name = GBS_global_string_copy("display/%s_%c%c", spoolnameof[style], "MC"[color], "NH"[show_handles]);
4700
4701                    agt.set_tree_style(style, NULp);
4702
4703                    int var_mode = show_handles+2*color;
4704
4705                    static struct AttachSettings {
4706                        const char *suffix;
4707                        double      bySize;
4708                        double      byLength;
4709                    } attach_settings[] = {
4710                        { "",            -1,  0 }, // [size only] traditional attach point (produces old test results)
4711                        { "_long",        0,  1 }, // [len only]  attach at long branch
4712                        { "_shortSmall", -1, -1 }, // [both]      attach at short+small branch
4713                        { "_centered",    0,  0 }, // [none]      center attach points
4714                        { NULp,           0,  0 },
4715                    };
4716
4717                    for (int attach_style = 0; attach_settings[attach_style].suffix; ++attach_style) {
4718                        if (attach_style>0) {
4719                            if (style != AP_TREE_NORMAL) continue;  // test attach-styles only for dendrogram-view
4720                            if (attach_style != var_mode) continue; // do not create too many permutations
4721                        }
4722
4723                        const AttachSettings& SETT = attach_settings[attach_style];
4724                        char *spool_name2 = GBS_global_string_copy("%s%s", spool_name, SETT.suffix);
4725
4726                        for (BranchStyle bstyle = BS_RECTANGULAR; bstyle<=BS_DIAGONAL; bstyle = BranchStyle(bstyle+1)) {
4727                            if (bstyle != BS_RECTANGULAR) { // test alternate branch-styles only ..
4728                                if (istyle != AP_TREE_NORMAL) continue; // .. for dendrogram view
4729                                if (attach_style != 0 && attach_style != 3) continue; // .. for traditional and centered attach_points
4730                            }
4731
4732                            static const char *suffix[] = {
4733                                "",
4734                                "_diagonal",
4735                            };
4736
4737                            char *spool_name3 = GBS_global_string_copy("%s%s", spool_name2, suffix[bstyle]);
4738
4739// #define TEST_AUTO_UPDATE // dont test, instead update expected results
4740                            {
4741                                char *spool_file     = GBS_global_string_copy("%s_curr.fig", spool_name3);
4742                                char *spool_expected = GBS_global_string_copy("%s.fig", spool_name3);
4743
4744#if defined(TEST_AUTO_UPDATE)
4745#warning TEST_AUTO_UPDATE is active (non-default)
4746                                TEST_EXPECT_NO_ERROR(print_dev.open(spool_expected));
4747#else
4748                                TEST_EXPECT_NO_ERROR(print_dev.open(spool_file));
4749#endif
4750
4751                                {
4752                                    GB_transaction ta(gb_main);
4753                                    agt.set_var_mode(var_mode);
4754                                    agt.set_attach(SETT.bySize, SETT.byLength);
4755                                    agt.set_branchstyle(bstyle);
4756                                    agt.test_print_tree(&print_dev, style, show_handles);
4757                                }
4758
4759                                print_dev.close();
4760
4761#if !defined(TEST_AUTO_UPDATE)
4762                                TEST_EXPECT_TEXTFILES_EQUAL(spool_expected, spool_file);
4763                                TEST_EXPECT_ZERO_OR_SHOW_ERRNO(unlink(spool_file));
4764#endif
4765                                free(spool_expected);
4766                                free(spool_file);
4767                            }
4768                            free(spool_name3);
4769                        }
4770                        free(spool_name2);
4771                    }
4772                    free(spool_name);
4773                }
4774            }
4775        }
4776    }
4777
4778    GB_close(gb_main);
4779}
4780
4781#endif // UNIT_TESTS
4782
Note: See TracBrowser for help on using the repository browser.