1 #include "treerecs_draw.h"
2
3 #include <sys/stat.h>
4
5 #include <FL/fl_utf8.h>
6
7 #if __APPLE_CC__
8 #include <xlocale.h>
9 #endif
10 #include <iostream>
11 #include <fstream>
12 #include <cstring>
13
14 using std::string;
15
16 // Extern definitions
17 extern char *argname(int argc, char *argv[], const char *arg);
18 extern float argval(int argc, char *argv[], const char *arg, float defval);
19 extern bool isarg(int argc, char *argv[], const char *arg);
20 extern void interrupt_callback(Fl_Widget *ob, void *data);
21 extern int run_external_prog_in_pseudoterm(char *cmd, const char *dialogfname, const char *label);
22 extern char *create_tmp_filename(void);
23 extern void delete_tmp_filename(const char *base_fname);
24
25 int treerecs_run_interrupted;
26
27 // Directory separator
28 #ifdef WIN32
29 static const char preferred_separator = '\\';
30 #else
31 static const char preferred_separator = '/';
32 #endif
33
34 // Static variable
35 static string tmp_file_path;
36 static string output_file_canonical_path;
37 static string output_dir;
38
39 /// Delete tmp files
delete_tmp_files(Fl_Widget * widget)40 void delete_tmp_files(Fl_Widget* widget) {
41 delete_tmp_filename(output_file_canonical_path.data());
42 fl_rmdir(output_dir.data());
43 delete_tmp_filename(tmp_file_path.data());
44 widget->hide();
45 }
46
47 /// Check file
is_regular_file(const char * path)48 bool is_regular_file(const char* path) {
49 struct stat buf;
50 fl_stat(path, &buf);
51 return S_ISREG(buf.st_mode);
52 }
53
update_treerecs_prog_path(const char * str)54 void update_treerecs_prog_path(const char* str) {
55 if(str) {
56 if(not is_regular_file(str)) {
57 printf("Error: impossible to get Treerecs in %s\n", str);
58 exit(1);
59 }
60 treerecs_prog_path[0] = '\0';
61 strcat(treerecs_prog_path, str);
62 }
63 };
64
create_treerecs_command_parameters(const string & genetree_path,const char * speciestree_path,const bool genenames_mapping,const char * genenames_separator,const bool speciesnames_prefix_position,const char * smap_filename,const char * duplication_cost,const char * loss_cost,const char * threshold,const string & output_dir,const bool full_reconciliation,const bool find_best_root)65 string create_treerecs_command_parameters(
66 const string& genetree_path
67 , const char* speciestree_path
68 , const bool genenames_mapping
69 , const char* genenames_separator
70 , const bool speciesnames_prefix_position
71 , const char* smap_filename
72 , const char* duplication_cost
73 , const char* loss_cost
74 , const char* threshold
75 , const string& output_dir
76 , const bool full_reconciliation
77 , const bool find_best_root
78 ) {
79 string treerecs_command;
80
81 // LOAD DATA
82 // Gene trees
83 treerecs_command += " -g ";
84 treerecs_command += genetree_path;
85
86 // Species tree
87 treerecs_command += " -s ";
88 treerecs_command += speciestree_path;
89
90 // Mapping
91 if (genenames_mapping) {
92 if(genenames_separator != NULL and strcmp(genenames_separator, "") != 0) {
93 treerecs_command += " --sep ";
94 treerecs_command += genenames_separator;
95 }
96
97 treerecs_command += " --prefix ";
98
99 if (speciesnames_prefix_position) {
100 treerecs_command += "yes ";
101 } else {
102 treerecs_command += "no ";
103 }
104
105 } else if (smap_filename != NULL and strcmp(smap_filename, "") != 0) {
106 treerecs_command += " --smap ";
107 treerecs_command += smap_filename;
108 }
109
110 // Treerecs settings
111 // Costs
112 if(duplication_cost != NULL and strcmp(duplication_cost, "") != 0) {
113 treerecs_command += " --dupcost ";
114 treerecs_command += duplication_cost;
115 }
116
117 if(loss_cost != NULL and strcmp(loss_cost, "") != 0 ) {
118 treerecs_command += " --losscost ";
119 treerecs_command += loss_cost;
120 }
121
122 // Branch support threshold
123 if(threshold != NULL and strcmp(threshold, "") != 0) {
124 treerecs_command += " --threshold ";
125 treerecs_command += threshold;
126 }
127
128 // Output configuration
129 treerecs_command += " --outdir ";
130 treerecs_command += output_dir;
131 treerecs_command += " -f";
132
133 if(full_reconciliation) {
134 treerecs_command += " --output-format recphyloxml:svg ";
135 } else {
136 treerecs_command += " --output-without-description ";
137 }
138
139 // Optional commands
140 if (find_best_root) {
141 treerecs_command += " --reroot ";
142 }
143
144 treerecs_command += " --verbose";
145
146 return treerecs_command;
147 }
148
149 /// Create and open Treerecs window dialog and launch treerecs.
treerecs_dialog(Fl_Widget * view,void * data)150 void treerecs_dialog(Fl_Widget *view, void* data)
151 {
152 static int first = TRUE;
153
154 // All Fl_Widgets
155 static Fl_Window *w;
156 static Fl_File_Input* speciestree_file;
157 static Fl_Button *speciestree_file_button;
158 static Fl_Float_Input *contraction_threshold;
159 static Fl_Float_Input *duplication_cost;
160 static Fl_Float_Input *loss_cost;
161 static Fl_Box *box;
162 static Fl_Check_Button *auto_mapping;
163 static Fl_Round_Button *genenames_mapping;
164 static Fl_Round_Button *file_mapping;
165 static Fl_Check_Button *find_best_root;
166
167 static Fl_Input* genenames_mapping_separator;
168 static Fl_Check_Button* genenames_mapping_species_in_prefix_position;
169 static Fl_File_Input* file_mapping_file;
170 static Fl_Button *file_mapping_file_button;
171
172 static Fl_Round_Button *rearrangment_mode;
173 static Fl_Round_Button *reconciliation_mode;
174
175 static Fl_Button *interrupt;
176 static Fl_Return_Button *go;
177 int started;
178
179 // Get current directory path.
180 char cwd[1024];
181 if (getcwd(cwd, sizeof(cwd)) == NULL) {
182 perror("getcwd() error");
183 }
184 string current_working_directory = cwd;
185
186 if(first) {
187 if(strlen(treerecs_prog_path) == 0) {
188 fl_alert("Could no find Treerecs in neither\n"
189 "seaview directory nor PATH");
190 return;
191 }
192
193 first = FALSE;
194
195 int x_max = 300;
196 int y_max = 400;
197
198 int widget_space = 3;
199
200 w = new Fl_Window(x_max, y_max);
201 w->label("Treerecs configuration");
202 w->set_modal();
203
204 // Treerecs global settings
205 Fl_Group* treerecs_setting_group = new Fl_Group(0, 20, w->w(), (25 + widget_space) * 5.5, "Treerecs global settings");
206 treerecs_setting_group->box(FL_ROUNDED_BOX);
207
208 int fl_float_input_width = w->w()/5;
209 int output_format_content_x = fl_float_input_width * 3.75 - 2 * widget_space;
210 int output_format_content_y = 20 ;
211
212 // Speciestree filename input.
213 speciestree_file = new Fl_File_Input(1.25*w->w()/4, output_format_content_y + widget_space, 1.75*w->w()/4 - widget_space, 20 * 1.5, "Species tree");
214 speciestree_file->value(current_working_directory.c_str());
215 speciestree_file->align(FL_ALIGN_LEFT);
216 speciestree_file->callback(load_file_callback);
217
218 // Speciestree filename input button.
219 speciestree_file_button = new Fl_Button(speciestree_file->x() + speciestree_file->w() + widget_space, speciestree_file->y(), speciestree_file->h() * 2, speciestree_file->h(), "Select");
220 speciestree_file_button->align(FL_ALIGN_INSIDE);
221 speciestree_file_button->callback(load_file_button_callback, speciestree_file);
222
223 // Branch support threshold input field.
224 contraction_threshold = new Fl_Float_Input(output_format_content_x + widget_space, speciestree_file->y() + speciestree_file->h() + widget_space, fl_float_input_width, 25, "Branch support threshold");
225 contraction_threshold->align(FL_ALIGN_LEFT);
226 contraction_threshold->value("-1.0");
227
228 // Duplication cost input.
229 duplication_cost = new Fl_Float_Input(contraction_threshold->x(), contraction_threshold->y() + contraction_threshold->h() + widget_space, fl_float_input_width, 25, "Duplication cost");
230 duplication_cost->align(FL_ALIGN_LEFT);
231 duplication_cost->value("2.0");
232
233 // Loss cost input.
234 loss_cost = new Fl_Float_Input(duplication_cost->x(), duplication_cost->y() + duplication_cost->h() + widget_space, fl_float_input_width, 25, "Loss cost");
235 loss_cost->align(FL_ALIGN_LEFT);
236 loss_cost->value("1.0");
237
238 // Find best root check button.
239 find_best_root = new Fl_Check_Button(loss_cost->x(), loss_cost->y() + loss_cost->h() + widget_space, w->w()/2.0, 25, "Find best root");
240 find_best_root->align(FL_ALIGN_LEFT);
241 find_best_root->set();
242
243 treerecs_setting_group->end();
244
245 // Mapping settings
246 Fl_Group *mapping_group = new Fl_Group(treerecs_setting_group->x(), treerecs_setting_group->y() + treerecs_setting_group->h() + 20, w->w(), 90, "Gene <> Species mapping method");
247 mapping_group->box(FL_ROUNDED_BOX);
248 mapping_group->align(FL_ALIGN_TOP|FL_ALIGN_CENTER);
249
250 int mapping_content_x = treerecs_setting_group->x();
251 int mapping_content_y = treerecs_setting_group->y() + treerecs_setting_group->h() + 20 + widget_space;
252 int trinome_space = widget_space;
253 int trinome_width = (int)((double)w->w()/3.0) - 2*(trinome_space);
254
255 // Genenames mapping has several options below.
256 genenames_mapping = new Fl_Round_Button(mapping_content_x + trinome_space, mapping_content_y + widget_space, trinome_width * 1.75, 20, "Use gene names");
257 genenames_mapping->type(FL_RADIO_BUTTON);
258 genenames_mapping->set();
259
260 // File mapping.
261 file_mapping = new Fl_Round_Button(genenames_mapping->x() + genenames_mapping->w() + trinome_space, genenames_mapping->y(), genenames_mapping->w(), genenames_mapping->h(), "Use file");
262 file_mapping->type(FL_RADIO_BUTTON);
263
264 // Automatic mapping using genenames.
265 auto_mapping = new Fl_Check_Button(genenames_mapping->x(), genenames_mapping->y() + genenames_mapping->h() + widget_space, trinome_width, 20, "Auto");
266 if(genenames_mapping->value()) auto_mapping->activate();
267 else auto_mapping->deactivate();
268
269 // Character separator input for mapping using gene names.
270 genenames_mapping_separator = new Fl_Input(auto_mapping->x() + trinome_width + trinome_space + 30, auto_mapping->y(), 20, 20, "Separator");
271 genenames_mapping_separator->align(FL_ALIGN_LEFT);
272 genenames_mapping_separator->value("_");
273 if(genenames_mapping->value() and not auto_mapping->value())
274 genenames_mapping_separator->deactivate();
275 else
276 genenames_mapping_separator->activate();
277
278 // Species name position in gene name for mapping using gene names.
279 genenames_mapping_species_in_prefix_position = new Fl_Check_Button(genenames_mapping_separator->x() + trinome_width*1.5 - 2*trinome_space, genenames_mapping_separator->y(), trinome_width, genenames_mapping_separator->h(), "Species before");
280 genenames_mapping_species_in_prefix_position->align(FL_ALIGN_LEFT);
281 genenames_mapping_species_in_prefix_position->set();
282 genenames_mapping_species_in_prefix_position->deactivate();
283 if(genenames_mapping->value() and not auto_mapping->value())
284 genenames_mapping_species_in_prefix_position->deactivate();
285 else
286 genenames_mapping_species_in_prefix_position->activate();
287
288 // Map file input for mapping using file.
289 file_mapping_file = new Fl_File_Input(1.25*w->w()/4, genenames_mapping_species_in_prefix_position->y() + genenames_mapping_species_in_prefix_position->h() + widget_space, 1.75*w->w()/4 - widget_space, 20 * 1.5, "Map file");
290 file_mapping_file->align(FL_ALIGN_LEFT);
291 file_mapping_file->value(current_working_directory.c_str());
292 file_mapping_file->callback(load_file_callback);
293
294 // Map file input button for mapping using file.
295 file_mapping_file_button = new Fl_Button(file_mapping_file->x() + file_mapping_file->w() + widget_space, file_mapping_file->y(), file_mapping_file->h()*2, file_mapping_file->h(), "Select");
296 file_mapping_file_button->align(FL_ALIGN_INSIDE);
297 file_mapping_file_button->callback(load_file_button_callback, file_mapping_file);
298
299 if(not file_mapping->value()) {
300 file_mapping_file->deactivate();
301 file_mapping_file_button->deactivate();
302 }
303 else {
304 file_mapping_file->activate();
305 file_mapping_file_button->activate();
306 }
307
308 auto_mapping->set();
309 mapping_group->end();
310
311 // Finally the output settings : choose between rearrangement and reconciliation.
312 Fl_Group *output_group = new Fl_Group(mapping_group->x(), mapping_group->y() + mapping_group->h() + 20, w->w(), 30, "Output tree");
313 output_group->box(FL_ROUNDED_BOX);
314 output_group->align(FL_ALIGN_TOP|FL_ALIGN_CENTER);
315
316 rearrangment_mode = new Fl_Round_Button(mapping_group->x() + widget_space + w->w()/8, mapping_group->y() + mapping_group->h() + 20 + widget_space, w->w()/3, 20, "Gene tree");
317 rearrangment_mode->type(FL_RADIO_BUTTON);
318
319 reconciliation_mode = new Fl_Round_Button(rearrangment_mode->x() + rearrangment_mode->w() + 2*widget_space, rearrangment_mode->y(), w->w()/3, 20, "Full reconciliation");
320 reconciliation_mode->type(FL_RADIO_BUTTON);
321 reconciliation_mode->set();
322
323 output_group->end();
324
325 box = new Fl_Box(output_group->x(), output_group->y() + output_group->h() + widget_space, output_group->w(), 25);
326 box->label("Note: please configure Treerecs.");
327
328 interrupt = new Fl_Button(3, w->h() - 25, 70, 20, "");
329 interrupt->callback(interrupt_callback, &treerecs_run_interrupted);
330 go = new Fl_Return_Button(w->w() - 70 - 3, interrupt->y() , 70, 20, "Go");
331 go->callback(interrupt_callback, &started);
332 w->end();
333 w->callback(interrupt_callback, &treerecs_run_interrupted);
334 }
335
336 interrupt->label("Cancel");
337
338 started = treerecs_run_interrupted = 0;
339 go->show();
340 go->deactivate();
341 w->show();
342
343 // Run, while treerecs has not been launched or be interrupted.
344 while(!started && !treerecs_run_interrupted) {
345 Fl_Widget *o = Fl::readqueue();
346
347 if (!o) Fl::wait();
348 else if(o == auto_mapping|| o == file_mapping || o == genenames_mapping) {
349 if( file_mapping->value() ) {
350 file_mapping_file->activate();
351 file_mapping_file_button->activate();
352 } else {
353 file_mapping_file->deactivate();
354 file_mapping_file_button->deactivate();
355 }
356
357 if( genenames_mapping->value() ) {
358 auto_mapping->activate();
359 genenames_mapping_separator->activate();
360 genenames_mapping_species_in_prefix_position->activate();
361 } else {
362 auto_mapping->deactivate();
363 genenames_mapping_separator->deactivate();
364 genenames_mapping_species_in_prefix_position->deactivate();
365 }
366
367 if( auto_mapping->value() ){
368 genenames_mapping_separator->deactivate();
369 genenames_mapping_species_in_prefix_position->deactivate();
370 } else if(genenames_mapping->value()) {
371 genenames_mapping_separator->activate();
372 genenames_mapping_species_in_prefix_position->activate();
373 }
374 }
375
376 // Check form consistency.
377 // NOTE: the order is important: the error message will be that
378 // corresponding to the first detected problem
379 // - Check species tree file
380 if(not speciestree_file->value() or
381 not is_regular_file(speciestree_file->value())) {
382 go->deactivate();
383 const char* error_msg = "Invalid species tree (mandatory).";
384 if (strcmp(box->label(), error_msg)) box->label(error_msg);
385 } else
386 // - Check contraction threshold definition
387 // (if active and value is either absent or invalid)
388 if(contraction_threshold->active() and
389 (not contraction_threshold->value() or
390 not strcmp(contraction_threshold->value(), ""))) {
391 go->deactivate();
392 const char* error_msg = "Invalid branch support threshold.";
393 if (strcmp(box->label(), error_msg)) box->label(error_msg);
394 } else
395 // - Check mapping file
396 // (if "Use file" is checked and either no file has been selected or it
397 // is invalid)
398 if (file_mapping->value() and
399 (not file_mapping_file->value() or
400 not is_regular_file(file_mapping_file->value()))) {
401 go->deactivate();
402 const char* error_msg = "Invalid mapping file.";
403 if (strcmp(box->label(), error_msg)) box->label(error_msg);
404 } else
405 // - Everything is ok, user can start Treerecs.
406 {
407 go->activate();
408 box->label("");
409 }
410 }
411
412 if(!treerecs_run_interrupted) {
413 // Load current gene tree and create temporary file which contains this one.
414 FD_nj_plot *fd_nj_plot = (FD_nj_plot*)view->user_data();
415
416 char* current_tree = fd_nj_plot->current_tree;
417
418 // Run treerecs. Before we need to build the command line to launch Treerecs with options.
419 go->hide();
420 Fl::flush();
421
422 // Set input and output file names and paths
423 tmp_file_path = create_tmp_filename();
424 string input_genetree_path = tmp_file_path + "_genetree";
425 string input_speciestree_path = tmp_file_path + "_speciestree";
426 output_dir = tmp_file_path + "_treerecs_output";
427
428 /*auto*/size_t pos = input_genetree_path.find_last_of(preferred_separator);
429 string tmp_fname = (pos != string::npos) ?
430 input_genetree_path.substr(pos+1) :
431 input_genetree_path;
432 output_file_canonical_path =
433 output_dir + preferred_separator + tmp_fname + "_recs";
434
435 // Prepare gene tree and species tree for Treerecs
436 create_temp_tree_file(input_genetree_path, current_tree);
437 copyfile(input_speciestree_path.data(), speciestree_file->value());
438
439 // Create our Treerecs command.
440 string treerecs_command =
441 treerecs_prog_path +
442 create_treerecs_command_parameters(
443 input_genetree_path.data(), input_speciestree_path.data(),
444 genenames_mapping->value() and
445 not auto_mapping->value(),
446 genenames_mapping_separator->value(),
447 genenames_mapping_species_in_prefix_position->value(),
448 (file_mapping->value()
449 ? file_mapping_file->value() : ""),
450 duplication_cost->value(), loss_cost->value(),
451 contraction_threshold->value(),
452 output_dir,
453 reconciliation_mode->value(),
454 find_best_root->value());
455
456 w->hide();
457
458 int status =
459 run_external_prog_in_pseudoterm((char*) treerecs_command.c_str(),
460 NULL,
461 "Treerecs");
462
463 // TODO<dpa> this test is poor, see status ?
464 // If the "main" output file exists, Treerecs success.
465 string main_output_file_path = output_file_canonical_path +
466 (reconciliation_mode->value()
467 ? ".recphylo.xml" : ".nwk");
468
469 if(is_regular_file(main_output_file_path.c_str())) {
470 FILE *res = fl_fopen(main_output_file_path.c_str(), "r");
471 fseek(res, 0, SEEK_END);
472 long l = ftell(res);
473 fseek(res, 0, SEEK_SET);
474 char *resulting_tree = (char *) malloc(l + 1);
475 char *p = resulting_tree;
476 while (l-- > 0) {
477 char c = fgetc(res);
478 *(p++) = c;
479 }
480 *p = 0;
481 fclose(res);
482
483 if(not reconciliation_mode->value()) {
484 treedraw(resulting_tree, (SEA_VIEW *) view->user_data(), main_output_file_path.c_str(), FALSE);
485 } else {
486 string svg_file_path = output_file_canonical_path + ".svg";
487
488 if(not is_regular_file(svg_file_path.c_str())) {
489 perror("Invalid svg filename.\n");
490 }
491
492 Fl_Window* recphylowml_window =
493 svgdraw(svg_file_path,
494 main_output_file_path,
495 "Full-reconciliation view");
496 }
497 } else {
498 fl_message("Error with Treerecs: invalid data.\n");
499 }
500 }
501 w->hide();
502 }
503
create_temp_tree_file(const string & path,const char * tree)504 void create_temp_tree_file(const string& path, const char* tree) {
505 int tree_begin = 0;
506 if(tree[tree_begin] == '[') {
507 while (tree[tree_begin] != ']') {
508 tree_begin++;
509 }
510 tree_begin++;
511 }
512
513 int tree_end = tree_begin;
514 while(tree[tree_end] != ';'){
515 tree_end++;
516 }
517 tree_end++;
518
519 char tmp_tree[tree_end - tree_begin + 1];
520 int i = 0;
521 while(tree_begin + i < tree_end){
522 tmp_tree[i] = tree[tree_begin + i];
523 i++;
524 }
525 tmp_tree[i] = '\0';
526
527 FILE* temp = fl_fopen(path.c_str(), "w");
528 fwrite(tmp_tree, 1, strlen(tmp_tree), temp);
529 fputs("\n", temp);
530 fclose(temp);
531 }
532
533 /*!
534 * @brief Handle svg image in a widget as a Fl_Box.
535 */
536 class Fl_SVG_Box : public Fl_Widget {
537 typedef struct {
538 /*!
539 * @struct SVGText
540 * @brief Contains all info of a text (position, size and content).
541 * @details Fl_SVG_Image cannot use text tags, we need to manage these elements after Fl_SVG_Image creation.
542 */
543 const char* content;
544 double size;
545 double x;
546 double y;
547 } SVGText;
548
549 /*!
550 * @brief Read SVG file and store each SVG text tags in a vector of SVGText.
551 * @param filename Name of the SVG file.
552 * @return
553 */
extract_texts(const char * filename)554 void extract_texts(const char* filename){
555 // First of all get the total number of lines which contains "<text" str.
556 FILE* svgfile = fl_fopen(filename, "r");
557
558 char line[1024];
559 svgtexts_size = 0;
560
561 while(fgets(line, sizeof(line), svgfile)) {
562 char* pch = strstr(line, "<text");
563 if(pch != NULL) svgtexts_size++;
564 }
565
566 fseek(svgfile, 0, SEEK_SET);
567
568 svgtexts = (SVGText*)malloc(sizeof(SVGText)*svgtexts_size);
569
570 line[0] = '\0';
571 unsigned int i = 0;
572 while(fgets(line, sizeof(line), svgfile)) { // I changed this, see below
573 //unsigned long pos = line.find("<text");
574 char* pch = strstr(line, "<text");
575 // We assume that we have only one <text> tag per line.
576 if (pch != NULL) {
577 while(*pch != ' ' and *pch != '\0') ++pch;
578 // Create SVGText by extracting attributes and their value.
579 SVGText svgtext;
580 while(*pch != '\0' and *pch != '>') {
581 while(*pch == ' ' and *pch != '\0') ++pch;
582 if(*pch == '>') break;
583 char attribute_name[1024];
584 attribute_name[0] = '\0';
585 char* attribute_name_pch = attribute_name;
586
587 while(*pch != '=' and *pch != ' ' and *pch != '\0') {
588 *attribute_name_pch = *pch;
589 ++attribute_name_pch;
590 ++pch;
591 }
592 *attribute_name_pch = '\0';
593
594 //printf("Find attribute %s\n", attribute_name);
595
596
597 char attribute_value[1024];
598 attribute_value[0] = '\0';
599 char* attribute_value_pch = attribute_value;
600
601 while(*pch != '\"' and *pch != '\0') ++pch;
602 ++pch;
603 while(*pch != '\"' and *pch != '\0'){
604 *attribute_value_pch = *pch;
605 ++attribute_value_pch;
606 ++pch;
607 }
608 *attribute_value_pch = '\0';
609
610 if(*pch == '\0') break;
611 ++pch;
612
613 //printf("Find value %s\n", attribute_value);
614
615 if(strcmp(attribute_name, "x") == 0)
616 svgtext.x = atof(attribute_value);
617 else if(strcmp(attribute_name, "y") == 0)
618 svgtext.y = atof(attribute_value);
619 else if(strcmp(attribute_name, "font-size") == 0)
620 svgtext.size = atof(attribute_value);
621 else {};
622 }
623 ++pch; // be after the '>' character
624 char* text_content = (char*)malloc(sizeof(char) * 1024);
625 char* text_content_pch = text_content;
626 while(*pch != '<' and *pch != '\0'){
627 *text_content_pch = *pch;
628 ++text_content_pch;
629 ++pch;
630 }
631 *text_content_pch = '\0';
632 //printf("Text content found = %s\n", text_content);
633 svgtext.content = text_content;
634 //printf("Extracted : %s\n", text_content);
635 assert(i < svgtexts_size);
636 svgtexts[i] = svgtext;
637 i++;
638 }
639 }
640
641 fclose(svgfile);
642 }
643
644 /*!
645 * @brief Draw the Fl_SVG_Box.
646 */
draw(void)647 FL_EXPORT void draw(void) {
648
649 double transformation_ratio_x = (double)svg_image->w()/original_svg_dim_x;
650 double transformation_ratio_y = (double)svg_image->h()/original_svg_dim_y;
651
652 svg_image->draw(x(), y(), w(), h(), svg_shift_x, svg_shift_y);
653 fl_color(Fl_Color(FL_BLACK));
654
655 int i = 0;
656
657 while(i < svgtexts_size){
658 //printf("Place %s in (%d, %d)\n", svgtexts[i].content.c_str(), (int)(svgtexts[i].x * transformation_ratio_x),(int)(svgtexts[i].y * transformation_ratio_y) );
659 double text_pos_x = (x() - svg_shift_x) + (svgtexts[i].x * transformation_ratio_x);
660 double text_pos_y = (y() - svg_shift_y) + (svgtexts[i].y * transformation_ratio_y);
661
662 if(text_pos_x >= x() and text_pos_x <= x() + w()
663 and text_pos_y >= y() and text_pos_y <= y() + h()) {
664
665 Fl_Fontsize oldsize = fl_size();
666
667 Fl_Fontsize newsize = static_cast<Fl_Fontsize>(12);//computed_fontsize);
668 fl_font(fl_font(), newsize);
669
670 fl_draw(svgtexts[i].content, text_pos_x, text_pos_y);
671
672 fl_font(fl_font(), oldsize);
673 }
674 i++;
675 }
676
677 // We draw a rectangle to hide texts which are written beyond the limits
678 fl_rectf(x() + w(), y() - 12,
679 w(), h() + 12, Fl_Color(FL_BACKGROUND_COLOR));
680 };
681
handle(int)682 FL_EXPORT int handle(int) {return 0;};
683
684 public:
685
686 /// Original SVG image ratio.
original_ratio() const687 double original_ratio() const {return original_svg_dim_x/original_svg_dim_y; };
688
Fl_SVG_Box(int x,int y,int w,int h,const char * filename)689 FL_EXPORT Fl_SVG_Box(int x, int y, int w, int h, const char* filename) :
690 Fl_Widget(x, y, w, h), zoom(1), svg_shift_x(0), svg_shift_y(0), svgtexts_size(0) {
691
692 // Create a Fl_SVG_Image.
693 svg_image = new Fl_SVG_Image(filename);
694
695 // Check SVG read.
696 switch ( svg_image->fail() ) {
697 case Fl_Image::ERR_FILE_ACCESS:
698 // File couldn't load? show path + os error to user
699 fl_alert("%s: %s\n", filename, strerror(errno));
700 exit(EXIT_FAILURE);
701 case Fl_Image::ERR_FORMAT:
702 // Parsing error
703 fl_alert("%s: couldn't decode image\n", filename);
704 exit(EXIT_FAILURE);
705 }
706
707 original_svg_dim_x = svg_image->w();
708 original_svg_dim_y = svg_image->h();
709
710 extract_texts(filename); // Because of Fl_SVG_Image cannot read text tags, we need to extract them after.
711
712 if(original_ratio() < 1) {
713 svg_image->resize(w, w / original_ratio());
714 zoom = (w / original_ratio())/h;
715 }
716 else {
717 svg_image->resize(h * original_ratio(), h);
718 zoom = (h * original_ratio())/w;
719 }
720 //svg_image->proportional = false; // Be able to change shape of the Fl_SVG_Image.
721 }
722
~Fl_SVG_Box()723 ~Fl_SVG_Box() {
724 delete svg_image;
725 for(int i = 0; i < svgtexts_size ; i++) {
726 free((char*)svgtexts[i].content);
727 }
728
729 free(svgtexts);
730 };
731
732 /// SVG image (without text).
733 Fl_SVG_Image* svg_image;
734 /// svgtexts contains all infos to draw texts on the svg image.
735 SVGText* svgtexts;
736 unsigned int svgtexts_size;
737 double original_svg_dim_x;
738 double original_svg_dim_y;
739 double svg_shift_x;
740 double svg_shift_y;
741 double zoom;
742 };
743
744 /// Contains all data for the SVG viewer window. This class is in charge of
745 /// freeing/deleting svg window data.
746 class SVG_View_data {
747 public:
748 /// Widget which is drawing the SVG image.
749 Fl_SVG_Box* svg_box;
750 /// Zoom button.
751 Fl_Simple_Counter* zoom;
752 /// Scrollbar in x.
753 Fl_Scrollbar* bar_x;
754 /// Scrollbar in y.
755 Fl_Scrollbar* bar_y;
756 /// Window which contains all data.
757 Fl_Window* w;
758 /// Filenames of the SVG and RecPhyloXML sources.
759 const char* svg_fname;
760 const char* rpx_fname;
761 /// Total cost score of the reconciliation
762 float total_cost;
763 int duplication_number;
764 int loss_number;
765 const char* reconciliation_score_message;
766
SVG_View_data()767 SVG_View_data() :
768 svg_box(NULL)
769 , zoom(NULL)
770 , bar_x(NULL)
771 , bar_y(NULL)
772 , w(NULL)
773 , svg_fname(NULL)
774 , rpx_fname(NULL)
775 , reconciliation_score_message(NULL)
776 , total_cost(0.f)
777 , duplication_number(0)
778 , loss_number(0) {};
779
~SVG_View_data()780 ~SVG_View_data() {
781 if (svg_fname != NULL)
782 free((char*)svg_fname);
783 if (rpx_fname != NULL)
784 free((char*)rpx_fname);
785
786 if (reconciliation_score_message != NULL)
787 free((char*)reconciliation_score_message);
788 };
789
790 // Update values and redraw the window.
update()791 void update() {
792 double zoom_value = zoom->value();
793 svg_box->svg_image->resize(svg_box->w() * zoom_value, svg_box->h() * zoom_value);
794
795 bar_x->slider_size((float)svg_box->w()/svg_box->svg_image->w());
796
797 bar_y->slider_size((float)svg_box->h()/svg_box->svg_image->h());
798
799 w->redraw();
800 };
801 };
802
803 /// Zoom in SGV image.
svg_zoom_callback(Fl_Widget * obj,void * data)804 void svg_zoom_callback(Fl_Widget* obj, void* data) {
805 SVG_View_data* view_data = (SVG_View_data*)data;
806
807 view_data->update();
808 }
809
810 /// Scroll in x in SVG image.
svg_scrollbar_x_callback(Fl_Widget * obj,void * data)811 void svg_scrollbar_x_callback(Fl_Widget *obj, void *data) {
812 Fl_Scrollbar* scrollbar_widget = (Fl_Scrollbar*)obj;
813 double value = scrollbar_widget->value();
814
815 SVG_View_data* view_data = (SVG_View_data*)data;
816 Fl_SVG_Box *svg_box = view_data->svg_box;
817 svg_box->svg_shift_x = value/scrollbar_widget->maximum() * ((svg_box->svg_image->w()) - svg_box->w());
818
819 view_data->update();
820 }
821
822 /// Scroll in y in SVG image.
svg_scrollbar_y_callback(Fl_Widget * obj,void * data)823 void svg_scrollbar_y_callback(Fl_Widget *obj, void *data) {
824 Fl_Scrollbar* scrollbar_widget = (Fl_Scrollbar*)obj;
825 double value = scrollbar_widget->value();
826
827 SVG_View_data* view_data = (SVG_View_data*)data;
828 Fl_SVG_Box *svg_box = view_data->svg_box;
829 svg_box->svg_shift_y = value/scrollbar_widget->maximum() * (svg_box->svg_image->h() - svg_box->h());
830
831 view_data->update();
832 }
833
extract_costs_from_xml_description(const char * filename,float * cost,int * ndup,int * nlos)834 int extract_costs_from_xml_description(const char* filename,
835 float* cost, int* ndup, int* nlos) {
836 // Open file
837 std::ifstream xml_file(filename, std::ios::in);
838 /*try {
839 if (not xml_file.is_open()) {
840 throw std::invalid_argument(std::string("could not open file ") + filename);
841 }
842 } catch(std::exception const& e) {
843 std::cerr << "Error: " << e.what() << std::endl;
844 }*/
845 if (not xml_file.is_open()) {
846 fprintf(stderr, "Could not open file: %s\n", filename);
847 return 1;
848 }
849
850 // Read file (read entire file content into xml_descr
851 std::string xml_descr((std::istreambuf_iterator<char>(xml_file)), std::istreambuf_iterator<char>() );
852
853 // Locate and extract (in a substring str) our information of interest
854 const string motif("<!-- family");
855 /*auto*/size_t pos = xml_descr.find(motif) + motif.size();
856 string str =
857 xml_descr.substr(pos, xml_descr.find("-->", pos + motif.size()) - pos);
858
859 // Find positions of total cost, number of duplications and of losses
860 const string cost_str("total cost = ");
861 const string ndup_str("duplications = ");
862 const string nlos_str("losses = ");
863 /*auto*/size_t cost_pos = str.find(cost_str) + cost_str.size();
864 /*auto*/size_t ndup_pos = str.find(ndup_str) + ndup_str.size();
865 /*auto*/size_t nlos_pos = str.find(nlos_str) + nlos_str.size();
866
867 // Convert to corresponding types and assign values to out params
868 *cost = /*std::*/strtof(str.data() + cost_pos, NULL/*nullptr*/);
869 *ndup = /*std::*/strtol(str.data() + ndup_pos, NULL/*nullptr*/, 10);
870 *nlos = /*std::*/strtol(str.data() + nlos_pos, NULL/*nullptr*/, 10);
871
872 return 0;
873 }
874
875 /// Draw a window with a reconciled tree in SVG and its widgets to navigate or save files.
876 /// Edit/alloc variables in a SVG_View_data.
svgdraw(const string & svg_file_path,const string & data_file_path,const char * label)877 Fl_Window * svgdraw(const string& svg_file_path,
878 const string& data_file_path,
879 const char *label) {
880
881 // Window and svg init dimensions.
882 double window_width = 800;
883 double window_height = 600;
884 double svg_width = window_width - 50;
885 double svg_height = window_height - 80;
886
887 // SVG_View_data manages all widgets and update of the svg_image.
888 SVG_View_data* view_data = new SVG_View_data();
889 char* svg_fname_copy = (char*)malloc(sizeof(char) * svg_file_path.size() + 1);
890 strcpy(svg_fname_copy, svg_file_path.c_str());
891 view_data->svg_fname = (const char*)svg_fname_copy;
892 char* rpx_fname_copy = (char*)malloc(sizeof(char) * data_file_path.size() + 1);
893 strcpy(rpx_fname_copy, data_file_path.c_str());
894 view_data->rpx_fname = (const char*)rpx_fname_copy;
895
896 if ((extract_costs_from_xml_description(data_file_path.c_str(),
897 &view_data->total_cost,
898 &view_data->duplication_number,
899 &view_data->loss_number) != 0)) {
900 return NULL;
901 }
902
903 // Create window and its widgets
904 Fl_Window* w = new Fl_Window(window_width, window_height);
905 w->label(label);
906 w->color(Fl_Color(FL_BACKGROUND_COLOR));
907 view_data->w = w;
908 w->callback(delete_tmp_files);
909
910 // Menu bar
911 Fl_Menu_Bar *menu = new Fl_Menu_Bar(0, 0, window_width, 25);
912 menu->add("Save/SVG", FL_COMMAND+'s', save_svg_file_callback, view_data);
913 menu->add("Save/RecPhyloXML"
914 , FL_COMMAND+'x'
915 , save_recphyloxml_file_callback
916 , view_data);
917
918 // Add text box to print a summary of events in the reconciled tree.
919 Fl_Box* text_box = new Fl_Box(25, menu->y() + menu->h() + 10, svg_width/3, 20);
920 char* text_box_message = (char*)malloc(sizeof(char)*1024);
921 sprintf(
922 text_box_message
923 , "Total cost of the reconciliation = %.1f, duplications = %d, losses = %d"
924 , view_data->total_cost
925 , view_data->duplication_number
926 , view_data->loss_number
927 );
928 view_data->reconciliation_score_message = text_box_message;
929
930 text_box->align(FL_ALIGN_INSIDE|FL_ALIGN_LEFT);
931 text_box->label(view_data->reconciliation_score_message);
932
933 // Create SVG image (has no text)
934 Fl_SVG_Box* svg_box = new Fl_SVG_Box(25, 55, svg_width, svg_height,
935 svg_file_path.c_str());
936 view_data->svg_box = svg_box;
937
938 w->resizable(svg_box);
939
940 // Create an Fl_Simple_Counter to zoom into the SVG image.
941 Fl_Simple_Counter* zoom = new Fl_Simple_Counter(
942 svg_box->x() + svg_box->w() - 50, text_box->y(), 50, 20, "Zoom");
943 zoom->value(svg_box->zoom);
944 view_data->zoom = zoom;
945 zoom->callback(svg_zoom_callback, view_data);
946
947 // Scrollbar to navigate into the svg_image
948 Fl_Scrollbar* svg_box_scrollbar_x = new Fl_Scrollbar(
949 svg_box->x(), svg_box->y() + svg_box->h(), svg_box->w(), 10);
950 svg_box_scrollbar_x->slider_size((float)svg_box->w()/svg_box->svg_image->w());
951 svg_box_scrollbar_x->bounds(0, 100);
952 svg_box_scrollbar_x->type(FL_HORIZONTAL);
953 view_data->bar_x = svg_box_scrollbar_x;
954 svg_box_scrollbar_x->callback(svg_scrollbar_x_callback, view_data);
955
956 Fl_Scrollbar* svg_box_scrollbar_y = new Fl_Scrollbar(
957 svg_box->x() + svg_box->w(), svg_box->y(), 10, svg_box->h());
958 svg_box_scrollbar_y->slider_size((float)svg_box->h()/svg_box->svg_image->h());
959 svg_box_scrollbar_y->bounds(0, 100);
960 view_data->bar_y = svg_box_scrollbar_y;
961 svg_box_scrollbar_y->callback(svg_scrollbar_y_callback, view_data);
962
963 w->show();
964
965 w->end();
966
967 return w;
968 }
969
970 /// Open Treerecs dialog.
treerecs_callback(Fl_Widget * ob,void * data)971 void treerecs_callback(Fl_Widget *ob, void *data)
972 {
973 //SEA_VIEW *view = (SEA_VIEW *) ob->user_data();
974 treerecs_dialog(ob, data);
975 }
976
977 /// Open a windows with a file chooser.
load_file_callback(Fl_Widget * ob)978 void load_file_callback(Fl_Widget *ob)
979 {
980 Fl_File_Input* o = (Fl_File_Input*) ob;
981
982 //SEA_VIEW *view = (SEA_VIEW *) ob->user_data();
983 Fl_Native_File_Chooser* chooser = new Fl_Native_File_Chooser();
984 if (o->label())
985 chooser->title(o->label());
986 else
987 chooser->title("Choose a file");
988 chooser->type(Fl_Native_File_Chooser::BROWSE_FILE);
989 char* filename = run_and_close_native_file_chooser(chooser);
990 if(filename != NULL and strcmp(filename, "") != 0) {
991 (o)->value(filename);
992 }
993
994 }
995
996 /// Open a windows with a file chooser. Send value to adj.
load_file_button_callback(Fl_Widget * ob,void * adj)997 void load_file_button_callback(Fl_Widget *ob, void *adj)
998 {
999 //SEA_VIEW *view = (SEA_VIEW *) ob->user_data();
1000 load_file_callback ((Fl_File_Input*)adj);
1001 }
1002
1003 /// Create a file (to) which is a copy of an other (from).
copyfile(const char * to,const char * from)1004 int copyfile(const char *to, const char *from)
1005 {
1006 char ch;
1007 FILE* source = fl_fopen(from, "r");
1008 FILE* target = fl_fopen(to, "w");
1009
1010 while( ( ch = fgetc(source) ) != EOF )
1011 fputc(ch, target);
1012
1013 fclose(source);
1014 fclose(target);
1015
1016 return 0;
1017 }
1018
1019 /// Open and close a window to save a SVG file with SVG_View_data.
save_svg_file_callback(Fl_Widget * ob,void * data)1020 void save_svg_file_callback(Fl_Widget *ob, void *data) {
1021 SVG_View_data* svg_data = (SVG_View_data*)data;
1022
1023 Fl_Native_File_Chooser* chooser = new Fl_Native_File_Chooser(Fl_Native_File_Chooser::BROWSE_SAVE_FILE);
1024 chooser->title("Save file");
1025 chooser->options(Fl_Native_File_Chooser::USE_FILTER_EXT | chooser->options());
1026 chooser->filter("SVG files\t*.svg");
1027 const char* filename = run_and_close_native_file_chooser(chooser);
1028
1029 if(filename != NULL and strcmp(filename, "") != 0) {
1030 copyfile(filename, svg_data->svg_fname);
1031 }
1032 }
1033
1034 /// Open and close a window to save a RecPhyloXML file with SVG_View_data.
save_recphyloxml_file_callback(Fl_Widget * ob,void * data)1035 void save_recphyloxml_file_callback(Fl_Widget *ob, void *data) {
1036 SVG_View_data* svg_data = (SVG_View_data*)data;
1037
1038 Fl_Native_File_Chooser* chooser = new Fl_Native_File_Chooser();
1039 chooser->title("Save file");
1040 chooser->type(Fl_Native_File_Chooser::BROWSE_SAVE_FILE);
1041 const char* filename = run_and_close_native_file_chooser(chooser);
1042
1043 if(filename != NULL and strcmp(filename, "") != 0) {
1044 copyfile(filename, svg_data->rpx_fname);
1045 }
1046 }
1047