// Copyright (c) 2010, B. Scott Michel package net.sf.eclipsefp.haskell.ui.internal.preferences.scion; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import net.sf.eclipsefp.haskell.core.cabal.CabalImplementation; import net.sf.eclipsefp.haskell.core.cabal.CabalImplementationManager; import net.sf.eclipsefp.haskell.ui.internal.util.UITexts; import net.sf.eclipsefp.haskell.ui.util.SWTUtil; import org.eclipse.core.runtime.ListenerList; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.layout.TableColumnLayout; import org.eclipse.jface.preference.PreferencePage; import org.eclipse.jface.viewers.CheckStateChangedEvent; import org.eclipse.jface.viewers.CheckboxTableViewer; import org.eclipse.jface.viewers.ColumnWeightData; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.ICheckStateListener; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.Image; 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.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; /** The Cabal implementations table and associated buttons, dialogs and UI * niceties. * * @author B. Scott Michel */ public class CabalImplsBlock implements ISelectionProvider { private final static String KEY_SORT_COLUMN = ".sortColumn"; private final static String KEY_COLUMN_WIDTH = ".columnWidth"; private CheckboxTableViewer viewer; private int sortColumn; private Table table; private Button btnAdd; private Button btnRemove; private Button btnEdit; private Button btnAutoDetect; private final List<CabalImplementation> impls = new ArrayList<>(); private final ListenerList selectionListeners = new ListenerList(); Composite createControl( final Composite parent, final PreferencePage prefParent ) { Composite composite = new Composite( parent, SWT.NONE ); Font parentFont = parent.getFont(); GridLayout glayout = new GridLayout(2, false); glayout.marginHeight = 0; glayout.marginWidth = 0; composite.setLayout( glayout ); composite.setFont( parentFont ); Label tableLabel = new Label( composite, SWT.NONE ); tableLabel.setText( UITexts.cabalImplsBlock_installed ); GridData gdata = new GridData( SWT.FILL, SWT.TOP, true, false ); gdata.horizontalSpan = 2; tableLabel.setLayoutData( gdata ); tableLabel.setFont( parentFont ); Composite tableComposite = new Composite ( composite, SWT.NONE ); tableComposite.setLayoutData( new GridData( SWT.FILL, SWT.FILL, true, true) ); table = SWTUtil.createTable( tableComposite ); createColumns( tableComposite ); createViewer(); // Deep copy the implementations, replace them later. CabalImplementationManager cMgr = CabalImplementationManager.getInstance(); impls.clear(); for (CabalImplementation impl : cMgr.getCabalImplementations()) { impls.add( new CabalImplementation( impl ) ); } viewer.setInput( impls ); // And set the current default (checked) implementation CabalImplementation defImpl = cMgr.getDefaultCabalImplementation(); if (defImpl != null) { CabalImplementation impl = findImplementation( defImpl.getUserIdentifier() ); setCheckedCabalImplementation( impl ); } Composite buttonsComp = new Composite( composite, SWT.NONE ); glayout = new GridLayout(1, true); glayout.marginHeight = 0; glayout.marginWidth = 0; buttonsComp.setLayout( glayout ); buttonsComp.setLayoutData( new GridData( SWT.CENTER, SWT.TOP, false, false ) ); buttonsComp.setFont( parentFont ); createButtons( buttonsComp ); enableButtons(); return composite; } /** Put the Cabal implementation preferences into the preference store. */ public boolean updateCabalImplementations( ) { CabalImplementationManager cMgr = CabalImplementationManager.getInstance(); Object[] selected = viewer.getCheckedElements(); CabalImplementation impl = (CabalImplementation) selected[0]; String defaultImplIdent = new String(); if (impl != null) { defaultImplIdent = impl.getUserIdentifier(); } cMgr.setCabalImplementations( impls, defaultImplIdent ); return true; } public boolean validate( final PreferencePage parent ) { boolean retval = true; String errorMsg = null; if( impls.size() <= 0 ) { errorMsg = UITexts.cabalImplsBlock_noCabalInstallations; retval = !retval; } else { Object[] checked = viewer.getCheckedElements(); if (checked.length > 0) { if (checked.length > 1) { errorMsg = UITexts.cabalImplsBlock_multipleImplsSelected; retval = !retval; } } else { // <= 0... errorMsg = UITexts.cabalImplsBlock_noCabalInstallationSelected; retval = !retval; } } if (!retval) { parent.setErrorMessage( errorMsg ); } return retval; } // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // Internal helper methods // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ private void add(final CabalImplementation impl) { impls.add( impl ); } private void update (final String identifier, final CabalImplementation theImpl) { CabalImplementation impl = findImplementation( identifier ); if (impl != null) { // Update the fields. impl.copy (theImpl); } } @Override public void addSelectionChangedListener( final ISelectionChangedListener listener ) { selectionListeners.add( listener ); } @Override public ISelection getSelection() { return new StructuredSelection( viewer.getCheckedElements() ); } public CabalImplementation getCheckedCabalImplementation() { CabalImplementation result = null; Object[] objects = viewer.getCheckedElements(); if( objects.length > 0 ) { result = ( CabalImplementation ) objects[ 0 ]; } return result; } @Override public void removeSelectionChangedListener( final ISelectionChangedListener listener ) { selectionListeners.remove( listener ); } @Override public void setSelection( final ISelection selection ) { if( selection instanceof IStructuredSelection ) { Object elem = ( ( IStructuredSelection )selection ).getFirstElement(); if( elem == null ) { viewer.setCheckedElements( new Object[ 0 ] ); } else { viewer.setCheckedElements( new Object[] { elem } ); viewer.reveal( elem ); } fireSelectionChanged(); } } private void setCheckedCabalImplementation( final String identifier ) { setCheckedCabalImplementation( findImplementation( identifier ) ); } private void setCheckedCabalImplementation( final CabalImplementation impl ) { if( impl == null ) { setSelection( new StructuredSelection() ); } else { setSelection( new StructuredSelection( impl ) ); } fireSelectionChanged(); } private CabalImplementation findImplementation( final String ident ) { CabalImplementation retval = null; for (CabalImplementation impl : impls) { if (impl.getUserIdentifier().equals( ident )) { retval = impl; break; } } return retval; } // Helper methods: private void createColumns(final Composite composite) { TableColumn colName = createColumn( UITexts.cabalImplsBlock_colName, new SelectionAdapter() { @Override public void widgetSelected( final SelectionEvent evt ) { sortByUserIdentifier(); } } ); TableColumn colInstallVersion = createColumn (UITexts.cabalImplsBlock_colCabalInstallVersion, new SelectionAdapter() { @Override public void widgetSelected ( final SelectionEvent evt ) { sortByInstallVersion(); } } ); TableColumn colLibraryVersion = createColumn (UITexts.cabalImplsBlock_colCabalLibraryVersion, new SelectionAdapter() { @Override public void widgetSelected ( final SelectionEvent evt ) { sortByLibraryVersion(); } } ); TableColumn colCabalPath = createColumn (UITexts.cabalImplsBlock_colCabalPath, new SelectionAdapter() { @Override public void widgetSelected ( final SelectionEvent evt ) { sortByExecutablePath(); } }); TableColumnLayout tcLayout = new TableColumnLayout(); composite.setLayout( tcLayout ); tcLayout.setColumnData( colName, new ColumnWeightData( 25, true ) ); tcLayout.setColumnData( colInstallVersion, new ColumnWeightData( 20, true ) ); tcLayout.setColumnData( colLibraryVersion, new ColumnWeightData( 20, true ) ); tcLayout.setColumnData( colCabalPath, new ColumnWeightData( 35, true ) ); } private TableColumn createColumn( final String text, final SelectionListener listener ) { TableColumn result = new TableColumn( table, SWT.NONE ); result.setText( text ); result.addSelectionListener( listener ); return result; } private void createButtons( final Composite buttonsComp ) { String sAdd = UITexts.cabalImplsBlock_btnAdd; btnAdd = SWTUtil.createPushButton( buttonsComp, sAdd ); btnAdd.addListener( SWT.Selection, new Listener() { @Override public void handleEvent( final Event evt ) { addCabalImplementation(); } } ); String sEdit = UITexts.implementationsBlock_btnEdit; btnEdit = SWTUtil.createPushButton( buttonsComp, sEdit ); btnEdit.addListener( SWT.Selection, new Listener() { @Override public void handleEvent( final Event evt ) { editCabalImplementation(); } } ); String sRemove = UITexts.cabalImplsBlock_btnRemove; btnRemove = SWTUtil.createPushButton( buttonsComp, sRemove ); btnRemove.addListener( SWT.Selection, new Listener() { @Override public void handleEvent( final Event evt ) { removeSelectedCabalImplementations(); } } ); String sDetect = UITexts.cabalImplsBlock_btnAutoDetect; btnAutoDetect = SWTUtil.createPushButton( buttonsComp, sDetect ); btnAutoDetect.addListener( SWT.Selection, new Listener() { @Override public void handleEvent (final Event ev) { autoDetectCabalImpls(); } }); } private void createViewer() { viewer = new CheckboxTableViewer( table ); viewer.setLabelProvider( new CabalImplsLP() ); viewer.setContentProvider( new CabalImplsCP( impls ) ); sortByUserIdentifier(); viewer.addSelectionChangedListener( new ISelectionChangedListener() { @Override public void selectionChanged( final SelectionChangedEvent evt ) { enableButtons(); } } ); viewer.addCheckStateListener( new ICheckStateListener() { @Override public void checkStateChanged( final CheckStateChangedEvent event ) { CabalImplementation element = ( CabalImplementation ) event.getElement(); if (!event.getChecked()) { element = null; } setCheckedCabalImplementation( element ); } } ); viewer.addDoubleClickListener( new IDoubleClickListener() { @Override public void doubleClick( final DoubleClickEvent e ) { if( !viewer.getSelection().isEmpty() ) { editCabalImplementation(); } } } ); } private void fireSelectionChanged() { SelectionChangedEvent evt = new SelectionChangedEvent( this, getSelection() ); Object[] lis = selectionListeners.getListeners(); for( int i = 0; i < lis.length; i++ ) { ISelectionChangedListener li = ( ISelectionChangedListener )lis[ i ]; li.selectionChanged( evt ); } } private void enableButtons() { IStructuredSelection ssel = (IStructuredSelection) viewer.getSelection(); btnEdit.setEnabled( ssel.size() == 1 ); boolean moreThanSelectedItem = ssel.size() > 0 && ssel.size() < viewer.getTable().getItemCount(); btnRemove.setEnabled( moreThanSelectedItem ); } private void autoSelectSingle( final IStructuredSelection prev ) { IStructuredSelection curr = ( IStructuredSelection ) getSelection(); if( !curr.equals( prev ) || curr.isEmpty() ) { if( curr.size() == 0 && impls.size() == 1 ) { // pick a default automatically setSelection( new StructuredSelection( impls.get( 0 ) ) ); } } fireSelectionChanged(); } private void addCabalImplementation() { IStructuredSelection prev = ( IStructuredSelection )getSelection(); CabalImplementationDialog dialog = new CabalImplementationDialog( table.getShell(), null ); dialog.setTitle( UITexts.cabalImplsBlock_dlgAdd ); if( dialog.open() == Window.OK ) { CabalImplementation impl = dialog.getResult(); if (validateImpl(impl)) { add( impl ); viewer.setInput( impl ); viewer.refresh(); } autoSelectSingle( prev ); } } private void editCabalImplementation() { IStructuredSelection prev = ( IStructuredSelection )viewer.getSelection(); if (prev!=null) { CabalImplementation impl = (CabalImplementation)prev.getFirstElement(); String implIdent = impl.getUserIdentifier(); CabalImplementationDialog dialog = new CabalImplementationDialog( table.getShell(), impl ); dialog.setTitle( UITexts.cabalImplsBlock_dlgEdit ); if (dialog.open() == Window.OK) { CabalImplementation updatedImpl = dialog.getResult(); if (validateImpl( updatedImpl )) { update( implIdent, updatedImpl ); setCheckedCabalImplementation( updatedImpl.getUserIdentifier() ); viewer.setInput( impl ); } viewer.refresh(); autoSelectSingle( prev ); } } } private void removeSelectedCabalImplementations() { IStructuredSelection ssel = ( IStructuredSelection ) viewer.getSelection(); IStructuredSelection prev = ( IStructuredSelection ) getSelection(); CabalImplementation[] insts = new CabalImplementation[ ssel.size() ]; @SuppressWarnings("unchecked") Iterator<CabalImplementation> iter = ssel.iterator(); int i = 0; while( iter.hasNext() ) { insts[ i ] = iter.next(); i++; } removeCabalImplementations( insts ); viewer.refresh(); autoSelectSingle( prev ); } private void removeCabalImplementations( final CabalImplementation[] insts) { for( int i = 0; i < insts.length; i++ ) { impls.remove( insts[ i ] ); } } private void autoDetectCabalImpls() { viewer.remove( impls ); viewer.setInput( null ); List<CabalImplementation> detected=CabalImplementationManager.autoDetectCabalImpls(); impls.clear(); impls.addAll( detected ); if (impls.size() > 0) { viewer.add(impls); viewer.setInput( impls.toArray() ); setSelection(new StructuredSelection( impls.get(0) )); } else { setSelection( new StructuredSelection ( ) ); } viewer.refresh(true); } private boolean validateImpl( final CabalImplementation impl ) { return (impl != null) && CabalImplementationManager.isUniqueUserIdentifier(impl.getUserIdentifier(),impls); } private void sortByUserIdentifier() { viewer.setComparator( new Compare_UserIdentifier() ); sortColumn = 1; } private void sortByExecutablePath() { viewer.setComparator( new Compare_ExecutablePath() ); sortColumn = 4; } private void sortByInstallVersion() { viewer.setComparator( new Compare_InstallVersion() ); sortColumn = 2; } private void sortByLibraryVersion() { viewer.setComparator( new Compare_LibraryVersion() ); sortColumn = 3; } public void saveColumnSettings( final IDialogSettings settings, final String qualifier ) { int columnCount = table.getColumnCount(); for( int i = 0; i < columnCount; i++ ) { int width = table.getColumn( i ).getWidth(); settings.put( qualifier + KEY_COLUMN_WIDTH + i, width ); } settings.put( qualifier + KEY_SORT_COLUMN, sortColumn ); } public void restoreColumnSettings( final IDialogSettings settings, final String qualifier ) { viewer.getTable().layout( true ); restoreColumnWidths( settings, qualifier ); try { sortColumn = settings.getInt( qualifier + KEY_SORT_COLUMN ); } catch( final NumberFormatException numfex ) { sortColumn = 1; } switch( sortColumn ) { case 1: sortByUserIdentifier(); break; case 2: sortByInstallVersion(); break; case 3: sortByLibraryVersion(); break; case 4: sortByExecutablePath(); break; } } private void restoreColumnWidths( final IDialogSettings settings, final String qualifier ) { int columnCount = table.getColumnCount(); for( int i = 0; i < columnCount; i++ ) { int width = -1; try { width = settings.getInt( qualifier + KEY_COLUMN_WIDTH + i ); } catch( final NumberFormatException numfex ) { // ignored } if( width > 0 ) { // Only override column widths if the preference exists, otherwise, // we go with the column weights previously configured in the table's // layout. table.getColumn( i ).setWidth( width ); } } } /** The internal content provider class */ private class CabalImplsCP implements IStructuredContentProvider { CabalImplsCP( final List<CabalImplementation> impls ) { // Unused. } @Override public void dispose() { // Unused. } @Override public void inputChanged( final Viewer viewer, final Object oldInput, final Object newInput ) { // unused } @Override public Object[] getElements( final Object inputElement ) { return impls.toArray(); } } /** Internal table label provider class */ public class CabalImplsLP extends LabelProvider implements ITableLabelProvider { @Override public Image getColumnImage( final Object element, final int columnIndex ) { return null; } @Override public String getColumnText( final Object elem, final int columnIndex ) { String result = null; if ( elem instanceof CabalImplementation ) { CabalImplementation impl = ( CabalImplementation ) elem; switch( columnIndex ) { case 0: { result = impl.getUserIdentifier(); break; } case 1: result = impl.getInstallVersion(); break; case 2: result = impl.getLibraryVersion(); break; case 3: { result = impl.getCabalExecutableName().toOSString(); break; } } } else { result = elem.toString(); } return result; } } /** Internal viewer comparator class: sort by user identifier */ private final class Compare_UserIdentifier extends ViewerComparator { @Override public int compare( final Viewer viewer, final Object e1, final Object e2 ) { int result = super.compare( viewer, e1, e2 ); if( ( e1 instanceof CabalImplementation ) && ( e2 instanceof CabalImplementation ) ) { CabalImplementation left = ( CabalImplementation ) e1; CabalImplementation right = ( CabalImplementation )e2; result = left.getUserIdentifier().compareToIgnoreCase( right.getUserIdentifier() ); } return result; } @Override public boolean isSorterProperty( final Object element, final String property ) { return true; } } public class Compare_ExecutablePath extends ViewerComparator { @Override public int compare( final Viewer viewer, final Object e1, final Object e2 ) { int result = super.compare( viewer, e1, e2 ); if( ( e1 instanceof CabalImplementation ) && ( e2 instanceof CabalImplementation ) ) { CabalImplementation left = ( CabalImplementation ) e1; CabalImplementation right = ( CabalImplementation )e2; String pathLeft = left.getCabalExecutableName().toOSString(); String pathRight = right.getCabalExecutableName().toOSString(); result = pathLeft.compareToIgnoreCase( pathRight ); } return result; } @Override public boolean isSorterProperty( final Object element, final String property ) { return true; } } public class Compare_InstallVersion extends ViewerComparator { @Override public int compare( final Viewer viewer, final Object e1, final Object e2 ) { int result = super.compare( viewer, e1, e2 ); if( ( e1 instanceof CabalImplementation ) && ( e2 instanceof CabalImplementation ) ) { CabalImplementation left = ( CabalImplementation ) e1; CabalImplementation right = ( CabalImplementation )e2; result = left.getInstallVersion().compareToIgnoreCase( right.getInstallVersion() ); } return result; } @Override public boolean isSorterProperty( final Object element, final String property ) { return true; } } public class Compare_LibraryVersion extends ViewerComparator { @Override public int compare( final Viewer viewer, final Object e1, final Object e2 ) { int result = super.compare( viewer, e1, e2 ); if( ( e1 instanceof CabalImplementation ) && ( e2 instanceof CabalImplementation ) ) { CabalImplementation left = ( CabalImplementation ) e1; CabalImplementation right = ( CabalImplementation )e2; result = left.getLibraryVersion().compareToIgnoreCase( right.getLibraryVersion() ); } return result; } @Override public boolean isSorterProperty( final Object element, final String property ) { return true; } } }