2011-06-12 Adam Barth <abarth@webkit.org>
[WebKit-https.git] / Source / WebCore / rendering / RenderFileUploadControl.cpp
1 /*
2  * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  *
19  */
20
21 #include "config.h"
22 #include "RenderFileUploadControl.h"
23
24 #include "Chrome.h"
25 #include "FileList.h"
26 #include "Frame.h"
27 #include "FrameView.h"
28 #include "GraphicsContext.h"
29 #include "HTMLInputElement.h"
30 #include "HTMLNames.h"
31 #include "Icon.h"
32 #include "LocalizedStrings.h"
33 #include "Page.h"
34 #include "PaintInfo.h"
35 #include "RenderButton.h"
36 #include "RenderText.h"
37 #include "RenderTheme.h"
38 #include "RenderView.h"
39 #include "ScriptController.h"
40 #include "ShadowRoot.h"
41 #include "TextRun.h"
42 #include <math.h>
43
44 using namespace std;
45
46 namespace WebCore {
47
48 using namespace HTMLNames;
49
50 const int afterButtonSpacing = 4;
51 const int iconHeight = 16;
52 const int iconWidth = 16;
53 const int iconFilenameSpacing = 2;
54 const int defaultWidthNumChars = 34;
55 const int buttonShadowHeight = 2;
56
57 RenderFileUploadControl::RenderFileUploadControl(HTMLInputElement* input)
58     : RenderBlock(input)
59 {
60     FileList* list = input->files();
61     Vector<String> filenames;
62     unsigned length = list ? list->length() : 0;
63     for (unsigned i = 0; i < length; ++i)
64         filenames.append(list->item(i)->path());
65     m_fileChooser = FileChooser::create(this, filenames);
66 }
67
68 RenderFileUploadControl::~RenderFileUploadControl()
69 {
70     m_fileChooser->disconnectClient();
71 }
72
73 void RenderFileUploadControl::valueChanged()
74 {
75     // dispatchFormControlChangeEvent may destroy this renderer
76     RefPtr<FileChooser> fileChooser = m_fileChooser;
77
78     HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node());
79     inputElement->setFileListFromRenderer(fileChooser->filenames());
80     inputElement->dispatchFormControlChangeEvent();
81  
82     // only repaint if it doesn't seem we have been destroyed
83     if (!fileChooser->disconnected())
84         repaint();
85 }
86
87 bool RenderFileUploadControl::allowsMultipleFiles()
88 {
89 #if ENABLE(DIRECTORY_UPLOAD)
90     if (allowsDirectoryUpload())
91       return true;
92 #endif
93
94     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
95     return input->fastHasAttribute(multipleAttr);
96 }
97
98 #if ENABLE(DIRECTORY_UPLOAD)
99 bool RenderFileUploadControl::allowsDirectoryUpload()
100 {
101     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
102     return input->fastHasAttribute(webkitdirectoryAttr);
103 }
104
105 void RenderFileUploadControl::receiveDropForDirectoryUpload(const Vector<String>& paths)
106 {
107     if (Chrome* chromePointer = chrome())
108         chromePointer->enumerateChosenDirectory(paths[0], m_fileChooser.get());
109 }
110 #endif
111
112 String RenderFileUploadControl::acceptTypes()
113 {
114     return static_cast<HTMLInputElement*>(node())->accept();
115 }
116
117 void RenderFileUploadControl::chooseIconForFiles(FileChooser* chooser, const Vector<String>& filenames)
118 {
119     if (Chrome* chromePointer = chrome())
120         chromePointer->chooseIconForFiles(filenames, chooser);
121 }
122
123 void RenderFileUploadControl::click()
124 {
125     if (!ScriptController::processingUserGesture())
126         return;
127     if (Chrome* chromePointer = chrome())
128         chromePointer->runOpenPanel(frame(), m_fileChooser);
129 }
130
131 Chrome* RenderFileUploadControl::chrome() const
132 {
133     Frame* frame = node()->document()->frame();
134     if (!frame)
135         return 0;
136     Page* page = frame->page();
137     if (!page)
138         return 0;
139     return page->chrome();
140 }
141
142 void RenderFileUploadControl::updateFromElement()
143 {
144     HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node());
145     ASSERT(inputElement->isFileUpload());
146
147
148     if (HTMLInputElement* button = uploadButton())
149         button->setDisabled(!theme()->isEnabled(this));
150
151     // This only supports clearing out the files, but that's OK because for
152     // security reasons that's the only change the DOM is allowed to make.
153     FileList* files = inputElement->files();
154     ASSERT(files);
155     if (files && files->isEmpty() && !m_fileChooser->filenames().isEmpty()) {
156         m_fileChooser->clear();
157         repaint();
158     }
159 }
160
161 static int nodeWidth(Node* node)
162 {
163     return node ? node->renderBox()->width() : 0;
164 }
165
166 int RenderFileUploadControl::maxFilenameWidth() const
167 {
168     return max(0, contentWidth() - nodeWidth(uploadButton()) - afterButtonSpacing
169         - (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0));
170 }
171
172 void RenderFileUploadControl::paintObject(PaintInfo& paintInfo, const IntPoint& paintOffset)
173 {
174     if (style()->visibility() != VISIBLE)
175         return;
176     ASSERT(m_fileChooser);
177     
178     // Push a clip.
179     GraphicsContextStateSaver stateSaver(*paintInfo.context, false);
180     if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) {
181         IntRect clipRect(paintOffset.x() + borderLeft(), paintOffset.y() + borderTop(),
182                          width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop() + buttonShadowHeight);
183         if (clipRect.isEmpty())
184             return;
185         stateSaver.save();
186         paintInfo.context->clip(clipRect);
187     }
188
189     if (paintInfo.phase == PaintPhaseForeground) {
190         const String& displayedFilename = fileTextValue();
191         const Font& font = style()->font();
192         TextRun textRun = constructTextRun(this, font, displayedFilename, style(), TextRun::AllowTrailingExpansion, RespectDirection | RespectDirectionOverride);
193
194         // Determine where the filename should be placed
195         int contentLeft = paintOffset.x() + borderLeft() + paddingLeft();
196         HTMLInputElement* button = uploadButton();
197         if (!button)
198             return;
199
200         int buttonWidth = nodeWidth(button);
201         int buttonAndIconWidth = buttonWidth + afterButtonSpacing
202             + (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0);
203         int textX;
204         if (style()->isLeftToRightDirection())
205             textX = contentLeft + buttonAndIconWidth;
206         else
207             textX = contentLeft + contentWidth() - buttonAndIconWidth - font.width(textRun);
208         // We want to match the button's baseline
209         RenderButton* buttonRenderer = toRenderButton(button->renderer());
210         int textY = buttonRenderer->absoluteBoundingBoxRect().y()
211             + buttonRenderer->marginTop() + buttonRenderer->borderTop() + buttonRenderer->paddingTop()
212             + buttonRenderer->baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine);
213
214         paintInfo.context->setFillColor(style()->visitedDependentColor(CSSPropertyColor), style()->colorSpace());
215         
216         // Draw the filename
217         paintInfo.context->drawBidiText(font, textRun, IntPoint(textX, textY));
218         
219         if (m_fileChooser->icon()) {
220             // Determine where the icon should be placed
221             int iconY = paintOffset.y() + borderTop() + paddingTop() + (contentHeight() - iconHeight) / 2;
222             int iconX;
223             if (style()->isLeftToRightDirection())
224                 iconX = contentLeft + buttonWidth + afterButtonSpacing;
225             else
226                 iconX = contentLeft + contentWidth() - buttonWidth - afterButtonSpacing - iconWidth;
227
228             // Draw the file icon
229             m_fileChooser->icon()->paint(paintInfo.context, IntRect(iconX, iconY, iconWidth, iconHeight));
230         }
231     }
232
233     // Paint the children.
234     RenderBlock::paintObject(paintInfo, paintOffset);
235 }
236
237 void RenderFileUploadControl::computePreferredLogicalWidths()
238 {
239     ASSERT(preferredLogicalWidthsDirty());
240
241     m_minPreferredLogicalWidth = 0;
242     m_maxPreferredLogicalWidth = 0;
243
244     RenderStyle* style = this->style();
245     ASSERT(style);
246
247     const Font& font = style->font();
248     if (style->width().isFixed() && style->width().value() > 0)
249         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style->width().value());
250     else {
251         // Figure out how big the filename space needs to be for a given number of characters
252         // (using "0" as the nominal character).
253         const UChar ch = '0';
254         float charWidth = font.width(constructTextRun(this, font, String(&ch, 1), style, TextRun::AllowTrailingExpansion));
255         m_maxPreferredLogicalWidth = (int)ceilf(charWidth * defaultWidthNumChars);
256     }
257
258     if (style->minWidth().isFixed() && style->minWidth().value() > 0) {
259         m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style->minWidth().value()));
260         m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style->minWidth().value()));
261     } else if (style->width().isPercent() || (style->width().isAuto() && style->height().isPercent()))
262         m_minPreferredLogicalWidth = 0;
263     else
264         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth;
265
266     if (style->maxWidth().isFixed() && style->maxWidth().value() != undefinedLength) {
267         m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style->maxWidth().value()));
268         m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style->maxWidth().value()));
269     }
270
271     int toAdd = borderAndPaddingWidth();
272     m_minPreferredLogicalWidth += toAdd;
273     m_maxPreferredLogicalWidth += toAdd;
274
275     setPreferredLogicalWidthsDirty(false);
276 }
277
278 VisiblePosition RenderFileUploadControl::positionForPoint(const IntPoint&)
279 {
280     return VisiblePosition();
281 }
282
283 HTMLInputElement* RenderFileUploadControl::uploadButton() const
284 {
285     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
286
287     ASSERT(input->shadowRoot());
288
289     Node* buttonNode = input->shadowRoot()->firstChild();
290     return buttonNode && buttonNode->isHTMLElement() && buttonNode->hasTagName(inputTag) ? static_cast<HTMLInputElement*>(buttonNode) : 0;
291 }
292
293 void RenderFileUploadControl::receiveDroppedFiles(const Vector<String>& paths)
294 {
295 #if ENABLE(DIRECTORY_UPLOAD)
296     if (allowsDirectoryUpload()) {
297         receiveDropForDirectoryUpload(paths);
298         return;
299     }
300 #endif
301
302     if (allowsMultipleFiles())
303         m_fileChooser->chooseFiles(paths);
304     else
305         m_fileChooser->chooseFile(paths[0]);
306 }
307
308 String RenderFileUploadControl::buttonValue()
309 {
310     if (HTMLInputElement* button = uploadButton())
311         return button->value();
312     
313     return String();
314 }
315
316 String RenderFileUploadControl::fileTextValue() const
317 {
318     return m_fileChooser->basenameForWidth(style()->font(), maxFilenameWidth());
319 }
320     
321 } // namespace WebCore