| /* |
| * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| * |
| */ |
| |
| #include "config.h" |
| #include "RenderFileUploadControl.h" |
| |
| #include "Chrome.h" |
| #include "FileList.h" |
| #include "Frame.h" |
| #include "FrameView.h" |
| #include "GraphicsContext.h" |
| #include "HTMLInputElement.h" |
| #include "HTMLNames.h" |
| #include "ShadowElement.h" |
| #include "Icon.h" |
| #include "LocalizedStrings.h" |
| #include "Page.h" |
| #include "PaintInfo.h" |
| #include "RenderButton.h" |
| #include "RenderText.h" |
| #include "RenderTheme.h" |
| #include "RenderView.h" |
| #include "TextRun.h" |
| #include <math.h> |
| |
| using namespace std; |
| |
| namespace WebCore { |
| |
| using namespace HTMLNames; |
| |
| const int afterButtonSpacing = 4; |
| const int iconHeight = 16; |
| const int iconWidth = 16; |
| const int iconFilenameSpacing = 2; |
| const int defaultWidthNumChars = 34; |
| const int buttonShadowHeight = 2; |
| |
| RenderFileUploadControl::RenderFileUploadControl(HTMLInputElement* input) |
| : RenderBlock(input) |
| { |
| FileList* list = input->files(); |
| Vector<String> filenames; |
| unsigned length = list ? list->length() : 0; |
| for (unsigned i = 0; i < length; ++i) |
| filenames.append(list->item(i)->path()); |
| m_fileChooser = FileChooser::create(this, filenames); |
| } |
| |
| RenderFileUploadControl::~RenderFileUploadControl() |
| { |
| if (m_button) |
| m_button->detach(); |
| m_fileChooser->disconnectClient(); |
| } |
| |
| void RenderFileUploadControl::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) |
| { |
| RenderBlock::styleDidChange(diff, oldStyle); |
| if (m_button) |
| m_button->renderer()->setStyle(createButtonStyle(style())); |
| } |
| |
| void RenderFileUploadControl::valueChanged() |
| { |
| // dispatchFormControlChangeEvent may destroy this renderer |
| RefPtr<FileChooser> fileChooser = m_fileChooser; |
| |
| HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node()); |
| inputElement->setFileListFromRenderer(fileChooser->filenames()); |
| inputElement->dispatchFormControlChangeEvent(); |
| |
| // only repaint if it doesn't seem we have been destroyed |
| if (!fileChooser->disconnected()) |
| repaint(); |
| } |
| |
| bool RenderFileUploadControl::allowsMultipleFiles() |
| { |
| #if ENABLE(DIRECTORY_UPLOAD) |
| if (allowsDirectoryUpload()) |
| return true; |
| #endif |
| |
| HTMLInputElement* input = static_cast<HTMLInputElement*>(node()); |
| return input->fastHasAttribute(multipleAttr); |
| } |
| |
| #if ENABLE(DIRECTORY_UPLOAD) |
| bool RenderFileUploadControl::allowsDirectoryUpload() |
| { |
| HTMLInputElement* input = static_cast<HTMLInputElement*>(node()); |
| return input->fastHasAttribute(webkitdirectoryAttr); |
| } |
| |
| void RenderFileUploadControl::receiveDropForDirectoryUpload(const Vector<String>& paths) |
| { |
| if (Chrome* chromePointer = chrome()) |
| chromePointer->enumerateChosenDirectory(paths[0], m_fileChooser.get()); |
| } |
| #endif |
| |
| String RenderFileUploadControl::acceptTypes() |
| { |
| return static_cast<HTMLInputElement*>(node())->accept(); |
| } |
| |
| #if ENABLE(MEDIA_CAPTURE) |
| String RenderFileUploadControl::capture() |
| { |
| return static_cast<HTMLInputElement*>(node())->capture(); |
| } |
| #endif |
| |
| void RenderFileUploadControl::chooseIconForFiles(FileChooser* chooser, const Vector<String>& filenames) |
| { |
| if (Chrome* chromePointer = chrome()) |
| chromePointer->chooseIconForFiles(filenames, chooser); |
| } |
| |
| void RenderFileUploadControl::click() |
| { |
| // Requires a user gesture to open the file dialog. |
| if (!frame() || !frame()->loader()->isProcessingUserGesture()) |
| return; |
| if (Chrome* chromePointer = chrome()) |
| chromePointer->runOpenPanel(frame(), m_fileChooser); |
| } |
| |
| Chrome* RenderFileUploadControl::chrome() const |
| { |
| Frame* frame = node()->document()->frame(); |
| if (!frame) |
| return 0; |
| Page* page = frame->page(); |
| if (!page) |
| return 0; |
| return page->chrome(); |
| } |
| |
| void RenderFileUploadControl::updateFromElement() |
| { |
| HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node()); |
| ASSERT(inputElement->isFileUpload()); |
| |
| if (!m_button) { |
| m_button = ShadowInputElement::create(inputElement); |
| m_button->setType("button"); |
| m_button->setValue(fileButtonChooseFileLabel()); |
| RefPtr<RenderStyle> buttonStyle = createButtonStyle(style()); |
| RenderObject* renderer = m_button->createRenderer(renderArena(), buttonStyle.get()); |
| m_button->setRenderer(renderer); |
| renderer->setStyle(buttonStyle.release()); |
| renderer->updateFromElement(); |
| m_button->setAttached(); |
| m_button->setInDocument(); |
| |
| addChild(renderer); |
| } |
| |
| m_button->setDisabled(!theme()->isEnabled(this)); |
| |
| // This only supports clearing out the files, but that's OK because for |
| // security reasons that's the only change the DOM is allowed to make. |
| FileList* files = inputElement->files(); |
| ASSERT(files); |
| if (files && files->isEmpty() && !m_fileChooser->filenames().isEmpty()) { |
| m_fileChooser->clear(); |
| repaint(); |
| } |
| } |
| |
| int RenderFileUploadControl::maxFilenameWidth() const |
| { |
| return max(0, contentWidth() - m_button->renderBox()->width() - afterButtonSpacing |
| - (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0)); |
| } |
| |
| PassRefPtr<RenderStyle> RenderFileUploadControl::createButtonStyle(const RenderStyle* parentStyle) const |
| { |
| RefPtr<RenderStyle> style = getCachedPseudoStyle(FILE_UPLOAD_BUTTON); |
| if (!style) { |
| style = RenderStyle::create(); |
| if (parentStyle) |
| style->inheritFrom(parentStyle); |
| } |
| |
| // Button text will wrap on file upload controls with widths smaller than the intrinsic button width |
| // without this setWhiteSpace. |
| style->setWhiteSpace(NOWRAP); |
| |
| return style.release(); |
| } |
| |
| void RenderFileUploadControl::paintObject(PaintInfo& paintInfo, int tx, int ty) |
| { |
| if (style()->visibility() != VISIBLE) |
| return; |
| ASSERT(m_fileChooser); |
| |
| // Push a clip. |
| if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) { |
| IntRect clipRect(tx + borderLeft(), ty + borderTop(), |
| width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop() + buttonShadowHeight); |
| if (clipRect.isEmpty()) |
| return; |
| paintInfo.context->save(); |
| paintInfo.context->clip(clipRect); |
| } |
| |
| if (paintInfo.phase == PaintPhaseForeground) { |
| const String& displayedFilename = fileTextValue(); |
| unsigned length = displayedFilename.length(); |
| const UChar* string = displayedFilename.characters(); |
| TextRun textRun(string, length, false, 0, 0, TextRun::AllowTrailingExpansion, !style()->isLeftToRightDirection(), style()->unicodeBidi() == Override); |
| |
| // Determine where the filename should be placed |
| int contentLeft = tx + borderLeft() + paddingLeft(); |
| int buttonAndIconWidth = m_button->renderBox()->width() + afterButtonSpacing |
| + (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0); |
| int textX; |
| if (style()->isLeftToRightDirection()) |
| textX = contentLeft + buttonAndIconWidth; |
| else |
| textX = contentLeft + contentWidth() - buttonAndIconWidth - style()->font().width(textRun); |
| // We want to match the button's baseline |
| RenderButton* buttonRenderer = toRenderButton(m_button->renderer()); |
| int textY = buttonRenderer->absoluteBoundingBoxRect().y() |
| + buttonRenderer->marginTop() + buttonRenderer->borderTop() + buttonRenderer->paddingTop() |
| + buttonRenderer->baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine); |
| |
| paintInfo.context->setFillColor(style()->visitedDependentColor(CSSPropertyColor), style()->colorSpace()); |
| |
| // Draw the filename |
| paintInfo.context->drawBidiText(style()->font(), textRun, IntPoint(textX, textY)); |
| |
| if (m_fileChooser->icon()) { |
| // Determine where the icon should be placed |
| int iconY = ty + borderTop() + paddingTop() + (contentHeight() - iconHeight) / 2; |
| int iconX; |
| if (style()->isLeftToRightDirection()) |
| iconX = contentLeft + m_button->renderBox()->width() + afterButtonSpacing; |
| else |
| iconX = contentLeft + contentWidth() - m_button->renderBox()->width() - afterButtonSpacing - iconWidth; |
| |
| // Draw the file icon |
| m_fileChooser->icon()->paint(paintInfo.context, IntRect(iconX, iconY, iconWidth, iconHeight)); |
| } |
| } |
| |
| // Paint the children. |
| RenderBlock::paintObject(paintInfo, tx, ty); |
| |
| // Pop the clip. |
| if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) |
| paintInfo.context->restore(); |
| } |
| |
| void RenderFileUploadControl::computePreferredLogicalWidths() |
| { |
| ASSERT(preferredLogicalWidthsDirty()); |
| |
| m_minPreferredLogicalWidth = 0; |
| m_maxPreferredLogicalWidth = 0; |
| |
| if (style()->width().isFixed() && style()->width().value() > 0) |
| m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style()->width().value()); |
| else { |
| // Figure out how big the filename space needs to be for a given number of characters |
| // (using "0" as the nominal character). |
| const UChar ch = '0'; |
| float charWidth = style()->font().width(TextRun(&ch, 1, false, 0, 0, TextRun::AllowTrailingExpansion, false)); |
| m_maxPreferredLogicalWidth = (int)ceilf(charWidth * defaultWidthNumChars); |
| } |
| |
| if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) { |
| m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value())); |
| m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value())); |
| } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent())) |
| m_minPreferredLogicalWidth = 0; |
| else |
| m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth; |
| |
| if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) { |
| m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value())); |
| m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value())); |
| } |
| |
| int toAdd = borderAndPaddingWidth(); |
| m_minPreferredLogicalWidth += toAdd; |
| m_maxPreferredLogicalWidth += toAdd; |
| |
| setPreferredLogicalWidthsDirty(false); |
| } |
| |
| VisiblePosition RenderFileUploadControl::positionForPoint(const IntPoint&) |
| { |
| return VisiblePosition(); |
| } |
| |
| void RenderFileUploadControl::receiveDroppedFiles(const Vector<String>& paths) |
| { |
| #if ENABLE(DIRECTORY_UPLOAD) |
| if (allowsDirectoryUpload()) { |
| receiveDropForDirectoryUpload(paths); |
| return; |
| } |
| #endif |
| |
| if (allowsMultipleFiles()) |
| m_fileChooser->chooseFiles(paths); |
| else |
| m_fileChooser->chooseFile(paths[0]); |
| } |
| |
| String RenderFileUploadControl::buttonValue() |
| { |
| if (!m_button) |
| return String(); |
| |
| return m_button->value(); |
| } |
| |
| String RenderFileUploadControl::fileTextValue() const |
| { |
| return m_fileChooser->basenameForWidth(style()->font(), maxFilenameWidth()); |
| } |
| |
| } // namespace WebCore |