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/platform/graphics/Path.h" 29 #include "core/platform/graphics/transforms/AffineTransform.h" 30 #include "core/rendering/HitTestResult.h" 31 #include "core/rendering/RenderImage.h" 32 #include "core/rendering/RenderView.h" 33 34 using namespace std; 35 36 namespace WebCore { 37 38 using namespace HTMLNames; 39 40 inline HTMLAreaElement::HTMLAreaElement(const QualifiedName& tagName, Document* document) 41 : HTMLAnchorElement(tagName, document) 42 , m_lastSize(-1, -1) 43 , m_shape(Unknown) 44 { 45 ASSERT(hasTagName(areaTag)); 46 ScriptWrappable::init(this); 47 } 48 49 PassRefPtr<HTMLAreaElement> HTMLAreaElement::create(const QualifiedName& tagName, Document* document) 50 { 51 return adoptRef(new HTMLAreaElement(tagName, document)); 52 } 53 54 void HTMLAreaElement::parseAttribute(const QualifiedName& name, const AtomicString& value) 55 { 56 if (name == shapeAttr) { 57 if (equalIgnoringCase(value, "default")) 58 m_shape = Default; 59 else if (equalIgnoringCase(value, "circle")) 60 m_shape = Circle; 61 else if (equalIgnoringCase(value, "poly")) 62 m_shape = Poly; 63 else if (equalIgnoringCase(value, "rect")) 64 m_shape = Rect; 65 invalidateCachedRegion(); 66 } else if (name == coordsAttr) { 67 m_coords = parseHTMLAreaElementCoords(value.string()); 68 invalidateCachedRegion(); 69 } else if (name == altAttr || name == accesskeyAttr) { 70 // Do nothing. 71 } else 72 HTMLAnchorElement::parseAttribute(name, value); 73 } 74 75 void HTMLAreaElement::invalidateCachedRegion() 76 { 77 m_lastSize = LayoutSize(-1, -1); 78 } 79 80 bool HTMLAreaElement::mapMouseEvent(LayoutPoint location, const LayoutSize& size, HitTestResult& result) 81 { 82 if (m_lastSize != size) { 83 m_region = adoptPtr(new Path(getRegion(size))); 84 m_lastSize = size; 85 } 86 87 if (!m_region->contains(location)) 88 return false; 89 90 result.setInnerNode(this); 91 result.setURLElement(this); 92 return true; 93 } 94 95 Path HTMLAreaElement::computePath(RenderObject* obj) const 96 { 97 if (!obj) 98 return Path(); 99 100 // FIXME: This doesn't work correctly with transforms. 101 FloatPoint absPos = obj->localToAbsolute(); 102 103 // Default should default to the size of the containing object. 104 LayoutSize size = m_lastSize; 105 if (m_shape == Default) 106 size = obj->absoluteOutlineBounds().size(); 107 108 Path p = getRegion(size); 109 float zoomFactor = obj->style()->effectiveZoom(); 110 if (zoomFactor != 1.0f) { 111 AffineTransform zoomTransform; 112 zoomTransform.scale(zoomFactor); 113 p.transform(zoomTransform); 114 } 115 116 p.translate(toFloatSize(absPos)); 117 return p; 118 } 119 120 LayoutRect HTMLAreaElement::computeRect(RenderObject* obj) const 121 { 122 return enclosingLayoutRect(computePath(obj).boundingRect()); 123 } 124 125 Path HTMLAreaElement::getRegion(const LayoutSize& size) const 126 { 127 if (m_coords.isEmpty() && m_shape != Default) 128 return Path(); 129 130 LayoutUnit width = size.width(); 131 LayoutUnit height = size.height(); 132 133 // If element omits the shape attribute, select shape based on number of coordinates. 134 Shape shape = m_shape; 135 if (shape == Unknown) { 136 if (m_coords.size() == 3) 137 shape = Circle; 138 else if (m_coords.size() == 4) 139 shape = Rect; 140 else if (m_coords.size() >= 6) 141 shape = Poly; 142 } 143 144 Path path; 145 RenderView* renderView = document()->renderView(); 146 switch (shape) { 147 case Poly: 148 if (m_coords.size() >= 6) { 149 int numPoints = m_coords.size() / 2; 150 path.moveTo(FloatPoint(minimumValueForLength(m_coords[0], width, renderView), minimumValueForLength(m_coords[1], height, renderView))); 151 for (int i = 1; i < numPoints; ++i) 152 path.addLineTo(FloatPoint(minimumValueForLength(m_coords[i * 2], width, renderView), minimumValueForLength(m_coords[i * 2 + 1], height, renderView))); 153 path.closeSubpath(); 154 } 155 break; 156 case Circle: 157 if (m_coords.size() >= 3) { 158 Length radius = m_coords[2]; 159 int r = min(minimumValueForLength(radius, width, renderView), minimumValueForLength(radius, height, renderView)); 160 path.addEllipse(FloatRect(minimumValueForLength(m_coords[0], width, renderView) - r, minimumValueForLength(m_coords[1], height, renderView) - r, 2 * r, 2 * r)); 161 } 162 break; 163 case Rect: 164 if (m_coords.size() >= 4) { 165 int x0 = minimumValueForLength(m_coords[0], width, renderView); 166 int y0 = minimumValueForLength(m_coords[1], height, renderView); 167 int x1 = minimumValueForLength(m_coords[2], width, renderView); 168 int y1 = minimumValueForLength(m_coords[3], height, renderView); 169 path.addRect(FloatRect(x0, y0, x1 - x0, y1 - y0)); 170 } 171 break; 172 case Default: 173 path.addRect(FloatRect(0, 0, width, height)); 174 break; 175 case Unknown: 176 break; 177 } 178 179 return path; 180 } 181 182 HTMLImageElement* HTMLAreaElement::imageElement() const 183 { 184 Element* mapElement = parentElement(); 185 while (mapElement && !mapElement->hasTagName(mapTag)) 186 mapElement = mapElement->parentElement(); 187 188 if (!mapElement) 189 return 0; 190 191 return toHTMLMapElement(mapElement)->imageElement(); 192 } 193 194 bool HTMLAreaElement::isKeyboardFocusable() const 195 { 196 return isFocusable(); 197 } 198 199 bool HTMLAreaElement::isMouseFocusable() const 200 { 201 return isFocusable(); 202 } 203 204 bool HTMLAreaElement::rendererIsFocusable() const 205 { 206 HTMLImageElement* image = imageElement(); 207 if (!image || !image->renderer() || image->renderer()->style()->visibility() != VISIBLE) 208 return false; 209 210 return supportsFocus() && Element::tabIndex() >= 0; 211 } 212 213 void HTMLAreaElement::setFocus(bool shouldBeFocused) 214 { 215 if (focused() == shouldBeFocused) 216 return; 217 218 HTMLAnchorElement::setFocus(shouldBeFocused); 219 220 HTMLImageElement* imageElement = this->imageElement(); 221 if (!imageElement) 222 return; 223 224 RenderObject* renderer = imageElement->renderer(); 225 if (!renderer || !renderer->isImage()) 226 return; 227 228 toRenderImage(renderer)->areaElementFocusChanged(this); 229 } 230 231 void HTMLAreaElement::updateFocusAppearance(bool restorePreviousSelection) 232 { 233 if (!isFocusable()) 234 return; 235 236 HTMLImageElement* imageElement = this->imageElement(); 237 if (!imageElement) 238 return; 239 240 imageElement->updateFocusAppearance(restorePreviousSelection); 241 } 242 243 bool HTMLAreaElement::supportsFocus() const 244 { 245 // If the AREA element was a link, it should support focus. 246 // FIXME: This means that an AREA that is not a link cannot be made focusable through contenteditable or tabindex. Is it correct? 247 return isLink(); 248 } 249 250 String HTMLAreaElement::target() const 251 { 252 return getAttribute(targetAttr); 253 } 254 255 } 256