1/**
2 * Copyright (C) 2006, 2007, 2010 Apple Inc. All rights reserved.
3 *           (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
4 * Copyright (C) 2010 Google Inc. All rights reserved.
5 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB.  If not, write to
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
21 *
22 */
23
24#include "config.h"
25#include "core/rendering/RenderTextControlSingleLine.h"
26
27#include "core/CSSValueKeywords.h"
28#include "core/InputTypeNames.h"
29#include "core/dom/shadow/ShadowRoot.h"
30#include "core/editing/FrameSelection.h"
31#include "core/frame/LocalFrame.h"
32#include "core/html/shadow/ShadowElementNames.h"
33#include "core/rendering/HitTestResult.h"
34#include "core/rendering/RenderLayer.h"
35#include "core/rendering/RenderTheme.h"
36#include "platform/PlatformKeyboardEvent.h"
37#include "platform/fonts/SimpleFontData.h"
38
39namespace blink {
40
41using namespace HTMLNames;
42
43RenderTextControlSingleLine::RenderTextControlSingleLine(HTMLInputElement* element)
44    : RenderTextControl(element)
45    , m_shouldDrawCapsLockIndicator(false)
46    , m_desiredInnerEditorLogicalHeight(-1)
47{
48}
49
50RenderTextControlSingleLine::~RenderTextControlSingleLine()
51{
52}
53
54inline Element* RenderTextControlSingleLine::containerElement() const
55{
56    return inputElement()->userAgentShadowRoot()->getElementById(ShadowElementNames::textFieldContainer());
57}
58
59inline Element* RenderTextControlSingleLine::editingViewPortElement() const
60{
61    return inputElement()->userAgentShadowRoot()->getElementById(ShadowElementNames::editingViewPort());
62}
63
64inline HTMLElement* RenderTextControlSingleLine::innerSpinButtonElement() const
65{
66    return toHTMLElement(inputElement()->userAgentShadowRoot()->getElementById(ShadowElementNames::spinButton()));
67}
68
69void RenderTextControlSingleLine::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
70{
71    RenderTextControl::paint(paintInfo, paintOffset);
72
73    if (paintInfo.phase == PaintPhaseBlockBackground && m_shouldDrawCapsLockIndicator) {
74        LayoutRect contentsRect = contentBoxRect();
75
76        // Center in the block progression direction.
77        if (isHorizontalWritingMode())
78            contentsRect.setY((height() - contentsRect.height()) / 2);
79        else
80            contentsRect.setX((width() - contentsRect.width()) / 2);
81
82        // Convert the rect into the coords used for painting the content
83        contentsRect.moveBy(paintOffset + location());
84        RenderTheme::theme().paintCapsLockIndicator(this, paintInfo, pixelSnappedIntRect(contentsRect));
85    }
86}
87
88LayoutUnit RenderTextControlSingleLine::computeLogicalHeightLimit() const
89{
90    return containerElement() ? contentLogicalHeight() : logicalHeight();
91}
92
93void RenderTextControlSingleLine::layout()
94{
95    SubtreeLayoutScope layoutScope(*this);
96
97    // FIXME: We should remove the height-related hacks in layout() and
98    // styleDidChange(). We need them because
99    // - Center the inner elements vertically if the input height is taller than
100    //   the intrinsic height of the inner elements.
101    // - Shrink the inner elment heights if the input height is samller than the
102    //   intrinsic heights of the inner elements.
103
104    // We don't honor paddings and borders for textfields without decorations
105    // and type=search if the text height is taller than the contentHeight()
106    // because of compability.
107
108    RenderBox* innerEditorRenderer = innerEditorElement()->renderBox();
109    RenderBox* viewPortRenderer = editingViewPortElement() ? editingViewPortElement()->renderBox() : 0;
110
111    // To ensure consistency between layouts, we need to reset any conditionally overriden height.
112    if (innerEditorRenderer && !innerEditorRenderer->style()->logicalHeight().isAuto()) {
113        innerEditorRenderer->style()->setLogicalHeight(Length(Auto));
114        layoutScope.setNeedsLayout(innerEditorRenderer);
115        HTMLElement* placeholderElement = inputElement()->placeholderElement();
116        if (RenderBox* placeholderBox = placeholderElement ? placeholderElement->renderBox() : 0)
117            layoutScope.setNeedsLayout(placeholderBox);
118    }
119    if (viewPortRenderer && !viewPortRenderer->style()->logicalHeight().isAuto()) {
120        viewPortRenderer->style()->setLogicalHeight(Length(Auto));
121        layoutScope.setNeedsLayout(viewPortRenderer);
122    }
123
124    RenderBlockFlow::layoutBlock(false);
125
126    Element* container = containerElement();
127    RenderBox* containerRenderer = container ? container->renderBox() : 0;
128
129    // Set the text block height
130    LayoutUnit desiredLogicalHeight = textBlockLogicalHeight();
131    LayoutUnit logicalHeightLimit = computeLogicalHeightLimit();
132    if (innerEditorRenderer && innerEditorRenderer->logicalHeight() > logicalHeightLimit) {
133        if (desiredLogicalHeight != innerEditorRenderer->logicalHeight())
134            layoutScope.setNeedsLayout(this);
135
136        m_desiredInnerEditorLogicalHeight = desiredLogicalHeight;
137
138        innerEditorRenderer->style()->setLogicalHeight(Length(desiredLogicalHeight, Fixed));
139        layoutScope.setNeedsLayout(innerEditorRenderer);
140        if (viewPortRenderer) {
141            viewPortRenderer->style()->setLogicalHeight(Length(desiredLogicalHeight, Fixed));
142            layoutScope.setNeedsLayout(viewPortRenderer);
143        }
144    }
145    // The container might be taller because of decoration elements.
146    if (containerRenderer) {
147        containerRenderer->layoutIfNeeded();
148        LayoutUnit containerLogicalHeight = containerRenderer->logicalHeight();
149        if (containerLogicalHeight > logicalHeightLimit) {
150            containerRenderer->style()->setLogicalHeight(Length(logicalHeightLimit, Fixed));
151            layoutScope.setNeedsLayout(this);
152        } else if (containerRenderer->logicalHeight() < contentLogicalHeight()) {
153            containerRenderer->style()->setLogicalHeight(Length(contentLogicalHeight(), Fixed));
154            layoutScope.setNeedsLayout(this);
155        } else
156            containerRenderer->style()->setLogicalHeight(Length(containerLogicalHeight, Fixed));
157    }
158
159    // If we need another layout pass, we have changed one of children's height so we need to relayout them.
160    if (needsLayout())
161        RenderBlockFlow::layoutBlock(true);
162
163    // Center the child block in the block progression direction (vertical centering for horizontal text fields).
164    if (!container && innerEditorRenderer && innerEditorRenderer->height() != contentLogicalHeight()) {
165        LayoutUnit logicalHeightDiff = innerEditorRenderer->logicalHeight() - contentLogicalHeight();
166        innerEditorRenderer->setLogicalTop(innerEditorRenderer->logicalTop() - (logicalHeightDiff / 2 + layoutMod(logicalHeightDiff, 2)));
167    } else
168        centerContainerIfNeeded(containerRenderer);
169
170    HTMLElement* placeholderElement = inputElement()->placeholderElement();
171    if (RenderBox* placeholderBox = placeholderElement ? placeholderElement->renderBox() : 0) {
172        LayoutSize innerEditorSize;
173
174        if (innerEditorRenderer)
175            innerEditorSize = innerEditorRenderer->size();
176        placeholderBox->style()->setWidth(Length(innerEditorSize.width() - placeholderBox->borderAndPaddingWidth(), Fixed));
177        placeholderBox->style()->setHeight(Length(innerEditorSize.height() - placeholderBox->borderAndPaddingHeight(), Fixed));
178        bool neededLayout = placeholderBox->needsLayout();
179        placeholderBox->layoutIfNeeded();
180        LayoutPoint textOffset;
181        if (innerEditorRenderer)
182            textOffset = innerEditorRenderer->location();
183        if (editingViewPortElement() && editingViewPortElement()->renderBox())
184            textOffset += toLayoutSize(editingViewPortElement()->renderBox()->location());
185        if (containerRenderer)
186            textOffset += toLayoutSize(containerRenderer->location());
187        placeholderBox->setLocation(textOffset);
188
189        // The placeholder gets layout last, after the parent text control and its other children,
190        // so in order to get the correct overflow from the placeholder we need to recompute it now.
191        if (neededLayout)
192            computeOverflow(clientLogicalBottom());
193    }
194}
195
196bool RenderTextControlSingleLine::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
197{
198    if (!RenderTextControl::nodeAtPoint(request, result, locationInContainer, accumulatedOffset, hitTestAction))
199        return false;
200
201    // Say that we hit the inner text element if
202    //  - we hit a node inside the inner text element,
203    //  - we hit the <input> element (e.g. we're over the border or padding), or
204    //  - we hit regions not in any decoration buttons.
205    Element* container = containerElement();
206    if (result.innerNode()->isDescendantOf(innerEditorElement()) || result.innerNode() == node() || (container && container == result.innerNode())) {
207        LayoutPoint pointInParent = locationInContainer.point();
208        if (container && editingViewPortElement()) {
209            if (editingViewPortElement()->renderBox())
210                pointInParent -= toLayoutSize(editingViewPortElement()->renderBox()->location());
211            if (container->renderBox())
212                pointInParent -= toLayoutSize(container->renderBox()->location());
213        }
214        hitInnerEditorElement(result, pointInParent, accumulatedOffset);
215    }
216    return true;
217}
218
219void RenderTextControlSingleLine::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
220{
221    m_desiredInnerEditorLogicalHeight = -1;
222    RenderTextControl::styleDidChange(diff, oldStyle);
223
224    // We may have set the width and the height in the old style in layout().
225    // Reset them now to avoid getting a spurious layout hint.
226    Element* viewPort = editingViewPortElement();
227    if (RenderObject* viewPortRenderer = viewPort ? viewPort->renderer() : 0) {
228        viewPortRenderer->style()->setHeight(Length());
229        viewPortRenderer->style()->setWidth(Length());
230    }
231    Element* container = containerElement();
232    if (RenderObject* containerRenderer = container ? container->renderer() : 0) {
233        containerRenderer->style()->setHeight(Length());
234        containerRenderer->style()->setWidth(Length());
235    }
236    RenderObject* innerEditorRenderer = innerEditorElement()->renderer();
237    if (innerEditorRenderer && diff.needsFullLayout())
238        innerEditorRenderer->setNeedsLayoutAndFullPaintInvalidation();
239    if (HTMLElement* placeholder = inputElement()->placeholderElement())
240        placeholder->setInlineStyleProperty(CSSPropertyTextOverflow, textShouldBeTruncated() ? CSSValueEllipsis : CSSValueClip);
241    setHasOverflowClip(false);
242}
243
244void RenderTextControlSingleLine::capsLockStateMayHaveChanged()
245{
246    if (!node())
247        return;
248
249    // Only draw the caps lock indicator if these things are true:
250    // 1) The field is a password field
251    // 2) The frame is active
252    // 3) The element is focused
253    // 4) The caps lock is on
254    bool shouldDrawCapsLockIndicator = false;
255
256    if (LocalFrame* frame = document().frame())
257        shouldDrawCapsLockIndicator = inputElement()->type() == InputTypeNames::password && frame->selection().isFocusedAndActive() && document().focusedElement() == node() && PlatformKeyboardEvent::currentCapsLockState();
258
259    if (shouldDrawCapsLockIndicator != m_shouldDrawCapsLockIndicator) {
260        m_shouldDrawCapsLockIndicator = shouldDrawCapsLockIndicator;
261        setShouldDoFullPaintInvalidation(true);
262    }
263}
264
265bool RenderTextControlSingleLine::hasControlClip() const
266{
267    // Apply control clip for text fields with decorations.
268    return !!containerElement();
269}
270
271LayoutRect RenderTextControlSingleLine::controlClipRect(const LayoutPoint& additionalOffset) const
272{
273    ASSERT(hasControlClip());
274    LayoutRect clipRect = contentBoxRect();
275    if (containerElement()->renderBox())
276        clipRect = unionRect(clipRect, containerElement()->renderBox()->frameRect());
277    clipRect.moveBy(additionalOffset);
278    return clipRect;
279}
280
281float RenderTextControlSingleLine::getAvgCharWidth(AtomicString family)
282{
283    // Since Lucida Grande is the default font, we want this to match the width
284    // of MS Shell Dlg, the default font for textareas in Firefox, Safari Win and
285    // IE for some encodings (in IE, the default font is encoding specific).
286    // 901 is the avgCharWidth value in the OS/2 table for MS Shell Dlg.
287    if (family == "Lucida Grande")
288        return scaleEmToUnits(901);
289
290    return RenderTextControl::getAvgCharWidth(family);
291}
292
293LayoutUnit RenderTextControlSingleLine::preferredContentLogicalWidth(float charWidth) const
294{
295    int factor;
296    bool includesDecoration = inputElement()->sizeShouldIncludeDecoration(factor);
297    if (factor <= 0)
298        factor = 20;
299
300    LayoutUnit result = LayoutUnit::fromFloatCeil(charWidth * factor);
301
302    float maxCharWidth = 0.f;
303    AtomicString family = style()->font().fontDescription().family().family();
304    // Since Lucida Grande is the default font, we want this to match the width
305    // of MS Shell Dlg, the default font for textareas in Firefox, Safari Win and
306    // IE for some encodings (in IE, the default font is encoding specific).
307    // 4027 is the (xMax - xMin) value in the "head" font table for MS Shell Dlg.
308    if (family == "Lucida Grande")
309        maxCharWidth = scaleEmToUnits(4027);
310    else if (hasValidAvgCharWidth(family))
311        maxCharWidth = roundf(style()->font().primaryFont()->maxCharWidth());
312
313    // For text inputs, IE adds some extra width.
314    if (maxCharWidth > 0.f)
315        result += maxCharWidth - charWidth;
316
317    if (includesDecoration) {
318        HTMLElement* spinButton = innerSpinButtonElement();
319        if (RenderBox* spinRenderer = spinButton ? spinButton->renderBox() : 0) {
320            result += spinRenderer->borderAndPaddingLogicalWidth();
321            // Since the width of spinRenderer is not calculated yet, spinRenderer->logicalWidth() returns 0.
322            // So computedStyle()->logicalWidth() is used instead.
323            result += spinButton->computedStyle()->logicalWidth().value();
324        }
325    }
326
327    return result;
328}
329
330LayoutUnit RenderTextControlSingleLine::computeControlLogicalHeight(LayoutUnit lineHeight, LayoutUnit nonContentHeight) const
331{
332    return lineHeight + nonContentHeight;
333}
334
335PassRefPtr<RenderStyle> RenderTextControlSingleLine::createInnerEditorStyle(const RenderStyle* startStyle) const
336{
337    RefPtr<RenderStyle> textBlockStyle = RenderStyle::create();
338    textBlockStyle->inheritFrom(startStyle);
339    adjustInnerEditorStyle(textBlockStyle.get());
340
341    textBlockStyle->setWhiteSpace(PRE);
342    textBlockStyle->setOverflowWrap(NormalOverflowWrap);
343    textBlockStyle->setOverflowX(OHIDDEN);
344    textBlockStyle->setOverflowY(OHIDDEN);
345    textBlockStyle->setTextOverflow(textShouldBeTruncated() ? TextOverflowEllipsis : TextOverflowClip);
346
347    if (m_desiredInnerEditorLogicalHeight >= 0)
348        textBlockStyle->setLogicalHeight(Length(m_desiredInnerEditorLogicalHeight, Fixed));
349    // Do not allow line-height to be smaller than our default.
350    if (textBlockStyle->fontMetrics().lineSpacing() > lineHeight(true, HorizontalLine, PositionOfInteriorLineBoxes))
351        textBlockStyle->setLineHeight(RenderStyle::initialLineHeight());
352
353    textBlockStyle->setDisplay(BLOCK);
354    textBlockStyle->setUnique();
355
356    if (inputElement()->shouldRevealPassword())
357        textBlockStyle->setTextSecurity(TSNONE);
358
359    return textBlockStyle.release();
360}
361
362bool RenderTextControlSingleLine::textShouldBeTruncated() const
363{
364    return document().focusedElement() != node() && style()->textOverflow() == TextOverflowEllipsis;
365}
366
367void RenderTextControlSingleLine::autoscroll(const IntPoint& position)
368{
369    RenderBox* renderer = innerEditorElement()->renderBox();
370    if (!renderer)
371        return;
372
373    renderer->autoscroll(position);
374}
375
376LayoutUnit RenderTextControlSingleLine::scrollWidth() const
377{
378    if (innerEditorElement())
379        return innerEditorElement()->scrollWidth();
380    return RenderBlockFlow::scrollWidth();
381}
382
383LayoutUnit RenderTextControlSingleLine::scrollHeight() const
384{
385    if (innerEditorElement())
386        return innerEditorElement()->scrollHeight();
387    return RenderBlockFlow::scrollHeight();
388}
389
390LayoutUnit RenderTextControlSingleLine::scrollLeft() const
391{
392    if (innerEditorElement())
393        return innerEditorElement()->scrollLeft();
394    return RenderBlockFlow::scrollLeft();
395}
396
397LayoutUnit RenderTextControlSingleLine::scrollTop() const
398{
399    if (innerEditorElement())
400        return innerEditorElement()->scrollTop();
401    return RenderBlockFlow::scrollTop();
402}
403
404void RenderTextControlSingleLine::setScrollLeft(LayoutUnit newLeft)
405{
406    if (innerEditorElement())
407        innerEditorElement()->setScrollLeft(newLeft);
408}
409
410void RenderTextControlSingleLine::setScrollTop(LayoutUnit newTop)
411{
412    if (innerEditorElement())
413        innerEditorElement()->setScrollTop(newTop);
414}
415
416HTMLInputElement* RenderTextControlSingleLine::inputElement() const
417{
418    return toHTMLInputElement(node());
419}
420
421}
422