Reviewed by Hyatt.
[WebKit-https.git] / WebCore / editing / DeleteButtonController.cpp
1 /*
2  * Copyright (C) 2006 Apple Computer, 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 "RenderObject.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 {
61 }
62
63 static bool isDeletableElement(Node* node)
64 {
65     if (!node || !node->isHTMLElement() || !node->isContentEditable())
66         return false;
67
68     const unsigned minimumWidth = 25;
69     const unsigned minimumHeight = 25;
70     const unsigned minimumVisibleBorders = 3;
71
72     RenderObject* renderer = node->renderer();
73     if (!renderer || renderer->width() < minimumWidth || renderer->height() < minimumHeight)
74         return false;
75
76     if (renderer->isTable())
77         return true;
78
79     if (node->hasTagName(ulTag) || node->hasTagName(olTag))
80         return true;
81
82     if (renderer->isPositioned())
83         return true;
84
85     // allow block elements (excluding table cells) that have some non-transparent borders
86     if (renderer->isRenderBlock() && !renderer->isTableCell()) {
87         RenderStyle* style = renderer->style();
88         if (style && style->hasBorder()) {
89             unsigned visibleBorders = !style->borderTop().isTransparent() + !style->borderBottom().isTransparent() + !style->borderLeft().isTransparent() + !style->borderRight().isTransparent();
90             if (visibleBorders >= minimumVisibleBorders)
91                 return true;
92         }
93     }
94
95     return false;
96 }
97
98 static HTMLElement* enclosingDeletableElement(const Selection& selection)
99 {
100     if (!selection.isContentEditable())
101         return 0;
102
103     RefPtr<Range> range = selection.toRange();
104     if (!range)
105         return 0;
106
107     ExceptionCode ec = 0;
108     Node* container = range->commonAncestorContainer(ec);
109     ASSERT(container);
110     ASSERT(ec == 0);
111
112     // The enclosingNodeOfType function only works on nodes that are editable
113     // (which is strange, given its name).
114     if (!container->isContentEditable())
115         return 0;
116
117     Node* element = enclosingNodeOfType(container, &isDeletableElement);
118     if (!element)
119         return 0;
120
121     ASSERT(element->isHTMLElement());
122     return static_cast<HTMLElement*>(element);
123 }
124
125 void DeleteButtonController::respondToChangedSelection(const Selection& oldSelection)
126 {
127     HTMLElement* oldElement = enclosingDeletableElement(oldSelection);
128     HTMLElement* newElement = enclosingDeletableElement(m_frame->selectionController()->selection());
129     if (oldElement == newElement)
130         return;
131
132     // If the base is inside a deletable element, give the element a delete widget.
133     if (newElement)
134         show(newElement);
135     else
136         hide();
137 }
138
139 void DeleteButtonController::respondToChangedContents()
140 {
141     updateOutlineStyle();
142 }
143
144 void DeleteButtonController::updateOutlineStyle()
145 {
146     if (!m_element || !m_element->renderer() || !m_outlineElement)
147         return;
148
149     CSSMutableStyleDeclaration* style = m_outlineElement->getInlineStyleDecl();
150     style->setProperty(CSS_PROP_WIDTH, String::number(m_element->renderer()->overflowWidth()) + "px");
151     style->setProperty(CSS_PROP_HEIGHT, String::number(m_element->renderer()->overflowHeight()) + "px");
152 }
153
154 void DeleteButtonController::show(HTMLElement* element)
155 {
156     hide();
157
158     if (!element->renderer() || !element->renderer()->isRenderBlock())
159         return;
160
161     if (!m_frame->editor()->shouldShowDeleteInterface(static_cast<HTMLElement*>(element)))
162         return;
163
164     m_element = element;
165
166     m_containerElement = new HTMLDivElement(m_element->document());
167     m_containerElement->setId(containerElementIdentifier);
168
169     CSSMutableStyleDeclaration* style = m_containerElement->getInlineStyleDecl();
170     style->setProperty(CSS_PROP_POSITION, CSS_VAL_ABSOLUTE);
171     style->setProperty(CSS_PROP_TOP, String::number(-m_element->renderer()->borderTop()) + "px");
172     style->setProperty(CSS_PROP_LEFT, String::number(-m_element->renderer()->borderLeft()) + "px");
173     style->setProperty(CSS_PROP__WEBKIT_USER_DRAG, CSS_VAL_NONE);
174     style->setProperty(CSS_PROP__WEBKIT_USER_SELECT, CSS_VAL_NONE);
175     style->setProperty(CSS_PROP__WEBKIT_USER_MODIFY, CSS_VAL_NONE);
176
177     m_outlineElement = new HTMLDivElement(m_element->document());
178     m_outlineElement->setId(outlineElementIdentifier);
179
180     const int borderWidth = 4;
181     const int borderRadius = 6;
182
183     style = m_outlineElement->getInlineStyleDecl();
184     style->setProperty(CSS_PROP_POSITION, CSS_VAL_ABSOLUTE);
185     style->setProperty(CSS_PROP_CURSOR, CSS_VAL_DEFAULT);
186     style->setProperty(CSS_PROP__WEBKIT_USER_DRAG, CSS_VAL_NONE);
187     style->setProperty(CSS_PROP__WEBKIT_USER_SELECT, CSS_VAL_NONE);
188     style->setProperty(CSS_PROP__WEBKIT_USER_MODIFY, CSS_VAL_NONE);
189     style->setProperty(CSS_PROP_Z_INDEX, String::number(-1));
190     style->setProperty(CSS_PROP_TOP, String::number(-borderWidth) + "px");
191     style->setProperty(CSS_PROP_LEFT, String::number(-borderWidth) + "px");
192     style->setProperty(CSS_PROP_BORDER, String::number(borderWidth) + "px solid rgba(0, 0, 0, 0.6)");
193     style->setProperty(CSS_PROP__WEBKIT_BORDER_RADIUS, String::number(borderRadius) + "px");
194
195     updateOutlineStyle();
196
197     ExceptionCode ec = 0;
198     m_containerElement->appendChild(m_outlineElement.get(), ec);
199     ASSERT(ec == 0);
200     if (ec) {
201         hide();
202         return;
203     }
204
205     m_buttonElement = new DeleteButton(m_element->document());
206     m_buttonElement->setId(buttonElementIdentifier);
207
208     const int buttonWidth = 30;
209     const int buttonHeight = 30;
210
211     style = m_buttonElement->getInlineStyleDecl();
212     style->setProperty(CSS_PROP_POSITION, CSS_VAL_ABSOLUTE);
213     style->setProperty(CSS_PROP_CURSOR, CSS_VAL_DEFAULT);
214     style->setProperty(CSS_PROP__WEBKIT_USER_DRAG, CSS_VAL_NONE);
215     style->setProperty(CSS_PROP__WEBKIT_USER_SELECT, CSS_VAL_NONE);
216     style->setProperty(CSS_PROP__WEBKIT_USER_MODIFY, CSS_VAL_NONE);
217     style->setProperty(CSS_PROP_LEFT, String::number(-buttonWidth / 2) + "px");
218     style->setProperty(CSS_PROP_TOP, String::number(-buttonHeight / 2) + "px");
219     style->setProperty(CSS_PROP_WIDTH, String::number(buttonWidth) + "px");
220     style->setProperty(CSS_PROP_HEIGHT, String::number(buttonHeight) + "px");
221
222     m_buttonElement->setCachedImage(new CachedImage(Image::loadPlatformResource("deleteButton")));
223
224     m_containerElement->appendChild(m_buttonElement.get(), ec);
225     ASSERT(ec == 0);
226     if (ec) {
227         hide();
228         return;
229     }
230
231     m_element->appendChild(m_containerElement.get(), ec);
232     ASSERT(ec == 0);
233     if (ec) {
234         hide();
235         return;
236     }
237
238     if (m_element->renderer()->style()->position() == StaticPosition) {
239         m_element->getInlineStyleDecl()->setProperty(CSS_PROP_POSITION, CSS_VAL_RELATIVE);
240         m_wasStaticPositioned = true;
241     }
242
243     if (m_element->renderer()->style()->hasAutoZIndex()) {
244         m_element->getInlineStyleDecl()->setProperty(CSS_PROP_Z_INDEX, "0");
245         m_wasAutoZIndex = true;
246     }
247
248     m_element->renderer()->repaint(true);
249 }
250
251 void DeleteButtonController::hide()
252 {
253     ExceptionCode ec = 0;
254     if (m_containerElement)
255         m_containerElement->parentNode()->removeChild(m_containerElement.get(), ec);
256
257     if (m_element && m_wasStaticPositioned)
258         m_element->getInlineStyleDecl()->setProperty(CSS_PROP_POSITION, CSS_VAL_STATIC);
259
260     if (m_element && m_wasAutoZIndex)
261         m_element->getInlineStyleDecl()->setProperty(CSS_PROP_Z_INDEX, CSS_VAL_AUTO);
262
263     m_wasStaticPositioned = false;
264     m_wasAutoZIndex = false;
265     m_element = 0;
266     m_outlineElement = 0;
267     m_buttonElement = 0;
268     m_containerElement = 0;
269 }
270
271 void DeleteButtonController::deleteTarget()
272 {
273     if (!m_element)
274         return;
275
276     RefPtr<Node> element = m_element;
277     hide();
278
279     RefPtr<RemoveNodeCommand> command = new RemoveNodeCommand(element.get());
280     command->apply();
281 }
282
283 } // namespace WebCore