source: tags/testbuild/SL/TREEDISP/TreeDisplay.cxx

Last change on this file was 13236, checked in by westram, 10 years ago
  • use AP_pars_root instead of AP_tree_root for AP_tree_nlen-trees (currently just a stub)
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 106.6 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
13#include <nds.h>
14#include <aw_preset.hxx>
15#include <aw_awars.hxx>
16#include <aw_msg.hxx>
17#include <aw_root.hxx>
18#include <aw_question.hxx>
19
20#include <awt_attributes.hxx>
21#include <arb_defs.h>
22#include <arb_strarray.h>
23#include <arb_diff.h>
24
25#include <unistd.h>
26#include <iostream>
27#include <arb_global_defs.h>
28#include <cfloat>
29
30/*!*************************
31  class AP_tree
32****************************/
33
34#define RULER_LINEWIDTH "ruler/ruler_width" // line width of ruler
35#define RULER_SIZE      "ruler/size"
36
37#define DEFAULT_RULER_LINEWIDTH tree_defaults::LINEWIDTH
38#define DEFAULT_RULER_LENGTH    tree_defaults::LENGTH
39
40using namespace AW;
41
42AW_gc_manager AWT_graphic_tree::init_devices(AW_window *aww, AW_device *device, AWT_canvas* ntw) {
43    AW_gc_manager gc_manager =
44        AW_manage_GC(aww,
45                     ntw->get_gc_base_name(),
46                     device, AWT_GC_CURSOR, AWT_GC_MAX, AW_GCM_DATA_AREA,
47                     makeWindowCallback(AWT_resize_cb, ntw),
48                     true,      // define color groups
49                     "#3be",
50
51                     // Important note :
52                     // Many gc indices are shared between ABR_NTREE and ARB_PARSIMONY
53                     // e.g. the tree drawing routines use same gc's for drawing both trees
54                     // (check PARS_dtree.cxx AWT_graphic_parsimony::init_devices)
55
56                     "Cursor$white",
57                     "Branch remarks$#b6ffb6",
58                     "+-Bootstrap$#53d3ff",    "-B.(limited)$white",
59                     "-GROUP_BRACKETS$#000",
60                     "Marked$#ffe560",
61                     "Some marked$#bb8833",
62                     "Not marked$#622300",
63                     "Zombies etc.$#977a0e",
64
65                     "+-No probe$black",    "-Probes 1+2$yellow",
66                     "+-Probe 1$red",       "-Probes 1+3$magenta",
67                     "+-Probe 2$green",     "-Probes 2+3$cyan",
68                     "+-Probe 3$blue",      "-All probes$white",
69                     NULL);
70
71    return gc_manager;
72}
73
74void AWT_graphic_tree::mark_species_in_tree(AP_tree *at, int mark_mode) {
75    /*
76      mode      does
77
78      0         unmark all
79      1         mark all
80      2         invert all marks
81    */
82
83    if (!at) return;
84
85    GB_transaction ta(tree_static->get_gb_main());
86    if (at->is_leaf) {
87        if (at->gb_node) {      // not a zombie
88            switch (mark_mode) {
89                case 0: GB_write_flag(at->gb_node, 0); break;
90                case 1: GB_write_flag(at->gb_node, 1); break;
91                case 2: GB_write_flag(at->gb_node, !GB_read_flag(at->gb_node)); break;
92                default: td_assert(0);
93            }
94        }
95    }
96
97    mark_species_in_tree(at->get_leftson(), mark_mode);
98    mark_species_in_tree(at->get_rightson(), mark_mode);
99}
100
101void AWT_graphic_tree::mark_species_in_tree_that(AP_tree *at, int mark_mode, int (*condition)(GBDATA*, void*), void *cd) {
102    /*
103      mark_mode does
104
105      0         unmark all
106      1         mark all
107      2         invert all marks
108
109      marks are only changed for those species for that condition() != 0
110    */
111
112    if (!at) return;
113
114    GB_transaction ta(tree_static->get_gb_main());
115    if (at->is_leaf) {
116        if (at->gb_node) {      // not a zombie
117            int oldMark = GB_read_flag(at->gb_node);
118            if (oldMark != mark_mode && condition(at->gb_node, cd)) {
119                switch (mark_mode) {
120                    case 0: GB_write_flag(at->gb_node, 0); break;
121                    case 1: GB_write_flag(at->gb_node, 1); break;
122                    case 2: GB_write_flag(at->gb_node, !oldMark); break;
123                    default: td_assert(0);
124                }
125            }
126        }
127    }
128
129    mark_species_in_tree_that(at->get_leftson(), mark_mode, condition, cd);
130    mark_species_in_tree_that(at->get_rightson(), mark_mode, condition, cd);
131}
132
133
134// same as mark_species_in_tree but works on rest of tree
135void AWT_graphic_tree::mark_species_in_rest_of_tree(AP_tree *at, int mark_mode) {
136    if (at) {
137        AP_tree *pa = at->get_father();
138        if (pa) {
139            GB_transaction ta(tree_static->get_gb_main());
140
141            mark_species_in_tree(at->get_brother(), mark_mode);
142            mark_species_in_rest_of_tree(pa, mark_mode);
143        }
144    }
145}
146
147// same as mark_species_in_tree_that but works on rest of tree
148void AWT_graphic_tree::mark_species_in_rest_of_tree_that(AP_tree *at, int mark_mode, int (*condition)(GBDATA*, void*), void *cd) {
149    if (at) {
150        AP_tree *pa = at->get_father();
151        if (pa) {
152            GB_transaction ta(tree_static->get_gb_main());
153
154            mark_species_in_tree_that(at->get_brother(), mark_mode, condition, cd);
155            mark_species_in_rest_of_tree_that(pa, mark_mode, condition, cd);
156        }
157    }
158}
159
160bool AWT_graphic_tree::tree_has_marks(AP_tree *at) {
161    if (!at) return false;
162
163    if (at->is_leaf) {
164        if (!at->gb_node) return false; // zombie
165        int marked = GB_read_flag(at->gb_node);
166        return marked;
167    }
168
169    return tree_has_marks(at->get_leftson()) || tree_has_marks(at->get_rightson());
170}
171
172bool AWT_graphic_tree::rest_tree_has_marks(AP_tree *at) {
173    if (!at) return false;
174
175    AP_tree *pa = at->get_father();
176    if (!pa) return false;
177
178    return tree_has_marks(at->get_brother()) || rest_tree_has_marks(pa);
179}
180
181struct AWT_graphic_tree_group_state {
182    int closed, opened;
183    int closed_terminal, opened_terminal;
184    int closed_with_marked, opened_with_marked;
185    int marked_in_groups, marked_outside_groups;
186
187    void clear() {
188        closed                = 0;
189        opened                = 0;
190        closed_terminal       = 0;
191        opened_terminal       = 0;
192        closed_with_marked    = 0;
193        opened_with_marked    = 0;
194        marked_in_groups      = 0;
195        marked_outside_groups = 0;
196    }
197
198    AWT_graphic_tree_group_state() { clear(); }
199
200    bool has_groups() const { return closed+opened; }
201    int marked() const { return marked_in_groups+marked_outside_groups; }
202
203    bool all_opened() const { return closed == 0 && opened>0; }
204    bool all_closed() const { return opened == 0 && closed>0; }
205    bool all_terminal_closed() const { return opened_terminal == 0 && closed_terminal == closed; }
206    bool all_marked_opened() const { return marked_in_groups > 0 && closed_with_marked == 0; }
207
208    CollapseMode next_expand_mode() const {
209        if (closed_with_marked) return EXPAND_MARKED;
210        return EXPAND_ALL;
211    }
212
213    CollapseMode next_collapse_mode() const {
214        if (all_terminal_closed()) return COLLAPSE_ALL;
215        return COLLAPSE_TERMINAL;
216    }
217};
218
219void AWT_graphic_tree::detect_group_state(AP_tree *at, AWT_graphic_tree_group_state *state, AP_tree *skip_this_son) {
220    if (!at) return;
221    if (at->is_leaf) {
222        if (at->gb_node && GB_read_flag(at->gb_node)) state->marked_outside_groups++; // count marks
223        return;                 // leafs never get grouped
224    }
225
226    if (at->is_named_group()) {
227        AWT_graphic_tree_group_state sub_state;
228        if (at->leftson != skip_this_son) detect_group_state(at->get_leftson(), &sub_state, skip_this_son);
229        if (at->rightson != skip_this_son) detect_group_state(at->get_rightson(), &sub_state, skip_this_son);
230
231        if (at->gr.grouped) {   // a closed group
232            state->closed++;
233            if (!sub_state.has_groups()) state->closed_terminal++;
234            if (sub_state.marked()) state->closed_with_marked++;
235            state->closed += sub_state.opened;
236        }
237        else { // an open group
238            state->opened++;
239            if (!sub_state.has_groups()) state->opened_terminal++;
240            if (sub_state.marked()) state->opened_with_marked++;
241            state->opened += sub_state.opened;
242        }
243
244        state->marked_in_groups += sub_state.marked();
245
246        state->closed             += sub_state.closed;
247        state->opened_terminal    += sub_state.opened_terminal;
248        state->closed_terminal    += sub_state.closed_terminal;
249        state->opened_with_marked += sub_state.opened_with_marked;
250        state->closed_with_marked += sub_state.closed_with_marked;
251    }
252    else { // not a group
253        if (at->leftson != skip_this_son) detect_group_state(at->get_leftson(), state, skip_this_son);
254        if (at->rightson != skip_this_son) detect_group_state(at->get_rightson(), state, skip_this_son);
255    }
256}
257
258void AWT_graphic_tree::group_rest_tree(AP_tree *at, CollapseMode mode, int color_group) {
259    if (at) {
260        AP_tree *pa = at->get_father();
261        if (pa) {
262            group_tree(at->get_brother(), mode, color_group);
263            group_rest_tree(pa, mode, color_group);
264        }
265    }
266}
267
268bool AWT_graphic_tree::group_tree(AP_tree *at, CollapseMode mode, int color_group) {
269    /*! collapse/expand subtree according to mode and color_group
270     * Run on father! (why?)
271     * @return true if subtree shall expand
272     */
273
274    if (!at) return true;
275
276    GB_transaction ta(tree_static->get_gb_main());
277
278    bool expand_me = false;
279    if (at->is_leaf) {
280        if (mode & EXPAND_ALL) expand_me = true;
281        else if (at->gb_node) {
282            if (mode & EXPAND_MARKED) { // do not group marked
283                if (GB_read_flag(at->gb_node)) expand_me = true;
284            }
285            if (!expand_me && (mode & EXPAND_COLOR)) { // do not group specified color_group
286                int my_color_group = AW_find_color_group(at->gb_node, true);
287
288                expand_me =
289                    my_color_group == color_group || // specific or no color
290                    (my_color_group != 0 && color_group == -1); // any color
291            }
292        }
293        else { // zombie
294            expand_me = mode & EXPAND_ZOMBIES;
295        }
296    }
297    else {
298        expand_me = group_tree(at->get_leftson (), mode, color_group);
299        expand_me = group_tree(at->get_rightson(), mode, color_group) || expand_me;
300
301        at->gr.grouped = 0;
302
303        if (!expand_me) { // no son requests to be expanded
304            if (at->is_named_group()) {
305                at->gr.grouped = 1; // group me
306                if (mode & COLLAPSE_TERMINAL) expand_me = true; // do not group non-terminal groups
307            }
308        }
309        if (!at->father) get_root_node()->compute_tree();
310    }
311    return expand_me;
312}
313
314void AWT_graphic_tree::reorder_tree(TreeOrder mode) {
315    AP_tree *at = get_root_node();
316    if (at) {
317        at->reorder_tree(mode);
318        at->compute_tree();
319    }
320}
321
322static void show_bootstrap_circle(AW_device *device, const char *bootstrap, double zoom_factor, double max_radius, double len, const Position& center, bool elipsoid, double elip_ysize, int filter) { // @@@ directly pass bootstrap value?
323    double radius           = .01 * atoi(bootstrap); // bootstrap values are given in % (0..100)
324    if (radius < .1) radius = .1;
325
326    radius  = 1.0 / sqrt(radius); // -> bootstrap->radius : 100% -> 1, 0% -> inf
327    radius -= 1.0;              // -> bootstrap->radius : 100% -> 0, 0% -> inf
328    radius *= 2; // diameter ?
329
330    // Note : radius goes against infinite, if bootstrap values go against zero
331    //        For this reason we do some limitation here:
332#define BOOTSTRAP_RADIUS_LIMIT max_radius
333
334    int gc = AWT_GC_BOOTSTRAP;
335
336    if (radius > BOOTSTRAP_RADIUS_LIMIT) {
337        radius = BOOTSTRAP_RADIUS_LIMIT;
338        gc     = AWT_GC_BOOTSTRAP_LIMITED;
339    }
340
341    double radiusx = radius * len * zoom_factor;     // multiply with length of branch (and zoomfactor)
342    if (radiusx<0 || nearlyZero(radiusx)) return;    // skip too small circles
343
344    double radiusy;
345    if (elipsoid) {
346        radiusy = elip_ysize * zoom_factor;
347        if (radiusy > radiusx) radiusy = radiusx;
348    }
349    else {
350        radiusy = radiusx;
351    }
352
353    device->circle(gc, false, center, Vector(radiusx, radiusy), filter);
354    // device->arc(gc, false, center, Vector(radiusx, radiusy), 45, -90, filter); // @@@ use to test AW_device_print::arc_impl
355}
356
357static void AWT_graphic_tree_root_changed(void *cd, AP_tree *old, AP_tree *newroot) {
358    AWT_graphic_tree *agt = (AWT_graphic_tree*)cd;
359    if (agt->displayed_root == old || agt->displayed_root->is_inside(old)) {
360        agt->displayed_root = newroot;
361    }
362}
363
364static void AWT_graphic_tree_node_deleted(void *cd, AP_tree *del) {
365    AWT_graphic_tree *agt = (AWT_graphic_tree*)cd;
366    if (agt->displayed_root == del) {
367        agt->displayed_root = agt->get_root_node();
368    }
369    if (agt->get_root_node() == del) {
370        agt->displayed_root = 0;
371    }
372}
373void AWT_graphic_tree::toggle_group(AP_tree * at) {
374    GB_ERROR error = NULL;
375
376    if (at->is_named_group()) { // existing group
377        const char *msg = GBS_global_string("What to do with group '%s'?", at->name);
378
379        switch (aw_question(NULL, msg, "Rename,Destroy,Cancel")) {
380            case 0: { // rename
381                char *new_gname = aw_input("Rename group", "Change group name:", at->name);
382                if (new_gname) {
383                    freeset(at->name, new_gname);
384                    error = GBT_write_string(at->gb_node, "group_name", new_gname);
385                    exports.save = !error;
386                }
387                break;
388            }
389
390            case 1: // destroy
391                at->gr.grouped = 0;
392                at->name       = 0;
393                error          = GB_delete(at->gb_node); // ODD: expecting this to also destroy linewidth, rot and spread - but it doesn't!
394                at->gb_node    = 0;
395                exports.save = !error; // ODD: even when commenting out this line info is not deleted
396                break;
397
398            case 2: // cancel
399                break;
400        }
401    }
402    else {
403        error = create_group(at); // create new group
404        if (!error && at->name) at->gr.grouped = 1;
405    }
406
407    if (error) aw_message(error);
408}
409
410GB_ERROR AWT_graphic_tree::create_group(AP_tree * at) {
411    GB_ERROR error = NULL;
412    if (!at->name) {
413        char *gname = aw_input("Enter Name of Group");
414        if (gname && gname[0]) {
415            GBDATA         *gb_tree  = tree_static->get_gb_tree();
416            GBDATA         *gb_mainT = GB_get_root(gb_tree);
417            GB_transaction  ta(gb_mainT);
418
419            GBDATA *gb_node     = GB_create_container(gb_tree, "node");
420            if (!gb_node) error = GB_await_error();
421
422            if (at->gb_node) {                                     // already have existing node info (e.g. for linewidth)
423                if (!error) error = GB_copy(gb_node, at->gb_node); // copy existing node and ..
424                if (!error) error = GB_delete(at->gb_node);        // .. delete old one (to trigger invalidation of taxonomy-cache)
425            }
426
427            if (!error) error = GBT_write_int(gb_node, "id", 0); // force re-creation of node-id
428
429            if (!error) {
430                GBDATA *gb_name     = GB_search(gb_node, "group_name", GB_STRING);
431                if (!gb_name) error = GB_await_error();
432                else    error       = GBT_write_group_name(gb_name, gname);
433            }
434            exports.save = !error;
435            if (!error) {
436                at->gb_node = gb_node;
437                at->name    = gname;
438            }
439            error = ta.close(error);
440        }
441    }
442    else if (!at->gb_node) {
443        td_assert(0); // please note here if this else-branch is ever reached (and if: how!)
444        at->gb_node = GB_create_container(tree_static->get_gb_tree(), "node");
445
446        if (!at->gb_node) error = GB_await_error();
447        else {
448            error = GBT_write_int(at->gb_node, "id", 0);
449            exports.save = !error;
450        }
451    }
452
453    return error;
454}
455
456class Dragged : public AWT_command_data {
457    /*! Is dragged and can be dropped.
458     * Knows how to indicate dragging.
459     */
460    AWT_graphic_exports& exports;
461
462protected:
463    AWT_graphic_exports& get_exports() { return exports; }
464
465public:
466    enum DragAction { DRAGGING, DROPPED };
467
468    Dragged(AWT_graphic_exports& exports_)
469        : exports(exports_)
470    {}
471
472    static bool valid_drag_device(AW_device *device) { return device->type() == AW_DEVICE_SCREEN; }
473
474    virtual void draw_drag_indicator(AW_device *device, int drag_gc) const = 0;
475    virtual void perform(DragAction action, const AW_clicked_element *target, const Position& mousepos) = 0;
476    virtual void abort() = 0;
477
478    void do_drag(const AW_clicked_element *drag_target, const Position& mousepos) {
479        perform(DRAGGING, drag_target, mousepos);
480    }
481    void do_drop(const AW_clicked_element *drop_target, const Position& mousepos) {
482        perform(DROPPED, drop_target, mousepos);
483    }
484
485    void hide_drag_indicator(AW_device *device, int drag_gc) const {
486#ifdef ARB_MOTIF
487        // hide by XOR-drawing only works in motif
488        draw_drag_indicator(device, drag_gc);
489#else // ARB_GTK
490        exports.refresh = 1;
491#endif
492    }
493};
494
495bool AWT_graphic_tree::warn_inappropriate_mode(AWT_COMMAND_MODE mode) {
496    if (mode == AWT_MODE_ROTATE || mode == AWT_MODE_SPREAD) {
497        if (tree_sort != AP_TREE_RADIAL) {
498            aw_message("Please select the radial tree display mode to use this command");
499            return true;
500        }
501    }
502    return false;
503}
504
505
506void AWT_graphic_tree::handle_key(AW_device *device, AWT_graphic_event& event) {
507    //! handles AW_Event_Type = AW_Keyboard_Press.
508
509    td_assert(event.type() == AW_Keyboard_Press);
510
511    if (event.key_code() == AW_KEY_NONE) return;
512    if (event.key_code() == AW_KEY_ASCII && event.key_char() == 0) return;
513
514#if defined(DEBUG)
515    printf("key_char=%i (=%c)\n", int(event.key_char()), event.key_char());
516#endif // DEBUG
517
518    // ------------------------
519    //      drag&drop keys
520
521    if (event.key_code() == AW_KEY_ESCAPE) {
522        AWT_command_data *cmddata = get_command_data();
523        if (cmddata) {
524            Dragged *dragging = dynamic_cast<Dragged*>(cmddata);
525            if (dragging) {
526                dragging->hide_drag_indicator(device, drag_gc);
527                dragging->abort(); // abort what ever we did
528                store_command_data(NULL);
529            }
530        }
531    }
532
533    // ----------------------------------------
534    //      commands independent of tree :
535
536    bool handled = true;
537    switch (event.key_char()) {
538        case 9: {     // Ctrl-i = invert all
539            GBT_mark_all(gb_main, 2);
540            exports.structure_change = 1;
541            break;
542        }
543        case 13: {     // Ctrl-m = mark/unmark all
544            int mark   = GBT_first_marked_species(gb_main) == 0; // no species marked -> mark all
545            GBT_mark_all(gb_main, mark);
546            exports.structure_change = 1;
547            break;
548        }
549        default: {
550            handled = false;
551            break;
552        }
553    }
554
555    if (!handled) {
556        // handle key events specific to pointed-to tree-element
557        ClickedTarget pointed(this, event.best_click());
558
559        if (pointed.species()) {
560            handled = true;
561            switch (event.key_char()) {
562                case 'i':
563                case 'm': {     // i/m = mark/unmark species
564                    GB_write_flag(pointed.species(), 1-GB_read_flag(pointed.species()));
565                    exports.structure_change = 1;
566                    break;
567                }
568                case 'I': {     // I = invert all but current
569                    int mark = GB_read_flag(pointed.species());
570                    GBT_mark_all(gb_main, 2);
571                    GB_write_flag(pointed.species(), mark);
572                    exports.structure_change = 1;
573                    break;
574                }
575                case 'M': {     // M = mark/unmark all but current
576                    int mark = GB_read_flag(pointed.species());
577                    GB_write_flag(pointed.species(), 0); // unmark current
578                    GBT_mark_all(gb_main, GBT_first_marked_species(gb_main) == 0);
579                    GB_write_flag(pointed.species(), mark); // restore mark of current
580                    exports.structure_change = 1;
581                    break;
582                }
583                default: handled = false; break;
584            }
585        }
586
587        if (!handled && event.key_char() == '0') {
588            // handle reset-key promised by
589            // - KEYINFO_ABORT_AND_RESET (AWT_MODE_ROTATE, AWT_MODE_LENGTH, AWT_MODE_MULTIFURC, AWT_MODE_LINE, AWT_MODE_SPREAD)
590            // - KEYINFO_RESET (AWT_MODE_LZOOM)
591
592            if (event.cmd() == AWT_MODE_LZOOM) {
593                displayed_root     = displayed_root->get_root_node();
594                exports.zoom_reset = 1;
595            }
596            else if (pointed.is_ruler()) {
597                GBDATA *gb_tree = tree_static->get_gb_tree();
598                td_assert(gb_tree);
599
600                switch (event.cmd()) {
601                    case AWT_MODE_ROTATE: break; // ruler has no rotation
602                    case AWT_MODE_SPREAD: break; // ruler has no spread
603                    case AWT_MODE_LENGTH: {
604                        GB_transaction ta(gb_tree);
605                        GBDATA *gb_ruler_size = GB_searchOrCreate_float(gb_tree, RULER_SIZE, DEFAULT_RULER_LENGTH);
606                        GB_write_float(gb_ruler_size, DEFAULT_RULER_LENGTH);
607                        exports.structure_change = 1;
608                        break;
609                    }
610                    case AWT_MODE_LINE: {
611                        GB_transaction ta(gb_tree);
612                        GBDATA *gb_ruler_width = GB_searchOrCreate_int(gb_tree, RULER_LINEWIDTH, DEFAULT_RULER_LINEWIDTH);
613                        GB_write_int(gb_ruler_width, DEFAULT_RULER_LINEWIDTH);
614                        exports.structure_change = 1;
615                        break;
616                    }
617                    default: break;
618                }
619            }
620            else if (pointed.node()) {
621                if (warn_inappropriate_mode(event.cmd())) return;
622                switch (event.cmd()) {
623                    case AWT_MODE_ROTATE:
624                        pointed.node()->reset_subtree_angles();
625                        exports.save = 1;
626                        break;
627
628                    case AWT_MODE_LENGTH:
629                        pointed.node()->set_branchlength_unrooted(0.0);
630                        exports.save = 1;
631                        break;
632
633                    case AWT_MODE_MULTIFURC:
634                        pointed.node()->multifurcate();
635                        exports.save = 1;
636                        break;
637
638                    case AWT_MODE_LINE:
639                        pointed.node()->reset_subtree_linewidths();
640                        exports.save = 1;
641                        break;
642
643                    case AWT_MODE_SPREAD:
644                        pointed.node()->reset_subtree_spreads();
645                        exports.save = 1;
646                        break;
647
648                    default: break;
649                }
650            }
651            return;
652        }
653
654        if (pointed.node()) {
655            switch (event.key_char()) {
656                case 'm': {     // m = mark/unmark (sub)tree
657                    mark_species_in_tree(pointed.node(), !tree_has_marks(pointed.node()));
658                    exports.structure_change = 1;
659                    break;
660                }
661                case 'M': {     // M = mark/unmark all but (sub)tree
662                    mark_species_in_rest_of_tree(pointed.node(), !rest_tree_has_marks(pointed.node()));
663                    exports.structure_change = 1;
664                    break;
665                }
666
667                case 'i': {     // i = invert (sub)tree
668                    mark_species_in_tree(pointed.node(), 2);
669                    exports.structure_change = 1;
670                    break;
671                }
672                case 'I': {     // I = invert all but (sub)tree
673                    mark_species_in_rest_of_tree(pointed.node(), 2);
674                    exports.structure_change = 1;
675                    break;
676                }
677                case 'c':
678                case 'x': {
679                    AWT_graphic_tree_group_state  state;
680                    AP_tree                      *at = pointed.node();
681
682                    detect_group_state(at, &state, 0);
683
684                    if (!state.has_groups()) { // somewhere inside group
685                      do_parent :
686                        at  = at->get_father();
687                        while (at) {
688                            if (at->is_named_group()) break;
689                            at = at->get_father();
690                        }
691
692                        if (at) {
693                            state.clear();
694                            detect_group_state(at, &state, 0);
695                        }
696                    }
697
698                    if (at) {
699                        CollapseMode next_group_mode;
700
701                        if (event.key_char() == 'x') {  // expand
702                            next_group_mode = state.next_expand_mode();
703                        }
704                        else { // collapse
705                            if (state.all_closed()) goto do_parent;
706                            next_group_mode = state.next_collapse_mode();
707                        }
708
709                        group_tree(at, next_group_mode, 0);
710                        exports.save    = true;
711                    }
712                    break;
713                }
714                case 'C':
715                case 'X': {
716                    AP_tree *root_node = pointed.node();
717                    while (root_node->father) root_node = root_node->get_father(); // search father
718
719                    td_assert(root_node);
720
721                    AWT_graphic_tree_group_state state;
722                    detect_group_state(root_node, &state, pointed.node());
723
724                    CollapseMode next_group_mode;
725                    if (event.key_char() == 'X') {  // expand
726                        next_group_mode = state.next_expand_mode();
727                    }
728                    else { // collapse
729                        next_group_mode = state.next_collapse_mode();
730                    }
731
732                    group_rest_tree(pointed.node(), next_group_mode, 0);
733                    exports.save = true;
734
735                    break;
736                }
737            }
738        }
739    }
740}
741
742static bool command_on_GBDATA(GBDATA *gbd, const AWT_graphic_event& event, AD_map_viewer_cb map_viewer_cb) {
743    // modes listed here are available in ALL tree-display-modes (i.e. as well in listmode)
744
745    bool refresh = false;
746
747    if (event.type() == AW_Mouse_Press && event.button() != AW_BUTTON_MIDDLE) {
748        AD_MAP_VIEWER_TYPE selectType = ADMVT_NONE;
749
750        switch (event.cmd()) {
751            case AWT_MODE_MARK: // see also .@OTHER_MODE_MARK_HANDLER
752                switch (event.button()) {
753                    case AW_BUTTON_LEFT:
754                        GB_write_flag(gbd, 1);
755                        selectType = ADMVT_SELECT;
756                        break;
757                    case AW_BUTTON_RIGHT:
758                        GB_write_flag(gbd, 0);
759                        break;
760                    default:
761                        break;
762                }
763                refresh = true;
764                break;
765
766            case AWT_MODE_WWW:  selectType = ADMVT_WWW;    break;
767            case AWT_MODE_INFO: selectType = ADMVT_INFO;   break;
768            default:            selectType = ADMVT_SELECT; break;
769        }
770
771        if (selectType != ADMVT_NONE) {
772            map_viewer_cb(gbd, selectType);
773            refresh = true;
774        }
775    }
776
777    return refresh;
778}
779
780class LineOrText {
781    /*! Stores a copy of AW_clicked_line or AW_clicked_text.
782     * Used as Drag&Drop source and target.
783     */
784    AW_clicked_line line;
785    AW_clicked_text text;
786    const AW_clicked_element *elem;
787
788public:
789    LineOrText(const AW_clicked_element& e) { set(e); }
790    LineOrText(const LineOrText& other) { set(*other.element()); }
791    DECLARE_ASSIGNMENT_OPERATOR(LineOrText);
792
793    void set(const AW_clicked_line& l) { line = l; elem = &line; }
794    void set(const AW_clicked_text& t) { text = t; elem = &text; }
795    void set(const AW_clicked_element& e) {
796        if (e.is_text()) set(dynamic_cast<const AW_clicked_text&>(e));
797        else set(dynamic_cast<const AW_clicked_line&>(e));
798    }
799    const AW_clicked_element *element() const { return elem; }
800
801    bool operator == (const LineOrText& other) const {
802        return
803            element()->is_text() == other.element()->is_text() &&
804            line                 == other.line                 &&
805            text                 == other.text;
806    }
807    bool operator != (const LineOrText& other) const { return !(*this == other); }
808};
809
810class DragNDrop : public Dragged {
811    LineOrText Drag, Drop;
812
813    virtual void perform_drop() = 0;
814
815    void drag(const AW_clicked_element *drag_target)  {
816        Drop = drag_target ? *drag_target : Drag;
817    }
818    void drop(const AW_clicked_element *drop_target) {
819        drag(drop_target);
820        perform_drop();
821    }
822
823    void perform(DragAction action, const AW_clicked_element *target, const Position&) OVERRIDE {
824        switch (action) {
825            case DRAGGING: drag(target); break;
826            case DROPPED:  drop(target); break;
827        }
828    }
829
830    void abort() OVERRIDE {
831        perform(DROPPED, Drag.element(), Position()); // drop dragged element onto itself to abort
832    }
833
834protected:
835    const AW_clicked_element *source_element() const { return Drag.element(); }
836    const AW_clicked_element *dest_element() const { return Drop.element(); }
837
838public:
839    DragNDrop(const AW_clicked_element *dragFrom, AWT_graphic_exports& exports_)
840        : Dragged(exports_),
841          Drag(*dragFrom), Drop(Drag)
842    {}
843
844    void draw_drag_indicator(AW_device *device, int drag_gc) const {
845        td_assert(valid_drag_device(device));
846        source_element()->indicate_selected(device, drag_gc);
847        if (Drag != Drop) dest_element()->indicate_selected(device, drag_gc);
848        device->line(drag_gc, source_element()->get_connecting_line(*dest_element()));
849    }
850};
851
852class BranchMover : public DragNDrop {
853    AW_MouseButton button;
854
855    void perform_drop() OVERRIDE {
856        ClickedTarget source(source_element());
857        ClickedTarget dest(dest_element());
858
859        if (source.node() && dest.node() && source.node() != dest.node()) {
860            GB_ERROR error = NULL;
861            switch (button) {
862                case AW_BUTTON_LEFT:
863                    error = source.node()->cantMoveNextTo(dest.node());
864                    if (!error) source.node()->moveNextTo(dest.node(), dest.get_rel_attach());
865                    break;
866
867                case AW_BUTTON_RIGHT:
868                    error = source.node()->move_group_info(dest.node());
869                    break;
870                default:
871                    td_assert(0);
872                    break;
873            }
874
875            if (error) {
876                aw_message(error);
877            }
878            else {
879                get_exports().save = 1;
880            }
881        }
882        else {
883#if defined(DEBUG)
884            if (!source.node()) printf("No source.node\n");
885            if (!dest.node()) printf("No dest.node\n");
886            if (dest.node() == source.node()) printf("source==dest\n");
887#endif
888        }
889    }
890
891public:
892    BranchMover(const AW_clicked_element *dragFrom, AW_MouseButton button_, AWT_graphic_exports& exports_)
893        : DragNDrop(dragFrom, exports_),
894          button(button_)
895    {}
896};
897
898
899class Scaler : public Dragged {
900    Position mouse_start; // screen-coordinates
901    Position last_drag_pos;
902    double unscale;
903
904    virtual void draw_scale_indicator(const AW::Position& drag_pos, AW_device *device, int drag_gc) const = 0;
905    virtual void do_scale(const Position& drag_pos) = 0;
906
907    void perform(DragAction action, const AW_clicked_element *, const Position& mousepos) OVERRIDE {
908        switch (action) {
909            case DRAGGING:
910                last_drag_pos = mousepos;
911                // fall-through (aka instantly apply drop-action while dragging)
912            case DROPPED:
913                do_scale(mousepos);
914                break;
915        }
916    }
917    void abort() OVERRIDE {
918        perform(DROPPED, NULL, mouse_start); // drop exactly where dragging started
919    }
920
921
922protected:
923    const Position& startpos() const { return mouse_start; }
924    Vector scaling(const Position& current) const { return Vector(mouse_start, current)*unscale; } // returns world-coordinates
925
926public:
927    Scaler(const Position& start, double unscale_, AWT_graphic_exports& exports_)
928        : Dragged(exports_),
929          mouse_start(start),
930          last_drag_pos(start),
931          unscale(unscale_)
932    {
933        td_assert(!is_nan_or_inf(unscale));
934    }
935
936    void draw_drag_indicator(AW_device *device, int drag_gc) const { draw_scale_indicator(last_drag_pos, device, drag_gc); }
937};
938
939inline double discrete_value(double analog_value, int discretion_factor) {
940    // discretion_factor:
941    //     10 -> 1st digit behind dot
942    //    100 -> 2nd ------- " ------
943    //   1000 -> 3rd ------- " ------
944
945    if (analog_value<0.0) return -discrete_value(-analog_value, discretion_factor);
946    return int(analog_value*discretion_factor+0.5)/double(discretion_factor);
947}
948
949class DB_scalable {
950    //! a DB entry scalable by dragging
951    GBDATA   *gbd;
952    GB_TYPES  type;
953    double    min;
954    double    max;
955    int       discretion_factor;
956    bool      inversed;
957
958    static CONSTEXPR double INTSCALE = 100.0;
959
960    void init() {
961        min = -DBL_MAX;
962        max =  DBL_MAX;
963
964        discretion_factor = 0;
965        inversed          = false;
966    }
967
968public:
969    DB_scalable() : gbd(NULL), type(GB_NONE) { init(); }
970    DB_scalable(GBDATA *gbd_) : gbd(gbd_), type(GB_read_type(gbd)) { init(); }
971
972    GBDATA *data() { return gbd; }
973
974    double read() {
975        double res = 0.0;
976        switch (type) {
977            case GB_FLOAT: res = GB_read_float(gbd);        break;
978            case GB_INT:   res = GB_read_int(gbd)/INTSCALE; break;
979            default: break;
980        }
981        return inversed ? -res : res;
982    }
983    bool write(double val) {
984        double old = read();
985
986        if (inversed) val = -val;
987
988        val = val<=min ? min : (val>=max ? max : val);
989        val = discretion_factor ? discrete_value(val, discretion_factor) : val;
990
991        switch (type) {
992            case GB_FLOAT:
993                GB_write_float(gbd, val);
994                break;
995            case GB_INT:
996                GB_write_int(gbd, int(val*INTSCALE+0.5));
997                break;
998            default: break;
999        }
1000
1001        return old != read();
1002    }
1003
1004    void set_discretion_factor(int df) { discretion_factor = df; }
1005    void set_min(double val) { min = (type == GB_INT) ? val*INTSCALE : val; }
1006    void set_max(double val) { max = (type == GB_INT) ? val*INTSCALE : val; }
1007    void inverse() { inversed = !inversed; }
1008};
1009
1010class RulerScaler : public Scaler { // derived from Noncopyable
1011    Position    awar_start;
1012    DB_scalable x, y; // DB entries scaled by x/y movement
1013
1014    GBDATA *gbdata() {
1015        GBDATA *gbd   = x.data();
1016        if (!gbd) gbd = y.data();
1017        td_assert(gbd);
1018        return gbd;
1019    }
1020
1021    Position read_pos() { return Position(x.read(), y.read()); }
1022    bool write_pos(Position p) {
1023        bool xchanged = x.write(p.xpos());
1024        bool ychanged = y.write(p.ypos());
1025        return xchanged || ychanged;
1026    }
1027
1028    void draw_scale_indicator(const AW::Position& , AW_device *, int ) const {}
1029    void do_scale(const Position& drag_pos) {
1030        GB_transaction ta(gbdata());
1031        if (write_pos(awar_start+scaling(drag_pos))) get_exports().refresh = 1;
1032    }
1033public:
1034    RulerScaler(const Position& start, double unscale_, const DB_scalable& xs, const DB_scalable& ys, AWT_graphic_exports& exports_)
1035        : Scaler(start, unscale_, exports_),
1036          x(xs),
1037          y(ys)
1038    {
1039        GB_transaction ta(gbdata());
1040        awar_start = read_pos();
1041    }
1042};
1043
1044static void text_near_head(AW_device *device, int gc, const LineVector& line, const char *text) {
1045    // @@@ should keep a little distance between the line-head and the text (depending on line orientation)
1046    Position at = line.head();
1047    device->text(gc, text, at);
1048}
1049
1050enum ScaleMode { SCALE_LENGTH, SCALE_LENGTH_PRESERVING, SCALE_SPREAD };
1051
1052class BranchScaler : public Scaler { // derived from Noncopyable
1053    ScaleMode  mode;
1054    AP_tree   *node;
1055
1056    float start_val;   // length or spread
1057    bool  zero_val_removed;
1058
1059    LineVector branch;
1060    Position   attach; // point on 'branch' (next to click position)
1061
1062    bool discrete; // @@@ replace me by (discretion_factor == 0);
1063    int  discretion_factor;
1064
1065    bool allow_neg_val;
1066
1067    float get_val() const {
1068        switch (mode) {
1069            case SCALE_LENGTH_PRESERVING:
1070            case SCALE_LENGTH: return node->get_branchlength_unrooted();
1071            case SCALE_SPREAD: return node->gr.spread;
1072        }
1073        td_assert(0);
1074        return 0.0;
1075    }
1076    void set_val(float val) {
1077        switch (mode) {
1078            case SCALE_LENGTH_PRESERVING: node->set_branchlength_preserving(val); break;
1079            case SCALE_LENGTH: node->set_branchlength_unrooted(val); break;
1080            case SCALE_SPREAD: node->gr.spread = val; break;
1081        }
1082    }
1083
1084    void init_discretion_factor() {
1085        if (discrete) {
1086            discretion_factor = 10;
1087            if (start_val != 0) {
1088                while ((start_val*discretion_factor)<1) {
1089                    discretion_factor *= 10;
1090                }
1091            }
1092        }
1093    }
1094
1095    Position get_dragged_attach(const AW::Position& drag_pos) const {
1096        // return dragged position of 'attach'
1097        Vector moved      = scaling(drag_pos);
1098        Vector attach2tip = branch.head()-attach;
1099
1100        if (attach2tip.length()>0) {
1101            Vector   moveOnBranch = orthogonal_projection(moved, attach2tip);
1102            return attach+moveOnBranch;
1103        }
1104        return Position(); // no position
1105    }
1106
1107
1108    void draw_scale_indicator(const AW::Position& drag_pos, AW_device *device, int drag_gc) const {
1109        td_assert(valid_drag_device(device));
1110        Position attach_dragged = get_dragged_attach(drag_pos);
1111        if (attach_dragged.valid()) {
1112            Position   drag_world = device->rtransform(drag_pos);
1113            LineVector to_dragged(attach_dragged, drag_world);
1114            LineVector to_start(attach, -to_dragged.line_vector());
1115
1116            device->set_line_attributes(drag_gc, 1, AW_SOLID);
1117
1118            device->line(drag_gc, to_start);
1119            device->line(drag_gc, to_dragged);
1120
1121            text_near_head(device, drag_gc, to_start,   GBS_global_string("old=%.3f", start_val));
1122            text_near_head(device, drag_gc, to_dragged, GBS_global_string("new=%.3f", get_val()));
1123        }
1124
1125        device->set_line_attributes(drag_gc, 3, AW_SOLID);
1126        device->line(drag_gc, branch);
1127    }
1128
1129    void do_scale(const Position& drag_pos) {
1130        double oldval = get_val();
1131
1132        if (start_val == 0.0) { // can't scale
1133            if (!zero_val_removed) {
1134                switch (mode) {
1135                    case SCALE_LENGTH:
1136                    case SCALE_LENGTH_PRESERVING:
1137                        set_val(tree_defaults::LENGTH); // fake branchlength (can't scale zero-length branches)
1138                        aw_message("Cannot scale zero sized branches\nBranchlength has been set to 0.1\nNow you may scale the branch");
1139                        break;
1140                    case SCALE_SPREAD:
1141                        set_val(tree_defaults::SPREAD); // reset spread (can't scale unspreaded branches)
1142                        aw_message("Cannot spread unspreaded branches\nSpreading has been set to 1.0\nNow you may spread the branch"); // @@@ clumsy
1143                        break;
1144                }
1145                zero_val_removed = true;
1146            }
1147        }
1148        else {
1149            Position attach_dragged = get_dragged_attach(drag_pos);
1150            if (attach_dragged.valid()) {
1151                Vector to_attach(branch.start(), attach);
1152                Vector to_attach_dragged(branch.start(), attach_dragged);
1153
1154                double tal = to_attach.length();
1155                double tdl = to_attach_dragged.length();
1156
1157                if (tdl>0.0 && tal>0.0) {
1158                    bool   negate = are_antiparallel(to_attach, to_attach_dragged);
1159                    double scale  = tdl/tal * (negate ? -1 : 1);
1160
1161                    float val = start_val * scale;
1162                    if (val<0.0) {
1163                        if (node->is_leaf || !allow_neg_val) {
1164                            val = 0.0; // do NOT accept negative values
1165                        }
1166                    }
1167                    if (discrete) {
1168                        val = discrete_value(val, discretion_factor);
1169                    }
1170                    set_val(NONAN(val));
1171                }
1172            }
1173        }
1174
1175        if (oldval != get_val()) {
1176            get_exports().save = 1;
1177        }
1178    }
1179
1180public:
1181
1182    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_)
1183        : Scaler(start, unscale_, exports_),
1184          mode(mode_),
1185          node(node_),
1186          start_val(get_val()),
1187          zero_val_removed(false),
1188          branch(branch_),
1189          attach(attach_),
1190          discrete(discrete_),
1191          allow_neg_val(allow_neg_values_)
1192    {
1193        init_discretion_factor();
1194    }
1195};
1196
1197class BranchLinewidthScaler : public Scaler, virtual Noncopyable {
1198    AP_tree *node;
1199    int      start_width;
1200    bool     wholeSubtree;
1201
1202public:
1203    BranchLinewidthScaler(AP_tree *node_, const Position& start, bool wholeSubtree_, AWT_graphic_exports& exports_)
1204        : Scaler(start, 0.1, exports_), // 0.1 = > change linewidth dragpixel/10
1205          node(node_),
1206          start_width(node->get_linewidth()),
1207          wholeSubtree(wholeSubtree_)
1208    {}
1209
1210    void draw_scale_indicator(const AW::Position& , AW_device *, int ) const OVERRIDE {}
1211    void do_scale(const Position& drag_pos) OVERRIDE {
1212        Vector moved = scaling(drag_pos);
1213        double ymove = -moved.y();
1214        int    old   = node->get_linewidth();
1215
1216        int width = start_width + ymove;
1217        if (width<tree_defaults::LINEWIDTH) width = tree_defaults::LINEWIDTH;
1218
1219        if (width != old) {
1220            if (wholeSubtree) {
1221                node->set_linewidth_recursive(width);
1222            }
1223            else {
1224                node->set_linewidth(width);
1225            }
1226            get_exports().save = 1;
1227        }
1228    }
1229};
1230
1231class BranchRotator : public Dragged, virtual Noncopyable {
1232    AW_device  *device;
1233    AP_tree    *node;
1234    LineVector  clicked_branch;
1235    float       orig_angle;      // of node
1236    Position    hinge;
1237    Position    mousepos_world;
1238
1239    void perform(DragAction, const AW_clicked_element *, const Position& mousepos) OVERRIDE {
1240        mousepos_world = device->rtransform(mousepos);
1241
1242        double prev_angle = node->get_angle();
1243
1244        Angle current(hinge, mousepos_world);
1245        Angle orig(clicked_branch.line_vector());
1246        Angle diff = current-orig;
1247
1248        node->set_angle(orig_angle + diff.radian());
1249
1250        if (node->get_angle() != prev_angle) get_exports().save = 1;
1251    }
1252
1253    void abort() OVERRIDE {
1254        node->set_angle(orig_angle);
1255        get_exports().save    = 1;
1256        get_exports().refresh = 1;
1257    }
1258
1259public:
1260    BranchRotator(AW_device *device_, AP_tree *node_, const LineVector& clicked_branch_, const Position& mousepos, AWT_graphic_exports& exports_)
1261        : Dragged(exports_),
1262          device(device_),
1263          node(node_),
1264          clicked_branch(clicked_branch_),
1265          orig_angle(node->get_angle()),
1266          hinge(clicked_branch.start()),
1267          mousepos_world(device->rtransform(mousepos))
1268    {
1269        td_assert(valid_drag_device(device));
1270    }
1271
1272    void draw_drag_indicator(AW_device *IF_DEBUG(same_device), int drag_gc) const OVERRIDE {
1273        td_assert(valid_drag_device(same_device));
1274        td_assert(device == same_device);
1275
1276        device->line(drag_gc, clicked_branch);
1277        device->line(drag_gc, LineVector(hinge, mousepos_world));
1278        device->circle(drag_gc, false, hinge, device->rtransform(Vector(5, 5)));
1279    }
1280};
1281
1282static AWT_graphic_event::ClickPreference preferredForCommand(AWT_COMMAND_MODE mode) {
1283    // return preferred click target for tree-display
1284    // (Note: not made this function a member of AWT_graphic_event,
1285    //  since modes are still reused in other ARB applications,
1286    //  e.g. AWT_MODE_ROTATE in SECEDIT)
1287
1288    switch (mode) {
1289        case AWT_MODE_ROTATE:
1290        case AWT_MODE_LENGTH:
1291        case AWT_MODE_MULTIFURC:
1292        case AWT_MODE_SPREAD:
1293            return AWT_graphic_event::PREFER_LINE;
1294
1295        default:
1296            return AWT_graphic_event::PREFER_NEARER;
1297    }
1298}
1299
1300void AWT_graphic_tree::handle_command(AW_device *device, AWT_graphic_event& event) {
1301    td_assert(event.button()!=AW_BUTTON_MIDDLE); // shall be handled by caller
1302
1303    if (!tree_static) return;                      // no tree -> no commands
1304
1305    if (event.type() == AW_Keyboard_Release) return;
1306    if (event.type() == AW_Keyboard_Press) return handle_key(device, event);
1307
1308    // @@@ move code below into separate member function handle_mouse()
1309    if (event.button() == AW_BUTTON_NONE) return;
1310    td_assert(event.button() == AW_BUTTON_LEFT || event.button() == AW_BUTTON_RIGHT); // nothing else should come here
1311
1312    ClickedTarget clicked(this, event.best_click(preferredForCommand(event.cmd())));
1313    if (clicked.species()) {
1314        if (command_on_GBDATA(clicked.species(), event, map_viewer_cb)) {
1315            exports.refresh = 1;
1316        }
1317        return;
1318    }
1319
1320    if (!tree_static->get_root_node()) return; // no tree -> no commands
1321   
1322    GBDATA          *gb_tree  = tree_static->get_gb_tree();
1323    const Position&  mousepos = event.position();
1324
1325    // -------------------------------------
1326    //      generic drag & drop handler
1327    {
1328        AWT_command_data *cmddata = get_command_data();
1329        if (cmddata) {
1330            Dragged *dragging = dynamic_cast<Dragged*>(cmddata);
1331            if (dragging) {
1332                dragging->hide_drag_indicator(device, drag_gc);
1333                if (event.type() == AW_Mouse_Press) {
1334                    // mouse pressed while dragging (e.g. press other button)
1335                    dragging->abort(); // abort what ever we did
1336                    store_command_data(NULL);
1337                }
1338                else {
1339                    switch (event.type()) {
1340                        case AW_Mouse_Drag:
1341                            dragging->do_drag(clicked.element(), mousepos);
1342                            dragging->draw_drag_indicator(device, drag_gc);
1343                            break;
1344
1345                        case AW_Mouse_Release:
1346                            dragging->do_drop(clicked.element(), mousepos);
1347                            store_command_data(NULL);
1348                            break;
1349                        default:
1350                            break;
1351                    }
1352                }
1353                return;
1354            }
1355        }
1356    }
1357
1358    if (event.type() == AW_Mouse_Press && clicked.is_ruler()) {
1359        DB_scalable xdata;
1360        DB_scalable ydata;
1361        double      unscale = device->get_unscale();
1362
1363        switch (event.cmd()) {
1364            case AWT_MODE_LENGTH:
1365            case AWT_MODE_MULTIFURC: { // scale ruler
1366                xdata = GB_searchOrCreate_float(gb_tree, RULER_SIZE, DEFAULT_RULER_LENGTH);
1367
1368                double rel  = clicked.get_rel_attach();
1369                if (tree_sort == AP_TREE_IRS) {
1370                    unscale /= (rel-1)*irs_tree_ruler_scale_factor; // ruler has opposite orientation in IRS mode
1371                }
1372                else {
1373                    unscale /= rel;
1374                }
1375
1376                if (event.button() == AW_BUTTON_RIGHT) xdata.set_discretion_factor(10);
1377                xdata.set_min(0.01);
1378                break;
1379            }
1380            case AWT_MODE_LINE: // scale ruler linewidth
1381                ydata = GB_searchOrCreate_int(gb_tree, RULER_LINEWIDTH, DEFAULT_RULER_LINEWIDTH);
1382                ydata.set_min(0);
1383                ydata.inverse();
1384                break;
1385
1386            default: { // move ruler or ruler text
1387                bool isText = clicked.is_text();
1388                xdata = GB_searchOrCreate_float(gb_tree, ruler_awar(isText ? "text_x" : "ruler_x"), 0.0);
1389                ydata = GB_searchOrCreate_float(gb_tree, ruler_awar(isText ? "text_y" : "ruler_y"), 0.0);
1390                break;
1391            }
1392        }
1393        store_command_data(new RulerScaler(mousepos, unscale, xdata, ydata, exports));
1394        return;
1395    }
1396
1397    if (event.type() == AW_Mouse_Press && warn_inappropriate_mode(event.cmd())) return;
1398
1399    switch (event.cmd()) {
1400        // -----------------------------
1401        //      two point commands:
1402
1403        case AWT_MODE_MOVE:
1404            if (event.type() == AW_Mouse_Press && clicked.node() && clicked.node()->father) {
1405                BranchMover *mover = new BranchMover(clicked.element(), event.button(), exports);
1406                store_command_data(mover);
1407                mover->draw_drag_indicator(device, drag_gc);
1408            }
1409            break;
1410
1411        case AWT_MODE_LENGTH:
1412        case AWT_MODE_MULTIFURC:
1413            if (event.type() == AW_Mouse_Press && clicked.node() && clicked.is_branch()) {
1414                bool allow_neg_branches = aw_root->awar(AWAR_EXPERT)->read_int();
1415                bool discrete_lengths   = event.button() == AW_BUTTON_RIGHT;
1416
1417                const AW_clicked_line *cl = dynamic_cast<const AW_clicked_line*>(clicked.element());
1418                td_assert(cl);
1419
1420                ScaleMode     mode   = event.cmd() == AWT_MODE_LENGTH ? SCALE_LENGTH : SCALE_LENGTH_PRESERVING;
1421                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);
1422
1423                store_command_data(scaler);
1424                scaler->draw_drag_indicator(device, drag_gc);
1425            }
1426            break;
1427
1428        case AWT_MODE_ROTATE:
1429            if (event.type() == AW_Mouse_Press && clicked.node() && clicked.is_branch()) {
1430                const AW_clicked_line *cl = dynamic_cast<const AW_clicked_line*>(clicked.element());
1431                td_assert(cl);
1432                BranchRotator *rotator = new BranchRotator(device, clicked.node(), cl->get_line(), mousepos, exports);
1433                store_command_data(rotator);
1434                rotator->draw_drag_indicator(device, drag_gc);
1435            }
1436            break;
1437
1438        case AWT_MODE_LINE:
1439            if (event.type()==AW_Mouse_Press && clicked.node()) {
1440                BranchLinewidthScaler *widthScaler = new BranchLinewidthScaler(clicked.node(), mousepos, event.button() == AW_BUTTON_RIGHT, exports);
1441                store_command_data(widthScaler);
1442                widthScaler->draw_drag_indicator(device, drag_gc);
1443            }
1444            break;
1445
1446        case AWT_MODE_SPREAD:
1447            if (event.type() == AW_Mouse_Press && clicked.node() && clicked.is_branch()) {
1448                const AW_clicked_line *cl = dynamic_cast<const AW_clicked_line*>(clicked.element());
1449                td_assert(cl);
1450                BranchScaler *spreader = new BranchScaler(SCALE_SPREAD, clicked.node(), cl->get_line(), clicked.element()->get_attach_point(), mousepos, device->get_unscale(), false, false, exports);
1451                store_command_data(spreader);
1452                spreader->draw_drag_indicator(device, drag_gc);
1453            }
1454            break;
1455
1456        // -----------------------------
1457        //      one point commands:
1458
1459        case AWT_MODE_LZOOM:
1460            if (event.type()==AW_Mouse_Press) {
1461                switch (event.button()) {
1462                    case AW_BUTTON_LEFT:
1463                        if (clicked.node()) {
1464                            displayed_root     = clicked.node();
1465                            exports.zoom_reset = 1;
1466                        }
1467                        break;
1468                    case AW_BUTTON_RIGHT:
1469                        if (displayed_root->father) {
1470                            displayed_root     = displayed_root->get_father();
1471                            exports.zoom_reset = 1;
1472                        }
1473                        break;
1474
1475                    default: td_assert(0); break;
1476                }
1477            }
1478            break;
1479
1480    act_like_group :
1481        case AWT_MODE_GROUP:
1482            if (event.type()==AW_Mouse_Press && clicked.node()) {
1483                switch (event.button()) {
1484                    case AW_BUTTON_LEFT: {
1485                        AP_tree *at = clicked.node();
1486                        if ((!at->gr.grouped) && (!at->name)) break; // not grouped and no name
1487                        if (at->is_leaf) break; // don't touch zombies
1488
1489                        if (gb_tree) {
1490                            GB_ERROR error = this->create_group(at);
1491                            if (error) aw_message(error);
1492                        }
1493                        if (at->name) {
1494                            at->gr.grouped  ^= 1;
1495                            exports.save     = 1;
1496                        }
1497                        break;
1498                    }
1499                    case AW_BUTTON_RIGHT:
1500                        if (gb_tree) {
1501                            toggle_group(clicked.node());
1502                        }
1503                        break;
1504                    default: td_assert(0); break;
1505                }
1506            }
1507            break;
1508
1509        case AWT_MODE_SETROOT:
1510            if (event.type() == AW_Mouse_Press) {
1511                switch (event.button()) {
1512                    case AW_BUTTON_LEFT:
1513                        if (clicked.node()) clicked.node()->set_root();
1514                        break;
1515                    case AW_BUTTON_RIGHT:
1516                        tree_static->find_innermost_edge().set_root();
1517                        break;
1518                    default: td_assert(0); break;
1519                }
1520                exports.save       = 1;
1521                exports.zoom_reset = 1;
1522            } 
1523            break;
1524
1525        case AWT_MODE_SWAP:
1526            if (event.type()==AW_Mouse_Press && clicked.node()) {
1527                switch (event.button()) {
1528                    case AW_BUTTON_LEFT:  clicked.node()->swap_sons(); break;
1529                    case AW_BUTTON_RIGHT: clicked.node()->rotate_subtree();     break;
1530                    default: td_assert(0); break;
1531                }
1532                exports.save = 1;
1533            }
1534            break;
1535
1536        case AWT_MODE_MARK: // see also .@OTHER_MODE_MARK_HANDLER
1537            if (event.type() == AW_Mouse_Press && clicked.node()) {
1538                GB_transaction ta(tree_static->get_gb_main());
1539
1540                if (event.type() == AW_Mouse_Press) {
1541                    switch (event.button()) {
1542                        case AW_BUTTON_LEFT:  mark_species_in_tree(clicked.node(), 1); break;
1543                        case AW_BUTTON_RIGHT: mark_species_in_tree(clicked.node(), 0); break;
1544                        default: td_assert(0); break;
1545                    }
1546                }
1547                exports.refresh = 1;
1548                tree_static->update_timers(); // do not reload the tree
1549                update_structure();
1550            }
1551            break;
1552
1553        case AWT_MODE_NONE:
1554        case AWT_MODE_SELECT:
1555            if (event.type()==AW_Mouse_Press && clicked.node()) {
1556                GB_transaction ta(tree_static->get_gb_main());
1557                exports.refresh = 1;        // No refresh needed !! AD_map_viewer will do the refresh (needed by arb_pars)
1558                map_viewer_cb(clicked.node()->gb_node, ADMVT_SELECT);
1559
1560                if (event.button() == AW_BUTTON_LEFT) goto act_like_group; // now do the same like in AWT_MODE_GROUP
1561            }
1562            break;
1563
1564        // now handle all modes which only act on tips (aka species) and
1565        // shall perform identically in tree- and list-modes
1566
1567        case AWT_MODE_INFO:
1568        case AWT_MODE_WWW: { 
1569            if (clicked.node() && clicked.node()->gb_node) {
1570                if (command_on_GBDATA(clicked.node()->gb_node, event, map_viewer_cb)) {
1571                    exports.refresh = 1;
1572                }
1573            }
1574            break;
1575        }
1576        default:
1577            break;
1578    }
1579}
1580
1581void AWT_graphic_tree::set_tree_type(AP_tree_display_type type, AWT_canvas *ntw) {
1582    if (sort_is_list_style(type)) {
1583        if (tree_sort == type) { // we are already in wanted view
1584            nds_show_all = !nds_show_all; // -> toggle between 'marked' and 'all'
1585        }
1586        else {
1587            nds_show_all = true; // default to all
1588        }
1589    }
1590    tree_sort = type;
1591    apply_zoom_settings_for_treetype(ntw); // sets default padding
1592
1593    exports.fit_mode  = AWT_FIT_LARGER;
1594    exports.zoom_mode = AWT_ZOOM_BOTH;
1595
1596    exports.dont_scroll = 0;
1597
1598    switch (type) {
1599        case AP_TREE_RADIAL:
1600            break;
1601
1602        case AP_LIST_SIMPLE:
1603        case AP_LIST_NDS:
1604            exports.fit_mode  = AWT_FIT_NEVER;
1605            exports.zoom_mode = AWT_ZOOM_NEVER;
1606
1607            break;
1608
1609        case AP_TREE_IRS:    // folded dendrogram
1610            exports.fit_mode    = AWT_FIT_X;
1611            exports.zoom_mode   = AWT_ZOOM_X;
1612            exports.dont_scroll = 1;
1613            break;
1614
1615        case AP_TREE_NORMAL: // normal dendrogram
1616            exports.fit_mode  = AWT_FIT_X;
1617            exports.zoom_mode = AWT_ZOOM_X;
1618            break;
1619    }
1620    exports.resize = 1;
1621}
1622
1623AWT_graphic_tree::AWT_graphic_tree(AW_root *aw_root_, GBDATA *gb_main_, AD_map_viewer_cb map_viewer_cb_)
1624    : AWT_graphic(),
1625      line_filter          (AW_SCREEN|AW_CLICK|AW_CLICK_DROP|AW_PRINTER|AW_SIZE),
1626      vert_line_filter     (AW_SCREEN|AW_CLICK|AW_CLICK_DROP|AW_PRINTER),
1627      mark_filter          (AW_SCREEN|AW_PRINTER_EXT),
1628      group_bracket_filter (AW_SCREEN|AW_CLICK|AW_CLICK_DROP|AW_PRINTER|AW_SIZE_UNSCALED),
1629      bs_circle_filter     (AW_SCREEN|AW_PRINTER|AW_SIZE_UNSCALED),
1630      leaf_text_filter     (AW_SCREEN|AW_CLICK|AW_CLICK_DROP|AW_PRINTER|AW_SIZE_UNSCALED),
1631      group_text_filter    (AW_SCREEN|AW_CLICK|AW_CLICK_DROP|AW_PRINTER|AW_SIZE_UNSCALED),
1632      remark_text_filter   (AW_SCREEN|AW_CLICK|AW_CLICK_DROP|AW_PRINTER|AW_SIZE_UNSCALED),
1633      other_text_filter    (AW_SCREEN|AW_PRINTER|AW_SIZE_UNSCALED),
1634      ruler_filter         (AW_SCREEN|AW_CLICK|AW_PRINTER), // appropriate size-filter added manually in code
1635      root_filter          (AW_SCREEN|AW_PRINTER_EXT)
1636
1637{
1638    td_assert(gb_main_);
1639
1640    set_tree_type(AP_TREE_NORMAL, NULL);
1641    displayed_root   = 0;
1642    tree_proto       = 0;
1643    link_to_database = false;
1644    tree_static      = 0;
1645    baselinewidth    = 1;
1646    species_name     = 0;
1647    aw_root          = aw_root_;
1648    gb_main          = gb_main_;
1649    cmd_data         = NULL;
1650    nds_show_all     = true;
1651    map_viewer_cb    = map_viewer_cb_;
1652}
1653
1654AWT_graphic_tree::~AWT_graphic_tree() {
1655    delete cmd_data;
1656    free(species_name);
1657    delete tree_proto;
1658    delete tree_static;
1659}
1660
1661AP_tree_root *AWT_graphic_tree::create_tree_root(RootedTreeNodeFactory *nodeMaker_, AliView *aliview, AP_sequence *seq_prototype, bool insert_delete_cbs) {
1662    return new AP_tree_root(aliview, nodeMaker_, seq_prototype, insert_delete_cbs);
1663}
1664
1665void AWT_graphic_tree::init(RootedTreeNodeFactory *nodeMaker_, AliView *aliview, AP_sequence *seq_prototype, bool link_to_database_, bool insert_delete_cbs) {
1666    tree_static      = create_tree_root(nodeMaker_, aliview, seq_prototype, insert_delete_cbs);
1667    td_assert(!insert_delete_cbs || link_to_database); // inserting delete callbacks w/o linking to DB has no effect!
1668    link_to_database = link_to_database_;
1669}
1670
1671void AWT_graphic_tree::unload() {
1672    delete tree_static->get_root_node();
1673    displayed_root = 0;
1674}
1675
1676GB_ERROR AWT_graphic_tree::load(GBDATA *, const char *name, AW_CL /* cl_link_to_database */, AW_CL /* cl_insert_delete_cbs */) {
1677    GB_ERROR error = 0;
1678
1679    if (!name) { // happens in error-case (called by AWT_graphic::postevent_handler to load previous state)
1680        if (tree_static) {
1681            name = tree_static->get_tree_name();
1682            td_assert(name);
1683        }
1684        else {
1685            error = "Please select a tree (name lost)";
1686        }
1687    }
1688
1689    if (!error) {
1690        if (name[0] == 0 || strcmp(name, NO_TREE_SELECTED) == 0) {
1691            unload();
1692            zombies    = 0;
1693            duplicates = 0;
1694        }
1695        else {
1696            freenull(tree_static->gone_tree_name);
1697            {
1698                char *name_dup = strdup(name); // name might be freed by unload()
1699                unload();
1700                error = tree_static->loadFromDB(name_dup);
1701                free(name_dup);
1702            }
1703
1704            if (!error && link_to_database) {
1705                error = tree_static->linkToDB(&zombies, &duplicates);
1706            }
1707
1708            if (error) {
1709                delete tree_static->get_root_node();
1710            }
1711            else {
1712                displayed_root = get_root_node();
1713
1714                get_root_node()->compute_tree();
1715
1716                tree_static->set_root_changed_callback(AWT_graphic_tree_root_changed, this);
1717                tree_static->set_node_deleted_callback(AWT_graphic_tree_node_deleted, this);
1718            }
1719        }
1720    }
1721
1722    return error;
1723}
1724
1725GB_ERROR AWT_graphic_tree::save(GBDATA * /* dummy */, const char * /* name */, AW_CL /* cd1 */, AW_CL /* cd2 */) {
1726    GB_ERROR error = NULL;
1727    if (get_root_node()) {
1728        error = tree_static->saveToDB();
1729    }
1730    else if (tree_static && tree_static->get_tree_name()) {
1731        if (tree_static->gb_tree_gone) {
1732            td_assert(!tree_static->gone_tree_name);
1733            tree_static->gone_tree_name = strdup(tree_static->get_tree_name());
1734
1735            GB_transaction ta(gb_main);
1736            error = GB_delete(tree_static->gb_tree_gone);
1737            error = ta.close(error);
1738
1739            if (!error) {
1740                aw_message(GBS_global_string("Tree '%s' lost all leafs and has been deleted", tree_static->get_tree_name()));
1741#if defined(WARN_TODO)
1742#warning somehow update selected tree
1743
1744                // solution: currently selected tree (in NTREE, maybe also in PARSIMONY)
1745                // needs to add a delete callback on treedata in DB
1746
1747#endif
1748            }
1749
1750            tree_static->gb_tree_gone = 0; // do not delete twice
1751        }
1752    }
1753    return error;
1754}
1755
1756int AWT_graphic_tree::check_update(GBDATA *) {
1757    AP_UPDATE_FLAGS flags(AP_UPDATE_OK);
1758
1759    if (tree_static) {
1760        AP_tree *tree_root = get_root_node();
1761        if (tree_root) {
1762            GB_transaction ta(gb_main);
1763
1764            flags = tree_root->check_update();
1765            switch (flags) {
1766                case AP_UPDATE_OK:
1767                case AP_UPDATE_ERROR:
1768                    break;
1769
1770                case AP_UPDATE_RELOADED: {
1771                    const char *name = tree_static->get_tree_name();
1772                    if (name) {
1773                        GB_ERROR error = load(gb_main, name, 1, 0);
1774                        if (error) aw_message(error);
1775                        exports.resize = 1;
1776                    }
1777                    break;
1778                }
1779                case AP_UPDATE_RELINKED: {
1780                    GB_ERROR error = tree_root->relink();
1781                    if (!error) tree_root->compute_tree();
1782                    if (error) aw_message(error);
1783                    break;
1784                }
1785            }
1786        }
1787    }
1788
1789    return (int)flags;
1790}
1791
1792void AWT_graphic_tree::update(GBDATA *) {
1793    if (tree_static) {
1794        AP_tree *root = get_root_node();
1795        if (root) {
1796            GB_transaction ta(gb_main);
1797            root->update();
1798        }
1799    }
1800}
1801
1802void AWT_graphic_tree::box(int gc, const AW::Position& pos, int pixel_width, bool filled) {
1803    double diameter = disp_device->rtransform_pixelsize(pixel_width);
1804    Vector diagonal(diameter, diameter);
1805
1806    if (filled) disp_device->set_grey_level(gc, grey_level);
1807    else        disp_device->set_line_attributes(gc, 1, AW_SOLID);
1808
1809    disp_device->box(gc, filled, pos-0.5*diagonal, diagonal, mark_filter);
1810}
1811
1812void AWT_graphic_tree::diamond(int gc, const Position& pos, int pixel_width) {
1813    // box with one corner down
1814    double diameter = disp_device->rtransform_pixelsize(pixel_width);
1815    double radius  = diameter*0.5;
1816   
1817    Position t(pos.xpos(), pos.ypos()-radius);
1818    Position b(pos.xpos(), pos.ypos()+radius);
1819    Position l(pos.xpos()-radius, pos.ypos());
1820    Position r(pos.xpos()+radius, pos.ypos());
1821
1822    disp_device->line(gc, l, t, mark_filter);
1823    disp_device->line(gc, r, t, mark_filter);
1824    disp_device->line(gc, l, b, mark_filter);
1825    disp_device->line(gc, r, b, mark_filter);
1826}
1827
1828bool AWT_show_branch_remark(AW_device *device, const char *remark_branch, bool is_leaf, const Position& pos, AW_pos alignment, AW_bitset filteri, int bootstrap_min) {
1829    // returns true if a bootstrap was DISPLAYED
1830    char       *end          = 0;
1831    int         bootstrap    = int(strtol(remark_branch, &end, 10));
1832    bool        is_bootstrap = end[0] == '%' && end[1] == 0;
1833    bool        show         = true;
1834    const char *text         = 0;
1835
1836    if (is_bootstrap) {
1837        if (bootstrap == 100) {
1838            show           = !is_leaf; // do not show 100% bootstraps at leafs
1839            if (show) text = "100%";
1840        }
1841        else if (bootstrap < bootstrap_min) {
1842            show = false;
1843            text = NULL;
1844        }
1845        else {
1846            if (bootstrap == 0) {
1847                text = "<1%"; // show instead of '0%'
1848            }
1849            else {
1850                text = GBS_global_string("%i%%", bootstrap);
1851            }
1852        }
1853    }
1854    else {
1855        text = remark_branch;
1856    }
1857
1858    if (show) {
1859        td_assert(text != 0);
1860        device->text(AWT_GC_BRANCH_REMARK, text, pos, alignment, filteri);
1861    }
1862
1863    return is_bootstrap && show;
1864}
1865
1866bool AWT_show_branch_remark(AW_device *device, const char *remark_branch, bool is_leaf, AW_pos x, AW_pos y, AW_pos alignment, AW_bitset filteri, int bootstrap_min) {
1867    return AWT_show_branch_remark(device, remark_branch, is_leaf, Position(x, y), alignment, filteri, bootstrap_min);
1868}
1869
1870void AWT_graphic_tree::show_dendrogram(AP_tree *at, Position& Pen, DendroSubtreeLimits& limits) {
1871    // 'Pen' points to the upper-left corner of the area into which subtree gets painted
1872    // after return 'Pen' is Y-positioned for next tree-tip (X is undefined)
1873
1874    if (disp_device->type() != AW_DEVICE_SIZE) { // tree below cliprect bottom can be cut
1875        Position p(0, Pen.ypos() - scaled_branch_distance *2.0);
1876        Position s = disp_device->transform(p);
1877
1878        bool   is_clipped = false;
1879        double offset     = 0.0;
1880        if (disp_device->is_below_clip(s.ypos())) {
1881            offset     = scaled_branch_distance;
1882            is_clipped = true;
1883        }
1884        else {
1885            p.sety(Pen.ypos() + scaled_branch_distance * (at->gr.view_sum+2));
1886            s = disp_device->transform(p);;
1887
1888            if (disp_device->is_above_clip(s.ypos())) {
1889                offset     = scaled_branch_distance*at->gr.view_sum;
1890                is_clipped = true;
1891            }
1892        }
1893
1894        if (is_clipped) {
1895            limits.x_right  = Pen.xpos();
1896            limits.y_branch = Pen.ypos();
1897            Pen.movey(offset);
1898            limits.y_top    = limits.y_bot = Pen.ypos();
1899            return;
1900        }
1901    }
1902
1903    AW_click_cd cd(disp_device, (AW_CL)at);
1904    if (at->is_leaf) {
1905        if (at->gb_node && GB_read_flag(at->gb_node)) {
1906            set_line_attributes_for(at);
1907            filled_box(at->gr.gc, Pen, NT_BOX_WIDTH);
1908        }
1909        if (at->hasName(species_name)) cursor = Pen;
1910
1911        if (at->name && (disp_device->get_filter() & leaf_text_filter)) {
1912            // display text
1913            const char            *data       = make_node_text_nds(this->gb_main, at->gb_node, NDS_OUTPUT_LEAFTEXT, at, tree_static->get_tree_name());
1914            const AW_font_limits&  charLimits = disp_device->get_font_limits(at->gr.gc, 'A');
1915
1916            double   unscale  = disp_device->get_unscale();
1917            size_t   data_len = strlen(data);
1918            Position textPos  = Pen + 0.5*Vector((charLimits.width+NT_BOX_WIDTH)*unscale, scaled_font.ascent);
1919            disp_device->text(at->gr.gc, data, textPos, 0.0, leaf_text_filter, data_len);
1920
1921            double textsize = disp_device->get_string_size(at->gr.gc, data, data_len) * unscale;
1922            limits.x_right = textPos.xpos() + textsize;
1923        }
1924        else {
1925            limits.x_right = Pen.xpos();
1926        }
1927
1928        limits.y_top = limits.y_bot = limits.y_branch = Pen.ypos();
1929        Pen.movey(scaled_branch_distance);
1930    }
1931
1932    //   s0-------------n0
1933    //   |
1934    //   attach (to father)
1935    //   |
1936    //   s1------n1
1937
1938    else if (at->gr.grouped) {
1939        double height     = scaled_branch_distance * at->gr.view_sum;
1940        double box_height = height-scaled_branch_distance;
1941
1942        Position s0(Pen);
1943        Position s1(s0);  s1.movey(box_height);
1944        Position n0(s0);  n0.movex(at->gr.max_tree_depth);
1945        Position n1(s1);  n1.movex(at->gr.min_tree_depth);
1946
1947        Position group[4] = { s0, s1, n1, n0 };
1948
1949        set_line_attributes_for(at);
1950        disp_device->set_grey_level(at->gr.gc, grey_level);
1951        disp_device->filled_area(at->gr.gc, 4, group, line_filter);
1952
1953        const AW_font_limits& charLimits  = disp_device->get_font_limits(at->gr.gc, 'A');
1954        double                text_ascent = charLimits.ascent * disp_device->get_unscale();
1955
1956        Vector text_offset = 0.5 * Vector(text_ascent, text_ascent+box_height);
1957
1958        limits.x_right = n0.xpos();
1959
1960        if (at->gb_node && (disp_device->get_filter() & group_text_filter)) {
1961            const char *data     = make_node_text_nds(this->gb_main, at->gb_node, NDS_OUTPUT_LEAFTEXT, at, tree_static->get_tree_name());
1962            size_t      data_len = strlen(data);
1963
1964            Position textPos = n0+text_offset;
1965            disp_device->text(at->gr.gc, data, textPos, 0.0, group_text_filter, data_len);
1966
1967            double textsize = disp_device->get_string_size(at->gr.gc, data, data_len) * disp_device->get_unscale();
1968            limits.x_right  = textPos.xpos()+textsize;
1969        }
1970
1971        Position    countPos = s0+text_offset;
1972        const char *count    = GBS_global_string(" %u", at->gr.leaf_sum);
1973        disp_device->text(at->gr.gc, count, countPos, 0.0, group_text_filter);
1974
1975        limits.y_top    = s0.ypos();
1976        limits.y_bot    = s1.ypos();
1977        limits.y_branch = centroid(limits.y_top, limits.y_bot);
1978
1979        Pen.movey(height);
1980    }
1981    else { // furcation
1982        Position s0(Pen);
1983
1984        Pen.movex(at->leftlen);
1985        Position n0(Pen);
1986
1987        show_dendrogram(at->get_leftson(), Pen, limits); // re-use limits for left branch
1988       
1989        n0.sety(limits.y_branch);
1990        s0.sety(limits.y_branch);
1991
1992        Pen.setx(s0.xpos());
1993        Position attach(Pen); attach.movey(- .5*scaled_branch_distance);
1994        Pen.movex(at->rightlen);
1995        Position n1(Pen);
1996        {
1997            DendroSubtreeLimits right_lim;
1998            show_dendrogram(at->get_rightson(), Pen, right_lim);
1999            n1.sety(right_lim.y_branch);
2000            limits.combine(right_lim);
2001        }
2002
2003        Position s1(s0.xpos(), n1.ypos());
2004       
2005        if (at->name) {
2006            diamond(at->gr.gc, attach, NT_BOX_WIDTH*2);
2007
2008            if (show_brackets) {
2009                double                unscale          = disp_device->get_unscale();
2010                const AW_font_limits& charLimits       = disp_device->get_font_limits(at->gr.gc, 'A');
2011                double                half_text_ascent = charLimits.ascent * unscale * 0.5;
2012
2013                double x1 = limits.x_right + scaled_branch_distance*0.1;
2014                double x2 = x1 + scaled_branch_distance * 0.3;
2015                double y1 = limits.y_top - half_text_ascent * 0.5;
2016                double y2 = limits.y_bot + half_text_ascent * 0.5;
2017
2018                Rectangle bracket(Position(x1, y1), Position(x2, y2));
2019
2020                set_line_attributes_for(at);
2021
2022                unsigned int gc = at->gr.gc;
2023                disp_device->line(gc, bracket.upper_edge(), group_bracket_filter);
2024                disp_device->line(gc, bracket.lower_edge(), group_bracket_filter);
2025                disp_device->line(gc, bracket.right_edge(), group_bracket_filter);
2026
2027                limits.x_right = x2;
2028           
2029                if (at->gb_node && (disp_device->get_filter() & group_text_filter)) {
2030                    const char *data     = make_node_text_nds(this->gb_main, at->gb_node, NDS_OUTPUT_LEAFTEXT, at, tree_static->get_tree_name());
2031                    size_t      data_len = strlen(data);
2032
2033                    LineVector worldBracket = disp_device->transform(bracket.right_edge());
2034                    LineVector clippedWorldBracket;
2035                    bool       visible      = disp_device->clip(worldBracket, clippedWorldBracket);
2036                    if (visible) {
2037                        LineVector clippedBracket = disp_device->rtransform(clippedWorldBracket);
2038
2039                        Position textPos = clippedBracket.centroid()+Vector(half_text_ascent, half_text_ascent);
2040                        disp_device->text(at->gr.gc, data, textPos, 0.0, group_text_filter, data_len);
2041
2042                        double textsize = disp_device->get_string_size(at->gr.gc, data, data_len) * unscale;
2043                        limits.x_right  = textPos.xpos()+textsize;
2044                    }
2045                }
2046            }
2047        }
2048
2049        for (int right = 0; right<2; ++right) {
2050            AP_tree         *son;
2051            GBT_LEN          len;
2052            const Position&  n = right ? n1 : n0;
2053            const Position&  s = right ? s1 : s0;
2054
2055            if (right) {
2056                son = at->get_rightson();
2057                len = at->rightlen;
2058            }
2059            else {
2060                son = at->get_leftson();
2061                len = at->leftlen;
2062            }
2063
2064            AW_click_cd cds(disp_device, (AW_CL)son);
2065            if (son->get_remark()) {
2066                Position remarkPos(n);
2067                remarkPos.movey(-scaled_font.ascent*0.1);
2068                bool bootstrap_shown = AWT_show_branch_remark(disp_device, son->get_remark(), son->is_leaf, remarkPos, 1, remark_text_filter, bootstrap_min);
2069                if (show_circle && bootstrap_shown) {
2070                    show_bootstrap_circle(disp_device, son->get_remark(), circle_zoom_factor, circle_max_size, len, n, use_ellipse, scaled_branch_distance, bs_circle_filter);
2071                }
2072            }
2073
2074            set_line_attributes_for(son);
2075            unsigned int gc = son->gr.gc;
2076            draw_branch_line(gc, s, n, line_filter);
2077            draw_branch_line(gc, attach, s, vert_line_filter);
2078        }
2079        limits.y_branch = attach.ypos();
2080    }
2081}
2082
2083
2084void AWT_graphic_tree::scale_text_koordinaten(AW_device *device, int gc, double& x, double& y, double orientation, int flag) {
2085    if (flag!=1) {
2086        const AW_font_limits& charLimits  = device->get_font_limits(gc, 'A');
2087        double                text_height = charLimits.height * disp_device->get_unscale();
2088        double                dist        = charLimits.height * disp_device->get_unscale();
2089
2090        x += cos(orientation) * dist;
2091        y += sin(orientation) * dist + 0.3*text_height;
2092    }
2093}
2094
2095void AWT_graphic_tree::show_radial_tree(AP_tree * at, double x_center,
2096                                        double y_center, double tree_spread, double tree_orientation,
2097                                        double x_root, double y_root)
2098{
2099    double l, r, w, z, l_min, l_max;
2100
2101    AW_click_cd cd(disp_device, (AW_CL)at);
2102    set_line_attributes_for(at);
2103    draw_branch_line(at->gr.gc, Position(x_root, y_root), Position(x_center, y_center), line_filter);
2104
2105    if (at->is_leaf) {
2106        // draw mark box
2107        if (at->gb_node && GB_read_flag(at->gb_node)) {
2108            filled_box(at->gr.gc, Position(x_center, y_center), NT_BOX_WIDTH);
2109        }
2110
2111        if (at->name && (disp_device->get_filter() & leaf_text_filter)) {
2112            if (at->hasName(species_name)) cursor = Position(x_center, y_center);
2113            scale_text_koordinaten(disp_device, at->gr.gc, x_center, y_center, tree_orientation, 0);
2114            const char *data =  make_node_text_nds(this->gb_main, at->gb_node, NDS_OUTPUT_LEAFTEXT, at, tree_static->get_tree_name());
2115            disp_device->text(at->gr.gc, data,
2116                              (AW_pos)x_center, (AW_pos) y_center,
2117                              (AW_pos) .5 - .5 * cos(tree_orientation),
2118                              leaf_text_filter);
2119        }
2120        return;
2121    }
2122
2123    if (at->gr.grouped) {
2124        l_min = at->gr.min_tree_depth;
2125        l_max = at->gr.max_tree_depth;
2126
2127        r    = l = 0.5;
2128        AW_pos q[6];
2129        q[0] = x_center;
2130        q[1] = y_center;
2131        w    = tree_orientation + r*0.5*tree_spread + at->gr.right_angle;
2132        q[2] = x_center+l_min*cos(w);
2133        q[3] = y_center+l_min*sin(w);
2134        w    = tree_orientation - l*0.5*tree_spread + at->gr.right_angle;
2135        q[4] = x_center+l_max*cos(w);
2136        q[5] = y_center+l_max*sin(w);
2137
2138        disp_device->set_grey_level(at->gr.gc, grey_level);
2139        disp_device->filled_area(at->gr.gc, 3, &q[0], line_filter);
2140
2141        if (at->gb_node && (disp_device->get_filter() & group_text_filter)) {
2142            w = tree_orientation + at->gr.right_angle;
2143            l_max = (l_max+l_min)*.5;
2144            x_center = x_center+l_max*cos(w);
2145            y_center = y_center+l_max*sin(w);
2146            scale_text_koordinaten(disp_device, at->gr.gc, x_center, y_center, w, 0);
2147
2148            // insert text (e.g. name of group)
2149            const char *data = make_node_text_nds(this->gb_main, at->gb_node, NDS_OUTPUT_LEAFTEXT, at, tree_static->get_tree_name());
2150            disp_device->text(at->gr.gc, data,
2151                              (AW_pos)x_center, (AW_pos) y_center,
2152                              (AW_pos).5 - .5 * cos(tree_orientation),
2153                              group_text_filter);
2154        }
2155        return;
2156    }
2157    l = (double) at->get_leftson()->gr.view_sum / (double)at->gr.view_sum;
2158    r = 1.0 - (double)l;
2159
2160    {
2161        AP_tree *at_leftson  = at->get_leftson();
2162        AP_tree *at_rightson = at->get_rightson();
2163
2164        if (at_leftson->gr.gc > at_rightson->gr.gc) {
2165            // bring selected gc to front
2166
2167            //!* left branch **
2168            w = r*0.5*tree_spread + tree_orientation + at->gr.left_angle;
2169            z = at->leftlen;
2170            show_radial_tree(at_leftson,
2171                             x_center + z * cos(w),
2172                             y_center + z * sin(w),
2173                             (at->leftson->is_leaf) ? 1.0 : tree_spread * l * at_leftson->gr.spread,
2174                             w,
2175                             x_center, y_center);
2176
2177            //!* right branch **
2178            w = tree_orientation - l*0.5*tree_spread + at->gr.right_angle;
2179            z = at->rightlen;
2180            show_radial_tree(at_rightson,
2181                             x_center + z * cos(w),
2182                             y_center + z * sin(w),
2183                             (at->rightson->is_leaf) ? 1.0 : tree_spread * r * at_rightson->gr.spread,
2184                             w,
2185                             x_center, y_center);
2186        }
2187        else {
2188            //!* right branch **
2189            w = tree_orientation - l*0.5*tree_spread + at->gr.right_angle;
2190            z = at->rightlen;
2191            show_radial_tree(at_rightson,
2192                             x_center + z * cos(w),
2193                             y_center + z * sin(w),
2194                             (at->rightson->is_leaf) ? 1.0 : tree_spread * r * at_rightson->gr.spread,
2195                             w,
2196                             x_center, y_center);
2197
2198            //!* left branch **
2199            w = r*0.5*tree_spread + tree_orientation + at->gr.left_angle;
2200            z = at->leftlen;
2201            show_radial_tree(at_leftson,
2202                             x_center + z * cos(w),
2203                             y_center + z * sin(w),
2204                             (at->leftson->is_leaf) ? 1.0 : tree_spread * l * at_leftson->gr.spread,
2205                             w,
2206                             x_center, y_center);
2207        }
2208    }
2209    if (show_circle) {
2210        if (at->leftson->get_remark()) {
2211            AW_click_cd cdl(disp_device, (AW_CL)at->leftson);
2212            w = r*0.5*tree_spread + tree_orientation + at->gr.left_angle;
2213            z = at->leftlen * .5;
2214            Position center(x_center + z * cos(w), y_center + z * sin(w));
2215            show_bootstrap_circle(disp_device, at->leftson->get_remark(), circle_zoom_factor, circle_max_size, at->leftlen, center, false, 0, bs_circle_filter);
2216        }
2217        if (at->rightson->get_remark()) {
2218            AW_click_cd cdr(disp_device, (AW_CL)at->rightson);
2219            w = tree_orientation - l*0.5*tree_spread + at->gr.right_angle;
2220            z = at->rightlen * .5;
2221            Position center(x_center + z * cos(w), y_center + z * sin(w));
2222            show_bootstrap_circle(disp_device, at->rightson->get_remark(), circle_zoom_factor, circle_max_size, at->rightlen, center, false, 0, bs_circle_filter);
2223        }
2224    }
2225}
2226
2227const char *AWT_graphic_tree::ruler_awar(const char *name) {
2228    // return "ruler/TREETYPE/name" (path to entry below tree)
2229    const char *tree_awar = 0;
2230    switch (tree_sort) {
2231        case AP_TREE_NORMAL:
2232            tree_awar = "LIST";
2233            break;
2234        case AP_TREE_RADIAL:
2235            tree_awar = "RADIAL";
2236            break;
2237        case AP_TREE_IRS:
2238            tree_awar = "IRS";
2239            break;
2240        case AP_LIST_SIMPLE:
2241        case AP_LIST_NDS:
2242            // rulers not allowed in these display modes
2243            td_assert(0); // should not be called
2244            break;
2245    }
2246
2247    static char awar_name[256];
2248    sprintf(awar_name, "ruler/%s/%s", tree_awar, name);
2249    return awar_name;
2250}
2251
2252void AWT_graphic_tree::show_ruler(AW_device *device, int gc) {
2253    GBDATA *gb_tree = tree_static->get_gb_tree();
2254    if (!gb_tree) return; // no tree -> no ruler
2255
2256    bool mode_has_ruler = ruler_awar(NULL);
2257    if (mode_has_ruler) {
2258        GB_transaction ta(gb_tree);
2259
2260        float ruler_size = *GBT_readOrCreate_float(gb_tree, RULER_SIZE, DEFAULT_RULER_LENGTH);
2261        float ruler_y    = 0.0;
2262
2263        const char *awar = ruler_awar("ruler_y");
2264        if (!GB_search(gb_tree, awar, GB_FIND)) {
2265            if (device->type() == AW_DEVICE_SIZE) {
2266                AW_world world;
2267                DOWNCAST(AW_device_size*, device)->get_size_information(&world);
2268                ruler_y = world.b * 1.3;
2269            }
2270        }
2271
2272        double half_ruler_width = ruler_size*0.5;
2273
2274        float ruler_add_y  = 0.0;
2275        float ruler_add_x  = 0.0;
2276        switch (tree_sort) {
2277            case AP_TREE_IRS:
2278                // scale is different for IRS tree -> adjust:
2279                half_ruler_width *= irs_tree_ruler_scale_factor;
2280                ruler_y     = 0;
2281                ruler_add_y = this->list_tree_ruler_y;
2282                ruler_add_x = -half_ruler_width;
2283                break;
2284            case AP_TREE_NORMAL:
2285                ruler_y     = 0;
2286                ruler_add_y = this->list_tree_ruler_y;
2287                ruler_add_x = half_ruler_width;
2288                break;
2289            default:
2290                break;
2291        }
2292        ruler_y = ruler_add_y + *GBT_readOrCreate_float(gb_tree, awar, ruler_y);
2293
2294        float ruler_x = 0.0;
2295        ruler_x       = ruler_add_x + *GBT_readOrCreate_float(gb_tree, ruler_awar("ruler_x"), ruler_x);
2296
2297        td_assert(!is_nan_or_inf(ruler_x));
2298
2299        float ruler_text_x = 0.0;
2300        ruler_text_x       = *GBT_readOrCreate_float(gb_tree, ruler_awar("text_x"), ruler_text_x);
2301
2302        td_assert(!is_nan_or_inf(ruler_text_x));
2303
2304        float ruler_text_y = 0.0;
2305        ruler_text_y       = *GBT_readOrCreate_float(gb_tree, ruler_awar("text_y"), ruler_text_y);
2306
2307        td_assert(!is_nan_or_inf(ruler_text_y));
2308
2309        int ruler_width = *GBT_readOrCreate_int(gb_tree, RULER_LINEWIDTH, DEFAULT_RULER_LINEWIDTH);
2310
2311        device->set_line_attributes(gc, ruler_width+baselinewidth, AW_SOLID);
2312
2313        AW_click_cd cd(device, 0, (AW_CL)"ruler");
2314        device->line(gc,
2315                     ruler_x - half_ruler_width, ruler_y,
2316                     ruler_x + half_ruler_width, ruler_y,
2317                     this->ruler_filter|AW_SIZE);
2318       
2319        char ruler_text[20];
2320        sprintf(ruler_text, "%4.2f", ruler_size);
2321        device->text(gc, ruler_text,
2322                     ruler_x + ruler_text_x,
2323                     ruler_y + ruler_text_y,
2324                     0.5,
2325                     this->ruler_filter|AW_SIZE_UNSCALED);
2326    }
2327}
2328
2329struct Column : virtual Noncopyable {
2330    char   *text;
2331    size_t  len;
2332    double  print_width;
2333    bool    is_numeric; // also true for empty text
2334
2335    Column() : text(NULL) {}
2336    ~Column() { free(text); }
2337
2338    void init(const char *text_, AW_device& device, int gc) {
2339        text        = strdup(text_);
2340        len         = strlen(text);
2341        print_width = device.get_string_size(gc, text, len);
2342        is_numeric  = (strspn(text, "0123456789.") == len);
2343    }
2344};
2345
2346class ListDisplayRow : virtual Noncopyable {
2347    GBDATA *gb_species;
2348    AW_pos  y_position;
2349    int     gc;
2350    size_t  part_count;                             // NDS columns
2351    Column *column;
2352
2353public:
2354    ListDisplayRow(GBDATA *gb_main, GBDATA *gb_species_, AW_pos y_position_, int gc_, AW_device& device, bool use_nds, const char *tree_name)
2355        : gb_species(gb_species_)
2356        , y_position(y_position_)
2357        , gc(gc_)
2358    {
2359        const char *nds = use_nds
2360            ? make_node_text_nds(gb_main, gb_species, NDS_OUTPUT_TAB_SEPARATED, 0, tree_name)
2361            : GBT_read_name(gb_species);
2362
2363        ConstStrArray parts;
2364        GBT_split_string(parts, nds, "\t", false);
2365        part_count = parts.size();
2366
2367        column = new Column[part_count];
2368        for (size_t i = 0; i<part_count; ++i) {
2369            column[i].init(parts[i], device, gc);
2370        }
2371    }
2372
2373    ~ListDisplayRow() { delete [] column; }
2374
2375    size_t get_part_count() const { return part_count; }
2376    const Column& get_column(size_t p) const {
2377        td_assert(p<part_count);
2378        return column[p];
2379    }
2380    double get_print_width(size_t p) const { return get_column(p).print_width; }
2381    const char *get_text(size_t p, size_t& len) const {
2382        const Column& col = get_column(p);
2383        len = col.len;
2384        return col.text;
2385    }
2386    int get_gc() const { return gc; }
2387    double get_ypos() const { return y_position; }
2388    GBDATA *get_species() const { return gb_species; }
2389};
2390
2391void AWT_graphic_tree::show_nds_list(GBDATA *, bool use_nds) {
2392    AW_pos y_position = scaled_branch_distance;
2393    AW_pos x_position = NT_SELECTED_WIDTH * disp_device->get_unscale();
2394
2395    disp_device->text(nds_show_all ? AWT_GC_CURSOR : AWT_GC_SELECTED,
2396                      GBS_global_string("%s of %s species", use_nds ? "NDS List" : "Simple list", nds_show_all ? "all" : "marked"),
2397                      (AW_pos) x_position, (AW_pos) 0,
2398                      (AW_pos) 0, other_text_filter);
2399
2400    double max_x         = 0;
2401    double text_y_offset = scaled_font.ascent*.5;
2402
2403    GBDATA *selected_species;
2404    {
2405        GBDATA *selected_name = GB_find_string(GBT_get_species_data(gb_main), "name", this->species_name, GB_IGNORE_CASE, SEARCH_GRANDCHILD);
2406        selected_species      = selected_name ? GB_get_father(selected_name) : NULL;
2407    }
2408
2409    const char *tree_name = tree_static ? tree_static->get_tree_name() : NULL;
2410
2411    AW_pos y1, y2;
2412    {
2413        const AW_screen_area& clip_rect = disp_device->get_cliprect();
2414           
2415        AW_pos Y1 = clip_rect.t;
2416        AW_pos Y2 = clip_rect.b;
2417
2418        AW_pos x;
2419        disp_device->rtransform(0, Y1, x, y1);
2420        disp_device->rtransform(0, Y2, x, y2);
2421    }
2422
2423    y1 -= 2*scaled_branch_distance;                 // add two lines for safety
2424    y2 += 2*scaled_branch_distance;
2425
2426    size_t           displayed_rows = (y2-y1)/scaled_branch_distance+1;
2427    ListDisplayRow **row            = new ListDisplayRow*[displayed_rows];
2428
2429    size_t species_count = 0;
2430    size_t max_parts     = 0;
2431
2432    GBDATA *gb_species = nds_show_all ? GBT_first_species(gb_main) : GBT_first_marked_species(gb_main);
2433    if (gb_species) {
2434        int skip_over = (y1-y_position)/scaled_branch_distance-2;
2435        if (skip_over>0) {
2436            gb_species  = nds_show_all
2437                ? GB_followingEntry(gb_species, skip_over-1)
2438                : GB_following_marked(gb_species, "species", skip_over-1);
2439            y_position += skip_over*scaled_branch_distance;
2440        }
2441    }
2442
2443    for (; gb_species; gb_species = nds_show_all ? GBT_next_species(gb_species) : GBT_next_marked_species(gb_species)) {
2444        y_position += scaled_branch_distance;
2445        if (gb_species == selected_species) cursor = Position(0, y_position);
2446        if (y_position>y1) {
2447            if (y_position>y2) break;           // no need to examine rest of species
2448
2449            bool is_marked = nds_show_all ? GB_read_flag(gb_species) : true;
2450            if (is_marked) {
2451                disp_device->set_line_attributes(AWT_GC_SELECTED, baselinewidth, AW_SOLID);
2452                filled_box(AWT_GC_SELECTED, Position(0, y_position), NT_BOX_WIDTH);
2453            }
2454
2455            int gc                            = AWT_GC_NSELECTED;
2456            if (nds_show_all && is_marked) gc = AWT_GC_SELECTED;
2457            else {
2458                int color_group     = AWT_species_get_dominant_color(gb_species);
2459                if (color_group) gc = AWT_GC_FIRST_COLOR_GROUP+color_group-1;
2460            }
2461            ListDisplayRow *curr = new ListDisplayRow(gb_main, gb_species, y_position+text_y_offset, gc, *disp_device, use_nds, tree_name);
2462            max_parts            = std::max(max_parts, curr->get_part_count());
2463            row[species_count++] = curr;
2464        }
2465    }
2466
2467    td_assert(species_count <= displayed_rows);
2468
2469    // calculate column offsets and detect column alignment
2470    double *max_part_width = new double[max_parts];
2471    bool   *align_right    = new bool[max_parts];
2472
2473    for (size_t p = 0; p<max_parts; ++p) {
2474        max_part_width[p] = 0;
2475        align_right[p]    = true;
2476    }
2477
2478    for (size_t s = 0; s<species_count; ++s) {
2479        size_t parts = row[s]->get_part_count();
2480        for (size_t p = 0; p<parts; ++p) {
2481            const Column& col = row[s]->get_column(p);
2482            max_part_width[p] = std::max(max_part_width[p], col.print_width);
2483            align_right[p]    = align_right[p] && col.is_numeric;
2484        }
2485    }
2486
2487    double column_space = scaled_branch_distance;
2488
2489    double *part_x_pos = new double[max_parts];
2490    for (size_t p = 0; p<max_parts; ++p) {
2491        part_x_pos[p]  = x_position;
2492        x_position    += max_part_width[p]+column_space;
2493    }
2494    max_x = x_position;
2495
2496    // draw
2497
2498    for (size_t s = 0; s<species_count; ++s) {
2499        const ListDisplayRow& Row = *row[s];
2500
2501        size_t parts = Row.get_part_count();
2502        int    gc    = Row.get_gc();
2503        AW_pos y     = Row.get_ypos();
2504
2505        GBDATA      *gb_sp = Row.get_species();
2506        AW_click_cd  cd(disp_device, (AW_CL)gb_sp, (AW_CL)"species");
2507
2508        for (size_t p = 0; p<parts; ++p) {
2509            const Column& col = Row.get_column(p);
2510
2511            AW_pos x               = part_x_pos[p];
2512            if (align_right[p]) x += max_part_width[p] - col.print_width;
2513
2514            disp_device->text(gc, col.text, x, y, 0.0, leaf_text_filter, col.len);
2515        }
2516    }
2517
2518    delete [] part_x_pos;
2519    delete [] align_right;
2520    delete [] max_part_width;
2521
2522    for (size_t s = 0; s<species_count; ++s) delete row[s];
2523    delete [] row;
2524
2525    disp_device->invisible(Origin);  // @@@ remove when size-dev works
2526    disp_device->invisible(Position(max_x, y_position+scaled_branch_distance));  // @@@ remove when size-dev works
2527}
2528
2529void AWT_graphic_tree::read_tree_settings() {
2530    scaled_branch_distance = aw_root->awar(AWAR_DTREE_VERICAL_DIST)->read_float(); // not final value!
2531    grey_level             = aw_root->awar(AWAR_DTREE_GREY_LEVEL)->read_int()*.01;
2532    baselinewidth          = aw_root->awar(AWAR_DTREE_BASELINEWIDTH)->read_int();
2533    show_brackets          = aw_root->awar(AWAR_DTREE_SHOW_BRACKETS)->read_int();
2534    show_circle            = aw_root->awar(AWAR_DTREE_SHOW_CIRCLE)->read_int();
2535    circle_zoom_factor     = aw_root->awar(AWAR_DTREE_CIRCLE_ZOOM)->read_float();
2536    circle_max_size        = aw_root->awar(AWAR_DTREE_CIRCLE_MAX_SIZE)->read_float();
2537    use_ellipse            = aw_root->awar(AWAR_DTREE_USE_ELLIPSE)->read_int();
2538    bootstrap_min          = aw_root->awar(AWAR_DTREE_BOOTSTRAP_MIN)->read_int();
2539   
2540    freeset(species_name, aw_root->awar(AWAR_SPECIES_NAME)->read_string());
2541}
2542
2543void AWT_graphic_tree::apply_zoom_settings_for_treetype(AWT_canvas *ntw) {
2544    exports.set_standard_default_padding();
2545
2546    if (ntw) {
2547        bool zoom_fit_text       = false;
2548        int  left_padding  = 0;
2549        int  right_padding = 0;
2550
2551        switch (tree_sort) {
2552            case AP_TREE_RADIAL:
2553                zoom_fit_text = aw_root->awar(AWAR_DTREE_RADIAL_ZOOM_TEXT)->read_int();
2554                left_padding  = aw_root->awar(AWAR_DTREE_RADIAL_XPAD)->read_int();
2555                right_padding = left_padding;
2556                break;
2557
2558            case AP_TREE_NORMAL:
2559            case AP_TREE_IRS:
2560                zoom_fit_text = aw_root->awar(AWAR_DTREE_DENDRO_ZOOM_TEXT)->read_int();
2561                left_padding  = STANDARD_PADDING;
2562                right_padding = aw_root->awar(AWAR_DTREE_DENDRO_XPAD)->read_int();
2563                break;
2564
2565            default :
2566                break;
2567        }
2568
2569        exports.set_default_padding(STANDARD_PADDING, STANDARD_PADDING, left_padding, right_padding);
2570   
2571        ntw->set_consider_text_for_zoom_reset(zoom_fit_text);
2572    }
2573}
2574
2575void AWT_graphic_tree::show(AW_device *device) {
2576    if (tree_static && tree_static->get_gb_tree()) {
2577        check_update(gb_main);
2578    }
2579
2580    read_tree_settings();
2581   
2582    disp_device = device;
2583    disp_device->reset_style();
2584
2585    const AW_font_limits& charLimits = disp_device->get_font_limits(AWT_GC_SELECTED, 0);
2586
2587    scaled_font.init(charLimits, device->get_unscale());
2588    scaled_branch_distance *= scaled_font.height;
2589
2590    make_node_text_init(gb_main);
2591
2592    cursor = Origin;
2593
2594    if (!displayed_root && sort_is_tree_style(tree_sort)) { // if there is no tree, but display style needs tree
2595        static const char *no_tree_text[] = {
2596            "No tree (selected)",
2597            "",
2598            "In the top area you may click on",
2599            "- the listview-button to see a plain list of species",
2600            "- the tree-selection-button to select a tree",
2601            NULL
2602        };
2603
2604        Position p0(0, -3*scaled_branch_distance);
2605        cursor = p0;
2606        for (int i = 0; no_tree_text[i]; ++i) {
2607            cursor.movey(scaled_branch_distance);
2608            device->text(AWT_GC_CURSOR, no_tree_text[i], cursor);
2609        }
2610        device->line(AWT_GC_CURSOR, p0, cursor);
2611    }
2612    else {
2613        switch (tree_sort) {
2614            case AP_TREE_NORMAL: {
2615                DendroSubtreeLimits limits;
2616                Position pen(0, 0.05);
2617                show_dendrogram(displayed_root, pen, limits);
2618                list_tree_ruler_y = pen.ypos() + 2.0 * scaled_branch_distance;
2619                break;
2620            }
2621            case AP_TREE_RADIAL:
2622                empty_box(displayed_root->gr.gc, Origin, NT_ROOT_WIDTH);
2623                show_radial_tree(displayed_root, 0, 0, 2*M_PI, 0.0, 0, 0);
2624                break;
2625
2626            case AP_TREE_IRS:
2627                show_irs_tree(displayed_root, scaled_branch_distance);
2628                break;
2629
2630            case AP_LIST_NDS:       // this is the list all/marked species mode (no tree)
2631                show_nds_list(gb_main, true);
2632                break;
2633
2634            case AP_LIST_SIMPLE:    // simple list of names (used at startup only)
2635                // don't see why we need to draw ANY tree at startup -> disabled
2636                // show_nds_list(gb_main, false);
2637                break;
2638        }
2639        if (are_distinct(Origin, cursor)) empty_box(AWT_GC_CURSOR, cursor, NT_SELECTED_WIDTH);
2640        if (sort_is_tree_style(tree_sort)) show_ruler(disp_device, AWT_GC_CURSOR);
2641    }
2642
2643    if (cmd_data && Dragged::valid_drag_device(disp_device)) {
2644        Dragged *dragging = dynamic_cast<Dragged*>(cmd_data);
2645        if (dragging) {
2646            // if tree is redisplayed while dragging, redraw the drag indicator.
2647            // (happens in modes which modify the tree during drag, e.g. when scaling branches)
2648            dragging->draw_drag_indicator(disp_device, drag_gc);
2649        }
2650    }
2651
2652    disp_device = NULL;
2653}
2654
2655void AWT_graphic_tree::info(AW_device */*device*/, AW_pos /*x*/, AW_pos /*y*/, AW_clicked_line */*cl*/, AW_clicked_text */*ct*/) {
2656    aw_message("INFO MESSAGE");
2657}
2658
2659AWT_graphic_tree *NT_generate_tree(AW_root *root, GBDATA *gb_main, AD_map_viewer_cb map_viewer_cb) {
2660    AWT_graphic_tree *apdt = new AWT_graphic_tree(root, gb_main, map_viewer_cb);
2661    apdt->init(new AP_TreeNodeFactory, new AliView(gb_main), NULL, true, false); // tree w/o sequence data
2662    return apdt;
2663}
2664
2665void awt_create_dtree_awars(AW_root *aw_root, AW_default db) {
2666    aw_root->awar_int  (AWAR_DTREE_BASELINEWIDTH, 1)  ->set_minmax(1,    10);
2667    aw_root->awar_float(AWAR_DTREE_VERICAL_DIST,  1.0)->set_minmax(0.01, 30);
2668
2669    aw_root->awar_int(AWAR_DTREE_AUTO_JUMP,      AP_JUMP_KEEP_VISIBLE);
2670    aw_root->awar_int(AWAR_DTREE_AUTO_JUMP_TREE, AP_JUMP_FORCE_VCENTER);
2671
2672    aw_root->awar_int(AWAR_DTREE_SHOW_BRACKETS, 1);
2673    aw_root->awar_int(AWAR_DTREE_SHOW_CIRCLE,   0);
2674    aw_root->awar_int(AWAR_DTREE_USE_ELLIPSE,   1);
2675
2676    aw_root->awar_float(AWAR_DTREE_CIRCLE_ZOOM,     1.0)->set_minmax(0.01, 20);
2677    aw_root->awar_float(AWAR_DTREE_CIRCLE_MAX_SIZE, 1.5)->set_minmax(0.01, 200);
2678    aw_root->awar_int  (AWAR_DTREE_GREY_LEVEL,      20) ->set_minmax(0,    100);
2679
2680    aw_root->awar_int  (AWAR_DTREE_BOOTSTRAP_MIN, 0)->set_minmax(0,100);
2681   
2682    aw_root->awar_int(AWAR_DTREE_RADIAL_ZOOM_TEXT, 0);
2683    aw_root->awar_int(AWAR_DTREE_RADIAL_XPAD,      150);
2684    aw_root->awar_int(AWAR_DTREE_DENDRO_ZOOM_TEXT, 0);
2685    aw_root->awar_int(AWAR_DTREE_DENDRO_XPAD,      300);
2686
2687    aw_root->awar_int(AWAR_TREE_REFRESH, 0, db);
2688}
2689
2690void TREE_insert_jump_option_menu(AW_window *aws, const char *label, const char *awar_name) {
2691    aws->label(label);
2692    aws->create_option_menu(awar_name, true);
2693    aws->insert_default_option("do nothing",        "n", AP_DONT_JUMP);
2694    aws->insert_option        ("keep visible",      "k", AP_JUMP_KEEP_VISIBLE);
2695    aws->insert_option        ("center vertically", "v", AP_JUMP_FORCE_VCENTER);
2696    aws->insert_option        ("center",            "c", AP_JUMP_FORCE_CENTER);
2697    aws->update_option_menu();
2698    aws->at_newline();
2699}
2700
2701// --------------------------------------------------------------------------------
2702
2703#ifdef UNIT_TESTS
2704#include <test_unit.h>
2705#include <../../WINDOW/aw_common.hxx>
2706
2707static void fake_AD_map_viewer_cb(GBDATA *, AD_MAP_VIEWER_TYPE ) {}
2708
2709static AW_rgb colors_def[] = {
2710    AW_NO_COLOR, AW_NO_COLOR, AW_NO_COLOR, AW_NO_COLOR, AW_NO_COLOR, AW_NO_COLOR,
2711    0x30b0e0,
2712    0xff8800, // AWT_GC_CURSOR
2713    0xa3b3cf, // AWT_GC_BRANCH_REMARK
2714    0x53d3ff, // AWT_GC_BOOTSTRAP
2715    0x808080, // AWT_GC_BOOTSTRAP_LIMITED
2716    0x000000, // AWT_GC_GROUPS
2717    0xf0c000, // AWT_GC_SELECTED
2718    0xbb8833, // AWT_GC_UNDIFF
2719    0x622300, // AWT_GC_NSELECTED
2720    0x977a0e, // AWT_GC_ZOMBIES
2721    0x000000, // AWT_GC_BLACK
2722    0xffff00, // AWT_GC_YELLOW
2723    0xff0000, // AWT_GC_RED
2724    0xff00ff, // AWT_GC_MAGENTA
2725    0x00ff00, // AWT_GC_GREEN
2726    0x00ffff, // AWT_GC_CYAN
2727    0x0000ff, // AWT_GC_BLUE
2728    0x808080, // AWT_GC_WHITE
2729    0xd50000, // AWT_GC_FIRST_COLOR_GROUP
2730    0x00c0a0, 
2731    0x00ff77,
2732    0xc700c7,
2733    0x0000ff,
2734    0xffce5b,
2735    0xab2323,
2736    0x008888,
2737    0x008800,
2738    0x880088,
2739    0x000088,
2740    0x888800,
2741    AW_NO_COLOR
2742};
2743static AW_rgb *fcolors       = colors_def;
2744static AW_rgb *dcolors       = colors_def;
2745static long    dcolors_count = ARRAY_ELEMS(colors_def);
2746
2747class fake_AW_GC : public AW_GC {
2748    virtual void wm_set_foreground_color(AW_rgb /*col*/) OVERRIDE {  }
2749    virtual void wm_set_function(AW_function /*mode*/) OVERRIDE { td_assert(0); }
2750    virtual void wm_set_lineattributes(short /*lwidth*/, AW_linestyle /*lstyle*/) OVERRIDE {}
2751    virtual void wm_set_font(AW_font /*font_nr*/, int size, int */*found_size*/) OVERRIDE {
2752        unsigned int i;
2753        for (i = AW_FONTINFO_CHAR_ASCII_MIN; i <= AW_FONTINFO_CHAR_ASCII_MAX; i++) {
2754            set_char_size(i, size, 0, size-2); // good fake size for Courier 8pt
2755        }
2756    }
2757public:
2758    fake_AW_GC(AW_common *common_) : AW_GC(common_) {}
2759    virtual int get_available_fontsizes(AW_font /*font_nr*/, int */*available_sizes*/) const OVERRIDE {
2760        td_assert(0);
2761        return 0;
2762    }
2763};
2764
2765struct fake_AW_common : public AW_common {
2766    fake_AW_common()
2767        : AW_common(fcolors, dcolors, dcolors_count)
2768    {
2769        for (int gc = 0; gc < dcolors_count-AW_STD_COLOR_IDX_MAX; ++gc) { // gcs used in this example
2770            new_gc(gc);
2771            AW_GC *gcm = map_mod_gc(gc);
2772            gcm->set_line_attributes(1, AW_SOLID);
2773            gcm->set_function(AW_COPY);
2774            gcm->set_font(12, 8, NULL); // 12 is Courier (use monospaced here, cause font limits are faked)
2775
2776            gcm->set_fg_color(colors_def[gc+AW_STD_COLOR_IDX_MAX]);
2777        }
2778    }
2779    virtual ~fake_AW_common() OVERRIDE {}
2780
2781    virtual AW_GC *create_gc() {
2782        return new fake_AW_GC(this);
2783    }
2784};
2785
2786class fake_AWT_graphic_tree : public AWT_graphic_tree {
2787    int var_mode;
2788
2789    virtual void read_tree_settings() OVERRIDE {
2790        scaled_branch_distance = 1.0; // not final value!
2791        // var_mode is in range [0..3]
2792        // it is used to vary tree settings such that many different combinations get tested
2793        grey_level             = 20*.01;
2794        baselinewidth          = (var_mode == 3)+1;
2795        show_brackets          = (var_mode != 2);
2796        show_circle            = var_mode%3;
2797        use_ellipse            = var_mode%2;
2798        circle_zoom_factor     = 1.3;
2799        circle_max_size        = 1.5;
2800        bootstrap_min          = 0;
2801    }
2802
2803public:
2804    fake_AWT_graphic_tree(GBDATA *gbmain, const char *selected_species)
2805        : AWT_graphic_tree(NULL, gbmain, fake_AD_map_viewer_cb),
2806          var_mode(0)
2807    {
2808        species_name = strdup(selected_species);
2809    }
2810
2811    void set_var_mode(int mode) { var_mode = mode; }
2812    void test_show_tree(AW_device *device) { show(device); }
2813
2814    void test_print_tree(AW_device_print *print_device, AP_tree_display_type type, bool show_handles) {
2815        const int      SCREENSIZE = 541; // use a prime as screensize to reduce rounding errors
2816        AW_device_size size_device(print_device->get_common());
2817
2818        size_device.reset();
2819        size_device.zoom(1.0);
2820        size_device.set_filter(AW_SIZE|AW_SIZE_UNSCALED);
2821        test_show_tree(&size_device);
2822
2823        Rectangle drawn = size_device.get_size_information();
2824
2825        td_assert(drawn.surface() >= 0.0);
2826
2827        double zoomx = SCREENSIZE/drawn.width();
2828        double zoomy = SCREENSIZE/drawn.height();
2829        double zoom  = 0.0;
2830
2831        switch (type) {
2832            case AP_LIST_SIMPLE:
2833            case AP_TREE_RADIAL:
2834                zoom = std::max(zoomx, zoomy);
2835                break;
2836
2837            case AP_TREE_NORMAL:
2838            case AP_TREE_IRS:
2839                zoom = zoomx;
2840                break;
2841
2842            case AP_LIST_NDS:
2843                zoom = 1.0;
2844                break;
2845        }
2846
2847        if (!nearlyEqual(zoom, 1.0)) {
2848            // recalculate size
2849            size_device.restart_tracking();
2850            size_device.reset();
2851            size_device.zoom(zoom);
2852            size_device.set_filter(AW_SIZE|AW_SIZE_UNSCALED);
2853            test_show_tree(&size_device);
2854        }
2855
2856        drawn = size_device.get_size_information();
2857
2858        const AW_borders& text_overlap = size_device.get_unscaleable_overlap();
2859        Rectangle         drawn_text   = size_device.get_size_information_inclusive_text();
2860
2861        int            EXTRA = SCREENSIZE*0.05;
2862        AW_screen_area clipping;
2863
2864        clipping.l = 0; clipping.r = drawn.width()+text_overlap.l+text_overlap.r + 2*EXTRA;
2865        clipping.t = 0; clipping.b = drawn.height()+text_overlap.t+text_overlap.b + 2*EXTRA;
2866
2867        print_device->get_common()->set_screen(clipping);
2868        print_device->set_filter(AW_PRINTER|(show_handles ? AW_PRINTER_EXT : 0));
2869        print_device->reset();
2870
2871        print_device->zoom(zoom);
2872
2873        Rectangle drawn_world      = print_device->rtransform(drawn);
2874        Rectangle drawn_text_world = print_device->rtransform(drawn_text);
2875
2876        Vector extra_shift  = Vector(EXTRA, EXTRA);
2877        Vector corner_shift = -Vector(drawn.upper_left_corner());
2878        Vector text_shift = Vector(text_overlap.l, text_overlap.t);
2879
2880        Vector offset(extra_shift+corner_shift+text_shift);
2881        print_device->set_offset(offset/(zoom*zoom)); // dont really understand this, but it does the right shift
2882
2883        test_show_tree(print_device);
2884        print_device->box(AWT_GC_CURSOR, false, drawn_world);
2885        print_device->box(AWT_GC_GROUPS, false, drawn_text_world);
2886    }
2887};
2888
2889void fake_AW_init_color_groups();
2890void AW_init_color_groups(AW_root *awr, AW_default def);
2891
2892
2893void TEST_treeDisplay() {
2894    GB_shell  shell;
2895    GBDATA   *gb_main = GB_open("../../demo.arb", "r");
2896
2897    fake_AWT_graphic_tree agt(gb_main, "OctSprin");
2898    fake_AW_common        fake_common;
2899
2900    AW_device_print print_dev(&fake_common);
2901    AW_init_color_group_defaults(NULL);
2902    fake_AW_init_color_groups();
2903
2904    agt.init(new AP_TreeNodeFactory, new AliView(gb_main), NULL, true, false);
2905
2906    {
2907        GB_transaction ta(gb_main);
2908        ASSERT_RESULT(const char *, NULL, agt.load(NULL, "tree_test", 0, 0));
2909    }
2910
2911    const char *spoolnameof[] = {
2912        "dendro", 
2913        "radial",
2914        "irs", 
2915        "nds",
2916        NULL, // "simple", (too simple, need no test)
2917    };
2918
2919    for (int show_handles = 0; show_handles <= 1; ++show_handles) {
2920        for (int color = 0; color <= 1; ++color) {
2921            print_dev.set_color_mode(color);
2922            // for (int itype = AP_TREE_NORMAL; itype <= AP_LIST_SIMPLE; ++itype) {
2923            for (int itype = AP_LIST_SIMPLE; itype >= AP_TREE_NORMAL; --itype) {
2924                AP_tree_display_type type = AP_tree_display_type(itype);
2925                if (spoolnameof[type]) {
2926                    char *spool_name     = GBS_global_string_copy("display/%s_%c%c", spoolnameof[type], "MC"[color], "NH"[show_handles]);
2927                    char *spool_file     = GBS_global_string_copy("%s_curr.fig", spool_name);
2928                    char *spool_expected = GBS_global_string_copy("%s.fig", spool_name);
2929
2930
2931// #define TEST_AUTO_UPDATE // dont test, instead update expected results
2932                   
2933                    agt.set_tree_type(type, NULL);
2934
2935#if defined(TEST_AUTO_UPDATE)
2936#warning TEST_AUTO_UPDATE is active (non-default)
2937                    TEST_EXPECT_NO_ERROR(print_dev.open(spool_expected));
2938#else
2939                    TEST_EXPECT_NO_ERROR(print_dev.open(spool_file));
2940#endif
2941
2942                    {
2943                        GB_transaction ta(gb_main);
2944                        agt.set_var_mode(show_handles+2*color);
2945                        agt.test_print_tree(&print_dev, type, show_handles);
2946                    }
2947
2948                    print_dev.close();
2949
2950#if !defined(TEST_AUTO_UPDATE)
2951                    // if (strcmp(spool_expected, "display/irs_CH.fig") == 0) {
2952                        TEST_EXPECT_TEXTFILES_EQUAL(spool_expected, spool_file);
2953                    // }
2954                    TEST_EXPECT_ZERO_OR_SHOW_ERRNO(unlink(spool_file));
2955#endif
2956                    free(spool_expected);
2957                    free(spool_file);
2958                    free(spool_name);
2959                }
2960            }
2961        }
2962    }
2963
2964    GB_close(gb_main);
2965}
2966
2967#endif // UNIT_TESTS
Note: See TracBrowser for help on using the repository browser.