1/*
2 * Copyright (C) 2004, 2008, 2009, 2010 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "core/editing/Caret.h"
28
29#include "core/dom/Document.h"
30#include "core/editing/VisibleUnits.h"
31#include "core/editing/htmlediting.h"
32#include "core/frame/LocalFrame.h"
33#include "core/frame/Settings.h"
34#include "core/html/HTMLTextFormControlElement.h"
35#include "core/rendering/RenderBlock.h"
36#include "core/rendering/RenderLayer.h"
37#include "core/rendering/RenderView.h"
38#include "platform/graphics/GraphicsContext.h"
39
40namespace blink {
41
42CaretBase::CaretBase(CaretVisibility visibility)
43    : m_caretRectNeedsUpdate(true)
44    , m_caretVisibility(visibility)
45{
46}
47
48DragCaretController::DragCaretController()
49    : CaretBase(Visible)
50{
51}
52
53PassOwnPtrWillBeRawPtr<DragCaretController> DragCaretController::create()
54{
55    return adoptPtrWillBeNoop(new DragCaretController);
56}
57
58bool DragCaretController::isContentRichlyEditable() const
59{
60    return isRichlyEditablePosition(m_position.deepEquivalent());
61}
62
63void DragCaretController::setCaretPosition(const VisiblePosition& position)
64{
65    // for querying RenderLayer::compositingState()
66    // This code is probably correct, since it doesn't occur in a stack that involves updating compositing state.
67    DisableCompositingQueryAsserts disabler;
68
69    if (Node* node = m_position.deepEquivalent().deprecatedNode())
70        invalidateCaretRect(node);
71    m_position = position;
72    setCaretRectNeedsUpdate();
73    Document* document = 0;
74    if (Node* node = m_position.deepEquivalent().deprecatedNode()) {
75        invalidateCaretRect(node);
76        document = &node->document();
77    }
78    if (m_position.isNull() || m_position.isOrphan()) {
79        clearCaretRect();
80    } else {
81        document->updateRenderTreeIfNeeded();
82        updateCaretRect(document, m_position);
83    }
84}
85
86static bool removingNodeRemovesPosition(Node& node, const Position& position)
87{
88    if (!position.anchorNode())
89        return false;
90
91    if (position.anchorNode() == node)
92        return true;
93
94    if (!node.isElementNode())
95        return false;
96
97    Element& element = toElement(node);
98    return element.containsIncludingShadowDOM(position.anchorNode());
99}
100
101void DragCaretController::nodeWillBeRemoved(Node& node)
102{
103    if (!hasCaret() || !node.inActiveDocument())
104        return;
105
106    if (!removingNodeRemovesPosition(node, m_position.deepEquivalent()))
107        return;
108
109    m_position.deepEquivalent().document()->renderView()->clearSelection();
110    clear();
111}
112
113void DragCaretController::trace(Visitor* visitor)
114{
115    visitor->trace(m_position);
116}
117
118void CaretBase::clearCaretRect()
119{
120    m_caretLocalRect = LayoutRect();
121}
122
123static inline bool caretRendersInsideNode(Node* node)
124{
125    return node && !isRenderedTableElement(node) && !editingIgnoresContent(node);
126}
127
128RenderBlock* CaretBase::caretRenderer(Node* node)
129{
130    if (!node)
131        return 0;
132
133    RenderObject* renderer = node->renderer();
134    if (!renderer)
135        return 0;
136
137    // if caretNode is a block and caret is inside it then caret should be painted by that block
138    bool paintedByBlock = renderer->isRenderBlock() && caretRendersInsideNode(node);
139    return paintedByBlock ? toRenderBlock(renderer) : renderer->containingBlock();
140}
141
142static void mapCaretRectToCaretPainter(RenderObject* caretRenderer, RenderBlock* caretPainter, LayoutRect& caretRect)
143{
144    // FIXME: This shouldn't be called on un-rooted subtrees.
145    // FIXME: This should probably just use mapLocalToContainer.
146    // Compute an offset between the caretRenderer and the caretPainter.
147
148    ASSERT(caretRenderer->isDescendantOf(caretPainter));
149
150    bool unrooted = false;
151    while (caretRenderer != caretPainter) {
152        RenderObject* containerObject = caretRenderer->container();
153        if (!containerObject) {
154            unrooted = true;
155            break;
156        }
157        caretRect.move(caretRenderer->offsetFromContainer(containerObject, caretRect.location()));
158        caretRenderer = containerObject;
159    }
160
161    if (unrooted)
162        caretRect = LayoutRect();
163}
164
165bool CaretBase::updateCaretRect(Document* document, const PositionWithAffinity& caretPosition)
166{
167    m_caretLocalRect = LayoutRect();
168
169    m_caretRectNeedsUpdate = false;
170
171    if (caretPosition.position().isNull())
172        return false;
173
174    ASSERT(caretPosition.position().deprecatedNode()->renderer());
175
176    // First compute a rect local to the renderer at the selection start.
177    RenderObject* renderer;
178    m_caretLocalRect = localCaretRectOfPosition(caretPosition, renderer);
179
180    // Get the renderer that will be responsible for painting the caret
181    // (which is either the renderer we just found, or one of its containers).
182    RenderBlock* caretPainter = caretRenderer(caretPosition.position().deprecatedNode());
183
184    mapCaretRectToCaretPainter(renderer, caretPainter, m_caretLocalRect);
185
186    return true;
187}
188
189bool CaretBase::updateCaretRect(Document* document, const VisiblePosition& caretPosition)
190{
191    return updateCaretRect(document, PositionWithAffinity(caretPosition.deepEquivalent(), caretPosition.affinity()));
192}
193
194RenderBlock* DragCaretController::caretRenderer() const
195{
196    return CaretBase::caretRenderer(m_position.deepEquivalent().deprecatedNode());
197}
198
199IntRect CaretBase::absoluteBoundsForLocalRect(Node* node, const LayoutRect& rect) const
200{
201    RenderBlock* caretPainter = caretRenderer(node);
202    if (!caretPainter)
203        return IntRect();
204
205    LayoutRect localRect(rect);
206    caretPainter->flipForWritingMode(localRect);
207    return caretPainter->localToAbsoluteQuad(FloatRect(localRect)).enclosingBoundingBox();
208}
209
210void CaretBase::invalidateLocalCaretRect(Node* node, const LayoutRect& rect)
211{
212    RenderBlock* caretPainter = caretRenderer(node);
213    if (!caretPainter)
214        return;
215
216    // FIXME: Need to over-paint 1 pixel to workaround some rounding problems.
217    // https://bugs.webkit.org/show_bug.cgi?id=108283
218    LayoutRect inflatedRect = rect;
219    inflatedRect.inflate(1);
220
221    // FIXME: We should use mapLocalToContainer() since we know we're not un-rooted.
222    mapCaretRectToCaretPainter(node->renderer(), caretPainter, inflatedRect);
223
224    caretPainter->invalidatePaintRectangle(inflatedRect);
225}
226
227bool CaretBase::shouldRepaintCaret(const RenderView* view, bool isContentEditable) const
228{
229    ASSERT(view);
230    bool caretBrowsing = false;
231    if (FrameView* frameView = view->frameView()) {
232        LocalFrame& frame = frameView->frame(); // The frame where the selection started
233        caretBrowsing = frame.settings() && frame.settings()->caretBrowsingEnabled();
234    }
235    return (caretBrowsing || isContentEditable);
236}
237
238void CaretBase::invalidateCaretRect(Node* node, bool caretRectChanged)
239{
240    // EDIT FIXME: This is an unfortunate hack.
241    // Basically, we can't trust this layout position since we
242    // can't guarantee that the check to see if we are in unrendered
243    // content will work at this point. We may have to wait for
244    // a layout and re-render of the document to happen. So, resetting this
245    // flag will cause another caret layout to happen the first time
246    // that we try to paint the caret after this call. That one will work since
247    // it happens after the document has accounted for any editing
248    // changes which may have been done.
249    // And, we need to leave this layout here so the caret moves right
250    // away after clicking.
251    m_caretRectNeedsUpdate = true;
252
253    if (caretRectChanged)
254        return;
255
256    if (RenderView* view = node->document().renderView()) {
257        if (shouldRepaintCaret(view, node->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable)))
258            invalidateLocalCaretRect(node, localCaretRectWithoutUpdate());
259    }
260}
261
262void CaretBase::paintCaret(Node* node, GraphicsContext* context, const LayoutPoint& paintOffset, const LayoutRect& clipRect) const
263{
264    if (m_caretVisibility == Hidden)
265        return;
266
267    LayoutRect drawingRect = localCaretRectWithoutUpdate();
268    if (RenderBlock* renderer = caretRenderer(node))
269        renderer->flipForWritingMode(drawingRect);
270    drawingRect.moveBy(roundedIntPoint(paintOffset));
271    LayoutRect caret = intersection(drawingRect, clipRect);
272    if (caret.isEmpty())
273        return;
274
275    Color caretColor = Color::black;
276
277    Element* element;
278    if (node->isElementNode())
279        element = toElement(node);
280    else
281        element = node->parentElement();
282
283    if (element && element->renderer())
284        caretColor = element->renderer()->resolveColor(CSSPropertyColor);
285
286    context->fillRect(caret, caretColor);
287}
288
289void DragCaretController::paintDragCaret(LocalFrame* frame, GraphicsContext* p, const LayoutPoint& paintOffset, const LayoutRect& clipRect) const
290{
291    if (m_position.deepEquivalent().deprecatedNode()->document().frame() == frame)
292        paintCaret(m_position.deepEquivalent().deprecatedNode(), p, paintOffset, clipRect);
293}
294
295}
296