001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.markup.html.link; 018 019import java.awt.Shape; 020import java.awt.geom.PathIterator; 021 022import org.apache.wicket.AttributeModifier; 023import org.apache.wicket.Component; 024import org.apache.wicket.behavior.Behavior; 025import org.apache.wicket.markup.ComponentTag; 026import org.apache.wicket.markup.html.image.Image; 027import org.apache.wicket.markup.html.panel.Panel; 028import org.apache.wicket.markup.repeater.RepeatingView; 029import org.apache.wicket.model.Model; 030import org.apache.wicket.model.PropertyModel; 031 032/** 033 * A client-side image map implementation which allows you to "attach" the map to any existing 034 * {@link Image} component. 035 * 036 * @since 1.5 037 */ 038public class ClientSideImageMap extends Panel 039{ 040 private static final long serialVersionUID = 1L; 041 private static final String CIRCLE = "circle"; 042 private static final String POLYGON = "poly"; 043 private static final String RECTANGLE = "rect"; 044 045 private RepeatingView areas; 046 047 /** 048 * Constructs a client-side image map which is "attached" to the given {@link Image} component. 049 * 050 * @param id 051 * the component id 052 * @param image 053 * the image component 054 */ 055 public ClientSideImageMap(String id, Image image) 056 { 057 super(id); 058 setOutputMarkupId(true); 059 add(AttributeModifier.replace("name", new PropertyModel<String>(this, "markupId"))); 060 image.add(AttributeModifier.replace("usemap", new UsemapModel())); 061 062 areas = new RepeatingView("area"); 063 add(areas); 064 } 065 066 private String circleCoordinates(int x, int y, int radius) 067 { 068 return x + "," + y + "," + radius; 069 } 070 071 private String polygonCoordinates(int... coordinates) 072 { 073 final StringBuilder buffer = new StringBuilder(); 074 for (int i = 0; i < coordinates.length; i++) 075 { 076 buffer.append(coordinates[i]); 077 078 if (i < (coordinates.length - 1)) 079 { 080 buffer.append(','); 081 } 082 } 083 return buffer.toString(); 084 } 085 086 private String rectangleCoordinates(int x1, int y1, int x2, int y2) 087 { 088 return x1 + "," + y1 + "," + x2 + "," + y2; 089 } 090 091 private String shapeCoordinates(Shape shape) 092 { 093 final StringBuilder sb = new StringBuilder(); 094 final PathIterator pi = shape.getPathIterator(null, 1.0); 095 final float[] coords = new float[6]; 096 final float[] lastMove = new float[2]; 097 while (!pi.isDone()) 098 { 099 switch (pi.currentSegment(coords)) 100 { 101 case PathIterator.SEG_MOVETO : 102 if (sb.length() != 0) 103 { 104 sb.append(','); 105 } 106 sb.append(Math.round(coords[0])); 107 sb.append(','); 108 sb.append(Math.round(coords[1])); 109 lastMove[0] = coords[0]; 110 lastMove[1] = coords[1]; 111 break; 112 case PathIterator.SEG_LINETO : 113 if (sb.length() != 0) 114 { 115 sb.append(','); 116 } 117 sb.append(Math.round(coords[0])); 118 sb.append(','); 119 sb.append(Math.round(coords[1])); 120 break; 121 case PathIterator.SEG_CLOSE : 122 if (sb.length() != 0) 123 { 124 sb.append(','); 125 } 126 sb.append(Math.round(lastMove[0])); 127 sb.append(','); 128 sb.append(Math.round(lastMove[1])); 129 break; 130 } 131 pi.next(); 132 } 133 return sb.toString(); 134 } 135 136 @Override 137 protected void onComponentTag(ComponentTag tag) 138 { 139 // Must be attached to an map tag 140 checkComponentTag(tag, "map"); 141 142 super.onComponentTag(tag); 143 } 144 145 /** 146 * Generates a unique id string. This makes it easy to add items to be rendered w/out having to 147 * worry about generating unique id strings in your code. 148 * 149 * @return unique child id 150 */ 151 public String newChildId() 152 { 153 return areas.newChildId(); 154 } 155 156 /** 157 * Adds a circle-shaped area centered at (x,y) with radius r. 158 * 159 * @param link 160 * the link 161 * @param x 162 * x coordinate of the center of the circle 163 * @param y 164 * y coordinate of center 165 * @param radius 166 * the radius 167 * @return this 168 */ 169 public ClientSideImageMap addCircleArea(AbstractLink link, int x, int y, int radius) 170 { 171 areas.add(link); 172 173 link.add(new Area(circleCoordinates(x, y, radius), CIRCLE)); 174 175 return this; 176 } 177 178 /** 179 * Adds a polygon-shaped area defined by coordinates. 180 * 181 * @param link 182 * the link 183 * @param coordinates 184 * the coordinates for the polygon 185 * @return This 186 */ 187 public ClientSideImageMap addPolygonArea(AbstractLink link, int... coordinates) 188 { 189 areas.add(link); 190 191 link.add(new Area(polygonCoordinates(coordinates), POLYGON)); 192 193 return this; 194 } 195 196 /** 197 * Adds a rectangular-shaped area. 198 * 199 * @param link 200 * the link 201 * @param x1 202 * top left x 203 * @param y1 204 * top left y 205 * @param x2 206 * bottom right x 207 * @param y2 208 * bottom right y 209 * @return this 210 */ 211 public ClientSideImageMap addRectangleArea(AbstractLink link, int x1, int y1, int x2, int y2) 212 { 213 areas.add(link); 214 link.add(new Area(rectangleCoordinates(x1, y1, x2, y2), RECTANGLE)); 215 return this; 216 } 217 218 /** 219 * Adds an area defined by a shape object. 220 * 221 * @param link 222 * the link 223 * @param shape 224 * the shape 225 * @return this 226 */ 227 public ClientSideImageMap addShapeArea(AbstractLink link, Shape shape) 228 { 229 areas.add(link); 230 link.add(new Area(shapeCoordinates(shape), POLYGON)); 231 return this; 232 } 233 234 /** 235 * Encapsulates the concept of an <area> within a <map>. 236 */ 237 private static class Area extends Behavior 238 { 239 private static final long serialVersionUID = 1L; 240 241 private final String coordinates; 242 private final String type; 243 244 protected Area(final String coordinates, final String type) 245 { 246 this.coordinates = coordinates; 247 this.type = type; 248 } 249 250 @Override 251 public void onComponentTag(Component component, ComponentTag tag) 252 { 253 tag.put("shape", type); 254 tag.put("coords", coordinates); 255 } 256 } 257 258 private class UsemapModel extends Model<String> 259 { 260 private static final long serialVersionUID = 1L; 261 262 @Override 263 public String getObject() 264 { 265 return "#" + getMarkupId(); 266 } 267 } 268}