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