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 void RenderFileUploadControl::chooseIconForFiles(FileChooser* chooser, const Vector<String>& filenames) 126 { 127 if (Chrome* chromePointer = chrome()) 128 chromePointer->chooseIconForFiles(filenames, chooser); 129 } 130 131 void 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 140 Chrome* 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 151 void 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 183 int RenderFileUploadControl::maxFilenameWidth() const 184 { 185 return max(0, contentWidth() - m_button->renderBox()->width() - afterButtonSpacing 186 - (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0)); 187 } 188 189 PassRefPtr<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 205 void 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 269 void 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 306 VisiblePosition RenderFileUploadControl::positionForPoint(const IntPoint&) 307 { 308 return VisiblePosition(); 309 } 310 311 void 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 326 String RenderFileUploadControl::buttonValue() 327 { 328 if (!m_button) 329 return String(); 330 331 return m_button->value(); 332 } 333 334 String RenderFileUploadControl::fileTextValue() const 335 { 336 return m_fileChooser->basenameForWidth(style()->font(), maxFilenameWidth()); 337 } 338 339 } // namespace WebCore 340