2011-06-18 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     , m_iconLoader(FileIconLoader::create(this))
60 {
61     FileList* list = input->files();
62     Vector<String> filenames;
63     unsigned length = list ? list->length() : 0;
64     for (unsigned i = 0; i < length; ++i)
65         filenames.append(list->item(i)->path());
66     m_fileChooser = FileChooser::create(this, filenames);
67     requestIcon(filenames);
68 }
69
70 RenderFileUploadControl::~RenderFileUploadControl()
71 {
72     m_iconLoader->disconnectClient();
73     m_fileChooser->disconnectClient();
74 }
75
76 void RenderFileUploadControl::requestIcon(const Vector<String>& filenames) const
77 {
78     if (!filenames.size())
79         return;
80
81     if (Chrome* chrome = this->chrome())
82         chrome->loadIconForFiles(filenames, m_iconLoader.get());
83 }
84
85 void RenderFileUploadControl::valueChanged()
86 {
87     // dispatchFormControlChangeEvent may destroy this renderer
88     RefPtr<FileChooser> fileChooser = m_fileChooser;
89
90     HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node());
91     inputElement->setFileListFromRenderer(fileChooser->filenames());
92     inputElement->dispatchFormControlChangeEvent();
93     if (fileChooser->disconnected())
94         return;
95
96     requestIcon(fileChooser->filenames());
97     repaint();
98 }
99
100 bool RenderFileUploadControl::allowsMultipleFiles()
101 {
102 #if ENABLE(DIRECTORY_UPLOAD)
103     if (allowsDirectoryUpload())
104       return true;
105 #endif
106
107     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
108     return input->fastHasAttribute(multipleAttr);
109 }
110
111 #if ENABLE(DIRECTORY_UPLOAD)
112 bool RenderFileUploadControl::allowsDirectoryUpload()
113 {
114     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
115     return input->fastHasAttribute(webkitdirectoryAttr);
116 }
117
118 void RenderFileUploadControl::receiveDropForDirectoryUpload(const Vector<String>& paths)
119 {
120     if (Chrome* chrome = this->chrome())
121         chrome->enumerateChosenDirectory(paths[0], m_fileChooser.get());
122 }
123 #endif
124
125 String RenderFileUploadControl::acceptTypes()
126 {
127     return static_cast<HTMLInputElement*>(node())->accept();
128 }
129
130 void RenderFileUploadControl::updateRendering(PassRefPtr<Icon> icon)
131 {
132     if (m_icon == icon)
133         return;
134
135     m_icon = icon;
136     repaint();
137 }
138
139 void RenderFileUploadControl::click()
140 {
141     if (!ScriptController::processingUserGesture())
142         return;
143     if (Chrome* chrome = this->chrome())
144         chrome->runOpenPanel(frame(), m_fileChooser);
145 }
146
147 Chrome* RenderFileUploadControl::chrome() const
148 {
149     Frame* frame = node()->document()->frame();
150     if (!frame)
151         return 0;
152     Page* page = frame->page();
153     if (!page)
154         return 0;
155     return page->chrome();
156 }
157
158 void RenderFileUploadControl::updateFromElement()
159 {
160     HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node());
161     ASSERT(inputElement->isFileUpload());
162
163
164     if (HTMLInputElement* button = uploadButton())
165         button->setDisabled(!theme()->isEnabled(this));
166
167     // This only supports clearing out the files, but that's OK because for
168     // security reasons that's the only change the DOM is allowed to make.
169     FileList* files = inputElement->files();
170     ASSERT(files);
171     if (files && files->isEmpty() && !m_fileChooser->filenames().isEmpty()) {
172         m_fileChooser->clear();
173         m_icon = 0;
174         repaint();
175     }
176 }
177
178 static int nodeWidth(Node* node)
179 {
180     return node ? node->renderBox()->width() : 0;
181 }
182
183 int RenderFileUploadControl::maxFilenameWidth() const
184 {
185     return max(0, contentWidth() - nodeWidth(uploadButton()) - afterButtonSpacing
186         - (m_icon ? iconWidth + iconFilenameSpacing : 0));
187 }
188
189 void RenderFileUploadControl::paintObject(PaintInfo& paintInfo, const IntPoint& paintOffset)
190 {
191     if (style()->visibility() != VISIBLE)
192         return;
193     
194     // Push a clip.
195     GraphicsContextStateSaver stateSaver(*paintInfo.context, false);
196     if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) {
197         IntRect clipRect(paintOffset.x() + borderLeft(), paintOffset.y() + borderTop(),
198                          width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop() + buttonShadowHeight);
199         if (clipRect.isEmpty())
200             return;
201         stateSaver.save();
202         paintInfo.context->clip(clipRect);
203     }
204
205     if (paintInfo.phase == PaintPhaseForeground) {
206         const String& displayedFilename = fileTextValue();
207         const Font& font = style()->font();
208         TextRun textRun = constructTextRun(this, font, displayedFilename, style(), TextRun::AllowTrailingExpansion, RespectDirection | RespectDirectionOverride);
209
210         // Determine where the filename should be placed
211         int contentLeft = paintOffset.x() + borderLeft() + paddingLeft();
212         HTMLInputElement* button = uploadButton();
213         if (!button)
214             return;
215
216         int buttonWidth = nodeWidth(button);
217         int buttonAndIconWidth = buttonWidth + afterButtonSpacing
218             + (m_icon ? iconWidth + iconFilenameSpacing : 0);
219         int textX;
220         if (style()->isLeftToRightDirection())
221             textX = contentLeft + buttonAndIconWidth;
222         else
223             textX = contentLeft + contentWidth() - buttonAndIconWidth - font.width(textRun);
224         // We want to match the button's baseline
225         RenderButton* buttonRenderer = toRenderButton(button->renderer());
226         int textY = buttonRenderer->absoluteBoundingBoxRect().y()
227             + buttonRenderer->marginTop() + buttonRenderer->borderTop() + buttonRenderer->paddingTop()
228             + buttonRenderer->baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine);
229
230         paintInfo.context->setFillColor(style()->visitedDependentColor(CSSPropertyColor), style()->colorSpace());
231         
232         // Draw the filename
233         paintInfo.context->drawBidiText(font, textRun, IntPoint(textX, textY));
234         
235         if (m_icon) {
236             // Determine where the icon should be placed
237             int iconY = paintOffset.y() + borderTop() + paddingTop() + (contentHeight() - iconHeight) / 2;
238             int iconX;
239             if (style()->isLeftToRightDirection())
240                 iconX = contentLeft + buttonWidth + afterButtonSpacing;
241             else
242                 iconX = contentLeft + contentWidth() - buttonWidth - afterButtonSpacing - iconWidth;
243
244             // Draw the file icon
245             m_icon->paint(paintInfo.context, IntRect(iconX, iconY, iconWidth, iconHeight));
246         }
247     }
248
249     // Paint the children.
250     RenderBlock::paintObject(paintInfo, paintOffset);
251 }
252
253 void RenderFileUploadControl::computePreferredLogicalWidths()
254 {
255     ASSERT(preferredLogicalWidthsDirty());
256
257     m_minPreferredLogicalWidth = 0;
258     m_maxPreferredLogicalWidth = 0;
259
260     RenderStyle* style = this->style();
261     ASSERT(style);
262
263     const Font& font = style->font();
264     if (style->width().isFixed() && style->width().value() > 0)
265         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style->width().value());
266     else {
267         // Figure out how big the filename space needs to be for a given number of characters
268         // (using "0" as the nominal character).
269         const UChar ch = '0';
270         float charWidth = font.width(constructTextRun(this, font, String(&ch, 1), style, TextRun::AllowTrailingExpansion));
271         m_maxPreferredLogicalWidth = (int)ceilf(charWidth * defaultWidthNumChars);
272     }
273
274     if (style->minWidth().isFixed() && style->minWidth().value() > 0) {
275         m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style->minWidth().value()));
276         m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style->minWidth().value()));
277     } else if (style->width().isPercent() || (style->width().isAuto() && style->height().isPercent()))
278         m_minPreferredLogicalWidth = 0;
279     else
280         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth;
281
282     if (style->maxWidth().isFixed() && style->maxWidth().value() != undefinedLength) {
283         m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style->maxWidth().value()));
284         m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style->maxWidth().value()));
285     }
286
287     int toAdd = borderAndPaddingWidth();
288     m_minPreferredLogicalWidth += toAdd;
289     m_maxPreferredLogicalWidth += toAdd;
290
291     setPreferredLogicalWidthsDirty(false);
292 }
293
294 VisiblePosition RenderFileUploadControl::positionForPoint(const IntPoint&)
295 {
296     return VisiblePosition();
297 }
298
299 HTMLInputElement* RenderFileUploadControl::uploadButton() const
300 {
301     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
302
303     ASSERT(input->shadowRoot());
304
305     Node* buttonNode = input->shadowRoot()->firstChild();
306     return buttonNode && buttonNode->isHTMLElement() && buttonNode->hasTagName(inputTag) ? static_cast<HTMLInputElement*>(buttonNode) : 0;
307 }
308
309 void RenderFileUploadControl::receiveDroppedFiles(const Vector<String>& paths)
310 {
311 #if ENABLE(DIRECTORY_UPLOAD)
312     if (allowsDirectoryUpload()) {
313         receiveDropForDirectoryUpload(paths);
314         return;
315     }
316 #endif
317
318     if (allowsMultipleFiles())
319         m_fileChooser->chooseFiles(paths);
320     else
321         m_fileChooser->chooseFile(paths[0]);
322 }
323
324 String RenderFileUploadControl::buttonValue()
325 {
326     if (HTMLInputElement* button = uploadButton())
327         return button->value();
328     
329     return String();
330 }
331
332 String RenderFileUploadControl::fileTextValue() const
333 {
334     ASSERT(m_fileChooser);
335     return theme()->fileListNameForWidth(m_fileChooser->filenames(), style()->font(), maxFilenameWidth());
336 }
337     
338 } // namespace WebCore