| /** |
| * Copyright (C) 2006, 2007, 2010 Apple Inc. All rights reserved. |
| * (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) |
| * Copyright (C) 2010 Google Inc. All rights reserved. |
| * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| * |
| */ |
| |
| #include "config.h" |
| #include "RenderSearchField.h" |
| |
| #include "CSSFontSelector.h" |
| #include "CSSValueKeywords.h" |
| #include "Chrome.h" |
| #include "Frame.h" |
| #include "FrameSelection.h" |
| #include "FrameView.h" |
| #include "HTMLInputElement.h" |
| #include "HTMLNames.h" |
| #include "HitTestResult.h" |
| #include "LocalizedStrings.h" |
| #include "Page.h" |
| #include "PlatformKeyboardEvent.h" |
| #include "RenderLayer.h" |
| #include "RenderScrollbar.h" |
| #include "RenderTheme.h" |
| #include "SearchPopupMenu.h" |
| #include "Settings.h" |
| #include "SimpleFontData.h" |
| #include "StyleResolver.h" |
| #include "TextControlInnerElements.h" |
| |
| using namespace std; |
| |
| namespace WebCore { |
| |
| using namespace HTMLNames; |
| |
| // ---------------------------- |
| |
| RenderSearchField::RenderSearchField(ContainerNode* node) |
| : RenderTextControlSingleLine(node) |
| , m_searchPopupIsVisible(false) |
| , m_searchPopup(0) |
| { |
| ASSERT(node->isHTMLElement()); |
| ASSERT(node->toInputElement()); |
| ASSERT(node->toInputElement()->isSearchField()); |
| } |
| |
| RenderSearchField::~RenderSearchField() |
| { |
| if (m_searchPopup) { |
| m_searchPopup->popupMenu()->disconnectClient(); |
| m_searchPopup = 0; |
| } |
| } |
| |
| inline HTMLElement* RenderSearchField::resultsButtonElement() const |
| { |
| return inputElement()->resultsButtonElement(); |
| } |
| |
| inline HTMLElement* RenderSearchField::cancelButtonElement() const |
| { |
| return inputElement()->cancelButtonElement(); |
| } |
| |
| void RenderSearchField::addSearchResult() |
| { |
| HTMLInputElement* input = inputElement(); |
| if (input->maxResults() <= 0) |
| return; |
| |
| String value = input->value(); |
| if (value.isEmpty()) |
| return; |
| |
| Settings* settings = document()->settings(); |
| if (!settings || settings->privateBrowsingEnabled()) |
| return; |
| |
| int size = static_cast<int>(m_recentSearches.size()); |
| for (int i = size - 1; i >= 0; --i) { |
| if (m_recentSearches[i] == value) |
| m_recentSearches.remove(i); |
| } |
| |
| m_recentSearches.insert(0, value); |
| while (static_cast<int>(m_recentSearches.size()) > input->maxResults()) |
| m_recentSearches.removeLast(); |
| |
| const AtomicString& name = autosaveName(); |
| if (!m_searchPopup) |
| m_searchPopup = document()->page()->chrome()->createSearchPopupMenu(this); |
| |
| m_searchPopup->saveRecentSearches(name, m_recentSearches); |
| } |
| |
| void RenderSearchField::showPopup() |
| { |
| if (m_searchPopupIsVisible) |
| return; |
| |
| if (!m_searchPopup) |
| m_searchPopup = document()->page()->chrome()->createSearchPopupMenu(this); |
| |
| if (!m_searchPopup->enabled()) |
| return; |
| |
| m_searchPopupIsVisible = true; |
| |
| const AtomicString& name = autosaveName(); |
| m_searchPopup->loadRecentSearches(name, m_recentSearches); |
| |
| // Trim the recent searches list if the maximum size has changed since we last saved. |
| HTMLInputElement* input = inputElement(); |
| if (static_cast<int>(m_recentSearches.size()) > input->maxResults()) { |
| do { |
| m_recentSearches.removeLast(); |
| } while (static_cast<int>(m_recentSearches.size()) > input->maxResults()); |
| |
| m_searchPopup->saveRecentSearches(name, m_recentSearches); |
| } |
| |
| m_searchPopup->popupMenu()->show(pixelSnappedIntRect(absoluteBoundingBoxRect()), document()->view(), -1); |
| } |
| |
| void RenderSearchField::hidePopup() |
| { |
| if (m_searchPopup) |
| m_searchPopup->popupMenu()->hide(); |
| } |
| |
| LayoutUnit RenderSearchField::computeControlHeight(LayoutUnit lineHeight, LayoutUnit nonContentHeight) const |
| { |
| HTMLElement* resultsButton = resultsButtonElement(); |
| if (RenderBox* resultsRenderer = resultsButton ? resultsButton->renderBox() : 0) { |
| resultsRenderer->updateLogicalHeight(); |
| nonContentHeight = max(nonContentHeight, resultsRenderer->borderAndPaddingHeight() + resultsRenderer->marginHeight()); |
| lineHeight = max(lineHeight, resultsRenderer->height()); |
| } |
| HTMLElement* cancelButton = cancelButtonElement(); |
| if (RenderBox* cancelRenderer = cancelButton ? cancelButton->renderBox() : 0) { |
| cancelRenderer->updateLogicalHeight(); |
| nonContentHeight = max(nonContentHeight, cancelRenderer->borderAndPaddingHeight() + cancelRenderer->marginHeight()); |
| lineHeight = max(lineHeight, cancelRenderer->height()); |
| } |
| |
| return lineHeight + nonContentHeight; |
| } |
| |
| void RenderSearchField::updateFromElement() |
| { |
| RenderTextControlSingleLine::updateFromElement(); |
| |
| if (cancelButtonElement()) |
| updateCancelButtonVisibility(); |
| |
| if (m_searchPopupIsVisible) |
| m_searchPopup->popupMenu()->updateFromElement(); |
| } |
| |
| void RenderSearchField::updateCancelButtonVisibility() const |
| { |
| RenderObject* cancelButtonRenderer = cancelButtonElement()->renderer(); |
| if (!cancelButtonRenderer) |
| return; |
| |
| const RenderStyle* curStyle = cancelButtonRenderer->style(); |
| EVisibility buttonVisibility = visibilityForCancelButton(); |
| if (curStyle->visibility() == buttonVisibility) |
| return; |
| |
| RefPtr<RenderStyle> cancelButtonStyle = RenderStyle::clone(curStyle); |
| cancelButtonStyle->setVisibility(buttonVisibility); |
| cancelButtonRenderer->setStyle(cancelButtonStyle); |
| } |
| |
| EVisibility RenderSearchField::visibilityForCancelButton() const |
| { |
| return (style()->visibility() == HIDDEN || inputElement()->value().isEmpty()) ? HIDDEN : VISIBLE; |
| } |
| |
| const AtomicString& RenderSearchField::autosaveName() const |
| { |
| return static_cast<Element*>(node())->getAttribute(autosaveAttr); |
| } |
| |
| // PopupMenuClient methods |
| void RenderSearchField::valueChanged(unsigned listIndex, bool fireEvents) |
| { |
| ASSERT(static_cast<int>(listIndex) < listSize()); |
| HTMLInputElement* input = inputElement(); |
| if (static_cast<int>(listIndex) == (listSize() - 1)) { |
| if (fireEvents) { |
| m_recentSearches.clear(); |
| const AtomicString& name = autosaveName(); |
| if (!name.isEmpty()) { |
| if (!m_searchPopup) |
| m_searchPopup = document()->page()->chrome()->createSearchPopupMenu(this); |
| m_searchPopup->saveRecentSearches(name, m_recentSearches); |
| } |
| } |
| } else { |
| input->setValue(itemText(listIndex)); |
| if (fireEvents) |
| input->onSearch(); |
| input->select(); |
| } |
| } |
| |
| String RenderSearchField::itemText(unsigned listIndex) const |
| { |
| int size = listSize(); |
| if (size == 1) { |
| ASSERT(!listIndex); |
| return searchMenuNoRecentSearchesText(); |
| } |
| if (!listIndex) |
| return searchMenuRecentSearchesText(); |
| if (itemIsSeparator(listIndex)) |
| return String(); |
| if (static_cast<int>(listIndex) == (size - 1)) |
| return searchMenuClearRecentSearchesText(); |
| return m_recentSearches[listIndex - 1]; |
| } |
| |
| String RenderSearchField::itemLabel(unsigned) const |
| { |
| return String(); |
| } |
| |
| String RenderSearchField::itemIcon(unsigned) const |
| { |
| return String(); |
| } |
| |
| bool RenderSearchField::itemIsEnabled(unsigned listIndex) const |
| { |
| if (!listIndex || itemIsSeparator(listIndex)) |
| return false; |
| return true; |
| } |
| |
| PopupMenuStyle RenderSearchField::itemStyle(unsigned) const |
| { |
| return menuStyle(); |
| } |
| |
| PopupMenuStyle RenderSearchField::menuStyle() const |
| { |
| return PopupMenuStyle(style()->visitedDependentColor(CSSPropertyColor), style()->visitedDependentColor(CSSPropertyBackgroundColor), style()->font(), style()->visibility() == VISIBLE, |
| style()->display() == NONE, style()->textIndent(), style()->direction(), isOverride(style()->unicodeBidi())); |
| } |
| |
| int RenderSearchField::clientInsetLeft() const |
| { |
| // Inset the menu by the radius of the cap on the left so that |
| // it only runs along the straight part of the bezel. |
| return height() / 2; |
| } |
| |
| int RenderSearchField::clientInsetRight() const |
| { |
| // Inset the menu by the radius of the cap on the right so that |
| // it only runs along the straight part of the bezel (unless it needs |
| // to be wider). |
| return height() / 2; |
| } |
| |
| LayoutUnit RenderSearchField::clientPaddingLeft() const |
| { |
| LayoutUnit padding = paddingLeft(); |
| if (RenderBox* box = innerBlockElement() ? innerBlockElement()->renderBox() : 0) |
| padding += box->x(); |
| return padding; |
| } |
| |
| LayoutUnit RenderSearchField::clientPaddingRight() const |
| { |
| LayoutUnit padding = paddingRight(); |
| if (RenderBox* containerBox = containerElement() ? containerElement()->renderBox() : 0) { |
| if (RenderBox* innerBlockBox = innerBlockElement() ? innerBlockElement()->renderBox() : 0) |
| padding += containerBox->width() - (innerBlockBox->x() + innerBlockBox->width()); |
| } |
| return padding; |
| } |
| |
| int RenderSearchField::listSize() const |
| { |
| // If there are no recent searches, then our menu will have 1 "No recent searches" item. |
| if (!m_recentSearches.size()) |
| return 1; |
| // Otherwise, leave room in the menu for a header, a separator, and the "Clear recent searches" item. |
| return m_recentSearches.size() + 3; |
| } |
| |
| int RenderSearchField::selectedIndex() const |
| { |
| return -1; |
| } |
| |
| void RenderSearchField::popupDidHide() |
| { |
| m_searchPopupIsVisible = false; |
| } |
| |
| bool RenderSearchField::itemIsSeparator(unsigned listIndex) const |
| { |
| // The separator will be the second to last item in our list. |
| return static_cast<int>(listIndex) == (listSize() - 2); |
| } |
| |
| bool RenderSearchField::itemIsLabel(unsigned listIndex) const |
| { |
| return !listIndex; |
| } |
| |
| bool RenderSearchField::itemIsSelected(unsigned) const |
| { |
| return false; |
| } |
| |
| void RenderSearchField::setTextFromItem(unsigned listIndex) |
| { |
| inputElement()->setValue(itemText(listIndex)); |
| } |
| |
| FontSelector* RenderSearchField::fontSelector() const |
| { |
| return document()->styleResolver()->fontSelector(); |
| } |
| |
| HostWindow* RenderSearchField::hostWindow() const |
| { |
| return document()->view()->hostWindow(); |
| } |
| |
| PassRefPtr<Scrollbar> RenderSearchField::createScrollbar(ScrollableArea* scrollableArea, ScrollbarOrientation orientation, ScrollbarControlSize controlSize) |
| { |
| RefPtr<Scrollbar> widget; |
| bool hasCustomScrollbarStyle = style()->hasPseudoStyle(SCROLLBAR); |
| if (hasCustomScrollbarStyle) |
| widget = RenderScrollbar::createCustomScrollbar(scrollableArea, orientation, this->node()); |
| else |
| widget = Scrollbar::createNativeScrollbar(scrollableArea, orientation, controlSize); |
| return widget.release(); |
| } |
| |
| LayoutUnit RenderSearchField::computeHeightLimit() const |
| { |
| return height(); |
| } |
| |
| void RenderSearchField::centerContainerIfNeeded(RenderBox* containerRenderer) const |
| { |
| if (!containerRenderer) |
| return; |
| |
| if (containerRenderer->height() <= contentHeight()) |
| return; |
| |
| // A quirk for find-in-page box on Safari Windows. |
| // http://webkit.org/b/63157 |
| LayoutUnit heightDiff = containerRenderer->height() - contentHeight(); |
| containerRenderer->setY(containerRenderer->y() - (heightDiff / 2 + layoutMod(heightDiff, 2))); |
| } |
| |
| } |