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