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/rendering/PaintInfo.h" 31 #include "core/rendering/RenderButton.h" 32 #include "core/rendering/RenderTheme.h" 33 #include "platform/fonts/Font.h" 34 #include "platform/graphics/GraphicsContextStateSaver.h" 35 #include "platform/text/PlatformLocale.h" 36 #include "platform/text/TextRun.h" 37 38 using namespace std; 39 40 namespace WebCore { 41 42 using namespace HTMLNames; 43 44 const int afterButtonSpacing = 4; 45 const int defaultWidthNumChars = 34; 46 const int buttonShadowHeight = 2; 47 48 RenderFileUploadControl::RenderFileUploadControl(HTMLInputElement* input) 49 : RenderBlockFlow(input) 50 , m_canReceiveDroppedFiles(input->canReceiveDroppedFiles()) 51 { 52 } 53 54 RenderFileUploadControl::~RenderFileUploadControl() 55 { 56 } 57 58 void RenderFileUploadControl::updateFromElement() 59 { 60 HTMLInputElement* input = toHTMLInputElement(node()); 61 ASSERT(input->isFileUpload()); 62 63 if (HTMLInputElement* button = uploadButton()) { 64 bool newCanReceiveDroppedFilesState = input->canReceiveDroppedFiles(); 65 if (m_canReceiveDroppedFiles != newCanReceiveDroppedFilesState) { 66 m_canReceiveDroppedFiles = newCanReceiveDroppedFilesState; 67 button->setActive(newCanReceiveDroppedFilesState); 68 } 69 } 70 71 // This only supports clearing out the files, but that's OK because for 72 // security reasons that's the only change the DOM is allowed to make. 73 FileList* files = input->files(); 74 ASSERT(files); 75 if (files && files->isEmpty()) 76 repaint(); 77 } 78 79 static int nodeWidth(Node* node) 80 { 81 return (node && node->renderBox()) ? node->renderBox()->pixelSnappedWidth() : 0; 82 } 83 84 int RenderFileUploadControl::maxFilenameWidth() const 85 { 86 return max(0, contentBoxRect().pixelSnappedWidth() - nodeWidth(uploadButton()) - afterButtonSpacing); 87 } 88 89 void RenderFileUploadControl::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset) 90 { 91 if (style()->visibility() != VISIBLE) 92 return; 93 94 // Push a clip. 95 GraphicsContextStateSaver stateSaver(*paintInfo.context, false); 96 if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) { 97 IntRect clipRect = enclosingIntRect(LayoutRect(paintOffset.x() + borderLeft(), paintOffset.y() + borderTop(), 98 width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop() + buttonShadowHeight)); 99 if (clipRect.isEmpty()) 100 return; 101 stateSaver.save(); 102 paintInfo.context->clip(clipRect); 103 } 104 105 if (paintInfo.phase == PaintPhaseForeground) { 106 const String& displayedFilename = fileTextValue(); 107 const Font& font = style()->font(); 108 TextRun textRun = constructTextRun(this, font, displayedFilename, style(), TextRun::AllowTrailingExpansion, RespectDirection | RespectDirectionOverride); 109 textRun.disableRoundingHacks(); 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 textRunPaintInfo.bounds = FloatRect(textX, 135 textY - style()->fontMetrics().ascent(), 136 textWidth, 137 style()->fontMetrics().height()); 138 139 paintInfo.context->setFillColor(resolveColor(CSSPropertyColor)); 140 141 // Draw the filename 142 paintInfo.context->drawBidiText(font, textRunPaintInfo, IntPoint(roundToInt(textX), roundToInt(textY))); 143 } 144 145 // Paint the children. 146 RenderBlock::paintObject(paintInfo, paintOffset); 147 } 148 149 void RenderFileUploadControl::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const 150 { 151 // Figure out how big the filename space needs to be for a given number of characters 152 // (using "0" as the nominal character). 153 const UChar character = '0'; 154 const String characterAsString = String(&character, 1); 155 const Font& font = style()->font(); 156 // FIXME: Remove the need for this const_cast by making constructTextRun take a const RenderObject*. 157 RenderFileUploadControl* renderer = const_cast<RenderFileUploadControl*>(this); 158 float minDefaultLabelWidth = defaultWidthNumChars * font.width(constructTextRun(renderer, font, characterAsString, style(), TextRun::AllowTrailingExpansion)); 159 160 const String label = toHTMLInputElement(node())->locale().queryString(blink::WebLocalizedString::FileButtonNoFileSelectedLabel); 161 float defaultLabelWidth = font.width(constructTextRun(renderer, font, label, style(), TextRun::AllowTrailingExpansion)); 162 if (HTMLInputElement* button = uploadButton()) 163 if (RenderObject* buttonRenderer = button->renderer()) 164 defaultLabelWidth += buttonRenderer->maxPreferredLogicalWidth() + afterButtonSpacing; 165 maxLogicalWidth = static_cast<int>(ceilf(max(minDefaultLabelWidth, defaultLabelWidth))); 166 167 if (!style()->width().isPercent()) 168 minLogicalWidth = maxLogicalWidth; 169 } 170 171 void RenderFileUploadControl::computePreferredLogicalWidths() 172 { 173 ASSERT(preferredLogicalWidthsDirty()); 174 175 m_minPreferredLogicalWidth = 0; 176 m_maxPreferredLogicalWidth = 0; 177 178 if (style()->width().isFixed() && style()->width().value() > 0) 179 m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = adjustContentBoxLogicalWidthForBoxSizing(style()->width().value()); 180 else 181 computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth); 182 183 if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) { 184 m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->minWidth().value())); 185 m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->minWidth().value())); 186 } 187 188 if (style()->maxWidth().isFixed()) { 189 m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->maxWidth().value())); 190 m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->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 buttonNode && buttonNode->isHTMLElement() && buttonNode->hasTagName(inputTag) ? 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 WebCore 229