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 "CSSPrimitiveValue.h"
31 #include "CSSPropertyNames.h"
32 #include "CSSValueKeywords.h"
33 #include "CompositeEditCommand.h"
34 #include "DeleteButton.h"
38 #include "FrameSelection.h"
39 #include "htmlediting.h"
40 #include "HTMLDivElement.h"
41 #include "HTMLNames.h"
46 #include "RemoveNodeCommand.h"
47 #include "RenderBox.h"
48 #include "StylePropertySet.h"
52 using namespace HTMLNames;
54 const char* const DeleteButtonController::containerElementIdentifier = "WebKit-Editing-Delete-Container";
55 const char* const DeleteButtonController::buttonElementIdentifier = "WebKit-Editing-Delete-Button";
56 const char* const DeleteButtonController::outlineElementIdentifier = "WebKit-Editing-Delete-Outline";
58 DeleteButtonController::DeleteButtonController(Frame* frame)
60 , m_wasStaticPositioned(false)
61 , m_wasAutoZIndex(false)
66 static bool isDeletableElement(const Node* node)
68 if (!node || !node->isHTMLElement() || !node->inDocument() || !node->rendererIsEditable())
71 // In general we want to only draw the UI around object of a certain area, but we still keep the min width/height to
72 // make sure we don't end up with very thin or very short elements getting the UI.
73 const int minimumArea = 2500;
74 const int minimumWidth = 48;
75 const int minimumHeight = 16;
76 const unsigned minimumVisibleBorders = 1;
78 RenderObject* renderer = node->renderer();
79 if (!renderer || !renderer->isBox())
82 // Disallow the body element since it isn't practical to delete, and the deletion UI would be clipped.
83 if (node->hasTagName(bodyTag))
86 // Disallow elements with any overflow clip, since the deletion UI would be clipped as well. <rdar://problem/6840161>
87 if (renderer->hasOverflowClip())
90 // Disallow Mail blockquotes since the deletion UI would get in the way of editing for these.
91 if (isMailBlockquote(node))
94 RenderBox* box = toRenderBox(renderer);
95 IntRect borderBoundingBox = box->borderBoundingBox();
96 if (borderBoundingBox.width() < minimumWidth || borderBoundingBox.height() < minimumHeight)
99 if ((borderBoundingBox.width() * borderBoundingBox.height()) < minimumArea)
102 if (renderer->isTable())
105 if (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(iframeTag))
108 if (renderer->isOutOfFlowPositioned())
111 if (renderer->isRenderBlock() && !renderer->isTableCell()) {
112 RenderStyle* style = renderer->style();
116 // Allow blocks that have background images
117 if (style->hasBackgroundImage()) {
118 for (const FillLayer* background = style->backgroundLayers(); background; background = background->next()) {
119 if (background->image() && background->image()->canRender(renderer, 1))
124 // Allow blocks with a minimum number of non-transparent borders
125 unsigned visibleBorders = style->borderTop().isVisible() + style->borderBottom().isVisible() + style->borderLeft().isVisible() + style->borderRight().isVisible();
126 if (visibleBorders >= minimumVisibleBorders)
129 // Allow blocks that have a different background from it's parent
130 ContainerNode* parentNode = node->parentNode();
134 RenderObject* parentRenderer = parentNode->renderer();
138 RenderStyle* parentStyle = parentRenderer->style();
142 if (renderer->hasBackground() && (!parentRenderer->hasBackground() || style->visitedDependentColor(CSSPropertyBackgroundColor) != parentStyle->visitedDependentColor(CSSPropertyBackgroundColor)))
149 static HTMLElement* enclosingDeletableElement(const VisibleSelection& selection)
151 if (!selection.isContentEditable())
154 RefPtr<Range> range = selection.toNormalizedRange();
158 ExceptionCode ec = 0;
159 Node* container = range->commonAncestorContainer(ec);
163 // The enclosingNodeOfType function only works on nodes that are editable
164 // (which is strange, given its name).
165 if (!container->rendererIsEditable())
168 Node* element = enclosingNodeOfType(firstPositionInNode(container), &isDeletableElement);
169 return element && element->isHTMLElement() ? toHTMLElement(element) : 0;
172 void DeleteButtonController::respondToChangedSelection(const VisibleSelection& oldSelection)
177 HTMLElement* oldElement = enclosingDeletableElement(oldSelection);
178 HTMLElement* newElement = enclosingDeletableElement(m_frame->selection()->selection());
179 if (oldElement == newElement)
182 // If the base is inside a deletable element, give the element a delete widget.
189 void DeleteButtonController::deviceScaleFactorChanged()
194 HTMLElement* currentTarget = m_target.get();
197 // Setting m_containerElement to 0 will force the deletionUI to be re-created with
198 // artwork of the appropriate resolution in show().
199 m_containerElement = 0;
203 void DeleteButtonController::createDeletionUI()
205 RefPtr<HTMLDivElement> container = HTMLDivElement::create(m_target->document());
206 container->setIdAttribute(containerElementIdentifier);
208 container->setInlineStyleProperty(CSSPropertyWebkitUserDrag, CSSValueNone);
209 container->setInlineStyleProperty(CSSPropertyWebkitUserSelect, CSSValueNone);
210 container->setInlineStyleProperty(CSSPropertyWebkitUserModify, CSSValueReadOnly);
211 container->setInlineStyleProperty(CSSPropertyVisibility, CSSValueHidden);
212 container->setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute);
213 container->setInlineStyleProperty(CSSPropertyCursor, CSSValueDefault);
214 container->setInlineStyleProperty(CSSPropertyTop, "0");
215 container->setInlineStyleProperty(CSSPropertyRight, "0");
216 container->setInlineStyleProperty(CSSPropertyBottom, "0");
217 container->setInlineStyleProperty(CSSPropertyLeft, "0");
219 RefPtr<HTMLDivElement> outline = HTMLDivElement::create(m_target->document());
220 outline->setIdAttribute(outlineElementIdentifier);
222 const int borderWidth = 4;
223 const int borderRadius = 6;
225 outline->setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute);
226 outline->setInlineStyleProperty(CSSPropertyZIndex, String::number(-1000000));
227 outline->setInlineStyleProperty(CSSPropertyTop, String::number(-borderWidth - m_target->renderBox()->borderTop()) + "px");
228 outline->setInlineStyleProperty(CSSPropertyRight, String::number(-borderWidth - m_target->renderBox()->borderRight()) + "px");
229 outline->setInlineStyleProperty(CSSPropertyBottom, String::number(-borderWidth - m_target->renderBox()->borderBottom()) + "px");
230 outline->setInlineStyleProperty(CSSPropertyLeft, String::number(-borderWidth - m_target->renderBox()->borderLeft()) + "px");
231 outline->setInlineStyleProperty(CSSPropertyBorder, String::number(borderWidth) + "px solid rgba(0, 0, 0, 0.6)");
232 outline->setInlineStyleProperty(CSSPropertyWebkitBorderRadius, String::number(borderRadius) + "px");
233 outline->setInlineStyleProperty(CSSPropertyVisibility, CSSValueVisible);
235 ExceptionCode ec = 0;
236 container->appendChild(outline.get(), ec);
241 RefPtr<DeleteButton> button = DeleteButton::create(m_target->document());
242 button->setIdAttribute(buttonElementIdentifier);
244 const int buttonWidth = 30;
245 const int buttonHeight = 30;
246 const int buttonBottomShadowOffset = 2;
248 button->setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute);
249 button->setInlineStyleProperty(CSSPropertyZIndex, String::number(1000000));
250 button->setInlineStyleProperty(CSSPropertyTop, String::number((-buttonHeight / 2) - m_target->renderBox()->borderTop() - (borderWidth / 2) + buttonBottomShadowOffset) + "px");
251 button->setInlineStyleProperty(CSSPropertyLeft, String::number((-buttonWidth / 2) - m_target->renderBox()->borderLeft() - (borderWidth / 2)) + "px");
252 button->setInlineStyleProperty(CSSPropertyWidth, String::number(buttonWidth) + "px");
253 button->setInlineStyleProperty(CSSPropertyHeight, String::number(buttonHeight) + "px");
254 button->setInlineStyleProperty(CSSPropertyVisibility, CSSValueVisible);
256 float deviceScaleFactor = WebCore::deviceScaleFactor(m_frame);
257 RefPtr<Image> buttonImage;
258 if (deviceScaleFactor >= 2)
259 buttonImage = Image::loadPlatformResource("deleteButton@2x");
261 buttonImage = Image::loadPlatformResource("deleteButton");
263 if (buttonImage->isNull())
266 button->setCachedImage(new CachedImage(buttonImage.get()));
268 container->appendChild(button.get(), ec);
273 m_containerElement = container.release();
274 m_outlineElement = outline.release();
275 m_buttonElement = button.release();
278 void DeleteButtonController::show(HTMLElement* element)
282 if (!enabled() || !element || !element->inDocument() || !isDeletableElement(element))
285 if (!m_frame->editor()->shouldShowDeleteInterface(element))
288 // we rely on the renderer having current information, so we should update the layout if needed
289 m_frame->document()->updateLayoutIgnorePendingStylesheets();
293 if (!m_containerElement) {
295 if (!m_containerElement) {
301 ExceptionCode ec = 0;
302 m_target->appendChild(m_containerElement.get(), ec);
309 if (m_target->renderer()->style()->position() == StaticPosition) {
310 m_target->setInlineStyleProperty(CSSPropertyPosition, CSSValueRelative);
311 m_wasStaticPositioned = true;
314 if (m_target->renderer()->style()->hasAutoZIndex()) {
315 m_target->setInlineStyleProperty(CSSPropertyZIndex, "0");
316 m_wasAutoZIndex = true;
320 void DeleteButtonController::hide()
322 m_outlineElement = 0;
325 ExceptionCode ec = 0;
326 if (m_containerElement && m_containerElement->parentNode())
327 m_containerElement->parentNode()->removeChild(m_containerElement.get(), ec);
330 if (m_wasStaticPositioned)
331 m_target->setInlineStyleProperty(CSSPropertyPosition, CSSValueStatic);
333 m_target->setInlineStyleProperty(CSSPropertyZIndex, CSSValueAuto);
336 m_wasStaticPositioned = false;
337 m_wasAutoZIndex = false;
340 void DeleteButtonController::enable()
342 ASSERT(m_disableStack > 0);
343 if (m_disableStack > 0)
346 // Determining if the element is deletable currently depends on style
347 // because whether something is editable depends on style, so we need
348 // to recalculate style before calling enclosingDeletableElement.
349 m_frame->document()->updateStyleIfNeeded();
350 show(enclosingDeletableElement(m_frame->selection()->selection()));
354 void DeleteButtonController::disable()
361 class RemoveTargetCommand : public CompositeEditCommand {
363 static PassRefPtr<RemoveTargetCommand> create(Document* document, PassRefPtr<Node> target)
365 return adoptRef(new RemoveTargetCommand(document, target));
369 RemoveTargetCommand(Document* document, PassRefPtr<Node> target)
370 : CompositeEditCommand(document)
376 removeNode(m_target);
380 RefPtr<Node> m_target;
383 void DeleteButtonController::deleteTarget()
385 if (!enabled() || !m_target)
390 // Because the deletion UI only appears when the selection is entirely
391 // within the target, we unconditionally update the selection to be
392 // a caret where the target had been.
393 Position pos = positionInParentBeforeNode(m_target.get());
394 applyCommand(RemoveTargetCommand::create(m_frame->document(), m_target));
395 m_frame->selection()->setSelection(VisiblePosition(pos));
398 } // namespace WebCore