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