• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "RenderButton.h"
35 #include "RenderText.h"
36 #include "RenderTheme.h"
37 #include "RenderView.h"
38 #include <math.h>
39 
40 using namespace std;
41 
42 namespace WebCore {
43 
44 using namespace HTMLNames;
45 
46 const int afterButtonSpacing = 4;
47 const int iconHeight = 16;
48 const int iconWidth = 16;
49 const int iconFilenameSpacing = 2;
50 const int defaultWidthNumChars = 34;
51 const int buttonShadowHeight = 2;
52 
53 class HTMLFileUploadInnerButtonElement : public HTMLInputElement {
54 public:
55     HTMLFileUploadInnerButtonElement(Document*, Node* shadowParent);
56 
isShadowNode() const57     virtual bool isShadowNode() const { return true; }
shadowParentNode()58     virtual Node* shadowParentNode() { return m_shadowParent; }
59 
60 private:
61     Node* m_shadowParent;
62 };
63 
RenderFileUploadControl(HTMLInputElement * input)64 RenderFileUploadControl::RenderFileUploadControl(HTMLInputElement* input)
65     : RenderBlock(input)
66     , m_button(0)
67 {
68     FileList* list = input->files();
69     Vector<String> filenames;
70     unsigned length = list ? list->length() : 0;
71     for (unsigned i = 0; i < length; ++i)
72         filenames.append(list->item(i)->path());
73     m_fileChooser = FileChooser::create(this, filenames);
74 }
75 
~RenderFileUploadControl()76 RenderFileUploadControl::~RenderFileUploadControl()
77 {
78     if (m_button)
79         m_button->detach();
80     m_fileChooser->disconnectClient();
81 }
82 
styleDidChange(StyleDifference diff,const RenderStyle * oldStyle)83 void RenderFileUploadControl::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
84 {
85     RenderBlock::styleDidChange(diff, oldStyle);
86     if (m_button)
87         m_button->renderer()->setStyle(createButtonStyle(style()));
88 
89     setReplaced(isInline());
90 }
91 
valueChanged()92 void RenderFileUploadControl::valueChanged()
93 {
94     // dispatchFormControlChangeEvent may destroy this renderer
95     RefPtr<FileChooser> fileChooser = m_fileChooser;
96 
97     HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node());
98     inputElement->setFileListFromRenderer(fileChooser->filenames());
99     inputElement->dispatchFormControlChangeEvent();
100 
101     // only repaint if it doesn't seem we have been destroyed
102     if (!fileChooser->disconnected())
103         repaint();
104 }
105 
allowsMultipleFiles()106 bool RenderFileUploadControl::allowsMultipleFiles()
107 {
108     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
109     return !input->getAttribute(multipleAttr).isNull();
110 }
111 
acceptTypes()112 String RenderFileUploadControl::acceptTypes()
113 {
114     return static_cast<HTMLInputElement*>(node())->accept();
115 }
116 
click()117 void RenderFileUploadControl::click()
118 {
119     Frame* frame = node()->document()->frame();
120     if (!frame)
121         return;
122     Page* page = frame->page();
123     if (!page)
124         return;
125     page->chrome()->runOpenPanel(frame, m_fileChooser);
126 }
127 
updateFromElement()128 void RenderFileUploadControl::updateFromElement()
129 {
130     HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node());
131     ASSERT(inputElement->inputType() == HTMLInputElement::FILE);
132 
133     if (!m_button) {
134         m_button = new HTMLFileUploadInnerButtonElement(document(), inputElement);
135         m_button->setInputType("button");
136         m_button->setValue(fileButtonChooseFileLabel());
137         RefPtr<RenderStyle> buttonStyle = createButtonStyle(style());
138         RenderObject* renderer = m_button->createRenderer(renderArena(), buttonStyle.get());
139         m_button->setRenderer(renderer);
140         renderer->setStyle(buttonStyle.release());
141         renderer->updateFromElement();
142         m_button->setAttached();
143         m_button->setInDocument(true);
144 
145         addChild(renderer);
146     }
147 
148     m_button->setDisabled(!theme()->isEnabled(this));
149 
150     // This only supports clearing out the files, but that's OK because for
151     // security reasons that's the only change the DOM is allowed to make.
152     FileList* files = inputElement->files();
153     ASSERT(files);
154     if (files && files->isEmpty() && !m_fileChooser->filenames().isEmpty()) {
155         m_fileChooser->clear();
156         repaint();
157     }
158 }
159 
maxFilenameWidth() const160 int RenderFileUploadControl::maxFilenameWidth() const
161 {
162     return max(0, contentWidth() - m_button->renderBox()->width() - afterButtonSpacing
163         - (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0));
164 }
165 
createButtonStyle(const RenderStyle * parentStyle) const166 PassRefPtr<RenderStyle> RenderFileUploadControl::createButtonStyle(const RenderStyle* parentStyle) const
167 {
168     RefPtr<RenderStyle> style = getCachedPseudoStyle(FILE_UPLOAD_BUTTON);
169     if (!style) {
170         style = RenderStyle::create();
171         if (parentStyle)
172             style->inheritFrom(parentStyle);
173     }
174 
175     // Button text will wrap on file upload controls with widths smaller than the intrinsic button width
176     // without this setWhiteSpace.
177     style->setWhiteSpace(NOWRAP);
178 
179     return style.release();
180 }
181 
paintObject(PaintInfo & paintInfo,int tx,int ty)182 void RenderFileUploadControl::paintObject(PaintInfo& paintInfo, int tx, int ty)
183 {
184     if (style()->visibility() != VISIBLE)
185         return;
186 
187     // Push a clip.
188     if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) {
189         IntRect clipRect(tx + borderLeft(), ty + borderTop(),
190                          width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop() + buttonShadowHeight);
191         if (clipRect.isEmpty())
192             return;
193         paintInfo.context->save();
194         paintInfo.context->clip(clipRect);
195     }
196 
197     if (paintInfo.phase == PaintPhaseForeground) {
198         const String& displayedFilename = fileTextValue();
199         unsigned length = displayedFilename.length();
200         const UChar* string = displayedFilename.characters();
201         TextRun textRun(string, length, false, 0, 0, style()->direction() == RTL, style()->unicodeBidi() == Override);
202 
203         // Determine where the filename should be placed
204         int contentLeft = tx + borderLeft() + paddingLeft();
205         int buttonAndIconWidth = m_button->renderBox()->width() + afterButtonSpacing
206             + (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0);
207         int textX;
208         if (style()->direction() == LTR)
209             textX = contentLeft + buttonAndIconWidth;
210         else
211             textX = contentLeft + contentWidth() - buttonAndIconWidth - style()->font().width(textRun);
212         // We want to match the button's baseline
213         RenderButton* buttonRenderer = toRenderButton(m_button->renderer());
214         int textY = buttonRenderer->absoluteBoundingBoxRect().y()
215             + buttonRenderer->marginTop() + buttonRenderer->borderTop() + buttonRenderer->paddingTop()
216             + buttonRenderer->baselinePosition(true, false);
217 
218         paintInfo.context->setFillColor(style()->color(), style()->colorSpace());
219 
220         // Draw the filename
221         paintInfo.context->drawBidiText(style()->font(), textRun, IntPoint(textX, textY));
222 
223         if (m_fileChooser->icon()) {
224             // Determine where the icon should be placed
225             int iconY = ty + borderTop() + paddingTop() + (contentHeight() - iconHeight) / 2;
226             int iconX;
227             if (style()->direction() == LTR)
228                 iconX = contentLeft + m_button->renderBox()->width() + afterButtonSpacing;
229             else
230                 iconX = contentLeft + contentWidth() - m_button->renderBox()->width() - afterButtonSpacing - iconWidth;
231 
232             // Draw the file icon
233             m_fileChooser->icon()->paint(paintInfo.context, IntRect(iconX, iconY, iconWidth, iconHeight));
234         }
235     }
236 
237     // Paint the children.
238     RenderBlock::paintObject(paintInfo, tx, ty);
239 
240     // Pop the clip.
241     if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds)
242         paintInfo.context->restore();
243 }
244 
calcPrefWidths()245 void RenderFileUploadControl::calcPrefWidths()
246 {
247     ASSERT(prefWidthsDirty());
248 
249     m_minPrefWidth = 0;
250     m_maxPrefWidth = 0;
251 
252     if (style()->width().isFixed() && style()->width().value() > 0)
253         m_minPrefWidth = m_maxPrefWidth = calcContentBoxWidth(style()->width().value());
254     else {
255         // Figure out how big the filename space needs to be for a given number of characters
256         // (using "0" as the nominal character).
257         const UChar ch = '0';
258         float charWidth = style()->font().floatWidth(TextRun(&ch, 1, false, 0, 0, false, false, false));
259         m_maxPrefWidth = (int)ceilf(charWidth * defaultWidthNumChars);
260     }
261 
262     if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
263         m_maxPrefWidth = max(m_maxPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
264         m_minPrefWidth = max(m_minPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
265     } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
266         m_minPrefWidth = 0;
267     else
268         m_minPrefWidth = m_maxPrefWidth;
269 
270     if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
271         m_maxPrefWidth = min(m_maxPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
272         m_minPrefWidth = min(m_minPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
273     }
274 
275     int toAdd = paddingLeft() + paddingRight() + borderLeft() + borderRight();
276     m_minPrefWidth += toAdd;
277     m_maxPrefWidth += toAdd;
278 
279     setPrefWidthsDirty(false);
280 }
281 
receiveDroppedFiles(const Vector<String> & paths)282 void RenderFileUploadControl::receiveDroppedFiles(const Vector<String>& paths)
283 {
284     if (allowsMultipleFiles())
285         m_fileChooser->chooseFiles(paths);
286     else
287         m_fileChooser->chooseFile(paths[0]);
288 }
289 
buttonValue()290 String RenderFileUploadControl::buttonValue()
291 {
292     if (!m_button)
293         return String();
294 
295     return m_button->value();
296 }
297 
fileTextValue() const298 String RenderFileUploadControl::fileTextValue() const
299 {
300     return m_fileChooser->basenameForWidth(style()->font(), maxFilenameWidth());
301 }
302 
HTMLFileUploadInnerButtonElement(Document * doc,Node * shadowParent)303 HTMLFileUploadInnerButtonElement::HTMLFileUploadInnerButtonElement(Document* doc, Node* shadowParent)
304     : HTMLInputElement(inputTag, doc)
305     , m_shadowParent(shadowParent)
306 {
307 }
308 
309 } // namespace WebCore
310