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

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