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