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