/****************************************************************************** * Copyright (c) 2010-2013, Linagora * * 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: * Linagora - initial API and implementation *******************************************************************************/ package com.ebmwebsourcing.petals.services.wizards; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IStatus; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.CheckboxCellEditor; import org.eclipse.jface.viewers.ColumnWeightData; import org.eclipse.jface.viewers.ICellModifier; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.TableLayout; import org.eclipse.jface.viewers.TextCellEditor; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerSorter; import org.eclipse.jface.wizard.WizardPage; 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.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.DirectoryDialog; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.wst.xml.ui.internal.properties.StringComboBoxCellEditor; import com.ebmwebsourcing.petals.services.PetalsServicesPlugin; import com.ebmwebsourcing.petals.services.wizards.beans.SaImportBean; import com.ebmwebsourcing.petals.services.wizards.beans.ServiceImportBean; import com.ebmwebsourcing.petals.services.wizards.beans.SuImportBean; /** * A page that shows a tree and widgets to create SU and SA projects. * @author Vincent Zurczak - EBM WebSourcing */ public abstract class AbstractPetalsServiceCreationWizardPage extends WizardPage { private static final String COMPONENT_NAME = "Component"; private static final String PROJECT_NAME = "Project Name"; private static final String COMPONENT_VERSION = "Version"; private static final String CREATE = "Create"; private final static String OVERWRITE = "Overwrite"; /** * The default array of versions, to use to not make the combo editor crash. */ private final static String[] DEFAULT_VERSIONS = new String[] { "" }; private Image suImg, saImg; private Image checked, unchecked; private String projectLocation; private boolean complete = false; private boolean isAtDefaultLocation = true; private TreeViewer viewer; private final List<SaImportBean> importsBeans = new ArrayList<SaImportBean> (); /** * Constructor. * @param title the page title * @param description the page description */ public AbstractPetalsServiceCreationWizardPage( String title, String description ) { super( title ); setTitle( title ); //NON-NLS-1 setDescription( description ); //NON-NLS-1 try { ImageDescriptor desc = PetalsServicesPlugin.getImageDescriptor( "icons/obj16/service_unit.png" ); if( desc != null ) this.suImg = desc.createImage(); desc = PetalsServicesPlugin.getImageDescriptor( "icons/obj16/service_assembly.png" ); if( desc != null ) this.saImg = desc.createImage(); desc = PetalsServicesPlugin.getImageDescriptor( "icons/obj16/checked.gif" ); if( desc != null ) this.checked = desc.createImage(); desc = PetalsServicesPlugin.getImageDescriptor( "icons/obj16/unchecked.gif" ); if( desc != null ) this.unchecked = desc.createImage(); } catch( Exception e ) { PetalsServicesPlugin.log( e, IStatus.WARNING ); } } /* * (non-Javadoc) * @see org.eclipse.jface.dialogs.IDialogPage * #createControl(org.eclipse.swt.widgets.Composite) */ @Override @SuppressWarnings( "restriction" ) public void createControl( Composite parent ) { Composite container = new Composite( parent, SWT.NONE ); container.setLayout( new GridLayout ()); container.setLayoutData( new GridData( GridData.FILL_BOTH )); // Before the workspace location createWidgetsBeforeProjectLocation( container ); // Workspace location final Button useDefaultLocButton = new Button( container, SWT.CHECK ); useDefaultLocButton.setText( "Create the project(s) in the default location" ); GridData layoutData = new GridData (); layoutData.verticalIndent = 17; useDefaultLocButton.setLayoutData( layoutData ); Composite locContainer = new Composite( container, SWT.NONE ); GridLayout layout = new GridLayout( 3, false ); layout.marginHeight = layout.marginWidth = 0; locContainer.setLayout( layout ); locContainer.setLayoutData( new GridData( GridData.FILL_HORIZONTAL )); final Label locLabel = new Label( locContainer, SWT.NONE ); locLabel.setText( "Project(s) location:" ); final Text projectLocationText = new Text( locContainer, SWT.SINGLE | SWT.BORDER ); projectLocationText.setLayoutData( new GridData( GridData.FILL_HORIZONTAL )); projectLocationText.setText( ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString()); projectLocationText.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { AbstractPetalsServiceCreationWizardPage.this.projectLocation = projectLocationText.getText(); validate(); } }); final Button browseButton = new Button( locContainer, SWT.PUSH ); browseButton.setText( "Browse..." ); browseButton.addSelectionListener( new SelectionAdapter () { @Override public void widgetSelected( SelectionEvent e ) { String location = new DirectoryDialog( getShell()).open(); if( location != null ) projectLocationText.setText( location ); } }); useDefaultLocButton.setSelection( this.isAtDefaultLocation ); useDefaultLocButton.addSelectionListener( new SelectionAdapter () { @Override public void widgetSelected( SelectionEvent e ) { AbstractPetalsServiceCreationWizardPage.this.isAtDefaultLocation = useDefaultLocButton.getSelection(); boolean use = ! AbstractPetalsServiceCreationWizardPage.this.isAtDefaultLocation; locLabel.setEnabled( use ); projectLocationText.setEnabled( use ); browseButton.setEnabled( use ); projectLocationText.setFocus(); } }); // List of projects to create Label l = new Label( container, SWT.NONE ); l.setText( "Specify the properties of the target project(s)." ); layoutData = new GridData(); layoutData.verticalIndent = 17; l.setLayoutData( layoutData ); Tree tree = new Tree( container, SWT.FULL_SELECTION | SWT.BORDER | SWT.SINGLE ); layoutData = new GridData( GridData.FILL_BOTH ); layoutData.heightHint = 90; tree.setLayoutData( layoutData ); tree.setHeaderVisible( true ); tree.setLinesVisible( true ); TableLayout tlayout = new TableLayout(); tlayout.addColumnData( new ColumnWeightData( 40, 150, false )); tlayout.addColumnData( new ColumnWeightData( 10, 10, false )); tlayout.addColumnData( new ColumnWeightData( 10, 15, false )); tlayout.addColumnData( new ColumnWeightData( 20, 80, true )); tlayout.addColumnData( new ColumnWeightData( 20, 60, true )); tree.setLayout( tlayout ); TreeColumn column = new TreeColumn( tree, SWT.LEFT ); column.setText( PROJECT_NAME ); column = new TreeColumn( tree, SWT.CENTER ); column.setText( CREATE ); column = new TreeColumn( tree, SWT.CENTER ); column.setText( OVERWRITE ); column = new TreeColumn( tree, SWT.LEFT ); column.setText( COMPONENT_NAME ); column = new TreeColumn( tree, SWT.CENTER ); column.setText( COMPONENT_VERSION ); // Create the viewer this.viewer = new TreeViewer( tree ); // Define its content provider this.viewer.setContentProvider( new ITreeContentProvider() { @Override public Object[] getChildren( Object parentElement ) { Object[] children = new Object[ 0 ]; if( parentElement instanceof SaImportBean ) { children = new Object[((SaImportBean) parentElement).countSuBeans()]; children = ((SaImportBean) parentElement).getSuBeans().toArray( children ); } return children; } @Override public Object getParent( Object element ) { return null; } @Override public boolean hasChildren( Object element ) { boolean hasChildren = false; if( element instanceof SaImportBean ) { hasChildren = ((SaImportBean) element).countSuBeans() > 0; } return hasChildren; } @Override public Object[] getElements( Object inputElement ) { Object[] result = new Object[ ((Collection<?>) inputElement).size()]; return ((Collection<?>) inputElement).toArray( result ); } @Override public void dispose() { // nothing } @Override public void inputChanged( Viewer viewer, Object oldInput, Object newInput ) { // nothing } }); // Define its label provider this.viewer.setLabelProvider( new ITableLabelProvider() { @Override public Image getColumnImage( Object element, int columnIndex ) { Image result = null; switch( columnIndex ) { case 0: if( element instanceof SaImportBean ) result = AbstractPetalsServiceCreationWizardPage.this.saImg; else if( element instanceof SuImportBean ) result = AbstractPetalsServiceCreationWizardPage.this.suImg; break; case 1: if( element instanceof ServiceImportBean ) { result = ((ServiceImportBean) element).isToCreate() ? AbstractPetalsServiceCreationWizardPage.this.checked : AbstractPetalsServiceCreationWizardPage.this.unchecked; } break; case 2: if( element instanceof ServiceImportBean ) { result = ((ServiceImportBean) element).isToOverwrite() ? AbstractPetalsServiceCreationWizardPage.this.checked : AbstractPetalsServiceCreationWizardPage.this.unchecked; } break; } return result; } @Override public String getColumnText( Object element, int columnIndex ) { String result = ""; switch( columnIndex ) { case 0: if( element instanceof ServiceImportBean ) result = ((ServiceImportBean) element).getProjectName(); break; case 1: case 2: break; case 3: if( element instanceof SuImportBean ) result = ((SuImportBean) element).getComponentName(); break; case 4: if( element instanceof SuImportBean ) result = ((SuImportBean) element).getComponentVersion(); break; } return result; } @Override public void addListener( ILabelProviderListener listener ) { // nothing } @Override public void dispose() { // nothing } @Override public boolean isLabelProperty( Object element, String property ) { return false; } @Override public void removeListener( ILabelProviderListener listener ) { // nothing } }); // Define its sorter this.viewer.setSorter( new ViewerSorter() { @Override public int compare( Viewer viewer, Object e1, Object e2 ) { if( e1 instanceof ServiceImportBean && e2 instanceof ServiceImportBean ) { String n1 = ((ServiceImportBean) e1).getProjectName(); if( n1 == null ) n1 = ""; String n2 = ((ServiceImportBean) e2).getProjectName(); if( n2 == null ) n2 = ""; return n1.compareTo( n2 ); } return super.compare( viewer, e1, e2 ); } }); // Create the editor for the versions (it must be done before creating the cell modifiers). // This is done this way to allow the editor to store and retrieve custom values (i.e. versions // that are not registered but manually entered by the user). final StringComboBoxCellEditor comboEditor = new StringComboBoxCellEditor( tree, DEFAULT_VERSIONS, SWT.DROP_DOWN ); this.viewer.addSelectionChangedListener( new ISelectionChangedListener() { @Override public void selectionChanged( SelectionChangedEvent event ) { if( ! event.getSelection().isEmpty()) { Object o = ((IStructuredSelection) event.getSelection()).getFirstElement(); if( o instanceof SuImportBean ) { // If there is a custom value, add it in the list SuImportBean suBean = (SuImportBean) o; List<String> versions = Arrays.asList( suBean.getSupportedVersions()); versions = new ArrayList<String>( versions ); if( ! versions.contains( suBean.getComponentVersion())) versions.add( suBean.getComponentVersion()); String[] items = new String[ versions.size()]; comboEditor.setItems( versions.toArray( items )); } else if( o instanceof SaImportBean ) comboEditor.setItems( DEFAULT_VERSIONS ); } } }); // Define its cell modifier this.viewer.setCellModifier( new ICellModifier() { @Override public void modify( Object element, String property, Object value ) { TreeItem tableItem = (TreeItem) element; if( PROJECT_NAME.equals( property )) { ServiceImportBean bean = (ServiceImportBean) tableItem.getData(); bean.setProjectName((String) value); AbstractPetalsServiceCreationWizardPage.this.viewer.refresh( bean ); validate(); } else if( CREATE.equals( property )) { ServiceImportBean bean = (ServiceImportBean) tableItem.getData(); bean.setToCreate((Boolean) value); AbstractPetalsServiceCreationWizardPage.this.viewer.refresh( bean ); validate(); } else if( OVERWRITE.equals( property )) { ServiceImportBean bean = (ServiceImportBean) tableItem.getData(); bean.setOverwrite((Boolean) value); AbstractPetalsServiceCreationWizardPage.this.viewer.refresh( bean ); validate(); } else if( tableItem.getData() instanceof SuImportBean ) { if( COMPONENT_NAME.equals( property )) { SuImportBean bean = (SuImportBean) tableItem.getData(); bean.setComponentName((String) value); AbstractPetalsServiceCreationWizardPage.this.viewer.refresh( bean ); validate(); } else if( COMPONENT_VERSION.equals( property )) { SuImportBean bean = (SuImportBean) tableItem.getData(); bean.setComponentVersion((String) value); AbstractPetalsServiceCreationWizardPage.this.viewer.refresh( bean ); validate(); } } } @Override public Object getValue( Object element, String property ) { Object value = null; if( PROJECT_NAME.equals( property )) value = ((ServiceImportBean) element).getProjectName(); else if( CREATE.equals( property )) value = Boolean.valueOf(((ServiceImportBean) element).isToCreate()); else if( OVERWRITE.equals( property )) value = ((ServiceImportBean) element).isToOverwrite(); else if( element instanceof SuImportBean ) { SuImportBean bean = (SuImportBean) element; if( COMPONENT_NAME.equals( property )) value = bean.getComponentName(); else if( COMPONENT_VERSION.equals( property )) value = bean.getComponentVersion(); } return value; } @Override public boolean canModify( Object element, String property ) { boolean canModify = true; if( element instanceof SaImportBean ) { canModify = PROJECT_NAME.equals( property ) || CREATE.equals( property ) || OVERWRITE.equals( property ); } return canModify; } }); // End up with the viewer properties this.viewer.setColumnProperties( new String[] { PROJECT_NAME, CREATE, OVERWRITE, COMPONENT_NAME, COMPONENT_VERSION }); this.viewer.setCellEditors( new CellEditor[] { new TextCellEditor( tree ), new CheckboxCellEditor( tree ), new CheckboxCellEditor( tree ), new TextCellEditor( tree, SWT.READ_ONLY ), comboEditor }); // Last steps if( ! this.importsBeans.isEmpty()) { this.viewer.setInput( this.importsBeans ); this.viewer.expandAll(); } useDefaultLocButton.notifyListeners( SWT.Selection, new Event()); tree.notifyListeners( SWT.Selection, new Event()); validate(); setErrorMessage( null ); setControl( container ); // Force the shell size Point size = getShell().computeSize( 700, 600 ); getShell().setSize( size ); Rectangle rect = Display.getCurrent().getBounds(); getShell().setLocation((rect.width - size.x) / 2, (rect.height - size.y) / 2); } /** * Validates the page entries. * @return true if the validation succeeded, false otherwise */ protected boolean validate() { // Project location if( ! this.isAtDefaultLocation ) { if( this.projectLocation.trim().length() == 0 ) { setErrorMessage( "The project(s) location is not specified." ); setPageComplete( false ); return false; } if( ! new File( this.projectLocation ).exists()) { setErrorMessage( "The project(s) location is not a valid directory." ); setPageComplete( false ); return false; } } // Validate the projects Set<String> names = new HashSet<String> (); for( SaImportBean saBean : this.importsBeans ) { if( ! validateServiceProject( saBean )) return false; for( SuImportBean suBean : saBean.getSuBeans()) { if( ! validateServiceProject( suBean )) return false; if( suBean.isToCreate() && ! isNameUnique( suBean.getProjectName(), names )) return false; } if( saBean.isToCreate() && ! isNameUnique( saBean.getProjectName(), names )) return false; } setErrorMessage( null ); setPageComplete( true ); return true; } /** * Tests the uniqueness of the project names. * @param name the name to check * @param names the stores names * @return true if this name is unique, false otherwise */ private boolean isNameUnique( String name, Set<String> names ) { boolean result = true; if( names.contains( name )) { setErrorMessage( "There is a conflict in the project names. " + name + " is already in the list." ); result = false; } else { names.add( name ); } setPageComplete( result ); return result; } /** * Validates the creation of a Petals service project. * @param serviceImportBean a service import bean * @return true if a project can be created from this bean, false otherwise */ private boolean validateServiceProject( ServiceImportBean serviceImportBean ) { boolean valid = true; if( serviceImportBean.isToCreate()) { IWorkspaceRoot wr = ResourcesPlugin.getWorkspace().getRoot(); // Existing project IProject p = wr.getProject( serviceImportBean.getProjectName()); if( p.exists() && serviceImportBean.isToCreate() && ! serviceImportBean.isToOverwrite()) { setErrorMessage( "The project " + serviceImportBean.getProjectName() + " already exists." ); setPageComplete( false ); valid = false; } // Existing target location ? if( ! this.isAtDefaultLocation ) { File f = new File( this.projectLocation, serviceImportBean.getProjectName()); if( f.exists()) { setErrorMessage( "A file already exists at the target location for " + serviceImportBean.getProjectName() + "." ); setPageComplete( false ); valid = false; } } } return valid; } /* * (non-Javadoc) * @see org.eclipse.jface.wizard.WizardPage * #isPageComplete() */ @Override public boolean isPageComplete() { return this.complete; } /* * (non-Javadoc) * @see org.eclipse.jface.wizard.WizardPage * #setPageComplete(boolean) */ @Override public void setPageComplete( boolean complete ) { this.complete = complete; super.setPageComplete( complete ); } /** * @return the importsBeans */ public List<SaImportBean> getImportsBeans() { return this.importsBeans; } /** * Adds import beans in the list managed by this class. * <p> * After insertion, the viewer is refreshed and expanded. * </p> * * @param importBeans the import beans to set (may be null) * @param clearBeforeInsertion true to empty the current list before insertion */ public void updateImportBeans( List<SaImportBean> importBeans, boolean clearBeforeInsertion ) { if( clearBeforeInsertion ) this.importsBeans.clear(); if( importBeans != null ) this.importsBeans.addAll( importBeans ); if( this.viewer != null ) { this.viewer.setInput( importBeans ); this.viewer.refresh(); this.viewer.expandAll(); } } /** * @return the isAtDefaultLocation */ public boolean isAtDefaultLocation() { return this.isAtDefaultLocation; } /** * @return the projectLocation */ public String getProjectLocation() { return this.projectLocation; } /* * (non-Javadoc) * @see org.eclipse.jface.dialogs.DialogPage#dispose() */ @Override public void dispose() { if( this.viewer.getLabelProvider() != null ) this.viewer.getLabelProvider().dispose(); if( this.suImg != null ) this.suImg.dispose(); if( this.saImg != null ) this.saImg.dispose(); if( this.checked != null ) this.checked.dispose(); if( this.unchecked != null ) this.unchecked.dispose(); releaseResources(); super.dispose(); } /** * Releases additional resources. */ protected abstract void releaseResources(); /** * Creates the widgets to show before the project location UI part. * @param container */ protected abstract void createWidgetsBeforeProjectLocation( Composite container ); }