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