--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2002-2007 Critical Software S.A. All rights reserved. This
+ * program and the accompanying materials are made available under the terms of
+ * the Eclipse Public License v1.0 which accompanies this distribution, and is
+ * available at http://www.eclipse.org/legal/epl-v10.html Contributors: Tiago
+ * Rodrigues (Critical Software S.A.) - initial implementation Joel Oliveira
+ * (Critical Software S.A.) - initial commit
+ ******************************************************************************/
+package org.eclipse.rwt.widgets;
+
+import java.io.File;
+
+import org.eclipse.rwt.graphics.Graphics;
+import org.eclipse.rwt.internal.theme.IThemeAdapter;
+import org.eclipse.rwt.widgets.internal.uploadkit.IUploadAdapter;
+import org.eclipse.rwt.widgets.internal.uploadkit.UploadThemeAdapter;
+import org.eclipse.rwt.widgets.upload.servlet.*;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.SWTException;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Widget representing an Upload box.
+ *
+ * @author tjarodrigues
+ * @author stefan.roeck
+ */
+public class Upload extends Control {
+ /**
+ * Displays a progress bar inside the widget.
+ */
+ public final static int SHOW_PROGRESS = 1;
+
+ /**
+ * Fires progress events to registered UploadListeners.
+ * If this flag is not set, only the {@link UploadListener#uploadFinished()}
+ * event is fired.
+ * @see UploadListener#uploadInProgress(UploadEvent)
+ */
+ public final static int FIRE_PROGRESS_EVENTS = 4;
+ /**
+ * Displays a upload button next to the browse button.
+ */
+ public final static int SHOW_UPLOAD_BUTTON = 2;
+
+ static {
+ // TODO: [sr] move to extension point if existent
+ // Register FileUploadServiceHandler
+ FileUploadServiceHandler.register();
+ }
+
+ private String lastFileUploaded;
+ private final String servlet;
+ private String path;
+ private boolean performUpload = false;
+ private boolean resetUpload = false;
+ private int flags;
+ private UploadLCAAdapter uploadLCAAdapter;
+ private String browseButtonText = "Browse";
+ private String uploadButtonText = "Upload";
+ private boolean[] uploadInProgresses = { false };
+
+ // avoid exposure of upload internal stuff
+ private final class UploadLCAAdapter implements IUploadAdapter {
+ public boolean performUpload() {
+ boolean result = Upload.this.performUpload;
+ Upload.this.performUpload = false;
+ return result;
+ }
+
+ public int getFlags() {
+ return flags;
+ }
+
+ public void setPath( final String path ) {
+ // TODO: [sr] Frank, why not throw this event within readData of the LCA?
+ // Its quite hidden here :-)
+ if( path != null ) {
+ if( !path.equals( Upload.this.path ) ) {
+ Upload.this.path = path;
+ ModifyEvent modifyEvent = new ModifyEvent( Upload.this );
+ modifyEvent.processEvent();
+ }
+ }
+ }
+
+ public void setLastFileUploaded( final String lastFileUploaded ) {
+ Upload.this.lastFileUploaded = lastFileUploaded;
+ }
+
+ public String getServletPath() {
+ return Upload.this.servlet;
+ }
+
+ public boolean isResetUpload() {
+ return Upload.this.resetUpload;
+ }
+
+ public void setResetUpload( boolean resetUpload ) {
+ Upload.this.resetUpload = resetUpload;
+ }
+
+ public long getBytesRead() {
+ final FileUploadStorageItem uploadStorageItem = FileUploadStorage.getInstance().getUploadStorageItem( getWidgetId());
+ return uploadStorageItem != null ? uploadStorageItem.getBytesRead() : 0L;
+ }
+
+ public FileUploadStorageItem getStorageItem() {
+ return Upload.this.getUploadStorageItem();
+ }
+
+ public long getContentLength() {
+ final FileUploadStorageItem uploadStorageItem = FileUploadStorage.getInstance().getUploadStorageItem( getWidgetId());
+ return uploadStorageItem != null ? uploadStorageItem.getContentLength() : 0L;
+ }
+ }
+
+
+ /**
+ * Initializes the Upload.
+ *
+ * @param parent Parent container.
+ * @param style Widget style.
+ * @param servlet The upload servlet name.
+ * @param showProgress Indicates if the progress bar should be visible.
+ * @deprecated use Upload(Composite, int, int) instead
+ */
+ public Upload( final Composite parent,
+ final int style,
+ final String servlet,
+ final boolean showProgress )
+ {
+ this( parent,
+ style,
+ servlet,
+ ( showProgress ? SHOW_PROGRESS : 0 ) | SHOW_UPLOAD_BUTTON );
+ }
+
+ /**
+ * @deprecated use Upload(Composite, int, int) instead
+ */
+ public Upload( final Composite parent,
+ final int style,
+ final String servlet )
+ {
+ this( parent, style, servlet, 0 );
+ }
+
+ /**
+ * @deprecated use Upload(Composite, int, int) instead
+ */
+ public Upload( final Composite parent,
+ final int style,
+ final String servlet,
+ final int flags )
+ {
+ super( parent, style );
+ this.servlet = ( ( servlet == null ) ? FileUploadServiceHandler.getUrl(getWidgetId()) : servlet );
+ this.flags = flags;
+
+ if ((this.flags & SHOW_PROGRESS) > 0) {
+ this.flags |= FIRE_PROGRESS_EVENTS;
+ }
+
+ this.lastFileUploaded = "";
+ this.path = "";
+
+ // Add a fileStorage item which is used for transfering the uploaded file
+ FileUploadStorage.getInstance().setUploadStorageItem( getWidgetId(), new FileUploadStorageItem() );
+ }
+
+ /**
+ * Constructs a upload widget.
+ * @param style Supported styles:
+ * {@link SWT#BORDER}
+ * @param flags supported flags:
+ * {@link Upload#SHOW_PROGRESS}
+ * {@link Upload#SHOW_UPLOAD_BUTTON}
+ * {@link Upload#FIRE_PROGRESS_EVENTS}
+ * The SHOW_PROGRESS flag implies the flag FIRE_PROGRESS_EVENTS.
+ */
+ public Upload( final Composite parent,
+ final int style,
+ final int flags )
+ {
+ this (parent, style, null, flags);
+ }
+
+ /**
+ * Convenience constructor for creating an upload widget without upload
+ * button and progress bar. Same as {@link Upload(parent,int,int)} with 0 as
+ * value for the flags parameter.
+ */
+ public Upload( final Composite parent,
+ final int style )
+ {
+ this( parent, style, null, 0 );
+ }
+
+
+ /**
+ * Gets the servlet.
+ *
+ * @return Servlet name.
+ * @deprecated This method will be removed in a future version as the servlet
+ * is only used internally and cannot be set from outside anymore.
+ */
+ public String getServlet() {
+ checkWidget();
+ return servlet;
+ }
+
+ /**
+ * Returns the full file name of the last
+ * uploaded file including the file path as
+ * selected by the user on his local machine.
+ * <br>
+ * The full path including the directory and file
+ * drive are only returned, if the browser supports
+ * reading this properties. In Firefox 3, only
+ * the filename is returned.
+ * @see Upload#getLastFileUploaded()
+ */
+ public String getPath() {
+ checkWidget();
+ return path;
+ }
+
+ /**
+ * Triggers a file upload. This method immediately returns, if the user hasn't
+ * selected a file, yet. Otherwise, a upload is triggered on the Browser side.
+ * This method returns, if the upload has finished.
+ * <br/>
+ * Note: This method doesn't fire exceptions. Instead, see {@link #addUploadListener(UploadListener)}
+ * and {@link UploadEvent#getUploadException()} on how to get notified about exceptions during upload.
+ * @return <code>true</code> if the upload has been started and has finished
+ * without an error.
+ * @see Upload#addUploadListener(UploadListener)
+ */
+ public boolean performUpload() {
+ checkWidget();
+
+ // Clean state (from previous uploads)
+ resetStorageItem();
+
+ final boolean uploadSuccessful[] = {false};
+ // Always check if user selected a file because otherwise the UploadWidget itself doesn't trigger a POST and therefore, the
+ // subsequent loop never terminates.
+ if (getPath() != null && !"".equals( getPath() )) {
+ if( isEnabled() && !uploadInProgresses[ 0 ] ) {
+ performUpload = true;
+ UploadListener listener = new UploadAdapter() {
+ public void uploadFinished(UploadEvent event) {
+ uploadInProgresses[ 0 ] = false;
+ uploadSuccessful[ 0 ] = true;
+ }
+
+ public void uploadException( UploadEvent uploadEvent ) {
+ uploadInProgresses[ 0 ] = false;
+ }
+ };
+ addUploadListener( listener );
+ uploadInProgresses[ 0 ] = true;
+ try {
+ while( uploadInProgresses[ 0 ] && !isDisposed()) {
+ if( !getDisplay().readAndDispatch() ) {
+ getDisplay().sleep();
+ }
+ }
+ } finally {
+ uploadInProgresses[ 0 ] = false;
+ performUpload = false;
+ // 324732: [upload] Widget is disposed if widget is disposed while upload is in progress
+ // https://bugs.eclipse.org/bugs/show_bug.cgi?id=324732
+ if (!isDisposed()) {
+ removeUploadListener( listener );
+ }
+ }
+ }
+
+ }
+ return uploadSuccessful[ 0 ];
+ }
+
+ public Object getAdapter( final Class adapter ) {
+ Object result;
+ if( adapter == IUploadAdapter.class ) {
+ if( uploadLCAAdapter == null ) {
+ uploadLCAAdapter = new UploadLCAAdapter();
+ }
+ result = uploadLCAAdapter;
+ } else {
+ result = super.getAdapter( adapter );
+ }
+ return result;
+ }
+
+ // TODO [fappel]: improve this preliminary compute size implementation
+ public Point computeSize( final int wHint,
+ final int hHint,
+ final boolean changed )
+ {
+ Point browseButtonSize = computeBrowseButtonSize();
+
+ int browseButtonHeight = browseButtonSize.y;
+ int progressHeight = 20;
+
+ int height = 0, width = 0;
+ if( wHint == SWT.DEFAULT || hHint == SWT.DEFAULT ) {
+ if( ( ( flags & SHOW_PROGRESS ) > 0 )
+ && ( ( flags & SHOW_UPLOAD_BUTTON ) > 0 ) )
+ {
+ // progress bar and upload button visible
+ width = computeBaseWidth();
+ final Point textExtent = Graphics.stringExtent( getFont(), getUploadButtonText());
+ width += textExtent.x;
+
+ height = Math.max( computeBaseHeight(),
+ Math.max( textExtent.y, browseButtonHeight ) );
+
+ height += progressHeight;
+
+ } else if( ( flags & SHOW_PROGRESS ) > 0 ) {
+ // progress bar visible
+ width = computeBaseWidth();
+ height = Math.max( computeBaseHeight(), browseButtonHeight );
+ height += progressHeight;
+
+ } else if( ( flags & SHOW_UPLOAD_BUTTON ) > 0 ) {
+ // upload button visible
+ width = computeBaseWidth();
+
+ final Point textExtent = Graphics.stringExtent( getFont(), getUploadButtonText());
+ width += textExtent.x;
+
+ height = Math.max( computeBaseHeight(),
+ Math.max( textExtent.y, browseButtonHeight ) );
+
+ } else {
+ // no progress bar and no upload button visible
+ width = computeBaseWidth();
+ height = Math.max( computeBaseHeight(), browseButtonHeight );
+ }
+ }
+
+ if( wHint != SWT.DEFAULT ) {
+ width = wHint;
+ }
+
+ if( hHint != SWT.DEFAULT ) {
+ height = hHint;
+ }
+
+ return new Point( width, height +2);
+ }
+
+ private int computeBaseHeight() {
+ return Graphics.getCharHeight( getFont() );
+ }
+
+ /**
+ * These lines are copied from {@link Button#computeSize(int, int)}.
+ * TODO: [sr] Find a better solution to avoid this code duplication...
+ */
+ private Point computeBrowseButtonSize() {
+ final int border = getButtonBorder();
+ int width = 0, height = 0;
+
+ final Point extent = Graphics.stringExtent( getFont(), getBrowseButtonText() );
+ height = Math.max( height, extent.y );
+ width += extent.x;
+
+ width += 12;
+ height += 10;
+
+ width += border * 2;
+ height += border * 2;
+ return new Point( width, height );
+ }
+
+ private int getButtonBorder() {
+ UploadThemeAdapter themeAdapter
+ = ( UploadThemeAdapter )getAdapter( IThemeAdapter.class );
+ return themeAdapter.getButtonBorderWidth( this );
+ }
+
+ private int computeBaseWidth() {
+ float avgCharWidth = Graphics.getAvgCharWidth( getFont() );
+ return ( int )( avgCharWidth * 50 );
+ }
+
+ /**
+ * Set the text of the browse button.
+ */
+ public void setBrowseButtonText( final String browseButtonText ) {
+ checkWidget();
+ if( browseButtonText == null ) {
+ SWT.error( SWT.ERROR_NULL_ARGUMENT );
+ }
+ this.browseButtonText = browseButtonText;
+ }
+
+ /**
+ * Returns the text of the browse button.
+ */
+ public String getBrowseButtonText() {
+ checkWidget();
+ return browseButtonText;
+ }
+
+ /**
+ * Sets the text of the upload button. Only applies, if {@link #SHOW_UPLOAD_BUTTON}
+ * is set as style.
+ * @param Text for the upload button, must not be <code>null</code>.
+ * @see #Upload(Composite, int, int)
+ */
+ public void setUploadButtonText( final String uploadButtonText ) {
+ checkWidget();
+ if( uploadButtonText == null ) {
+ SWT.error( SWT.ERROR_NULL_ARGUMENT );
+ }
+ this.uploadButtonText = uploadButtonText;
+ }
+
+ /**
+ * Returns the text of the upload button. Can return <code>null</code>.
+ */
+ public String getUploadButtonText() {
+ checkWidget();
+ return uploadButtonText;
+ }
+
+ /**
+ * Adds the listener to the collection of listeners who will
+ * be notified when the receiver's path is modified, by sending
+ * it one of the messages defined in the <code>ModifyListener</code>
+ * interface.
+ *
+ * @param listener the listener which should be notified
+ *
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
+ * </ul>
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see ModifyListener
+ * @see #removeModifyListener
+ */
+ public void addModifyListener( final ModifyListener listener ) {
+ checkWidget();
+ ModifyEvent.addListener( this, listener );
+ }
+
+ /**
+ * Removes the listener from the collection of listeners who will
+ * be notified when the receiver's path is modified.
+ *
+ * @param listener the listener which should no longer be notified
+ *
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
+ * </ul>
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see ModifyListener
+ * @see #addModifyListener
+ */
+ public void removeModifyListener( final ModifyListener listener ) {
+ checkWidget();
+ ModifyEvent.removeListener( this, listener );
+ }
+
+ /**
+ * Gets the name of the last uploaded file. This method
+ * can be called even if the upload has not finished yet.
+ * @see Upload#getPath()
+ *
+ * @return The name of the last uploaded file.
+ */
+ public String getLastFileUploaded() {
+ checkWidget();
+ return lastFileUploaded;
+ }
+
+ /**
+ * Returns the <code>java.io.File<code> that represents the absolute
+ * path to the last uploaded file disk.
+ *
+ * @return The <code>java.io.File<code> that represents the absolute
+ * path to the last uploaded file disk or null if no file was uploaded. The
+ * latter may be the case if the path entered by the user doesn't exist.
+ * @deprecated This method is no longer supported and always returns null.
+ * Please use {@link Upload#getUploadItem()} instead.
+ */
+ public File getLastUploadedFile() {
+ checkWidget();
+ return null;
+// HttpSession session = RWT.getSessionStore().getHttpSession();
+// File tmpDir = FileUploadServlet.getUploadTempDir( session );
+// File result = new File( tmpDir, lastFileUploaded );
+// if( !result.exists() ) {
+// result = null;
+// }
+// return result;
+ }
+
+
+ /**
+ * After uploading has finished this method returns the uploaded file
+ * and all available meta data, as file name, content type, etc.
+ * @throws SWTException SWT.ERROR_WIDGET_DISPOSED if widget is disposed.
+ */
+ public UploadItem getUploadItem() {
+ checkWidget();
+
+ // TODO: [sr] remove if implemented in Widget#checkWidget()
+ if (isDisposed()) {
+ SWT.error( SWT.ERROR_WIDGET_DISPOSED );
+ }
+
+ final FileUploadStorageItem uploadedFile = getUploadStorageItem();
+ final UploadItem uploadItem = new UploadItem( uploadedFile.getFileInputStream(),
+ uploadedFile.getContentType(),
+ getLastFileUploaded(),
+ getPath(),
+ uploadedFile.getContentLength());
+ return uploadItem;
+ }
+
+ private String getWidgetId() {
+ return String.valueOf(this.hashCode());
+ }
+
+ /**
+ * Sets the name of the last uploaded file.
+ *
+ * @param lastFileUploaded The name of the last uploaded file.
+ * @deprecated This method should not be used and will be removed
+ * in a future version because the semantics don't make sense.
+ */
+ public void setLastFileUploaded( final String lastFileUploaded ) {
+ checkWidget();
+ this.lastFileUploaded = lastFileUploaded;
+ }
+
+ /**
+ * Adds a new Listener to the Upload.
+ *
+ * @param listener The new listener.
+ */
+ public void addUploadListener( final UploadListener listener ) {
+ checkWidget();
+ UploadEvent.addListener( this, listener );
+
+ }
+
+ /**
+ * Removes a Listener from the Upload.
+ *
+ * @param listener The new listener.
+ */
+ public void removeUploadListener( final UploadListener listener ) {
+ checkWidget();
+ UploadEvent.removeListener( this, listener );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void dispose() {
+ FileUploadStorage.getInstance().setUploadStorageItem( getWidgetId(), null );
+ super.dispose();
+ }
+
+ /**
+ * Resets the internal state of the widget so that all information about the last
+ * uploaded file are lost. Additionally the text and the progressbar (if visible)
+ * are reset to the defaults.
+ */
+ public void reset() {
+ checkWidget();
+
+ this.lastFileUploaded = "";
+ this.path = "";
+
+ resetStorageItem();
+
+ resetUpload = true;
+ }
+
+ /**
+ * Resets the internal storage item that is used to transfer the file content
+ * and progress between widget and ServiceHandler.
+ */
+ private void resetStorageItem() {
+ final FileUploadStorageItem storageItem = FileUploadStorage.getInstance().getUploadStorageItem( getWidgetId() );
+ storageItem.reset();
+ }
+
+ /**
+ * Returns a configuration facade. Note that this configuration
+ * is shared for all update widgets.
+ */
+ public IUploadConfiguration getConfiguration() {
+ return FileUploadServiceHandler.getConfiguration();
+ }
+
+ protected FileUploadStorageItem getUploadStorageItem() {
+ final FileUploadStorage storage = FileUploadStorage.getInstance();
+ final FileUploadStorageItem uploadedFile = storage.getUploadStorageItem( getWidgetId() );
+ return uploadedFile;
+ }
+
+}