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
43using namespace std;
44
45namespace WebCore {
46
47using namespace HTMLNames;
48
49const int afterButtonSpacing = 4;
50const int iconHeight = 16;
51const int iconWidth = 16;
52const int iconFilenameSpacing = 2;
53const int defaultWidthNumChars = 34;
54const int buttonShadowHeight = 2;
55
56RenderFileUploadControl::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
67RenderFileUploadControl::~RenderFileUploadControl()
68{
69    if (m_button)
70        m_button->detach();
71    m_fileChooser->disconnectClient();
72}
73
74void 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
81void 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
95bool 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)
107bool RenderFileUploadControl::allowsDirectoryUpload()
108{
109    HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
110    return input->fastHasAttribute(webkitdirectoryAttr);
111}
112
113void RenderFileUploadControl::receiveDropForDirectoryUpload(const Vector<String>& paths)
114{
115    if (Chrome* chromePointer = chrome())
116        chromePointer->enumerateChosenDirectory(paths[0], m_fileChooser.get());
117}
118#endif
119
120String RenderFileUploadControl::acceptTypes()
121{
122    return static_cast<HTMLInputElement*>(node())->accept();
123}
124
125#if ENABLE(MEDIA_CAPTURE)
126String RenderFileUploadControl::capture()
127{
128    return static_cast<HTMLInputElement*>(node())->capture();
129}
130#endif
131
132void RenderFileUploadControl::chooseIconForFiles(FileChooser* chooser, const Vector<String>& filenames)
133{
134    if (Chrome* chromePointer = chrome())
135        chromePointer->chooseIconForFiles(filenames, chooser);
136}
137
138void 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
147Chrome* 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
158void 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
190int RenderFileUploadControl::maxFilenameWidth() const
191{
192    return max(0, contentWidth() - m_button->renderBox()->width() - afterButtonSpacing
193        - (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0));
194}
195
196PassRefPtr<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
212void 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
276void 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
313VisiblePosition RenderFileUploadControl::positionForPoint(const IntPoint&)
314{
315    return VisiblePosition();
316}
317
318void 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
333String RenderFileUploadControl::buttonValue()
334{
335    if (!m_button)
336        return String();
337
338    return m_button->value();
339}
340
341String RenderFileUploadControl::fileTextValue() const
342{
343    return m_fileChooser->basenameForWidth(style()->font(), maxFilenameWidth());
344}
345
346} // namespace WebCore
347