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