blob: a68c1a139ad7300b165cec0ef458682a2c91db4d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2013 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
* Austin Riddle (Texas Center for Applied Technology) - RAP implementation
* EclipseSource - ongoing development
*******************************************************************************/
package org.eclipse.swt.widgets;
import java.io.File;
import java.util.ArrayList;
import org.eclipse.rap.rwt.graphics.Graphics;
import org.eclipse.rap.rwt.internal.textsize.TextSizeUtil;
import org.eclipse.rap.rwt.lifecycle.UICallBack;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.internal.widgets.ExtensionValidationStrategy;
import org.eclipse.swt.internal.widgets.ProgressCollector;
import org.eclipse.swt.internal.widgets.UploadPanel;
import org.eclipse.swt.internal.widgets.ValidationHandler;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
/**
* Instances of this class allow the user to navigate the file system and select
* a file name. The selected file will be uploaded to the server and the path
* made available as the result of the dialog.
* <dl>
* <dt><b>Styles:</b></dt>
* <dd><!--SAVE, OPEN,--> MULTI</dd>
* <dt><b>Events:</b></dt>
* <dd>(none)</dd>
* </dl>
* <p>
* The OPEN style is applied by default and setting any other styles has no
* effect. <!--Note: Only one of the styles SAVE and OPEN may be specified.-->
* </p>
* <p>
* IMPORTANT: This class is intended to be subclassed <em>only</em> within the
* SWT implementation.
* </p>
*
* @see <a href="http://www.eclipse.org/swt/snippets/#filedialog">FileDialog
* snippets</a>
* @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example:
* ControlExample, Dialog tab</a>
* @see <a href="http://www.eclipse.org/swt/">Sample code and further
* information</a>
*/
@SuppressWarnings("restriction")
public class FileDialog extends Dialog {
private static final long serialVersionUID = 1L;
// RAP implementation fields taken from JFace
private final static int HORIZONTAL_DIALOG_UNIT_PER_CHAR = 4;
private final static int BUTTON_WIDTH = 61;
// SWT FileDialog API fields
String[] filterNames = new String[ 0 ];
String[] filterExtensions = new String[ 0 ];
String[] fileNames = new String[ 0 ];
String filterPath = "";
String fileName;
int filterIndex;
boolean overwrite = false;
// RAP implementation fields
private java.util.List<UploadPanel> uploadPanels;
private Button okButton;
private ProgressBar totalProgressBar;
private Combo filterSelector;
private Button addFileSelectorButton;
private Composite scrollChild;
private Composite uploadsWrapper;
private ScrolledComposite uploadScroller;
private boolean autoUpload;
private boolean uploadLocked;
private ProgressCollector progressCollector;
private ValidationHandler validationHandler;
private Image addImage;
/**
* Constructs a new instance of this class given only its parent.
*
* @param parent a shell which will be the parent of the new instance
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the parent</li>
* <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed
* subclass</li>
* </ul>
*/
public FileDialog( Shell parent ) {
this( parent, SWT.TITLE | SWT.BORDER | SWT.APPLICATION_MODAL );
}
/**
* Constructs a new instance of this class given its parent and a style value
* describing its behavior and appearance.
* <p>
* The style value is either one of the style constants defined in class
* <code>SWT</code> which is applicable to instances of this class, or must be
* built by <em>bitwise OR</em>'ing together (that is, using the
* <code>int</code> "|" operator) two or more of those <code>SWT</code> style
* constants. The class description lists the style constants that are
* applicable to the class. Style bits are also inherited from superclasses.
* </p>
*
* @param parent a shell which will be the parent of the new instance
* @param style the style of dialog to construct
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the parent</li>
* <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed
* subclass</li>
* </ul>
*/
public FileDialog( Shell parent, int style ) {
super( parent, checkStyle( parent, style ) );
checkSubclass();
}
static int checkStyle( Shell parent, int style ) {
int result = style;
int mask = SWT.PRIMARY_MODAL | SWT.APPLICATION_MODAL | SWT.SYSTEM_MODAL;
if( ( result & SWT.SHEET ) != 0 ) {
result &= ~SWT.SHEET;
if( ( result & mask ) == 0 ) {
result |= parent == null ? SWT.APPLICATION_MODAL : SWT.PRIMARY_MODAL;
}
}
if( ( result & mask ) == 0 ) {
result |= SWT.APPLICATION_MODAL;
}
if( ( result & ( SWT.LEFT_TO_RIGHT ) ) == 0 ) {
if( parent != null ) {
if( ( parent.getStyle() & SWT.LEFT_TO_RIGHT ) != 0 ) {
result |= SWT.LEFT_TO_RIGHT;
}
}
}
// [if] force SWT.TITLE as in SWT titlebar is always shown
result |= SWT.TITLE;
// [if] Min button has no sense in RAP
result &= ~SWT.MIN;
return result;
}
/**
* Returns the path of the first file that was selected in the dialog relative
* to the filter path, or an empty string if no such file has been selected.
*
* @return the relative path of the file
*/
public String getFileName() {
return fileName;
}
/**
* Returns a (possibly empty) array with the paths of all files that were
* selected in the dialog relative to the filter path.
*
* @return the relative paths of the files
*/
public String[] getFileNames() {
return fileNames;
}
/**
* Returns the file extensions which the dialog will use to filter the files
* it shows.
*
* @return the file extensions filter
*/
public String[] getFilterExtensions() {
return filterExtensions;
}
/**
* Get the 0-based index of the file extension filter which was selected by
* the user, or -1 if no filter was selected.
* <p>
* This is an index into the FilterExtensions array and the FilterNames array.
* </p>
*
* @return index the file extension filter index
* @see #getFilterExtensions
* @see #getFilterNames
*/
public int getFilterIndex() {
return filterIndex;
}
/**
* Returns the names that describe the filter extensions which the dialog will
* use to filter the files it shows.
*
* @return the list of filter names
*/
public String[] getFilterNames() {
return filterNames;
}
/**
* Returns the directory path that the dialog will use, or an empty string if
* this is not set. File names in this path will appear in the dialog,
* filtered according to the filter extensions.
*
* @return the directory path string
* @see #setFilterExtensions
*/
public String getFilterPath() {
return filterPath;
}
/**
* Returns the flag that the dialog will use to determine whether to prompt
* the user for file overwrite if the selected file already exists.
*
* @return true if the dialog will prompt for file overwrite, false otherwise
*/
public boolean getOverwrite() {
return overwrite;
}
/**
* Set the initial filename which the dialog will select by default when
* opened to the argument, which may be null. The name will be prefixed with
* the filter path when one is supplied.
*
* @param string the file name
*/
public void setFileName( String string ) {
fileName = string;
}
/**
* Set the file extensions which the dialog will use to filter the files it
* shows to the argument, which may be null.
* <p>
* The strings are platform specific. For example, on some platforms, an
* extension filter string is typically of the form "*.extension", where "*.*"
* matches all files. For filters with multiple extensions, use semicolon as a
* separator, e.g. "*.jpg;*.png".
* </p>
*
* @param extensions the file extension filter
* @see #setFilterNames to specify the user-friendly names corresponding to
* the extensions
*/
public void setFilterExtensions( String[] extensions ) {
filterExtensions = extensions;
}
/**
* Set the 0-based index of the file extension filter which the dialog will
* use initially to filter the files it shows to the argument.
* <p>
* This is an index into the FilterExtensions array and the FilterNames array.
* </p>
*
* @param index the file extension filter index
* @see #setFilterExtensions
* @see #setFilterNames
*/
public void setFilterIndex( int index ) {
filterIndex = index;
}
/**
* Sets the names that describe the filter extensions which the dialog will
* use to filter the files it shows to the argument, which may be null.
* <p>
* Each name is a user-friendly short description shown for its corresponding
* filter. The <code>names</code> array must be the same length as the
* <code>extensions</code> array.
* </p>
*
* @param names the list of filter names, or null for no filter names
* @see #setFilterExtensions
*/
public void setFilterNames( String[] names ) {
filterNames = names;
}
/**
* Sets the directory path that the dialog will use to the argument, which may
* be null. File names in this path will appear in the dialog, filtered
* according to the filter extensions. If the string is null, then the
* operating system's default filter path will be used.
* <p>
* Note that the path string is platform dependent. For convenience, either
* '/' or '\' can be used as a path separator.
* </p>
*
* @param string the directory path
* @see #setFilterExtensions
*/
public void setFilterPath( String string ) {
filterPath = string;
}
/**
* Sets the flag that the dialog will use to determine whether to prompt the
* user for file overwrite if the selected file already exists.
*
* @param overwrite true if the dialog will prompt for file overwrite, false
* otherwise
*/
public void setOverwrite( boolean overwrite ) {
this.overwrite = overwrite;
}
/**
* Sets the auto-upload state of the dialog. If true, the dialog will start
* uploading files immediately after they are selected. In any case, if the
* dialog is canceled, files are not made available to the application.
* <p>
* <strong>Note:</strong> this method is <em>not</em> part of the SWT API. It
* only exists in the RWT version.
* </p>
*
* @param autoUpload <code>true</code> to set the dialog into autoupload mode,
* <code>false</code> otherwise
*/
public void setAutoUpload( boolean autoUpload ) {
this.autoUpload = autoUpload;
}
/**
* Returns the auto-upload state of the dialog. If true, the dialog will
* upload files automatically as they are selected.
* <p>
* <strong>Note:</strong> this method is <em>not</em> part of the SWT API. It
* only exists in the RWT version.
* </p>
*
* @return <code>true</code> if the dialog is configured to auto upload files,
* <code>false</code> otherwise
*/
public boolean getAutoUpload() {
return autoUpload;
}
public String open() {
prepareOpen();
runEventLoop( shell );
return fileName;
}
@Override
protected void prepareOpen() {
UICallBack.activate( FileDialog.class.getName() + hashCode() );
initializeDefaults();
createShell();
createControls();
layoutAndCenterShell();
}
private void initializeDefaults() {
uploadPanels = new ArrayList<UploadPanel>();
uploadLocked = false;
// [ar] - add a strategy for content type?
ExtensionValidationStrategy validationStrategy
= new ExtensionValidationStrategy( filterExtensions, filterIndex );
validationHandler = new ValidationHandler() {
@Override
public void updateEnablement() {
FileDialog.this.updateEnablement();
}
};
validationHandler.addValidationStrategy( validationStrategy );
progressCollector = new ProgressCollector( validationHandler );
validationHandler.setNumUploads( uploadPanels.size() );
}
private void createShell() {
// int style = SWT.TITLE | SWT.BORDER | SWT.APPLICATION_MODAL;
shell = new Shell( getParent(), getStyle() );
shell.setText( getText() );
shell.addShellListener( new ShellAdapter() {
@Override
public void shellClosed( ShellEvent e ) {
handleShellClose();
}
} );
}
private void layoutAndCenterShell() {
Point prefSize = shell.computeSize( SWT.DEFAULT, SWT.DEFAULT );
prefSize.x += 50;
prefSize.y += 10;
if( allowMultiple() ) {
// leave room for five upload slots + add button
prefSize.y += 100;
}
shell.setSize( prefSize );
shell.setMinimumSize( prefSize );
Rectangle parentSize = getParent().getBounds();
int locationX = ( parentSize.width - prefSize.x ) / 2 + parentSize.x;
int locationY = ( parentSize.height - prefSize.y ) / 2 + parentSize.y;
shell.setLocation( locationX, locationY );
}
private void createControls() {
GridLayout mainLayout = new GridLayout();
mainLayout.marginWidth = 10;
mainLayout.marginHeight = 10;
mainLayout.horizontalSpacing = 10;
mainLayout.verticalSpacing = 10;
shell.setLayout( mainLayout );
createDialogArea( shell );
createButtonArea( shell );
}
private boolean allowMultiple() {
return ( getStyle() & ( SWT.MULTI ) ) != 0;
}
private Control createDialogArea( Composite parentComposite ) {
// create a composite with standard margins and spacing
Composite main = new Composite( parentComposite, SWT.NONE );
main.setLayoutData( new GridData( SWT.FILL, SWT.FILL, true, true ) );
GridLayout layout = new GridLayout( 1, true );
layout.marginWidth = 0;
layout.marginHeight = 0;
main.setLayout( layout );
if( allowMultiple() ) {
createMultiSelector( main );
} else {
createSingleSelector( main );
}
Composite footerComp = new Composite( main, SWT.NONE );
GridLayout footerLayout = new GridLayout( 2, false );
footerLayout.marginWidth = 0;
footerLayout.marginHeight = 0;
footerComp.setLayout( footerLayout );
footerComp.setLayoutData( new GridData( SWT.FILL, SWT.FILL, true, false ) );
if( allowMultiple() ) {
createAddSelectorButton( scrollChild );
}
filterSelector = createFilterSelector( footerComp );
filterSelector.setLayoutData( new GridData( SWT.FILL, SWT.FILL, false, false ) );
totalProgressBar = new ProgressBar( footerComp, SWT.HORIZONTAL | SWT.SMOOTH );
totalProgressBar.setLayoutData( new GridData( SWT.FILL, SWT.FILL, true, false ) );
totalProgressBar.setToolTipText( "Total upload progress" );
totalProgressBar.setMaximum( 100 );
progressCollector.setProgressBar( totalProgressBar );
return main;
}
private void createSingleSelector( Composite main ) {
UploadPanel uploadPanel = new UploadPanel( main, UploadPanel.FULL );
uploadPanel.setValidationHandler( validationHandler );
uploadPanel.setLayoutData( new GridData( SWT.FILL, SWT.FILL, true, true ) );
uploadPanel.setProgressCollector( progressCollector );
uploadPanel.setAutoUpload( getAutoUpload() );
uploadPanels.add( uploadPanel );
validationHandler.setNumUploads( uploadPanels.size() );
}
private void createMultiSelector( Composite main ) {
uploadScroller = new ScrolledComposite( main, SWT.V_SCROLL | SWT.BORDER );
uploadScroller.setExpandHorizontal( true );
uploadScroller.setExpandVertical( true );
GridData uploadScrollerLayoutData = new GridData( SWT.FILL, SWT.FILL, true, true );
uploadScroller.setLayoutData( uploadScrollerLayoutData );
scrollChild = new Composite( uploadScroller, SWT.NONE );
GridLayout scrollChildLayout = new GridLayout( 1, true );
scrollChild.setLayout( scrollChildLayout );
uploadsWrapper = new Composite( scrollChild, SWT.NONE );
GridLayout uploadWrapperLayout = new GridLayout( 1, true );
// [if] marginBottom = 1 is needed to avoid default composite size (64) if it is empty
uploadWrapperLayout.marginBottom = 1;
uploadWrapperLayout.marginWidth = 0;
uploadWrapperLayout.marginHeight = 0;
uploadsWrapper.setLayout( uploadWrapperLayout );
uploadsWrapper.setLayoutData( new GridData( SWT.FILL, SWT.TOP, true, false ) );
addUploadPanel();
uploadScroller.setContent( scrollChild );
uploadScroller.setMinSize( scrollChild.computeSize( SWT.DEFAULT, SWT.DEFAULT ) );
}
private void createAddSelectorButton( Composite parent ) {
addFileSelectorButton = new Button( parent, SWT.PUSH );
if( addImage == null ) {
addImage = Graphics.getImage( "resources/add_obj.gif", getClass().getClassLoader() );
}
addFileSelectorButton.setImage( addImage );
addFileSelectorButton.setToolTipText( "Add file" );
addFileSelectorButton.addSelectionListener( new SelectionAdapter() {
@Override
public void widgetSelected( SelectionEvent e ) {
final UploadPanel uploadPanel = addUploadPanel();
progressCollector.updateTotalProgress();
uploadScroller.setMinSize( scrollChild.computeSize( SWT.DEFAULT, SWT.DEFAULT ) );
// [if] workaround for ScrolledComposite scroll issue - see bug 349301 and 349301
uploadPanel.setEnabled( false );
uploadPanel.setVisible( false );
uploadScroller.getDisplay().timerExec( 10, new Runnable() {
public void run() {
uploadPanel.setEnabled( true );
uploadPanel.setVisible( true );
int scrollTop = scrollChild.getSize().y - uploadScroller.getClientArea().y;
uploadScroller.setOrigin( 0, Math.max( 0, scrollTop ) );
}
} );
}
} );
}
private UploadPanel addUploadPanel() {
int uploadPanelStyle = UploadPanel.COMPACT | UploadPanel.PROGRESS | UploadPanel.REMOVEABLE;
final UploadPanel uploadPanel= new UploadPanel( uploadsWrapper, uploadPanelStyle );
uploadPanel.addDisposeListener( new DisposeListener() {
public void widgetDisposed( DisposeEvent event ) {
Display.getCurrent().asyncExec( new Runnable() {
public void run() {
if( !uploadsWrapper.isDisposed() ) {
uploadPanels.remove( uploadPanel );
validationHandler.setNumUploads( uploadPanels.size() );
scrollChild.pack( true );
progressCollector.updateTotalProgress();
uploadScroller.setMinSize( scrollChild.computeSize( SWT.DEFAULT, SWT.DEFAULT ) );
updateEnablement();
}
}
} );
}
} );
uploadPanel.setValidationHandler( validationHandler );
uploadPanel.setLayoutData( new GridData( SWT.FILL, SWT.FILL, true, true ) );
uploadPanel.setProgressCollector( progressCollector );
uploadPanel.setAutoUpload( getAutoUpload() );
uploadPanels.add( uploadPanel );
validationHandler.setNumUploads( uploadPanels.size() );
scrollChild.pack( true );
updateEnablement();
return uploadPanel;
}
private Combo createFilterSelector( Composite headerComp ) {
final Combo filterCombo = new Combo( headerComp, SWT.DROP_DOWN | SWT.READ_ONLY );
filterCombo.setToolTipText( "Selected filename filter" );
String[] names = getFilterNames();
String[] exts = getFilterExtensions();
if( ( names == null || names.length == 0 ) && ( exts == null || exts.length == 0 ) ) {
names = new String[]{
"All Files"
};
exts = new String[]{
"*.*"
};
}
for( int i = 0; i < exts.length; i++ ) {
StringBuffer sb = new StringBuffer();
if( names != null && i < names.length ) {
sb.append( names[ i ] ).append( " " );
}
sb.append( "(" );
sb.append( exts[ i ] );
sb.append( ")" );
filterCombo.add( sb.toString() );
}
filterCombo.select( getFilterIndex() );
filterCombo.addSelectionListener( new SelectionAdapter() {
@Override
public void widgetSelected( SelectionEvent e ) {
setFilterIndex( filterCombo.getSelectionIndex() );
for( int i = 0; i < uploadPanels.size(); i++ ) {
UploadPanel panel = uploadPanels.get( i );
panel.validate();
}
}
} );
return filterCombo;
}
private void createButtonArea( Composite parent ) {
Composite buttonComposite = new Composite( parent, SWT.NONE );
GridData layoutData = new GridData( SWT.RIGHT, SWT.CENTER, false, false );
layoutData.horizontalSpan = 2;
buttonComposite.setLayoutData( layoutData );
GridLayout buttonCompLayout = new GridLayout( 2, false );
buttonCompLayout.marginWidth = 0;
buttonCompLayout.marginHeight = 0;
buttonComposite.setLayout( buttonCompLayout );
String okText = SWT.getMessage( "SWT_OK" );
okButton = createButton( buttonComposite, okText );
parent.getShell().setDefaultButton( okButton );
okButton.forceFocus();
String cancelText = SWT.getMessage( "SWT_Cancel" );
Button cancelButton = createButton( buttonComposite, cancelText );
okButton.addSelectionListener( new SelectionAdapter() {
@Override
public void widgetSelected( SelectionEvent e ) {
startUploads();
}
} );
cancelButton.addSelectionListener( new SelectionAdapter() {
@Override
public void widgetSelected( SelectionEvent e ) {
closeShell();
}
} );
updateEnablement();
}
// [if] Calling shell.close() in the selection listener directly throws IllegalAccessError
// see bug 253818
private void closeShell() {
shell.close();
}
private Button createButton( Composite parent, String text ) {
Button result = new Button( parent, SWT.PUSH );
result.setText( text );
GridData data = new GridData( GridData.HORIZONTAL_ALIGN_FILL );
Font dialogFont = result.getFont();
float charWidth = TextSizeUtil.getAvgCharWidth( dialogFont );
float width = charWidth * BUTTON_WIDTH + HORIZONTAL_DIALOG_UNIT_PER_CHAR / 2;
int widthHint = ( int )( width / HORIZONTAL_DIALOG_UNIT_PER_CHAR );
Point minSize = result.computeSize( SWT.DEFAULT, SWT.DEFAULT, true );
data.widthHint = Math.max( widthHint, minSize.x );
result.setLayoutData( data );
return result;
}
private void startUploads() {
java.util.List<UploadPanel> needsProcessing = new ArrayList<UploadPanel>();
for( int i = 0; i < uploadPanels.size(); i++ ) {
UploadPanel panel = uploadPanels.get( i );
if( !panel.isFinished() ) {
needsProcessing.add( panel );
}
}
uploadLocked = true;
if( needsProcessing.size() > 0 ) {
okButton.setText( "Uploading..." );
okButton.setToolTipText( "Waiting for uploads to finish" );
okButton.setEnabled( false );
if( addFileSelectorButton != null ) {
addFileSelectorButton.setEnabled( false );
}
for( int i = 0; i < needsProcessing.size(); i++ ) {
UploadPanel panel = needsProcessing.get( i );
if( !panel.isStarted() ) {
panel.startUpload();
}
panel.setEnabled( false );
}
}
updateEnablement();
}
private void updateEnablement() {
if( okButton != null && !okButton.isDisposed() ) {
String okText = SWT.getMessage( "SWT_OK" );
if( uploadPanels.size() == 0 ) {
okButton.setText( okText );
okButton.setEnabled( false );
} else if( uploadPanels.size() > 0 ) {
boolean enabled = true;
if( uploadLocked ) {
if( progressCollector.isFinished() ) {
okButton.setText( okText );
fileNames = new String[ uploadPanels.size() ];
fileName = null;
for( int i = 0; i < uploadPanels.size(); i++ ) {
UploadPanel uploadPanel = uploadPanels.get( i );
File uploadedFile = uploadPanel.getUploadedFile();
// TODO [rst] Understand if file can be null
if( uploadedFile != null ) {
fileNames[ i ] = uploadedFile.getAbsolutePath();
}
if( fileName == null || fileName.length() == 0 ) {
fileName = fileNames[ 0 ];
}
}
shell.close();
} else {
okButton.setText( "Uploading..." );
okButton.setToolTipText( "Waiting for uploads to finish" );
okButton.setEnabled( false );
}
} else {
okButton.setText( okText );
for( int i = 0; i < uploadPanels.size(); i++ ) {
UploadPanel panel = uploadPanels.get( i );
if( panel.getSelectedFilename().length() == 0 ) {
enabled = false;
}
}
if( !enabled ) {
okButton.setToolTipText( "Specify files in all empty selectors to continue." );
} else {
okButton.setToolTipText( "" );
}
okButton.setEnabled( enabled );
}
}
}
}
private void cleanup() {
UICallBack.deactivate( FileDialog.class.getName() + hashCode() );
}
private void handleShellClose() {
cleanup();
}
}