JAL-4409 Make HttpUtils.equivalentJalviewUrlString 'non-destructive'
[jalview.git] / src / jalview / io / HTMLOutput.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.io;
22
23 import java.io.BufferedReader;
24 import java.io.File;
25 import java.io.IOException;
26 import java.io.InputStreamReader;
27 import java.net.URL;
28 import java.util.Objects;
29
30 import jalview.api.AlignExportSettingsI;
31 import jalview.bin.Cache;
32 import jalview.bin.Jalview;
33 import jalview.datamodel.AlignExportSettingsAdapter;
34 import jalview.datamodel.AlignmentExportData;
35 import jalview.gui.AlignmentPanel;
36 import jalview.gui.IProgressIndicator;
37 import jalview.io.exceptions.ImageOutputException;
38 import jalview.util.HttpUtils;
39 import jalview.util.MessageManager;
40
41 public abstract class HTMLOutput implements Runnable
42 {
43   protected AlignmentPanel ap;
44
45   /*
46    * key for progress or status messages
47    */
48   protected long pSessionId;
49
50   /*
51    * (optional) place to write progress messages to
52    */
53   protected IProgressIndicator pIndicator;
54
55   protected File generatedFile;
56
57   String _bioJson = null;
58
59   private String description;
60
61   /**
62    * Constructor given an alignment panel (which should not be null)
63    * 
64    * @param ap
65    * @param desc
66    */
67   public HTMLOutput(AlignmentPanel ap, String desc)
68   {
69     this.ap = ap;
70     this.pIndicator = ap.alignFrame;
71     this.description = desc;
72     this.pSessionId = System.currentTimeMillis();
73   }
74
75   /**
76    * Gets the BioJSON data as a string, with lazy evaluation (first time called
77    * only). If the output format is configured not to embed BioJSON, returns
78    * null.
79    * 
80    * @return
81    */
82   public String getBioJSONData()
83   {
84     if (!isEmbedData())
85     {
86       return null;
87     }
88     if (_bioJson == null)
89     {
90       AlignExportSettingsI options = new AlignExportSettingsAdapter(true);
91       AlignmentExportData exportData = ap.getAlignViewport()
92               .getAlignExportData(options);
93       _bioJson = new FormatAdapter(ap, options).formatSequences(
94               FileFormat.Json, exportData.getAlignment(),
95               exportData.getOmitHidden(), exportData.getStartEndPostions(),
96               ap.getAlignViewport().getAlignment().getHiddenColumns());
97     }
98
99     return _bioJson;
100   }
101
102   /**
103    * Read a template file content as string
104    * 
105    * @param file
106    *          - the file to be read
107    * @return File content as String
108    * @throws IOException
109    */
110   public static String readFileAsString(File file) throws IOException
111   {
112     InputStreamReader isReader = null;
113     BufferedReader buffReader = null;
114     StringBuilder sb = new StringBuilder();
115     Objects.requireNonNull(file, "File must not be null!");
116     @SuppressWarnings("deprecation")
117     URL url = file.toURL();
118     if (url != null)
119     {
120       try
121       {
122         isReader = new InputStreamReader(HttpUtils.openStream(url));
123         buffReader = new BufferedReader(isReader);
124         String line;
125         String lineSeparator = System.getProperty("line.separator");
126         while ((line = buffReader.readLine()) != null)
127         {
128           sb.append(line).append(lineSeparator);
129         }
130
131       } catch (Exception ex)
132       {
133         ex.printStackTrace();
134       } finally
135       {
136         if (isReader != null)
137         {
138           isReader.close();
139         }
140
141         if (buffReader != null)
142         {
143           buffReader.close();
144         }
145       }
146     }
147     return sb.toString();
148   }
149
150   public static String getImageMapHTML()
151   {
152     return new String("<html>\n" + "<head>\n"
153             + "<script language=\"JavaScript\">\n"
154             + "var ns4 = document.layers;\n"
155             + "var ns6 = document.getElementById && !document.all;\n"
156             + "var ie4 = document.all;\n" + "offsetX = 0;\n"
157             + "offsetY = 20;\n" + "var toolTipSTYLE=\"\";\n"
158             + "function initToolTips()\n" + "{\n" + "  if(ns4||ns6||ie4)\n"
159             + "  {\n"
160             + "    if(ns4) toolTipSTYLE = document.toolTipLayer;\n"
161             + "    else if(ns6) toolTipSTYLE = document.getElementById(\"toolTipLayer\").style;\n"
162             + "    else if(ie4) toolTipSTYLE = document.all.toolTipLayer.style;\n"
163             + "    if(ns4) document.captureEvents(Event.MOUSEMOVE);\n"
164             + "    else\n" + "    {\n"
165             + "      toolTipSTYLE.visibility = \"visible\";\n"
166             + "      toolTipSTYLE.display = \"none\";\n" + "    }\n"
167             + "    document.onmousemove = moveToMouseLoc;\n" + "  }\n"
168             + "}\n" + "function toolTip(msg, fg, bg)\n" + "{\n"
169             + "  if(toolTip.arguments.length < 1) // hide\n" + "  {\n"
170             + "    if(ns4) toolTipSTYLE.visibility = \"hidden\";\n"
171             + "    else toolTipSTYLE.display = \"none\";\n" + "  }\n"
172             + "  else // show\n" + "  {\n"
173             + "    if(!fg) fg = \"#555555\";\n"
174             + "    if(!bg) bg = \"#FFFFFF\";\n" + "    var content =\n"
175             + "    '<table border=\"0\" cellspacing=\"0\" cellpadding=\"1\" bgcolor=\"' + fg + '\"><td>' +\n"
176             + "    '<table border=\"0\" cellspacing=\"0\" cellpadding=\"1\" bgcolor=\"' + bg + \n"
177             + "    '\"><td align=\"center\"><font face=\"sans-serif\" color=\"' + fg +\n"
178             + "    '\" size=\"-2\">&nbsp;' + msg +\n"
179             + "    '&nbsp;</font></td></table></td></table>';\n"
180             + "    if(ns4)\n" + "    {\n"
181             + "      toolTipSTYLE.document.write(content);\n"
182             + "      toolTipSTYLE.document.close();\n"
183             + "      toolTipSTYLE.visibility = \"visible\";\n" + "    }\n"
184             + "    if(ns6)\n" + "    {\n"
185             + "      document.getElementById(\"toolTipLayer\").innerHTML = content;\n"
186             + "      toolTipSTYLE.display='block'\n" + "    }\n"
187             + "    if(ie4)\n" + "    {\n"
188             + "      document.all(\"toolTipLayer\").innerHTML=content;\n"
189             + "      toolTipSTYLE.display='block'\n" + "    }\n" + "  }\n"
190             + "}\n" + "function moveToMouseLoc(e)\n" + "{\n"
191             + "  if(ns4||ns6)\n" + "  {\n" + "    x = e.pageX;\n"
192             + "    y = e.pageY;\n" + "  }\n" + "  else\n" + "  {\n"
193             + "    x = event.x + document.body.scrollLeft;\n"
194             + "    y = event.y + document.body.scrollTop;\n" + "  }\n"
195             + "  toolTipSTYLE.left = x + offsetX;\n"
196             + "  toolTipSTYLE.top = y + offsetY;\n" + "  return true;\n"
197             + "}\n" + "</script>\n" + "</head>\n" + "<body>\n"
198             + "<div id=\"toolTipLayer\" style=\"position:absolute; visibility: hidden\"></div>\n"
199             + "<script language=\"JavaScript\"><!--\n"
200             + "initToolTips(); //--></script>\n");
201
202   }
203
204   /**
205    * Prompts the user to choose an output file and returns the file path, or
206    * null on Cancel
207    * 
208    * @return
209    */
210   public String getOutputFile()
211   {
212     String selectedFile = null;
213
214     // TODO: JAL-3048 generate html rendered view (requires SvgGraphics and/or
215     // Jalview HTML rendering system- probably not required for Jalview-JS)
216     JalviewFileChooser jvFileChooser = new JalviewFileChooser("html",
217             "HTML files");
218     jvFileChooser.setFileView(new JalviewFileView());
219
220     jvFileChooser
221             .setDialogTitle(MessageManager.getString("label.save_as_html"));
222     jvFileChooser.setToolTipText(MessageManager.getString("action.save"));
223
224     int fileChooserOpt = jvFileChooser.showSaveDialog(null);
225     if (fileChooserOpt == JalviewFileChooser.APPROVE_OPTION)
226     {
227       Cache.setProperty("LAST_DIRECTORY",
228               jvFileChooser.getSelectedFile().getParent());
229       selectedFile = jvFileChooser.getSelectedFile().getPath();
230     }
231
232     return selectedFile;
233   }
234
235   protected void setProgressMessage(String message)
236   {
237     if (pIndicator != null && !isHeadless())
238     {
239       pIndicator.setProgressBar(message, pSessionId);
240     }
241     else
242     {
243       jalview.bin.Console.outPrintln(message);
244     }
245   }
246
247   /**
248    * Answers true if HTML export is invoke in headless mode or false otherwise
249    * 
250    * @return
251    */
252   protected boolean isHeadless()
253   {
254     return System.getProperty("java.awt.headless") != null
255             && System.getProperty("java.awt.headless").equals("true");
256   }
257
258   /**
259    * This method provides implementation of consistent behaviour which should
260    * occur after a HTML file export. It MUST be called at the end of the
261    * exportHTML() method implementation.
262    */
263   protected void exportCompleted()
264   {
265     if (isLaunchInBrowserAfterExport() && !isHeadless())
266     {
267       /*
268       try
269       {
270       */
271       jalview.util.BrowserLauncher.openURL("file:///" + getExportedFile());
272       /*
273       } catch (IOException e)
274       {
275         e.printStackTrace();
276       }
277       */
278     }
279   }
280
281   /**
282    * if this answers true then BioJSON data will be embedded to the exported
283    * HTML file otherwise it won't be embedded.
284    * 
285    * @return
286    */
287   public abstract boolean isEmbedData();
288
289   /**
290    * if this answers true then the generated HTML file is opened for viewing in
291    * a browser after its generation otherwise it won't be opened in a browser
292    * 
293    * @return
294    */
295   public abstract boolean isLaunchInBrowserAfterExport();
296
297   /**
298    * handle to the generated HTML file
299    * 
300    * @return
301    */
302   public File getExportedFile()
303   {
304     return generatedFile;
305   }
306
307   public void exportHTML(String outputFile) throws ImageOutputException
308   {
309     exportHTML(outputFile, null);
310   }
311
312   public void exportHTML(String outputFile, String renderer)
313           throws ImageOutputException
314   {
315     setProgressMessage(MessageManager.formatMessage(
316             "status.exporting_alignment_as_x_file", getDescription()));
317     try
318     {
319       if (outputFile == null)
320       {
321         /*
322          * prompt for output file
323          */
324         outputFile = getOutputFile();
325         if (outputFile == null)
326         {
327           setProgressMessage(MessageManager.formatMessage(
328                   "status.cancelled_image_export_operation",
329                   getDescription()));
330           return;
331         }
332       }
333       generatedFile = new File(outputFile);
334     } catch (Exception e)
335     {
336       setProgressMessage(MessageManager
337               .formatMessage("info.error_creating_file", getDescription()));
338       e.printStackTrace();
339       return;
340     }
341     if (Jalview.isHeadlessMode())
342     {
343       this.run(renderer);
344     }
345     else
346     {
347       new Thread(this).start();
348     }
349
350   }
351
352   /**
353    * Answers a short description of the image format suitable for display in
354    * messages
355    * 
356    * @return
357    */
358   protected final String getDescription()
359   {
360     return description;
361   }
362
363   // used to pass an option such as render to run
364   public abstract void run(String string) throws ImageOutputException;
365 }