/***************************************************************************** * Copyright (c) 2008 g-Eclipse Consortium * 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 * * Initial development of the original code was made for the * g-Eclipse project founded by European Union * project number: FP6-IST-034327 http://www.geclipse.eu/ * * Contributors: * Mathias Stuempert - initial API and implementation *****************************************************************************/ package eu.geclipse.ui.dialogs; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.regex.Pattern; import org.eclipse.core.filesystem.EFS; import org.eclipse.core.filesystem.IFileInfo; import org.eclipse.core.filesystem.IFileStore; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.TitleAreaDialog; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.ILabelDecorator; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.VerifyEvent; import org.eclipse.swt.events.VerifyListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; import org.eclipse.ui.PlatformUI; import eu.geclipse.core.filesystem.GEclipseURI; import eu.geclipse.core.model.GridModel; import eu.geclipse.core.model.IGridConnectionElement; import eu.geclipse.core.model.IGridContainer; import eu.geclipse.core.model.IGridElement; import eu.geclipse.core.model.IGridModelEvent; import eu.geclipse.core.model.IGridModelListener; import eu.geclipse.ui.comparators.TreeColumnComparator; import eu.geclipse.ui.internal.Activator; import eu.geclipse.ui.listeners.TreeColumnListener; import eu.geclipse.ui.providers.DecoratingGridModelLabelProvider; import eu.geclipse.ui.providers.FileStoreLabelProvider; import eu.geclipse.ui.providers.GridFileDialogContentProvider; import eu.geclipse.ui.providers.NewGridModelLabelProvider; import eu.geclipse.ui.providers.ProgressTreeNode; import eu.geclipse.ui.widgets.StoredCombo; /** * This is an implementation of a file dialog for both local * and remote files that are made available via EFS-implementations. * The remote parts make use of the Grid connections that are defined * within the user's. The dialog is highly configurable and allows to * only show the remote or local parts or both. Furthermore it can be * used to select only files or directories or both and only existing * or to specify even new files/directories. Last but not least multi- * selection may also be possible. */ public class GridFileDialog extends TitleAreaDialog { /** * Private interface used to listen to mode changes. */ private interface IModeChangeListener { /** * Invoked whenever the dialog's mode has changed. * * @param mode The new mode. */ public void modeChanged( final int mode ); } /** * The <code>ModeManager</code> does manage the dialogs modes. * On the one hand it manages the activation states of the * provided {@link ToolItem}s, on the other hand it informs * listeners about mode changes. */ private static class ModeManager extends SelectionAdapter { /** * Mode constant for the remote mode. */ public static final int CONNECTION_MODE = 1; /** * Mode constant for the local mode with the user's home * as root directory. */ public static final int HOME_MODE = 2; /** * Mode constant for the local mode with the workspace * as root directory. */ public static final int WS_MODE = 3; /** * Mode constant for the local mode with the system's root * as root directory. */ public static final int ROOT_MODE = 4; /** * Identifier used to tag the {@link ToolItem}s. */ private static final String MODE_KEY = "button.mode"; //$NON-NLS-1$ /** * List of all available {@link ToolItem}s. */ private List< ToolItem > modeItems = new ArrayList< ToolItem >(); /** * List of all registered listeners. */ private List< IModeChangeListener > listeners = new ArrayList< IModeChangeListener >(); /** * Standard constructor. */ public ModeManager() { // empty implementation } /** * Add a new {@link IModeChangeListener} to the list of listeners. * * @param l The new listener. */ public void addModeChangeListener( final IModeChangeListener l ) { if ( ! this.listeners.contains( l ) ) { this.listeners.add( l ); } } /** * Add a new mode item to the list of items. Any formerly defined * item with the same mode will be overwritten. * * @param item The new item used to select the specified mode. * @param mode The mode that refers to the specified item. */ public void addModeItem( final ToolItem item, final int mode ) { item.setData( MODE_KEY, Integer.valueOf( mode ) ); this.modeItems.add( item ); item.addSelectionListener( this ); } /** * Set the mode, i.e. update the selection states of the mode items. * * @param mode The new mode. */ public void setMode( final int mode ) { for ( ToolItem item : this.modeItems ) { Integer m = ( Integer ) item.getData( MODE_KEY ); item.setSelection( ( m != null ) && ( m.intValue() == mode ) ); } } /* (non-Javadoc) * @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent) */ @Override public void widgetSelected( final SelectionEvent e ) { for ( ToolItem item : this.modeItems ) { item.setSelection( item == e.widget ); } Object mode = e.widget.getData( MODE_KEY ); if ( mode instanceof Integer ) { fireModeChanged( ( ( Integer ) mode ).intValue() ); } } /** * Inform all registered {@link IModeChangeListener}s about a mode * changed event. * * @param mode The new mode to be reported to the listeners. */ private void fireModeChanged( final int mode ) { for ( IModeChangeListener l : this.listeners ) { l.modeChanged( mode ); } } } /** * {@link ViewerFilter} that filters out all non-folders. */ private static class FolderFilter extends ViewerFilter { /** * Standard constructor. */ public FolderFilter() { // empty implementation } /* (non-Javadoc) * @see org.eclipse.jface.viewers.ViewerFilter#select(org.eclipse.jface.viewers.Viewer, * java.lang.Object, java.lang.Object) */ @Override public boolean select( final Viewer viewer, final Object parentElement, final Object element ) { boolean result = false; if ( element instanceof IGridElement ) { if ( element instanceof IGridConnectionElement ) { result = ( ( IGridConnectionElement ) element ).isFolder(); } else if ( element instanceof IGridContainer ) { result = true; } } else if ( element instanceof IFileStore ) { IFileInfo info = ( ( IFileStore ) element ).fetchInfo(); result = info.isDirectory(); } else if ( element instanceof ProgressTreeNode ) { result = true; } return result; } } /** * {@link ViewerFilter} that filters out all non-remote elements. */ private static class RemoteConnectionFilter extends ViewerFilter { /** * Standard constructor. */ public RemoteConnectionFilter() { // empty implementation } @Override public boolean select( final Viewer viewer, final Object parentElement, final Object element ) { boolean result = false; if ( element instanceof IGridConnectionElement ) { result = ! ( ( IGridConnectionElement ) element ).isLocal(); } else if ( element instanceof ProgressTreeNode ) { result = true; } return result; } } /** * {@link ViewerFilter} that filters out all files without a specific * file extension. */ private static class FileTypeFilter extends ViewerFilter { /** * Constant for the wildcard filter. */ private static final String WILDCARD = "*"; //$NON-NLS-1$ /** * Separator used to separate filename and prefix. */ private static final String PREFIX_SEPARATOR = "."; //$NON-NLS-1$ /** * The file extension for allowed files. */ private String prefix; /** * Construct a new standard filter, i.e. a filter that allows all * files (*.*). */ public FileTypeFilter() { this( null ); } /** * Construct a new filter that filters all files with other extensions * than the specified. Folders are not filtered. * * @param prefix Prefix for all non-filtered files. */ public FileTypeFilter( final String prefix ) { this.prefix = prefix; } /** * Get the prefix of the filtered files. * * @return The file's prefix. */ public String getPrefix() { return this.prefix == null ? WILDCARD : this.prefix; } /* (non-Javadoc) * @see org.eclipse.jface.viewers.ViewerFilter#select(org.eclipse.jface.viewers.Viewer, * java.lang.Object, java.lang.Object) */ @Override public boolean select( final Viewer viewer, final Object parentElement, final Object element ) { boolean result = false; if ( this.prefix != null ) { if ( element instanceof IFileStore ) { boolean isDir = ( ( IFileStore ) element ).fetchInfo().isDirectory(); String name = ( ( IFileStore ) element ).getName(); result = isDir || name.endsWith( PREFIX_SEPARATOR + this.prefix ); } else if ( element instanceof IGridConnectionElement ) { boolean isDir = ( ( IGridConnectionElement ) element ).isFolder(); String name = ( ( IGridConnectionElement ) element ).getName(); result = isDir || name.endsWith( PREFIX_SEPARATOR + this.prefix ); } else if ( element instanceof IGridContainer ) { IResource resource = ( ( IGridContainer ) element ).getResource(); if ( ( resource != null ) && ( resource.getType() == IResource.FILE ) ) { String name = resource.getName(); result = name.endsWith( PREFIX_SEPARATOR + this.prefix ); } else { result = true; } } else if ( element instanceof IGridElement ) { String name = ( ( IGridElement ) element ).getName(); result = name.endsWith( PREFIX_SEPARATOR + this.prefix ); } else if ( element instanceof ProgressTreeNode ){ result = true; } } else { result = true; } return result; } } /** * Style constant for none styles. */ public static final int STYLE_NONE = 0x00; /** * Style constant for a dialog that allows only local files, * i.e. no Grid connections. */ public static final int STYLE_ALLOW_ONLY_LOCAL = 0x01; /** * Style constant for a dialog that allows Grid connections. */ public static final int STYLE_ALLOW_ONLY_CONNECTIONS = 0x02; /** * Style constant for a dialog that allows only remote files, * i.e. only Grid connections that are not referring to local files. */ public static final int STYLE_ALLOW_ONLY_REMOTE_CONNECTIONS = 0x04; /** * Style constant for a dialog that allows only the selection of * files. */ public static final int STYLE_ALLOW_ONLY_FILES = 0x08; /** * Style constant for a dialog that allows only the selection of * directories. */ public static final int STYLE_ALLOW_ONLY_FOLDERS = 0x10; /** * Style constant for a dialog that allows only the selection of * existing files/directories. The filename and url combos can not * be edited. */ public static final int STYLE_ALLOW_ONLY_EXISTING = 0x20; /** * Style contant for a file dialog that allows multi selection. */ public static final int STYLE_MULTI_SELECTION = 0x40; /** * Empty string constant. */ private static final String EMPTY_STRING = ""; //$NON-NLS-1$ /** * Path separator. */ private static final String PATH_SEPARATOR = "/"; //$NON-NLS-1$ /** * System property for the user's home directory. */ private static final String HOME_PROPERTY = "user.home"; //$NON-NLS-1$ /** * Root folder constant. */ private static final String ROOT_FOLDER = "/"; //$NON-NLS-1$ private static final Pattern POSIX_FILENAME = Pattern.compile( "^[\\w\\.][\\w\\.-]*" ); //$NON-NLS-1$ /** * The dialog's tree viewer. */ protected TreeViewer treeViewer; /** * The combo for selecting the file type. */ protected Combo filetypeCombo; /** * The combo for selecting and editing the URI directly. */ protected StoredCombo uriCombo; /** * Map of all available {@link FileTypeFilter}s. */ protected Hashtable< String, FileTypeFilter > filetypeFilters = new Hashtable< String, FileTypeFilter >(); /** * The combo for selecting and editing the filename directly. */ private StoredCombo filenameCombo; /** * {@link ModeManager} used to manage the tree viewer's mode. */ private ModeManager modeManager; /** * Listener used to listen to changes in the Grid model. */ private IGridModelListener modelListener; /** * Listener used to listen to modifications in the uri combo. */ private ModifyListener uriListener; /** * Listener used to listen to modifications in the filename combo. */ private VerifyListener filenameListener; /** * Listener used to listen to selections in the tree viewer. */ private ISelectionChangedListener selectionListener; /** * The style of the dialog. */ private int style; /** * The current selection of the tree viewer. */ private IFileStore[] currentSelection; /** * Create a new dialog with the specified style constant. * * @param parent The dialog's parent {@link Shell}. * @param style The dialog's style, i.e. a bitwise or of style * constants. */ public GridFileDialog( final Shell parent, final int style ) { super( parent ); this.style = style; assertStyle(); setShellStyle( SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.RESIZE ); URL imgURL = Activator.getDefault().getBundle() .getResource( "icons/wizban/newconn_wiz.gif" ); //$NON-NLS-1$ ImageDescriptor imgDesc = ImageDescriptor.createFromURL( imgURL ); setTitleImage( imgDesc.createImage() ); } /** * Convenience method to open a file dialog. If filters have to be * specified this method may not be used. * * @param parent The dialog's parent {@link Shell}. * @param style The dialog's style, i.e. a bitwise or of style * constants. * @return Array containing the {@link URI}s of all selected elements * or <code>null</code> if no selection is available. */ public static URI[] openFileDialog( final Shell parent, final int style ) { URI[] result = null; GridFileDialog dialog = new GridFileDialog( parent, style ); if ( dialog.open() == Window.OK ) { result = dialog.getSelectedURIs(); } return result; } /** * Add a file type filter to this dialog. The filter will appear in * the file type combo. * * @param prefix The prefix of files that will be shown. * @param description A description of the filter. This description * will be shown in the file type combo. */ public void addFileTypeFilter( final String prefix, final String description ) { FileTypeFilter filter = new FileTypeFilter( prefix ); addFileTypeFilter( filter, description ); } /* (non-Javadoc) * @see org.eclipse.jface.dialogs.TrayDialog#close() */ @Override public boolean close() { if ( this.modelListener != null ) { GridModel.getRoot().removeGridModelListener( this.modelListener ); } return super.close(); } /** * Get all currently selected {@link IFileStore}s that meet the style of * this dialog. * * @return Array containing the {@link IFileStore}s of all selected elements * or <code>null</code> if no selection is available. */ public IFileStore[] getSelectedFileStores() { IFileStore[] result = null; if ( ( this.currentSelection != null ) && ( this.currentSelection.length > 0 ) ) { result = new IFileStore[ this.currentSelection.length ]; System.arraycopy( this.currentSelection, 0, result, 0, result.length ); } return result; } /** * Get all currently selected {@link URI}s. * * @return Array containing the {@link URI}s of all selected elements * or <code>null</code> if no selection is available. */ public URI[] getSelectedURIs() { URI[] result = null; IFileStore[] stores = getSelectedFileStores(); if ( ( stores != null ) && ( stores.length > 0 ) ) { result = new URI[ stores.length ]; for ( int i = 0 ; i < stores.length ; i++ ) { URI uri = stores[ i ].toURI(); GEclipseURI gUri = new GEclipseURI( uri ); result[ i ] = gUri.toSlaveURI(); } } return result; } /** * Configure the tree viewer's filters according to the dialog's style. */ protected void configureViewerFilters() { if ( this.treeViewer != null ) { this.treeViewer.resetFilters(); if ( hasStyle( STYLE_ALLOW_ONLY_FOLDERS ) ) { this.treeViewer.addFilter( new FolderFilter() ); } if ( hasStyle( STYLE_ALLOW_ONLY_REMOTE_CONNECTIONS ) ) { this.treeViewer.addFilter( new RemoteConnectionFilter() ); } if ( this.filetypeCombo != null ) { if ( this.filetypeCombo.getItemCount() != this.filetypeFilters.size() ) { initializeFileTypeCombo(); } String key = this.filetypeCombo.getText(); FileTypeFilter filter = this.filetypeFilters.get( key ); if ( filter != null ) { this.treeViewer.addFilter( filter ); } } } } /* (non-Javadoc) * @see org.eclipse.jface.dialogs.TitleAreaDialog#createDialogArea(org.eclipse.swt.widgets.Composite) */ @Override protected Control createDialogArea( final Composite parent ) { this.modeManager = new ModeManager(); this.modeManager.addModeChangeListener( new IModeChangeListener() { public void modeChanged( final int mode ) { setMode( mode ); } } ); GridData gData; Label topRule = new Label( parent, SWT.HORIZONTAL | SWT.SEPARATOR ); topRule.setLayoutData( new GridData( GridData.FILL_HORIZONTAL ) ); Composite mainComp = new Composite( parent, SWT.NONE ); mainComp.setLayout( new GridLayout( 2, false ) ); gData = new GridData( GridData.FILL_BOTH ); gData.grabExcessHorizontalSpace = true; gData.grabExcessVerticalSpace = true; gData.widthHint = 500; gData.heightHint = 400; mainComp.setLayoutData( gData ); Label bottomRule = new Label( parent, SWT.HORIZONTAL | SWT.SEPARATOR ); bottomRule.setLayoutData( new GridData( GridData.FILL_HORIZONTAL ) ); if ( ! hasStyle( STYLE_MULTI_SELECTION ) ) { Composite uriComp = new Composite( mainComp, SWT.NONE ); uriComp.setLayout( new GridLayout( 2, false ) ); gData = new GridData( GridData.FILL_HORIZONTAL ); gData.grabExcessHorizontalSpace = true; gData.horizontalSpan = 2; uriComp.setLayoutData( gData ); Label uriLabel = new Label( uriComp, SWT.NONE ); uriLabel.setText( Messages.getString("GridFileDialog.label_URI") ); //$NON-NLS-1$ gData = new GridData(); uriLabel.setLayoutData( gData ); this.uriCombo = new StoredCombo( uriComp, SWT.NONE ); gData = new GridData( GridData.FILL_HORIZONTAL ); gData.grabExcessHorizontalSpace = true; this.uriCombo.setLayoutData( gData ); this.uriCombo.setEnabled( ! hasStyle( STYLE_ALLOW_ONLY_EXISTING ) ); } if ( ! hasStyle( STYLE_ALLOW_ONLY_CONNECTIONS | STYLE_ALLOW_ONLY_REMOTE_CONNECTIONS ) ) { ToolBar modeBar = new ToolBar( mainComp, SWT.VERTICAL | SWT.BORDER ); modeBar.setBackground( getShell().getDisplay().getSystemColor( SWT.COLOR_WHITE ) ); gData = new GridData( GridData.FILL_VERTICAL ); gData.grabExcessVerticalSpace = true; modeBar.setLayoutData( gData ); if ( ! hasStyle( STYLE_ALLOW_ONLY_LOCAL ) ) { URL connURL = Activator.getDefault().getBundle() .getResource( "icons/extras/grid_file_dialog_conn_mode.gif" ); //$NON-NLS-1$ ImageDescriptor connDesc = ImageDescriptor.createFromURL( connURL ); ToolItem connItem = new ToolItem( modeBar, SWT.CHECK ); connItem.setImage( connDesc.createImage() ); connItem.setToolTipText( Messages.getString("GridFileDialog.switch_to_connections") ); //$NON-NLS-1$ this.modeManager.addModeItem( connItem, ModeManager.CONNECTION_MODE ); } if ( ! hasStyle( STYLE_ALLOW_ONLY_CONNECTIONS | STYLE_ALLOW_ONLY_REMOTE_CONNECTIONS ) ) { URL wsURL = Activator.getDefault().getBundle() .getResource( "icons/extras/grid_file_dialog_ws_mode.gif" ); //$NON-NLS-1$ ImageDescriptor wsDesc = ImageDescriptor.createFromURL( wsURL ); ToolItem wsItem = new ToolItem( modeBar, SWT.CHECK ); wsItem.setImage( wsDesc.createImage() ); wsItem.setToolTipText( Messages.getString("GridFileDialog.switch_to_workspace") ); //$NON-NLS-1$ this.modeManager.addModeItem( wsItem, ModeManager.WS_MODE ); URL homeURL = Activator.getDefault().getBundle() .getResource( "icons/extras/grid_file_dialog_home_mode.gif" ); //$NON-NLS-1$ ImageDescriptor homeDesc = ImageDescriptor.createFromURL( homeURL ); ToolItem homeItem = new ToolItem( modeBar, SWT.CHECK ); homeItem.setImage( homeDesc.createImage() ); homeItem.setToolTipText( Messages.getString("GridFileDialog.switch_to_home") ); //$NON-NLS-1$ this.modeManager.addModeItem( homeItem, ModeManager.HOME_MODE ); URL rootURL = Activator.getDefault().getBundle() .getResource( "icons/extras/grid_file_dialog_root_mode.gif" ); //$NON-NLS-1$ ImageDescriptor rootDesc = ImageDescriptor.createFromURL( rootURL ); ToolItem rootItem = new ToolItem( modeBar, SWT.CHECK ); rootItem.setImage( rootDesc.createImage() ); rootItem.setToolTipText( Messages.getString("GridFileDialog.switch_to_root") ); //$NON-NLS-1$ this.modeManager.addModeItem( rootItem, ModeManager.ROOT_MODE ); } } Composite browserComp = new Composite( mainComp, SWT.NONE ); GridLayout browserLayout = new GridLayout( 1, false ); browserLayout.marginWidth = 0; browserLayout.marginHeight = 0; browserComp.setLayout( browserLayout ); gData = new GridData( GridData.FILL_BOTH ); gData.grabExcessHorizontalSpace = true; gData.grabExcessVerticalSpace = true; browserComp.setLayoutData( gData ); int treeStyle = SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER; if ( hasStyle( STYLE_MULTI_SELECTION ) ) { treeStyle |= SWT.MULTI; } else { treeStyle |= SWT.SINGLE; } this.treeViewer = new TreeViewer( browserComp, treeStyle ); this.treeViewer.setContentProvider( new GridFileDialogContentProvider() ); NewGridModelLabelProvider lProvider = new NewGridModelLabelProvider(); lProvider.addColumn( 0, FileStoreLabelProvider.COLUMN_TYPE_NAME ); lProvider.addColumn( 1, FileStoreLabelProvider.COLUMN_TYPE_SIZE ); lProvider.addColumn( 2, FileStoreLabelProvider.COLUMN_TYPE_MOD_DATE ); ILabelDecorator decorator = PlatformUI.getWorkbench().getDecoratorManager().getLabelDecorator(); DecoratingGridModelLabelProvider dProvider = new DecoratingGridModelLabelProvider( lProvider, decorator ); this.treeViewer.setLabelProvider( dProvider ); Tree tree = this.treeViewer.getTree(); tree.setHeaderVisible( true ); gData = new GridData( GridData.FILL_BOTH ); gData.grabExcessHorizontalSpace = true; gData.grabExcessVerticalSpace = true; tree.setLayoutData( gData ); TreeColumn nameColumn = new TreeColumn( tree, SWT.NONE ); nameColumn.setText( Messages.getString("GridFileDialog.column_title_name") ); //$NON-NLS-1$ nameColumn.setAlignment( SWT.LEFT ); nameColumn.setWidth( 300 ); TreeColumn sizeColumn = new TreeColumn( tree, SWT.NONE ); sizeColumn.setText( Messages.getString("GridFileDialog.column_title_size") ); //$NON-NLS-1$ sizeColumn.setAlignment( SWT.RIGHT ); sizeColumn.setWidth( 100 ); TreeColumn modColumn = new TreeColumn( tree, SWT.NONE ); modColumn.setText( Messages.getString("GridFileDialog.column_title_last_modification") ); //$NON-NLS-1$ modColumn.setAlignment( SWT.CENTER ); modColumn.setWidth( 200 ); TreeColumnListener columnListener = new TreeColumnListener( this.treeViewer ); for ( TreeColumn column : tree.getColumns() ) { column.addSelectionListener( columnListener ); } tree.setSortColumn( nameColumn ); tree.setSortDirection( SWT.UP ); this.treeViewer.setComparator( new TreeColumnComparator( nameColumn ) ); Composite fileComp = new Composite( browserComp, SWT.NONE ); fileComp.setLayout( new GridLayout( 2, false ) ); gData = new GridData( GridData.FILL_HORIZONTAL ); gData.grabExcessHorizontalSpace = true; fileComp.setLayoutData( gData ); if ( ! hasStyle( STYLE_MULTI_SELECTION ) ) { Label filenameLabel = new Label( fileComp, SWT.NONE ); filenameLabel.setText( hasStyle( STYLE_ALLOW_ONLY_FOLDERS ) ? Messages.getString("GridFileDialog.label_foldername") //$NON-NLS-1$ : hasStyle( STYLE_ALLOW_ONLY_FILES ) ? Messages.getString("GridFileDialog.label_filename") //$NON-NLS-1$ : Messages.getString("GridFileDialog.label_name") //$NON-NLS-1$ ); gData = new GridData(); gData.horizontalAlignment = GridData.BEGINNING; filenameLabel.setLayoutData( gData ); this.filenameCombo = new StoredCombo( fileComp, SWT.BORDER ); gData = new GridData( GridData.FILL_HORIZONTAL ); gData.grabExcessHorizontalSpace = true; this.filenameCombo.setLayoutData( gData ); this.filenameCombo.setEnabled( ! hasStyle( STYLE_ALLOW_ONLY_EXISTING ) ); } if ( ! hasStyle( STYLE_ALLOW_ONLY_FOLDERS ) ) { Label filetypeLabel = new Label( fileComp, SWT.NONE ); filetypeLabel.setText( Messages.getString("GridFileDialog.label_filetype") ); //$NON-NLS-1$ gData = new GridData(); gData.horizontalAlignment = GridData.BEGINNING; filetypeLabel.setLayoutData( gData ); this.filetypeCombo = new Combo( fileComp, SWT.BORDER | SWT.READ_ONLY ); gData = new GridData( GridData.FILL_HORIZONTAL ); gData.grabExcessHorizontalSpace = true; this.filetypeCombo.setLayoutData( gData ); } int mode = ! hasStyle( STYLE_ALLOW_ONLY_LOCAL ) ? ModeManager.CONNECTION_MODE : ModeManager.WS_MODE; this.modeManager.setMode( mode ); setMode( mode ); this.treeViewer.addDoubleClickListener( new IDoubleClickListener() { public void doubleClick( final DoubleClickEvent event ) { handleDoubleClick(); } } ); this.selectionListener = new ISelectionChangedListener() { public void selectionChanged( final SelectionChangedEvent event ) { setNotificationEnabled( false ); handleSelectionChanged(); setNotificationEnabled( true ); } }; this.treeViewer.addSelectionChangedListener( this.selectionListener ); if ( this.uriCombo != null ) { this.uriListener = new ModifyListener() { public void modifyText( final ModifyEvent e ) { setNotificationEnabled( false ); handleUriChanged(); setNotificationEnabled( true ); } }; this.uriCombo.addModifyListener( this.uriListener ); } if ( this.filenameCombo != null ) { this.filenameListener = new VerifyListener() { public void verifyText( final VerifyEvent e ) { setNotificationEnabled( false ); handleFilenameChanged( e ); setNotificationEnabled( true ); } }; this.filenameCombo.addVerifyListener( this.filenameListener ); } if ( this.filetypeCombo != null ) { this.filetypeCombo.addSelectionListener( new SelectionAdapter() { @Override public void widgetSelected( final SelectionEvent e ) { configureViewerFilters(); } } ); } this.modelListener = new IGridModelListener() { public void gridModelChanged( final IGridModelEvent event ) { if ( ( event.getType() == IGridModelEvent.ELEMENTS_ADDED ) || ( event.getType() == IGridModelEvent.ELEMENTS_REMOVED ) ) { refreshViewer( event.getSource() ); } } }; GridModel.getRoot().addGridModelListener( this.modelListener ); Shell shell = getShell(); if ( hasStyle( STYLE_ALLOW_ONLY_FILES ) ) { if ( hasStyle( STYLE_MULTI_SELECTION ) ) { shell.setText( Messages.getString("GridFileDialog.shell_title_files") ); //$NON-NLS-1$ } else { shell.setText( Messages.getString("GridFileDialog.shell_title_file") ); //$NON-NLS-1$ } } else if ( hasStyle( STYLE_ALLOW_ONLY_FOLDERS ) ) { if ( hasStyle( STYLE_MULTI_SELECTION ) ) { shell.setText( Messages.getString("GridFileDialog.shell_title_folders") ); //$NON-NLS-1$ } else { shell.setText( Messages.getString("GridFileDialog.shell_title_folder") ); //$NON-NLS-1$ } } else { if ( hasStyle( STYLE_MULTI_SELECTION ) ) { shell.setText( Messages.getString("GridFileDialog.shell_title_files_folders") ); //$NON-NLS-1$ } else { shell.setText( Messages.getString("GridFileDialog.shell_title_file_folder") ); //$NON-NLS-1$ } } setTitle( Messages.getString("GridFileDialog.title") ); //$NON-NLS-1$ if ( hasStyle( STYLE_ALLOW_ONLY_FILES ) ) { if ( hasStyle( STYLE_MULTI_SELECTION ) ) { setMessage( Messages.getString("GridFileDialog.title_files") ); //$NON-NLS-1$ } else { setMessage( Messages.getString("GridFileDialog.title_file") ); //$NON-NLS-1$ } } else if ( hasStyle( STYLE_ALLOW_ONLY_FOLDERS ) ) { if ( hasStyle( STYLE_MULTI_SELECTION ) ) { setMessage( Messages.getString("GridFileDialog.title_folders") ); //$NON-NLS-1$ } else { setMessage( Messages.getString("GridFileDialog.title_folder") ); //$NON-NLS-1$ } } else { if ( hasStyle( STYLE_MULTI_SELECTION ) ) { setMessage( Messages.getString("GridFileDialog.title_files_folders") ); //$NON-NLS-1$ } else { setMessage( Messages.getString("GridFileDialog.title_file_folder") ); //$NON-NLS-1$ } } addFileTypeFilter( new FileTypeFilter(), Messages.getString("GridFileDialog.label_all_files") ); //$NON-NLS-1$ if ( this.filenameCombo != null && this.filenameCombo.getItemCount() != 0 ) { this.filetypeCombo.select( 0 ); } return mainComp; } /** * Handler method for handling double clicks in the {@link TreeViewer}. This * handler expands or collapses tree nodes if the associated * element is a folder or selects an element and * closes the dialog if the associated element is a file. */ protected void handleDoubleClick() { IStructuredSelection selection = ( IStructuredSelection ) this.treeViewer.getSelection(); Object object = selection.getFirstElement(); if ( this.treeViewer.isExpandable( object ) ) { boolean state = this.treeViewer.getExpandedState( object ); this.treeViewer.setExpandedState( object, ! state ); } else { setReturnCode( IDialogConstants.OK_ID ); close(); } } /** * Handler that handles changes in the filename combo. */ protected void handleFilenameChanged( final VerifyEvent e ) { String errMsg = null; URI uri = getURI(); if ( uri != null ) { IPath path = new Path( uri.getPath() ); String lastSegment = path.lastSegment(); String filename = getFilename(); if ( filename == null ) { filename = EMPTY_STRING; } if ( ( lastSegment != null ) && lastSegment.equals( filename ) ) { path = path.removeLastSegments( 1 ); } String newFilename = filename.substring( 0, e.start ) + e.text + filename.substring( e.end ); if ( ( newFilename.length() > 0 ) && ( ! validateFilename( newFilename ) ) ) { e.doit = false; } else { path = path.append( newFilename ); String spath = path.toString(); if ( ! spath.startsWith( PATH_SEPARATOR ) ) { spath = PATH_SEPARATOR + spath; } try { uri = new URI( uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), spath, uri.getQuery(), uri.getFragment() ); setURI( uri ); try { IFileStore fileStore = EFS.getStore( uri ); IFileInfo fileInfo = fileStore.fetchInfo(); if ( hasStyle( STYLE_ALLOW_ONLY_FILES ) && fileInfo.isDirectory() ) { errMsg = Messages.getString("GridFileDialog.file_only_mode_warning"); //$NON-NLS-1$ } else if ( hasStyle( STYLE_ALLOW_ONLY_FOLDERS ) && ! fileInfo.isDirectory() ) { errMsg = Messages.getString("GridFileDialog.folder_only_mode_warning"); //$NON-NLS-1$ } else { setCurrentSelection( new IFileStore[] { fileStore } ); } } catch ( CoreException cExc ) { errMsg = String.format( Messages.getString("GridFileDialog.create_file_store_error"), cExc.getLocalizedMessage() ); //$NON-NLS-1$ } } catch ( URISyntaxException uriExc ) { errMsg = String.format( Messages.getString("GridFileDialog.invalid_file_name_error"), newFilename ); //$NON-NLS-1$ } } } if ( errMsg != null ) { setCurrentSelection( null ); } setErrorMessage( errMsg ); } protected void handleSelectionChanged() { String errMsg = null; IFileStore[] selection = getSelection(); if ( ( selection == null ) || ( selection.length == 0 ) ) { setFilename( null ); setURI( null ); } else if ( hasStyle( STYLE_ALLOW_ONLY_FILES ) || hasStyle( STYLE_ALLOW_ONLY_FOLDERS ) ) { for ( IFileStore store : selection ) { IFileInfo info = store.fetchInfo(); if ( hasStyle( STYLE_ALLOW_ONLY_FILES ) && info.isDirectory() ) { errMsg = Messages.getString("GridFileDialog.file_only_mode_warning"); //$NON-NLS-1$ break; } else if ( hasStyle( STYLE_ALLOW_ONLY_FOLDERS ) && ! info.isDirectory() ) { errMsg = Messages.getString("GridFileDialog.folder_only_mode_warning"); //$NON-NLS-1$ break; } } } if ( ( selection != null ) && ( selection.length == 1 ) ) { setURI( selection[ 0 ].toURI() ); if ( errMsg == null ) { setFilename( selection[ 0 ].getName() ); } else { setFilename( null ); } } else { setURI( null ); setFilename( null ); } if ( errMsg == null ) { setCurrentSelection( selection ); } else { setCurrentSelection( null ); } setErrorMessage( errMsg ); } /** * Handler that handles changes in the uri combo. */ protected void handleUriChanged() { URI uri = getURI(); if ( uri != null ) { String errMsg = null; try { IFileStore fileStore = EFS.getStore( uri ); IFileInfo fileInfo = fileStore.fetchInfo(); if ( hasStyle( STYLE_ALLOW_ONLY_FILES ) && fileInfo.isDirectory() ) { errMsg = Messages.getString("GridFileDialog.file_only_mode_warning"); //$NON-NLS-1$ } else if ( hasStyle( STYLE_ALLOW_ONLY_FOLDERS ) && ! fileInfo.isDirectory() ) { errMsg = Messages.getString("GridFileDialog.folder_only_mode_warning"); //$NON-NLS-1$ } else { setFilename( fileInfo.getName() ); setCurrentSelection( new IFileStore[] { fileStore } ); } } catch ( CoreException cExc ) { errMsg = String.format( Messages.getString("GridFileDialog.create_file_store_error"), cExc.getLocalizedMessage() ); //$NON-NLS-1$ } if ( errMsg != null ) { setFilename( null ); setCurrentSelection( null ); } setErrorMessage( errMsg ); } else { setFilename( null ); setCurrentSelection( null ); } } protected void setMode( final int mode ) { switch ( mode ) { case ModeManager.CONNECTION_MODE: this.treeViewer.setInput( GridModel.getConnectionManager() ); break; case ModeManager.ROOT_MODE: this.treeViewer.setInput( EFS.getLocalFileSystem().getStore( new Path( ROOT_FOLDER ) ) ); break; case ModeManager.HOME_MODE: String home = System.getProperty( HOME_PROPERTY ); this.treeViewer.setInput( EFS.getLocalFileSystem().getStore( new Path( home ) ) ); break; case ModeManager.WS_MODE: this.treeViewer.setInput( GridModel.getRoot() ); break; } } /** * Refreshes the {@link TreeViewer} starting with the specified element. If * the element is <code>null</code> the whole {@link TreeViewer} will be * refreshed. * * @param element The {@link IGridElement} that will be refreshed. This also * includes the element's children. */ protected void refreshViewer( final IGridElement element ) { Control control = this.treeViewer.getControl(); if ( ! control.isDisposed() ) { Display display = control.getDisplay(); display.asyncExec( new Runnable() { public void run() { if ( ! GridFileDialog.this.treeViewer.getControl().isDisposed() ) { if ( element == null ) { GridFileDialog.this.treeViewer.refresh( false ); } else { if ( element instanceof IGridContainer ) { IGridContainer container = ( IGridContainer ) element; if ( container.isLazy() && container.isDirty() ) { GridFileDialog.this.treeViewer.setChildCount( container, container.getChildCount() ); } } GridFileDialog.this.treeViewer.refresh( element, false ); } } } } ); } } /** * Add the specified {@link FileTypeFilter} to this dialog's filters. * * @param filter The filter to be added. * @param description A description of the filter set as refering text * in the type filter combo. */ private void addFileTypeFilter( final FileTypeFilter filter, final String description ) { this.filetypeFilters.put( description, filter ); configureViewerFilters(); } /** * Helper method to determine if this dialog was constructed with * the specified style. * * @param bit One of the style constants. * @return True if the specified style constant was specified for * this dialog. */ private boolean hasStyle( final int bit ) { return ( this.style & bit ) != 0; } /** * Initialize the file type combo with the available file type filters. */ private void initializeFileTypeCombo() { if ( this.filetypeCombo != null ) { this.filetypeCombo.removeAll(); Set< String > keySet = this.filetypeFilters.keySet(); String[] keyArray = keySet.toArray( new String[ keySet.size() ] ); Arrays.sort( keyArray, new Comparator< String >() { public int compare( final String s1, final String s2 ) { String p1 = GridFileDialog.this.filetypeFilters.get( s1 ).getPrefix(); String p2 = GridFileDialog.this.filetypeFilters.get( s2 ).getPrefix(); return p1.compareToIgnoreCase( p2 ); } } ); this.filetypeCombo.setItems( keyArray ); this.filetypeCombo.select( 0 ); } } /** * Check if there are not ambiguities in the dialog's style. */ private void assertStyle() { Assert.isTrue( ! ( ( hasStyle( STYLE_ALLOW_ONLY_LOCAL ) && hasStyle( STYLE_ALLOW_ONLY_CONNECTIONS ) ) || ( hasStyle( STYLE_ALLOW_ONLY_LOCAL ) && hasStyle( STYLE_ALLOW_ONLY_REMOTE_CONNECTIONS ) ) || ( hasStyle( STYLE_ALLOW_ONLY_CONNECTIONS ) && hasStyle( STYLE_ALLOW_ONLY_REMOTE_CONNECTIONS ) ) ), "Only one of STYLE_ALLOW_ONLY_LOCAL, STYLE_ALLOW_ONLY_CONNECTIONS" //$NON-NLS-1$ + " and STYLE_ALLOW_ONLY_REMOTE_CONNECTIONS is allowed" //$NON-NLS-1$ ); Assert.isTrue( ! ( hasStyle( STYLE_ALLOW_ONLY_FILES ) && hasStyle( STYLE_ALLOW_ONLY_FOLDERS ) ), "Only one of STYLE_ALLOW_ONLY_FILES and STYLE_ALLOW_ONLY_FOLDERS is allowed" //$NON-NLS-1$ ); } private String getFilename() { String result = null; if ( ( this.filenameCombo != null ) && ! this.filenameCombo.isDisposed() ) { result = this.filenameCombo.getText(); } return result; } private IFileStore[] getSelection() { IFileStore[] result = null; if ( ( this.treeViewer != null ) && ! this.treeViewer.getTree().isDisposed() ) { List< IFileStore > list = new ArrayList< IFileStore >(); IStructuredSelection selection = ( IStructuredSelection ) this.treeViewer.getSelection(); Iterator< ? > iterator = selection.iterator(); while ( iterator.hasNext() ) { Object o = iterator.next(); if ( o instanceof IGridConnectionElement ) { try { o = ( ( IGridConnectionElement ) o ).getConnectionFileStore(); } catch ( CoreException cExc ) { // Silently ignored } } else if ( o instanceof IGridElement ) { o = ( ( IGridElement ) o ).getFileStore(); } if ( o instanceof IFileStore ) { list.add( ( IFileStore ) o ); } } if ( ! list.isEmpty() ) { result = list.toArray( new IFileStore[ list.size() ] ); } } return result; } private URI getURI() { URI result = null; setErrorMessage( null ); if ( ( this.uriCombo != null ) && ! this.uriCombo.isDisposed() ) { String text = this.uriCombo.getText(); try { result = new URI( text ); } catch ( URISyntaxException uriExc ) { setErrorMessage( String.format( Messages.getString("GridFileDialog.invalid_uri_error"), uriExc.getLocalizedMessage() ) ); //$NON-NLS-1$ } } return result; } private void setCurrentSelection( final IFileStore[] selection ) { if ( ( selection != null ) && ( selection.length > 0 ) ){ this.currentSelection = new IFileStore[ selection.length ]; System.arraycopy( selection, 0, this.currentSelection, 0, selection.length ); getButton( IDialogConstants.OK_ID ).setEnabled( true ); } else { this.currentSelection = null; getButton( IDialogConstants.OK_ID ).setEnabled( false ); } } private void setFilename( final String filename ) { if ( ( this.filenameCombo != null ) && ! this.filenameCombo.isDisposed() ) { if ( filename == null ) { this.filenameCombo.setText( EMPTY_STRING ); } else { this.filenameCombo.setText( filename ); } } } private void setURI( final URI uri ) { if ( ( this.uriCombo != null ) && ! this.uriCombo.isDisposed() ) { if ( uri == null ) { this.uriCombo.setText( EMPTY_STRING ); } else { GEclipseURI geclURI = new GEclipseURI( uri ); this.uriCombo.setText( geclURI.toSlaveURI().toString() ); } } } /** * Enables or disables notifications about modifications of the uri * and filename combos and changes in the selection of the tree viewer. * * @param b If <code>true</code> the notifications will be switched * on, if <code>false</code> they will be switched of. */ protected void setNotificationEnabled( final boolean b ) { if ( b ) { if ( this.uriCombo != null ) { this.uriCombo.addModifyListener( this.uriListener ); } if ( this.filenameCombo != null ) { this.filenameCombo.addVerifyListener( this.filenameListener ); } this.treeViewer.addSelectionChangedListener( this.selectionListener ); } else { if ( this.uriCombo != null ) { this.uriCombo.removeModifyListener( this.uriListener ); } if ( this.filenameCombo != null ) { this.filenameCombo.removeVerifyListener( this.filenameListener ); } this.treeViewer.removeSelectionChangedListener( this.selectionListener ); } } private boolean validateFilename( final String filename ) { return POSIX_FILENAME.matcher( filename ).matches(); } }