Add zeroLayoutUnit constant.
[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 "ShadowRootList.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     : RenderBlock(input)
55 {
56 }
57
58 RenderFileUploadControl::~RenderFileUploadControl()
59 {
60 }
61
62 void RenderFileUploadControl::updateFromElement()
63 {
64     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
65     ASSERT(input->isFileUpload());
66
67     if (HTMLInputElement* button = uploadButton()) {
68         bool newDisabled = !theme()->isEnabled(this);
69         // We should avoid to call HTMLFormControlElement::setDisabled() as
70         // possible because setAttribute() in setDisabled() can cause style
71         // recalculation, and HTMLFormControlElement::recalcStyle() calls
72         // updateFromElement() eventually.
73         if (button->disabled() != newDisabled)
74             button->setDisabled(newDisabled);
75         
76         button->setActive(input->canReceiveDroppedFiles());
77     }
78
79     // This only supports clearing out the files, but that's OK because for
80     // security reasons that's the only change the DOM is allowed to make.
81     FileList* files = input->files();
82     ASSERT(files);
83     if (files && files->isEmpty())
84         repaint();
85 }
86
87 static int nodeWidth(Node* node)
88 {
89     return node ? node->renderBox()->width() : zeroLayoutUnit;
90 }
91
92 int RenderFileUploadControl::maxFilenameWidth() const
93 {
94     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
95     return max(0, contentWidth() - nodeWidth(uploadButton()) - afterButtonSpacing
96         - (input->icon() ? iconWidth + iconFilenameSpacing : 0));
97 }
98
99 void RenderFileUploadControl::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
100 {
101     if (style()->visibility() != VISIBLE)
102         return;
103     
104     // Push a clip.
105     GraphicsContextStateSaver stateSaver(*paintInfo.context, false);
106     if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) {
107         LayoutRect clipRect(paintOffset.x() + borderLeft(), paintOffset.y() + borderTop(),
108                          width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop() + buttonShadowHeight);
109         if (clipRect.isEmpty())
110             return;
111         stateSaver.save();
112         paintInfo.context->clip(clipRect);
113     }
114
115     if (paintInfo.phase == PaintPhaseForeground) {
116         const String& displayedFilename = fileTextValue();
117         const Font& font = style()->font();
118         TextRun textRun = constructTextRun(this, font, displayedFilename, style(), TextRun::AllowTrailingExpansion, RespectDirection | RespectDirectionOverride);
119         textRun.disableRoundingHacks();
120
121         // Determine where the filename should be placed
122         LayoutUnit contentLeft = paintOffset.x() + borderLeft() + paddingLeft();
123         HTMLInputElement* button = uploadButton();
124         if (!button)
125             return;
126
127         HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
128         LayoutUnit buttonWidth = nodeWidth(button);
129         LayoutUnit buttonAndIconWidth = buttonWidth + afterButtonSpacing
130             + (input->icon() ? iconWidth + iconFilenameSpacing : 0);
131         LayoutUnit textX;
132         if (style()->isLeftToRightDirection())
133             textX = contentLeft + buttonAndIconWidth;
134         else
135             textX = contentLeft + contentWidth() - buttonAndIconWidth - font.width(textRun);
136         // We want to match the button's baseline
137         RenderButton* buttonRenderer = toRenderButton(button->renderer());
138         // FIXME: Make this work with transforms.
139         LayoutUnit textY = buttonRenderer->absoluteBoundingBoxRectIgnoringTransforms().y()
140             + buttonRenderer->baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine);
141
142         paintInfo.context->setFillColor(style()->visitedDependentColor(CSSPropertyColor), style()->colorSpace());
143         
144         // Draw the filename
145         paintInfo.context->drawBidiText(font, textRun, LayoutPoint(textX, textY));
146         
147         if (input->icon()) {
148             // Determine where the icon should be placed
149             LayoutUnit iconY = paintOffset.y() + borderTop() + paddingTop() + (contentHeight() - iconHeight) / 2;
150             LayoutUnit iconX;
151             if (style()->isLeftToRightDirection())
152                 iconX = contentLeft + buttonWidth + afterButtonSpacing;
153             else
154                 iconX = contentLeft + contentWidth() - buttonWidth - afterButtonSpacing - iconWidth;
155
156             // Draw the file icon
157             input->icon()->paint(paintInfo.context, LayoutRect(iconX, iconY, iconWidth, iconHeight));
158         }
159     }
160
161     // Paint the children.
162     RenderBlock::paintObject(paintInfo, paintOffset);
163 }
164
165 void RenderFileUploadControl::computePreferredLogicalWidths()
166 {
167     ASSERT(preferredLogicalWidthsDirty());
168
169     m_minPreferredLogicalWidth = 0;
170     m_maxPreferredLogicalWidth = 0;
171
172     RenderStyle* style = this->style();
173     ASSERT(style);
174
175     const Font& font = style->font();
176     if (style->width().isFixed() && style->width().value() > 0)
177         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style->width().value());
178     else {
179         // Figure out how big the filename space needs to be for a given number of characters
180         // (using "0" as the nominal character).
181         const UChar ch = '0';
182         const String str = String(&ch, 1);
183         float charWidth = font.width(constructTextRun(this, font, str, style, TextRun::AllowTrailingExpansion));
184         m_maxPreferredLogicalWidth = (int)ceilf(charWidth * defaultWidthNumChars);
185     }
186
187     if (style->minWidth().isFixed() && style->minWidth().value() > 0) {
188         m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style->minWidth().value()));
189         m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style->minWidth().value()));
190     } else if (style->width().isPercent() || (style->width().isAuto() && style->height().isPercent()))
191         m_minPreferredLogicalWidth = 0;
192     else
193         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth;
194
195     if (style->maxWidth().isFixed()) {
196         m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style->maxWidth().value()));
197         m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(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 {
214     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
215
216     ASSERT(input->hasShadowRoot());
217
218     Node* buttonNode = input->shadowRootList()->oldestShadowRoot()->firstChild();
219     return buttonNode && buttonNode->isHTMLElement() && buttonNode->hasTagName(inputTag) ? static_cast<HTMLInputElement*>(buttonNode) : 0;
220 }
221
222 String RenderFileUploadControl::buttonValue()
223 {
224     if (HTMLInputElement* button = uploadButton())
225         return button->value();
226     
227     return String();
228 }
229
230 String RenderFileUploadControl::fileTextValue() const
231 {
232     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
233     ASSERT(input->files());
234     return theme()->fileListNameForWidth(input->files()->paths(), style()->font(), maxFilenameWidth(), input->multiple());
235 }
236     
237 } // namespace WebCore