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    ScrollableArea* scrollableArea,
258    ScrollbarOrientation orientation,
259    ScrollbarControlSize size)
260{
261    return Scrollbar::createNativeScrollbar(scrollableArea, 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(), textField->renderer()->style()->unicodeBidi() == Override));
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                                          m_regularStyle->hasTextDirectionOverride()));
310}
311
312void AutoFillPopupMenuClient::setSuggestions(const WebVector<WebString>& names,
313                                             const WebVector<WebString>& labels,
314                                             const WebVector<WebString>& icons,
315                                             const WebVector<int>& uniqueIDs,
316                                             int separatorIndex)
317{
318    ASSERT(names.size() == labels.size());
319    ASSERT(names.size() == icons.size());
320    ASSERT(names.size() == uniqueIDs.size());
321    ASSERT(separatorIndex < static_cast<int>(names.size()));
322
323    m_names.clear();
324    m_labels.clear();
325    m_icons.clear();
326    m_uniqueIDs.clear();
327    for (size_t i = 0; i < names.size(); ++i) {
328        m_names.append(names[i]);
329        m_labels.append(labels[i]);
330        m_icons.append(icons[i]);
331        m_uniqueIDs.append(uniqueIDs[i]);
332    }
333
334    m_separatorIndex = separatorIndex;
335
336    // Try to preserve selection if possible.
337    if (getSelectedIndex() >= static_cast<int>(names.size()))
338        setSelectedIndex(-1);
339}
340
341int AutoFillPopupMenuClient::convertListIndexToInternalIndex(unsigned listIndex) const
342{
343    if (listIndex == static_cast<unsigned>(m_separatorIndex))
344        return -1;
345
346    if (m_separatorIndex == -1 || listIndex < static_cast<unsigned>(m_separatorIndex))
347        return listIndex;
348    return listIndex - 1;
349}
350
351WebViewImpl* AutoFillPopupMenuClient::getWebView() const
352{
353    Frame* frame = m_textField->document()->frame();
354    if (!frame)
355        return 0;
356
357    Page* page = frame->page();
358    if (!page)
359        return 0;
360
361    return static_cast<WebViewImpl*>(page->chrome()->client()->webView());
362}
363
364RenderStyle* AutoFillPopupMenuClient::textFieldStyle() const
365{
366    RenderStyle* style = m_textField->computedStyle();
367    if (!style) {
368        // It seems we can only have a 0 style in a TextField if the
369        // node is detached, in which case we the popup should not be
370        // showing.  Please report this in http://crbug.com/7708 and
371        // include the page you were visiting.
372        ASSERT_NOT_REACHED();
373    }
374    return style;
375}
376
377} // namespace WebKit
378