2011-06-22 Dimitri Glazkov <dglazkov@chromium.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     ASSERT(input->files());
61     requestIcon(input->files()->filenames());
62 }
63
64 RenderFileUploadControl::~RenderFileUploadControl()
65 {
66 }
67
68 void RenderFileUploadControl::requestIcon(const Vector<String>& filenames)
69 {
70     if (!filenames.size())
71         return;
72
73     if (Chrome* chrome = this->chrome())
74         chrome->loadIconForFiles(filenames, newFileIconLoader());
75 }
76
77 void RenderFileUploadControl::filesChosen(const Vector<String>& filenames)
78 {
79     HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node());
80     inputElement->setFileListFromRenderer(filenames);
81     requestIcon(filenames);
82
83     repaint();
84     // This call may cause destruction of this instance and thus must always be last in the function.
85     inputElement->dispatchFormControlChangeEvent();
86 }
87
88 #if ENABLE(DIRECTORY_UPLOAD)
89 void RenderFileUploadControl::receiveDropForDirectoryUpload(const Vector<String>& paths)
90 {
91     if (Chrome* chrome = this->chrome()) {
92         FileChooserSettings settings;
93         settings.allowsDirectoryUpload = true;
94         settings.allowsMultipleFiles = true;
95         settings.selectedFiles.append(paths[0]);
96         chrome->enumerateChosenDirectory(newFileChooser(settings));
97     }
98 }
99 #endif
100
101
102 void RenderFileUploadControl::updateRendering(PassRefPtr<Icon> icon)
103 {
104     if (m_icon == icon)
105         return;
106
107     m_icon = icon;
108     repaint();
109 }
110
111 void RenderFileUploadControl::click()
112 {
113     if (!ScriptController::processingUserGesture())
114         return;
115
116     if (Chrome* chrome = this->chrome()) {
117         FileChooserSettings settings;
118         HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
119 #if ENABLE(DIRECTORY_UPLOAD)
120         settings.allowsDirectoryUpload = input->fastHasAttribute(webkitdirectoryAttr);
121         settings.allowsMultipleFiles = settings.allowsDirectoryUpload || input->fastHasAttribute(multipleAttr);
122 #else
123         settings.allowsMultipleFiles = input->fastHasAttribute(multipleAttr);
124 #endif
125         settings.acceptTypes = input->accept();
126         ASSERT(input->files());
127         settings.selectedFiles = input->files()->filenames();
128         chrome->runOpenPanel(frame(), newFileChooser(settings));
129     }
130 }
131
132 Chrome* RenderFileUploadControl::chrome() const
133 {
134     Frame* frame = node()->document()->frame();
135     if (!frame)
136         return 0;
137     Page* page = frame->page();
138     if (!page)
139         return 0;
140     return page->chrome();
141 }
142
143 void RenderFileUploadControl::updateFromElement()
144 {
145     HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node());
146     ASSERT(inputElement->isFileUpload());
147
148
149     if (HTMLInputElement* button = uploadButton())
150         button->setDisabled(!theme()->isEnabled(this));
151
152     // This only supports clearing out the files, but that's OK because for
153     // security reasons that's the only change the DOM is allowed to make.
154     FileList* files = inputElement->files();
155     ASSERT(files);
156     if (files && files->isEmpty() && m_icon) {
157         m_icon = 0;
158         repaint();
159     }
160 }
161
162 static int nodeWidth(Node* node)
163 {
164     return node ? node->renderBox()->width() : 0;
165 }
166
167 int RenderFileUploadControl::maxFilenameWidth() const
168 {
169     return max(0, contentWidth() - nodeWidth(uploadButton()) - afterButtonSpacing
170         - (m_icon ? iconWidth + iconFilenameSpacing : 0));
171 }
172
173 void RenderFileUploadControl::paintObject(PaintInfo& paintInfo, const IntPoint& paintOffset)
174 {
175     if (style()->visibility() != VISIBLE)
176         return;
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_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_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_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     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
296 #if ENABLE(DIRECTORY_UPLOAD)
297     if (input->fastHasAttribute(webkitdirectoryAttr)) {
298         receiveDropForDirectoryUpload(paths);
299         return;
300     }
301 #endif
302
303     if (input->fastHasAttribute(multipleAttr))
304         filesChosen(paths);
305     else {
306         Vector<String> firstPathOnly;
307         firstPathOnly.append(paths[0]);
308         filesChosen(firstPathOnly);
309     }
310 }
311
312 String RenderFileUploadControl::buttonValue()
313 {
314     if (HTMLInputElement* button = uploadButton())
315         return button->value();
316     
317     return String();
318 }
319
320 String RenderFileUploadControl::fileTextValue() const
321 {
322     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
323     ASSERT(input->files());
324     return theme()->fileListNameForWidth(input->files()->filenames(), style()->font(), maxFilenameWidth());
325 }
326     
327 } // namespace WebCore