WebCore/rendering/TextControlInnerElements.cpp
changeset 0 4f2f89ce4247
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebCore/rendering/TextControlInnerElements.cpp	Fri Sep 17 09:02:29 2010 +0300
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2006, 2008, 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+ 
+#include "config.h"
+#include "TextControlInnerElements.h"
+
+#include "BeforeTextInsertedEvent.h"
+#include "Document.h"
+#include "EventHandler.h"
+#include "EventNames.h"
+#include "Frame.h"
+#include "HTMLInputElement.h"
+#include "HTMLNames.h"
+#include "HTMLTextAreaElement.h"
+#include "MouseEvent.h"
+#include "Page.h"
+#include "RenderLayer.h"
+#include "RenderTextControlSingleLine.h"
+#include "SpeechInput.h"
+
+namespace WebCore {
+
+using namespace HTMLNames;
+
+class RenderTextControlInnerBlock : public RenderBlock {
+public:
+    RenderTextControlInnerBlock(Node* node, bool isMultiLine) : RenderBlock(node), m_multiLine(isMultiLine) { }
+
+private:
+    virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction);
+    virtual VisiblePosition positionForPoint(const IntPoint&);
+
+    bool m_multiLine;
+};
+
+bool RenderTextControlInnerBlock::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty, HitTestAction hitTestAction)
+{
+    RenderObject* renderer = node()->shadowAncestorNode()->renderer();
+
+    bool placeholderIsVisible = false;
+    if (renderer->isTextField())
+        placeholderIsVisible = toRenderTextControlSingleLine(renderer)->placeholderIsVisible();
+
+    return RenderBlock::nodeAtPoint(request, result, x, y, tx, ty, placeholderIsVisible ? HitTestBlockBackground : hitTestAction);
+}
+
+VisiblePosition RenderTextControlInnerBlock::positionForPoint(const IntPoint& point)
+{
+    IntPoint contentsPoint(point);
+
+    // Multiline text controls have the scroll on shadowAncestorNode, so we need to take that
+    // into account here.
+    if (m_multiLine) {
+        RenderTextControl* renderer = toRenderTextControl(node()->shadowAncestorNode()->renderer());
+        if (renderer->hasOverflowClip())
+            contentsPoint += renderer->layer()->scrolledContentOffset();
+    }
+
+    return RenderBlock::positionForPoint(contentsPoint);
+}
+
+// ----------------------------
+
+TextControlInnerElement::TextControlInnerElement(Document* document, Node* shadowParent)
+    : HTMLDivElement(divTag, document)
+    , m_shadowParent(shadowParent)
+{
+}
+
+PassRefPtr<TextControlInnerElement> TextControlInnerElement::create(Node* shadowParent)
+{
+    return adoptRef(new TextControlInnerElement(shadowParent->document(), shadowParent));
+}
+
+void TextControlInnerElement::attachInnerElement(Node* parent, PassRefPtr<RenderStyle> style, RenderArena* arena)
+{
+    // 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.
+
+    // Create the renderer with the specified style
+    RenderObject* renderer = createRenderer(arena, style.get());
+    if (renderer) {
+        setRenderer(renderer);
+        renderer->setStyle(style);
+    }
+    
+    // Set these explicitly since this normally happens during an attach()
+    setAttached();
+    setInDocument();
+    
+    // For elements without a shadow parent, add the node to the DOM normally.
+    if (!m_shadowParent)
+        parent->legacyParserAddChild(this);
+    
+    // Add the renderer to the render tree
+    if (renderer)
+        parent->renderer()->addChild(renderer);
+}
+
+// ----------------------------
+
+inline TextControlInnerTextElement::TextControlInnerTextElement(Document* document, Node* shadowParent)
+    : TextControlInnerElement(document, shadowParent)
+{
+}
+
+PassRefPtr<TextControlInnerTextElement> TextControlInnerTextElement::create(Document* document, Node* shadowParent)
+{
+    return adoptRef(new TextControlInnerTextElement(document, shadowParent));
+}
+
+void TextControlInnerTextElement::defaultEventHandler(Event* event)
+{
+    // FIXME: In the future, we should add a way to have default event listeners.
+    // Then we would add one to the text field's inner div, and we wouldn't need this subclass.
+    // Or possibly we could just use a normal event listener.
+    if (event->isBeforeTextInsertedEvent() || event->type() == eventNames().webkitEditableContentChangedEvent) {
+        if (Node* shadowAncestor = shadowAncestorNode())
+            shadowAncestor->defaultEventHandler(event);
+    }
+    if (event->defaultHandled())
+        HTMLDivElement::defaultEventHandler(event);
+}
+
+RenderObject* TextControlInnerTextElement::createRenderer(RenderArena* arena, RenderStyle*)
+{
+    bool multiLine = false;
+    Node* shadowAncestor = shadowAncestorNode();
+    if (shadowAncestor && shadowAncestor->renderer()) {
+        ASSERT(shadowAncestor->renderer()->isTextField() || shadowAncestor->renderer()->isTextArea());
+        multiLine = shadowAncestor->renderer()->isTextArea();
+    }
+    return new (arena) RenderTextControlInnerBlock(this, multiLine);
+}
+
+// ----------------------------
+
+inline SearchFieldResultsButtonElement::SearchFieldResultsButtonElement(Document* document)
+    : TextControlInnerElement(document)
+{
+}
+
+PassRefPtr<SearchFieldResultsButtonElement> SearchFieldResultsButtonElement::create(Document* document)
+{
+    return adoptRef(new SearchFieldResultsButtonElement(document));
+}
+
+void SearchFieldResultsButtonElement::defaultEventHandler(Event* event)
+{
+    // On mousedown, bring up a menu, if needed
+    HTMLInputElement* input = static_cast<HTMLInputElement*>(shadowAncestorNode());
+    if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) {
+        input->focus();
+        input->select();
+        RenderTextControlSingleLine* renderer = toRenderTextControlSingleLine(input->renderer());
+        if (renderer->popupIsVisible())
+            renderer->hidePopup();
+        else if (input->maxResults() > 0)
+            renderer->showPopup();
+        event->setDefaultHandled();
+    }
+
+    if (!event->defaultHandled())
+        HTMLDivElement::defaultEventHandler(event);
+}
+
+// ----------------------------
+
+inline SearchFieldCancelButtonElement::SearchFieldCancelButtonElement(Document* document)
+    : TextControlInnerElement(document)
+    , m_capturing(false)
+{
+}
+
+PassRefPtr<SearchFieldCancelButtonElement> SearchFieldCancelButtonElement::create(Document* document)
+{
+    return adoptRef(new SearchFieldCancelButtonElement(document));
+}
+
+void SearchFieldCancelButtonElement::detach()
+{
+    if (m_capturing) {
+        if (Frame* frame = document()->frame())
+            frame->eventHandler()->setCapturingMouseEventsNode(0);      
+    }
+    TextControlInnerElement::detach();
+}
+
+
+void SearchFieldCancelButtonElement::defaultEventHandler(Event* event)
+{
+    // If the element is visible, on mouseup, clear the value, and set selection
+    HTMLInputElement* input = static_cast<HTMLInputElement*>(shadowAncestorNode());
+    if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) {
+        if (renderer() && renderer()->visibleToHitTesting()) {
+            if (Frame* frame = document()->frame()) {
+                frame->eventHandler()->setCapturingMouseEventsNode(this);
+                m_capturing = true;
+            }
+        }
+        input->focus();
+        input->select();
+        event->setDefaultHandled();
+    }
+    if (event->type() == eventNames().mouseupEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) {
+        if (m_capturing && renderer() && renderer()->visibleToHitTesting()) {
+            if (Frame* frame = document()->frame()) {
+                frame->eventHandler()->setCapturingMouseEventsNode(0);
+                m_capturing = false;
+            }
+            if (hovered()) {
+                input->setValue("");
+                input->onSearch();
+                event->setDefaultHandled();
+            }
+        }
+    }
+
+    if (!event->defaultHandled())
+        HTMLDivElement::defaultEventHandler(event);
+}
+
+// ----------------------------
+
+inline SpinButtonElement::SpinButtonElement(Node* shadowParent)
+    : TextControlInnerElement(shadowParent->document(), shadowParent)
+    , m_capturing(false)
+    , m_upDownState(Indeterminate)
+{
+}
+
+PassRefPtr<SpinButtonElement> SpinButtonElement::create(Node* shadowParent)
+{
+    return adoptRef(new SpinButtonElement(shadowParent));
+}
+
+void SpinButtonElement::defaultEventHandler(Event* event)
+{
+    if (!event->isMouseEvent()) {
+        if (!event->defaultHandled())
+            HTMLDivElement::defaultEventHandler(event);
+        return;
+    }
+
+    MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
+    if (mouseEvent->button() != LeftButton) {
+        if (!event->defaultHandled())
+            HTMLDivElement::defaultEventHandler(event);
+        return;
+    }
+
+    RenderBox* box = renderBox();
+    if (!box) {
+        if (!event->defaultHandled())
+            HTMLDivElement::defaultEventHandler(event);
+        return;        
+    }
+    
+    HTMLInputElement* input = static_cast<HTMLInputElement*>(shadowAncestorNode());
+    if (input->disabled() || input->isReadOnlyFormControl()) {
+        if (!event->defaultHandled())
+            HTMLDivElement::defaultEventHandler(event);
+        return;
+    }
+
+    IntPoint local = roundedIntPoint(box->absoluteToLocal(mouseEvent->absoluteLocation(), false, true));
+    if (event->type() == eventNames().clickEvent) {
+        if (box->borderBoxRect().contains(local)) {
+            RefPtr<Node> protector(input);
+            input->focus();
+            input->select();
+            if (local.y() < box->height() / 2)
+                input->stepUpFromRenderer(1);
+            else
+                input->stepUpFromRenderer(-1);
+            event->setDefaultHandled();
+        }
+    } else if (event->type() == eventNames().mousemoveEvent) {
+        if (box->borderBoxRect().contains(local)) {
+            if (!m_capturing) {
+                if (Frame* frame = document()->frame()) {
+                    frame->eventHandler()->setCapturingMouseEventsNode(input);
+                    m_capturing = true;
+                }
+            }
+            UpDownState oldUpDownState = m_upDownState;
+            m_upDownState = local.y() < box->height() / 2 ? Up : Down;
+            if (m_upDownState != oldUpDownState)
+                renderer()->repaint();
+        } else {
+            if (m_capturing) {
+                if (Frame* frame = document()->frame()) {
+                    frame->eventHandler()->setCapturingMouseEventsNode(0);
+                    m_capturing = false;
+                }
+            }
+        }
+    }
+
+    if (!event->defaultHandled())
+        HTMLDivElement::defaultEventHandler(event);
+}
+
+void SpinButtonElement::setHovered(bool flag)
+{
+    if (!hovered() && flag)
+        m_upDownState = Indeterminate;
+    TextControlInnerElement::setHovered(flag);
+}
+
+
+// ----------------------------
+
+#if ENABLE(INPUT_SPEECH)
+
+inline InputFieldSpeechButtonElement::InputFieldSpeechButtonElement(Document* document)
+    : TextControlInnerElement(document)
+    , m_capturing(false)
+{
+}
+
+PassRefPtr<InputFieldSpeechButtonElement> InputFieldSpeechButtonElement::create(Document* document)
+{
+    return adoptRef(new InputFieldSpeechButtonElement(document));
+}
+
+void InputFieldSpeechButtonElement::defaultEventHandler(Event* event)
+{
+    // On mouse down, select the text and set focus.
+    HTMLInputElement* input = static_cast<HTMLInputElement*>(shadowAncestorNode());
+    if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) {
+        if (renderer() && renderer()->visibleToHitTesting()) {
+            if (Frame* frame = document()->frame()) {
+                frame->eventHandler()->setCapturingMouseEventsNode(this);
+                m_capturing = true;
+            }
+        }
+        // The call to focus() below dispatches a focus event, and an event handler in the page might
+        // remove the input element from DOM. To make sure it remains valid until we finish our work
+        // here, we take a temporary reference.
+        RefPtr<HTMLInputElement> holdRef(input);
+        input->focus();
+        input->select();
+        event->setDefaultHandled();
+    }
+    // On mouse up, start speech recognition.
+    if (event->type() == eventNames().mouseupEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) {
+        if (m_capturing && renderer() && renderer()->visibleToHitTesting()) {
+            if (Frame* frame = document()->frame()) {
+                frame->eventHandler()->setCapturingMouseEventsNode(0);
+                m_capturing = false;
+            }
+            if (hovered()) {
+                speechInput()->startRecognition();
+                event->setDefaultHandled();
+            }
+        }
+    }
+
+    if (!event->defaultHandled())
+        HTMLDivElement::defaultEventHandler(event);
+}
+
+SpeechInput* InputFieldSpeechButtonElement::speechInput()
+{
+    if (!m_speechInput)
+        m_speechInput.set(new SpeechInput(document()->page()->speechInputClient(), this));
+    return m_speechInput.get();
+}
+
+void InputFieldSpeechButtonElement::recordingComplete()
+{
+    // FIXME: Add UI feedback here to indicate that audio recording stopped and recognition is
+    // in progress.
+}
+
+void InputFieldSpeechButtonElement::setRecognitionResult(const String& result)
+{
+    HTMLInputElement* input = static_cast<HTMLInputElement*>(shadowAncestorNode());
+    // The call to setValue() below dispatches an event, and an event handler in the page might
+    // remove the input element from DOM. To make sure it remains valid until we finish our work
+    // here, we take a temporary reference.
+    RefPtr<HTMLInputElement> holdRef(input);
+    input->setValue(result);
+    input->dispatchFormControlChangeEvent();
+    renderer()->repaint();
+}
+
+void InputFieldSpeechButtonElement::detach()
+{
+    if (m_capturing) {
+        if (Frame* frame = document()->frame())
+            frame->eventHandler()->setCapturingMouseEventsNode(0);      
+    }
+    TextControlInnerElement::detach();
+}
+
+#endif // ENABLE(INPUT_SPEECH)
+
+}