/*******************************************************************************
* 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;
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() );
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 = null;
this.path = null;
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;
}
}