Home | History | Annotate | Download | only in rendering
      1 /*
      2  * Copyright (C) 2006, 2007, 2012 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 "core/rendering/RenderFileUploadControl.h"
     23 
     24 #include <math.h>
     25 #include "HTMLNames.h"
     26 #include "core/dom/shadow/ElementShadow.h"
     27 #include "core/dom/shadow/ShadowRoot.h"
     28 #include "core/fileapi/FileList.h"
     29 #include "core/html/HTMLInputElement.h"
     30 #include "core/platform/graphics/Font.h"
     31 #include "core/platform/graphics/GraphicsContextStateSaver.h"
     32 #include "core/platform/graphics/TextRun.h"
     33 #include "core/rendering/PaintInfo.h"
     34 #include "core/rendering/RenderButton.h"
     35 #include "core/rendering/RenderTheme.h"
     36 
     37 using namespace std;
     38 
     39 namespace WebCore {
     40 
     41 using namespace HTMLNames;
     42 
     43 const int afterButtonSpacing = 4;
     44 const int defaultWidthNumChars = 34;
     45 const int buttonShadowHeight = 2;
     46 
     47 RenderFileUploadControl::RenderFileUploadControl(HTMLInputElement* input)
     48     : RenderBlock(input)
     49     , m_canReceiveDroppedFiles(input->canReceiveDroppedFiles())
     50 {
     51 }
     52 
     53 RenderFileUploadControl::~RenderFileUploadControl()
     54 {
     55 }
     56 
     57 bool RenderFileUploadControl::canBeReplacedWithInlineRunIn() const
     58 {
     59     return false;
     60 }
     61 
     62 void RenderFileUploadControl::updateFromElement()
     63 {
     64     HTMLInputElement* input = toHTMLInputElement(node());
     65     ASSERT(input->isFileUpload());
     66 
     67     if (HTMLInputElement* button = uploadButton()) {
     68         bool newCanReceiveDroppedFilesState = input->canReceiveDroppedFiles();
     69         if (m_canReceiveDroppedFiles != newCanReceiveDroppedFilesState) {
     70             m_canReceiveDroppedFiles = newCanReceiveDroppedFilesState;
     71             button->setActive(newCanReceiveDroppedFilesState);
     72         }
     73     }
     74 
     75     // This only supports clearing out the files, but that's OK because for
     76     // security reasons that's the only change the DOM is allowed to make.
     77     FileList* files = input->files();
     78     ASSERT(files);
     79     if (files && files->isEmpty())
     80         repaint();
     81 }
     82 
     83 static int nodeWidth(Node* node)
     84 {
     85     return (node && node->renderBox()) ? node->renderBox()->pixelSnappedWidth() : 0;
     86 }
     87 
     88 int RenderFileUploadControl::maxFilenameWidth() const
     89 {
     90     HTMLInputElement* input = toHTMLInputElement(node());
     91     return max(0, contentBoxRect().pixelSnappedWidth() - nodeWidth(uploadButton()) - afterButtonSpacing);
     92 }
     93 
     94 void RenderFileUploadControl::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
     95 {
     96     if (style()->visibility() != VISIBLE)
     97         return;
     98 
     99     // Push a clip.
    100     GraphicsContextStateSaver stateSaver(*paintInfo.context, false);
    101     if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) {
    102         IntRect clipRect = enclosingIntRect(LayoutRect(paintOffset.x() + borderLeft(), paintOffset.y() + borderTop(),
    103                          width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop() + buttonShadowHeight));
    104         if (clipRect.isEmpty())
    105             return;
    106         stateSaver.save();
    107         paintInfo.context->clip(clipRect);
    108     }
    109 
    110     if (paintInfo.phase == PaintPhaseForeground) {
    111         const String& displayedFilename = fileTextValue();
    112         const Font& font = style()->font();
    113         TextRun textRun = constructTextRun(this, font, displayedFilename, style(), TextRun::AllowTrailingExpansion, RespectDirection | RespectDirectionOverride);
    114         textRun.disableRoundingHacks();
    115 
    116         // Determine where the filename should be placed
    117         LayoutUnit contentLeft = paintOffset.x() + borderLeft() + paddingLeft();
    118         HTMLInputElement* button = uploadButton();
    119         if (!button)
    120             return;
    121 
    122         HTMLInputElement* input = toHTMLInputElement(node());
    123         LayoutUnit buttonWidth = nodeWidth(button);
    124         LayoutUnit buttonAndSpacingWidth = buttonWidth + afterButtonSpacing;
    125         float textWidth = font.width(textRun);
    126         LayoutUnit textX;
    127         if (style()->isLeftToRightDirection())
    128             textX = contentLeft + buttonAndSpacingWidth;
    129         else
    130             textX = contentLeft + contentWidth() - buttonAndSpacingWidth - textWidth;
    131 
    132         LayoutUnit textY = 0;
    133         // We want to match the button's baseline
    134         // FIXME: Make this work with transforms.
    135         if (RenderButton* buttonRenderer = toRenderButton(button->renderer()))
    136             textY = paintOffset.y() + borderTop() + paddingTop() + buttonRenderer->baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine);
    137         else
    138             textY = baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine);
    139         TextRunPaintInfo textRunPaintInfo(textRun);
    140         textRunPaintInfo.bounds = FloatRect(textX,
    141                                             textY - style()->fontMetrics().ascent(),
    142                                             textWidth,
    143                                             style()->fontMetrics().height());
    144 
    145         paintInfo.context->setFillColor(resolveColor(CSSPropertyColor));
    146 
    147         // Draw the filename
    148         paintInfo.context->drawBidiText(font, textRunPaintInfo, IntPoint(roundToInt(textX), roundToInt(textY)));
    149     }
    150 
    151     // Paint the children.
    152     RenderBlock::paintObject(paintInfo, paintOffset);
    153 }
    154 
    155 void RenderFileUploadControl::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const
    156 {
    157     // Figure out how big the filename space needs to be for a given number of characters
    158     // (using "0" as the nominal character).
    159     const UChar character = '0';
    160     const String characterAsString = String(&character, 1);
    161     const Font& font = style()->font();
    162     // FIXME: Remove the need for this const_cast by making constructTextRun take a const RenderObject*.
    163     RenderFileUploadControl* renderer = const_cast<RenderFileUploadControl*>(this);
    164     float minDefaultLabelWidth = defaultWidthNumChars * font.width(constructTextRun(renderer, font, characterAsString, style(), TextRun::AllowTrailingExpansion));
    165 
    166     const String label = theme()->fileListDefaultLabel(toHTMLInputElement(node())->multiple());
    167     float defaultLabelWidth = font.width(constructTextRun(renderer, font, label, style(), TextRun::AllowTrailingExpansion));
    168     if (HTMLInputElement* button = uploadButton())
    169         if (RenderObject* buttonRenderer = button->renderer())
    170             defaultLabelWidth += buttonRenderer->maxPreferredLogicalWidth() + afterButtonSpacing;
    171     maxLogicalWidth = static_cast<int>(ceilf(max(minDefaultLabelWidth, defaultLabelWidth)));
    172 
    173     if (!style()->width().isPercent())
    174         minLogicalWidth = maxLogicalWidth;
    175 }
    176 
    177 void RenderFileUploadControl::computePreferredLogicalWidths()
    178 {
    179     ASSERT(preferredLogicalWidthsDirty());
    180 
    181     m_minPreferredLogicalWidth = 0;
    182     m_maxPreferredLogicalWidth = 0;
    183 
    184     if (style()->width().isFixed() && style()->width().value() > 0)
    185         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = adjustContentBoxLogicalWidthForBoxSizing(style()->width().value());
    186     else
    187         computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth);
    188 
    189     if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
    190         m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->minWidth().value()));
    191         m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->minWidth().value()));
    192     }
    193 
    194     if (style()->maxWidth().isFixed()) {
    195         m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->maxWidth().value()));
    196         m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->maxWidth().value()));
    197     }
    198 
    199     int toAdd = borderAndPaddingWidth();
    200     m_minPreferredLogicalWidth += toAdd;
    201     m_maxPreferredLogicalWidth += toAdd;
    202 
    203     setPreferredLogicalWidthsDirty(false);
    204 }
    205 
    206 PositionWithAffinity RenderFileUploadControl::positionForPoint(const LayoutPoint&)
    207 {
    208     return PositionWithAffinity();
    209 }
    210 
    211 HTMLInputElement* RenderFileUploadControl::uploadButton() const
    212 {
    213     // FIXME: This should be on HTMLInputElement as an API like innerButtonElement().
    214     HTMLInputElement* input = toHTMLInputElement(node());
    215     Node* buttonNode = input->userAgentShadowRoot()->firstChild();
    216     return buttonNode && buttonNode->isHTMLElement() && buttonNode->hasTagName(inputTag) ? toHTMLInputElement(buttonNode) : 0;
    217 }
    218 
    219 String RenderFileUploadControl::buttonValue()
    220 {
    221     if (HTMLInputElement* button = uploadButton())
    222         return button->value();
    223 
    224     return String();
    225 }
    226 
    227 String RenderFileUploadControl::fileTextValue() const
    228 {
    229     HTMLInputElement* input = toHTMLInputElement(node());
    230     ASSERT(input->files());
    231     return theme()->fileListNameForWidth(input->files(), style()->font(), maxFilenameWidth(), input->multiple());
    232 }
    233 
    234 } // namespace WebCore
    235