2008-11-04 Anders Carlsson <andersca@apple.com>
[webkit/qt.git] / WebCore / rendering / RenderTextControl.cpp
blobfe266adc4446104299e3aa3cc8d2bb9311336abd
1 /**
2 * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
21 #include "config.h"
22 #include "RenderTextControl.h"
24 #include "CharacterNames.h"
25 #include "CSSStyleSelector.h"
26 #include "Document.h"
27 #include "Editor.h"
28 #include "EditorClient.h"
29 #include "Event.h"
30 #include "EventNames.h"
31 #include "FontSelector.h"
32 #include "Frame.h"
33 #include "FrameView.h"
34 #include "HTMLBRElement.h"
35 #include "HTMLInputElement.h"
36 #include "HTMLNames.h"
37 #include "HTMLTextAreaElement.h"
38 #include "HitTestResult.h"
39 #include "LocalizedStrings.h"
40 #include "MouseEvent.h"
41 #include "PlatformKeyboardEvent.h"
42 #include "RenderScrollbar.h"
43 #include "RenderTheme.h"
44 #include "ScrollbarTheme.h"
45 #include "SearchPopupMenu.h"
46 #include "SelectionController.h"
47 #include "Settings.h"
48 #include "Text.h"
49 #include "TextControlInnerElements.h"
50 #include "TextIterator.h"
51 #include "htmlediting.h"
52 #include "visible_units.h"
53 #include <math.h>
55 using namespace std;
57 namespace WebCore {
59 using namespace HTMLNames;
61 // Value chosen by observation. This can be tweaked.
62 static const int minColorContrastValue = 1300;
64 static Color disabledTextColor(const Color& textColor, const Color& backgroundColor)
66 // The explicit check for black is an optimization for the 99% case (black on white).
67 // This also means that black on black will turn into grey on black when disabled.
68 Color disabledColor;
69 if (textColor.rgb() == Color::black || differenceSquared(textColor, Color::white) > differenceSquared(backgroundColor, Color::white))
70 disabledColor = textColor.light();
71 else
72 disabledColor = textColor.dark();
74 // If there's not very much contrast between the disabled color and the background color,
75 // just leave the text color alone. We don't want to change a good contrast color scheme so that it has really bad contrast.
76 // If the the contrast was already poor, then it doesn't do any good to change it to a different poor contrast color scheme.
77 if (differenceSquared(disabledColor, backgroundColor) < minColorContrastValue)
78 return textColor;
80 return disabledColor;
83 RenderTextControl::RenderTextControl(Node* node, bool multiLine)
84 : RenderBlock(node)
85 , m_dirty(false)
86 , m_multiLine(multiLine)
87 , m_placeholderVisible(false)
88 , m_userEdited(false)
89 , m_shouldDrawCapsLockIndicator(false)
90 , m_searchPopup(0)
91 , m_searchPopupIsVisible(false)
92 , m_searchEventTimer(this, &RenderTextControl::searchEventTimerFired)
96 RenderTextControl::~RenderTextControl()
98 if (m_searchPopup) {
99 m_searchPopup->disconnectClient();
100 m_searchPopup = 0;
102 if (m_multiLine && node())
103 static_cast<HTMLTextAreaElement*>(node())->rendererWillBeDestroyed();
104 // The children renderers have already been destroyed by destroyLeftoverChildren
105 if (m_innerBlock)
106 m_innerBlock->detach();
107 else if (m_innerText)
108 m_innerText->detach();
111 void RenderTextControl::styleDidChange(RenderStyle::Diff diff, const RenderStyle* oldStyle)
113 RenderBlock::styleDidChange(diff, oldStyle);
114 if (m_innerBlock) {
115 // We may have set the width and the height in the old style in layout(). Reset them now to avoid
116 // getting a spurious layout hint.
117 m_innerBlock->renderer()->style()->setHeight(Length());
118 m_innerBlock->renderer()->style()->setWidth(Length());
119 m_innerBlock->renderer()->setStyle(createInnerBlockStyle(style()));
122 if (m_innerText) {
123 RenderBlock* textBlockRenderer = static_cast<RenderBlock*>(m_innerText->renderer());
124 RefPtr<RenderStyle> textBlockStyle = createInnerTextStyle(style());
125 // We may have set the width and the height in the old style in layout(). Reset them now to avoid
126 // getting a spurious layout hint.
127 textBlockRenderer->style()->setHeight(Length());
128 textBlockRenderer->style()->setWidth(Length());
129 textBlockRenderer->setStyle(textBlockStyle);
130 for (Node* n = m_innerText->firstChild(); n; n = n->traverseNextNode(m_innerText.get())) {
131 if (n->renderer())
132 n->renderer()->setStyle(textBlockStyle);
135 if (m_resultsButton && m_resultsButton->renderer())
136 m_resultsButton->renderer()->setStyle(createResultsButtonStyle(style()));
138 if (m_cancelButton && m_cancelButton->renderer())
139 m_cancelButton->renderer()->setStyle(createCancelButtonStyle(style()));
141 setHasOverflowClip(false);
142 setReplaced(isInline());
145 PassRefPtr<RenderStyle> RenderTextControl::createInnerBlockStyle(const RenderStyle* startStyle)
147 RefPtr<RenderStyle> innerBlockStyle = RenderStyle::create();
149 innerBlockStyle->inheritFrom(startStyle);
150 innerBlockStyle->setDisplay(BLOCK);
151 innerBlockStyle->setDirection(LTR);
152 // 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.
153 innerBlockStyle->setUserModify(READ_ONLY);
155 return innerBlockStyle.release();
158 PassRefPtr<RenderStyle> RenderTextControl::createInnerTextStyle(const RenderStyle* startStyle)
160 HTMLFormControlElement* element = static_cast<HTMLFormControlElement*>(node());
161 bool placeholderShouldBeVisible = !m_multiLine && static_cast<HTMLInputElement*>(element)->placeholderShouldBeVisible();
163 RefPtr<RenderStyle> textBlockStyle;
164 if (placeholderShouldBeVisible) {
165 RenderStyle* pseudoStyle = getCachedPseudoStyle(RenderStyle::INPUT_PLACEHOLDER);
166 textBlockStyle = RenderStyle::clone(pseudoStyle);
167 } else {
168 textBlockStyle = RenderStyle::create();
169 textBlockStyle->inheritFrom(startStyle);
172 // The inner block, if present, always has its direction set to LTR,
173 // so we need to inherit the direction from the element.
174 textBlockStyle->setDirection(style()->direction());
175 textBlockStyle->setUserModify(element->isReadOnlyControl() || element->disabled() ? READ_ONLY : READ_WRITE_PLAINTEXT_ONLY);
176 if (m_innerBlock)
177 textBlockStyle->setDisplay(INLINE_BLOCK);
178 else
179 textBlockStyle->setDisplay(BLOCK);
181 if (m_multiLine) {
182 // Forward overflow properties.
183 textBlockStyle->setOverflowX(startStyle->overflowX() == OVISIBLE ? OAUTO : startStyle->overflowX());
184 textBlockStyle->setOverflowY(startStyle->overflowY() == OVISIBLE ? OAUTO : startStyle->overflowY());
186 // Set word wrap property based on wrap attribute.
187 if (!static_cast<HTMLTextAreaElement*>(element)->shouldWrapText()) {
188 textBlockStyle->setWhiteSpace(PRE);
189 textBlockStyle->setWordWrap(NormalWordWrap);
190 } else {
191 textBlockStyle->setWhiteSpace(PRE_WRAP);
192 textBlockStyle->setWordWrap(BreakWordWrap);
194 } else {
195 textBlockStyle->setWhiteSpace(PRE);
196 textBlockStyle->setWordWrap(NormalWordWrap);
197 textBlockStyle->setOverflowX(OHIDDEN);
198 textBlockStyle->setOverflowY(OHIDDEN);
200 // Do not allow line-height to be smaller than our default.
201 if (textBlockStyle->font().lineSpacing() > lineHeight(true, true))
202 textBlockStyle->setLineHeight(Length(-100.0f, Percent));
205 if (!m_multiLine) {
206 // We're adding one extra pixel of padding to match WinIE.
207 textBlockStyle->setPaddingLeft(Length(1, Fixed));
208 textBlockStyle->setPaddingRight(Length(1, Fixed));
209 } else {
210 // We're adding three extra pixels of padding to line textareas up with text fields.
211 textBlockStyle->setPaddingLeft(Length(3, Fixed));
212 textBlockStyle->setPaddingRight(Length(3, Fixed));
215 // When the placeholder is going to be displayed, temporarily override the text security to be "none".
216 // After this, updateFromElement will immediately update the text displayed.
217 // When the placeholder is no longer visible, updatePlaceholderVisiblity will reset the style,
218 // and the text security mode will be set back to the computed value correctly.
219 if (!m_multiLine && static_cast<HTMLInputElement*>(element)->placeholderShouldBeVisible())
220 textBlockStyle->setTextSecurity(TSNONE);
222 if (!element->isEnabled())
223 textBlockStyle->setColor(disabledTextColor(textBlockStyle->color(), startStyle->backgroundColor()));
225 return textBlockStyle.release();
228 PassRefPtr<RenderStyle> RenderTextControl::createResultsButtonStyle(const RenderStyle* startStyle)
230 ASSERT(!m_multiLine);
231 HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
232 RefPtr<RenderStyle> resultsBlockStyle;
233 if (input->maxResults() < 0)
234 resultsBlockStyle = getCachedPseudoStyle(RenderStyle::SEARCH_DECORATION);
235 else if (!input->maxResults())
236 resultsBlockStyle = getCachedPseudoStyle(RenderStyle::SEARCH_RESULTS_DECORATION);
237 else
238 resultsBlockStyle = getCachedPseudoStyle(RenderStyle::SEARCH_RESULTS_BUTTON);
240 if (!resultsBlockStyle)
241 resultsBlockStyle = RenderStyle::create();
243 if (startStyle)
244 resultsBlockStyle->inheritFrom(startStyle);
246 return resultsBlockStyle.release();
249 PassRefPtr<RenderStyle> RenderTextControl::createCancelButtonStyle(const RenderStyle* startStyle)
251 RefPtr<RenderStyle> cancelBlockStyle;
253 if (RefPtr<RenderStyle> pseudoStyle = getCachedPseudoStyle(RenderStyle::SEARCH_CANCEL_BUTTON))
254 // We may be sharing style with another search field, but we must not share the cancel button style.
255 cancelBlockStyle = RenderStyle::clone(pseudoStyle.get());
256 else
257 cancelBlockStyle = RenderStyle::create();
259 if (startStyle)
260 cancelBlockStyle->inheritFrom(startStyle);
262 updateCancelButtonVisibility(cancelBlockStyle.get());
264 return cancelBlockStyle.release();
267 void RenderTextControl::createSubtreeIfNeeded()
269 bool isSearchField = !m_multiLine && static_cast<HTMLInputElement*>(node())->isSearchField();
270 if (isSearchField && !m_innerBlock) {
271 // Create the inner block element
272 m_innerBlock = new TextControlInnerElement(document(), node());
273 m_innerBlock->attachInnerElement(node(), createInnerBlockStyle(style()), renderArena());
275 if (isSearchField && !m_resultsButton) {
276 // Create the search results button element
277 m_resultsButton = new SearchFieldResultsButtonElement(document());
278 m_resultsButton->attachInnerElement(m_innerBlock.get(), createResultsButtonStyle(m_innerBlock->renderer()->style()), renderArena());
280 if (!m_innerText) {
281 // Create the text block element
282 // For non-search fields, there is no intermediate m_innerBlock as the shadow node.
283 // m_innerText will be the shadow node in that case.
285 RenderStyle* parentStyle = style();
286 if (m_innerBlock)
287 parentStyle = m_innerBlock->renderer()->style();
288 m_innerText = new TextControlInnerTextElement(document(), m_innerBlock ? 0 : node());
289 m_innerText->attachInnerElement(m_innerBlock ? m_innerBlock.get() : node(), createInnerTextStyle(parentStyle), renderArena());
291 if (isSearchField && !m_cancelButton) {
292 // Create the cancel button element
293 m_cancelButton = new SearchFieldCancelButtonElement(document());
294 m_cancelButton->attachInnerElement(m_innerBlock.get(), createCancelButtonStyle(m_innerBlock->renderer()->style()), renderArena());
298 void RenderTextControl::updateFromElement()
300 HTMLFormControlElement* element = static_cast<HTMLFormControlElement*>(node());
302 bool placeholderShouldBeVisible = !m_multiLine && static_cast<HTMLInputElement*>(element)->placeholderShouldBeVisible();
303 bool placeholderVisibilityShouldChange = m_placeholderVisible != placeholderShouldBeVisible;
304 m_placeholderVisible = placeholderShouldBeVisible;
306 createSubtreeIfNeeded();
308 if (m_cancelButton && m_cancelButton->renderer())
309 updateCancelButtonVisibility(m_cancelButton->renderer()->style());
311 m_innerText->renderer()->style()->setUserModify(element->isReadOnlyControl() || element->disabled() ? READ_ONLY : READ_WRITE_PLAINTEXT_ONLY);
313 if (m_placeholderVisible) {
314 ExceptionCode ec;
315 m_innerText->setInnerText(static_cast<HTMLInputElement*>(element)->getAttribute(placeholderAttr), ec);
316 } else if (!element->valueMatchesRenderer() || m_multiLine || placeholderVisibilityShouldChange) {
317 String value;
318 if (m_multiLine)
319 value = static_cast<HTMLTextAreaElement*>(element)->value();
320 else
321 value = static_cast<HTMLInputElement*>(element)->value();
322 if (value.isNull())
323 value = "";
324 else
325 value = value.replace('\\', backslashAsCurrencySymbol());
326 if (value != text() || !m_innerText->hasChildNodes()) {
327 if (value != text()) {
328 if (Frame* frame = document()->frame())
329 frame->editor()->clearUndoRedoOperations();
331 ExceptionCode ec = 0;
332 m_innerText->setInnerText(value, ec);
333 if (value.endsWith("\n") || value.endsWith("\r"))
334 m_innerText->appendChild(new HTMLBRElement(document()), ec);
335 m_dirty = false;
336 m_userEdited = false;
338 element->setValueMatchesRenderer();
341 if (m_searchPopupIsVisible)
342 m_searchPopup->updateFromElement();
345 void RenderTextControl::setUserEdited(bool isUserEdited)
347 m_userEdited = isUserEdited;
348 document()->setIgnoreAutofocus(isUserEdited);
351 int RenderTextControl::selectionStart()
353 Frame* frame = document()->frame();
354 if (!frame)
355 return 0;
356 return indexForVisiblePosition(frame->selection()->start());
359 int RenderTextControl::selectionEnd()
361 Frame* frame = document()->frame();
362 if (!frame)
363 return 0;
364 return indexForVisiblePosition(frame->selection()->end());
367 void RenderTextControl::setSelectionStart(int start)
369 setSelectionRange(start, max(start, selectionEnd()));
372 void RenderTextControl::setSelectionEnd(int end)
374 setSelectionRange(min(end, selectionStart()), end);
377 void RenderTextControl::select()
379 setSelectionRange(0, text().length());
382 void RenderTextControl::setSelectionRange(int start, int end)
384 end = max(end, 0);
385 start = min(max(start, 0), end);
387 document()->updateLayout();
389 if (style()->visibility() == HIDDEN || !m_innerText || !m_innerText->renderer() || !m_innerText->renderer()->height()) {
390 if (m_multiLine)
391 static_cast<HTMLTextAreaElement*>(node())->cacheSelection(start, end);
392 else
393 static_cast<HTMLInputElement*>(node())->cacheSelection(start, end);
394 return;
396 VisiblePosition startPosition = visiblePositionForIndex(start);
397 VisiblePosition endPosition;
398 if (start == end)
399 endPosition = startPosition;
400 else
401 endPosition = visiblePositionForIndex(end);
403 ASSERT(startPosition.isNotNull() && endPosition.isNotNull());
404 ASSERT(startPosition.deepEquivalent().node()->shadowAncestorNode() == node() && endPosition.deepEquivalent().node()->shadowAncestorNode() == node());
406 Selection newSelection = Selection(startPosition, endPosition);
408 if (Frame* frame = document()->frame())
409 frame->selection()->setSelection(newSelection);
411 // FIXME: Granularity is stored separately on the frame, but also in the selection controller.
412 // The granularity in the selection controller should be used, and then this line of code would not be needed.
413 if (Frame* frame = document()->frame())
414 frame->setSelectionGranularity(CharacterGranularity);
417 Selection RenderTextControl::selection(int start, int end) const
419 return Selection(VisiblePosition(m_innerText.get(), start, VP_DEFAULT_AFFINITY),
420 VisiblePosition(m_innerText.get(), end, VP_DEFAULT_AFFINITY));
423 VisiblePosition RenderTextControl::visiblePositionForIndex(int index)
425 if (index <= 0)
426 return VisiblePosition(m_innerText.get(), 0, DOWNSTREAM);
427 ExceptionCode ec = 0;
428 RefPtr<Range> range = Range::create(document());
429 range->selectNodeContents(m_innerText.get(), ec);
430 CharacterIterator it(range.get());
431 it.advance(index - 1);
432 return VisiblePosition(it.range()->endContainer(ec), it.range()->endOffset(ec), UPSTREAM);
435 int RenderTextControl::indexForVisiblePosition(const VisiblePosition& pos)
437 Position indexPosition = pos.deepEquivalent();
438 if (!indexPosition.node() || indexPosition.node()->rootEditableElement() != m_innerText)
439 return 0;
440 ExceptionCode ec = 0;
441 RefPtr<Range> range = Range::create(document());
442 range->setStart(m_innerText.get(), 0, ec);
443 range->setEnd(indexPosition.node(), indexPosition.offset(), ec);
444 return TextIterator::rangeLength(range.get());
447 void RenderTextControl::updateCancelButtonVisibility(RenderStyle* style)
449 ASSERT(!m_multiLine);
450 HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
451 if (input->value().isEmpty())
452 style->setVisibility(HIDDEN);
453 else
454 style->setVisibility(VISIBLE);
457 void RenderTextControl::subtreeHasChanged()
459 bool wasDirty = m_dirty;
460 m_dirty = true;
461 m_userEdited = true;
462 HTMLFormControlElement* element = static_cast<HTMLFormControlElement*>(node());
463 if (m_multiLine) {
464 element->setValueMatchesRenderer(false);
465 if (element->focused())
466 if (Frame* frame = document()->frame())
467 frame->textDidChangeInTextArea(element);
468 } else {
469 HTMLInputElement* input = static_cast<HTMLInputElement*>(element);
470 input->setValueFromRenderer(input->constrainValue(text()));
471 if (m_cancelButton && m_cancelButton->renderer())
472 updateCancelButtonVisibility(m_cancelButton->renderer()->style());
474 // If the incremental attribute is set, then dispatch the search event
475 if (!input->getAttribute(incrementalAttr).isNull())
476 startSearchEventTimer();
478 if (!wasDirty) {
479 if (input->focused())
480 if (Frame* frame = document()->frame())
481 frame->textFieldDidBeginEditing(input);
483 if (input->focused())
484 if (Frame* frame = document()->frame())
485 frame->textDidChangeInTextField(input);
489 String RenderTextControl::finishText(Vector<UChar>& result) const
491 // Remove one trailing newline; there's always one that's collapsed out by rendering.
492 size_t size = result.size();
493 if (size && result[size - 1] == '\n')
494 result.shrink(--size);
496 // Convert backslash to currency symbol.
497 UChar symbol = backslashAsCurrencySymbol();
498 if (symbol != '\\') {
499 for (size_t i = 0; i < size; ++i) {
500 if (result[i] == '\\')
501 result[i] = symbol;
505 return String::adopt(result);
508 HTMLElement* RenderTextControl::innerTextElement() const
510 return m_innerText.get();
513 String RenderTextControl::text()
515 if (!m_innerText)
516 return "";
518 Frame* frame = document()->frame();
519 Text* compositionNode = frame ? frame->editor()->compositionNode() : 0;
521 Vector<UChar> result;
523 for (Node* n = m_innerText.get(); n; n = n->traverseNextNode(m_innerText.get())) {
524 if (n->isTextNode()) {
525 Text* text = static_cast<Text*>(n);
526 String data = text->data();
527 unsigned length = data.length();
528 if (text != compositionNode)
529 result.append(data.characters(), length);
530 else {
531 unsigned compositionStart = min(frame->editor()->compositionStart(), length);
532 unsigned compositionEnd = min(max(compositionStart, frame->editor()->compositionEnd()), length);
533 result.append(data.characters(), compositionStart);
534 result.append(data.characters() + compositionEnd, length - compositionEnd);
539 return finishText(result);
542 static void getNextSoftBreak(RootInlineBox*& line, Node*& breakNode, unsigned& breakOffset)
544 RootInlineBox* next;
545 for (; line; line = next) {
546 next = line->nextRootBox();
547 if (next && !line->endsWithBreak()) {
548 ASSERT(line->lineBreakObj());
549 breakNode = line->lineBreakObj()->node();
550 breakOffset = line->lineBreakPos();
551 line = next;
552 return;
555 breakNode = 0;
558 String RenderTextControl::textWithHardLineBreaks()
560 if (!m_innerText)
561 return "";
562 Node* firstChild = m_innerText->firstChild();
563 if (!firstChild)
564 return "";
566 document()->updateLayout();
568 RenderObject* renderer = firstChild->renderer();
569 if (!renderer)
570 return "";
572 InlineBox* box = renderer->isText() ? static_cast<RenderText*>(renderer)->firstTextBox() : renderer->inlineBoxWrapper();
573 if (!box)
574 return "";
576 Frame* frame = document()->frame();
577 Text* compositionNode = frame ? frame->editor()->compositionNode() : 0;
579 Node* breakNode;
580 unsigned breakOffset;
581 RootInlineBox* line = box->root();
582 getNextSoftBreak(line, breakNode, breakOffset);
584 Vector<UChar> result;
586 for (Node* n = firstChild; n; n = n->traverseNextNode(m_innerText.get())) {
587 if (n->hasTagName(brTag))
588 result.append(&newlineCharacter, 1);
589 else if (n->isTextNode()) {
590 Text* text = static_cast<Text*>(n);
591 String data = text->data();
592 unsigned length = data.length();
593 unsigned compositionStart = (text == compositionNode)
594 ? min(frame->editor()->compositionStart(), length) : 0;
595 unsigned compositionEnd = (text == compositionNode)
596 ? min(max(compositionStart, frame->editor()->compositionEnd()), length) : 0;
597 unsigned position = 0;
598 while (breakNode == n && breakOffset < compositionStart) {
599 result.append(data.characters() + position, breakOffset - position);
600 position = breakOffset;
601 result.append(&newlineCharacter, 1);
602 getNextSoftBreak(line, breakNode, breakOffset);
604 result.append(data.characters() + position, compositionStart - position);
605 position = compositionEnd;
606 while (breakNode == n && breakOffset <= length) {
607 if (breakOffset > position) {
608 result.append(data.characters() + position, breakOffset - position);
609 position = breakOffset;
610 result.append(&newlineCharacter, 1);
612 getNextSoftBreak(line, breakNode, breakOffset);
614 result.append(data.characters() + position, length - position);
616 while (breakNode == n)
617 getNextSoftBreak(line, breakNode, breakOffset);
620 return finishText(result);
623 void RenderTextControl::calcHeight()
625 int rows = 1;
626 if (m_multiLine)
627 rows = static_cast<HTMLTextAreaElement*>(node())->rows();
629 int line = m_innerText->renderer()->lineHeight(true, true);
630 int toAdd = paddingTop() + paddingBottom() + borderTop() + borderBottom();
632 int innerToAdd = m_innerText->renderer()->borderTop() + m_innerText->renderer()->borderBottom() +
633 m_innerText->renderer()->paddingTop() + m_innerText->renderer()->paddingBottom() +
634 m_innerText->renderer()->marginTop() + m_innerText->renderer()->marginBottom();
636 if (m_resultsButton && m_resultsButton->renderer()) {
637 static_cast<RenderBlock*>(m_resultsButton->renderer())->calcHeight();
638 innerToAdd = max(innerToAdd,
639 m_resultsButton->renderer()->borderTop() + m_resultsButton->renderer()->borderBottom() +
640 m_resultsButton->renderer()->paddingTop() + m_resultsButton->renderer()->paddingBottom() +
641 m_resultsButton->renderer()->marginTop() + m_resultsButton->renderer()->marginBottom());
642 line = max(line, m_resultsButton->renderer()->height());
644 if (m_cancelButton && m_cancelButton->renderer()) {
645 static_cast<RenderBlock*>(m_cancelButton->renderer())->calcHeight();
646 innerToAdd = max(innerToAdd,
647 m_cancelButton->renderer()->borderTop() + m_cancelButton->renderer()->borderBottom() +
648 m_cancelButton->renderer()->paddingTop() + m_cancelButton->renderer()->paddingBottom() +
649 m_cancelButton->renderer()->marginTop() + m_cancelButton->renderer()->marginBottom());
650 line = max(line, m_cancelButton->renderer()->height());
652 toAdd += innerToAdd;
654 // FIXME: We should get the size of the scrollbar from the RenderTheme instead.
655 int scrollbarSize = 0;
656 // We are able to have a horizontal scrollbar if the overflow style is scroll, or if its auto and there's no word wrap.
657 if (m_innerText->renderer()->style()->overflowX() == OSCROLL || (m_innerText->renderer()->style()->overflowX() == OAUTO && m_innerText->renderer()->style()->wordWrap() == NormalWordWrap))
658 scrollbarSize = ScrollbarTheme::nativeTheme()->scrollbarThickness();
660 m_height = line * rows + toAdd + scrollbarSize;
662 RenderBlock::calcHeight();
665 int RenderTextControl::baselinePosition(bool b, bool isRootLineBox) const
667 if (m_multiLine)
668 return height() + marginTop() + marginBottom();
669 return RenderBlock::baselinePosition(b, isRootLineBox);
672 bool RenderTextControl::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty, HitTestAction hitTestAction)
674 // 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
675 // was on the control but not on the inner element (see Radar 4617841).
677 // 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,
678 // and act as if we've hit the close block if we're to the right of the inner text block.
680 if (RenderBlock::nodeAtPoint(request, result, x, y, tx, ty, hitTestAction) &&
681 (result.innerNode() == element() || result.innerNode() == m_innerBlock)) {
682 IntPoint localPoint = IntPoint(x - tx - m_x, y - ty - m_y);
683 if (m_innerBlock) {
684 int textLeft = tx + m_x + m_innerBlock->renderer()->xPos() + m_innerText->renderer()->xPos();
685 int textRight = textLeft + m_innerText->renderer()->width();
686 if (m_resultsButton && m_resultsButton->renderer() && x < textLeft) {
687 result.setInnerNode(m_resultsButton.get());
688 result.setLocalPoint(IntPoint(localPoint.x() - m_innerText->renderer()->xPos() - m_innerBlock->renderer()->xPos() - m_resultsButton->renderer()->xPos(),
689 localPoint.y() - m_innerText->renderer()->yPos() - m_innerBlock->renderer()->yPos() - m_resultsButton->renderer()->yPos()));
690 return true;
692 if (m_cancelButton && m_cancelButton->renderer() && x > textRight) {
693 result.setInnerNode(m_cancelButton.get());
694 result.setLocalPoint(IntPoint(localPoint.x() - m_innerText->renderer()->xPos() - m_innerBlock->renderer()->xPos() - m_cancelButton->renderer()->xPos(),
695 localPoint.y() - m_innerText->renderer()->yPos() - m_innerBlock->renderer()->yPos() - m_cancelButton->renderer()->yPos()));
696 return true;
700 // Hit the inner text block.
701 result.setInnerNode(m_innerText.get());
702 result.setLocalPoint(IntPoint(localPoint.x() - m_innerText->renderer()->xPos() - (m_innerBlock.get() ? m_innerBlock->renderer()->xPos() : 0),
703 localPoint.y() - m_innerText->renderer()->yPos() - (m_innerBlock.get() ? m_innerBlock->renderer()->yPos() : 0)));
705 return true;
708 return false;
711 IntRect RenderTextControl::controlClipRect(int tx, int ty) const
713 IntRect clipRect = contentBox();
714 clipRect.move(tx, ty);
715 return clipRect;
718 void RenderTextControl::layout()
720 int oldHeight = m_height;
721 calcHeight();
722 bool relayoutChildren = oldHeight != m_height;
724 // Set the text block's height
725 int textBlockHeight = m_height - paddingTop() - paddingBottom() - borderTop() - borderBottom();
726 int currentTextBlockHeight = m_innerText->renderer()->height();
727 if (m_multiLine || m_innerBlock || currentTextBlockHeight > m_height) {
728 if (textBlockHeight != currentTextBlockHeight)
729 relayoutChildren = true;
730 m_innerText->renderer()->style()->setHeight(Length(textBlockHeight, Fixed));
732 if (m_innerBlock) {
733 if (textBlockHeight != m_innerBlock->renderer()->height())
734 relayoutChildren = true;
735 m_innerBlock->renderer()->style()->setHeight(Length(textBlockHeight, Fixed));
738 int oldWidth = m_width;
739 calcWidth();
740 if (oldWidth != m_width)
741 relayoutChildren = true;
743 int searchExtrasWidth = 0;
744 if (m_resultsButton && m_resultsButton->renderer()) {
745 m_resultsButton->renderer()->calcWidth();
746 searchExtrasWidth += m_resultsButton->renderer()->width();
748 if (m_cancelButton && m_cancelButton->renderer()) {
749 m_cancelButton->renderer()->calcWidth();
750 searchExtrasWidth += m_cancelButton->renderer()->width();
753 // Set the text block's width
754 int textBlockWidth = m_width - paddingLeft() - paddingRight() - borderLeft() - borderRight() -
755 m_innerText->renderer()->paddingLeft() - m_innerText->renderer()->paddingRight() - searchExtrasWidth;
756 if (textBlockWidth != m_innerText->renderer()->width())
757 relayoutChildren = true;
758 m_innerText->renderer()->style()->setWidth(Length(textBlockWidth, Fixed));
759 if (m_innerBlock) {
760 int innerBlockWidth = m_width - paddingLeft() - paddingRight() - borderLeft() - borderRight();
761 if (innerBlockWidth != m_innerBlock->renderer()->width())
762 relayoutChildren = true;
763 m_innerBlock->renderer()->style()->setWidth(Length(innerBlockWidth, Fixed));
766 RenderBlock::layoutBlock(relayoutChildren);
768 // For text fields, center the inner text vertically
769 // Don't do this for search fields, since we don't honor height for them
770 if (!m_multiLine) {
771 currentTextBlockHeight = m_innerText->renderer()->height();
772 if (!m_innerBlock && currentTextBlockHeight < m_height)
773 m_innerText->renderer()->setPos(m_innerText->renderer()->xPos(), (m_height - currentTextBlockHeight) / 2);
777 void RenderTextControl::paint(PaintInfo& paintInfo, int tx, int ty)
779 RenderBlock::paint(paintInfo, tx, ty);
780 if (paintInfo.phase == PaintPhaseBlockBackground && m_shouldDrawCapsLockIndicator)
781 theme()->paintCapsLockIndicator(this, paintInfo, absoluteContentBox());
784 void RenderTextControl::calcPrefWidths()
786 ASSERT(prefWidthsDirty());
788 m_minPrefWidth = 0;
789 m_maxPrefWidth = 0;
791 if (style()->width().isFixed() && style()->width().value() > 0)
792 m_minPrefWidth = m_maxPrefWidth = calcContentBoxWidth(style()->width().value());
793 else {
794 // Figure out how big a text control needs to be for a given number of characters
795 // (using "0" as the nominal character).
796 const UChar ch = '0';
797 float charWidth = style()->font().floatWidth(TextRun(&ch, 1, false, 0, 0, false, false, false));
798 int factor;
799 int scrollbarSize = 0;
800 if (m_multiLine) {
801 factor = static_cast<HTMLTextAreaElement*>(node())->cols();
802 // FIXME: We should get the size of the scrollbar from the RenderTheme instead.
803 if (m_innerText->renderer()->style()->overflowY() != OHIDDEN)
804 scrollbarSize = ScrollbarTheme::nativeTheme()->scrollbarThickness();
805 } else {
806 factor = static_cast<HTMLInputElement*>(node())->size();
807 if (factor <= 0)
808 factor = 20;
810 m_maxPrefWidth = static_cast<int>(ceilf(charWidth * factor)) + scrollbarSize +
811 m_innerText->renderer()->paddingLeft() + m_innerText->renderer()->paddingRight();
813 if (m_resultsButton && m_resultsButton->renderer())
814 m_maxPrefWidth += m_resultsButton->renderer()->borderLeft() + m_resultsButton->renderer()->borderRight() +
815 m_resultsButton->renderer()->paddingLeft() + m_resultsButton->renderer()->paddingRight();
816 if (m_cancelButton && m_cancelButton->renderer())
817 m_maxPrefWidth += m_cancelButton->renderer()->borderLeft() + m_cancelButton->renderer()->borderRight() +
818 m_cancelButton->renderer()->paddingLeft() + m_cancelButton->renderer()->paddingRight();
821 if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
822 m_maxPrefWidth = max(m_maxPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
823 m_minPrefWidth = max(m_minPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
824 } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
825 m_minPrefWidth = 0;
826 else
827 m_minPrefWidth = m_maxPrefWidth;
829 if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
830 m_maxPrefWidth = min(m_maxPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
831 m_minPrefWidth = min(m_minPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
834 int toAdd = paddingLeft() + paddingRight() + borderLeft() + borderRight();
836 m_minPrefWidth += toAdd;
837 m_maxPrefWidth += toAdd;
839 setPrefWidthsDirty(false);
842 void RenderTextControl::forwardEvent(Event* evt)
844 if (evt->type() == eventNames().blurEvent) {
845 RenderObject* innerRenderer = m_innerText->renderer();
846 if (innerRenderer) {
847 RenderLayer* innerLayer = innerRenderer->layer();
848 if (innerLayer && !m_multiLine)
849 innerLayer->scrollToOffset(style()->direction() == RTL ? innerLayer->scrollWidth() : 0, 0);
851 capsLockStateMayHaveChanged();
852 } else if (evt->type() == eventNames().focusEvent)
853 capsLockStateMayHaveChanged();
854 else {
855 if (evt->isMouseEvent() && m_resultsButton && static_cast<MouseEvent*>(evt)->x() < m_innerText->renderer()->absoluteBoundingBoxRect().x())
856 m_resultsButton->defaultEventHandler(evt);
857 else if (evt->isMouseEvent() && m_cancelButton && static_cast<MouseEvent*>(evt)->x() > m_innerText->renderer()->absoluteBoundingBoxRect().right())
858 m_cancelButton->defaultEventHandler(evt);
859 else
860 m_innerText->defaultEventHandler(evt);
864 void RenderTextControl::selectionChanged(bool userTriggered)
866 HTMLFormControlElement* element = static_cast<HTMLFormControlElement*>(node());
867 if (m_multiLine)
868 static_cast<HTMLTextAreaElement*>(element)->cacheSelection(selectionStart(), selectionEnd());
869 else
870 static_cast<HTMLInputElement*>(element)->cacheSelection(selectionStart(), selectionEnd());
871 if (Frame* frame = document()->frame())
872 if (frame->selection()->isRange() && userTriggered)
873 element->dispatchEventForType(eventNames().selectEvent, true, false);
876 void RenderTextControl::autoscroll()
878 RenderLayer* layer = m_innerText->renderer()->layer();
879 if (layer)
880 layer->autoscroll();
883 int RenderTextControl::scrollWidth() const
885 if (m_innerText)
886 return m_innerText->scrollWidth();
887 return RenderBlock::scrollWidth();
890 int RenderTextControl::scrollHeight() const
892 if (m_innerText)
893 return m_innerText->scrollHeight();
894 return RenderBlock::scrollHeight();
897 int RenderTextControl::scrollLeft() const
899 if (m_innerText)
900 return m_innerText->scrollLeft();
901 return RenderBlock::scrollLeft();
904 int RenderTextControl::scrollTop() const
906 if (m_innerText)
907 return m_innerText->scrollTop();
908 return RenderBlock::scrollTop();
911 void RenderTextControl::setScrollLeft(int newLeft)
913 if (m_innerText)
914 m_innerText->setScrollLeft(newLeft);
917 void RenderTextControl::setScrollTop(int newTop)
919 if (m_innerText)
920 m_innerText->setScrollTop(newTop);
923 const AtomicString& RenderTextControl::autosaveName() const
925 return static_cast<Element*>(node())->getAttribute(autosaveAttr);
928 void RenderTextControl::addSearchResult()
930 ASSERT(!m_multiLine);
932 HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
933 if (input->maxResults() <= 0)
934 return;
936 String value = input->value();
937 if (value.isEmpty())
938 return;
940 Settings* settings = document()->settings();
941 if (!settings || settings->privateBrowsingEnabled())
942 return;
944 int size = static_cast<int>(m_recentSearches.size());
945 for (int i = size - 1; i >= 0; --i)
946 if (m_recentSearches[i] == value)
947 m_recentSearches.remove(i);
949 m_recentSearches.insert(0, value);
950 while (static_cast<int>(m_recentSearches.size()) > input->maxResults())
951 m_recentSearches.removeLast();
953 const AtomicString& name = autosaveName();
954 if (!m_searchPopup)
955 m_searchPopup = SearchPopupMenu::create(this);
956 m_searchPopup->saveRecentSearches(name, m_recentSearches);
959 void RenderTextControl::showPopup()
961 if (m_searchPopupIsVisible)
962 return;
964 if (!m_searchPopup)
965 m_searchPopup = SearchPopupMenu::create(this);
967 if (!m_searchPopup->enabled())
968 return;
970 m_searchPopupIsVisible = true;
972 const AtomicString& name = autosaveName();
973 m_searchPopup->loadRecentSearches(name, m_recentSearches);
975 // Trim the recent searches list if the maximum size has changed since we last saved.
976 HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
977 if (static_cast<int>(m_recentSearches.size()) > input->maxResults()) {
979 m_recentSearches.removeLast();
980 while (static_cast<int>(m_recentSearches.size()) > input->maxResults());
981 m_searchPopup->saveRecentSearches(name, m_recentSearches);
984 m_searchPopup->show(absoluteBoundingBoxRect(), document()->view(), -1);
987 void RenderTextControl::hidePopup()
989 if (m_searchPopup)
990 m_searchPopup->hide();
991 m_searchPopupIsVisible = false;
994 void RenderTextControl::valueChanged(unsigned listIndex, bool fireEvents)
996 ASSERT(static_cast<int>(listIndex) < listSize());
997 HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
998 if (static_cast<int>(listIndex) == (listSize() - 1)) {
999 if (fireEvents) {
1000 m_recentSearches.clear();
1001 const AtomicString& name = autosaveName();
1002 if (!name.isEmpty()) {
1003 if (!m_searchPopup)
1004 m_searchPopup = SearchPopupMenu::create(this);
1005 m_searchPopup->saveRecentSearches(name, m_recentSearches);
1008 } else {
1009 input->setValue(itemText(listIndex));
1010 if (fireEvents)
1011 input->onSearch();
1012 input->select();
1016 String RenderTextControl::itemText(unsigned listIndex) const
1018 int size = listSize();
1019 if (size == 1) {
1020 ASSERT(!listIndex);
1021 return searchMenuNoRecentSearchesText();
1023 if (!listIndex)
1024 return searchMenuRecentSearchesText();
1025 if (itemIsSeparator(listIndex))
1026 return String();
1027 if (static_cast<int>(listIndex) == (size - 1))
1028 return searchMenuClearRecentSearchesText();
1029 return m_recentSearches[listIndex - 1];
1032 bool RenderTextControl::itemIsEnabled(unsigned listIndex) const
1034 if (!listIndex || itemIsSeparator(listIndex))
1035 return false;
1036 return true;
1039 PopupMenuStyle RenderTextControl::itemStyle(unsigned listIndex) const
1041 return menuStyle();
1044 PopupMenuStyle RenderTextControl::menuStyle() const
1046 return PopupMenuStyle(style()->color(), style()->backgroundColor(), style()->font(), style()->visibility() == VISIBLE);
1049 HostWindow* RenderTextControl::hostWindow() const
1051 return document()->view()->hostWindow();
1054 PassRefPtr<Scrollbar> RenderTextControl::createScrollbar(ScrollbarClient* client, ScrollbarOrientation orientation, ScrollbarControlSize controlSize)
1056 RefPtr<Scrollbar> widget;
1057 bool hasCustomScrollbarStyle = style()->hasPseudoStyle(RenderStyle::SCROLLBAR);
1058 if (hasCustomScrollbarStyle)
1059 widget = RenderScrollbar::createCustomScrollbar(client, orientation, this);
1060 else
1061 widget = Scrollbar::createNativeScrollbar(client, orientation, controlSize);
1062 return widget.release();
1065 int RenderTextControl::clientInsetLeft() const
1067 // Inset the menu by the radius of the cap on the left so that
1068 // it only runs along the straight part of the bezel.
1069 return height() / 2;
1072 int RenderTextControl::clientInsetRight() const
1074 // Inset the menu by the radius of the cap on the right so that
1075 // it only runs along the straight part of the bezel (unless it needs
1076 // to be wider).
1077 return height() / 2;
1080 int RenderTextControl::clientPaddingLeft() const
1082 int padding = paddingLeft();
1083 if (m_resultsButton->renderer())
1084 padding += m_resultsButton->renderer()->width();
1085 return padding;
1088 int RenderTextControl::clientPaddingRight() const
1090 int padding = paddingRight();
1091 if (m_cancelButton->renderer())
1092 padding += m_cancelButton->renderer()->width();
1093 return padding;
1096 int RenderTextControl::listSize() const
1098 // If there are no recent searches, then our menu will have 1 "No recent searches" item.
1099 if (!m_recentSearches.size())
1100 return 1;
1101 // Otherwise, leave room in the menu for a header, a separator, and the "Clear recent searches" item.
1102 return m_recentSearches.size() + 3;
1105 int RenderTextControl::selectedIndex() const
1107 return -1;
1110 bool RenderTextControl::itemIsSeparator(unsigned listIndex) const
1112 // The separator will be the second to last item in our list.
1113 return static_cast<int>(listIndex) == (listSize() - 2);
1116 bool RenderTextControl::itemIsLabel(unsigned listIndex) const
1118 return listIndex == 0;
1121 bool RenderTextControl::itemIsSelected(unsigned listIndex) const
1123 return false;
1126 void RenderTextControl::setTextFromItem(unsigned listIndex)
1128 static_cast<HTMLInputElement*>(node())->setValue(itemText(listIndex));
1131 bool RenderTextControl::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier)
1133 RenderLayer* layer = m_innerText->renderer()->layer();
1134 if (layer && layer->scroll(direction, granularity, multiplier))
1135 return true;
1136 return RenderObject::scroll(direction, granularity, multiplier);
1139 void RenderTextControl::searchEventTimerFired(Timer<RenderTextControl>*)
1141 static_cast<HTMLInputElement*>(node())->onSearch();
1144 void RenderTextControl::stopSearchEventTimer()
1146 m_searchEventTimer.stop();
1149 void RenderTextControl::startSearchEventTimer()
1151 unsigned length = text().length();
1153 // If there's no text, fire the event right away.
1154 if (!length) {
1155 m_searchEventTimer.stop();
1156 static_cast<HTMLInputElement*>(node())->onSearch();
1157 return;
1160 // After typing the first key, we wait 0.5 seconds.
1161 // After the second key, 0.4 seconds, then 0.3, then 0.2 from then on.
1162 m_searchEventTimer.startOneShot(max(0.2, 0.6 - 0.1 * length));
1165 bool RenderTextControl::isScrollable() const
1167 if (m_innerText && m_innerText->renderer()->isScrollable())
1168 return true;
1169 return RenderObject::isScrollable();
1172 FontSelector* RenderTextControl::fontSelector() const
1174 return document()->styleSelector()->fontSelector();
1177 void RenderTextControl::updatePlaceholderVisibility()
1179 RenderStyle* parentStyle = m_innerBlock ? m_innerBlock->renderer()->style() : style();
1180 RefPtr<RenderStyle> textBlockStyle = createInnerTextStyle(parentStyle);
1181 m_innerText->renderer()->setStyle(textBlockStyle);
1182 for (Node* n = m_innerText->firstChild(); n; n = n->traverseNextNode(m_innerText.get())) {
1183 if (n->renderer())
1184 n->renderer()->setStyle(textBlockStyle);
1186 updateFromElement();
1189 void RenderTextControl::capsLockStateMayHaveChanged()
1191 // Only draw the caps lock indicator if these things are true:
1192 // 1) The field is a password field
1193 // 2) The frame is active
1194 // 3) The element is focused
1195 // 4) The caps lock is on
1197 bool shouldDrawCapsLockIndicator = false;
1198 if (Node* n = node())
1199 if (Document* d = document())
1200 if (Frame* f = d->frame())
1201 shouldDrawCapsLockIndicator = !m_multiLine && static_cast<HTMLInputElement*>(n)->inputType() == HTMLInputElement::PASSWORD &&
1202 f->selection()->isFocusedAndActive() && d->focusedNode() == n && PlatformKeyboardEvent::currentCapsLockState();
1204 if (shouldDrawCapsLockIndicator != m_shouldDrawCapsLockIndicator) {
1205 m_shouldDrawCapsLockIndicator = shouldDrawCapsLockIndicator;
1206 repaint();
1210 } // namespace WebCore