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