Migrate GraphicsContexts from pointers to references
[WebKit-https.git] / Source / WebCore / rendering / RenderFileUploadControl.cpp
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 "RenderFileUploadControl.h"
23
24 #include "FileList.h"
25 #include "FontCascade.h"
26 #include "GraphicsContext.h"
27 #include "HTMLInputElement.h"
28 #include "HTMLNames.h"
29 #include "Icon.h"
30 #include "LocalizedStrings.h"
31 #include "PaintInfo.h"
32 #include "RenderButton.h"
33 #include "RenderText.h"
34 #include "RenderTheme.h"
35 #include "ShadowRoot.h"
36 #include "TextRun.h"
37 #include "VisiblePosition.h"
38 #include <math.h>
39
40 #if PLATFORM(IOS)
41 #include "StringTruncator.h"
42 #endif
43
44 namespace WebCore {
45
46 using namespace HTMLNames;
47
48 const int afterButtonSpacing = 4;
49 #if !PLATFORM(IOS)
50 const int iconHeight = 16;
51 const int iconWidth = 16;
52 const int iconFilenameSpacing = 2;
53 const int defaultWidthNumChars = 34;
54 #else
55 // On iOS the icon height matches the button height, to maximize the icon size.
56 const int iconFilenameSpacing = afterButtonSpacing;
57 const int defaultWidthNumChars = 38;
58 #endif
59 const int buttonShadowHeight = 2;
60
61 RenderFileUploadControl::RenderFileUploadControl(HTMLInputElement& input, Ref<RenderStyle>&& style)
62     : RenderBlockFlow(input, WTF::move(style))
63     , m_canReceiveDroppedFiles(input.canReceiveDroppedFiles())
64 {
65 }
66
67 RenderFileUploadControl::~RenderFileUploadControl()
68 {
69 }
70
71 HTMLInputElement& RenderFileUploadControl::inputElement() const
72 {
73     return downcast<HTMLInputElement>(nodeForNonAnonymous());
74 }
75
76 void RenderFileUploadControl::updateFromElement()
77 {
78     ASSERT(inputElement().isFileUpload());
79
80     if (HTMLInputElement* button = uploadButton()) {
81         bool newCanReceiveDroppedFilesState = inputElement().canReceiveDroppedFiles();
82         if (m_canReceiveDroppedFiles != newCanReceiveDroppedFilesState) {
83             m_canReceiveDroppedFiles = newCanReceiveDroppedFilesState;
84             button->setActive(newCanReceiveDroppedFilesState);
85         }
86     }
87
88     // This only supports clearing out the files, but that's OK because for
89     // security reasons that's the only change the DOM is allowed to make.
90     FileList* files = inputElement().files();
91     ASSERT(files);
92     if (files && files->isEmpty())
93         repaint();
94 }
95
96 static int nodeWidth(Node* node)
97 {
98     return (node && node->renderBox()) ? roundToInt(node->renderBox()->size().width()) : 0;
99 }
100
101 #if PLATFORM(IOS)
102 static int nodeHeight(Node* node)
103 {
104     return (node && node->renderBox()) ? roundToInt(node->renderBox()->size().height()) : 0;
105 }
106 #endif
107
108 int RenderFileUploadControl::maxFilenameWidth() const
109 {
110 #if PLATFORM(IOS)
111     int iconWidth = nodeHeight(uploadButton());
112 #endif
113     return std::max(0, snappedIntRect(contentBoxRect()).width() - nodeWidth(uploadButton()) - afterButtonSpacing
114         - (inputElement().icon() ? iconWidth + iconFilenameSpacing : 0));
115 }
116
117 void RenderFileUploadControl::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
118 {
119     if (style().visibility() != VISIBLE)
120         return;
121     
122     // Push a clip.
123     GraphicsContextStateSaver stateSaver(paintInfo.context(), false);
124     if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) {
125         IntRect clipRect = enclosingIntRect(LayoutRect(paintOffset.x() + borderLeft(), paintOffset.y() + borderTop(),
126                          width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop() + buttonShadowHeight));
127         if (clipRect.isEmpty())
128             return;
129         stateSaver.save();
130         paintInfo.context().clip(clipRect);
131     }
132
133     if (paintInfo.phase == PaintPhaseForeground) {
134         const String& displayedFilename = fileTextValue();
135         const FontCascade& font = style().fontCascade();
136         TextRun textRun = constructTextRun(this, font, displayedFilename, style(), AllowTrailingExpansion, RespectDirection | RespectDirectionOverride);
137         textRun.disableRoundingHacks();
138
139 #if PLATFORM(IOS)
140         int iconHeight = nodeHeight(uploadButton());
141         int iconWidth = iconHeight;
142 #endif
143         // Determine where the filename should be placed
144         LayoutUnit contentLeft = paintOffset.x() + borderLeft() + paddingLeft();
145         HTMLInputElement* button = uploadButton();
146         if (!button)
147             return;
148
149         LayoutUnit buttonWidth = nodeWidth(button);
150         LayoutUnit buttonAndIconWidth = buttonWidth + afterButtonSpacing
151             + (inputElement().icon() ? iconWidth + iconFilenameSpacing : 0);
152         LayoutUnit textX;
153         if (style().isLeftToRightDirection())
154             textX = contentLeft + buttonAndIconWidth;
155         else
156             textX = contentLeft + contentWidth() - buttonAndIconWidth - font.width(textRun);
157
158         LayoutUnit textY = 0;
159         // We want to match the button's baseline
160         // FIXME: Make this work with transforms.
161         if (RenderButton* buttonRenderer = downcast<RenderButton>(button->renderer()))
162             textY = paintOffset.y() + borderTop() + paddingTop() + buttonRenderer->baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine);
163         else
164             textY = baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine);
165
166         paintInfo.context().setFillColor(style().visitedDependentColor(CSSPropertyColor), style().colorSpace());
167         
168         // Draw the filename
169         paintInfo.context().drawBidiText(font, textRun, IntPoint(roundToInt(textX), roundToInt(textY)));
170         
171         if (inputElement().icon()) {
172             // Determine where the icon should be placed
173             LayoutUnit iconY = paintOffset.y() + borderTop() + paddingTop() + (contentHeight() - iconHeight) / 2;
174             LayoutUnit iconX;
175             if (style().isLeftToRightDirection())
176                 iconX = contentLeft + buttonWidth + afterButtonSpacing;
177             else
178                 iconX = contentLeft + contentWidth() - buttonWidth - afterButtonSpacing - iconWidth;
179
180 #if PLATFORM(IOS)
181             if (RenderButton* buttonRenderer = downcast<RenderButton>(button->renderer())) {
182                 // Draw the file icon and decorations.
183                 IntRect iconRect(iconX, iconY, iconWidth, iconHeight);
184                 RenderTheme::FileUploadDecorations decorationsType = inputElement().files()->length() == 1 ? RenderTheme::SingleFile : RenderTheme::MultipleFiles;
185                 theme().paintFileUploadIconDecorations(*this, *buttonRenderer, paintInfo, iconRect, inputElement().icon(), decorationsType);
186             }
187 #else
188             // Draw the file icon
189             inputElement().icon()->paint(paintInfo.context(), IntRect(roundToInt(iconX), roundToInt(iconY), iconWidth, iconHeight));
190 #endif
191         }
192     }
193
194     // Paint the children.
195     RenderBlockFlow::paintObject(paintInfo, paintOffset);
196 }
197
198 void RenderFileUploadControl::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const
199 {
200     // Figure out how big the filename space needs to be for a given number of characters
201     // (using "0" as the nominal character).
202     const UChar character = '0';
203     const String characterAsString = String(&character, 1);
204     const FontCascade& font = style().fontCascade();
205     // FIXME: Remove the need for this const_cast by making constructTextRun take a const RenderObject*.
206     RenderFileUploadControl* renderer = const_cast<RenderFileUploadControl*>(this);
207     float minDefaultLabelWidth = defaultWidthNumChars * font.width(constructTextRun(renderer, font, characterAsString, style(), AllowTrailingExpansion));
208
209     const String label = theme().fileListDefaultLabel(inputElement().multiple());
210     float defaultLabelWidth = font.width(constructTextRun(renderer, font, label, style(), AllowTrailingExpansion));
211     if (HTMLInputElement* button = uploadButton())
212         if (RenderObject* buttonRenderer = button->renderer())
213             defaultLabelWidth += buttonRenderer->maxPreferredLogicalWidth() + afterButtonSpacing;
214     maxLogicalWidth = static_cast<int>(ceilf(std::max(minDefaultLabelWidth, defaultLabelWidth)));
215
216     if (!style().width().isPercentOrCalculated())
217         minLogicalWidth = maxLogicalWidth;
218 }
219
220 void RenderFileUploadControl::computePreferredLogicalWidths()
221 {
222     ASSERT(preferredLogicalWidthsDirty());
223
224     m_minPreferredLogicalWidth = 0;
225     m_maxPreferredLogicalWidth = 0;
226
227     if (style().width().isFixed() && style().width().value() > 0)
228         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = adjustContentBoxLogicalWidthForBoxSizing(style().width().value());
229     else
230         computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth);
231
232     if (style().minWidth().isFixed() && style().minWidth().value() > 0) {
233         m_maxPreferredLogicalWidth = std::max(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().minWidth().value()));
234         m_minPreferredLogicalWidth = std::max(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().minWidth().value()));
235     }
236
237     if (style().maxWidth().isFixed()) {
238         m_maxPreferredLogicalWidth = std::min(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().maxWidth().value()));
239         m_minPreferredLogicalWidth = std::min(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().maxWidth().value()));
240     }
241
242     int toAdd = horizontalBorderAndPaddingExtent();
243     m_minPreferredLogicalWidth += toAdd;
244     m_maxPreferredLogicalWidth += toAdd;
245
246     setPreferredLogicalWidthsDirty(false);
247 }
248
249 VisiblePosition RenderFileUploadControl::positionForPoint(const LayoutPoint&, const RenderRegion*)
250 {
251     return VisiblePosition();
252 }
253
254 HTMLInputElement* RenderFileUploadControl::uploadButton() const
255 {
256     ASSERT(inputElement().shadowRoot());
257     Node* buttonNode = inputElement().shadowRoot()->firstChild();
258     return is<HTMLInputElement>(buttonNode) ? downcast<HTMLInputElement>(buttonNode) : nullptr;
259 }
260
261 String RenderFileUploadControl::buttonValue()
262 {
263     if (HTMLInputElement* button = uploadButton())
264         return button->value();
265     
266     return String();
267 }
268
269 String RenderFileUploadControl::fileTextValue() const
270 {
271     ASSERT(inputElement().files());
272 #if PLATFORM(IOS)
273     if (inputElement().files()->length())
274         return StringTruncator::rightTruncate(inputElement().displayString(), maxFilenameWidth(), style().fontCascade(), StringTruncator::EnableRoundingHacks);
275 #endif
276     return theme().fileListNameForWidth(inputElement().files(), style().fontCascade(), maxFilenameWidth(), inputElement().multiple());
277 }
278     
279 } // namespace WebCore