WebCore:
[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(new FileChooser(document(), this))
68 {
69 }
70
71 RenderFileUploadControl::~RenderFileUploadControl()
72 {
73     if (m_button)
74         m_button->detach();
75     if (m_fileChooser) {
76         m_fileChooser->uploadControlDetaching();
77         delete m_fileChooser;
78     }
79 }
80
81 void RenderFileUploadControl::setStyle(RenderStyle* s)
82 {
83     // Force text-align to match the direction
84     if (s->direction() == LTR)
85         s->setTextAlign(LEFT);
86     else
87         s->setTextAlign(RIGHT);
88     
89     RenderBlock::setStyle(s);
90     if (m_button)
91         m_button->renderer()->setStyle(createButtonStyle(s));
92
93     setReplaced(isInline());
94 }
95
96 void RenderFileUploadControl::valueChanged()
97 {
98     static_cast<HTMLInputElement*>(node())->setValueFromRenderer(m_fileChooser->filename());
99     static_cast<HTMLInputElement*>(node())->onChange();
100     repaint();
101 }
102
103 void RenderFileUploadControl::click(bool sendMouseEvents)
104 {
105     m_fileChooser->openFileChooser();
106 }
107
108 void RenderFileUploadControl::updateFromElement()
109 {
110     if (!m_button) {
111         m_button = new HTMLFileUploadInnerButtonElement(document(), node());
112         RenderStyle* buttonStyle = createButtonStyle(style());
113         m_button->setRenderer(m_button->createRenderer(renderArena(), buttonStyle));
114         m_button->renderer()->setStyle(buttonStyle);
115         static_cast<RenderButton*>(m_button->renderer())->setText(fileButtonChooseFileLabel());
116         m_button->setAttached();
117         m_button->setInDocument(true);
118         
119         addChild(m_button->renderer());
120     }
121 }
122
123 int RenderFileUploadControl::maxFilenameWidth()
124 {
125     return max(0, contentWidth() - m_button->renderer()->width() - afterButtonSpacing - (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0));
126 }
127
128 RenderStyle* RenderFileUploadControl::createButtonStyle(RenderStyle* parentStyle)
129 {
130     RenderStyle* style = getPseudoStyle(RenderStyle::FILE_UPLOAD_BUTTON);
131     if (!style)
132         style = new (renderArena()) RenderStyle();
133
134     if (parentStyle)
135         style->inheritFrom(parentStyle);
136
137     // Button text will wrap on file upload controls with widths smaller than the intrinsic button width
138     // without this setWhiteSpace.
139     style->setWhiteSpace(NOWRAP);
140
141     return style;
142 }
143
144 void RenderFileUploadControl::paintObject(PaintInfo& i, int tx, int ty)
145 {
146     // Push a clip.
147     if (i.phase == PaintPhaseForeground || i.phase == PaintPhaseChildBlockBackgrounds) {
148         IntRect clipRect(tx + borderLeft(), ty + borderTop(),
149                          width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop());
150         if (clipRect.width() == 0 || clipRect.height() == 0)
151             return;
152         i.p->save();
153         i.p->addClip(clipRect);
154     }
155     
156     if (i.phase == PaintPhaseForeground) {
157         const String& displayedFilename = m_fileChooser->basenameForWidth(maxFilenameWidth());
158         TextRun textRun(displayedFilename.characters(), displayedFilename.length());
159         
160         // Determine where the filename should be placed
161         int contentLeft = tx + borderLeft() + paddingLeft();
162         int buttonAndIconWidth = m_button->renderer()->width() + afterButtonSpacing + (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0);
163         int textX;
164         if (style()->direction() == LTR)
165             textX = contentLeft + buttonAndIconWidth;
166         else
167             textX = contentLeft + contentWidth() - buttonAndIconWidth - style()->font().width(textRun);
168         // We want to match the button's baseline
169         RenderButton* buttonRenderer = static_cast<RenderButton*>(m_button->renderer());
170         int textY = buttonRenderer->absoluteBoundingBoxRect().y() + buttonRenderer->marginTop() + buttonRenderer->borderTop() + buttonRenderer->paddingTop() + buttonRenderer->baselinePosition(true, false);
171         
172         i.p->setFont(style()->font());
173         i.p->setPen(style()->color());
174         
175         // Draw the filename
176         i.p->drawText(textRun, IntPoint(textX, textY));
177         
178         if (m_fileChooser->icon()) {
179             // Determine where the icon should be placed
180             int iconY = ty + borderTop() + paddingTop() + (contentHeight() - iconHeight) / 2;
181             int iconX;
182             if (style()->direction() == LTR)
183                 iconX = contentLeft + m_button->renderer()->width() + afterButtonSpacing;
184             else
185                 iconX = contentLeft + contentWidth() - m_button->renderer()->width() - afterButtonSpacing - iconWidth;
186             
187             // Draw the file icon
188             m_fileChooser->icon()->paint(i.p, IntRect(iconX, iconY, iconWidth, iconHeight));
189         }
190     }
191     
192     // Paint the children.
193     RenderBlock::paintObject(i, tx, ty);
194     
195     // Pop the clip.
196     if (i.phase == PaintPhaseForeground || i.phase == PaintPhaseChildBlockBackgrounds)
197         i.p->restore();
198 }
199
200 void RenderFileUploadControl::calcMinMaxWidth()
201 {
202     m_minWidth = 0;
203     m_maxWidth = 0;
204     
205     if (style()->width().isFixed() && style()->width().value() > 0)
206         m_minWidth = m_maxWidth = calcContentBoxWidth(style()->width().value());
207     else {
208         // Figure out how big the filename space needs to be for a given number of characters
209         // (using "0" as the nominal character).
210         const UChar ch = '0';
211         float charWidth = style()->font().floatWidth(TextRun(&ch, 1), TextStyle(0, 0, 0, false, false, false));
212         m_maxWidth = (int)ceilf(charWidth * defaultWidthNumChars);
213     }
214     
215     if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
216         m_maxWidth = max(m_maxWidth, calcContentBoxWidth(style()->minWidth().value()));
217         m_minWidth = max(m_minWidth, calcContentBoxWidth(style()->minWidth().value()));
218     } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
219         m_minWidth = 0;
220     else
221         m_minWidth = m_maxWidth;
222     
223     if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
224         m_maxWidth = min(m_maxWidth, calcContentBoxWidth(style()->maxWidth().value()));
225         m_minWidth = min(m_minWidth, calcContentBoxWidth(style()->maxWidth().value()));
226     }
227     
228     int toAdd = paddingLeft() + paddingRight() + borderLeft() + borderRight();
229     m_minWidth += toAdd;
230     m_maxWidth += toAdd;
231     
232     setMinMaxKnown();
233 }
234
235 HTMLFileUploadInnerButtonElement::HTMLFileUploadInnerButtonElement(Document* doc, Node* shadowParent)
236     : HTMLInputElement(doc)
237     , m_shadowParent(shadowParent)
238 {
239     setInputType("button");
240 }
241
242 RenderObject* HTMLFileUploadInnerButtonElement::createRenderer(RenderArena* arena, RenderStyle* style)
243 {
244     return HTMLInputElement::createRenderer(arena, style);
245 }
246
247 }