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