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 "ShadowElement.h"
32 #include "Icon.h"
33 #include "LocalizedStrings.h"
34 #include "Page.h"
35 #include "PaintInfo.h"
36 #include "RenderButton.h"
37 #include "RenderText.h"
38 #include "RenderTheme.h"
39 #include "RenderView.h"
40 #include "TextRun.h"
41 #include <math.h>
42
43 using namespace std;
44
45 namespace WebCore {
46
47 using namespace HTMLNames;
48
49 const int afterButtonSpacing = 4;
50 const int iconHeight = 16;
51 const int iconWidth = 16;
52 const int iconFilenameSpacing = 2;
53 const int defaultWidthNumChars = 34;
54 const int buttonShadowHeight = 2;
55
RenderFileUploadControl(HTMLInputElement * input)56 RenderFileUploadControl::RenderFileUploadControl(HTMLInputElement* input)
57 : RenderBlock(input)
58 {
59 FileList* list = input->files();
60 Vector<String> filenames;
61 unsigned length = list ? list->length() : 0;
62 for (unsigned i = 0; i < length; ++i)
63 filenames.append(list->item(i)->path());
64 m_fileChooser = FileChooser::create(this, filenames);
65 }
66
~RenderFileUploadControl()67 RenderFileUploadControl::~RenderFileUploadControl()
68 {
69 if (m_button)
70 m_button->detach();
71 m_fileChooser->disconnectClient();
72 }
73
styleDidChange(StyleDifference diff,const RenderStyle * oldStyle)74 void RenderFileUploadControl::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
75 {
76 RenderBlock::styleDidChange(diff, oldStyle);
77 if (m_button)
78 m_button->renderer()->setStyle(createButtonStyle(style()));
79 }
80
valueChanged()81 void RenderFileUploadControl::valueChanged()
82 {
83 // dispatchFormControlChangeEvent may destroy this renderer
84 RefPtr<FileChooser> fileChooser = m_fileChooser;
85
86 HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node());
87 inputElement->setFileListFromRenderer(fileChooser->filenames());
88 inputElement->dispatchFormControlChangeEvent();
89
90 // only repaint if it doesn't seem we have been destroyed
91 if (!fileChooser->disconnected())
92 repaint();
93 }
94
allowsMultipleFiles()95 bool RenderFileUploadControl::allowsMultipleFiles()
96 {
97 #if ENABLE(DIRECTORY_UPLOAD)
98 if (allowsDirectoryUpload())
99 return true;
100 #endif
101
102 HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
103 return input->fastHasAttribute(multipleAttr);
104 }
105
106 #if ENABLE(DIRECTORY_UPLOAD)
allowsDirectoryUpload()107 bool RenderFileUploadControl::allowsDirectoryUpload()
108 {
109 HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
110 return input->fastHasAttribute(webkitdirectoryAttr);
111 }
112
receiveDropForDirectoryUpload(const Vector<String> & paths)113 void RenderFileUploadControl::receiveDropForDirectoryUpload(const Vector<String>& paths)
114 {
115 if (Chrome* chromePointer = chrome())
116 chromePointer->enumerateChosenDirectory(paths[0], m_fileChooser.get());
117 }
118 #endif
119
acceptTypes()120 String RenderFileUploadControl::acceptTypes()
121 {
122 return static_cast<HTMLInputElement*>(node())->accept();
123 }
124
125 #if ENABLE(MEDIA_CAPTURE)
capture()126 String RenderFileUploadControl::capture()
127 {
128 return static_cast<HTMLInputElement*>(node())->capture();
129 }
130 #endif
131
chooseIconForFiles(FileChooser * chooser,const Vector<String> & filenames)132 void RenderFileUploadControl::chooseIconForFiles(FileChooser* chooser, const Vector<String>& filenames)
133 {
134 if (Chrome* chromePointer = chrome())
135 chromePointer->chooseIconForFiles(filenames, chooser);
136 }
137
click()138 void RenderFileUploadControl::click()
139 {
140 // Requires a user gesture to open the file dialog.
141 if (!frame() || !frame()->loader()->isProcessingUserGesture())
142 return;
143 if (Chrome* chromePointer = chrome())
144 chromePointer->runOpenPanel(frame(), m_fileChooser);
145 }
146
chrome() const147 Chrome* RenderFileUploadControl::chrome() const
148 {
149 Frame* frame = node()->document()->frame();
150 if (!frame)
151 return 0;
152 Page* page = frame->page();
153 if (!page)
154 return 0;
155 return page->chrome();
156 }
157
updateFromElement()158 void RenderFileUploadControl::updateFromElement()
159 {
160 HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node());
161 ASSERT(inputElement->isFileUpload());
162
163 if (!m_button) {
164 m_button = ShadowInputElement::create(inputElement);
165 m_button->setType("button");
166 m_button->setValue(fileButtonChooseFileLabel());
167 RefPtr<RenderStyle> buttonStyle = createButtonStyle(style());
168 RenderObject* renderer = m_button->createRenderer(renderArena(), buttonStyle.get());
169 m_button->setRenderer(renderer);
170 renderer->setStyle(buttonStyle.release());
171 renderer->updateFromElement();
172 m_button->setAttached();
173 m_button->setInDocument();
174
175 addChild(renderer);
176 }
177
178 m_button->setDisabled(!theme()->isEnabled(this));
179
180 // This only supports clearing out the files, but that's OK because for
181 // security reasons that's the only change the DOM is allowed to make.
182 FileList* files = inputElement->files();
183 ASSERT(files);
184 if (files && files->isEmpty() && !m_fileChooser->filenames().isEmpty()) {
185 m_fileChooser->clear();
186 repaint();
187 }
188 }
189
maxFilenameWidth() const190 int RenderFileUploadControl::maxFilenameWidth() const
191 {
192 return max(0, contentWidth() - m_button->renderBox()->width() - afterButtonSpacing
193 - (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0));
194 }
195
createButtonStyle(const RenderStyle * parentStyle) const196 PassRefPtr<RenderStyle> RenderFileUploadControl::createButtonStyle(const RenderStyle* parentStyle) const
197 {
198 RefPtr<RenderStyle> style = getCachedPseudoStyle(FILE_UPLOAD_BUTTON);
199 if (!style) {
200 style = RenderStyle::create();
201 if (parentStyle)
202 style->inheritFrom(parentStyle);
203 }
204
205 // Button text will wrap on file upload controls with widths smaller than the intrinsic button width
206 // without this setWhiteSpace.
207 style->setWhiteSpace(NOWRAP);
208
209 return style.release();
210 }
211
paintObject(PaintInfo & paintInfo,int tx,int ty)212 void RenderFileUploadControl::paintObject(PaintInfo& paintInfo, int tx, int ty)
213 {
214 if (style()->visibility() != VISIBLE)
215 return;
216 ASSERT(m_fileChooser);
217
218 // Push a clip.
219 if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) {
220 IntRect clipRect(tx + borderLeft(), ty + borderTop(),
221 width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop() + buttonShadowHeight);
222 if (clipRect.isEmpty())
223 return;
224 paintInfo.context->save();
225 paintInfo.context->clip(clipRect);
226 }
227
228 if (paintInfo.phase == PaintPhaseForeground) {
229 const String& displayedFilename = fileTextValue();
230 unsigned length = displayedFilename.length();
231 const UChar* string = displayedFilename.characters();
232 TextRun textRun(string, length, false, 0, 0, TextRun::AllowTrailingExpansion, !style()->isLeftToRightDirection(), style()->unicodeBidi() == Override);
233
234 // Determine where the filename should be placed
235 int contentLeft = tx + borderLeft() + paddingLeft();
236 int buttonAndIconWidth = m_button->renderBox()->width() + afterButtonSpacing
237 + (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0);
238 int textX;
239 if (style()->isLeftToRightDirection())
240 textX = contentLeft + buttonAndIconWidth;
241 else
242 textX = contentLeft + contentWidth() - buttonAndIconWidth - style()->font().width(textRun);
243 // We want to match the button's baseline
244 RenderButton* buttonRenderer = toRenderButton(m_button->renderer());
245 int textY = buttonRenderer->absoluteBoundingBoxRect().y()
246 + buttonRenderer->marginTop() + buttonRenderer->borderTop() + buttonRenderer->paddingTop()
247 + buttonRenderer->baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine);
248
249 paintInfo.context->setFillColor(style()->visitedDependentColor(CSSPropertyColor), style()->colorSpace());
250
251 // Draw the filename
252 paintInfo.context->drawBidiText(style()->font(), textRun, IntPoint(textX, textY));
253
254 if (m_fileChooser->icon()) {
255 // Determine where the icon should be placed
256 int iconY = ty + borderTop() + paddingTop() + (contentHeight() - iconHeight) / 2;
257 int iconX;
258 if (style()->isLeftToRightDirection())
259 iconX = contentLeft + m_button->renderBox()->width() + afterButtonSpacing;
260 else
261 iconX = contentLeft + contentWidth() - m_button->renderBox()->width() - afterButtonSpacing - iconWidth;
262
263 // Draw the file icon
264 m_fileChooser->icon()->paint(paintInfo.context, IntRect(iconX, iconY, iconWidth, iconHeight));
265 }
266 }
267
268 // Paint the children.
269 RenderBlock::paintObject(paintInfo, tx, ty);
270
271 // Pop the clip.
272 if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds)
273 paintInfo.context->restore();
274 }
275
computePreferredLogicalWidths()276 void RenderFileUploadControl::computePreferredLogicalWidths()
277 {
278 ASSERT(preferredLogicalWidthsDirty());
279
280 m_minPreferredLogicalWidth = 0;
281 m_maxPreferredLogicalWidth = 0;
282
283 if (style()->width().isFixed() && style()->width().value() > 0)
284 m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style()->width().value());
285 else {
286 // Figure out how big the filename space needs to be for a given number of characters
287 // (using "0" as the nominal character).
288 const UChar ch = '0';
289 float charWidth = style()->font().width(TextRun(&ch, 1, false, 0, 0, TextRun::AllowTrailingExpansion, false));
290 m_maxPreferredLogicalWidth = (int)ceilf(charWidth * defaultWidthNumChars);
291 }
292
293 if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
294 m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value()));
295 m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value()));
296 } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
297 m_minPreferredLogicalWidth = 0;
298 else
299 m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth;
300
301 if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
302 m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value()));
303 m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value()));
304 }
305
306 int toAdd = borderAndPaddingWidth();
307 m_minPreferredLogicalWidth += toAdd;
308 m_maxPreferredLogicalWidth += toAdd;
309
310 setPreferredLogicalWidthsDirty(false);
311 }
312
positionForPoint(const IntPoint &)313 VisiblePosition RenderFileUploadControl::positionForPoint(const IntPoint&)
314 {
315 return VisiblePosition();
316 }
317
receiveDroppedFiles(const Vector<String> & paths)318 void RenderFileUploadControl::receiveDroppedFiles(const Vector<String>& paths)
319 {
320 #if ENABLE(DIRECTORY_UPLOAD)
321 if (allowsDirectoryUpload()) {
322 receiveDropForDirectoryUpload(paths);
323 return;
324 }
325 #endif
326
327 if (allowsMultipleFiles())
328 m_fileChooser->chooseFiles(paths);
329 else
330 m_fileChooser->chooseFile(paths[0]);
331 }
332
buttonValue()333 String RenderFileUploadControl::buttonValue()
334 {
335 if (!m_button)
336 return String();
337
338 return m_button->value();
339 }
340
fileTextValue() const341 String RenderFileUploadControl::fileTextValue() const
342 {
343 return m_fileChooser->basenameForWidth(style()->font(), maxFilenameWidth());
344 }
345
346 } // namespace WebCore
347