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