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