1 /* 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 1999 Antti Koivisto (koivisto (at) kde.org) 4 * Copyright (C) 2004, 2005, 2006, 2009, 2011 Apple Inc. All rights reserved. 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Library General Public 8 * License as published by the Free Software Foundation; either 9 * version 2 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Library General Public License for more details. 15 * 16 * You should have received a copy of the GNU Library General Public License 17 * along with this library; see the file COPYING.LIB. If not, write to 18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 * Boston, MA 02110-1301, USA. 20 */ 21 22 #include "config.h" 23 #include "core/html/HTMLAreaElement.h" 24 25 #include "HTMLNames.h" 26 #include "core/html/HTMLImageElement.h" 27 #include "core/html/HTMLMapElement.h" 28 #include "core/rendering/HitTestResult.h" 29 #include "core/rendering/RenderImage.h" 30 #include "core/rendering/RenderView.h" 31 #include "platform/graphics/Path.h" 32 #include "platform/transforms/AffineTransform.h" 33 34 using namespace std; 35 36 namespace WebCore { 37 38 using namespace HTMLNames; 39 40 inline HTMLAreaElement::HTMLAreaElement(Document& document) 41 : HTMLAnchorElement(areaTag, document) 42 , m_lastSize(-1, -1) 43 , m_shape(Unknown) 44 { 45 ScriptWrappable::init(this); 46 } 47 48 PassRefPtr<HTMLAreaElement> HTMLAreaElement::create(Document& document) 49 { 50 return adoptRef(new HTMLAreaElement(document)); 51 } 52 53 void HTMLAreaElement::parseAttribute(const QualifiedName& name, const AtomicString& value) 54 { 55 if (name == shapeAttr) { 56 if (equalIgnoringCase(value, "default")) 57 m_shape = Default; 58 else if (equalIgnoringCase(value, "circle")) 59 m_shape = Circle; 60 else if (equalIgnoringCase(value, "poly")) 61 m_shape = Poly; 62 else if (equalIgnoringCase(value, "rect")) 63 m_shape = Rect; 64 invalidateCachedRegion(); 65 } else if (name == coordsAttr) { 66 m_coords = parseHTMLAreaElementCoords(value.string()); 67 invalidateCachedRegion(); 68 } else if (name == altAttr || name == accesskeyAttr) { 69 // Do nothing. 70 } else 71 HTMLAnchorElement::parseAttribute(name, value); 72 } 73 74 void HTMLAreaElement::invalidateCachedRegion() 75 { 76 m_lastSize = LayoutSize(-1, -1); 77 } 78 79 bool HTMLAreaElement::mapMouseEvent(LayoutPoint location, const LayoutSize& size, HitTestResult& result) 80 { 81 if (m_lastSize != size) { 82 m_region = adoptPtr(new Path(getRegion(size))); 83 m_lastSize = size; 84 } 85 86 if (!m_region->contains(location)) 87 return false; 88 89 result.setInnerNode(this); 90 result.setURLElement(this); 91 return true; 92 } 93 94 Path HTMLAreaElement::computePath(RenderObject* obj) const 95 { 96 if (!obj) 97 return Path(); 98 99 // FIXME: This doesn't work correctly with transforms. 100 FloatPoint absPos = obj->localToAbsolute(); 101 102 // Default should default to the size of the containing object. 103 LayoutSize size = m_lastSize; 104 if (m_shape == Default) 105 size = obj->absoluteOutlineBounds().size(); 106 107 Path p = getRegion(size); 108 float zoomFactor = obj->style()->effectiveZoom(); 109 if (zoomFactor != 1.0f) { 110 AffineTransform zoomTransform; 111 zoomTransform.scale(zoomFactor); 112 p.transform(zoomTransform); 113 } 114 115 p.translate(toFloatSize(absPos)); 116 return p; 117 } 118 119 LayoutRect HTMLAreaElement::computeRect(RenderObject* obj) const 120 { 121 return enclosingLayoutRect(computePath(obj).boundingRect()); 122 } 123 124 Path HTMLAreaElement::getRegion(const LayoutSize& size) const 125 { 126 if (m_coords.isEmpty() && m_shape != Default) 127 return Path(); 128 129 LayoutUnit width = size.width(); 130 LayoutUnit height = size.height(); 131 132 // If element omits the shape attribute, select shape based on number of coordinates. 133 Shape shape = m_shape; 134 if (shape == Unknown) { 135 if (m_coords.size() == 3) 136 shape = Circle; 137 else if (m_coords.size() == 4) 138 shape = Rect; 139 else if (m_coords.size() >= 6) 140 shape = Poly; 141 } 142 143 Path path; 144 RenderView* renderView = document().renderView(); 145 switch (shape) { 146 case Poly: 147 if (m_coords.size() >= 6) { 148 int numPoints = m_coords.size() / 2; 149 path.moveTo(FloatPoint(minimumValueForLength(m_coords[0], width, renderView), minimumValueForLength(m_coords[1], height, renderView))); 150 for (int i = 1; i < numPoints; ++i) 151 path.addLineTo(FloatPoint(minimumValueForLength(m_coords[i * 2], width, renderView), minimumValueForLength(m_coords[i * 2 + 1], height, renderView))); 152 path.closeSubpath(); 153 } 154 break; 155 case Circle: 156 if (m_coords.size() >= 3) { 157 Length radius = m_coords[2]; 158 int r = min(minimumValueForLength(radius, width, renderView), minimumValueForLength(radius, height, renderView)); 159 path.addEllipse(FloatRect(minimumValueForLength(m_coords[0], width, renderView) - r, minimumValueForLength(m_coords[1], height, renderView) - r, 2 * r, 2 * r)); 160 } 161 break; 162 case Rect: 163 if (m_coords.size() >= 4) { 164 int x0 = minimumValueForLength(m_coords[0], width, renderView); 165 int y0 = minimumValueForLength(m_coords[1], height, renderView); 166 int x1 = minimumValueForLength(m_coords[2], width, renderView); 167 int y1 = minimumValueForLength(m_coords[3], height, renderView); 168 path.addRect(FloatRect(x0, y0, x1 - x0, y1 - y0)); 169 } 170 break; 171 case Default: 172 path.addRect(FloatRect(0, 0, width, height)); 173 break; 174 case Unknown: 175 break; 176 } 177 178 return path; 179 } 180 181 HTMLImageElement* HTMLAreaElement::imageElement() const 182 { 183 Element* mapElement = parentElement(); 184 while (mapElement && !mapElement->hasTagName(mapTag)) 185 mapElement = mapElement->parentElement(); 186 187 if (!mapElement) 188 return 0; 189 190 return toHTMLMapElement(mapElement)->imageElement(); 191 } 192 193 bool HTMLAreaElement::isKeyboardFocusable() const 194 { 195 return isFocusable(); 196 } 197 198 bool HTMLAreaElement::isMouseFocusable() const 199 { 200 return isFocusable(); 201 } 202 203 bool HTMLAreaElement::rendererIsFocusable() const 204 { 205 HTMLImageElement* image = imageElement(); 206 if (!image || !image->renderer() || image->renderer()->style()->visibility() != VISIBLE) 207 return false; 208 209 return supportsFocus() && Element::tabIndex() >= 0; 210 } 211 212 void HTMLAreaElement::setFocus(bool shouldBeFocused) 213 { 214 if (focused() == shouldBeFocused) 215 return; 216 217 HTMLAnchorElement::setFocus(shouldBeFocused); 218 219 HTMLImageElement* imageElement = this->imageElement(); 220 if (!imageElement) 221 return; 222 223 RenderObject* renderer = imageElement->renderer(); 224 if (!renderer || !renderer->isImage()) 225 return; 226 227 toRenderImage(renderer)->areaElementFocusChanged(this); 228 } 229 230 void HTMLAreaElement::updateFocusAppearance(bool restorePreviousSelection) 231 { 232 if (!isFocusable()) 233 return; 234 235 HTMLImageElement* imageElement = this->imageElement(); 236 if (!imageElement) 237 return; 238 239 imageElement->updateFocusAppearance(restorePreviousSelection); 240 } 241 242 bool HTMLAreaElement::supportsFocus() const 243 { 244 // If the AREA element was a link, it should support focus. 245 // FIXME: This means that an AREA that is not a link cannot be made focusable through contenteditable or tabindex. Is it correct? 246 return isLink(); 247 } 248 249 String HTMLAreaElement::target() const 250 { 251 return getAttribute(targetAttr); 252 } 253 254 } 255