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

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