• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
DeleteButtonController(Frame * frame)56 DeleteButtonController::DeleteButtonController(Frame* frame)
57     : m_frame(frame)
58     , m_wasStaticPositioned(false)
59     , m_wasAutoZIndex(false)
60     , m_disableStack(0)
61 {
62 }
63 
isDeletableElement(const Node * node)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 
enclosingDeletableElement(const Selection & selection)104 static HTMLElement* enclosingDeletableElement(const Selection& selection)
105 {
106     if (!selection.isContentEditable())
107         return 0;
108 
109     RefPtr<Range> range = selection.toRange();
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 
respondToChangedSelection(const Selection & oldSelection)131 void DeleteButtonController::respondToChangedSelection(const Selection& 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 
createDeletionUI()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 
158     RefPtr<HTMLDivElement> outline = new HTMLDivElement(divTag, m_target->document());
159     outline->setId(outlineElementIdentifier);
160 
161     const int borderWidth = 4;
162     const int borderRadius = 6;
163 
164     style = outline->getInlineStyleDecl();
165     style->setProperty(CSSPropertyPosition, CSSValueAbsolute);
166     style->setProperty(CSSPropertyCursor, CSSValueDefault);
167     style->setProperty(CSSPropertyWebkitUserDrag, CSSValueNone);
168     style->setProperty(CSSPropertyWebkitUserSelect, CSSValueNone);
169     style->setProperty(CSSPropertyWebkitUserModify, CSSValueNone);
170     style->setProperty(CSSPropertyZIndex, String::number(-1000000));
171     style->setProperty(CSSPropertyTop, String::number(-borderWidth - m_target->renderBox()->borderTop()) + "px");
172     style->setProperty(CSSPropertyRight, String::number(-borderWidth - m_target->renderBox()->borderRight()) + "px");
173     style->setProperty(CSSPropertyBottom, String::number(-borderWidth - m_target->renderBox()->borderBottom()) + "px");
174     style->setProperty(CSSPropertyLeft, String::number(-borderWidth - m_target->renderBox()->borderLeft()) + "px");
175     style->setProperty(CSSPropertyBorder, String::number(borderWidth) + "px solid rgba(0, 0, 0, 0.6)");
176     style->setProperty(CSSPropertyWebkitBorderRadius, String::number(borderRadius) + "px");
177 
178     ExceptionCode ec = 0;
179     container->appendChild(outline.get(), ec);
180     ASSERT(ec == 0);
181     if (ec)
182         return;
183 
184     RefPtr<DeleteButton> button = new DeleteButton(m_target->document());
185     button->setId(buttonElementIdentifier);
186 
187     const int buttonWidth = 30;
188     const int buttonHeight = 30;
189     const int buttonBottomShadowOffset = 2;
190 
191     style = button->getInlineStyleDecl();
192     style->setProperty(CSSPropertyPosition, CSSValueAbsolute);
193     style->setProperty(CSSPropertyCursor, CSSValueDefault);
194     style->setProperty(CSSPropertyWebkitUserDrag, CSSValueNone);
195     style->setProperty(CSSPropertyWebkitUserSelect, CSSValueNone);
196     style->setProperty(CSSPropertyWebkitUserModify, CSSValueNone);
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 
203     RefPtr<Image> buttonImage = Image::loadPlatformResource("deleteButton");
204     if (buttonImage->isNull())
205         return;
206 
207     button->setCachedImage(new CachedImage(buttonImage.get()));
208 
209     container->appendChild(button.get(), ec);
210     ASSERT(ec == 0);
211     if (ec)
212         return;
213 
214     m_containerElement = container.release();
215     m_outlineElement = outline.release();
216     m_buttonElement = button.release();
217 }
218 
show(HTMLElement * element)219 void DeleteButtonController::show(HTMLElement* element)
220 {
221     hide();
222 
223     if (!enabled() || !element || !element->inDocument() || !isDeletableElement(element))
224         return;
225 
226     if (!m_frame->editor()->shouldShowDeleteInterface(static_cast<HTMLElement*>(element)))
227         return;
228 
229     // we rely on the renderer having current information, so we should update the layout if needed
230     m_frame->document()->updateLayoutIgnorePendingStylesheets();
231 
232     m_target = element;
233 
234     if (!m_containerElement) {
235         createDeletionUI();
236         if (!m_containerElement) {
237             hide();
238             return;
239         }
240     }
241 
242     ExceptionCode ec = 0;
243     m_target->appendChild(m_containerElement.get(), ec);
244     ASSERT(ec == 0);
245     if (ec) {
246         hide();
247         return;
248     }
249 
250     if (m_target->renderer()->style()->position() == StaticPosition) {
251         m_target->getInlineStyleDecl()->setProperty(CSSPropertyPosition, CSSValueRelative);
252         m_wasStaticPositioned = true;
253     }
254 
255     if (m_target->renderer()->style()->hasAutoZIndex()) {
256         m_target->getInlineStyleDecl()->setProperty(CSSPropertyZIndex, "0");
257         m_wasAutoZIndex = true;
258     }
259 }
260 
hide()261 void DeleteButtonController::hide()
262 {
263     m_outlineElement = 0;
264     m_buttonElement = 0;
265 
266     ExceptionCode ec = 0;
267     if (m_containerElement && m_containerElement->parentNode())
268         m_containerElement->parentNode()->removeChild(m_containerElement.get(), ec);
269 
270     if (m_target) {
271         if (m_wasStaticPositioned)
272             m_target->getInlineStyleDecl()->setProperty(CSSPropertyPosition, CSSValueStatic);
273         if (m_wasAutoZIndex)
274             m_target->getInlineStyleDecl()->setProperty(CSSPropertyZIndex, CSSValueAuto);
275     }
276 
277     m_wasStaticPositioned = false;
278     m_wasAutoZIndex = false;
279 }
280 
enable()281 void DeleteButtonController::enable()
282 {
283     ASSERT(m_disableStack > 0);
284     if (m_disableStack > 0)
285         m_disableStack--;
286     if (enabled())
287         show(enclosingDeletableElement(m_frame->selection()->selection()));
288 }
289 
disable()290 void DeleteButtonController::disable()
291 {
292     if (enabled())
293         hide();
294     m_disableStack++;
295 }
296 
deleteTarget()297 void DeleteButtonController::deleteTarget()
298 {
299     if (!enabled() || !m_target)
300         return;
301 
302     RefPtr<Node> element = m_target;
303     hide();
304 
305     // Because the deletion UI only appears when the selection is entirely
306     // within the target, we unconditionally update the selection to be
307     // a caret where the target had been.
308     Position pos = positionBeforeNode(element.get());
309     applyCommand(RemoveNodeCommand::create(element.release()));
310     m_frame->selection()->setSelection(VisiblePosition(pos));
311 }
312 
313 } // namespace WebCore
314