blob: 2ae685b80a8a455893daa419d052df8b0baa9f21 [file] [log] [blame]
/**
* Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
*
* 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 "RenderTextControl.h"
#include "CharacterNames.h"
#include "Document.h"
#include "Editor.h"
#include "EditorClient.h"
#include "Event.h"
#include "EventNames.h"
#include "Frame.h"
#include "HTMLBRElement.h"
#include "HTMLInputElement.h"
#include "HTMLNames.h"
#include "HTMLTextAreaElement.h"
#include "HTMLTextFieldInnerElement.h"
#include "HitTestResult.h"
#include "LocalizedStrings.h"
#include "MouseEvent.h"
#include "PlatformScrollBar.h"
#include "RenderTheme.h"
#include "SearchPopupMenu.h"
#include "SelectionController.h"
#include "Settings.h"
#include "Text.h"
#include "TextIterator.h"
#include "TextStyle.h"
#include "htmlediting.h"
#include "visible_units.h"
#include <math.h>
using namespace std;
namespace WebCore {
using namespace EventNames;
using namespace HTMLNames;
class RenderTextControlInnerBlock : public RenderBlock {
public:
RenderTextControlInnerBlock(Node* node) : RenderBlock(node) { }
virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction);
};
bool RenderTextControlInnerBlock::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty, HitTestAction hitTestAction)
{
RenderTextControl* renderer = static_cast<RenderTextControl*>(node()->shadowAncestorNode()->renderer());
return RenderBlock::nodeAtPoint(request, result, x, y, tx, ty, renderer->placeholderIsVisible() ? HitTestBlockBackground : hitTestAction);
}
RenderTextControl::RenderTextControl(Node* node, bool multiLine)
: RenderBlock(node)
, m_dirty(false)
, m_multiLine(multiLine)
, m_placeholderVisible(false)
, m_userEdited(false)
, m_searchPopup(0)
, m_searchPopupIsVisible(false)
, m_searchEventTimer(this, &RenderTextControl::searchEventTimerFired)
{
}
RenderTextControl::~RenderTextControl()
{
if (m_searchPopup) {
m_searchPopup->disconnectClient();
m_searchPopup = 0;
}
if (m_multiLine && node())
static_cast<HTMLTextAreaElement*>(node())->rendererWillBeDestroyed();
// The children renderers have already been destroyed by destroyLeftoverChildren
if (m_innerBlock)
m_innerBlock->detach();
else if (m_innerText)
m_innerText->detach();
}
void RenderTextControl::setStyle(RenderStyle* style)
{
RenderBlock::setStyle(style);
if (m_innerBlock) {
// We may have set the width and the height in the old style in layout(). Reset them now to avoid
// getting a spurious layout hint.
m_innerBlock->renderer()->style()->setHeight(Length());
m_innerBlock->renderer()->style()->setWidth(Length());
m_innerBlock->renderer()->setStyle(createInnerBlockStyle(style));
}
if (m_innerText) {
RenderBlock* textBlockRenderer = static_cast<RenderBlock*>(m_innerText->renderer());
RenderStyle* textBlockStyle = createInnerTextStyle(style);
// We may have set the width and the height in the old style in layout(). Reset them now to avoid
// getting a spurious layout hint.
textBlockRenderer->style()->setHeight(Length());
textBlockRenderer->style()->setWidth(Length());
textBlockRenderer->setStyle(textBlockStyle);
for (Node* n = m_innerText->firstChild(); n; n = n->traverseNextNode(m_innerText.get())) {
if (n->renderer())
n->renderer()->setStyle(textBlockStyle);
}
}
if (m_resultsButton)
m_resultsButton->renderer()->setStyle(createResultsButtonStyle(style));
if (m_cancelButton)
m_cancelButton->renderer()->setStyle(createCancelButtonStyle(style));
setHasOverflowClip(false);
setReplaced(isInline());
}
static Color disabledTextColor(const Color& textColor, const Color& backgroundColor)
{
// The explcit check for black is an optimization for the 99% case (black on white).
// This also means that black on black will turn into grey on black when disabled.
if (textColor.rgb() == Color::black || differenceSquared(textColor, Color::white) > differenceSquared(backgroundColor, Color::white))
return textColor.light();
return textColor.dark();
}
RenderStyle* RenderTextControl::createInnerBlockStyle(RenderStyle* startStyle)
{
RenderStyle* innerBlockStyle = new (renderArena()) RenderStyle();
innerBlockStyle->inheritFrom(startStyle);
innerBlockStyle->setDisplay(BLOCK);
innerBlockStyle->setDirection(LTR);
// We don't want the shadow dom to be editable, so we set this block to read-only in case the input itself is editable.
innerBlockStyle->setUserModify(READ_ONLY);
return innerBlockStyle;
}
RenderStyle* RenderTextControl::createInnerTextStyle(RenderStyle* startStyle)
{
RenderStyle* textBlockStyle = new (renderArena()) RenderStyle();
HTMLGenericFormElement* element = static_cast<HTMLGenericFormElement*>(node());
textBlockStyle->inheritFrom(startStyle);
// The inner block, if present, always has its direction set to LTR,
// so we need to inherit the direction from the element.
textBlockStyle->setDirection(style()->direction());
textBlockStyle->setUserModify(element->isReadOnlyControl() || element->disabled() ? READ_ONLY : READ_WRITE_PLAINTEXT_ONLY);
if (m_innerBlock)
textBlockStyle->setDisplay(INLINE_BLOCK);
else
textBlockStyle->setDisplay(BLOCK);
if (m_multiLine) {
// Forward overflow properties.
textBlockStyle->setOverflowX(startStyle->overflowX() == OVISIBLE ? OAUTO : startStyle->overflowX());
textBlockStyle->setOverflowY(startStyle->overflowY() == OVISIBLE ? OAUTO : startStyle->overflowY());
// Set word wrap property based on wrap attribute
if (static_cast<HTMLTextAreaElement*>(element)->wrap() == HTMLTextAreaElement::ta_NoWrap) {
textBlockStyle->setWhiteSpace(PRE);
textBlockStyle->setWordWrap(NormalWordWrap);
} else {
textBlockStyle->setWhiteSpace(PRE_WRAP);
textBlockStyle->setWordWrap(BreakWordWrap);
}
} else {
textBlockStyle->setWhiteSpace(PRE);
textBlockStyle->setWordWrap(NormalWordWrap);
textBlockStyle->setOverflowX(OHIDDEN);
textBlockStyle->setOverflowY(OHIDDEN);
// Do not allow line-height to be smaller than our default.
if (textBlockStyle->font().lineSpacing() > lineHeight(true, true))
textBlockStyle->setLineHeight(Length(-100.0f, Percent));
}
if (!m_multiLine) {
// We're adding one extra pixel of padding to match WinIE.
textBlockStyle->setPaddingLeft(Length(1, Fixed));
textBlockStyle->setPaddingRight(Length(1, Fixed));
} else {
// We're adding three extra pixels of padding to line textareas up with text fields.
textBlockStyle->setPaddingLeft(Length(3, Fixed));
textBlockStyle->setPaddingRight(Length(3, Fixed));
}
if (!element->isEnabled())
textBlockStyle->setColor(disabledTextColor(startStyle->color(), startStyle->backgroundColor()));
return textBlockStyle;
}
RenderStyle* RenderTextControl::createResultsButtonStyle(RenderStyle* startStyle)
{
ASSERT(!m_multiLine);
HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
RenderStyle* resultsBlockStyle;
if (input->maxResults() < 0)
resultsBlockStyle = getPseudoStyle(RenderStyle::SEARCH_DECORATION);
else if (!input->maxResults())
resultsBlockStyle = getPseudoStyle(RenderStyle::SEARCH_RESULTS_DECORATION);
else
resultsBlockStyle = getPseudoStyle(RenderStyle::SEARCH_RESULTS_BUTTON);
if (!resultsBlockStyle)
resultsBlockStyle = new (renderArena()) RenderStyle();
if (startStyle)
resultsBlockStyle->inheritFrom(startStyle);
resultsBlockStyle->setDisplay(INLINE_BLOCK);
return resultsBlockStyle;
}
RenderStyle* RenderTextControl::createCancelButtonStyle(RenderStyle* startStyle)
{
RenderStyle* cancelBlockStyle;
if (RenderStyle* pseudoStyle = getPseudoStyle(RenderStyle::SEARCH_CANCEL_BUTTON))
// We may be sharing style with another search field, but we must not share the cancel button style.
cancelBlockStyle = new (renderArena()) RenderStyle(*pseudoStyle);
else
cancelBlockStyle = new (renderArena()) RenderStyle();
if (startStyle)
cancelBlockStyle->inheritFrom(startStyle);
cancelBlockStyle->setDisplay(INLINE_BLOCK);
updateCancelButtonVisibility(cancelBlockStyle);
return cancelBlockStyle;
}
void RenderTextControl::updatePlaceholder()
{
bool oldPlaceholderVisible = m_placeholderVisible;
String placeholder;
if (!m_multiLine) {
HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
if (input->value().isEmpty() && document()->focusedNode() != node())
placeholder = input->getAttribute(placeholderAttr);
}
if (!placeholder.isEmpty() || m_placeholderVisible) {
ExceptionCode ec = 0;
m_innerText->setInnerText(placeholder, ec);
m_placeholderVisible = !placeholder.isEmpty();
}
Color color;
if (!placeholder.isEmpty())
color = Color::darkGray;
else if (node()->isEnabled())
color = style()->color();
else
color = disabledTextColor(style()->color(), style()->backgroundColor());
RenderObject* renderer = m_innerText->renderer();
RenderStyle* innerStyle = renderer->style();
if (innerStyle->color() != color) {
innerStyle->setColor(color);
renderer->repaint();
}
// temporary disable textSecurity if placeholder is visible
if (style()->textSecurity() != TSNONE && oldPlaceholderVisible != m_placeholderVisible) {
RenderStyle* newInnerStyle = new (renderArena()) RenderStyle(*innerStyle);
newInnerStyle->setTextSecurity(m_placeholderVisible ? TSNONE : style()->textSecurity());
renderer->setStyle(newInnerStyle);
for (Node* n = m_innerText->firstChild(); n; n = n->traverseNextNode(m_innerText.get())) {
if (n->renderer())
n->renderer()->setStyle(newInnerStyle);
}
}
}
void RenderTextControl::createSubtreeIfNeeded()
{
// When adding these elements, create the renderer & style first before adding to the DOM.
// Otherwise, the render tree will create some anonymous blocks that will mess up our layout.
bool isSearchField = !m_multiLine && static_cast<HTMLInputElement*>(node())->isSearchField();
if (isSearchField && !m_innerBlock) {
// Create the inner block element and give it a parent, renderer, and style
m_innerBlock = new HTMLTextFieldInnerElement(document(), node());
RenderBlock* innerBlockRenderer = new (renderArena()) RenderBlock(m_innerBlock.get());
m_innerBlock->setRenderer(innerBlockRenderer);
m_innerBlock->setAttached();
m_innerBlock->setInDocument(true);
innerBlockRenderer->setStyle(createInnerBlockStyle(style()));
// Add inner block renderer to Render tree
RenderBlock::addChild(innerBlockRenderer);
}
if (isSearchField && !m_resultsButton) {
// Create the results block element and give it a parent, renderer, and style
m_resultsButton = new HTMLSearchFieldResultsButtonElement(document());
RenderBlock* resultsBlockRenderer = new (renderArena()) RenderBlock(m_resultsButton.get());
m_resultsButton->setRenderer(resultsBlockRenderer);
m_resultsButton->setAttached();
m_resultsButton->setInDocument(true);
RenderStyle* resultsBlockStyle = createResultsButtonStyle(m_innerBlock->renderer()->style());
resultsBlockRenderer->setStyle(resultsBlockStyle);
// Add results renderer to DOM & Render tree
m_innerBlock->renderer()->addChild(resultsBlockRenderer);
ExceptionCode ec = 0;
m_innerBlock->appendChild(m_resultsButton, ec);
}
if (!m_innerText) {
// Create the text block element and give it a parent, renderer, and style
// For non-search fields, there is no intermediate m_innerBlock as the shadow node.
// m_innerText will be the shadow node in that case.
m_innerText = new HTMLTextFieldInnerTextElement(document(), m_innerBlock ? 0 : node());
RenderTextControlInnerBlock* textBlockRenderer = new (renderArena()) RenderTextControlInnerBlock(m_innerText.get());
m_innerText->setRenderer(textBlockRenderer);
m_innerText->setAttached();
m_innerText->setInDocument(true);
RenderStyle* parentStyle = style();
if (m_innerBlock)
parentStyle = m_innerBlock->renderer()->style();
RenderStyle* textBlockStyle = createInnerTextStyle(parentStyle);
textBlockRenderer->setStyle(textBlockStyle);
// Add text block renderer to Render tree
if (m_innerBlock) {
m_innerBlock->renderer()->addChild(textBlockRenderer);
ExceptionCode ec = 0;
// Add text block to the DOM
m_innerBlock->appendChild(m_innerText, ec);
} else
RenderBlock::addChild(textBlockRenderer);
}
if (isSearchField && !m_cancelButton) {
// Create the close block element and give it a parent, renderer, and style
m_cancelButton = new HTMLSearchFieldCancelButtonElement(document());
RenderBlock* closeBlockRenderer = new (renderArena()) RenderBlock(m_cancelButton.get());
m_cancelButton->setRenderer(closeBlockRenderer);
m_cancelButton->setAttached();
m_cancelButton->setInDocument(true);
RenderStyle* closeBlockStyle = createCancelButtonStyle(m_innerBlock->renderer()->style());
closeBlockRenderer->setStyle(closeBlockStyle);
// Add close block renderer to DOM & Render tree
m_innerBlock->renderer()->addChild(closeBlockRenderer);
ExceptionCode ec = 0;
m_innerBlock->appendChild(m_cancelButton, ec);
}
}
void RenderTextControl::updateFromElement()
{
HTMLGenericFormElement* element = static_cast<HTMLGenericFormElement*>(node());
createSubtreeIfNeeded();
if (m_cancelButton)
updateCancelButtonVisibility(m_cancelButton->renderer()->style());
updatePlaceholder();
m_innerText->renderer()->style()->setUserModify(element->isReadOnlyControl() || element->disabled() ? READ_ONLY : READ_WRITE_PLAINTEXT_ONLY);
if ((!element->valueMatchesRenderer() || m_multiLine) && !m_placeholderVisible) {
String value;
if (m_multiLine)
value = static_cast<HTMLTextAreaElement*>(element)->value();
else
value = static_cast<HTMLInputElement*>(element)->value();
if (value.isNull())
value = "";
else
value = value.replace('\\', backslashAsCurrencySymbol());
if (value != text() || !m_innerText->hasChildNodes()) {
ExceptionCode ec = 0;
m_innerText->setInnerText(value, ec);
if (value.endsWith("\n") || value.endsWith("\r"))
m_innerText->appendChild(new HTMLBRElement(document()), ec);
if (Frame* frame = document()->frame())
frame->editor()->clearUndoRedoOperations();
m_dirty = false;
m_userEdited = false;
}
element->setValueMatchesRenderer();
}
if (m_searchPopupIsVisible)
m_searchPopup->updateFromElement();
}
int RenderTextControl::selectionStart()
{
Frame* frame = document()->frame();
if (!frame)
return 0;
return indexForVisiblePosition(frame->selectionController()->start());
}
int RenderTextControl::selectionEnd()
{
Frame* frame = document()->frame();
if (!frame)
return 0;
return indexForVisiblePosition(frame->selectionController()->end());
}
void RenderTextControl::setSelectionStart(int start)
{
setSelectionRange(start, max(start, selectionEnd()));
}
void RenderTextControl::setSelectionEnd(int end)
{
setSelectionRange(min(end, selectionStart()), end);
}
void RenderTextControl::select()
{
setSelectionRange(0, text().length());
}
void RenderTextControl::setSelectionRange(int start, int end)
{
end = max(end, 0);
start = min(max(start, 0), end);
document()->updateLayout();
if (style()->visibility() == HIDDEN || !m_innerText || !m_innerText->renderer() || !m_innerText->renderer()->height()) {
if (m_multiLine)
static_cast<HTMLTextAreaElement*>(node())->cacheSelection(start, end);
else
static_cast<HTMLInputElement*>(node())->cacheSelection(start, end);
return;
}
VisiblePosition startPosition = visiblePositionForIndex(start);
VisiblePosition endPosition;
if (start == end)
endPosition = startPosition;
else
endPosition = visiblePositionForIndex(end);
ASSERT(startPosition.isNotNull() && endPosition.isNotNull());
ASSERT(startPosition.deepEquivalent().node()->shadowAncestorNode() == node() && endPosition.deepEquivalent().node()->shadowAncestorNode() == node());
Selection newSelection = Selection(startPosition, endPosition);
if (Frame* frame = document()->frame())
frame->selectionController()->setSelection(newSelection);
// FIXME: Granularity is stored separately on the frame, but also in the selection controller.
// The granularity in the selection controller should be used, and then this line of code would not be needed.
if (Frame* frame = document()->frame())
frame->setSelectionGranularity(CharacterGranularity);
}
Selection RenderTextControl::selection(int start, int end) const
{
return Selection(VisiblePosition(m_innerText.get(), start, VP_DEFAULT_AFFINITY),
VisiblePosition(m_innerText.get(), end, VP_DEFAULT_AFFINITY));
}
VisiblePosition RenderTextControl::visiblePositionForIndex(int index)
{
if (index <= 0)
return VisiblePosition(m_innerText.get(), 0, DOWNSTREAM);
ExceptionCode ec = 0;
RefPtr<Range> range = new Range(document());
range->selectNodeContents(m_innerText.get(), ec);
CharacterIterator it(range.get());
it.advance(index - 1);
return VisiblePosition(it.range()->endContainer(ec), it.range()->endOffset(ec), UPSTREAM);
}
int RenderTextControl::indexForVisiblePosition(const VisiblePosition& pos)
{
Position indexPosition = pos.deepEquivalent();
if (!indexPosition.node() || indexPosition.node()->rootEditableElement() != m_innerText)
return 0;
ExceptionCode ec = 0;
RefPtr<Range> range = new Range(document());
range->setStart(m_innerText.get(), 0, ec);
range->setEnd(indexPosition.node(), indexPosition.offset(), ec);
return TextIterator::rangeLength(range.get());
}
void RenderTextControl::updateCancelButtonVisibility(RenderStyle* style)
{
ASSERT(!m_multiLine);
HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
if (input->value().isEmpty())
style->setVisibility(HIDDEN);
else
style->setVisibility(VISIBLE);
}
void RenderTextControl::subtreeHasChanged()
{
bool wasDirty = m_dirty;
m_dirty = true;
m_userEdited = true;
HTMLGenericFormElement* element = static_cast<HTMLGenericFormElement*>(node());
if (m_multiLine) {
element->setValueMatchesRenderer(false);
if (element->focused())
if (Frame* frame = document()->frame())
frame->textDidChangeInTextArea(element);
} else {
HTMLInputElement* input = static_cast<HTMLInputElement*>(element);
input->setValueFromRenderer(input->constrainValue(text()));
if (m_cancelButton)
updateCancelButtonVisibility(m_cancelButton->renderer()->style());
// If the incremental attribute is set, then dispatch the search event
if (!input->getAttribute(incrementalAttr).isNull())
startSearchEventTimer();
if (!wasDirty) {
if (input->focused())
if (Frame* frame = document()->frame())
frame->textFieldDidBeginEditing(input);
}
if (input->focused())
if (Frame* frame = document()->frame())
frame->textDidChangeInTextField(input);
}
}
String RenderTextControl::finishText(Vector<UChar>& result) const
{
UChar symbol = backslashAsCurrencySymbol();
if (symbol != '\\') {
size_t size = result.size();
for (size_t i = 0; i < size; ++i)
if (result[i] == '\\')
result[i] = symbol;
}
return String::adopt(result);
}
String RenderTextControl::text()
{
if (!m_innerText)
return "";
Frame* frame = document()->frame();
Text* compositionNode = frame ? frame->editor()->compositionNode() : 0;
Vector<UChar> result;
for (Node* n = m_innerText.get(); n; n = n->traverseNextNode(m_innerText.get())) {
if (n->isTextNode()) {
Text* text = static_cast<Text*>(n);
String data = text->data();
unsigned length = data.length();
if (text != compositionNode)
result.append(data.characters(), length);
else {
unsigned compositionStart = min(frame->editor()->compositionStart(), length);
unsigned compositionEnd = min(max(compositionStart, frame->editor()->compositionEnd()), length);
result.append(data.characters(), compositionStart);
result.append(data.characters() + compositionEnd, length - compositionEnd);
}
}
}
return finishText(result);
}
static void getNextSoftBreak(RootInlineBox*& line, Node*& breakNode, unsigned& breakOffset)
{
RootInlineBox* next;
for (; line; line = next) {
next = line->nextRootBox();
if (next && !line->endsWithBreak()) {
ASSERT(line->lineBreakObj());
breakNode = line->lineBreakObj()->node();
breakOffset = line->lineBreakPos();
line = next;
return;
}
}
breakNode = 0;
}
String RenderTextControl::textWithHardLineBreaks()
{
if (!m_innerText)
return "";
Node* firstChild = m_innerText->firstChild();
if (!firstChild)
return "";
document()->updateLayout();
RenderObject* renderer = firstChild->renderer();
if (!renderer)
return "";
InlineBox* box = renderer->inlineBox(0, DOWNSTREAM);
if (!box)
return "";
Frame* frame = document()->frame();
Text* compositionNode = frame ? frame->editor()->compositionNode() : 0;
Node* breakNode;
unsigned breakOffset;
RootInlineBox* line = box->root();
getNextSoftBreak(line, breakNode, breakOffset);
Vector<UChar> result;
for (Node* n = firstChild; n; n = n->traverseNextNode(m_innerText.get())) {
if (n->hasTagName(brTag))
result.append(&newlineCharacter, 1);
else if (n->isTextNode()) {
Text* text = static_cast<Text*>(n);
String data = text->data();
unsigned length = data.length();
unsigned compositionStart = (text == compositionNode)
? min(frame->editor()->compositionStart(), length) : 0;
unsigned compositionEnd = (text == compositionNode)
? min(max(compositionStart, frame->editor()->compositionEnd()), length) : 0;
unsigned position = 0;
while (breakNode == n && breakOffset < compositionStart) {
result.append(data.characters() + position, breakOffset - position);
position = breakOffset;
result.append(&newlineCharacter, 1);
getNextSoftBreak(line, breakNode, breakOffset);
}
result.append(data.characters() + position, compositionStart - position);
position = compositionEnd;
while (breakNode == n && breakOffset <= length) {
if (breakOffset > position) {
result.append(data.characters() + position, breakOffset - position);
position = breakOffset;
result.append(&newlineCharacter, 1);
}
getNextSoftBreak(line, breakNode, breakOffset);
}
result.append(data.characters() + position, length - position);
}
while (breakNode == n)
getNextSoftBreak(line, breakNode, breakOffset);
}
return finishText(result);
}
void RenderTextControl::calcHeight()
{
int rows = 1;
if (m_multiLine)
rows = static_cast<HTMLTextAreaElement*>(node())->rows();
int line = m_innerText->renderer()->lineHeight(true, true);
int toAdd = paddingTop() + paddingBottom() + borderTop() + borderBottom();
int innerToAdd = m_innerText->renderer()->borderTop() + m_innerText->renderer()->borderBottom() +
m_innerText->renderer()->paddingTop() + m_innerText->renderer()->paddingBottom() +
m_innerText->renderer()->marginTop() + m_innerText->renderer()->marginBottom();
if (m_resultsButton) {
static_cast<RenderBlock*>(m_resultsButton->renderer())->calcHeight();
innerToAdd = max(innerToAdd,
m_resultsButton->renderer()->borderTop() + m_resultsButton->renderer()->borderBottom() +
m_resultsButton->renderer()->paddingTop() + m_resultsButton->renderer()->paddingBottom() +
m_resultsButton->renderer()->marginTop() + m_resultsButton->renderer()->marginBottom());
line = max(line, m_resultsButton->renderer()->height());
}
if (m_cancelButton) {
static_cast<RenderBlock*>(m_cancelButton->renderer())->calcHeight();
innerToAdd = max(innerToAdd,
m_cancelButton->renderer()->borderTop() + m_cancelButton->renderer()->borderBottom() +
m_cancelButton->renderer()->paddingTop() + m_cancelButton->renderer()->paddingBottom() +
m_cancelButton->renderer()->marginTop() + m_cancelButton->renderer()->marginBottom());
line = max(line, m_cancelButton->renderer()->height());
}
toAdd += innerToAdd;
// FIXME: We should get the size of the scrollbar from the RenderTheme instead.
int scrollbarSize = 0;
// We are able to have a horizontal scrollbar if the overflow style is scroll, or if its auto and there's no word wrap.
if (m_innerText->renderer()->style()->overflowX() == OSCROLL || (m_innerText->renderer()->style()->overflowX() == OAUTO && m_innerText->renderer()->style()->wordWrap() == NormalWordWrap))
scrollbarSize = PlatformScrollbar::horizontalScrollbarHeight();
m_height = line * rows + toAdd + scrollbarSize;
RenderBlock::calcHeight();
}
short RenderTextControl::baselinePosition(bool b, bool isRootLineBox) const
{
if (m_multiLine)
return height() + marginTop() + marginBottom();
return RenderBlock::baselinePosition(b, isRootLineBox);
}
bool RenderTextControl::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty, HitTestAction hitTestAction)
{
// If we're within the text control, we want to act as if we've hit the inner text block element, in case the point
// was on the control but not on the inner element (see Radar 4617841).
// In a search field, we want to act as if we've hit the results block if we're to the left of the inner text block,
// and act as if we've hit the close block if we're to the right of the inner text block.
if (RenderBlock::nodeAtPoint(request, result, x, y, tx, ty, hitTestAction) &&
(result.innerNode() == element() || result.innerNode() == m_innerBlock)) {
IntPoint localPoint = IntPoint(x - tx - m_x, y - ty - m_y);
if (m_innerBlock) {
int textLeft = tx + m_x + m_innerBlock->renderer()->xPos() + m_innerText->renderer()->xPos();
int textRight = textLeft + m_innerText->renderer()->width();
if (m_resultsButton && x < textLeft) {
result.setInnerNode(m_resultsButton.get());
result.setLocalPoint(IntPoint(localPoint.x() - m_innerText->renderer()->xPos() - m_innerBlock->renderer()->xPos() - m_resultsButton->renderer()->xPos(),
localPoint.y() - m_innerText->renderer()->yPos() - m_innerBlock->renderer()->yPos() - m_resultsButton->renderer()->yPos()));
return true;
}
if (m_cancelButton && x > textRight) {
result.setInnerNode(m_cancelButton.get());
result.setLocalPoint(IntPoint(localPoint.x() - m_innerText->renderer()->xPos() - m_innerBlock->renderer()->xPos() - m_cancelButton->renderer()->xPos(),
localPoint.y() - m_innerText->renderer()->yPos() - m_innerBlock->renderer()->yPos() - m_cancelButton->renderer()->yPos()));
return true;
}
}
// Hit the inner text block.
result.setInnerNode(m_innerText.get());
result.setLocalPoint(IntPoint(localPoint.x() - m_innerText->renderer()->xPos() - (m_innerBlock.get() ? m_innerBlock->renderer()->xPos() : 0),
localPoint.y() - m_innerText->renderer()->yPos() - (m_innerBlock.get() ? m_innerBlock->renderer()->yPos() : 0)));
return true;
}
return false;
}
IntRect RenderTextControl::controlClipRect(int tx, int ty) const
{
IntRect clipRect = contentBox();
clipRect.move(tx, ty);
return clipRect;
}
void RenderTextControl::layout()
{
int oldHeight = m_height;
calcHeight();
bool relayoutChildren = oldHeight != m_height;
// Set the text block's height
int textBlockHeight = m_height - paddingTop() - paddingBottom() - borderTop() - borderBottom();
int currentTextBlockHeight = m_innerText->renderer()->height();
if (m_multiLine || m_innerBlock || currentTextBlockHeight > m_height) {
if (textBlockHeight != currentTextBlockHeight)
relayoutChildren = true;
m_innerText->renderer()->style()->setHeight(Length(textBlockHeight, Fixed));
}
if (m_innerBlock) {
if (textBlockHeight != m_innerBlock->renderer()->height())
relayoutChildren = true;
m_innerBlock->renderer()->style()->setHeight(Length(textBlockHeight, Fixed));
}
int oldWidth = m_width;
calcWidth();
if (oldWidth != m_width)
relayoutChildren = true;
int searchExtrasWidth = 0;
if (m_resultsButton) {
m_resultsButton->renderer()->calcWidth();
searchExtrasWidth += m_resultsButton->renderer()->width();
}
if (m_cancelButton) {
m_cancelButton->renderer()->calcWidth();
searchExtrasWidth += m_cancelButton->renderer()->width();
}
// Set the text block's width
int textBlockWidth = m_width - paddingLeft() - paddingRight() - borderLeft() - borderRight() -
m_innerText->renderer()->paddingLeft() - m_innerText->renderer()->paddingRight() - searchExtrasWidth;
if (textBlockWidth != m_innerText->renderer()->width())
relayoutChildren = true;
m_innerText->renderer()->style()->setWidth(Length(textBlockWidth, Fixed));
if (m_innerBlock) {
int innerBlockWidth = m_width - paddingLeft() - paddingRight() - borderLeft() - borderRight();
if (innerBlockWidth != m_innerBlock->renderer()->width())
relayoutChildren = true;
m_innerBlock->renderer()->style()->setWidth(Length(innerBlockWidth, Fixed));
}
RenderBlock::layoutBlock(relayoutChildren);
// For text fields, center the inner text vertically
// Don't do this for search fields, since we don't honor height for them
if (!m_multiLine) {
currentTextBlockHeight = m_innerText->renderer()->height();
if (!m_innerBlock && currentTextBlockHeight < m_height)
m_innerText->renderer()->setPos(m_innerText->renderer()->xPos(), (m_height - currentTextBlockHeight) / 2);
}
}
void RenderTextControl::calcPrefWidths()
{
ASSERT(prefWidthsDirty());
m_minPrefWidth = 0;
m_maxPrefWidth = 0;
if (style()->width().isFixed() && style()->width().value() > 0)
m_minPrefWidth = m_maxPrefWidth = calcContentBoxWidth(style()->width().value());
else {
// Figure out how big a text control needs to be for a given number of characters
// (using "0" as the nominal character).
const UChar ch = '0';
float charWidth = style()->font().floatWidth(TextRun(&ch, 1), TextStyle(0, 0, 0, false, false, false));
int factor;
int scrollbarSize = 0;
if (m_multiLine) {
factor = static_cast<HTMLTextAreaElement*>(node())->cols();
// FIXME: We should get the size of the scrollbar from the RenderTheme instead.
if (m_innerText->renderer()->style()->overflowY() != OHIDDEN)
scrollbarSize = PlatformScrollbar::verticalScrollbarWidth();
} else {
factor = static_cast<HTMLInputElement*>(node())->size();
if (factor <= 0)
factor = 20;
}
m_maxPrefWidth = static_cast<int>(ceilf(charWidth * factor)) + scrollbarSize +
m_innerText->renderer()->paddingLeft() + m_innerText->renderer()->paddingRight();
if (m_resultsButton)
m_maxPrefWidth += m_resultsButton->renderer()->borderLeft() + m_resultsButton->renderer()->borderRight() +
m_resultsButton->renderer()->paddingLeft() + m_resultsButton->renderer()->paddingRight();
if (m_cancelButton)
m_maxPrefWidth += m_cancelButton->renderer()->borderLeft() + m_cancelButton->renderer()->borderRight() +
m_cancelButton->renderer()->paddingLeft() + m_cancelButton->renderer()->paddingRight();
}
if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
m_maxPrefWidth = max(m_maxPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
m_minPrefWidth = max(m_minPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
} else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
m_minPrefWidth = 0;
else
m_minPrefWidth = m_maxPrefWidth;
if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
m_maxPrefWidth = min(m_maxPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
m_minPrefWidth = min(m_minPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
}
int toAdd = paddingLeft() + paddingRight() + borderLeft() + borderRight();
m_minPrefWidth += toAdd;
m_maxPrefWidth += toAdd;
setPrefWidthsDirty(false);
}
void RenderTextControl::forwardEvent(Event* evt)
{
if (evt->type() == blurEvent) {
RenderObject* innerRenderer = m_innerText->renderer();
if (innerRenderer) {
RenderLayer* innerLayer = innerRenderer->layer();
if (innerLayer && !m_multiLine)
innerLayer->scrollToOffset(style()->direction() == RTL ? innerLayer->scrollWidth() : 0, 0);
}
updatePlaceholder();
} else if (evt->type() == focusEvent)
updatePlaceholder();
else {
if (evt->isMouseEvent() && m_resultsButton && static_cast<MouseEvent*>(evt)->x() < m_innerText->renderer()->absoluteBoundingBoxRect().x())
m_resultsButton->defaultEventHandler(evt);
else if (evt->isMouseEvent() && m_cancelButton && static_cast<MouseEvent*>(evt)->x() > m_innerText->renderer()->absoluteBoundingBoxRect().right())
m_cancelButton->defaultEventHandler(evt);
else
m_innerText->defaultEventHandler(evt);
}
}
void RenderTextControl::selectionChanged(bool userTriggered)
{
HTMLGenericFormElement* element = static_cast<HTMLGenericFormElement*>(node());
if (m_multiLine)
static_cast<HTMLTextAreaElement*>(element)->cacheSelection(selectionStart(), selectionEnd());
else
static_cast<HTMLInputElement*>(element)->cacheSelection(selectionStart(), selectionEnd());
if (Frame* frame = document()->frame())
if (frame->selectionController()->isRange() && userTriggered)
element->dispatchHTMLEvent(selectEvent, true, false);
}
void RenderTextControl::autoscroll()
{
RenderLayer* layer = m_innerText->renderer()->layer();
if (layer)
layer->autoscroll();
}
int RenderTextControl::scrollWidth() const
{
if (m_innerText)
return m_innerText->scrollWidth();
return RenderBlock::scrollWidth();
}
int RenderTextControl::scrollHeight() const
{
if (m_innerText)
return m_innerText->scrollHeight();
return RenderBlock::scrollHeight();
}
int RenderTextControl::scrollLeft() const
{
if (m_innerText)
return m_innerText->scrollLeft();
return RenderBlock::scrollLeft();
}
int RenderTextControl::scrollTop() const
{
if (m_innerText)
return m_innerText->scrollTop();
return RenderBlock::scrollTop();
}
void RenderTextControl::setScrollLeft(int newLeft)
{
if (m_innerText)
m_innerText->setScrollLeft(newLeft);
}
void RenderTextControl::setScrollTop(int newTop)
{
if (m_innerText)
m_innerText->setScrollTop(newTop);
}
const AtomicString& RenderTextControl::autosaveName() const
{
return static_cast<Element*>(node())->getAttribute(autosaveAttr);
}
void RenderTextControl::addSearchResult()
{
ASSERT(!m_multiLine);
HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
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 = SearchPopupMenu::create(this);
m_searchPopup->saveRecentSearches(name, m_recentSearches);
}
void RenderTextControl::showPopup()
{
if (m_searchPopupIsVisible)
return;
if (!m_searchPopup)
m_searchPopup = SearchPopupMenu::create(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 = static_cast<HTMLInputElement*>(node());
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->show(absoluteBoundingBoxRect(), document()->view(), -1);
}
void RenderTextControl::hidePopup()
{
if (m_searchPopup)
m_searchPopup->hide();
m_searchPopupIsVisible = false;
}
void RenderTextControl::valueChanged(unsigned listIndex, bool fireEvents)
{
ASSERT(static_cast<int>(listIndex) < listSize());
HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
if (static_cast<int>(listIndex) == (listSize() - 1)) {
if (fireEvents) {
m_recentSearches.clear();
const AtomicString& name = autosaveName();
if (!name.isEmpty()) {
if (!m_searchPopup)
m_searchPopup = SearchPopupMenu::create(this);
m_searchPopup->saveRecentSearches(name, m_recentSearches);
}
}
} else {
input->setValue(itemText(listIndex));
if (fireEvents)
input->onSearch();
input->select();
}
}
String RenderTextControl::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];
}
bool RenderTextControl::itemIsEnabled(unsigned listIndex) const
{
if (!listIndex || itemIsSeparator(listIndex))
return false;
return true;
}
RenderStyle* RenderTextControl::itemStyle(unsigned listIndex) const
{
return style();
}
Color RenderTextControl::itemBackgroundColor(unsigned listIndex) const
{
return style()->backgroundColor();
}
RenderStyle* RenderTextControl::clientStyle() const
{
return style();
}
Document* RenderTextControl::clientDocument() const
{
return document();
}
int RenderTextControl::clientPaddingLeft() const
{
return paddingLeft() + m_resultsButton->renderer()->width();
}
int RenderTextControl::clientPaddingRight() const
{
return paddingRight() + m_cancelButton->renderer()->width();
}
int RenderTextControl::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 RenderTextControl::selectedIndex() const
{
return -1;
}
bool RenderTextControl::itemIsSeparator(unsigned listIndex) const
{
// The separator will be the second to last item in our list.
return static_cast<int>(listIndex) == (listSize() - 2);
}
bool RenderTextControl::itemIsLabel(unsigned listIndex) const
{
return listIndex == 0;
}
bool RenderTextControl::itemIsSelected(unsigned listIndex) const
{
return false;
}
void RenderTextControl::setTextFromItem(unsigned listIndex)
{
static_cast<HTMLInputElement*>(node())->setValue(itemText(listIndex));
}
bool RenderTextControl::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier)
{
RenderLayer* layer = m_innerText->renderer()->layer();
if (layer && layer->scroll(direction, granularity, multiplier))
return true;
return RenderObject::scroll(direction, granularity, multiplier);
}
void RenderTextControl::searchEventTimerFired(Timer<RenderTextControl>*)
{
static_cast<HTMLInputElement*>(node())->onSearch();
}
void RenderTextControl::stopSearchEventTimer()
{
m_searchEventTimer.stop();
}
void RenderTextControl::startSearchEventTimer()
{
unsigned length = text().length();
// If there's no text, fire the event right away.
if (!length) {
m_searchEventTimer.stop();
static_cast<HTMLInputElement*>(node())->onSearch();
return;
}
// After typing the first key, we wait 0.5 seconds.
// After the second key, 0.4 seconds, then 0.3, then 0.2 from then on.
m_searchEventTimer.startOneShot(max(0.2, 0.6 - 0.1 * length));
}
bool RenderTextControl::isScrollable() const
{
if (m_innerText && m_innerText->renderer()->isScrollable())
return true;
return RenderObject::isScrollable();
}
} // namespace WebCore