Home | History | Annotate | Download | only in rendering
      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 
     57     virtual bool isShadowNode() const { return true; }
     58     virtual Node* shadowParentNode() { return m_shadowParent; }
     59 
     60 private:
     61     Node* m_shadowParent;
     62 };
     63 
     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 
     76 RenderFileUploadControl::~RenderFileUploadControl()
     77 {
     78     if (m_button)
     79         m_button->detach();
     80     m_fileChooser->disconnectClient();
     81 }
     82 
     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 
     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 
    106 bool RenderFileUploadControl::allowsMultipleFiles()
    107 {
    108     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
    109     return !input->getAttribute(multipleAttr).isNull();
    110 }
    111 
    112 String RenderFileUploadControl::acceptTypes()
    113 {
    114     return static_cast<HTMLInputElement*>(node())->accept();
    115 }
    116 
    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 
    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 
    160 int RenderFileUploadControl::maxFilenameWidth() const
    161 {
    162     return max(0, contentWidth() - m_button->renderBox()->width() - afterButtonSpacing
    163         - (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0));
    164 }
    165 
    166 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 
    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 
    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 
    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 
    290 String RenderFileUploadControl::buttonValue()
    291 {
    292     if (!m_button)
    293         return String();
    294 
    295     return m_button->value();
    296 }
    297 
    298 String RenderFileUploadControl::fileTextValue() const
    299 {
    300     return m_fileChooser->basenameForWidth(style()->font(), maxFilenameWidth());
    301 }
    302 
    303 HTMLFileUploadInnerButtonElement::HTMLFileUploadInnerButtonElement(Document* doc, Node* shadowParent)
    304     : HTMLInputElement(inputTag, doc)
    305     , m_shadowParent(shadowParent)
    306 {
    307 }
    308 
    309 } // namespace WebCore
    310