f35ae798cfbed3a22ac49785ce7487bc934d56ba
[WebKit-https.git] / WebCore / editing / DeleteButtonController.cpp
1 /*
2  * Copyright (C) 2006, 2008 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 "htmlediting.h"
39 #include "HTMLDivElement.h"
40 #include "HTMLNames.h"
41 #include "Image.h"
42 #include "Node.h"
43 #include "Range.h"
44 #include "RemoveNodeCommand.h"
45 #include "RenderBox.h"
46 #include "SelectionController.h"
47
48 namespace WebCore {
49
50 using namespace HTMLNames;
51
52 const char* const DeleteButtonController::containerElementIdentifier = "WebKit-Editing-Delete-Container";
53 const char* const DeleteButtonController::buttonElementIdentifier = "WebKit-Editing-Delete-Button";
54 const char* const DeleteButtonController::outlineElementIdentifier = "WebKit-Editing-Delete-Outline";
55
56 DeleteButtonController::DeleteButtonController(Frame* frame)
57     : m_frame(frame)
58     , m_wasStaticPositioned(false)
59     , m_wasAutoZIndex(false)
60     , m_disableStack(0)
61 {
62 }
63
64 static bool isDeletableElement(const Node* node)
65 {
66     if (!node || !node->isHTMLElement() || !node->inDocument() || !node->isContentEditable())
67         return false;
68
69     const int minimumWidth = 25;
70     const int minimumHeight = 25;
71     const unsigned minimumVisibleBorders = 3;
72
73     RenderObject* renderer = node->renderer();
74     if (!renderer || !renderer->isBox())
75         return false;
76
77     RenderBox* box = toRenderBox(renderer);
78     IntRect borderBoundingBox = box->borderBoundingBox();
79     if (borderBoundingBox.width() < minimumWidth || borderBoundingBox.height() < minimumHeight)
80         return false;
81
82     if (renderer->isTable())
83         return true;
84
85     if (node->hasTagName(ulTag) || node->hasTagName(olTag))
86         return true;
87
88     if (renderer->isPositioned())
89         return true;
90
91     // allow block elements (excluding table cells) that have some non-transparent borders
92     if (renderer->isRenderBlock() && !renderer->isTableCell()) {
93         RenderStyle* style = renderer->style();
94         if (style && style->hasBorder()) {
95             unsigned visibleBorders = style->borderTop().isVisible() + style->borderBottom().isVisible() + style->borderLeft().isVisible() + style->borderRight().isVisible();
96             if (visibleBorders >= minimumVisibleBorders)
97                 return true;
98         }
99     }
100
101     return false;
102 }
103
104 static HTMLElement* enclosingDeletableElement(const VisibleSelection& selection)
105 {
106     if (!selection.isContentEditable())
107         return 0;
108
109     RefPtr<Range> range = selection.toNormalizedRange();
110     if (!range)
111         return 0;
112
113     ExceptionCode ec = 0;
114     Node* container = range->commonAncestorContainer(ec);
115     ASSERT(container);
116     ASSERT(ec == 0);
117
118     // The enclosingNodeOfType function only works on nodes that are editable
119     // (which is strange, given its name).
120     if (!container->isContentEditable())
121         return 0;
122
123     Node* element = enclosingNodeOfType(Position(container, 0), &isDeletableElement);
124     if (!element)
125         return 0;
126
127     ASSERT(element->isHTMLElement());
128     return static_cast<HTMLElement*>(element);
129 }
130
131 void DeleteButtonController::respondToChangedSelection(const VisibleSelection& oldSelection)
132 {
133     if (!enabled())
134         return;
135
136     HTMLElement* oldElement = enclosingDeletableElement(oldSelection);
137     HTMLElement* newElement = enclosingDeletableElement(m_frame->selection()->selection());
138     if (oldElement == newElement)
139         return;
140
141     // If the base is inside a deletable element, give the element a delete widget.
142     if (newElement)
143         show(newElement);
144     else
145         hide();
146 }
147
148 void DeleteButtonController::createDeletionUI()
149 {
150     RefPtr<HTMLDivElement> container = new HTMLDivElement(divTag, m_target->document());
151     container->setId(containerElementIdentifier);
152
153     CSSMutableStyleDeclaration* style = container->getInlineStyleDecl();
154     style->setProperty(CSSPropertyWebkitUserDrag, CSSValueNone);
155     style->setProperty(CSSPropertyWebkitUserSelect, CSSValueNone);
156     style->setProperty(CSSPropertyWebkitUserModify, CSSValueNone);
157     style->setProperty(CSSPropertyVisibility, CSSValueHidden);
158     style->setProperty(CSSPropertyPosition, CSSValueAbsolute);
159     style->setProperty(CSSPropertyCursor, CSSValueDefault);
160     style->setProperty(CSSPropertyTop, "0");
161     style->setProperty(CSSPropertyRight, "0");
162     style->setProperty(CSSPropertyBottom, "0");
163     style->setProperty(CSSPropertyLeft, "0");
164
165     RefPtr<HTMLDivElement> outline = new HTMLDivElement(divTag, m_target->document());
166     outline->setId(outlineElementIdentifier);
167
168     const int borderWidth = 4;
169     const int borderRadius = 6;
170
171     style = outline->getInlineStyleDecl();
172     style->setProperty(CSSPropertyPosition, CSSValueAbsolute);
173     style->setProperty(CSSPropertyZIndex, String::number(-1000000));
174     style->setProperty(CSSPropertyTop, String::number(-borderWidth - m_target->renderBox()->borderTop()) + "px");
175     style->setProperty(CSSPropertyRight, String::number(-borderWidth - m_target->renderBox()->borderRight()) + "px");
176     style->setProperty(CSSPropertyBottom, String::number(-borderWidth - m_target->renderBox()->borderBottom()) + "px");
177     style->setProperty(CSSPropertyLeft, String::number(-borderWidth - m_target->renderBox()->borderLeft()) + "px");
178     style->setProperty(CSSPropertyBorder, String::number(borderWidth) + "px solid rgba(0, 0, 0, 0.6)");
179     style->setProperty(CSSPropertyWebkitBorderRadius, String::number(borderRadius) + "px");
180     style->setProperty(CSSPropertyVisibility, CSSValueVisible);
181
182     ExceptionCode ec = 0;
183     container->appendChild(outline.get(), ec);
184     ASSERT(ec == 0);
185     if (ec)
186         return;
187
188     RefPtr<DeleteButton> button = new DeleteButton(m_target->document());
189     button->setId(buttonElementIdentifier);
190
191     const int buttonWidth = 30;
192     const int buttonHeight = 30;
193     const int buttonBottomShadowOffset = 2;
194
195     style = button->getInlineStyleDecl();
196     style->setProperty(CSSPropertyPosition, CSSValueAbsolute);
197     style->setProperty(CSSPropertyZIndex, String::number(1000000));
198     style->setProperty(CSSPropertyTop, String::number((-buttonHeight / 2) - m_target->renderBox()->borderTop() - (borderWidth / 2) + buttonBottomShadowOffset) + "px");
199     style->setProperty(CSSPropertyLeft, String::number((-buttonWidth / 2) - m_target->renderBox()->borderLeft() - (borderWidth / 2)) + "px");
200     style->setProperty(CSSPropertyWidth, String::number(buttonWidth) + "px");
201     style->setProperty(CSSPropertyHeight, String::number(buttonHeight) + "px");
202     style->setProperty(CSSPropertyVisibility, CSSValueVisible);
203
204     RefPtr<Image> buttonImage = Image::loadPlatformResource("deleteButton");
205     if (buttonImage->isNull())
206         return;
207
208     button->setCachedImage(new CachedImage(buttonImage.get()));
209
210     container->appendChild(button.get(), ec);
211     ASSERT(ec == 0);
212     if (ec)
213         return;
214
215     m_containerElement = container.release();
216     m_outlineElement = outline.release();
217     m_buttonElement = button.release();
218 }
219
220 void DeleteButtonController::show(HTMLElement* element)
221 {
222     hide();
223
224     if (!enabled() || !element || !element->inDocument() || !isDeletableElement(element))
225         return;
226
227     if (!m_frame->editor()->shouldShowDeleteInterface(static_cast<HTMLElement*>(element)))
228         return;
229
230     // we rely on the renderer having current information, so we should update the layout if needed
231     m_frame->document()->updateLayoutIgnorePendingStylesheets();
232
233     m_target = element;
234
235     if (!m_containerElement) {
236         createDeletionUI();
237         if (!m_containerElement) {
238             hide();
239             return;
240         }
241     }
242
243     ExceptionCode ec = 0;
244     m_target->appendChild(m_containerElement.get(), ec);
245     ASSERT(ec == 0);
246     if (ec) {
247         hide();
248         return;
249     }
250
251     if (m_target->renderer()->style()->position() == StaticPosition) {
252         m_target->getInlineStyleDecl()->setProperty(CSSPropertyPosition, CSSValueRelative);
253         m_wasStaticPositioned = true;
254     }
255
256     if (m_target->renderer()->style()->hasAutoZIndex()) {
257         m_target->getInlineStyleDecl()->setProperty(CSSPropertyZIndex, "0");
258         m_wasAutoZIndex = true;
259     }
260 }
261
262 void DeleteButtonController::hide()
263 {
264     m_outlineElement = 0;
265     m_buttonElement = 0;
266
267     ExceptionCode ec = 0;
268     if (m_containerElement && m_containerElement->parentNode())
269         m_containerElement->parentNode()->removeChild(m_containerElement.get(), ec);
270
271     if (m_target) {
272         if (m_wasStaticPositioned)
273             m_target->getInlineStyleDecl()->setProperty(CSSPropertyPosition, CSSValueStatic);
274         if (m_wasAutoZIndex)
275             m_target->getInlineStyleDecl()->setProperty(CSSPropertyZIndex, CSSValueAuto);
276     }
277
278     m_wasStaticPositioned = false;
279     m_wasAutoZIndex = false;
280 }
281
282 void DeleteButtonController::enable()
283 {
284     ASSERT(m_disableStack > 0);
285     if (m_disableStack > 0)
286         m_disableStack--;
287     if (enabled())
288         show(enclosingDeletableElement(m_frame->selection()->selection()));
289 }
290
291 void DeleteButtonController::disable()
292 {
293     if (enabled())
294         hide();
295     m_disableStack++;
296 }
297
298 void DeleteButtonController::deleteTarget()
299 {
300     if (!enabled() || !m_target)
301         return;
302
303     RefPtr<Node> element = m_target;
304     hide();
305
306     // Because the deletion UI only appears when the selection is entirely
307     // within the target, we unconditionally update the selection to be
308     // a caret where the target had been.
309     Position pos = positionBeforeNode(element.get());
310     applyCommand(RemoveNodeCommand::create(element.release()));
311     m_frame->selection()->setSelection(VisiblePosition(pos));
312 }
313
314 } // namespace WebCore