Fix the incorrect behavior touch sound on single tap event.
[framework/web/webkit-efl.git] / Source / WebCore / editing / DeleteButtonController.cpp
1 /*
2  * Copyright (C) 2006, 2008, 2009 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 "DeleteButtonController.h"
28
29 #include "CachedImage.h"
30 #include "CSSPrimitiveValue.h"
31 #include "CSSPropertyNames.h"
32 #include "CSSValueKeywords.h"
33 #include "CompositeEditCommand.h"
34 #include "DeleteButton.h"
35 #include "Document.h"
36 #include "Editor.h"
37 #include "Frame.h"
38 #include "FrameSelection.h"
39 #include "htmlediting.h"
40 #include "HTMLDivElement.h"
41 #include "HTMLNames.h"
42 #include "Image.h"
43 #include "Node.h"
44 #include "Page.h"
45 #include "Range.h"
46 #include "RemoveNodeCommand.h"
47 #include "RenderBox.h"
48 #include "StylePropertySet.h"
49
50 namespace WebCore {
51
52 using namespace HTMLNames;
53
54 const char* const DeleteButtonController::containerElementIdentifier = "WebKit-Editing-Delete-Container";
55 const char* const DeleteButtonController::buttonElementIdentifier = "WebKit-Editing-Delete-Button";
56 const char* const DeleteButtonController::outlineElementIdentifier = "WebKit-Editing-Delete-Outline";
57
58 DeleteButtonController::DeleteButtonController(Frame* frame)
59     : m_frame(frame)
60     , m_wasStaticPositioned(false)
61     , m_wasAutoZIndex(false)
62     , m_disableStack(0)
63 {
64 }
65
66 static bool isDeletableElement(const Node* node)
67 {
68     if (!node || !node->isHTMLElement() || !node->inDocument() || !node->rendererIsEditable())
69         return false;
70
71     // In general we want to only draw the UI around object of a certain area, but we still keep the min width/height to
72     // make sure we don't end up with very thin or very short elements getting the UI.
73     const int minimumArea = 2500;
74     const int minimumWidth = 48;
75     const int minimumHeight = 16;
76     const unsigned minimumVisibleBorders = 1;
77
78     RenderObject* renderer = node->renderer();
79     if (!renderer || !renderer->isBox())
80         return false;
81
82     // Disallow the body element since it isn't practical to delete, and the deletion UI would be clipped.
83     if (node->hasTagName(bodyTag))
84         return false;
85
86     // Disallow elements with any overflow clip, since the deletion UI would be clipped as well. <rdar://problem/6840161>
87     if (renderer->hasOverflowClip())
88         return false;
89
90     // Disallow Mail blockquotes since the deletion UI would get in the way of editing for these.
91     if (isMailBlockquote(node))
92         return false;
93
94     RenderBox* box = toRenderBox(renderer);
95     IntRect borderBoundingBox = box->borderBoundingBox();
96     if (borderBoundingBox.width() < minimumWidth || borderBoundingBox.height() < minimumHeight)
97         return false;
98
99     if ((borderBoundingBox.width() * borderBoundingBox.height()) < minimumArea)
100         return false;
101
102     if (renderer->isTable())
103         return true;
104
105     if (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(iframeTag))
106         return true;
107
108     if (renderer->isOutOfFlowPositioned())
109         return true;
110
111     if (renderer->isRenderBlock() && !renderer->isTableCell()) {
112         RenderStyle* style = renderer->style();
113         if (!style)
114             return false;
115
116         // Allow blocks that have background images
117         if (style->hasBackgroundImage()) {
118             for (const FillLayer* background = style->backgroundLayers(); background; background = background->next()) {
119                 if (background->image() && background->image()->canRender(renderer, 1))
120                     return true;
121             }
122         }
123
124         // Allow blocks with a minimum number of non-transparent borders
125         unsigned visibleBorders = style->borderTop().isVisible() + style->borderBottom().isVisible() + style->borderLeft().isVisible() + style->borderRight().isVisible();
126         if (visibleBorders >= minimumVisibleBorders)
127             return true;
128
129         // Allow blocks that have a different background from it's parent
130         ContainerNode* parentNode = node->parentNode();
131         if (!parentNode)
132             return false;
133
134         RenderObject* parentRenderer = parentNode->renderer();
135         if (!parentRenderer)
136             return false;
137
138         RenderStyle* parentStyle = parentRenderer->style();
139         if (!parentStyle)
140             return false;
141
142         if (renderer->hasBackground() && (!parentRenderer->hasBackground() || style->visitedDependentColor(CSSPropertyBackgroundColor) != parentStyle->visitedDependentColor(CSSPropertyBackgroundColor)))
143             return true;
144     }
145
146     return false;
147 }
148
149 static HTMLElement* enclosingDeletableElement(const VisibleSelection& selection)
150 {
151     if (!selection.isContentEditable())
152         return 0;
153
154     RefPtr<Range> range = selection.toNormalizedRange();
155     if (!range)
156         return 0;
157
158     ExceptionCode ec = 0;
159     Node* container = range->commonAncestorContainer(ec);
160     ASSERT(container);
161     ASSERT(ec == 0);
162
163     // The enclosingNodeOfType function only works on nodes that are editable
164     // (which is strange, given its name).
165     if (!container->rendererIsEditable())
166         return 0;
167
168     Node* element = enclosingNodeOfType(firstPositionInNode(container), &isDeletableElement);
169     return element && element->isHTMLElement() ? toHTMLElement(element) : 0;
170 }
171
172 void DeleteButtonController::respondToChangedSelection(const VisibleSelection& oldSelection)
173 {
174     if (!enabled())
175         return;
176
177     HTMLElement* oldElement = enclosingDeletableElement(oldSelection);
178     HTMLElement* newElement = enclosingDeletableElement(m_frame->selection()->selection());
179     if (oldElement == newElement)
180         return;
181
182     // If the base is inside a deletable element, give the element a delete widget.
183     if (newElement)
184         show(newElement);
185     else
186         hide();
187 }
188
189 void DeleteButtonController::deviceScaleFactorChanged()
190 {
191     if (!enabled())
192         return;
193     
194     HTMLElement* currentTarget = m_target.get();
195     hide();
196
197     // Setting m_containerElement to 0 will force the deletionUI to be re-created with
198     // artwork of the appropriate resolution in show().
199     m_containerElement = 0;
200     show(currentTarget);
201 }
202
203 void DeleteButtonController::createDeletionUI()
204 {
205     RefPtr<HTMLDivElement> container = HTMLDivElement::create(m_target->document());
206     container->setIdAttribute(containerElementIdentifier);
207
208     container->setInlineStyleProperty(CSSPropertyWebkitUserDrag, CSSValueNone);
209     container->setInlineStyleProperty(CSSPropertyWebkitUserSelect, CSSValueNone);
210     container->setInlineStyleProperty(CSSPropertyWebkitUserModify, CSSValueReadOnly);
211     container->setInlineStyleProperty(CSSPropertyVisibility, CSSValueHidden);
212     container->setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute);
213     container->setInlineStyleProperty(CSSPropertyCursor, CSSValueDefault);
214     container->setInlineStyleProperty(CSSPropertyTop, "0");
215     container->setInlineStyleProperty(CSSPropertyRight, "0");
216     container->setInlineStyleProperty(CSSPropertyBottom, "0");
217     container->setInlineStyleProperty(CSSPropertyLeft, "0");
218
219     RefPtr<HTMLDivElement> outline = HTMLDivElement::create(m_target->document());
220     outline->setIdAttribute(outlineElementIdentifier);
221
222     const int borderWidth = 4;
223     const int borderRadius = 6;
224
225     outline->setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute);
226     outline->setInlineStyleProperty(CSSPropertyZIndex, String::number(-1000000));
227     outline->setInlineStyleProperty(CSSPropertyTop, String::number(-borderWidth - m_target->renderBox()->borderTop()) + "px");
228     outline->setInlineStyleProperty(CSSPropertyRight, String::number(-borderWidth - m_target->renderBox()->borderRight()) + "px");
229     outline->setInlineStyleProperty(CSSPropertyBottom, String::number(-borderWidth - m_target->renderBox()->borderBottom()) + "px");
230     outline->setInlineStyleProperty(CSSPropertyLeft, String::number(-borderWidth - m_target->renderBox()->borderLeft()) + "px");
231     outline->setInlineStyleProperty(CSSPropertyBorder, String::number(borderWidth) + "px solid rgba(0, 0, 0, 0.6)");
232     outline->setInlineStyleProperty(CSSPropertyWebkitBorderRadius, String::number(borderRadius) + "px");
233     outline->setInlineStyleProperty(CSSPropertyVisibility, CSSValueVisible);
234
235     ExceptionCode ec = 0;
236     container->appendChild(outline.get(), ec);
237     ASSERT(ec == 0);
238     if (ec)
239         return;
240
241     RefPtr<DeleteButton> button = DeleteButton::create(m_target->document());
242     button->setIdAttribute(buttonElementIdentifier);
243
244     const int buttonWidth = 30;
245     const int buttonHeight = 30;
246     const int buttonBottomShadowOffset = 2;
247
248     button->setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute);
249     button->setInlineStyleProperty(CSSPropertyZIndex, String::number(1000000));
250     button->setInlineStyleProperty(CSSPropertyTop, String::number((-buttonHeight / 2) - m_target->renderBox()->borderTop() - (borderWidth / 2) + buttonBottomShadowOffset) + "px");
251     button->setInlineStyleProperty(CSSPropertyLeft, String::number((-buttonWidth / 2) - m_target->renderBox()->borderLeft() - (borderWidth / 2)) + "px");
252     button->setInlineStyleProperty(CSSPropertyWidth, String::number(buttonWidth) + "px");
253     button->setInlineStyleProperty(CSSPropertyHeight, String::number(buttonHeight) + "px");
254     button->setInlineStyleProperty(CSSPropertyVisibility, CSSValueVisible);
255
256     float deviceScaleFactor = WebCore::deviceScaleFactor(m_frame);
257     RefPtr<Image> buttonImage;
258     if (deviceScaleFactor >= 2)
259         buttonImage = Image::loadPlatformResource("deleteButton@2x");
260     else
261         buttonImage = Image::loadPlatformResource("deleteButton");
262
263     if (buttonImage->isNull())
264         return;
265
266     button->setCachedImage(new CachedImage(buttonImage.get()));
267
268     container->appendChild(button.get(), ec);
269     ASSERT(ec == 0);
270     if (ec)
271         return;
272
273     m_containerElement = container.release();
274     m_outlineElement = outline.release();
275     m_buttonElement = button.release();
276 }
277
278 void DeleteButtonController::show(HTMLElement* element)
279 {
280     hide();
281
282     if (!enabled() || !element || !element->inDocument() || !isDeletableElement(element))
283         return;
284
285     if (!m_frame->editor()->shouldShowDeleteInterface(element))
286         return;
287
288     // we rely on the renderer having current information, so we should update the layout if needed
289     m_frame->document()->updateLayoutIgnorePendingStylesheets();
290
291     m_target = element;
292
293     if (!m_containerElement) {
294         createDeletionUI();
295         if (!m_containerElement) {
296             hide();
297             return;
298         }
299     }
300
301     ExceptionCode ec = 0;
302     m_target->appendChild(m_containerElement.get(), ec);
303     ASSERT(ec == 0);
304     if (ec) {
305         hide();
306         return;
307     }
308
309     if (m_target->renderer()->style()->position() == StaticPosition) {
310         m_target->setInlineStyleProperty(CSSPropertyPosition, CSSValueRelative);
311         m_wasStaticPositioned = true;
312     }
313
314     if (m_target->renderer()->style()->hasAutoZIndex()) {
315         m_target->setInlineStyleProperty(CSSPropertyZIndex, "0");
316         m_wasAutoZIndex = true;
317     }
318 }
319
320 void DeleteButtonController::hide()
321 {
322     m_outlineElement = 0;
323     m_buttonElement = 0;
324
325     ExceptionCode ec = 0;
326     if (m_containerElement && m_containerElement->parentNode())
327         m_containerElement->parentNode()->removeChild(m_containerElement.get(), ec);
328
329     if (m_target) {
330         if (m_wasStaticPositioned)
331             m_target->setInlineStyleProperty(CSSPropertyPosition, CSSValueStatic);
332         if (m_wasAutoZIndex)
333             m_target->setInlineStyleProperty(CSSPropertyZIndex, CSSValueAuto);
334     }
335
336     m_wasStaticPositioned = false;
337     m_wasAutoZIndex = false;
338 }
339
340 void DeleteButtonController::enable()
341 {
342     ASSERT(m_disableStack > 0);
343     if (m_disableStack > 0)
344         m_disableStack--;
345     if (enabled()) {
346         // Determining if the element is deletable currently depends on style
347         // because whether something is editable depends on style, so we need
348         // to recalculate style before calling enclosingDeletableElement.
349         m_frame->document()->updateStyleIfNeeded();
350         show(enclosingDeletableElement(m_frame->selection()->selection()));
351     }
352 }
353
354 void DeleteButtonController::disable()
355 {
356     if (enabled())
357         hide();
358     m_disableStack++;
359 }
360
361 class RemoveTargetCommand : public CompositeEditCommand {
362 public:
363     static PassRefPtr<RemoveTargetCommand> create(Document* document, PassRefPtr<Node> target)
364     {
365         return adoptRef(new RemoveTargetCommand(document, target));
366     }
367
368 private:
369     RemoveTargetCommand(Document* document, PassRefPtr<Node> target)
370         : CompositeEditCommand(document)
371         , m_target(target)
372     { }
373
374     void doApply()
375     {
376         removeNode(m_target);
377     }
378
379 private:
380     RefPtr<Node> m_target;
381 };
382
383 void DeleteButtonController::deleteTarget()
384 {
385     if (!enabled() || !m_target)
386         return;
387
388     hide();
389
390     // Because the deletion UI only appears when the selection is entirely
391     // within the target, we unconditionally update the selection to be
392     // a caret where the target had been.
393     Position pos = positionInParentBeforeNode(m_target.get());
394     applyCommand(RemoveTargetCommand::create(m_frame->document(), m_target));
395     m_frame->selection()->setSelection(VisiblePosition(pos));
396 }
397
398 } // namespace WebCore