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