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