Reviewed by Adam.
[WebKit-https.git] / WebCore / editing / DeleteButtonController.cpp
1 /*
2  * Copyright (C) 2006 Apple Computer, 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 "RenderObject.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 {
61 }
62
63 static HTMLElement* enclosingDeletableTable(const Selection& selection)
64 {
65     if (!selection.isContentEditable())
66         return 0;
67
68     RefPtr<Range> range = selection.toRange();
69     if (!range)
70         return 0;
71
72     ExceptionCode ec = 0;
73     Node* container = range->commonAncestorContainer(ec);
74     ASSERT(container);
75     ASSERT(ec == 0);
76
77     // The enclosingNodeWithTag function only works on nodes that are editable
78     // (which is strange, given its name).
79     if (!container->isContentEditable())
80         return 0;
81
82     Node* table = enclosingNodeWithTag(container, tableTag);
83     if (!table)
84         return 0;
85
86     // The table must be editable too.
87     if (!table->isContentEditable())
88         return 0;
89
90     ASSERT(table->isHTMLElement());
91     return static_cast<HTMLElement*>(table);
92 }
93
94 void DeleteButtonController::respondToChangedSelection(const Selection& oldSelection)
95 {
96     HTMLElement* oldTable = enclosingDeletableTable(oldSelection);
97     HTMLElement* newTable = enclosingDeletableTable(m_frame->selectionController()->selection());
98     if (oldTable == newTable)
99         return;
100
101     // If the base is inside an editable table, give the table a close widget.
102     if (newTable)
103         show(newTable);
104     else
105         hide();
106 }
107
108 void DeleteButtonController::respondToChangedContents()
109 {
110     updateOutlineStyle();
111 }
112
113 void DeleteButtonController::updateOutlineStyle()
114 {
115     if (!m_element || !m_element->renderer() || !m_outlineElement)
116         return;
117
118     CSSMutableStyleDeclaration* style = m_outlineElement->getInlineStyleDecl();
119     style->setProperty(CSS_PROP_WIDTH, String::number(m_element->renderer()->overflowWidth()) + "px");
120     style->setProperty(CSS_PROP_HEIGHT, String::number(m_element->renderer()->overflowHeight()) + "px");
121 }
122
123 void DeleteButtonController::show(HTMLElement* element)
124 {
125     hide();
126
127     if (!element->renderer() || !element->renderer()->isRenderBlock())
128         return;
129
130     if (!m_frame->editor()->shouldShowDeleteInterface(static_cast<HTMLElement*>(element)))
131         return;
132
133     m_element = element;
134
135     m_containerElement = new HTMLDivElement(m_element->document());
136     m_containerElement->setId(containerElementIdentifier);
137
138     CSSMutableStyleDeclaration* style = m_containerElement->getInlineStyleDecl();
139     style->setProperty(CSS_PROP_POSITION, CSS_VAL_ABSOLUTE);
140     style->setProperty(CSS_PROP_TOP, "0px");
141     style->setProperty(CSS_PROP_LEFT, "0px");
142     style->setProperty(CSS_PROP__WEBKIT_USER_DRAG, CSS_VAL_NONE);
143     style->setProperty(CSS_PROP__WEBKIT_USER_SELECT, CSS_VAL_NONE);
144     style->setProperty(CSS_PROP__WEBKIT_USER_MODIFY, CSS_VAL_NONE);
145
146     m_outlineElement = new HTMLDivElement(m_element->document());
147     m_outlineElement->setId(outlineElementIdentifier);
148
149     const int borderWidth = 4;
150     const int borderRadius = 6;
151
152     style = m_outlineElement->getInlineStyleDecl();
153     style->setProperty(CSS_PROP_POSITION, CSS_VAL_ABSOLUTE);
154     style->setProperty(CSS_PROP_CURSOR, CSS_VAL_DEFAULT);
155     style->setProperty(CSS_PROP__WEBKIT_USER_DRAG, CSS_VAL_NONE);
156     style->setProperty(CSS_PROP__WEBKIT_USER_SELECT, CSS_VAL_NONE);
157     style->setProperty(CSS_PROP__WEBKIT_USER_MODIFY, CSS_VAL_NONE);
158     style->setProperty(CSS_PROP_Z_INDEX, String::number(-1));
159     style->setProperty(CSS_PROP_TOP, String::number(-borderWidth) + "px");
160     style->setProperty(CSS_PROP_LEFT, String::number(-borderWidth) + "px");
161     style->setProperty(CSS_PROP_BORDER, String::number(borderWidth) + "px solid rgba(0, 0, 0, 0.6)");
162     style->setProperty(CSS_PROP__WEBKIT_BORDER_RADIUS, String::number(borderRadius) + "px");
163
164     updateOutlineStyle();
165
166     ExceptionCode ec = 0;
167     m_containerElement->appendChild(m_outlineElement.get(), ec);
168     ASSERT(ec == 0);
169     if (ec) {
170         hide();
171         return;
172     }
173
174     m_buttonElement = new DeleteButton(m_element->document());
175     m_buttonElement->setId(buttonElementIdentifier);
176
177     const int buttonWidth = 30;
178     const int buttonHeight = 30;
179
180     style = m_buttonElement->getInlineStyleDecl();
181     style->setProperty(CSS_PROP_POSITION, CSS_VAL_ABSOLUTE);
182     style->setProperty(CSS_PROP_CURSOR, CSS_VAL_DEFAULT);
183     style->setProperty(CSS_PROP__WEBKIT_USER_DRAG, CSS_VAL_NONE);
184     style->setProperty(CSS_PROP__WEBKIT_USER_SELECT, CSS_VAL_NONE);
185     style->setProperty(CSS_PROP__WEBKIT_USER_MODIFY, CSS_VAL_NONE);
186     style->setProperty(CSS_PROP_LEFT, String::number(-buttonWidth / 2) + "px");
187     style->setProperty(CSS_PROP_TOP, String::number(-buttonHeight / 2) + "px");
188     style->setProperty(CSS_PROP_WIDTH, String::number(buttonWidth) + "px");
189     style->setProperty(CSS_PROP_HEIGHT, String::number(buttonHeight) + "px");
190
191     m_buttonElement->setCachedImage(new CachedImage(Image::loadPlatformResource("deleteButton")));
192
193     m_containerElement->appendChild(m_buttonElement.get(), ec);
194     ASSERT(ec == 0);
195     if (ec) {
196         hide();
197         return;
198     }
199
200     m_element->appendChild(m_containerElement.get(), ec);
201     ASSERT(ec == 0);
202     if (ec) {
203         hide();
204         return;
205     }
206
207     if (m_element->renderer()->style()->position() == StaticPosition) {
208         m_element->getInlineStyleDecl()->setProperty(CSS_PROP_POSITION, CSS_VAL_RELATIVE);
209         m_wasStaticPositioned = true;
210     }
211
212     if (m_element->renderer()->style()->hasAutoZIndex()) {
213         m_element->getInlineStyleDecl()->setProperty(CSS_PROP_Z_INDEX, "0");
214         m_wasAutoZIndex = true;
215     }
216
217     m_element->renderer()->repaint(true);
218 }
219
220 void DeleteButtonController::hide()
221 {
222     ExceptionCode ec = 0;
223     if (m_containerElement)
224         m_containerElement->parentNode()->removeChild(m_containerElement.get(), ec);
225
226     if (m_element && m_wasStaticPositioned)
227         m_element->getInlineStyleDecl()->setProperty(CSS_PROP_POSITION, CSS_VAL_STATIC);
228
229     if (m_element && m_wasAutoZIndex)
230         m_element->getInlineStyleDecl()->setProperty(CSS_PROP_Z_INDEX, CSS_VAL_AUTO);
231
232     m_wasStaticPositioned = false;
233     m_wasAutoZIndex = false;
234     m_element = 0;
235     m_outlineElement = 0;
236     m_buttonElement = 0;
237     m_containerElement = 0;
238 }
239
240 void DeleteButtonController::deleteTarget()
241 {
242     if (!m_element)
243         return;
244
245     RefPtr<Node> element = m_element;
246     hide();
247
248     RefPtr<RemoveNodeCommand> command = new RemoveNodeCommand(element.get());
249     command->apply();
250 }
251
252 } // namespace WebCore