/* * Copyright 2003-2010 Tufts University Licensed under the * Educational Community License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may * obtain a copy of the License at * * http://www.osedu.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS IS" * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing * permissions and limitations under the License. */ package tufts.vue.action; import static tufts.Util.reverse; import java.io.*; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.awt.*; import java.awt.geom.Area; import java.awt.geom.GeneralPath; import java.awt.geom.PathIterator; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import tufts.vue.*; import tufts.vue.LWComponent.ChildKind; import tufts.vue.LWComponent.Order; /** * @version $Revision: 1.39 $ / $Date: 2010-03-29 13:48:48 $ / $Author: mike $ * * @author Jay Briedis * * Major revision: 2/17/09 -MK * The image map now uses a bunch of JQuery files stored on vue.tufts.edu, to enhance * the image map a bit. The map itself now supports nested nodes, and groups for the first time. * Intersecting areas of the image map are priortized by what appears first in this list, * so ideallly we want the lowest level descendants to appear first with the top level nodes * appearing last, also you want a groups descedents to appear before the actual group. * * The current tooltip code doesn't quite work right in IE7 so I cased it to only work in FF, and * use the standard tooltips in IE. I'll revisit this after the Feb. 09 release. I'd also like * to visit adding a zoom slider similar to google maps and scaling the image map appropriately. * * 3rd level children don't seem to size quite right, need to revisit that as well. Also, the tooltip * code lets us include Image Previews so we shoud look at using that to provide resource previews when * its as simple as pointing to a URL. * * Also I'd really like to add some simple templating as having the HTML in java is really cumbersome. */ public class ImageMap extends VueAction { public static final int UPPER_LEFT_MARGIN = 30; private int nodeCounter = 0; static final float ChildScale = VueResources.getInt("node.child.scale", 75) / 100f; private Dimension imageDimensions; private int xOffset, yOffset; /** Creates a new instance of ImageConversion */ public ImageMap() { } public ImageMap(String label) { super(label); } public void act() { File selectedFile = ActionUtil.selectFile("Saving Imap", "html"); if (selectedFile != null) createImageMap(selectedFile,1.0,"png"); } public void createImageMap(File file,LWMap map,double zoom) { String imageLocation = file.getAbsolutePath().substring(0, file.getAbsolutePath().length() - 5) + ".png"; String imageName = file.getName().substring(0, file.getName().length() - 5) + ".png"; String fileName = file.getAbsolutePath().substring(0, file.getAbsolutePath().length() - 5) + ".html"; File imageLocationFile = new File(imageLocation); if (imageLocationFile.exists()) { int confirm = VueUtil.confirm( VueResources.getString("imagemap.fileexists.warning"), VueResources.getString("imagemap.fileexists.title")); if (confirm == javax.swing.JOptionPane.NO_OPTION) { VueUtil.alert(VueResources.getString("imagemap.mapnotsaved.error"), VueResources.getString("imagemap.mapnotsaved.title")); return; } } imageDimensions = ImageConversion.createActiveMapPng(imageLocationFile,map,1.0); createHtml(imageName, fileName,map,zoom); } public void createImageMap(File file, double zoom, String format) { // See: VUE-536 in JIRA, If SaveAction Class still chooses "html" as the // file type for image maps // html file will already not be overwritten // String imageLocation = file.getAbsolutePath().substring(0, // file.getAbsolutePath().length()-5)+"-for-image-map"+".jpeg"; // String imageName = file.getName().substring(0, // file.getName().length()-5)+"-for-image-map"+".jpeg"; String imageLocation = file.getAbsolutePath().substring(0, file.getAbsolutePath().length() - 5) + "." + format; String imageName = file.getName().substring(0, file.getName().length() - 5) + "." + format; String fileName = file.getAbsolutePath().substring(0, file.getAbsolutePath().length() - 5) + ".html"; File imageLocationFile = new File(imageLocation); if (imageLocationFile.exists()) { int confirm = VueUtil.confirm( VueResources.getString("imagemap.fileexists.warning"), VueResources.getString("imagemap.fileexists.title")); if (confirm == javax.swing.JOptionPane.NO_OPTION) { VueUtil.alert(VueResources.getString("imagemap.mapnotsaved.error"), VueResources.getString("imagemap.mapnotsaved.title")); return; } } // createJpeg(imageLocation, "jpeg", currentMap, size); // ImageConversion.createActiveMapJpeg(new File(imageLocation)); if (format.equals("jpeg")) imageDimensions = ImageConversion.createActiveMapJpeg(imageLocationFile, zoom); else imageDimensions = ImageConversion.createActiveMapPng(imageLocationFile, zoom); createHtml(imageName, fileName,zoom); } /** * Returns a string containing the coordinates (x1, y1, x2, y2) for a given * rectangle. This string is intended for use in an image map. * * @param rectangle * the rectangle * * @return Upper left and lower right corner of a rectangle. */ private String getRectCoords(Rectangle2D rectangle, double zoom) { if (rectangle == null) { throw new IllegalArgumentException("Null 'rectangle' argument."); } int x1 = (int) (rectangle.getX()* zoom); int y1 = (int) (rectangle.getY()* zoom); int x2 = (int) (rectangle.getWidth()* zoom); int y2 = (int) (rectangle.getHeight()* zoom); if (x2 == x1) { x2++; } if (y2 == y1) { y2++; } return x1 + "," + y1 + "," + x2 + "," + y2; } private Rectangle getRectNode(LWComponent node) { int groupX = 0; int groupY = 0; int ox; int oy; int ow; int oh; oy = (int) node.getMapY() + groupY - yOffset - 13; ox = (int) node.getMapX() + groupX - xOffset - 14; ow = (int) node.getWidth() + ox; oh = (int) node.getHeight() + oy; if (node.getParent() instanceof LWNode) { System.out.println(node.getDepth()); ow = (int) (ow - ((ow - ox) * (1 - Math.pow(ChildScale,(node.getDepth()-2))))); /// (node.getDepth() - 2)))); oh = (int) (oh - ((oh - oy) * (1 - Math.pow(ChildScale,(node.getDepth()-2))))); /// (node.getDepth() - 2)))); } return new Rectangle(ox, oy, ow, oh); } private String writeMapforContainer(LWContainer container,LWMap map, double zoom) { /* * I'm using an array list to gather all the lines of the image map * so that I can push things to the top. Intersecting areas of the image map * are priortized by what appears first in this list, so ideallly we want * the lowest level descendants to appear first with the top level nodes * appearing last, also you want a groups descedents to appear before the * actual group -MK 2/16/09 */ java.util.List<String> arrayList = new ArrayList<String>(); //java.util.ArrayList<LWComponent> comps = new ArrayList<LWComponent>(); //get the current map. // handle in reverse order (top layer on top) for (LWComponent layer : reverse(map.getChildren())) { //for (LWComponent c : reverse(layer.getChildren())) // for (LWComponent c : reverse() Iterator<LWComponent> iter = layer.getAllDescendents(ChildKind.PROPER, new ArrayList(), Order.DEPTH).iterator(); //comps.iterator(); //container.getAllDescendents( // LWComponent.ChildKind.PROPER).iterator(); while (iter.hasNext()) { LWComponent comp = (LWComponent) iter.next(); String type = "node"; if (container instanceof LWGroup) type = "group"; if (!(comp instanceof LWNode) && !(comp instanceof LWGroup)) continue; String href = null; String altLabel = null; Resource res = null; if (comp instanceof LWNode) { if(comp.getResource() != null){ res = comp.getResource(); //res = resource.toString(); altLabel=res.getSpec(); // see VUE-873 getSpec() should be fine for now //if(!(res.startsWith("http://") || res.startsWith("https://"))) res = "file:///" + res; } } if (res == null) href =""; else if(res.equals("null")) href = ""; else href = "href=\"" + res.getSpec() + "\" target=\"_blank\""; String notes =""; notes = comp.getNotes(); if (notes == null) notes =""; else { notes = VueUtil.formatLines(notes, 20); notes ="class=\"tooltip\" title=\""+notes+"\" "; } if (!comp.hasChildren()) { arrayList.add(" <area "+href+" "+notes+" id=\"" + type + (nodeCounter++) + "\" shape=\"rect\" coords=\"" + getRectCoords(getRectNode(comp),zoom) + "\"></area>\n"); } else { Collection<LWComponent> children = comp.getAllDescendents(); LWComponent[] array = new LWComponent[children.size()]; children.toArray(array); for (int i = array.length - 1; i >= 0; i--) { if (array[i] instanceof LWNode) { String childHref; Resource childRes = array[i].getResource(); if (childRes == null) childHref =""; else if(childRes.equals("null")) childHref = ""; else childHref = "href=\"" + childRes.getSpec() + "\" target=\"_blank\""; arrayList.add(0, " <area "+childHref+" "+notes+" id=\"" + type + (nodeCounter++) + "\" shape=\"rect\" coords=\"" + getRectCoords(getRectNode(array[i]),zoom) + "\"></area>\n"); } } arrayList.add(" <area "+href+" "+notes+" id=\"" + type + (nodeCounter++) + "\" shape=\"rect\" coords=\"" + getRectCoords(getRectNode(comp),zoom) + "\"></area>\n"); //if (comp instanceof LWGroup) { // String groupOutput = writeMapforContainer((LWGroup) comp,map,zoom); // arrayList.add(groupOutput); //} }// end else }// end while } String buf = ""; Iterator<String> iter2 = arrayList.iterator(); while (iter2.hasNext()) { String st = iter2.next(); buf += st; } return buf; } private void createHtml(String imageName, String fileName,double zoom) { LWMap currentMap = VUE.getActiveMap(); createHtml(imageName,fileName,currentMap,zoom); } private void createHtml(String imageName, String fileName,LWMap currentMap,double zoom) { Rectangle2D bounds = currentMap.getMapBounds(); xOffset = (int) bounds.getX() - UPPER_LEFT_MARGIN; yOffset = (int) bounds.getY() - UPPER_LEFT_MARGIN; String out = "<html><head><title>" + currentMap.getLabel(); out += "</title>"; out += "<script src=\"http://ajax.googleapis.com/ajax/libs/jquery/1.3.1/jquery.min.js\" type=\"text/javascript\"></script>"; out += "<script type=\"text/javascript\">\n"; out += "jQuery.noConflict();\n"; out += "</script>\n"; out += "<script src=\"http://vue.tufts.edu/htmlexport-includes/jquery.maphilight.min.js\" type=\"text/javascript\"></script>"; out += "<script src=\"http://vue.tufts.edu/htmlexport-includes/v3/tooltip.min.js\" type=\"text/javascript\"></script>"; out += "<script type=\"text/javascript\">"; out += "jQuery(function() {jQuery.fn.maphilight.defaults = {\n"; out += " fill: false,\n"; out += " fillColor: '000000',\n"; out += " fillOpacity: 0.2,\n"; out += " stroke: true,\n"; out += " strokeColor: '282828',\n"; out += " strokeOpacity: 1,\n"; out += " strokeWidth: 4,\n"; out += " fade: true,\n"; out += " alwaysOn: false\n"; out += " }\n"; out += "jQuery('.example2 img').maphilight();\n"; out += "});\n"; out += "</script>\n"; out += "<style type=\"text/css\">\n"; out += "#tooltip{\n"; out += "position:absolute;\n"; out += "border:1px solid #333;\n"; out += "background:#f7f5d1;\n"; out += "padding:2px 5px;\n"; out += "color:#333;\n"; out += "display:none;\n"; out += "}\n"; out +="</style>\n"; out += "</head><body>\n"; out += "<div class=\"example2\">"; out += "<img class=\"map\" src=\"" + imageName + "\" width=\"" + imageDimensions.getWidth() + "\" height=\"" + imageDimensions.getHeight() + "\" usemap=\"#vuemap\">"; out += "<map name=\"vuemap\">"; nodeCounter = 0; out += writeMapforContainer(currentMap,currentMap,zoom); out += "\n</map></div></body></html>"; // write out to the selected file FileWriter output = null; try { output = new FileWriter(fileName); output.write(out); System.out.println("wrote to the file..."); } catch (IOException ioe) { System.out.println("Error trying to write to html file: " + ioe); } finally { try { output.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }