e657967e07c8f99bf860cecb24f23266316a9f6c
[WebKit-https.git] / WebCore / rendering / RenderFileUploadControl.cpp
1 /**
2  *
3  * Copyright (C) 2006 Apple Computer, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public License
16  * along with this library; see the file COPYING.LIB.  If not, write to
17  * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  *
20  */
21
22 #include "config.h"
23 #include "RenderFileUploadControl.h"
24
25 #include "FrameView.h"
26 #include "GraphicsContext.h"
27 #include "HTMLInputElement.h"
28 #include "HTMLNames.h"
29 #include "Icon.h"
30 #include "LocalizedStrings.h"
31 #include "RenderButton.h"
32 #include "RenderText.h"
33 #include "RenderTheme.h"
34 #include "RenderView.h"
35 #include "TextStyle.h"
36 #include <math.h>
37
38 using namespace std;
39
40 namespace WebCore {
41
42 const int afterButtonSpacing = 4;
43 const int iconHeight = 16;
44 const int iconWidth = 16;
45 const int iconFilenameSpacing = 2;
46 const int defaultWidthNumChars = 34;
47
48 using namespace HTMLNames;
49
50 class HTMLFileUploadInnerButtonElement : public HTMLInputElement {
51 public:
52     HTMLFileUploadInnerButtonElement(Document*, Node* shadowParent = 0);
53     
54     virtual RenderObject* createRenderer(RenderArena*, RenderStyle*);
55     
56     virtual bool isShadowNode() const { return true; }
57     
58     virtual Node* shadowParentNode() { return m_shadowParent; }
59     
60 private:
61     Node* m_shadowParent;    
62 };
63
64 RenderFileUploadControl::RenderFileUploadControl(Node* node)
65     : RenderBlock(node)
66     , m_button(0)
67     , m_fileChooser(FileChooser::create(document(), this))
68 {
69 }
70
71 RenderFileUploadControl::~RenderFileUploadControl()
72 {
73     if (m_button)
74         m_button->detach();
75     if (m_fileChooser)
76         m_fileChooser->disconnectUploadControl();
77 }
78
79 void RenderFileUploadControl::setStyle(RenderStyle* newStyle)
80 {
81     // Force text-align to match the direction
82     if (newStyle->direction() == LTR)
83         newStyle->setTextAlign(LEFT);
84     else
85         newStyle->setTextAlign(RIGHT);
86
87     RenderBlock::setStyle(newStyle);
88     if (m_button)
89         m_button->renderer()->setStyle(createButtonStyle(newStyle));
90
91     setReplaced(isInline());
92 }
93
94 void RenderFileUploadControl::valueChanged()
95 {
96     static_cast<HTMLInputElement*>(node())->setValueFromRenderer(m_fileChooser->filename());
97     static_cast<HTMLInputElement*>(node())->onChange();
98     repaint();
99 }
100
101 void RenderFileUploadControl::click(bool sendMouseEvents)
102 {
103     m_fileChooser->openFileChooser();
104 }
105
106 void RenderFileUploadControl::updateFromElement()
107 {
108     if (!m_button) {
109         m_button = new HTMLFileUploadInnerButtonElement(document(), node());
110         RenderStyle* buttonStyle = createButtonStyle(style());
111         m_button->setRenderer(m_button->createRenderer(renderArena(), buttonStyle));
112         m_button->renderer()->setStyle(buttonStyle);
113         static_cast<RenderButton*>(m_button->renderer())->setText(fileButtonChooseFileLabel());
114         m_button->setAttached();
115         m_button->setInDocument(true);
116         
117         addChild(m_button->renderer());
118     }
119 }
120
121 int RenderFileUploadControl::maxFilenameWidth()
122 {
123     return max(0, contentWidth() - m_button->renderer()->width() - afterButtonSpacing - (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0));
124 }
125
126 RenderStyle* RenderFileUploadControl::createButtonStyle(RenderStyle* parentStyle)
127 {
128     RenderStyle* style = getPseudoStyle(RenderStyle::FILE_UPLOAD_BUTTON);
129     if (!style)
130         style = new (renderArena()) RenderStyle();
131
132     if (parentStyle)
133         style->inheritFrom(parentStyle);
134
135     // Button text will wrap on file upload controls with widths smaller than the intrinsic button width
136     // without this setWhiteSpace.
137     style->setWhiteSpace(NOWRAP);
138
139     return style;
140 }
141
142 void RenderFileUploadControl::paintObject(PaintInfo& paintInfo, int tx, int ty)
143 {
144     // Push a clip.
145     if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) {
146         IntRect clipRect(tx + borderLeft(), ty + borderTop(),
147                          width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop());
148         if (clipRect.width() == 0 || clipRect.height() == 0)
149             return;
150         paintInfo.context->save();
151         paintInfo.context->clip(clipRect);
152     }
153
154     if (paintInfo.phase == PaintPhaseForeground) {
155         const String& displayedFilename = m_fileChooser->basenameForWidth(maxFilenameWidth());
156         TextRun textRun(displayedFilename.characters(), displayedFilename.length());
157
158         // Determine where the filename should be placed
159         int contentLeft = tx + borderLeft() + paddingLeft();
160         int buttonAndIconWidth = m_button->renderer()->width() + afterButtonSpacing + (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0);
161         int textX;
162         if (style()->direction() == LTR)
163             textX = contentLeft + buttonAndIconWidth;
164         else
165             textX = contentLeft + contentWidth() - buttonAndIconWidth - style()->font().width(textRun);
166         // We want to match the button's baseline
167         RenderButton* buttonRenderer = static_cast<RenderButton*>(m_button->renderer());
168         int textY = buttonRenderer->absoluteBoundingBoxRect().y() + buttonRenderer->marginTop() + buttonRenderer->borderTop() + buttonRenderer->paddingTop() + buttonRenderer->baselinePosition(true, false);
169
170         paintInfo.context->setFont(style()->font());
171         paintInfo.context->setPen(style()->color());
172
173         // Draw the filename
174         paintInfo.context->drawText(textRun, IntPoint(textX, textY));
175
176         if (m_fileChooser->icon()) {
177             // Determine where the icon should be placed
178             int iconY = ty + borderTop() + paddingTop() + (contentHeight() - iconHeight) / 2;
179             int iconX;
180             if (style()->direction() == LTR)
181                 iconX = contentLeft + m_button->renderer()->width() + afterButtonSpacing;
182             else
183                 iconX = contentLeft + contentWidth() - m_button->renderer()->width() - afterButtonSpacing - iconWidth;
184
185             // Draw the file icon
186             m_fileChooser->icon()->paint(paintInfo.context, IntRect(iconX, iconY, iconWidth, iconHeight));
187         }
188     }
189
190     // Paint the children.
191     RenderBlock::paintObject(paintInfo, tx, ty);
192
193     // Pop the clip.
194     if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds)
195         paintInfo.context->restore();
196 }
197
198 void RenderFileUploadControl::calcMinMaxWidth()
199 {
200     m_minWidth = 0;
201     m_maxWidth = 0;
202
203     if (style()->width().isFixed() && style()->width().value() > 0)
204         m_minWidth = m_maxWidth = calcContentBoxWidth(style()->width().value());
205     else {
206         // Figure out how big the filename space needs to be for a given number of characters
207         // (using "0" as the nominal character).
208         const UChar ch = '0';
209         float charWidth = style()->font().floatWidth(TextRun(&ch, 1), TextStyle(0, 0, 0, false, false, false));
210         m_maxWidth = (int)ceilf(charWidth * defaultWidthNumChars);
211     }
212
213     if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
214         m_maxWidth = max(m_maxWidth, calcContentBoxWidth(style()->minWidth().value()));
215         m_minWidth = max(m_minWidth, calcContentBoxWidth(style()->minWidth().value()));
216     } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
217         m_minWidth = 0;
218     else
219         m_minWidth = m_maxWidth;
220
221     if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
222         m_maxWidth = min(m_maxWidth, calcContentBoxWidth(style()->maxWidth().value()));
223         m_minWidth = min(m_minWidth, calcContentBoxWidth(style()->maxWidth().value()));
224     }
225
226     int toAdd = paddingLeft() + paddingRight() + borderLeft() + borderRight();
227     m_minWidth += toAdd;
228     m_maxWidth += toAdd;
229
230     setMinMaxKnown();
231 }
232
233 HTMLFileUploadInnerButtonElement::HTMLFileUploadInnerButtonElement(Document* doc, Node* shadowParent)
234     : HTMLInputElement(doc)
235     , m_shadowParent(shadowParent)
236 {
237     setInputType("button");
238 }
239
240 RenderObject* HTMLFileUploadInnerButtonElement::createRenderer(RenderArena* arena, RenderStyle* style)
241 {
242     return HTMLInputElement::createRenderer(arena, style);
243 }
244
245 } // namespace WebCore