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

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