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