blob: 282c940ced28272fa2b0f633c8f10385a13669ac [file] [log] [blame]
/**
* Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
* (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
*
* 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 "AXObjectCache.h"
#include "CharacterNames.h"
#include "Editor.h"
#include "Event.h"
#include "EventNames.h"
#include "Frame.h"
#include "HTMLBRElement.h"
#include "HTMLNames.h"
#include "HitTestResult.h"
#include "RenderLayer.h"
#include "RenderText.h"
#include "ScrollbarTheme.h"
#include "SelectionController.h"
#include "Text.h"
#include "TextControlInnerElements.h"
#include "TextIterator.h"
using namespace std;
namespace WebCore {
using namespace HTMLNames;
// Value chosen by observation. This can be tweaked.
static const int minColorContrastValue = 1300;
static Color disabledTextColor(const Color& textColor, const Color& backgroundColor)
{
// The explicit 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.
Color disabledColor;
if (textColor.rgb() == Color::black || differenceSquared(textColor, Color::white) > differenceSquared(backgroundColor, Color::white))
disabledColor = textColor.light();
else
disabledColor = textColor.dark();
// If there's not very much contrast between the disabled color and the background color,
// just leave the text color alone. We don't want to change a good contrast color scheme so that it has really bad contrast.
// If the the contrast was already poor, then it doesn't do any good to change it to a different poor contrast color scheme.
if (differenceSquared(disabledColor, backgroundColor) < minColorContrastValue)
return textColor;
return disabledColor;
}
RenderTextControl::RenderTextControl(Node* node)
: RenderBlock(node)
, m_edited(false)
, m_userEdited(false)
{
}
RenderTextControl::~RenderTextControl()
{
// The children renderers have already been destroyed by destroyLeftoverChildren
if (m_innerText)
m_innerText->detach();
}
void RenderTextControl::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
{
RenderBlock::styleDidChange(diff, oldStyle);
if (m_innerText) {
RenderBlock* textBlockRenderer = toRenderBlock(m_innerText->renderer());
RefPtr<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);
}
}
setReplaced(isInline());
}
static inline bool updateUserModifyProperty(Node* node, RenderStyle* style)
{
bool isEnabled = true;
bool isReadOnlyControl = false;
if (node->isElementNode()) {
Element* element = static_cast<Element*>(node);
isEnabled = element->isEnabledFormControl();
isReadOnlyControl = element->isReadOnlyFormControl();
}
style->setUserModify((isReadOnlyControl || !isEnabled) ? READ_ONLY : READ_WRITE_PLAINTEXT_ONLY);
return !isEnabled;
}
void RenderTextControl::adjustInnerTextStyle(const RenderStyle* startStyle, RenderStyle* textBlockStyle) const
{
// 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());
bool disabled = updateUserModifyProperty(node(), textBlockStyle);
if (disabled)
textBlockStyle->setColor(disabledTextColor(textBlockStyle->color(), startStyle->backgroundColor()));
}
void RenderTextControl::createSubtreeIfNeeded(TextControlInnerElement* innerBlock)
{
if (!m_innerText) {
// Create the text block element
// For non-search fields, there is no intermediate innerBlock as the shadow node.
// m_innerText will be the shadow node in that case.
RenderStyle* parentStyle = innerBlock ? innerBlock->renderer()->style() : style();
m_innerText = new TextControlInnerTextElement(document(), innerBlock ? 0 : node());
m_innerText->attachInnerElement(innerBlock ? innerBlock : node(), createInnerTextStyle(parentStyle), renderArena());
}
}
int RenderTextControl::textBlockHeight() const
{
return height() - paddingTop() - paddingBottom() - borderTop() - borderBottom();
}
int RenderTextControl::textBlockWidth() const
{
return width() - paddingLeft() - paddingRight() - borderLeft() - borderRight()
- m_innerText->renderBox()->paddingLeft() - m_innerText->renderBox()->paddingRight();
}
void RenderTextControl::updateFromElement()
{
updateUserModifyProperty(node(), m_innerText->renderer()->style());
}
void RenderTextControl::setInnerTextValue(const String& innerTextValue)
{
String value;
if (innerTextValue.isNull())
value = "";
else {
value = innerTextValue;
value = document()->displayStringModifiedByEncoding(value);
}
if (value != text() || !m_innerText->hasChildNodes()) {
if (value != text()) {
if (Frame* frame = document()->frame()) {
frame->editor()->clearUndoRedoOperations();
if (AXObjectCache::accessibilityEnabled())
document()->axObjectCache()->postNotification(this, "AXValueChanged", false);
}
}
ExceptionCode ec = 0;
m_innerText->setInnerText(value, ec);
ASSERT(!ec);
if (value.endsWith("\n") || value.endsWith("\r")) {
m_innerText->appendChild(new HTMLBRElement(brTag, document()), ec);
ASSERT(!ec);
}
m_edited = false;
m_userEdited = false;
}
static_cast<Element*>(node())->setFormControlValueMatchesRenderer(true);
}
void RenderTextControl::setUserEdited(bool isUserEdited)
{
m_userEdited = isUserEdited;
document()->setIgnoreAutofocus(isUserEdited);
}
int RenderTextControl::selectionStart()
{
Frame* frame = document()->frame();
if (!frame)
return 0;
return indexForVisiblePosition(frame->selection()->start());
}
int RenderTextControl::selectionEnd()
{
Frame* frame = document()->frame();
if (!frame)
return 0;
return indexForVisiblePosition(frame->selection()->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->renderBox()->height()) {
cacheSelection(start, end);
return;
}
VisiblePosition startPosition = visiblePositionForIndex(start);
VisiblePosition endPosition;
if (start == end)
endPosition = startPosition;
else
endPosition = visiblePositionForIndex(end);
// startPosition and endPosition can be null position for example when
// "-webkit-user-select: none" style attribute is specified.
if (startPosition.isNotNull() && endPosition.isNotNull()) {
ASSERT(startPosition.deepEquivalent().node()->shadowAncestorNode() == node() && endPosition.deepEquivalent().node()->shadowAncestorNode() == node());
}
VisibleSelection newSelection = VisibleSelection(startPosition, endPosition);
if (Frame* frame = document()->frame())
frame->selection()->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);
}
VisibleSelection RenderTextControl::selection(int start, int end) const
{
return VisibleSelection(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 = Range::create(document());
range->selectNodeContents(m_innerText.get(), ec);
ASSERT(!ec);
CharacterIterator it(range.get());
it.advance(index - 1);
Node* endContainer = it.range()->endContainer(ec);
ASSERT(!ec);
int endOffset = it.range()->endOffset(ec);
ASSERT(!ec);
return VisiblePosition(endContainer, endOffset, 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 = Range::create(document());
range->setStart(m_innerText.get(), 0, ec);
ASSERT(!ec);
range->setEnd(indexPosition.node(), indexPosition.deprecatedEditingOffset(), ec);
ASSERT(!ec);
return TextIterator::rangeLength(range.get());
}
void RenderTextControl::subtreeHasChanged()
{
m_edited = true;
m_userEdited = true;
}
String RenderTextControl::finishText(Vector<UChar>& result) const
{
// Remove one trailing newline; there's always one that's collapsed out by rendering.
size_t size = result.size();
if (size && result[size - 1] == '\n')
result.shrink(--size);
// Convert backslash to currency symbol.
document()->displayBufferModifiedByEncoding(result.data(), result.size());
return String::adopt(result);
}
String RenderTextControl::text()
{
if (!m_innerText)
return "";
Vector<UChar> result;
for (Node* n = m_innerText.get(); n; n = n->traverseNextNode(m_innerText.get())) {
if (n->hasTagName(brTag))
result.append(&newlineCharacter, 1);
else if (n->isTextNode()) {
String data = static_cast<Text*>(n)->data();
result.append(data.characters(), data.length());
}
}
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;
breakOffset = 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->isText() ? toRenderText(renderer)->firstTextBox() : toRenderBox(renderer)->inlineBoxWrapper();
if (!box)
return "";
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 position = 0;
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);
}
int RenderTextControl::scrollbarThickness() const
{
// FIXME: We should get the size of the scrollbar from the RenderTheme instead.
return ScrollbarTheme::nativeTheme()->scrollbarThickness();
}
void RenderTextControl::calcHeight()
{
setHeight(m_innerText->renderBox()->borderTop() + m_innerText->renderBox()->borderBottom() +
m_innerText->renderBox()->paddingTop() + m_innerText->renderBox()->paddingBottom() +
m_innerText->renderBox()->marginTop() + m_innerText->renderBox()->marginBottom());
adjustControlHeightBasedOnLineHeight(m_innerText->renderer()->lineHeight(true, true));
setHeight(height() + paddingTop() + paddingBottom() + borderTop() + borderBottom());
// 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 (style()->overflowX() == OSCROLL || (style()->overflowX() == OAUTO && m_innerText->renderer()->style()->wordWrap() == NormalWordWrap))
setHeight(height() + scrollbarThickness());
RenderBlock::calcHeight();
}
void RenderTextControl::hitInnerTextElement(HitTestResult& result, int xPos, int yPos, int tx, int ty)
{
result.setInnerNode(m_innerText.get());
result.setInnerNonSharedNode(m_innerText.get());
result.setLocalPoint(IntPoint(xPos - tx - x() - m_innerText->renderBox()->x(),
yPos - ty - y() - m_innerText->renderBox()->y()));
}
void RenderTextControl::forwardEvent(Event* event)
{
if (event->type() == eventNames().blurEvent || event->type() == eventNames().focusEvent)
return;
m_innerText->defaultEventHandler(event);
}
IntRect RenderTextControl::controlClipRect(int tx, int ty) const
{
IntRect clipRect = contentBoxRect();
clipRect.move(tx, ty);
return clipRect;
}
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, false, 0, 0, false, false, false));
m_maxPrefWidth = preferredContentWidth(charWidth) + m_innerText->renderBox()->paddingLeft() + m_innerText->renderBox()->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::selectionChanged(bool userTriggered)
{
cacheSelection(selectionStart(), selectionEnd());
if (Frame* frame = document()->frame()) {
if (frame->selection()->isRange() && userTriggered)
node()->dispatchEvent(eventNames().selectEvent, true, false);
}
}
void RenderTextControl::addFocusRingRects(GraphicsContext* graphicsContext, int tx, int ty)
{
graphicsContext->addFocusRingRect(IntRect(tx, ty, width(), height()));
}
HTMLElement* RenderTextControl::innerTextElement() const
{
return m_innerText.get();
}
} // namespace WebCore