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