source: tags/arb-6.0/SL/TREEDISP/TreeDisplay.cxx

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