1/*
2 * Copyright (C) 2010, 2011, 2012 Research In Motion Limited. All rights reserved.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18
19#ifndef FatFingers_h
20#define FatFingers_h
21
22#include "HitTestResult.h"
23#include "RenderLayer.h"
24
25#include <BlackBerryPlatformIntRectRegion.h>
26
27#include <utility>
28
29#include <wtf/HashSet.h>
30#include <wtf/ListHashSet.h>
31#include <wtf/Vector.h>
32
33namespace WebCore {
34class Document;
35class Element;
36class IntPoint;
37class IntRect;
38class IntSize;
39class Node;
40}
41
42#define DEBUG_FAT_FINGERS 0
43
44namespace BlackBerry {
45namespace WebKit {
46
47class WebPagePrivate;
48class FatFingersResult;
49class TouchEventHandler;
50
51
52class FatFingers {
53public:
54    enum TargetType { ClickableElement, Text };
55
56    FatFingers(WebPagePrivate* webpage, const WebCore::IntPoint& contentPos, TargetType);
57    ~FatFingers();
58
59    const FatFingersResult findBestPoint();
60
61#if DEBUG_FAT_FINGERS
62    // These debug vars are all in content coordinates. They are public so
63    // they can be read from BackingStore, which will draw a visible rect
64    // around the fat fingers area.
65    static WebCore::IntRect m_debugFatFingerRect;
66    static WebCore::IntPoint m_debugFatFingerClickPosition;
67    static WebCore::IntPoint m_debugFatFingerAdjustedPosition;
68#endif
69
70private:
71    enum MatchingApproachForClickable { ClickableByDefault = 0, MadeClickableByTheWebpage, Done };
72
73    typedef std::pair<WebCore::Node*, Platform::IntRectRegion> IntersectingRegion;
74
75    bool checkFingerIntersection(const Platform::IntRectRegion&,
76        const Platform::IntRectRegion& remainingFingerRegion,
77        WebCore::Node*,
78        Vector<IntersectingRegion>& intersectingRegions);
79
80    bool findIntersectingRegions(WebCore::Document*,
81        Vector<IntersectingRegion>& intersectingRegions,
82        Platform::IntRectRegion& remainingFingerRegion);
83
84    bool checkForClickableElement(WebCore::Element*,
85        Vector<IntersectingRegion>& intersectingRegions,
86        Platform::IntRectRegion& remainingFingerRegion,
87        WebCore::RenderLayer*& lowestPositionedEnclosingLayerSoFar);
88
89    bool checkForText(WebCore::Node*,
90        Vector<IntersectingRegion>& intersectingRegions,
91        Platform::IntRectRegion& fingerRegion);
92
93    void setSuccessfulFatFingersResult(FatFingersResult&, WebCore::Node*, const WebCore::IntPoint&);
94
95    void getNodesFromRect(WebCore::Document*, const WebCore::IntPoint&, ListHashSet<RefPtr<WebCore::Node> >&);
96
97    bool isElementClickable(WebCore::Element*) const;
98
99    inline WebCore::IntRect fingerRectForPoint(const WebCore::IntPoint&) const;
100    void getAdjustedPaddings(const WebCore::IntPoint&, unsigned& top, unsigned& right, unsigned& bottom, unsigned& left) const;
101
102    WebPagePrivate* m_webPage;
103    WebCore::IntPoint m_contentPos;
104    TargetType m_targetType;
105};
106
107class FatFingersResult {
108public:
109
110    FatFingersResult(const WebCore::IntPoint& p = WebCore::IntPoint::zero(), const FatFingers::TargetType targetType = FatFingers::ClickableElement)
111        : m_originalPosition(p)
112        , m_adjustedPosition(p)
113        , m_targetType(targetType)
114        , m_positionWasAdjusted(false)
115        , m_isTextInput(false)
116        , m_isValid(false)
117        , m_nodeUnderFatFinger(0)
118    {
119    }
120
121    void reset()
122    {
123        m_originalPosition = m_adjustedPosition = WebCore::IntPoint::zero();
124        m_positionWasAdjusted = false;
125        m_isTextInput = false;
126        m_isValid = false;
127        m_nodeUnderFatFinger = 0;
128    }
129
130    bool resultMatches(const WebCore::IntPoint& p, const FatFingers::TargetType targetType) const
131    {
132        return m_isValid && p == m_originalPosition && targetType == m_targetType;
133    }
134
135    WebCore::IntPoint originPosition() const { return m_originalPosition; }
136    WebCore::IntPoint adjustedPosition() const { return m_adjustedPosition; }
137    bool positionWasAdjusted() const { return m_isValid && m_positionWasAdjusted; }
138    bool isTextInput() const { return m_isValid && !!m_nodeUnderFatFinger && m_isTextInput; }
139    bool isValid() const { return m_isValid; }
140
141    enum ContentType { ShadowContentAllowed, ShadowContentNotAllowed };
142
143    WebCore::Node* node(ContentType type = ShadowContentAllowed, bool shouldUseRootEditableElement = false) const
144    {
145        if (!m_nodeUnderFatFinger || !m_nodeUnderFatFinger->inDocument())
146            return 0;
147
148        WebCore::Node* result = m_nodeUnderFatFinger.get();
149
150        if (type == ShadowContentAllowed)
151            return result;
152
153        // Shadow trees can be nested.
154        while (result->isInShadowTree())
155            result = toElement(result->deprecatedShadowAncestorNode());
156
157        if (!shouldUseRootEditableElement || !result->isElementNode())
158            return result;
159
160        // Retrieve the top level editable node
161        while (!result->isRootEditableElement()) {
162            WebCore::Element* parentElement = result->parentElement();
163            if (!parentElement)
164                break;
165            result = parentElement;
166        }
167
168        return result;
169    }
170
171    WebCore::Element* nodeAsElementIfApplicable(ContentType type = ShadowContentAllowed, bool shouldUseRootEditableElement = false) const
172    {
173        WebCore::Node* result = node(type, shouldUseRootEditableElement);
174        if (!result || !result->isElementNode())
175            return 0;
176
177        return static_cast<WebCore::Element*>(result);
178    }
179
180private:
181    friend class WebKit::FatFingers;
182    friend class WebKit::TouchEventHandler;
183
184    WebCore::IntPoint m_originalPosition; // Main frame contents coordinates.
185    WebCore::IntPoint m_adjustedPosition; // Main frame contents coordinates.
186    FatFingers::TargetType m_targetType;
187    bool m_positionWasAdjusted;
188    bool m_isTextInput; // Check if the element under the touch point will require a VKB be displayed so that the touch down can be suppressed.
189    bool m_isValid;
190    RefPtr<WebCore::Node> m_nodeUnderFatFinger;
191};
192
193}
194}
195
196#endif // FatFingers_h
197
198