WebCore/editing/DeleteButtonController.cpp
changeset 0 4f2f89ce4247
equal deleted inserted replaced
-1:000000000000 0:4f2f89ce4247
       
     1 /*
       
     2  * Copyright (C) 2006, 2008, 2009 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 
       
    56 DeleteButtonController::DeleteButtonController(Frame* frame)
       
    57     : m_frame(frame)
       
    58     , m_wasStaticPositioned(false)
       
    59     , m_wasAutoZIndex(false)
       
    60     , m_disableStack(0)
       
    61 {
       
    62 }
       
    63 
       
    64 static bool isDeletableElement(const Node* node)
       
    65 {
       
    66     if (!node || !node->isHTMLElement() || !node->inDocument() || !node->isContentEditable())
       
    67         return false;
       
    68 
       
    69     // In general we want to only draw the UI around object of a certain area, but we still keep the min width/height to
       
    70     // make sure we don't end up with very thin or very short elements getting the UI.
       
    71     const int minimumArea = 2500;
       
    72     const int minimumWidth = 48;
       
    73     const int minimumHeight = 16;
       
    74     const unsigned minimumVisibleBorders = 1;
       
    75 
       
    76     RenderObject* renderer = node->renderer();
       
    77     if (!renderer || !renderer->isBox())
       
    78         return false;
       
    79 
       
    80     // Disallow the body element since it isn't practical to delete, and the deletion UI would be clipped.
       
    81     if (node->hasTagName(bodyTag))
       
    82         return false;
       
    83 
       
    84     // Disallow elements with any overflow clip, since the deletion UI would be clipped as well. <rdar://problem/6840161>
       
    85     if (renderer->hasOverflowClip())
       
    86         return false;
       
    87 
       
    88     // Disallow Mail blockquotes since the deletion UI would get in the way of editing for these.
       
    89     if (isMailBlockquote(node))
       
    90         return false;
       
    91 
       
    92     RenderBox* box = toRenderBox(renderer);
       
    93     IntRect borderBoundingBox = box->borderBoundingBox();
       
    94     if (borderBoundingBox.width() < minimumWidth || borderBoundingBox.height() < minimumHeight)
       
    95         return false;
       
    96 
       
    97     if ((borderBoundingBox.width() * borderBoundingBox.height()) < minimumArea)
       
    98         return false;
       
    99 
       
   100     if (renderer->isTable())
       
   101         return true;
       
   102 
       
   103     if (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(iframeTag))
       
   104         return true;
       
   105 
       
   106     if (renderer->isPositioned())
       
   107         return true;
       
   108 
       
   109     if (renderer->isRenderBlock() && !renderer->isTableCell()) {
       
   110         RenderStyle* style = renderer->style();
       
   111         if (!style)
       
   112             return false;
       
   113 
       
   114         // Allow blocks that have background images
       
   115         if (style->hasBackgroundImage() && style->backgroundImage()->canRender(1.0f))
       
   116             return true;
       
   117 
       
   118         // Allow blocks with a minimum number of non-transparent borders
       
   119         unsigned visibleBorders = style->borderTop().isVisible() + style->borderBottom().isVisible() + style->borderLeft().isVisible() + style->borderRight().isVisible();
       
   120         if (visibleBorders >= minimumVisibleBorders)
       
   121             return true;
       
   122 
       
   123         // Allow blocks that have a different background from it's parent
       
   124         Node* parentNode = node->parentNode();
       
   125         if (!parentNode)
       
   126             return false;
       
   127 
       
   128         RenderObject* parentRenderer = parentNode->renderer();
       
   129         if (!parentRenderer)
       
   130             return false;
       
   131 
       
   132         RenderStyle* parentStyle = parentRenderer->style();
       
   133         if (!parentStyle)
       
   134             return false;
       
   135 
       
   136         if (renderer->hasBackground() && (!parentRenderer->hasBackground() || style->visitedDependentColor(CSSPropertyBackgroundColor) != parentStyle->visitedDependentColor(CSSPropertyBackgroundColor)))
       
   137             return true;
       
   138     }
       
   139 
       
   140     return false;
       
   141 }
       
   142 
       
   143 static HTMLElement* enclosingDeletableElement(const VisibleSelection& selection)
       
   144 {
       
   145     if (!selection.isContentEditable())
       
   146         return 0;
       
   147 
       
   148     RefPtr<Range> range = selection.toNormalizedRange();
       
   149     if (!range)
       
   150         return 0;
       
   151 
       
   152     ExceptionCode ec = 0;
       
   153     Node* container = range->commonAncestorContainer(ec);
       
   154     ASSERT(container);
       
   155     ASSERT(ec == 0);
       
   156 
       
   157     // The enclosingNodeOfType function only works on nodes that are editable
       
   158     // (which is strange, given its name).
       
   159     if (!container->isContentEditable())
       
   160         return 0;
       
   161 
       
   162     Node* element = enclosingNodeOfType(Position(container, 0), &isDeletableElement);
       
   163     if (!element)
       
   164         return 0;
       
   165 
       
   166     ASSERT(element->isHTMLElement());
       
   167     return static_cast<HTMLElement*>(element);
       
   168 }
       
   169 
       
   170 void DeleteButtonController::respondToChangedSelection(const VisibleSelection& oldSelection)
       
   171 {
       
   172     if (!enabled())
       
   173         return;
       
   174 
       
   175     HTMLElement* oldElement = enclosingDeletableElement(oldSelection);
       
   176     HTMLElement* newElement = enclosingDeletableElement(m_frame->selection()->selection());
       
   177     if (oldElement == newElement)
       
   178         return;
       
   179 
       
   180     // If the base is inside a deletable element, give the element a delete widget.
       
   181     if (newElement)
       
   182         show(newElement);
       
   183     else
       
   184         hide();
       
   185 }
       
   186 
       
   187 void DeleteButtonController::createDeletionUI()
       
   188 {
       
   189     RefPtr<HTMLDivElement> container = HTMLDivElement::create(m_target->document());
       
   190     container->setIdAttribute(containerElementIdentifier);
       
   191 
       
   192     CSSMutableStyleDeclaration* style = container->getInlineStyleDecl();
       
   193     style->setProperty(CSSPropertyWebkitUserDrag, CSSValueNone);
       
   194     style->setProperty(CSSPropertyWebkitUserSelect, CSSValueNone);
       
   195     style->setProperty(CSSPropertyWebkitUserModify, CSSValueNone);
       
   196     style->setProperty(CSSPropertyVisibility, CSSValueHidden);
       
   197     style->setProperty(CSSPropertyPosition, CSSValueAbsolute);
       
   198     style->setProperty(CSSPropertyCursor, CSSValueDefault);
       
   199     style->setProperty(CSSPropertyTop, "0");
       
   200     style->setProperty(CSSPropertyRight, "0");
       
   201     style->setProperty(CSSPropertyBottom, "0");
       
   202     style->setProperty(CSSPropertyLeft, "0");
       
   203 
       
   204     RefPtr<HTMLDivElement> outline = HTMLDivElement::create(m_target->document());
       
   205     outline->setIdAttribute(outlineElementIdentifier);
       
   206 
       
   207     const int borderWidth = 4;
       
   208     const int borderRadius = 6;
       
   209 
       
   210     style = outline->getInlineStyleDecl();
       
   211     style->setProperty(CSSPropertyPosition, CSSValueAbsolute);
       
   212     style->setProperty(CSSPropertyZIndex, String::number(-1000000));
       
   213     style->setProperty(CSSPropertyTop, String::number(-borderWidth - m_target->renderBox()->borderTop()) + "px");
       
   214     style->setProperty(CSSPropertyRight, String::number(-borderWidth - m_target->renderBox()->borderRight()) + "px");
       
   215     style->setProperty(CSSPropertyBottom, String::number(-borderWidth - m_target->renderBox()->borderBottom()) + "px");
       
   216     style->setProperty(CSSPropertyLeft, String::number(-borderWidth - m_target->renderBox()->borderLeft()) + "px");
       
   217     style->setProperty(CSSPropertyBorder, String::number(borderWidth) + "px solid rgba(0, 0, 0, 0.6)");
       
   218     style->setProperty(CSSPropertyWebkitBorderRadius, String::number(borderRadius) + "px");
       
   219     style->setProperty(CSSPropertyVisibility, CSSValueVisible);
       
   220 
       
   221     ExceptionCode ec = 0;
       
   222     container->appendChild(outline.get(), ec);
       
   223     ASSERT(ec == 0);
       
   224     if (ec)
       
   225         return;
       
   226 
       
   227     RefPtr<DeleteButton> button = DeleteButton::create(m_target->document());
       
   228     button->setIdAttribute(buttonElementIdentifier);
       
   229 
       
   230     const int buttonWidth = 30;
       
   231     const int buttonHeight = 30;
       
   232     const int buttonBottomShadowOffset = 2;
       
   233 
       
   234     style = button->getInlineStyleDecl();
       
   235     style->setProperty(CSSPropertyPosition, CSSValueAbsolute);
       
   236     style->setProperty(CSSPropertyZIndex, String::number(1000000));
       
   237     style->setProperty(CSSPropertyTop, String::number((-buttonHeight / 2) - m_target->renderBox()->borderTop() - (borderWidth / 2) + buttonBottomShadowOffset) + "px");
       
   238     style->setProperty(CSSPropertyLeft, String::number((-buttonWidth / 2) - m_target->renderBox()->borderLeft() - (borderWidth / 2)) + "px");
       
   239     style->setProperty(CSSPropertyWidth, String::number(buttonWidth) + "px");
       
   240     style->setProperty(CSSPropertyHeight, String::number(buttonHeight) + "px");
       
   241     style->setProperty(CSSPropertyVisibility, CSSValueVisible);
       
   242 
       
   243     RefPtr<Image> buttonImage = Image::loadPlatformResource("deleteButton");
       
   244     if (buttonImage->isNull())
       
   245         return;
       
   246 
       
   247     button->setCachedImage(new CachedImage(buttonImage.get()));
       
   248 
       
   249     container->appendChild(button.get(), ec);
       
   250     ASSERT(ec == 0);
       
   251     if (ec)
       
   252         return;
       
   253 
       
   254     m_containerElement = container.release();
       
   255     m_outlineElement = outline.release();
       
   256     m_buttonElement = button.release();
       
   257 }
       
   258 
       
   259 void DeleteButtonController::show(HTMLElement* element)
       
   260 {
       
   261     hide();
       
   262 
       
   263     if (!enabled() || !element || !element->inDocument() || !isDeletableElement(element))
       
   264         return;
       
   265 
       
   266     if (!m_frame->editor()->shouldShowDeleteInterface(static_cast<HTMLElement*>(element)))
       
   267         return;
       
   268 
       
   269     // we rely on the renderer having current information, so we should update the layout if needed
       
   270     m_frame->document()->updateLayoutIgnorePendingStylesheets();
       
   271 
       
   272     m_target = element;
       
   273 
       
   274     if (!m_containerElement) {
       
   275         createDeletionUI();
       
   276         if (!m_containerElement) {
       
   277             hide();
       
   278             return;
       
   279         }
       
   280     }
       
   281 
       
   282     ExceptionCode ec = 0;
       
   283     m_target->appendChild(m_containerElement.get(), ec);
       
   284     ASSERT(ec == 0);
       
   285     if (ec) {
       
   286         hide();
       
   287         return;
       
   288     }
       
   289 
       
   290     if (m_target->renderer()->style()->position() == StaticPosition) {
       
   291         m_target->getInlineStyleDecl()->setProperty(CSSPropertyPosition, CSSValueRelative);
       
   292         m_wasStaticPositioned = true;
       
   293     }
       
   294 
       
   295     if (m_target->renderer()->style()->hasAutoZIndex()) {
       
   296         m_target->getInlineStyleDecl()->setProperty(CSSPropertyZIndex, "0");
       
   297         m_wasAutoZIndex = true;
       
   298     }
       
   299 }
       
   300 
       
   301 void DeleteButtonController::hide()
       
   302 {
       
   303     m_outlineElement = 0;
       
   304     m_buttonElement = 0;
       
   305 
       
   306     ExceptionCode ec = 0;
       
   307     if (m_containerElement && m_containerElement->parentNode())
       
   308         m_containerElement->parentNode()->removeChild(m_containerElement.get(), ec);
       
   309 
       
   310     if (m_target) {
       
   311         if (m_wasStaticPositioned)
       
   312             m_target->getInlineStyleDecl()->setProperty(CSSPropertyPosition, CSSValueStatic);
       
   313         if (m_wasAutoZIndex)
       
   314             m_target->getInlineStyleDecl()->setProperty(CSSPropertyZIndex, CSSValueAuto);
       
   315     }
       
   316 
       
   317     m_wasStaticPositioned = false;
       
   318     m_wasAutoZIndex = false;
       
   319 }
       
   320 
       
   321 void DeleteButtonController::enable()
       
   322 {
       
   323     ASSERT(m_disableStack > 0);
       
   324     if (m_disableStack > 0)
       
   325         m_disableStack--;
       
   326     if (enabled()) {
       
   327         // Determining if the element is deletable currently depends on style
       
   328         // because whether something is editable depends on style, so we need
       
   329         // to recalculate style before calling enclosingDeletableElement.
       
   330         m_frame->document()->updateStyleIfNeeded();
       
   331         show(enclosingDeletableElement(m_frame->selection()->selection()));
       
   332     }
       
   333 }
       
   334 
       
   335 void DeleteButtonController::disable()
       
   336 {
       
   337     if (enabled())
       
   338         hide();
       
   339     m_disableStack++;
       
   340 }
       
   341 
       
   342 void DeleteButtonController::deleteTarget()
       
   343 {
       
   344     if (!enabled() || !m_target)
       
   345         return;
       
   346 
       
   347     RefPtr<Node> element = m_target;
       
   348     hide();
       
   349 
       
   350     // Because the deletion UI only appears when the selection is entirely
       
   351     // within the target, we unconditionally update the selection to be
       
   352     // a caret where the target had been.
       
   353     Position pos = positionInParentBeforeNode(element.get());
       
   354     applyCommand(RemoveNodeCommand::create(element.release()));
       
   355     m_frame->selection()->setSelection(VisiblePosition(pos));
       
   356 }
       
   357 
       
   358 } // namespace WebCore