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