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 "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 
     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 
     67 RenderFileUploadControl::~RenderFileUploadControl()
     68 {
     69     if (m_button)
     70         m_button->detach();
     71     m_fileChooser->disconnectClient();
     72 }
     73 
     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 
     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->fastHasAttribute(multipleAttr);
    104 }
    105 
    106 #if ENABLE(DIRECTORY_UPLOAD)
    107 bool RenderFileUploadControl::allowsDirectoryUpload()
    108 {
    109     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
    110     return input->fastHasAttribute(webkitdirectoryAttr);
    111 }
    112 
    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 
    120 String RenderFileUploadControl::acceptTypes()
    121 {
    122     return static_cast<HTMLInputElement*>(node())->accept();
    123 }
    124 
    125 #if ENABLE(MEDIA_CAPTURE)
    126 String RenderFileUploadControl::capture()
    127 {
    128     return static_cast<HTMLInputElement*>(node())->capture();
    129 }
    130 #endif
    131 
    132 void RenderFileUploadControl::chooseIconForFiles(FileChooser* chooser, const Vector<String>& filenames)
    133 {
    134     if (Chrome* chromePointer = chrome())
    135         chromePointer->chooseIconForFiles(filenames, chooser);
    136 }
    137 
    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 
    147 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 
    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 
    190 int RenderFileUploadControl::maxFilenameWidth() const
    191 {
    192     return max(0, contentWidth() - m_button->renderBox()->width() - afterButtonSpacing
    193         - (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0));
    194 }
    195 
    196 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 
    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 
    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 
    313 VisiblePosition RenderFileUploadControl::positionForPoint(const IntPoint&)
    314 {
    315     return VisiblePosition();
    316 }
    317 
    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 
    333 String RenderFileUploadControl::buttonValue()
    334 {
    335     if (!m_button)
    336         return String();
    337 
    338     return m_button->value();
    339 }
    340 
    341 String RenderFileUploadControl::fileTextValue() const
    342 {
    343     return m_fileChooser->basenameForWidth(style()->font(), maxFilenameWidth());
    344 }
    345 
    346 } // namespace WebCore
    347