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