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