AutoFillPopupMenuClient.cpp revision 65f03d4f644ce73618e5f4f50dd694b26f55ae12
1/*
2 * Copyright (C) 2010 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#include "config.h"
32#include "AutoFillPopupMenuClient.h"
33
34#include "CSSStyleSelector.h"
35#include "CSSValueKeywords.h"
36#include "Chrome.h"
37#include "FrameView.h"
38#include "HTMLInputElement.h"
39#include "RenderTheme.h"
40#include "WebAutoFillClient.h"
41#include "WebNode.h"
42#include "WebString.h"
43#include "WebVector.h"
44#include "WebViewClient.h"
45#include "WebViewImpl.h"
46
47using namespace WebCore;
48
49namespace WebKit {
50
51AutoFillPopupMenuClient::AutoFillPopupMenuClient()
52    : m_separatorIndex(-1)
53    , m_selectedIndex(-1)
54    , m_textField(0)
55{
56}
57
58AutoFillPopupMenuClient::~AutoFillPopupMenuClient()
59{
60}
61
62unsigned AutoFillPopupMenuClient::getSuggestionsCount() const
63{
64    return m_names.size() + ((m_separatorIndex == -1) ? 0 : 1);
65}
66
67WebString AutoFillPopupMenuClient::getSuggestion(unsigned listIndex) const
68{
69    int index = convertListIndexToInternalIndex(listIndex);
70    if (index == -1)
71        return WebString();
72
73    ASSERT(index >= 0 && static_cast<size_t>(index) < m_names.size());
74    return m_names[index];
75}
76
77WebString AutoFillPopupMenuClient::getLabel(unsigned listIndex) const
78{
79    int index = convertListIndexToInternalIndex(listIndex);
80    if (index == -1)
81        return WebString();
82
83    ASSERT(index >= 0 && static_cast<size_t>(index) < m_labels.size());
84    return m_labels[index];
85}
86
87WebString AutoFillPopupMenuClient::getIcon(unsigned listIndex) const
88{
89    int index = convertListIndexToInternalIndex(listIndex);
90    if (index == -1)
91        return WebString();
92
93    ASSERT(index >= 0 && static_cast<size_t>(index) < m_icons.size());
94    return m_icons[index];
95}
96
97void AutoFillPopupMenuClient::removeSuggestionAtIndex(unsigned listIndex)
98{
99    if (!canRemoveSuggestionAtIndex(listIndex))
100        return;
101
102    int index = convertListIndexToInternalIndex(listIndex);
103
104    ASSERT(static_cast<unsigned>(index) < m_names.size());
105
106    m_names.remove(index);
107    m_labels.remove(index);
108    m_icons.remove(index);
109    m_uniqueIDs.remove(index);
110
111    // Shift the separator index if necessary.
112    if (m_separatorIndex != -1)
113        m_separatorIndex--;
114}
115
116bool AutoFillPopupMenuClient::canRemoveSuggestionAtIndex(unsigned listIndex)
117{
118    // Only allow deletion of items before the separator that have unique id 0
119    // (i.e. are autocomplete rather than autofill items).
120    int index = convertListIndexToInternalIndex(listIndex);
121    return !m_uniqueIDs[index] && (m_separatorIndex == -1 || listIndex < static_cast<unsigned>(m_separatorIndex));
122}
123
124void AutoFillPopupMenuClient::valueChanged(unsigned listIndex, bool fireEvents)
125{
126    WebViewImpl* webView = getWebView();
127    if (!webView)
128        return;
129
130    if (m_separatorIndex != -1 && listIndex > static_cast<unsigned>(m_separatorIndex))
131        --listIndex;
132
133    ASSERT(listIndex < m_names.size());
134
135    webView->autoFillClient()->didAcceptAutoFillSuggestion(WebNode(getTextField()),
136                                                           m_names[listIndex],
137                                                           m_labels[listIndex],
138                                                           m_uniqueIDs[listIndex],
139                                                           listIndex);
140}
141
142void AutoFillPopupMenuClient::selectionChanged(unsigned listIndex, bool fireEvents)
143{
144    WebViewImpl* webView = getWebView();
145    if (!webView)
146        return;
147
148    if (m_separatorIndex != -1 && listIndex > static_cast<unsigned>(m_separatorIndex))
149        --listIndex;
150
151    ASSERT(listIndex < m_names.size());
152
153    webView->autoFillClient()->didSelectAutoFillSuggestion(WebNode(getTextField()),
154                                                           m_names[listIndex],
155                                                           m_labels[listIndex],
156                                                           m_uniqueIDs[listIndex]);
157}
158
159void AutoFillPopupMenuClient::selectionCleared()
160{
161    WebViewImpl* webView = getWebView();
162    if (webView)
163        webView->autoFillClient()->didClearAutoFillSelection(WebNode(getTextField()));
164}
165
166String AutoFillPopupMenuClient::itemText(unsigned listIndex) const
167{
168    return getSuggestion(listIndex);
169}
170
171String AutoFillPopupMenuClient::itemLabel(unsigned listIndex) const
172{
173    return getLabel(listIndex);
174}
175
176String AutoFillPopupMenuClient::itemIcon(unsigned listIndex) const
177{
178    return getIcon(listIndex);
179}
180
181bool AutoFillPopupMenuClient::itemIsEnabled(unsigned listIndex) const
182{
183    return !itemIsWarning(listIndex);
184}
185
186PopupMenuStyle AutoFillPopupMenuClient::itemStyle(unsigned listIndex) const
187{
188    return itemIsWarning(listIndex) ? *m_warningStyle : *m_regularStyle;
189}
190
191PopupMenuStyle AutoFillPopupMenuClient::menuStyle() const
192{
193    return *m_regularStyle;
194}
195
196int AutoFillPopupMenuClient::clientPaddingLeft() const
197{
198    // Bug http://crbug.com/7708 seems to indicate the style can be 0.
199    RenderStyle* style = textFieldStyle();
200    if (!style)
201       return 0;
202
203    return RenderTheme::defaultTheme()->popupInternalPaddingLeft(style);
204}
205
206int AutoFillPopupMenuClient::clientPaddingRight() const
207{
208    // Bug http://crbug.com/7708 seems to indicate the style can be 0.
209    RenderStyle* style = textFieldStyle();
210    if (!style)
211        return 0;
212
213    return RenderTheme::defaultTheme()->popupInternalPaddingRight(style);
214}
215
216void AutoFillPopupMenuClient::popupDidHide()
217{
218    WebViewImpl* webView = getWebView();
219    if (!webView)
220        return;
221
222    webView->autoFillPopupDidHide();
223    webView->autoFillClient()->didClearAutoFillSelection(WebNode(getTextField()));
224}
225
226bool AutoFillPopupMenuClient::itemIsSeparator(unsigned listIndex) const
227{
228    return (m_separatorIndex != -1 && static_cast<unsigned>(m_separatorIndex) == listIndex);
229}
230
231bool AutoFillPopupMenuClient::itemIsWarning(unsigned listIndex) const
232{
233    int index = convertListIndexToInternalIndex(listIndex);
234    if (index == -1)
235        return false;
236
237    ASSERT(index >= 0 && static_cast<size_t>(index) < m_uniqueIDs.size());
238    return m_uniqueIDs[index] < 0;
239}
240
241void AutoFillPopupMenuClient::setTextFromItem(unsigned listIndex)
242{
243    m_textField->setValue(getSuggestion(listIndex));
244}
245
246FontSelector* AutoFillPopupMenuClient::fontSelector() const
247{
248    return m_textField->document()->styleSelector()->fontSelector();
249}
250
251HostWindow* AutoFillPopupMenuClient::hostWindow() const
252{
253    return m_textField->document()->view()->hostWindow();
254}
255
256PassRefPtr<Scrollbar> AutoFillPopupMenuClient::createScrollbar(
257    ScrollbarClient* client,
258    ScrollbarOrientation orientation,
259    ScrollbarControlSize size)
260{
261    return Scrollbar::createNativeScrollbar(client, orientation, size);
262}
263
264void AutoFillPopupMenuClient::initialize(
265    HTMLInputElement* textField,
266    const WebVector<WebString>& names,
267    const WebVector<WebString>& labels,
268    const WebVector<WebString>& icons,
269    const WebVector<int>& uniqueIDs,
270    int separatorIndex)
271{
272    ASSERT(names.size() == labels.size());
273    ASSERT(names.size() == icons.size());
274    ASSERT(names.size() == uniqueIDs.size());
275    ASSERT(separatorIndex < static_cast<int>(names.size()));
276
277    m_selectedIndex = -1;
278    m_textField = textField;
279
280    // The suggestions must be set before initializing the
281    // AutoFillPopupMenuClient.
282    setSuggestions(names, labels, icons, uniqueIDs, separatorIndex);
283
284    FontDescription regularFontDescription;
285    RenderTheme::defaultTheme()->systemFont(CSSValueWebkitControl,
286                                            regularFontDescription);
287    RenderStyle* style = m_textField->computedStyle();
288    regularFontDescription.setComputedSize(style->fontDescription().computedSize());
289
290    Font regularFont(regularFontDescription, 0, 0);
291    regularFont.update(textField->document()->styleSelector()->fontSelector());
292    // The direction of text in popup menu is set the same as the direction of
293    // the input element: textField.
294    m_regularStyle.set(new PopupMenuStyle(Color::black, Color::white, regularFont,
295                                          true, false, Length(WebCore::Fixed),
296                                          textField->renderer()->style()->direction()));
297
298    FontDescription warningFontDescription = regularFont.fontDescription();
299    warningFontDescription.setItalic(true);
300    Font warningFont(warningFontDescription, regularFont.letterSpacing(), regularFont.wordSpacing());
301    warningFont.update(regularFont.fontSelector());
302    m_warningStyle.set(new PopupMenuStyle(Color::darkGray,
303                                          m_regularStyle->backgroundColor(),
304                                          warningFont,
305                                          m_regularStyle->isVisible(),
306                                          m_regularStyle->isDisplayNone(),
307                                          m_regularStyle->textIndent(),
308                                          m_regularStyle->textDirection()));
309}
310
311void AutoFillPopupMenuClient::setSuggestions(const WebVector<WebString>& names,
312                                             const WebVector<WebString>& labels,
313                                             const WebVector<WebString>& icons,
314                                             const WebVector<int>& uniqueIDs,
315                                             int separatorIndex)
316{
317    ASSERT(names.size() == labels.size());
318    ASSERT(names.size() == icons.size());
319    ASSERT(names.size() == uniqueIDs.size());
320    ASSERT(separatorIndex < static_cast<int>(names.size()));
321
322    m_names.clear();
323    m_labels.clear();
324    m_icons.clear();
325    m_uniqueIDs.clear();
326    for (size_t i = 0; i < names.size(); ++i) {
327        m_names.append(names[i]);
328        m_labels.append(labels[i]);
329        m_icons.append(icons[i]);
330        m_uniqueIDs.append(uniqueIDs[i]);
331    }
332
333    m_separatorIndex = separatorIndex;
334
335    // Try to preserve selection if possible.
336    if (getSelectedIndex() >= static_cast<int>(names.size()))
337        setSelectedIndex(-1);
338}
339
340int AutoFillPopupMenuClient::convertListIndexToInternalIndex(unsigned listIndex) const
341{
342    if (listIndex == static_cast<unsigned>(m_separatorIndex))
343        return -1;
344
345    if (m_separatorIndex == -1 || listIndex < static_cast<unsigned>(m_separatorIndex))
346        return listIndex;
347    return listIndex - 1;
348}
349
350WebViewImpl* AutoFillPopupMenuClient::getWebView() const
351{
352    Frame* frame = m_textField->document()->frame();
353    if (!frame)
354        return 0;
355
356    Page* page = frame->page();
357    if (!page)
358        return 0;
359
360    return static_cast<ChromeClientImpl*>(page->chrome()->client())->webView();
361}
362
363RenderStyle* AutoFillPopupMenuClient::textFieldStyle() const
364{
365    RenderStyle* style = m_textField->computedStyle();
366    if (!style) {
367        // It seems we can only have a 0 style in a TextField if the
368        // node is detached, in which case we the popup should not be
369        // showing.  Please report this in http://crbug.com/7708 and
370        // include the page you were visiting.
371        ASSERT_NOT_REACHED();
372    }
373    return style;
374}
375
376} // namespace WebKit
377