1/*
2 * Copyright (c) 2011, Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#ifndef PopupListBox_h
32#define PopupListBox_h
33
34#include "core/dom/Element.h"
35#include "platform/scroll/ScrollTypes.h"
36#include "platform/scroll/ScrollView.h"
37#include "platform/text/TextDirection.h"
38#include "wtf/text/WTFString.h"
39
40namespace blink {
41
42class Font;
43class GraphicsContext;
44class IntRect;
45class PlatformKeyboardEvent;
46class PlatformMouseEvent;
47class PlatformGestureEvent;
48class PlatformTouchEvent;
49class PlatformWheelEvent;
50class PopupContainer;
51class PopupMenuClient;
52typedef unsigned long long TimeStamp;
53
54class PopupContent {
55public:
56    virtual void layout() = 0;
57    virtual void setMaxHeight(int) = 0;
58    virtual void setMaxWidthAndLayout(int) = 0;
59    virtual int popupContentHeight() const = 0;
60    virtual ~PopupContent() { };
61};
62
63// A container for the data for each menu item (e.g. represented by <option>
64// or <optgroup> in a <select> widget) and is used by PopupListBox.
65struct PopupItem {
66    enum Type {
67        TypeOption,
68        TypeGroup,
69        TypeSeparator
70    };
71
72    PopupItem(const String& label, Type type)
73        : label(label)
74        , type(type)
75        , yOffset(0)
76    {
77    }
78    String label;
79    Type type;
80    int yOffset; // y offset of this item, relative to the top of the popup.
81    TextDirection textDirection;
82    bool hasTextDirectionOverride;
83    bool enabled;
84    bool displayNone;
85};
86
87// This class manages the scrollable content inside a <select> popup.
88class PopupListBox FINAL : public Widget, public ScrollableArea, public PopupContent {
89public:
90    static PassRefPtr<PopupListBox> create(PopupMenuClient* client, bool deviceSupportsTouch, PopupContainer* container)
91    {
92        return adoptRef(new PopupListBox(client, deviceSupportsTouch, container));
93    }
94
95    // Widget
96    virtual void invalidateRect(const IntRect&) OVERRIDE;
97    virtual void paint(GraphicsContext*, const IntRect&) OVERRIDE;
98    virtual HostWindow* hostWindow() const OVERRIDE;
99    virtual void setFrameRect(const IntRect&) OVERRIDE;
100    virtual IntPoint convertChildToSelf(const Widget* child, const IntPoint&) const OVERRIDE;
101    virtual IntPoint convertSelfToChild(const Widget* child, const IntPoint&) const OVERRIDE;
102
103    // ScrollableArea
104    virtual void invalidateScrollbarRect(Scrollbar*, const IntRect&) OVERRIDE;
105    virtual bool isActive() const OVERRIDE;
106    virtual bool scrollbarsCanBeActive() const OVERRIDE;
107    virtual IntRect scrollableAreaBoundingBox() const OVERRIDE;
108    virtual bool shouldPlaceVerticalScrollbarOnLeft() const OVERRIDE;
109    virtual int scrollSize(ScrollbarOrientation) const OVERRIDE;
110    virtual void setScrollOffset(const IntPoint&) OVERRIDE;
111    virtual bool isScrollCornerVisible() const OVERRIDE { return false; }
112    virtual bool userInputScrollable(ScrollbarOrientation orientation) const OVERRIDE { return orientation == VerticalScrollbar; }
113    virtual Scrollbar* verticalScrollbar() const OVERRIDE { return m_verticalScrollbar.get(); }
114    virtual IntRect visibleContentRect(IncludeScrollbarsInRect = ExcludeScrollbars) const OVERRIDE;
115    virtual IntSize contentsSize() const OVERRIDE { return m_contentsSize; }
116    virtual IntPoint scrollPosition() const OVERRIDE { return visibleContentRect().location(); }
117    virtual IntPoint maximumScrollPosition() const OVERRIDE; // The maximum position we can be scrolled to.
118    virtual IntPoint minimumScrollPosition() const OVERRIDE; // The minimum position we can be scrolled to.
119    virtual IntRect scrollCornerRect() const OVERRIDE { return IntRect(); }
120
121    // PopupListBox methods
122
123    bool handleMouseDownEvent(const PlatformMouseEvent&);
124    bool handleMouseMoveEvent(const PlatformMouseEvent&);
125    bool handleMouseReleaseEvent(const PlatformMouseEvent&);
126    bool handleWheelEvent(const PlatformWheelEvent&);
127    bool handleKeyEvent(const PlatformKeyboardEvent&);
128    bool handleTouchEvent(const PlatformTouchEvent&);
129    bool handleGestureEvent(const PlatformGestureEvent&);
130
131    // Closes the popup
132    void abandon();
133
134    // Updates our internal list to match the client.
135    void updateFromElement();
136
137    // Frees any allocated resources used in a particular popup session.
138    void clear();
139
140    // Sets the index of the option that is displayed in the <select> widget in the page
141    void setOriginalIndex(int);
142
143    // Gets the index of the item that the user is currently moused over or has
144    // selected with the keyboard. This is not the same as the original index,
145    // since the user has not yet accepted this input.
146    int selectedIndex() const { return m_selectedIndex; }
147
148    // Moves selection down/up the given number of items, scrolling if necessary.
149    // Positive is down. The resulting index will be clamped to the range
150    // [0, numItems), and non-option items will be skipped.
151    void adjustSelectedIndex(int delta);
152
153    // Returns the number of items in the list.
154    int numItems() const { return static_cast<int>(m_items.size()); }
155
156    void setBaseWidth(int width) { m_baseWidth = std::min(m_maxWindowWidth, width); }
157
158    // Computes the size of widget and children.
159    virtual void layout() OVERRIDE;
160
161    // Returns whether the popup wants to process events for the passed key.
162    bool isInterestedInEventForKey(int keyCode);
163
164    // Gets the height of a row.
165    int getRowHeight(int index) const;
166
167    int getRowBaseWidth(int index);
168
169    virtual void setMaxHeight(int maxHeight) OVERRIDE { m_maxHeight = maxHeight; }
170
171    void setMaxWidth(int maxWidth) { m_maxWindowWidth = maxWidth; }
172
173    virtual void setMaxWidthAndLayout(int) OVERRIDE;
174
175    void disconnectClient() { m_popupClient = 0; }
176
177    const Vector<PopupItem*>& items() const { return m_items; }
178
179    virtual int popupContentHeight() const OVERRIDE;
180
181    static const int defaultMaxHeight;
182
183protected:
184    virtual void invalidateScrollCornerRect(const IntRect&) OVERRIDE { }
185
186private:
187    friend class PopupContainer;
188    friend class RefCounted<PopupListBox>;
189
190    PopupListBox(PopupMenuClient*, bool deviceSupportsTouch, PopupContainer*);
191    virtual ~PopupListBox();
192
193    // Hides the popup. Other classes should not call this. Use abandon instead.
194    void hidePopup();
195
196    // Returns true if the selection can be changed to index.
197    // Disabled items, or labels cannot be selected.
198    bool isSelectableItem(int index);
199
200    // Select an index in the list, scrolling if necessary.
201    void selectIndex(int index);
202
203    // Accepts the selected index as the value to be displayed in the <select>
204    // widget on the web page, and closes the popup. Returns true if index is
205    // accepted.
206    bool acceptIndex(int index);
207
208    // Clears the selection (so no row appears selected).
209    void clearSelection();
210
211    // Scrolls to reveal the given index.
212    void scrollToRevealRow(int index);
213    void scrollToRevealSelection() { scrollToRevealRow(m_selectedIndex); }
214
215    // Invalidates the row at the given index.
216    void invalidateRow(int index);
217
218    // Get the bounds of a row.
219    IntRect getRowBounds(int index);
220
221    // Converts a point to an index of the row the point is over
222    int pointToRowIndex(const IntPoint&);
223
224    // Paint an individual row
225    void paintRow(GraphicsContext*, const IntRect&, int rowIndex);
226
227    // Test if the given point is within the bounds of the popup window.
228    bool isPointInBounds(const IntPoint&);
229
230    // Called when the user presses a text key. Does a prefix-search of the items.
231    void typeAheadFind(const PlatformKeyboardEvent&);
232
233    // Returns the font to use for the given row
234    Font getRowFont(int index) const;
235
236    // Moves the selection down/up one item, taking care of looping back to the
237    // first/last element if m_loopSelectionNavigation is true.
238    void selectPreviousRow();
239    void selectNextRow();
240
241    int scrollX() const { return scrollPosition().x(); }
242    int scrollY() const { return scrollPosition().y(); }
243    void updateScrollbars(IntPoint desiredOffset);
244    void setHasVerticalScrollbar(bool);
245    Scrollbar* scrollbarAtWindowPoint(const IntPoint& windowPoint);
246    IntRect contentsToWindow(const IntRect&) const;
247    void setContentsSize(const IntSize&);
248    void adjustScrollbarExistence();
249    void updateScrollbarGeometry();
250    IntRect windowClipRect() const;
251
252    // If the device is a touch screen we increase the height of menu items
253    // to make it easier to unambiguously touch them.
254    bool m_deviceSupportsTouch;
255
256    // This is the index of the item marked as "selected" - i.e. displayed in
257    // the widget on the page.
258    int m_originalIndex;
259
260    // This is the index of the item that the user is hovered over or has
261    // selected using the keyboard in the list. They have not confirmed this
262    // selection by clicking or pressing enter yet however.
263    int m_selectedIndex;
264
265    // If >= 0, this is the index we should accept if the popup is "abandoned".
266    // This is used for keyboard navigation, where we want the
267    // selection to change immediately, and is only used if the settings
268    // acceptOnAbandon field is true.
269    int m_acceptedIndexOnAbandon;
270
271    // This is the number of rows visible in the popup. The maximum number
272    // visible at a time is defined as being kMaxVisibleRows. For a scrolled
273    // popup, this can be thought of as the page size in data units.
274    int m_visibleRows;
275
276    // Our suggested width, not including scrollbar.
277    int m_baseWidth;
278
279    // The maximum height we can be without being off-screen.
280    int m_maxHeight;
281
282    // A list of the options contained within the <select>
283    Vector<PopupItem*> m_items;
284
285    // The <select> PopupMenuClient that opened us.
286    PopupMenuClient* m_popupClient;
287
288    // The scrollbar which has mouse capture. Mouse events go straight to this
289    // if not null.
290    RefPtr<Scrollbar> m_capturingScrollbar;
291
292    // The last scrollbar that the mouse was over. Used for mouseover highlights.
293    RefPtr<Scrollbar> m_lastScrollbarUnderMouse;
294
295    // The string the user has typed so far into the popup. Used for typeAheadFind.
296    String m_typedString;
297
298    // The char the user has hit repeatedly. Used for typeAheadFind.
299    UChar m_repeatingChar;
300
301    // The last time the user hit a key. Used for typeAheadFind.
302    TimeStamp m_lastCharTime;
303
304    // If width exeeds screen width, we have to clip it.
305    int m_maxWindowWidth;
306
307    // To forward last mouse release event.
308    RefPtrWillBePersistent<Element> m_focusedElement;
309
310    PopupContainer* m_container;
311
312    RefPtr<Scrollbar> m_verticalScrollbar;
313    IntSize m_contentsSize;
314    IntPoint m_scrollOffset;
315};
316
317} // namespace blink
318
319#endif
320