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