package org.sagemath.droid.views; import android.content.Context; import android.graphics.Bitmap; import android.text.TextUtils; import android.util.Log; import android.webkit.WebView; import android.webkit.WebViewClient; import org.sagemath.droid.constants.StringConstants; import org.sagemath.droid.database.SageSQLiteOpenHelper; import org.sagemath.droid.events.ProgressEvent; import org.sagemath.droid.models.database.Cell; import org.sagemath.droid.models.gson.*; import org.sagemath.droid.utils.BusProvider; import java.util.ArrayList; /** * The {@linkplain android.webkit.WebView} which displays the output from server. */ public class OutputWebView extends WebView { private final static String TAG = "SageDroid:OutputBlock"; private Context context; private ArrayList<String> divs = new ArrayList<String>(); private boolean isImageDiv = false; private boolean isHtmlScriptDiv = false; private String htmlData; private Cell cell; private SageSQLiteOpenHelper helper; public OutputWebView(Context context, Cell cell) { super(context); this.context = context; this.cell = cell; init(); } public OutputWebView(Context context, Cell cell, String htmlData) { super(context); this.htmlData = htmlData; this.context = context; this.cell = cell; init(); Log.i(TAG, "Created outputblock from htmldata."); divs.clear(); divs.add(htmlData); try { Log.i(TAG, "outputblock created: " + " " + cell.getTitle() + " " + cell.getUUID().toString() + " " + " "); } catch (Exception e) { Log.i(TAG, "outputblock exception: " + e.getMessage()); } } private void init() { this.getSettings().setJavaScriptEnabled(true); this.getSettings().setBuiltInZoomControls(true); this.setWebViewClient(client); helper = SageSQLiteOpenHelper.getInstance(context); BusProvider.getInstance().register(this); } public void unregister() { BusProvider.getInstance().unregister(this); } private static String htmlify(String str) { Log.i(TAG, "Converting to HTML: " + str); StringBuilder s = new StringBuilder(); s.append("<pre style=\"font-size:130%\">"); String[] lines = str.split("\n"); for (int i = 0; i < lines.length; i++) { if (i > 0) s.append(" "); s.append(TextUtils.htmlEncode(lines[i])); } s.append("</pre>"); Log.i(TAG, "Returning converted HTML: " + s.toString()); return s.toString(); } private String getHtml() { StringBuilder s = new StringBuilder(); s.append("<html>"); //Configure & Load MathJax if (isHtmlScriptDiv) { s.append(StringConstants.MATHJAX_CONFIG); s.append(StringConstants.MATHJAX_CDN); } if (isImageDiv) { s.append(StringConstants.IMAGE_STYLE); } s.append("<body>"); Log.i(TAG, "Constructing HTML with: " + divs.size() + "divs"); for (String div : divs) { s.append("<div>"); Log.i(TAG, "Adding div" + div); s.append(div); s.append("</div>"); } s.append("</body>"); s.append("</html>"); return s.toString(); } private void addDiv(BaseReply reply) { //Log.i(TAG, "Adding Reply: " + reply.toString()); if (reply instanceof ImageReply) { Log.i(TAG, "Adding an ImageReply"); addDivImageReply((ImageReply) reply); } else if (reply instanceof HtmlReply) { //Having text/html Log.i(TAG, "Adding HTML Reply"); addDivHtmlReply((HtmlReply) reply); } else if (reply instanceof PythonOutputReply) { //Having pyout Log.i(TAG, "Adding PyOut Reply"); addDivPythonOutputReply((PythonOutputReply) reply); } else if (reply instanceof PythonErrorReply) { //Having pyerr, Traceback Log.i(TAG, "Adding PyErr Reply"); addDivPythonErrorReply((PythonErrorReply) reply); } else if (reply instanceof StreamReply) { //Having Stream Log.i(TAG, "Adding Stream Reply"); addDivStreamReply((StreamReply) reply); } else if (reply instanceof StatusReply) { //Only Idle or Dead Status Reply is possible here, hence simply load URL Log.i(TAG, "Got Idle Status, Loading URL"); loadSavedUrl(); } else { Log.i(TAG, "Unknown Output"); } } /** * Add an image to the div * * @param reply */ private void addDivImageReply(ImageReply reply) { isImageDiv = true; String jpgDivTemplate = "<img src=\"%s\"alt=\"plot output\"></img>"; String svgDivTemplate = "<object data=\"%s\" type=\"image/svg+xml\"></img>"; String jpgDiv, svgDiv; if (reply.getImageMimeType().equals(ImageReply.MIME_IMAGE_PNG)) { Log.i(TAG, "Image is in .png format"); jpgDiv = String.format(jpgDivTemplate, reply.getImageURL()); Log.i(TAG, "Adding .png div" + jpgDiv); divs.add(jpgDiv); } else if (reply.getImageMimeType().equals(ImageReply.MIME_IMAGE_SVG)) { Log.i(TAG, "Image is in .svg format"); svgDiv = String.format(svgDivTemplate, reply.getImageURL()); Log.i(TAG, "Adding .svg div" + svgDiv); divs.add(svgDiv); } else if (reply.getImageMimeType() == null) { Log.i(TAG, "Unknown Image Type"); String div = "Unknown MIME type"; divs.add(div); } } private void addDivHtmlReply(HtmlReply reply) { String html = reply.getContent().getData().getHtmlCode(); if (html.contains("script")) { isHtmlScriptDiv = true; } divs.add(html); } private void addDivPythonOutputReply(PythonOutputReply reply) { String outputValue = reply.getContent().getData().getOutputValue(); //If the outputValue is empty, don't add it, might overwrite data which is actually valid. if (!outputValue.equalsIgnoreCase("")) { String div = htmlify(outputValue); divs.add(div); } } private void addDivStreamReply(StreamReply reply) { String div = htmlify(reply.getContent().getData()); divs.add(div); } private void addDivPythonErrorReply(PythonErrorReply reply) { String div = htmlify(reply.getContent().getEname() + ":" + reply.getContent().getEvalue()); divs.add(div); } public void add(BaseReply reply) { if (reply instanceof SageClearReply) { } addDiv(reply); } public void loadSavedUrl() { htmlData = getHtml(); Log.i(TAG, "Loading html: " + htmlData); this.post(new Runnable() { @Override public void run() { loadData(htmlData, "text/html", "utf-8"); } }); } public void reloadHtml(String savedHtml) { Log.i(TAG, "Loading Saved HTML" + htmlData); loadData(htmlData, "text/html", "utf-8"); reload(); } public void set(BaseReply reply) { Log.i(TAG, "Clearing divs"); add(reply); } public String getHtmlData() { if (htmlData != null) return htmlData; return null; } public void setHtmlFromSavedState(String html) { Log.i(TAG, "Setting HTML from saved state" + html); this.htmlData = html; loadData(htmlData, "text/html", "utf-8"); } public void clearBlocks() { divs.clear(); } private WebViewClient client = new WebViewClient() { @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { super.onPageStarted(view, url, favicon); BusProvider.getInstance().post(new ProgressEvent(StringConstants.ARG_PROGRESS_START)); } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); BusProvider.getInstance().post(new ProgressEvent(StringConstants.ARG_PROGRESS_END)); } }; }