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 "Chrome.h" 25 #include "FileList.h" 26 #include "Frame.h" 27 #include "FrameView.h" 28 #include "GraphicsContext.h" 29 #include "HTMLInputElement.h" 30 #include "HTMLNames.h" 31 #include "Icon.h" 32 #include "LocalizedStrings.h" 33 #include "Page.h" 34 #include "RenderButton.h" 35 #include "RenderText.h" 36 #include "RenderTheme.h" 37 #include "RenderView.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 class HTMLFileUploadInnerButtonElement : public HTMLInputElement { 54 public: 55 HTMLFileUploadInnerButtonElement(Document*, Node* shadowParent); 56 57 virtual bool isShadowNode() const { return true; } 58 virtual Node* shadowParentNode() { return m_shadowParent; } 59 60 private: 61 Node* m_shadowParent; 62 }; 63 64 RenderFileUploadControl::RenderFileUploadControl(HTMLInputElement* input) 65 : RenderBlock(input) 66 , m_button(0) 67 { 68 FileList* list = input->files(); 69 Vector<String> filenames; 70 unsigned length = list ? list->length() : 0; 71 for (unsigned i = 0; i < length; ++i) 72 filenames.append(list->item(i)->path()); 73 m_fileChooser = FileChooser::create(this, filenames); 74 } 75 76 RenderFileUploadControl::~RenderFileUploadControl() 77 { 78 if (m_button) 79 m_button->detach(); 80 m_fileChooser->disconnectClient(); 81 } 82 83 void RenderFileUploadControl::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) 84 { 85 RenderBlock::styleDidChange(diff, oldStyle); 86 if (m_button) 87 m_button->renderer()->setStyle(createButtonStyle(style())); 88 89 setReplaced(isInline()); 90 } 91 92 void RenderFileUploadControl::valueChanged() 93 { 94 // dispatchFormControlChangeEvent may destroy this renderer 95 RefPtr<FileChooser> fileChooser = m_fileChooser; 96 97 HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node()); 98 inputElement->setFileListFromRenderer(fileChooser->filenames()); 99 inputElement->dispatchFormControlChangeEvent(); 100 101 // only repaint if it doesn't seem we have been destroyed 102 if (!fileChooser->disconnected()) 103 repaint(); 104 } 105 106 bool RenderFileUploadControl::allowsMultipleFiles() 107 { 108 HTMLInputElement* input = static_cast<HTMLInputElement*>(node()); 109 return !input->getAttribute(multipleAttr).isNull(); 110 } 111 112 String RenderFileUploadControl::acceptTypes() 113 { 114 return static_cast<HTMLInputElement*>(node())->accept(); 115 } 116 117 void RenderFileUploadControl::click() 118 { 119 Frame* frame = node()->document()->frame(); 120 if (!frame) 121 return; 122 Page* page = frame->page(); 123 if (!page) 124 return; 125 page->chrome()->runOpenPanel(frame, m_fileChooser); 126 } 127 128 void RenderFileUploadControl::updateFromElement() 129 { 130 HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node()); 131 ASSERT(inputElement->inputType() == HTMLInputElement::FILE); 132 133 if (!m_button) { 134 m_button = new HTMLFileUploadInnerButtonElement(document(), inputElement); 135 m_button->setInputType("button"); 136 m_button->setValue(fileButtonChooseFileLabel()); 137 RefPtr<RenderStyle> buttonStyle = createButtonStyle(style()); 138 RenderObject* renderer = m_button->createRenderer(renderArena(), buttonStyle.get()); 139 m_button->setRenderer(renderer); 140 renderer->setStyle(buttonStyle.release()); 141 renderer->updateFromElement(); 142 m_button->setAttached(); 143 m_button->setInDocument(true); 144 145 addChild(renderer); 146 } 147 148 m_button->setDisabled(!theme()->isEnabled(this)); 149 150 // This only supports clearing out the files, but that's OK because for 151 // security reasons that's the only change the DOM is allowed to make. 152 FileList* files = inputElement->files(); 153 ASSERT(files); 154 if (files && files->isEmpty() && !m_fileChooser->filenames().isEmpty()) { 155 m_fileChooser->clear(); 156 repaint(); 157 } 158 } 159 160 int RenderFileUploadControl::maxFilenameWidth() const 161 { 162 return max(0, contentWidth() - m_button->renderBox()->width() - afterButtonSpacing 163 - (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0)); 164 } 165 166 PassRefPtr<RenderStyle> RenderFileUploadControl::createButtonStyle(const RenderStyle* parentStyle) const 167 { 168 RefPtr<RenderStyle> style = getCachedPseudoStyle(FILE_UPLOAD_BUTTON); 169 if (!style) { 170 style = RenderStyle::create(); 171 if (parentStyle) 172 style->inheritFrom(parentStyle); 173 } 174 175 // Button text will wrap on file upload controls with widths smaller than the intrinsic button width 176 // without this setWhiteSpace. 177 style->setWhiteSpace(NOWRAP); 178 179 return style.release(); 180 } 181 182 void RenderFileUploadControl::paintObject(PaintInfo& paintInfo, int tx, int ty) 183 { 184 if (style()->visibility() != VISIBLE) 185 return; 186 187 // Push a clip. 188 if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) { 189 IntRect clipRect(tx + borderLeft(), ty + borderTop(), 190 width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop() + buttonShadowHeight); 191 if (clipRect.isEmpty()) 192 return; 193 paintInfo.context->save(); 194 paintInfo.context->clip(clipRect); 195 } 196 197 if (paintInfo.phase == PaintPhaseForeground) { 198 const String& displayedFilename = fileTextValue(); 199 unsigned length = displayedFilename.length(); 200 const UChar* string = displayedFilename.characters(); 201 TextRun textRun(string, length, false, 0, 0, style()->direction() == RTL, style()->unicodeBidi() == Override); 202 203 // Determine where the filename should be placed 204 int contentLeft = tx + borderLeft() + paddingLeft(); 205 int buttonAndIconWidth = m_button->renderBox()->width() + afterButtonSpacing 206 + (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0); 207 int textX; 208 if (style()->direction() == LTR) 209 textX = contentLeft + buttonAndIconWidth; 210 else 211 textX = contentLeft + contentWidth() - buttonAndIconWidth - style()->font().width(textRun); 212 // We want to match the button's baseline 213 RenderButton* buttonRenderer = toRenderButton(m_button->renderer()); 214 int textY = buttonRenderer->absoluteBoundingBoxRect().y() 215 + buttonRenderer->marginTop() + buttonRenderer->borderTop() + buttonRenderer->paddingTop() 216 + buttonRenderer->baselinePosition(true, false); 217 218 paintInfo.context->setFillColor(style()->color(), style()->colorSpace()); 219 220 // Draw the filename 221 paintInfo.context->drawBidiText(style()->font(), textRun, IntPoint(textX, textY)); 222 223 if (m_fileChooser->icon()) { 224 // Determine where the icon should be placed 225 int iconY = ty + borderTop() + paddingTop() + (contentHeight() - iconHeight) / 2; 226 int iconX; 227 if (style()->direction() == LTR) 228 iconX = contentLeft + m_button->renderBox()->width() + afterButtonSpacing; 229 else 230 iconX = contentLeft + contentWidth() - m_button->renderBox()->width() - afterButtonSpacing - iconWidth; 231 232 // Draw the file icon 233 m_fileChooser->icon()->paint(paintInfo.context, IntRect(iconX, iconY, iconWidth, iconHeight)); 234 } 235 } 236 237 // Paint the children. 238 RenderBlock::paintObject(paintInfo, tx, ty); 239 240 // Pop the clip. 241 if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) 242 paintInfo.context->restore(); 243 } 244 245 void RenderFileUploadControl::calcPrefWidths() 246 { 247 ASSERT(prefWidthsDirty()); 248 249 m_minPrefWidth = 0; 250 m_maxPrefWidth = 0; 251 252 if (style()->width().isFixed() && style()->width().value() > 0) 253 m_minPrefWidth = m_maxPrefWidth = calcContentBoxWidth(style()->width().value()); 254 else { 255 // Figure out how big the filename space needs to be for a given number of characters 256 // (using "0" as the nominal character). 257 const UChar ch = '0'; 258 float charWidth = style()->font().floatWidth(TextRun(&ch, 1, false, 0, 0, false, false, false)); 259 m_maxPrefWidth = (int)ceilf(charWidth * defaultWidthNumChars); 260 } 261 262 if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) { 263 m_maxPrefWidth = max(m_maxPrefWidth, calcContentBoxWidth(style()->minWidth().value())); 264 m_minPrefWidth = max(m_minPrefWidth, calcContentBoxWidth(style()->minWidth().value())); 265 } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent())) 266 m_minPrefWidth = 0; 267 else 268 m_minPrefWidth = m_maxPrefWidth; 269 270 if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) { 271 m_maxPrefWidth = min(m_maxPrefWidth, calcContentBoxWidth(style()->maxWidth().value())); 272 m_minPrefWidth = min(m_minPrefWidth, calcContentBoxWidth(style()->maxWidth().value())); 273 } 274 275 int toAdd = paddingLeft() + paddingRight() + borderLeft() + borderRight(); 276 m_minPrefWidth += toAdd; 277 m_maxPrefWidth += toAdd; 278 279 setPrefWidthsDirty(false); 280 } 281 282 void RenderFileUploadControl::receiveDroppedFiles(const Vector<String>& paths) 283 { 284 if (allowsMultipleFiles()) 285 m_fileChooser->chooseFiles(paths); 286 else 287 m_fileChooser->chooseFile(paths[0]); 288 } 289 290 String RenderFileUploadControl::buttonValue() 291 { 292 if (!m_button) 293 return String(); 294 295 return m_button->value(); 296 } 297 298 String RenderFileUploadControl::fileTextValue() const 299 { 300 return m_fileChooser->basenameForWidth(style()->font(), maxFilenameWidth()); 301 } 302 303 HTMLFileUploadInnerButtonElement::HTMLFileUploadInnerButtonElement(Document* doc, Node* shadowParent) 304 : HTMLInputElement(inputTag, doc) 305 , m_shadowParent(shadowParent) 306 { 307 } 308 309 } // namespace WebCore 310