Source/WebCore: Filename text in file upload controls is not aligned with button...
[WebKit-https.git] / Source / WebCore / rendering / RenderFileUploadControl.cpp
1 /*
2  * Copyright (C) 2006, 2007 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 "GraphicsContext.h"
26 #include "HTMLInputElement.h"
27 #include "HTMLNames.h"
28 #include "Icon.h"
29 #include "LocalizedStrings.h"
30 #include "PaintInfo.h"
31 #include "RenderButton.h"
32 #include "RenderText.h"
33 #include "RenderTheme.h"
34 #include "ShadowRoot.h"
35 #include "TextRun.h"
36 #include "VisiblePosition.h"
37 #include <math.h>
38
39 using namespace std;
40
41 namespace WebCore {
42
43 using namespace HTMLNames;
44
45 const int afterButtonSpacing = 4;
46 const int iconHeight = 16;
47 const int iconWidth = 16;
48 const int iconFilenameSpacing = 2;
49 const int defaultWidthNumChars = 34;
50 const int buttonShadowHeight = 2;
51
52 RenderFileUploadControl::RenderFileUploadControl(HTMLInputElement* input)
53     : RenderBlock(input)
54 {
55 }
56
57 RenderFileUploadControl::~RenderFileUploadControl()
58 {
59 }
60
61 void RenderFileUploadControl::updateFromElement()
62 {
63     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
64     ASSERT(input->isFileUpload());
65
66     if (HTMLInputElement* button = uploadButton())
67         button->setDisabled(!theme()->isEnabled(this));
68
69     // This only supports clearing out the files, but that's OK because for
70     // security reasons that's the only change the DOM is allowed to make.
71     FileList* files = input->files();
72     ASSERT(files);
73     if (files && files->isEmpty())
74         repaint();
75 }
76
77 static int nodeWidth(Node* node)
78 {
79     return node ? node->renderBox()->width() : 0;
80 }
81
82 int RenderFileUploadControl::maxFilenameWidth() const
83 {
84     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
85     return max(0, contentWidth() - nodeWidth(uploadButton()) - afterButtonSpacing
86         - (input->icon() ? iconWidth + iconFilenameSpacing : 0));
87 }
88
89 void RenderFileUploadControl::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
90 {
91     if (style()->visibility() != VISIBLE)
92         return;
93     
94     // Push a clip.
95     GraphicsContextStateSaver stateSaver(*paintInfo.context, false);
96     if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) {
97         LayoutRect clipRect(paintOffset.x() + borderLeft(), paintOffset.y() + borderTop(),
98                          width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop() + buttonShadowHeight);
99         if (clipRect.isEmpty())
100             return;
101         stateSaver.save();
102         paintInfo.context->clip(clipRect);
103     }
104
105     if (paintInfo.phase == PaintPhaseForeground) {
106         const String& displayedFilename = fileTextValue();
107         const Font& font = style()->font();
108         TextRun textRun = constructTextRun(this, font, displayedFilename, style(), TextRun::AllowTrailingExpansion, RespectDirection | RespectDirectionOverride);
109         textRun.disableRoundingHacks();
110
111         // Determine where the filename should be placed
112         LayoutUnit contentLeft = paintOffset.x() + borderLeft() + paddingLeft();
113         HTMLInputElement* button = uploadButton();
114         if (!button)
115             return;
116
117         HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
118         LayoutUnit buttonWidth = nodeWidth(button);
119         LayoutUnit buttonAndIconWidth = buttonWidth + afterButtonSpacing
120             + (input->icon() ? iconWidth + iconFilenameSpacing : 0);
121         LayoutUnit textX;
122         if (style()->isLeftToRightDirection())
123             textX = contentLeft + buttonAndIconWidth;
124         else
125             textX = contentLeft + contentWidth() - buttonAndIconWidth - font.width(textRun);
126         // We want to match the button's baseline
127         RenderButton* buttonRenderer = toRenderButton(button->renderer());
128         LayoutUnit textY = buttonRenderer->absoluteBoundingBoxRect().y()
129             + buttonRenderer->baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine);
130
131         paintInfo.context->setFillColor(style()->visitedDependentColor(CSSPropertyColor), style()->colorSpace());
132         
133         // Draw the filename
134         paintInfo.context->drawBidiText(font, textRun, LayoutPoint(textX, textY));
135         
136         if (input->icon()) {
137             // Determine where the icon should be placed
138             LayoutUnit iconY = paintOffset.y() + borderTop() + paddingTop() + (contentHeight() - iconHeight) / 2;
139             LayoutUnit iconX;
140             if (style()->isLeftToRightDirection())
141                 iconX = contentLeft + buttonWidth + afterButtonSpacing;
142             else
143                 iconX = contentLeft + contentWidth() - buttonWidth - afterButtonSpacing - iconWidth;
144
145             // Draw the file icon
146             input->icon()->paint(paintInfo.context, LayoutRect(iconX, iconY, iconWidth, iconHeight));
147         }
148     }
149
150     // Paint the children.
151     RenderBlock::paintObject(paintInfo, paintOffset);
152 }
153
154 void RenderFileUploadControl::computePreferredLogicalWidths()
155 {
156     ASSERT(preferredLogicalWidthsDirty());
157
158     m_minPreferredLogicalWidth = 0;
159     m_maxPreferredLogicalWidth = 0;
160
161     RenderStyle* style = this->style();
162     ASSERT(style);
163
164     const Font& font = style->font();
165     if (style->width().isFixed() && style->width().value() > 0)
166         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style->width().value());
167     else {
168         // Figure out how big the filename space needs to be for a given number of characters
169         // (using "0" as the nominal character).
170         const UChar ch = '0';
171         const String str = String(&ch, 1);
172         float charWidth = font.width(constructTextRun(this, font, str, style, TextRun::AllowTrailingExpansion));
173         m_maxPreferredLogicalWidth = (int)ceilf(charWidth * defaultWidthNumChars);
174     }
175
176     if (style->minWidth().isFixed() && style->minWidth().value() > 0) {
177         m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style->minWidth().value()));
178         m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style->minWidth().value()));
179     } else if (style->width().isPercent() || (style->width().isAuto() && style->height().isPercent()))
180         m_minPreferredLogicalWidth = 0;
181     else
182         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth;
183
184     if (style->maxWidth().isFixed() && style->maxWidth().value() != undefinedLength) {
185         m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style->maxWidth().value()));
186         m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style->maxWidth().value()));
187     }
188
189     int toAdd = borderAndPaddingWidth();
190     m_minPreferredLogicalWidth += toAdd;
191     m_maxPreferredLogicalWidth += toAdd;
192
193     setPreferredLogicalWidthsDirty(false);
194 }
195
196 VisiblePosition RenderFileUploadControl::positionForPoint(const LayoutPoint&)
197 {
198     return VisiblePosition();
199 }
200
201 HTMLInputElement* RenderFileUploadControl::uploadButton() const
202 {
203     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
204
205     ASSERT(input->shadowRoot());
206
207     Node* buttonNode = input->shadowRoot()->firstChild();
208     return buttonNode && buttonNode->isHTMLElement() && buttonNode->hasTagName(inputTag) ? static_cast<HTMLInputElement*>(buttonNode) : 0;
209 }
210
211 String RenderFileUploadControl::buttonValue()
212 {
213     if (HTMLInputElement* button = uploadButton())
214         return button->value();
215     
216     return String();
217 }
218
219 String RenderFileUploadControl::fileTextValue() const
220 {
221     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
222     ASSERT(input->files());
223     return theme()->fileListNameForWidth(input->files()->paths(), style()->font(), maxFilenameWidth());
224 }
225     
226 } // namespace WebCore