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