1/*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 *           (C) 1999 Antti Koivisto (koivisto@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 "HTMLAreaElement.h"
24
25#include "AffineTransform.h"
26#include "Attribute.h"
27#include "Frame.h"
28#include "HTMLImageElement.h"
29#include "HTMLMapElement.h"
30#include "HTMLNames.h"
31#include "HitTestResult.h"
32#include "Path.h"
33#include "RenderImage.h"
34#include "RenderView.h"
35
36using namespace std;
37
38namespace WebCore {
39
40using namespace HTMLNames;
41
42inline HTMLAreaElement::HTMLAreaElement(const QualifiedName& tagName, Document* document)
43    : HTMLAnchorElement(tagName, document)
44    , m_coordsLen(0)
45    , m_lastSize(-1, -1)
46    , m_shape(Unknown)
47{
48    ASSERT(hasTagName(areaTag));
49}
50
51PassRefPtr<HTMLAreaElement> HTMLAreaElement::create(const QualifiedName& tagName, Document* document)
52{
53    return adoptRef(new HTMLAreaElement(tagName, document));
54}
55
56void HTMLAreaElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
57{
58    if (name == shapeAttr) {
59        if (equalIgnoringCase(value, "default"))
60            m_shape = Default;
61        else if (equalIgnoringCase(value, "circle"))
62            m_shape = Circle;
63        else if (equalIgnoringCase(value, "poly"))
64            m_shape = Poly;
65        else if (equalIgnoringCase(value, "rect"))
66            m_shape = Rect;
67        invalidateCachedRegion();
68    } else if (name == coordsAttr) {
69        m_coords = newCoordsArray(value.string(), m_coordsLen);
70        invalidateCachedRegion();
71    } else if (name == altAttr || name == accesskeyAttr) {
72        // Do nothing.
73    } else
74        HTMLAnchorElement::parseAttribute(name, value);
75}
76
77void HTMLAreaElement::invalidateCachedRegion()
78{
79    m_lastSize = LayoutSize(-1, -1);
80}
81
82bool HTMLAreaElement::mapMouseEvent(LayoutPoint location, const LayoutSize& size, HitTestResult& result)
83{
84    if (m_lastSize != size) {
85        m_region = adoptPtr(new Path(getRegion(size)));
86        m_lastSize = size;
87    }
88
89    if (!m_region->contains(location))
90        return false;
91
92    result.setInnerNode(this);
93    result.setURLElement(this);
94    return true;
95}
96
97Path HTMLAreaElement::computePath(RenderObject* obj) const
98{
99    if (!obj)
100        return Path();
101
102    // FIXME: This doesn't work correctly with transforms.
103    FloatPoint absPos = obj->localToAbsolute();
104
105    // Default should default to the size of the containing object.
106    LayoutSize size = m_lastSize;
107    if (m_shape == Default)
108        size = obj->absoluteOutlineBounds().size();
109
110    Path p = getRegion(size);
111    float zoomFactor = obj->style()->effectiveZoom();
112    if (zoomFactor != 1.0f) {
113        AffineTransform zoomTransform;
114        zoomTransform.scale(zoomFactor);
115        p.transform(zoomTransform);
116    }
117
118    p.translate(toFloatSize(absPos));
119    return p;
120}
121
122LayoutRect HTMLAreaElement::computeRect(RenderObject* obj) const
123{
124    return enclosingLayoutRect(computePath(obj).fastBoundingRect());
125}
126
127Path HTMLAreaElement::getRegion(const LayoutSize& size) const
128{
129    if (!m_coords && m_shape != Default)
130        return Path();
131
132    LayoutUnit width = size.width();
133    LayoutUnit height = size.height();
134
135    // If element omits the shape attribute, select shape based on number of coordinates.
136    Shape shape = m_shape;
137    if (shape == Unknown) {
138        if (m_coordsLen == 3)
139            shape = Circle;
140        else if (m_coordsLen == 4)
141            shape = Rect;
142        else if (m_coordsLen >= 6)
143            shape = Poly;
144    }
145
146    Path path;
147    RenderView* renderView = document()->renderView();
148    switch (shape) {
149        case Poly:
150            if (m_coordsLen >= 6) {
151                int numPoints = m_coordsLen / 2;
152                path.moveTo(FloatPoint(minimumValueForLength(m_coords[0], width, renderView), minimumValueForLength(m_coords[1], height, renderView)));
153                for (int i = 1; i < numPoints; ++i)
154                    path.addLineTo(FloatPoint(minimumValueForLength(m_coords[i * 2], width, renderView), minimumValueForLength(m_coords[i * 2 + 1], height, renderView)));
155                path.closeSubpath();
156            }
157            break;
158        case Circle:
159            if (m_coordsLen >= 3) {
160                Length radius = m_coords[2];
161                int r = min(minimumValueForLength(radius, width, renderView), minimumValueForLength(radius, height, renderView));
162                path.addEllipse(FloatRect(minimumValueForLength(m_coords[0], width, renderView) - r, minimumValueForLength(m_coords[1], height, renderView) - r, 2 * r, 2 * r));
163            }
164            break;
165        case Rect:
166            if (m_coordsLen >= 4) {
167                int x0 = minimumValueForLength(m_coords[0], width, renderView);
168                int y0 = minimumValueForLength(m_coords[1], height, renderView);
169                int x1 = minimumValueForLength(m_coords[2], width, renderView);
170                int y1 = minimumValueForLength(m_coords[3], height, renderView);
171                path.addRect(FloatRect(x0, y0, x1 - x0, y1 - y0));
172            }
173            break;
174        case Default:
175            path.addRect(FloatRect(0, 0, width, height));
176            break;
177        case Unknown:
178            break;
179    }
180
181    return path;
182}
183
184HTMLImageElement* HTMLAreaElement::imageElement() const
185{
186    Node* mapElement = parentNode();
187    if (!mapElement || !mapElement->hasTagName(mapTag))
188        return 0;
189
190    return static_cast<HTMLMapElement*>(mapElement)->imageElement();
191}
192
193bool HTMLAreaElement::isKeyboardFocusable(KeyboardEvent*) const
194{
195    return isFocusable();
196}
197
198bool HTMLAreaElement::isMouseFocusable() const
199{
200    return isFocusable();
201}
202
203bool HTMLAreaElement::isFocusable() 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
212void 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->isRenderImage())
225        return;
226
227    toRenderImage(renderer)->areaElementFocusChanged(this);
228}
229
230void 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
242bool HTMLAreaElement::supportsFocus() const
243{
244    // If the AREA element was a link, it should support focus.
245    // The inherited method is not used because it assumes that a render object must exist
246    // for the element to support focus. AREA elements do not have render objects.
247    return isLink();
248}
249
250String HTMLAreaElement::target() const
251{
252    return getAttribute(targetAttr);
253}
254
255#if ENABLE(MICRODATA)
256String HTMLAreaElement::itemValueText() const
257{
258    return getURLAttribute(hrefAttr);
259}
260
261void HTMLAreaElement::setItemValueText(const String& value, ExceptionCode&)
262{
263    setAttribute(hrefAttr, value);
264}
265#endif
266
267}
268