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