2 * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
3 * (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
23 #include "RenderTextControl.h"
25 #include "HTMLTextFormControlElement.h"
26 #include "HitTestResult.h"
27 #include "RenderText.h"
28 #include "ScrollbarTheme.h"
29 #include "TextIterator.h"
30 #include "VisiblePosition.h"
31 #include <wtf/unicode/CharacterNames.h>
37 // Value chosen by observation. This can be tweaked.
38 static const int minColorContrastValue = 1300;
39 // For transparent or translucent background color, use lightening.
40 static const int minDisabledColorAlphaValue = 128;
42 static Color disabledTextColor(const Color& textColor, const Color& backgroundColor)
44 // The explicit check for black is an optimization for the 99% case (black on white).
45 // This also means that black on black will turn into grey on black when disabled.
47 if (textColor.rgb() == Color::black || backgroundColor.alpha() < minDisabledColorAlphaValue || differenceSquared(textColor, Color::white) > differenceSquared(backgroundColor, Color::white))
48 disabledColor = textColor.light();
50 disabledColor = textColor.dark();
52 // If there's not very much contrast between the disabled color and the background color,
53 // just leave the text color alone. We don't want to change a good contrast color scheme so that it has really bad contrast.
54 // If the the contrast was already poor, then it doesn't do any good to change it to a different poor contrast color scheme.
55 if (differenceSquared(disabledColor, backgroundColor) < minColorContrastValue)
61 RenderTextControl::RenderTextControl(Node* node)
64 ASSERT(toTextFormControl(node));
67 RenderTextControl::~RenderTextControl()
71 HTMLTextFormControlElement* RenderTextControl::textFormControlElement() const
73 return static_cast<HTMLTextFormControlElement*>(node());
76 HTMLElement* RenderTextControl::innerTextElement() const
78 return textFormControlElement()->innerTextElement();
81 void RenderTextControl::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
83 RenderBlock::styleDidChange(diff, oldStyle);
84 Element* innerText = innerTextElement();
87 RenderBlock* innerTextRenderer = toRenderBlock(innerText->renderer());
88 if (innerTextRenderer) {
89 // We may have set the width and the height in the old style in layout().
90 // Reset them now to avoid getting a spurious layout hint.
91 innerTextRenderer->style()->setHeight(Length());
92 innerTextRenderer->style()->setWidth(Length());
93 innerTextRenderer->setStyle(createInnerTextStyle(style()));
94 innerText->setNeedsStyleRecalc();
96 textFormControlElement()->updatePlaceholderVisibility(false);
99 static inline bool updateUserModifyProperty(Node* node, RenderStyle* style)
101 bool isEnabled = true;
102 bool isReadOnlyControl = false;
104 if (node->isElementNode()) {
105 Element* element = static_cast<Element*>(node);
106 isEnabled = element->isEnabledFormControl();
107 isReadOnlyControl = element->isReadOnlyFormControl();
110 style->setUserModify((isReadOnlyControl || !isEnabled) ? READ_ONLY : READ_WRITE_PLAINTEXT_ONLY);
114 void RenderTextControl::adjustInnerTextStyle(const RenderStyle* startStyle, RenderStyle* textBlockStyle) const
116 // The inner block, if present, always has its direction set to LTR,
117 // so we need to inherit the direction and unicode-bidi style from the element.
118 textBlockStyle->setDirection(style()->direction());
119 textBlockStyle->setUnicodeBidi(style()->unicodeBidi());
121 bool disabled = updateUserModifyProperty(node(), textBlockStyle);
123 textBlockStyle->setColor(disabledTextColor(textBlockStyle->visitedDependentColor(CSSPropertyColor), startStyle->visitedDependentColor(CSSPropertyBackgroundColor)));
126 int RenderTextControl::textBlockHeight() const
128 return height() - borderAndPaddingHeight();
131 int RenderTextControl::textBlockWidth() const
133 Element* innerText = innerTextElement();
135 return width() - borderAndPaddingWidth() - innerText->renderBox()->paddingLeft() - innerText->renderBox()->paddingRight();
138 void RenderTextControl::updateFromElement()
140 Element* innerText = innerTextElement();
142 updateUserModifyProperty(node(), innerText->renderer()->style());
145 VisiblePosition RenderTextControl::visiblePositionForIndex(int index) const
148 return VisiblePosition(firstPositionInNode(innerTextElement()), DOWNSTREAM);
149 ExceptionCode ec = 0;
150 RefPtr<Range> range = Range::create(document());
151 range->selectNodeContents(innerTextElement(), ec);
153 CharacterIterator it(range.get());
154 it.advance(index - 1);
155 return VisiblePosition(it.range()->endPosition(), UPSTREAM);
158 int RenderTextControl::scrollbarThickness() const
160 // FIXME: We should get the size of the scrollbar from the RenderTheme instead.
161 return ScrollbarTheme::theme()->scrollbarThickness();
164 void RenderTextControl::computeLogicalHeight()
166 HTMLElement* innerText = innerTextElement();
168 RenderBox* innerTextRenderBox = innerText->renderBox();
170 setHeight(innerTextRenderBox->borderTop() + innerTextRenderBox->borderBottom() +
171 innerTextRenderBox->paddingTop() + innerTextRenderBox->paddingBottom() +
172 innerTextRenderBox->marginTop() + innerTextRenderBox->marginBottom());
174 adjustControlHeightBasedOnLineHeight(innerText->renderBox()->lineHeight(true, HorizontalLine, PositionOfInteriorLineBoxes));
175 setHeight(height() + borderAndPaddingHeight());
177 // We are able to have a horizontal scrollbar if the overflow style is scroll, or if its auto and there's no word wrap.
178 if (style()->overflowX() == OSCROLL || (style()->overflowX() == OAUTO && innerText->renderer()->style()->wordWrap() == NormalWordWrap))
179 setHeight(height() + scrollbarThickness());
181 RenderBlock::computeLogicalHeight();
184 void RenderTextControl::hitInnerTextElement(HitTestResult& result, const LayoutPoint& pointInContainer, const LayoutPoint& accumulatedOffset)
186 LayoutPoint adjustedLocation = accumulatedOffset + location();
187 HTMLElement* innerText = innerTextElement();
188 result.setInnerNode(innerText);
189 result.setInnerNonSharedNode(innerText);
190 result.setLocalPoint(pointInContainer - toLayoutSize(adjustedLocation + innerText->renderBox()->location()));
193 static const char* fontFamiliesWithInvalidCharWidth[] = {
194 "American Typewriter",
230 // For font families where any of the fonts don't have a valid entry in the OS/2 table
231 // for avgCharWidth, fallback to the legacy webkit behavior of getting the avgCharWidth
232 // from the width of a '0'. This only seems to apply to a fixed number of Mac fonts,
233 // but, in order to get similar rendering across platforms, we do this check for
235 bool RenderTextControl::hasValidAvgCharWidth(AtomicString family)
237 static HashSet<AtomicString>* fontFamiliesWithInvalidCharWidthMap = 0;
239 if (!fontFamiliesWithInvalidCharWidthMap) {
240 fontFamiliesWithInvalidCharWidthMap = new HashSet<AtomicString>;
242 for (size_t i = 0; i < WTF_ARRAY_LENGTH(fontFamiliesWithInvalidCharWidth); ++i)
243 fontFamiliesWithInvalidCharWidthMap->add(AtomicString(fontFamiliesWithInvalidCharWidth[i]));
246 return !fontFamiliesWithInvalidCharWidthMap->contains(family);
249 float RenderTextControl::getAvgCharWidth(AtomicString family)
251 if (hasValidAvgCharWidth(family))
252 return roundf(style()->font().primaryFont()->avgCharWidth());
254 const UChar ch = '0';
255 const String str = String(&ch, 1);
256 const Font& font = style()->font();
257 TextRun textRun = constructTextRun(this, font, str, style(), TextRun::AllowTrailingExpansion);
258 textRun.disableRoundingHacks();
259 return font.width(textRun);
262 float RenderTextControl::scaleEmToUnits(int x) const
264 // This matches the unitsPerEm value for MS Shell Dlg and Courier New from the "head" font table.
265 float unitsPerEm = 2048.0f;
266 return roundf(style()->font().size() * x / unitsPerEm);
269 void RenderTextControl::computePreferredLogicalWidths()
271 ASSERT(preferredLogicalWidthsDirty());
273 m_minPreferredLogicalWidth = 0;
274 m_maxPreferredLogicalWidth = 0;
276 if (style()->width().isFixed() && style()->width().value() > 0)
277 m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style()->width().value());
279 // Use average character width. Matches IE.
280 AtomicString family = style()->font().family().family();
281 RenderBox* innerTextRenderBox = innerTextElement()->renderBox();
282 m_maxPreferredLogicalWidth = preferredContentWidth(getAvgCharWidth(family)) + innerTextRenderBox->paddingLeft() + innerTextRenderBox->paddingRight();
285 if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
286 m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value()));
287 m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value()));
288 } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
289 m_minPreferredLogicalWidth = 0;
291 m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth;
293 if (style()->maxWidth().isFixed()) {
294 m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value()));
295 m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value()));
298 LayoutUnit toAdd = borderAndPaddingWidth();
300 m_minPreferredLogicalWidth += toAdd;
301 m_maxPreferredLogicalWidth += toAdd;
303 setPreferredLogicalWidthsDirty(false);
306 void RenderTextControl::addFocusRingRects(Vector<LayoutRect>& rects, const LayoutPoint& additionalOffset)
308 if (!size().isEmpty())
309 rects.append(LayoutRect(additionalOffset, size()));
312 RenderObject* RenderTextControl::layoutSpecialExcludedChild(bool relayoutChildren)
314 HTMLElement* placeholder = toTextFormControl(node())->placeholderElement();
315 RenderObject* placeholderRenderer = placeholder ? placeholder->renderer() : 0;
316 if (!placeholderRenderer)
318 if (relayoutChildren) {
319 // The markParents arguments should be false because this function is
320 // called from layout() of the parent and the placeholder layout doesn't
321 // affect the parent layout.
322 placeholderRenderer->setChildNeedsLayout(true, false);
324 return placeholderRenderer;
327 } // namespace WebCore