2 * Copyright (C) 2006, 2008, 2009 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "DeleteButtonController.h"
29 #include "CachedImage.h"
30 #include "CSSMutableStyleDeclaration.h"
31 #include "CSSPrimitiveValue.h"
32 #include "CSSPropertyNames.h"
33 #include "CSSValueKeywords.h"
34 #include "DeleteButton.h"
38 #include "htmlediting.h"
39 #include "HTMLDivElement.h"
40 #include "HTMLNames.h"
44 #include "RemoveNodeCommand.h"
45 #include "RenderBox.h"
46 #include "SelectionController.h"
50 using namespace HTMLNames;
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";
56 DeleteButtonController::DeleteButtonController(Frame* frame)
58 , m_wasStaticPositioned(false)
59 , m_wasAutoZIndex(false)
64 static bool isDeletableElement(const Node* node)
66 if (!node || !node->isHTMLElement() || !node->inDocument() || !node->isContentEditable())
69 const int minimumWidth = 25;
70 const int minimumHeight = 25;
71 const unsigned minimumVisibleBorders = 3;
73 RenderObject* renderer = node->renderer();
74 if (!renderer || !renderer->isBox())
77 RenderBox* box = toRenderBox(renderer);
78 IntRect borderBoundingBox = box->borderBoundingBox();
79 if (borderBoundingBox.width() < minimumWidth || borderBoundingBox.height() < minimumHeight)
82 if (renderer->isTable())
85 if (node->hasTagName(ulTag) || node->hasTagName(olTag))
88 if (renderer->isPositioned())
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)
104 static HTMLElement* enclosingDeletableElement(const VisibleSelection& selection)
106 if (!selection.isContentEditable())
109 RefPtr<Range> range = selection.toNormalizedRange();
113 ExceptionCode ec = 0;
114 Node* container = range->commonAncestorContainer(ec);
118 // The enclosingNodeOfType function only works on nodes that are editable
119 // (which is strange, given its name).
120 if (!container->isContentEditable())
123 Node* element = enclosingNodeOfType(Position(container, 0), &isDeletableElement);
127 ASSERT(element->isHTMLElement());
128 return static_cast<HTMLElement*>(element);
131 void DeleteButtonController::respondToChangedSelection(const VisibleSelection& oldSelection)
136 HTMLElement* oldElement = enclosingDeletableElement(oldSelection);
137 HTMLElement* newElement = enclosingDeletableElement(m_frame->selection()->selection());
138 if (oldElement == newElement)
141 // If the base is inside a deletable element, give the element a delete widget.
148 void DeleteButtonController::createDeletionUI()
150 RefPtr<HTMLDivElement> container = new HTMLDivElement(divTag, m_target->document());
151 container->setId(containerElementIdentifier);
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");
165 RefPtr<HTMLDivElement> outline = new HTMLDivElement(divTag, m_target->document());
166 outline->setId(outlineElementIdentifier);
168 const int borderWidth = 4;
169 const int borderRadius = 6;
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);
182 ExceptionCode ec = 0;
183 container->appendChild(outline.get(), ec);
188 RefPtr<DeleteButton> button = new DeleteButton(m_target->document());
189 button->setId(buttonElementIdentifier);
191 const int buttonWidth = 30;
192 const int buttonHeight = 30;
193 const int buttonBottomShadowOffset = 2;
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);
204 RefPtr<Image> buttonImage = Image::loadPlatformResource("deleteButton");
205 if (buttonImage->isNull())
208 button->setCachedImage(new CachedImage(buttonImage.get()));
210 container->appendChild(button.get(), ec);
215 m_containerElement = container.release();
216 m_outlineElement = outline.release();
217 m_buttonElement = button.release();
220 void DeleteButtonController::show(HTMLElement* element)
224 if (!enabled() || !element || !element->inDocument() || !isDeletableElement(element))
227 if (!m_frame->editor()->shouldShowDeleteInterface(static_cast<HTMLElement*>(element)))
230 // we rely on the renderer having current information, so we should update the layout if needed
231 m_frame->document()->updateLayoutIgnorePendingStylesheets();
235 if (!m_containerElement) {
237 if (!m_containerElement) {
243 ExceptionCode ec = 0;
244 m_target->appendChild(m_containerElement.get(), ec);
251 if (m_target->renderer()->style()->position() == StaticPosition) {
252 m_target->getInlineStyleDecl()->setProperty(CSSPropertyPosition, CSSValueRelative);
253 m_wasStaticPositioned = true;
256 if (m_target->renderer()->style()->hasAutoZIndex()) {
257 m_target->getInlineStyleDecl()->setProperty(CSSPropertyZIndex, "0");
258 m_wasAutoZIndex = true;
262 void DeleteButtonController::hide()
264 m_outlineElement = 0;
267 ExceptionCode ec = 0;
268 if (m_containerElement && m_containerElement->parentNode())
269 m_containerElement->parentNode()->removeChild(m_containerElement.get(), ec);
272 if (m_wasStaticPositioned)
273 m_target->getInlineStyleDecl()->setProperty(CSSPropertyPosition, CSSValueStatic);
275 m_target->getInlineStyleDecl()->setProperty(CSSPropertyZIndex, CSSValueAuto);
278 m_wasStaticPositioned = false;
279 m_wasAutoZIndex = false;
282 void DeleteButtonController::enable()
284 ASSERT(m_disableStack > 0);
285 if (m_disableStack > 0)
288 // Determining if the element is deletable currently depends on style
289 // because whether something is editable depends on style, so we need
290 // to recalculate style before calling enclosingDeletableElement.
291 m_frame->document()->updateRendering();
292 show(enclosingDeletableElement(m_frame->selection()->selection()));
296 void DeleteButtonController::disable()
303 void DeleteButtonController::deleteTarget()
305 if (!enabled() || !m_target)
308 RefPtr<Node> element = m_target;
311 // Because the deletion UI only appears when the selection is entirely
312 // within the target, we unconditionally update the selection to be
313 // a caret where the target had been.
314 Position pos = positionBeforeNode(element.get());
315 applyCommand(RemoveNodeCommand::create(element.release()));
316 m_frame->selection()->setSelection(VisiblePosition(pos));
319 } // namespace WebCore