2011-06-03 Levi Weintraub <leviw@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 "TextRun.h"
40 #include <math.h>
41
42 using namespace std;
43
44 namespace WebCore {
45
46 using namespace HTMLNames;
47
48 const int afterButtonSpacing = 4;
49 const int iconHeight = 16;
50 const int iconWidth = 16;
51 const int iconFilenameSpacing = 2;
52 const int defaultWidthNumChars = 34;
53 const int buttonShadowHeight = 2;
54
55 class UploadButton : public HTMLInputElement {
56 public:
57     static PassRefPtr<UploadButton> create(HTMLInputElement*);
58     virtual void detach();
59
60 private:
61     UploadButton(HTMLInputElement*);
62 };
63
64 PassRefPtr<UploadButton> UploadButton::create(HTMLInputElement* shadowParent)
65 {
66     return adoptRef(new UploadButton(shadowParent));
67 }
68
69 void UploadButton::detach()
70 {
71     HTMLInputElement::detach();
72     setShadowHost(0);
73 }
74
75 UploadButton::UploadButton(HTMLInputElement* shadowParent)
76     : HTMLInputElement(inputTag, shadowParent->document(), 0, false)
77 {
78     setShadowHost(shadowParent);
79 }
80
81 RenderFileUploadControl::RenderFileUploadControl(HTMLInputElement* input)
82     : RenderBlock(input)
83 {
84     FileList* list = input->files();
85     Vector<String> filenames;
86     unsigned length = list ? list->length() : 0;
87     for (unsigned i = 0; i < length; ++i)
88         filenames.append(list->item(i)->path());
89     m_fileChooser = FileChooser::create(this, filenames);
90 }
91
92 RenderFileUploadControl::~RenderFileUploadControl()
93 {
94     if (m_button)
95         m_button->detach();
96     m_fileChooser->disconnectClient();
97 }
98
99 void RenderFileUploadControl::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
100 {
101     RenderBlock::styleDidChange(diff, oldStyle);
102     if (m_button)
103         m_button->renderer()->setStyle(createButtonStyle(style()));
104 }
105
106 void RenderFileUploadControl::valueChanged()
107 {
108     // dispatchFormControlChangeEvent may destroy this renderer
109     RefPtr<FileChooser> fileChooser = m_fileChooser;
110
111     HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node());
112     inputElement->setFileListFromRenderer(fileChooser->filenames());
113     inputElement->dispatchFormControlChangeEvent();
114  
115     // only repaint if it doesn't seem we have been destroyed
116     if (!fileChooser->disconnected())
117         repaint();
118 }
119
120 bool RenderFileUploadControl::allowsMultipleFiles()
121 {
122 #if ENABLE(DIRECTORY_UPLOAD)
123     if (allowsDirectoryUpload())
124       return true;
125 #endif
126
127     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
128     return input->fastHasAttribute(multipleAttr);
129 }
130
131 #if ENABLE(DIRECTORY_UPLOAD)
132 bool RenderFileUploadControl::allowsDirectoryUpload()
133 {
134     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
135     return input->fastHasAttribute(webkitdirectoryAttr);
136 }
137
138 void RenderFileUploadControl::receiveDropForDirectoryUpload(const Vector<String>& paths)
139 {
140     if (Chrome* chromePointer = chrome())
141         chromePointer->enumerateChosenDirectory(paths[0], m_fileChooser.get());
142 }
143 #endif
144
145 String RenderFileUploadControl::acceptTypes()
146 {
147     return static_cast<HTMLInputElement*>(node())->accept();
148 }
149
150 void RenderFileUploadControl::chooseIconForFiles(FileChooser* chooser, const Vector<String>& filenames)
151 {
152     if (Chrome* chromePointer = chrome())
153         chromePointer->chooseIconForFiles(filenames, chooser);
154 }
155
156 void RenderFileUploadControl::click()
157 {
158     // FIXME: We should call ScriptController::processingUserGesture().
159     if (!frame() || !frame()->loader()->isProcessingUserGesture())
160         return;
161     if (Chrome* chromePointer = chrome())
162         chromePointer->runOpenPanel(frame(), m_fileChooser);
163 }
164
165 Chrome* RenderFileUploadControl::chrome() const
166 {
167     Frame* frame = node()->document()->frame();
168     if (!frame)
169         return 0;
170     Page* page = frame->page();
171     if (!page)
172         return 0;
173     return page->chrome();
174 }
175
176 void RenderFileUploadControl::updateFromElement()
177 {
178     HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node());
179     ASSERT(inputElement->isFileUpload());
180     
181     if (!m_button) {
182         m_button = UploadButton::create(inputElement);
183         m_button->setType("button");
184         m_button->setValue(fileButtonChooseFileLabel());
185         RefPtr<RenderStyle> buttonStyle = createButtonStyle(style());
186         RenderObject* renderer = m_button->createRenderer(renderArena(), buttonStyle.get());
187         m_button->setRenderer(renderer);
188         renderer->setStyle(buttonStyle.release());
189         renderer->updateFromElement();
190         m_button->setAttached();
191         m_button->setInDocument();
192
193         addChild(renderer);
194     }
195
196     m_button->setDisabled(!theme()->isEnabled(this));
197
198     // This only supports clearing out the files, but that's OK because for
199     // security reasons that's the only change the DOM is allowed to make.
200     FileList* files = inputElement->files();
201     ASSERT(files);
202     if (files && files->isEmpty() && !m_fileChooser->filenames().isEmpty()) {
203         m_fileChooser->clear();
204         repaint();
205     }
206 }
207
208 int RenderFileUploadControl::maxFilenameWidth() const
209 {
210     return max(0, contentWidth() - m_button->renderBox()->width() - afterButtonSpacing
211         - (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0));
212 }
213
214 PassRefPtr<RenderStyle> RenderFileUploadControl::createButtonStyle(const RenderStyle* parentStyle) const
215 {
216     RefPtr<RenderStyle> style = getCachedPseudoStyle(FILE_UPLOAD_BUTTON);
217     if (!style) {
218         style = RenderStyle::create();
219         if (parentStyle)
220             style->inheritFrom(parentStyle);
221     }
222
223     // Button text will wrap on file upload controls with widths smaller than the intrinsic button width
224     // without this setWhiteSpace.
225     style->setWhiteSpace(NOWRAP);
226
227     return style.release();
228 }
229
230 void RenderFileUploadControl::paintObject(PaintInfo& paintInfo, const IntPoint& paintOffset)
231 {
232     if (style()->visibility() != VISIBLE)
233         return;
234     ASSERT(m_fileChooser);
235     
236     // Push a clip.
237     GraphicsContextStateSaver stateSaver(*paintInfo.context, false);
238     if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) {
239         IntRect clipRect(paintOffset.x() + borderLeft(), paintOffset.y() + borderTop(),
240                          width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop() + buttonShadowHeight);
241         if (clipRect.isEmpty())
242             return;
243         stateSaver.save();
244         paintInfo.context->clip(clipRect);
245     }
246
247     if (paintInfo.phase == PaintPhaseForeground) {
248         const String& displayedFilename = fileTextValue();
249         const Font& font = style()->font();
250         TextRun textRun = constructTextRun(this, font, displayedFilename, style(), TextRun::AllowTrailingExpansion, RespectDirection | RespectDirectionOverride);
251
252         // Determine where the filename should be placed
253         int contentLeft = paintOffset.x() + borderLeft() + paddingLeft();
254         int buttonAndIconWidth = m_button->renderBox()->width() + afterButtonSpacing
255             + (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0);
256         int textX;
257         if (style()->isLeftToRightDirection())
258             textX = contentLeft + buttonAndIconWidth;
259         else
260             textX = contentLeft + contentWidth() - buttonAndIconWidth - font.width(textRun);
261         // We want to match the button's baseline
262         RenderButton* buttonRenderer = toRenderButton(m_button->renderer());
263         int textY = buttonRenderer->absoluteBoundingBoxRect().y()
264             + buttonRenderer->marginTop() + buttonRenderer->borderTop() + buttonRenderer->paddingTop()
265             + buttonRenderer->baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine);
266
267         paintInfo.context->setFillColor(style()->visitedDependentColor(CSSPropertyColor), style()->colorSpace());
268         
269         // Draw the filename
270         paintInfo.context->drawBidiText(font, textRun, IntPoint(textX, textY));
271         
272         if (m_fileChooser->icon()) {
273             // Determine where the icon should be placed
274             int iconY = paintOffset.y() + borderTop() + paddingTop() + (contentHeight() - iconHeight) / 2;
275             int iconX;
276             if (style()->isLeftToRightDirection())
277                 iconX = contentLeft + m_button->renderBox()->width() + afterButtonSpacing;
278             else
279                 iconX = contentLeft + contentWidth() - m_button->renderBox()->width() - afterButtonSpacing - iconWidth;
280
281             // Draw the file icon
282             m_fileChooser->icon()->paint(paintInfo.context, IntRect(iconX, iconY, iconWidth, iconHeight));
283         }
284     }
285
286     // Paint the children.
287     RenderBlock::paintObject(paintInfo, paintOffset);
288 }
289
290 void RenderFileUploadControl::computePreferredLogicalWidths()
291 {
292     ASSERT(preferredLogicalWidthsDirty());
293
294     m_minPreferredLogicalWidth = 0;
295     m_maxPreferredLogicalWidth = 0;
296
297     RenderStyle* style = this->style();
298     ASSERT(style);
299
300     const Font& font = style->font();
301     if (style->width().isFixed() && style->width().value() > 0)
302         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style->width().value());
303     else {
304         // Figure out how big the filename space needs to be for a given number of characters
305         // (using "0" as the nominal character).
306         const UChar ch = '0';
307         float charWidth = font.width(constructTextRun(this, font, String(&ch, 1), style, TextRun::AllowTrailingExpansion));
308         m_maxPreferredLogicalWidth = (int)ceilf(charWidth * defaultWidthNumChars);
309     }
310
311     if (style->minWidth().isFixed() && style->minWidth().value() > 0) {
312         m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style->minWidth().value()));
313         m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style->minWidth().value()));
314     } else if (style->width().isPercent() || (style->width().isAuto() && style->height().isPercent()))
315         m_minPreferredLogicalWidth = 0;
316     else
317         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth;
318
319     if (style->maxWidth().isFixed() && style->maxWidth().value() != undefinedLength) {
320         m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style->maxWidth().value()));
321         m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style->maxWidth().value()));
322     }
323
324     int toAdd = borderAndPaddingWidth();
325     m_minPreferredLogicalWidth += toAdd;
326     m_maxPreferredLogicalWidth += toAdd;
327
328     setPreferredLogicalWidthsDirty(false);
329 }
330
331 VisiblePosition RenderFileUploadControl::positionForPoint(const IntPoint&)
332 {
333     return VisiblePosition();
334 }
335
336 void RenderFileUploadControl::receiveDroppedFiles(const Vector<String>& paths)
337 {
338 #if ENABLE(DIRECTORY_UPLOAD)
339     if (allowsDirectoryUpload()) {
340         receiveDropForDirectoryUpload(paths);
341         return;
342     }
343 #endif
344
345     if (allowsMultipleFiles())
346         m_fileChooser->chooseFiles(paths);
347     else
348         m_fileChooser->chooseFile(paths[0]);
349 }
350
351 String RenderFileUploadControl::buttonValue()
352 {
353     if (!m_button)
354         return String();
355     
356     return m_button->value();
357 }
358
359 String RenderFileUploadControl::fileTextValue() const
360 {
361     return m_fileChooser->basenameForWidth(style()->font(), maxFilenameWidth());
362 }
363     
364 } // namespace WebCore