2011-01-19 Tony Gentilcore <tonyg@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 "ShadowElement.h"
32 #include "Icon.h"
33 #include "LocalizedStrings.h"
34 #include "Page.h"
35 #include "PaintInfo.h"
36 #include "RenderButton.h"
37 #include "RenderText.h"
38 #include "RenderTheme.h"
39 #include "RenderView.h"
40 #include "TextRun.h"
41 #include <math.h>
42
43 using namespace std;
44
45 namespace WebCore {
46
47 using namespace HTMLNames;
48
49 const int afterButtonSpacing = 4;
50 const int iconHeight = 16;
51 const int iconWidth = 16;
52 const int iconFilenameSpacing = 2;
53 const int defaultWidthNumChars = 34;
54 const int buttonShadowHeight = 2;
55
56 RenderFileUploadControl::RenderFileUploadControl(HTMLInputElement* input)
57     : RenderBlock(input)
58 {
59     FileList* list = input->files();
60     Vector<String> filenames;
61     unsigned length = list ? list->length() : 0;
62     for (unsigned i = 0; i < length; ++i)
63         filenames.append(list->item(i)->path());
64     m_fileChooser = FileChooser::create(this, filenames);
65 }
66
67 RenderFileUploadControl::~RenderFileUploadControl()
68 {
69     if (m_button)
70         m_button->detach();
71     m_fileChooser->disconnectClient();
72 }
73
74 void RenderFileUploadControl::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
75 {
76     RenderBlock::styleDidChange(diff, oldStyle);
77     if (m_button)
78         m_button->renderer()->setStyle(createButtonStyle(style()));
79 }
80
81 void RenderFileUploadControl::valueChanged()
82 {
83     // dispatchFormControlChangeEvent may destroy this renderer
84     RefPtr<FileChooser> fileChooser = m_fileChooser;
85
86     HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node());
87     inputElement->setFileListFromRenderer(fileChooser->filenames());
88     inputElement->dispatchFormControlChangeEvent();
89  
90     // only repaint if it doesn't seem we have been destroyed
91     if (!fileChooser->disconnected())
92         repaint();
93 }
94
95 bool RenderFileUploadControl::allowsMultipleFiles()
96 {
97 #if ENABLE(DIRECTORY_UPLOAD)
98     if (allowsDirectoryUpload())
99       return true;
100 #endif
101
102     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
103     return input->fastHasAttribute(multipleAttr);
104 }
105
106 #if ENABLE(DIRECTORY_UPLOAD)
107 bool RenderFileUploadControl::allowsDirectoryUpload()
108 {
109     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
110     return input->fastHasAttribute(webkitdirectoryAttr);
111 }
112 #endif
113
114 String RenderFileUploadControl::acceptTypes()
115 {
116     return static_cast<HTMLInputElement*>(node())->accept();
117 }
118
119 void RenderFileUploadControl::chooseIconForFiles(FileChooser* chooser, const Vector<String>& filenames)
120 {
121     if (Chrome* chromePointer = chrome())
122         chromePointer->chooseIconForFiles(filenames, chooser);
123 }
124
125 void RenderFileUploadControl::click()
126 {
127     // Requires a user gesture to open the file dialog.
128     if (!frame() || !frame()->loader()->isProcessingUserGesture())
129         return;
130     if (Chrome* chromePointer = chrome())
131         chromePointer->runOpenPanel(frame(), m_fileChooser);
132 }
133
134 Chrome* RenderFileUploadControl::chrome() const
135 {
136     Frame* frame = node()->document()->frame();
137     if (!frame)
138         return 0;
139     Page* page = frame->page();
140     if (!page)
141         return 0;
142     return page->chrome();
143 }
144
145 void RenderFileUploadControl::updateFromElement()
146 {
147     HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node());
148     ASSERT(inputElement->isFileUpload());
149     
150     if (!m_button) {
151         m_button = ShadowInputElement::create(inputElement);
152         m_button->setType("button");
153         m_button->setValue(fileButtonChooseFileLabel());
154         RefPtr<RenderStyle> buttonStyle = createButtonStyle(style());
155         RenderObject* renderer = m_button->createRenderer(renderArena(), buttonStyle.get());
156         m_button->setRenderer(renderer);
157         renderer->setStyle(buttonStyle.release());
158         renderer->updateFromElement();
159         m_button->setAttached();
160         m_button->setInDocument();
161
162         addChild(renderer);
163     }
164
165     m_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         repaint();
174     }
175 }
176
177 int RenderFileUploadControl::maxFilenameWidth() const
178 {
179     return max(0, contentWidth() - m_button->renderBox()->width() - afterButtonSpacing
180         - (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0));
181 }
182
183 PassRefPtr<RenderStyle> RenderFileUploadControl::createButtonStyle(const RenderStyle* parentStyle) const
184 {
185     RefPtr<RenderStyle> style = getCachedPseudoStyle(FILE_UPLOAD_BUTTON);
186     if (!style) {
187         style = RenderStyle::create();
188         if (parentStyle)
189             style->inheritFrom(parentStyle);
190     }
191
192     // Button text will wrap on file upload controls with widths smaller than the intrinsic button width
193     // without this setWhiteSpace.
194     style->setWhiteSpace(NOWRAP);
195
196     return style.release();
197 }
198
199 void RenderFileUploadControl::paintObject(PaintInfo& paintInfo, int tx, int ty)
200 {
201     if (style()->visibility() != VISIBLE)
202         return;
203     ASSERT(m_fileChooser);
204     
205     // Push a clip.
206     if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) {
207         IntRect clipRect(tx + borderLeft(), ty + borderTop(),
208                          width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop() + buttonShadowHeight);
209         if (clipRect.isEmpty())
210             return;
211         paintInfo.context->save();
212         paintInfo.context->clip(clipRect);
213     }
214
215     if (paintInfo.phase == PaintPhaseForeground) {
216         const String& displayedFilename = fileTextValue();
217         unsigned length = displayedFilename.length();
218         const UChar* string = displayedFilename.characters();
219         TextRun textRun(string, length, false, 0, 0, !style()->isLeftToRightDirection(), style()->unicodeBidi() == Override);
220         
221         // Determine where the filename should be placed
222         int contentLeft = tx + borderLeft() + paddingLeft();
223         int buttonAndIconWidth = m_button->renderBox()->width() + afterButtonSpacing
224             + (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0);
225         int textX;
226         if (style()->isLeftToRightDirection())
227             textX = contentLeft + buttonAndIconWidth;
228         else
229             textX = contentLeft + contentWidth() - buttonAndIconWidth - style()->font().width(textRun);
230         // We want to match the button's baseline
231         RenderButton* buttonRenderer = toRenderButton(m_button->renderer());
232         int textY = buttonRenderer->absoluteBoundingBoxRect().y()
233             + buttonRenderer->marginTop() + buttonRenderer->borderTop() + buttonRenderer->paddingTop()
234             + buttonRenderer->baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine);
235
236         paintInfo.context->setFillColor(style()->visitedDependentColor(CSSPropertyColor), style()->colorSpace());
237         
238         // Draw the filename
239         paintInfo.context->drawBidiText(style()->font(), textRun, IntPoint(textX, textY));
240         
241         if (m_fileChooser->icon()) {
242             // Determine where the icon should be placed
243             int iconY = ty + borderTop() + paddingTop() + (contentHeight() - iconHeight) / 2;
244             int iconX;
245             if (style()->isLeftToRightDirection())
246                 iconX = contentLeft + m_button->renderBox()->width() + afterButtonSpacing;
247             else
248                 iconX = contentLeft + contentWidth() - m_button->renderBox()->width() - afterButtonSpacing - iconWidth;
249
250             // Draw the file icon
251             m_fileChooser->icon()->paint(paintInfo.context, IntRect(iconX, iconY, iconWidth, iconHeight));
252         }
253     }
254
255     // Paint the children.
256     RenderBlock::paintObject(paintInfo, tx, ty);
257
258     // Pop the clip.
259     if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds)
260         paintInfo.context->restore();
261 }
262
263 void RenderFileUploadControl::computePreferredLogicalWidths()
264 {
265     ASSERT(preferredLogicalWidthsDirty());
266
267     m_minPreferredLogicalWidth = 0;
268     m_maxPreferredLogicalWidth = 0;
269
270     if (style()->width().isFixed() && style()->width().value() > 0)
271         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style()->width().value());
272     else {
273         // Figure out how big the filename space needs to be for a given number of characters
274         // (using "0" as the nominal character).
275         const UChar ch = '0';
276         float charWidth = style()->font().floatWidth(TextRun(&ch, 1, false, 0, 0, false, false, false));
277         m_maxPreferredLogicalWidth = (int)ceilf(charWidth * defaultWidthNumChars);
278     }
279
280     if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
281         m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value()));
282         m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value()));
283     } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
284         m_minPreferredLogicalWidth = 0;
285     else
286         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth;
287
288     if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
289         m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value()));
290         m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value()));
291     }
292
293     int toAdd = borderAndPaddingWidth();
294     m_minPreferredLogicalWidth += toAdd;
295     m_maxPreferredLogicalWidth += toAdd;
296
297     setPreferredLogicalWidthsDirty(false);
298 }
299
300 void RenderFileUploadControl::receiveDroppedFiles(const Vector<String>& paths)
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 (!m_button)
311         return String();
312     
313     return m_button->value();
314 }
315
316 String RenderFileUploadControl::fileTextValue() const
317 {
318     return m_fileChooser->basenameForWidth(style()->font(), maxFilenameWidth());
319 }
320     
321 } // namespace WebCore