WebCore/rendering/RenderFileUploadControl.cpp
changeset 0 4f2f89ce4247
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebCore/rendering/RenderFileUploadControl.cpp	Fri Sep 17 09:02:29 2010 +0300
@@ -0,0 +1,318 @@
+/*
+ * 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 "RenderButton.h"
+#include "RenderText.h"
+#include "RenderTheme.h"
+#include "RenderView.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()));
+
+    setReplaced(isInline());
+}
+
+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->getAttribute(multipleAttr).isNull();
+}
+
+#if ENABLE(DIRECTORY_UPLOAD)
+bool RenderFileUploadControl::allowsDirectoryUpload()
+{
+    HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
+    return !input->getAttribute(webkitdirectoryAttr).isNull();
+}
+#endif
+
+String RenderFileUploadControl::acceptTypes()
+{
+    return static_cast<HTMLInputElement*>(node())->accept();
+}
+
+void RenderFileUploadControl::chooseIconForFiles(FileChooser* chooser, const Vector<String>& filenames)
+{
+    if (Chrome* chromePointer = chrome())
+        chromePointer->chooseIconForFiles(filenames, chooser);
+}
+
+void RenderFileUploadControl::click()
+{
+    if (Chrome* chromePointer = chrome())
+        chromePointer->runOpenPanel(node()->document()->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->inputType() == HTMLInputElement::FILE);
+    
+    if (!m_button) {
+        m_button = ShadowInputElement::create(inputElement);
+        m_button->setInputType("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, style()->direction() == RTL, 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()->direction() == LTR)
+            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(true, false);
+
+        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()->direction() == LTR)
+                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::calcPrefWidths()
+{
+    ASSERT(prefWidthsDirty());
+
+    m_minPrefWidth = 0;
+    m_maxPrefWidth = 0;
+
+    if (style()->width().isFixed() && style()->width().value() > 0)
+        m_minPrefWidth = m_maxPrefWidth = calcContentBoxWidth(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().floatWidth(TextRun(&ch, 1, false, 0, 0, false, false, false));
+        m_maxPrefWidth = (int)ceilf(charWidth * defaultWidthNumChars);
+    }
+
+    if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
+        m_maxPrefWidth = max(m_maxPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
+        m_minPrefWidth = max(m_minPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
+    } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
+        m_minPrefWidth = 0;
+    else
+        m_minPrefWidth = m_maxPrefWidth;
+
+    if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
+        m_maxPrefWidth = min(m_maxPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
+        m_minPrefWidth = min(m_minPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
+    }
+
+    int toAdd = borderAndPaddingWidth();
+    m_minPrefWidth += toAdd;
+    m_maxPrefWidth += toAdd;
+
+    setPrefWidthsDirty(false);
+}
+
+void RenderFileUploadControl::receiveDroppedFiles(const Vector<String>& paths)
+{
+    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