JAL-1233 JAL-1576 copy constructor to propagate helix colours and applyTo implementat...
[jalview.git] / src / jalview / io / HtmlSvgOutput.java
1 package jalview.io;
2
3 import jalview.api.AlignExportSettingI;
4 import jalview.api.FeatureRenderer;
5 import jalview.datamodel.AlignmentExportData;
6 import jalview.datamodel.SequenceI;
7 import jalview.gui.AlignViewport;
8 import jalview.gui.AlignmentPanel;
9 import jalview.gui.HTMLOptions;
10 import jalview.math.AlignmentDimension;
11 import jalview.util.MessageManager;
12
13 import java.awt.Color;
14 import java.awt.FontMetrics;
15 import java.awt.Graphics;
16 import java.awt.print.Printable;
17 import java.awt.print.PrinterException;
18 import java.io.File;
19 import java.io.FileOutputStream;
20
21 import org.jfree.graphics2d.svg.SVGGraphics2D;
22 import org.jfree.graphics2d.svg.SVGHints;
23
24 public class HtmlSvgOutput
25 {
26   AlignViewport av;
27
28   FeatureRenderer fr;
29   AlignmentPanel ap;
30
31
32   public HtmlSvgOutput(File file, AlignmentPanel ap)
33   {
34     this.av = ap.av;
35     this.ap = ap;
36     fr = ap.cloneFeatureRenderer();
37     generateHtmlSvgOutput(file);
38   }
39
40   public void generateHtmlSvgOutput(File file)
41   {
42     try
43     {
44       if (file == null)
45       {
46
47       JalviewFileChooser chooser = getHTMLChooser();
48       chooser.setFileView(new jalview.io.JalviewFileView());
49       chooser.setDialogTitle(ap.alignFrame.getTitle());
50       chooser.setToolTipText(MessageManager.getString("action.save"));
51       int value = chooser.showSaveDialog(ap.alignFrame);
52
53       if (value == jalview.io.JalviewFileChooser.APPROVE_OPTION)
54       {
55         jalview.bin.Cache.setProperty("LAST_DIRECTORY", chooser
56                 .getSelectedFile().getParent());
57         file = chooser.getSelectedFile();
58       }
59       }
60
61       AlignmentDimension aDimension = ap.getAlignmentDimension();
62       SVGGraphics2D g1 = new SVGGraphics2D(aDimension.getWidth(),
63               aDimension.getHeight());
64       SVGGraphics2D g2 = new SVGGraphics2D(aDimension.getWidth(),
65               aDimension.getHeight());
66
67       String renderStyle = jalview.bin.Cache.getDefault("HTML_RENDERING",
68               "Prompt each time");
69
70       // If we need to prompt, and if the GUI is visible then
71       // Prompt for rendering style
72       if (renderStyle.equalsIgnoreCase("Prompt each time")
73               && !(System.getProperty("java.awt.headless") != null && System
74                       .getProperty("java.awt.headless").equals("true")))
75       {
76         HTMLOptions svgOption = new HTMLOptions();
77         renderStyle = svgOption.getValue();
78
79         if (renderStyle == null || svgOption.cancelled)
80         {
81           return;
82         }
83       }
84
85       if (renderStyle.equalsIgnoreCase("lineart"))
86       {
87         g1.setRenderingHint(SVGHints.KEY_DRAW_STRING_TYPE,
88                 SVGHints.VALUE_DRAW_STRING_TYPE_VECTOR);
89         g2.setRenderingHint(SVGHints.KEY_DRAW_STRING_TYPE,
90                 SVGHints.VALUE_DRAW_STRING_TYPE_VECTOR);
91       }
92       printUnwrapped(aDimension.getWidth(), aDimension.getHeight(), 0, g1,
93               g2);
94
95       String titleSvgData = g1.getSVGDocument();
96       String alignSvgData = g2.getSVGDocument();
97       String jsonData = null;
98       boolean isEmbbedBioJSON = Boolean.valueOf(jalview.bin.Cache
99               .getDefault("EXPORT_EMBBED_BIOJSON", "true"));
100       if (isEmbbedBioJSON)
101       {
102         AlignExportSettingI exportSettings = new AlignExportSettingI(){
103           @Override
104           public boolean isExportHiddenSequences()
105           {
106             return true;
107           }
108
109           @Override
110           public boolean isExportHiddenColumns()
111           {
112             return true;
113           }
114
115           @Override
116           public boolean isExportAnnotations()
117           {
118             return true;
119           }
120
121           @Override
122           public boolean isExportFeatures()
123           {
124             return true;
125           }
126
127           @Override
128           public boolean isExportGroups()
129           {
130             return true;
131           }
132
133           @Override
134           public boolean isCancelled()
135           {
136             return false;
137           }
138           
139         };
140         AlignmentExportData exportData = jalview.gui.AlignFrame
141                 .getAlignmentForExport(JSONFile.FILE_DESC, av,
142                         exportSettings);
143         jsonData = new FormatAdapter(ap, exportData.getSettings())
144               .formatSequences(JSONFile.FILE_DESC, exportData
145                       .getAlignment(), exportData.getOmitHidden(),
146                         exportData.getStartEndPostions(),
147                         av.getColumnSelection());
148       }
149       String htmlData = getHtml(titleSvgData, alignSvgData, jsonData);
150       FileOutputStream out = new FileOutputStream(file);
151       out.write(htmlData.getBytes());
152       out.flush();
153       out.close();
154       if (!(System.getProperty("java.awt.headless") != null && System
155               .getProperty("java.awt.headless").equals("true")))
156       {
157       jalview.util.BrowserLauncher.openURL("file:///" + file);
158       }
159     } catch (Exception e)
160     {
161       e.printStackTrace();
162     }
163   }
164   
165   static JalviewFileChooser getHTMLChooser()
166   {
167     return new jalview.io.JalviewFileChooser(
168             jalview.bin.Cache.getProperty("LAST_DIRECTORY"), new String[]
169             { "html" }, new String[]
170             { "Hypertext Markup Language" }, "Hypertext Markup Language");
171   }
172
173   public int printUnwrapped(int pwidth, int pheight, int pi, Graphics... pg)
174           throws PrinterException
175   {
176     int idWidth = ap.getVisibleIdWidth(false);
177     FontMetrics fm = ap.getFontMetrics(av.getFont());
178     int scaleHeight = av.getCharHeight() + fm.getDescent();
179
180     pg[0].setColor(Color.white);
181     pg[0].fillRect(0, 0, pwidth, pheight);
182     pg[0].setFont(av.getFont());
183
184     // //////////////////////////////////
185     // / How many sequences and residues can we fit on a printable page?
186     int totalRes = (pwidth - idWidth) / av.getCharWidth();
187     int totalSeq = (pheight - scaleHeight) / av.getCharHeight() - 1;
188     int pagesWide = (av.getAlignment().getWidth() / totalRes) + 1;
189
190     // ///////////////////////////
191     // / Only print these sequences and residues on this page
192     int startRes;
193
194     // ///////////////////////////
195     // / Only print these sequences and residues on this page
196     int endRes;
197
198     // ///////////////////////////
199     // / Only print these sequences and residues on this page
200     int startSeq;
201
202     // ///////////////////////////
203     // / Only print these sequences and residues on this page
204     int endSeq;
205     startRes = (pi % pagesWide) * totalRes;
206     endRes = (startRes + totalRes) - 1;
207
208     if (endRes > (av.getAlignment().getWidth() - 1))
209     {
210       endRes = av.getAlignment().getWidth() - 1;
211     }
212     startSeq = (pi / pagesWide) * totalSeq;
213     endSeq = startSeq + totalSeq;
214     if (endSeq > av.getAlignment().getHeight())
215     {
216       endSeq = av.getAlignment().getHeight();
217     }
218     int pagesHigh = ((av.getAlignment().getHeight() / totalSeq) + 1)
219             * pheight;
220     if (av.isShowAnnotation())
221     {
222       pagesHigh += ap.getAnnotationPanel().adjustPanelHeight() + 3;
223     }
224     pagesHigh /= pheight;
225     if (pi >= (pagesWide * pagesHigh))
226     {
227       return Printable.NO_SUCH_PAGE;
228     }
229
230     // draw Scale
231     pg[1].translate(0, 0);
232     ap.getScalePanel().drawScale(pg[1], startRes, endRes, pwidth - idWidth,
233             scaleHeight);
234     pg[1].translate(-idWidth, scaleHeight);
235
236     // //////////////
237     // Draw the ids
238     Color currentColor = null;
239     Color currentTextColor = null;
240     pg[0].translate(0, scaleHeight);
241     pg[0].setFont(ap.getIdPanel().getIdCanvas().getIdfont());
242     SequenceI seq;
243     for (int i = startSeq; i < endSeq; i++)
244     {
245       seq = av.getAlignment().getSequenceAt(i);
246       if ((av.getSelectionGroup() != null)
247               && av.getSelectionGroup().getSequences(null).contains(seq))
248       {
249         currentColor = Color.gray;
250         currentTextColor = Color.black;
251       }
252       else
253       {
254         currentColor = av.getSequenceColour(seq);
255         currentTextColor = Color.black;
256       }
257       pg[0].setColor(currentColor);
258       pg[0].fillRect(0, (i - startSeq) * av.getCharHeight(), idWidth,
259               av.getCharHeight());
260       pg[0].setColor(currentTextColor);
261       int xPos = 0;
262       if (av.isRightAlignIds())
263       {
264         fm = pg[0].getFontMetrics();
265         xPos = idWidth
266                 - fm.stringWidth(seq.getDisplayId(av.getShowJVSuffix()))
267                 - 4;
268       }
269       pg[0].drawString(
270               seq.getDisplayId(av.getShowJVSuffix()),
271               xPos,
272               (((i - startSeq) * av.getCharHeight()) + av.getCharHeight())
273                       - (av.getCharHeight() / 5));
274     }
275     pg[0].setFont(av.getFont());
276     pg[0].translate(idWidth, 0);
277
278     // draw main sequence panel
279     pg[1].translate(idWidth, 0);
280     ap.getSeqPanel().seqCanvas.drawPanel(pg[1], startRes, endRes, startSeq,
281             endSeq, 0);
282     if (av.isShowAnnotation() && (endSeq == av.getAlignment().getHeight()))
283     {
284       // draw annotation label - need to offset for current scroll position
285       int offset = -ap.getAlabels().getScrollOffset();
286       pg[0].translate(0, offset);
287       pg[0].translate(-idWidth - 3,
288               (endSeq - startSeq) * av.getCharHeight() + 3);
289       ap.getAlabels().drawComponent(pg[0], idWidth);
290       pg[0].translate(idWidth + 3, 0);
291       pg[0].translate(0, -offset);
292
293       // draw annotation - need to offset for current scroll position
294       pg[1].translate(0, offset);
295       pg[1].translate(-idWidth - 3,
296               (endSeq - startSeq) * av.getCharHeight() + 3);
297       pg[1].translate(idWidth + 3, 0);
298       ap.getAnnotationPanel().renderer.drawComponent(
299               ap.getAnnotationPanel(), av, pg[1], -1, startRes, endRes + 1);
300       pg[1].translate(0, -offset);
301     }
302
303     return Printable.PAGE_EXISTS;
304   }
305   
306   private String getHtml(String titleSvg, String alignmentSvg,
307           String jsonData)
308   {
309     StringBuilder htmlSvg = new StringBuilder();
310     htmlSvg.append("<html>\n");
311     if (jsonData != null)
312     {
313       htmlSvg.append("<button onclick=\"javascipt:openJalviewUsingCurrentUrl();\">Launch in Jalview</button>");
314       htmlSvg.append("<input type=\"hidden\" name=\"seqData\" id=\"seqData\" value='"
315               + jsonData + "'>");
316     }
317     htmlSvg.append("\n<style type=\"text/css\"> "
318             + "div.parent{ width:100%;<!-- overflow: auto; -->}\n"
319             + "div.titlex{ width:11%; float: left; }\n"
320             + "div.align{ width:89%; float: right; }\n"
321             + ".sub-category-container {overflow-y: scroll; overflow-x: hidden; width: 100%; height: 100%;}\n"
322             + "object {pointer-events: none;}"
323             + "</style>");
324     htmlSvg.append("<div>");
325     htmlSvg.append(
326 "<div class=\"titlex\">");
327     htmlSvg.append(
328 "<div class=\"sub-category-container\"> ")
329             .append(titleSvg)
330             .append("</div>")
331             .append("</div>\n\n<!-- ========================================================================================== -->\n\n");
332     htmlSvg.append(
333 "<div class=\"align\" >");
334     htmlSvg.append(
335             "<div class=\"sub-category-container\"> <div style=\"overflow-x: scroll;\">")
336             .append(alignmentSvg)
337 .append("</div></div>")
338             .append("</div>");
339     htmlSvg.append("</div>");
340
341     htmlSvg.append("<script language=\"JavaScript\" type=\"text/javascript\" src=\"http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js\"></script>\n"
342             + "<script language=\"JavaScript\" type=\"text/javascript\"  src=\"//ajax.googleapis.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.js\"></script>\n"
343             + "<script>\n"
344             + "var subCatContainer = $(\".sub-category-container\");\n"
345             + "subCatContainer.scroll(\nfunction() {\n"
346             + "subCatContainer.scrollTop($(this).scrollTop());\n});\n");
347
348     htmlSvg.append("</script>\n");
349
350     // javascript for launching file in Jalview
351
352     htmlSvg.append("<script language=\"JavaScript\">\n");
353     htmlSvg.append("function openJalviewUsingCurrentUrl(){\n");
354     htmlSvg.append("    var json = JSON.parse(document.getElementById(\"seqData\").value);\n");
355     htmlSvg.append("    var jalviewVersion = json['appSettings'].version;\n");
356     htmlSvg.append("    var url = json['appSettings'].webStartUrl;\n");
357     htmlSvg.append("    var myForm = document.createElement(\"form\");\n\n");
358     htmlSvg.append("    var heap = document.createElement(\"input\");\n");
359     htmlSvg.append("    heap.setAttribute(\"name\", \"jvm-max-heap\") ;\n");
360     htmlSvg.append("    heap.setAttribute(\"value\", \"2G\");\n\n");
361     htmlSvg.append("    var target = document.createElement(\"input\");\n");
362     htmlSvg.append("    target.setAttribute(\"name\", \"open\");\n");
363     htmlSvg.append("    target.setAttribute(\"value\", document.URL);\n\n");
364     htmlSvg.append("    var jvVersion = document.createElement(\"input\");\n");
365     htmlSvg.append("    jvVersion.setAttribute(\"name\", \"version\") ;\n");
366     htmlSvg.append("    jvVersion.setAttribute(\"value\", jalviewVersion);\n\n");
367     htmlSvg.append("    myForm.action = url;\n");
368     htmlSvg.append("    myForm.appendChild(heap);\n");
369     htmlSvg.append("    myForm.appendChild(target);\n");
370     htmlSvg.append("    myForm.appendChild(jvVersion);\n");
371     htmlSvg.append("    document.body.appendChild(myForm);\n");
372     htmlSvg.append("    myForm.submit() ;\n");
373     htmlSvg.append("    document.body.removeChild(myForm);\n");
374     htmlSvg.append("}\n");
375     htmlSvg.append("</script>\n");
376     htmlSvg.append("</hmtl>");
377     return htmlSvg.toString();
378   }
379 }