/**
* Copyright (c) 2013 by JP Moresmau
* This code is made available under the terms of the Eclipse Public License,
* version 1.0 (EPL). See http://www.eclipse.org/legal/epl-v10.html
*/
package net.sf.eclipsefp.haskell.ui.views;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import net.sf.eclipsefp.haskell.browser.BrowserPerspective;
import net.sf.eclipsefp.haskell.browser.BrowserPlugin;
import net.sf.eclipsefp.haskell.browser.views.packages.PackagesView;
import net.sf.eclipsefp.haskell.core.cabal.CabalImplementationManager;
import net.sf.eclipsefp.haskell.core.cabal.CabalPackageRef;
import net.sf.eclipsefp.haskell.core.cabal.CabalPackageVersion;
import net.sf.eclipsefp.haskell.debug.core.internal.launch.AbstractHaskellLaunchDelegate;
import net.sf.eclipsefp.haskell.ui.HaskellUIPlugin;
import net.sf.eclipsefp.haskell.ui.handlers.OpenDefinitionHandler;
import net.sf.eclipsefp.haskell.ui.internal.backend.BackendManager;
import net.sf.eclipsefp.haskell.ui.internal.backend.CabalPackageHelper;
import net.sf.eclipsefp.haskell.ui.internal.util.UITexts;
import net.sf.eclipsefp.haskell.ui.properties.ImportLibrariesLabelProvider;
import net.sf.eclipsefp.haskell.ui.util.HaskellUIImages;
import net.sf.eclipsefp.haskell.ui.util.IImageNames;
import net.sf.eclipsefp.haskell.util.CommandLineUtil;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.ErrorDialog;
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.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MenuEvent;
import org.eclipse.swt.events.MenuListener;
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.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.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.part.ViewPart;
/**
* A view showing all installed packages, all packages retrieved from hackage, and buttons to install a package and update the list
* @author JP Moresmau
*
*/
public class CabalPackagesView extends ViewPart {
public static final String ID="net.sf.eclipsefp.haskell.ui.views.CabalPackagesView";
public static void refresh(){
/** clear cache **/
CabalPackageHelper.getInstance().clear();
Display.getDefault().asyncExec( new Runnable(){
@Override
public void run() {
IWorkbenchPage p=HaskellUIPlugin.getActivePage();
IViewPart part=p.findView( ID );
if (part instanceof CabalPackagesView){
CabalPackagesView v=((CabalPackagesView)part);
/** only if we show the installed packages **/
/** nope, now we show ticks for installed packages on list of all packages */
//if (v.onlyInstalled){
v.refreshJob.schedule();
//}
}
}
});
}
private CabalPackageHelper helper;
private Button bAll;
private boolean onlyInstalled=true;
private TreeViewer packageViewer;
private final ImportLibrariesLabelProvider labelProvider=new ImportLibrariesLabelProvider();
private Label lUpdate;
/**
* current selection name (either package or package + version)
*/
private String currentName;
/**
* current selected version (if a package is selected, it's its last known version)
*/
private String currentNameWithVersion;
private Label lInstall;
boolean installed=false;
boolean installPossible=false;
private Text infoViewer;
private Label lSelected;
private Action updateAction;
private Action installAction;
private final Job refreshJob=new Job(UITexts.cabalPackagesView_list_running){
@Override
protected IStatus run( final IProgressMonitor arg0 ) {
try {
labelProvider.setShowInstalled(!onlyInstalled );
final List<CabalPackageRef> l=onlyInstalled?helper.getInstalled():helper.getAll();
CabalPackagesView.this.getSite().getShell().getDisplay().asyncExec( new Runnable(){
@Override
public void run() {
packageViewer.setInput( l );
}
} ) ;
} catch (IOException ioe){
return new Status( IStatus.ERROR, HaskellUIPlugin.getPluginId(), UITexts.cabalPackagesView_list_error,ioe );
}
return Status.OK_STATUS;
}
};
@Override
public void createPartControl( final Composite parent ) {
parent.setLayout( new GridLayout(2,true) );
Composite buttons=new Composite(parent,SWT.NONE);
buttons.setLayoutData( new GridData(GridData.FILL_HORIZONTAL) );
buttons.setLayout( new GridLayout(2,true) );
final Button bInstalled=new Button( buttons, SWT.RADIO );
bInstalled.setLayoutData( new GridData(GridData.FILL_HORIZONTAL) );
bInstalled.setText( UITexts.cabalPackagesView_installed );
bInstalled.setSelection( true );
bInstalled.setBackground( buttons.getBackground() );
bAll=new Button( buttons, SWT.RADIO );
bAll.setLayoutData( new GridData(GridData.FILL_HORIZONTAL) );
bAll.setText( UITexts.cabalPackagesView_all );
bAll.setBackground( buttons.getBackground() );
lSelected=new Label(parent,SWT.NONE);
GridData gd=new GridData(GridData.FILL_HORIZONTAL);
lSelected.setLayoutData(gd);
lSelected.setText( NLS.bind( UITexts.cabalPackagesView_selected, UITexts.none ) );
Label lFilter=new Label(parent,SWT.NONE);
gd=new GridData(GridData.FILL_HORIZONTAL);
lFilter.setLayoutData(gd);
lFilter.setText( UITexts.cabalPackagesView_filter );
// Label lOptions=new Label(parent,SWT.NONE);
// gd=new GridData(GridData.FILL_HORIZONTAL);
// lOptions.setLayoutData(gd);
// lOptions.setText( UITexts.cabalPackagesView_action_install_options );
new Label(parent,SWT.NONE);
final Text tFilter=new Text(parent,SWT.BORDER | SWT.SEARCH | SWT.ICON_SEARCH);
tFilter.setLayoutData( new GridData(GridData.FILL_HORIZONTAL) );
new Label(parent,SWT.NONE);
// final Text tOptions=new Text(parent,SWT.BORDER);
// tOptions.setLayoutData( new GridData(GridData.FILL_HORIZONTAL) );
final Label lMatching=new Label(parent,SWT.NONE);
lMatching.setLayoutData( new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_END) );
lMatching.setText( UITexts.cabalPackagesView_matching );
IToolBarManager tbm=getViewSite().getActionBars().getToolBarManager();
updateAction=new Action(UITexts.cabalPackagesView_action_update,HaskellUIImages.getImageDescriptor( IImageNames.HACKAGE_UPDATE )){
@Override
public void run() {
update();
}
};
tbm.add( updateAction );
installAction=new Action(UITexts.cabalPackagesView_action_install,HaskellUIImages.getImageDescriptor( IImageNames.HACKAGE_INSTALL )){
@Override
public void run() {
new InstallDialog( getSite().getShell() ).open();
}
};
tbm.add(installAction);
installAction.setEnabled( false );
// new Label(parent,SWT.NONE);
// Composite cInstallButtons=new Composite(parent,SWT.NONE);
// cInstallButtons.setLayoutData( new GridData(GridData.FILL_HORIZONTAL) );
// cInstallButtons.setLayout( new GridLayout(3,false) );
//
// final Button bUser=new Button(cInstallButtons,SWT.PUSH);
// bUser.setLayoutData( new GridData(GridData.HORIZONTAL_ALIGN_CENTER) );
// bUser.setText(UITexts.cabalPackagesView_action_install_user);
// bUser.setEnabled( false );
//
// final Button bGlobal=new Button(cInstallButtons,SWT.PUSH);
// bGlobal.setLayoutData( new GridData(GridData.HORIZONTAL_ALIGN_CENTER) );
// bGlobal.setText(UITexts.cabalPackagesView_action_install_global);
// bGlobal.setEnabled( false );
//
lInstall=new Label(parent,SWT.NONE);
GridData gdInstall=new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
gdInstall.grabExcessHorizontalSpace=true;
lInstall.setLayoutData( gdInstall );
packageViewer=new TreeViewer(parent,SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.SINGLE);
packageViewer.getTree().setLayoutData( new GridData(GridData.FILL_BOTH) );
packageViewer.setContentProvider( new CabalPackageContentProvider() );
packageViewer.setComparator( new CabalPackageViewerComparator() ); // cabal already sorts the data, but takes case into account...
packageViewer.setLabelProvider(labelProvider );
packageViewer.addFilter( new ViewerFilter() {
@Override
public boolean select( final Viewer viewer, final Object parentElement, final Object element ) {
if (element instanceof CabalPackageRef){
String name=((CabalPackageRef)element).getName();
return name.toLowerCase( Locale.ENGLISH ).startsWith( tFilter.getText().toLowerCase(Locale.ENGLISH) );
}
return true;
}
});
Menu m=new Menu( packageViewer.getTree() );
final Image img=HaskellUIImages.getImageDescriptor( IImageNames.HACKAGE_INSTALL ).createImage() ;
final MenuItem miInstallShort=new MenuItem( m, SWT.PUSH );
miInstallShort.setText( UITexts.cabalPackagesView_action_install_short );
miInstallShort.setImage(img );
miInstallShort.addSelectionListener( new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent arg0) {
install( false, "" );
}
} );
final MenuItem miInstallLong=new MenuItem( m, SWT.PUSH );
miInstallLong.setText( UITexts.cabalPackagesView_action_install );
miInstallLong.addSelectionListener( new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent arg0) {
new InstallDialog( getSite().getShell() ).open();
}
} );
miInstallLong.setImage( img);
final Image browserImg=HaskellUIImages.getImageDescriptor( IImageNames.HASKELL_MISC ).createImage() ;
final MenuItem miBrowser=new MenuItem( m, SWT.PUSH );
miBrowser.setText( UITexts.cabalPackagesView_info_browser );
miBrowser.addSelectionListener( new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent arg0) {
try {
IWorkbenchPage page=getViewSite().getWorkbenchWindow().getWorkbench().showPerspective( BrowserPerspective.class.getName(), getViewSite().getWorkbenchWindow() );
PackagesView view=(PackagesView)page.showView( PackagesView.ID );
view.select(currentNameWithVersion);
} catch (Throwable t){
HaskellUIPlugin.log( t );
}
}
} );
miBrowser.setImage( browserImg);
final MenuItem miMore=new MenuItem( m, SWT.PUSH );
miMore.setText( UITexts.cabalPackagesView_info_more );
miMore.addSelectionListener( new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent arg0) {
new Job(UITexts.cabalPackagesView_info_more_running){
@Override
protected IStatus run( final IProgressMonitor arg0 ) {
OpenDefinitionHandler.openExternalDefinition( getSite().getPage(), null, currentName, null, null, " " );
return Status.OK_STATUS;
}
}.schedule();
}
} );
miMore.setImage( browserImg);
m.addMenuListener( new MenuListener() {
@Override
public void menuShown( final MenuEvent arg0 ) {
miInstallShort.setEnabled( currentName!=null && installPossible);
miInstallLong.setEnabled( currentName!=null && installPossible);
miBrowser.setEnabled( currentNameWithVersion!=null && installed && BrowserPlugin.getDefault().isAnyDatabaseLoaded());
miMore.setEnabled( currentName!=null );
}
@Override
public void menuHidden( final MenuEvent arg0 ) {
// NOOP
}
} );
m.addDisposeListener( new DisposeListener() {
@Override
public void widgetDisposed( final DisposeEvent arg0 ) {
img.dispose();
browserImg.dispose();
}
} );
packageViewer.getTree().setMenu( m );
infoViewer=new Text(parent,SWT.MULTI | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
infoViewer.setLayoutData( new GridData(GridData.FILL_BOTH) );
// Composite cUpdate=new Composite(parent,SWT.NONE);
// cUpdate.setLayoutData( new GridData(GridData.FILL_HORIZONTAL) );
// cUpdate.setLayout( new GridLayout(2,false) );
// Button bUpdate=new Button(cUpdate,SWT.PUSH);
// bUpdate.setLayoutData( new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING) );
// bUpdate.setText( UITexts.cabalPackagesView_action_update );
lUpdate=new Label(parent,SWT.NONE);
lUpdate.setLayoutData( new GridData(GridData.FILL_HORIZONTAL) );
helper=CabalPackageHelper.getInstance();
refreshJob.schedule();
bInstalled.addSelectionListener( new SelectionAdapter() {
@Override
public void widgetSelected(final org.eclipse.swt.events.SelectionEvent e) {
onlyInstalled=true;
clearInfo();
packageViewer.setInput( Collections.emptyList());
refreshJob.schedule();
}
});
bAll.addSelectionListener( new SelectionAdapter() {
@Override
public void widgetSelected(final org.eclipse.swt.events.SelectionEvent e) {
onlyInstalled=false;
clearInfo();
packageViewer.setInput( Collections.emptyList());
refreshJob.schedule();
}
});
tFilter.addModifyListener( new ModifyListener() {
@Override
public void modifyText( final ModifyEvent paramModifyEvent ) {
packageViewer.refresh();
}
});
packageViewer.addSelectionChangedListener( new ISelectionChangedListener() {
@Override
public void selectionChanged( final SelectionChangedEvent arg0 ) {
IStructuredSelection sel=(IStructuredSelection)arg0.getSelection();
installPossible=sel.size()==1 && bAll.getSelection(); // ignore for now the fact that all show also what's installed...
//bGlobal.setEnabled( installPossible );
//bUser.setEnabled( installPossible );
installAction.setEnabled( installPossible );
if (sel.size()==1){
currentName=null;
currentNameWithVersion=null;
Object o=sel.getFirstElement();
installed=false;
if (o instanceof CabalPackageRef){
currentName=((CabalPackageRef)o).getName();
for (CabalPackageVersion v:((CabalPackageRef)o).getCabalPackageVersions()){
if (v.isLast()){
currentNameWithVersion=currentName+"-"+v.toString();
installed=v.isInstalled();
break;
}
}
} else if (o instanceof CabalPackageVersion){
CabalPackageVersion v=(CabalPackageVersion)o;
installed=v.isInstalled();
currentName=v.getRef().getName()+"-"+v.toString();
//if (v.isLast()){
currentNameWithVersion=currentName;
//}
}
if (currentName!=null){
showInfo();
}
}
}
});
}
/**
* clear all package info
*/
private void clearInfo(){
lInstall.setText( "" );
lSelected.setText( NLS.bind( UITexts.cabalPackagesView_selected, UITexts.none ) );
infoViewer.setText("");
}
/**
* show info for a given package
*/
private void showInfo(){
lSelected.setText( NLS.bind( UITexts.cabalPackagesView_selected, currentName ) );
if (bAll.getSelection()){
lInstall.setText( "" );
} else {
lInstall.setText( UITexts.cabalPackagesView_info_installed );
}
lInstall.getParent().layout( true );
new Job(UITexts.cabalPackagesView_info_running){
@Override
protected IStatus run( final IProgressMonitor arg0 ) {
try {
final String s=helper.getInfo( currentName );
CabalPackagesView.this.getSite().getShell().getDisplay().asyncExec( new Runnable(){
@Override
public void run() {
infoViewer.setText(s);
}
} ) ;
} catch (IOException ioe){
return new Status( IStatus.ERROR, HaskellUIPlugin.getPluginId(), UITexts.cabalPackagesView_info_error,ioe );
}
return Status.OK_STATUS;
}
}.schedule();
}
/**
* install a package
* @param global (true) or user (false)
* @param options additional options
*/
private void install(final boolean global,final String options){
final String cabalExecutable=CabalImplementationManager.getCabalExecutable();
if (cabalExecutable!=null){
final List<String> commands = new ArrayList<>();
commands.add( cabalExecutable );
commands.add("install");
commands.add(currentName);
// options
if (global){
commands.add( "--global" );
} else {
commands.add( "--user" );
}
BackendManager.addCabalInstallOptions( commands );
// force reinstall
commands.add( "--reinstall" );
if (options!=null && options.trim().length()>0){
commands.addAll(Arrays.asList(CommandLineUtil.parse( options.trim() )));
}
try {
// this requires to be in the UI thread
AbstractHaskellLaunchDelegate.runInConsole(null, commands, new File(cabalExecutable).getParentFile(), UITexts.cabalPackagesView_action_install_running,true, new Runnable() {
@Override
public void run() {
lInstall.getDisplay().asyncExec( new Runnable() {
@Override
public void run() {
lInstall.setText( UITexts.cabalPackagesView_info_installed );
helper.setInstalled( null );
if (!bAll.getSelection()){
refreshJob.schedule();
}
}
});
HaskellUIPlugin.getDefault().getBackendManager().rebuildBrowser();
}
});
} catch (Exception ioe){
HaskellUIPlugin.log(ioe);
final IStatus st=new Status( IStatus.ERROR, HaskellUIPlugin.getPluginId(),ioe.getLocalizedMessage(),ioe);
ErrorDialog.openError(getSite().getShell(), UITexts.cabalPackagesView_action_install_error, UITexts.cabalPackagesView_action_install_error, st);
}
}
}
private void update(){
lUpdate.setText( UITexts.cabalPackagesView_action_update_running );
final String cabalExecutable=CabalImplementationManager.getCabalExecutable();
if (cabalExecutable!=null){
final List<String> commands = new ArrayList<>();
commands.add( cabalExecutable );
commands.add("update");
try {
AbstractHaskellLaunchDelegate.runInConsole(null, commands, new File(cabalExecutable).getParentFile(), UITexts.cabalPackagesView_action_update_running,true, new Runnable() {
@Override
public void run() {
lUpdate.getDisplay().asyncExec( new Runnable() {
@Override
public void run() {
lUpdate.setText( UITexts.cabalPackagesView_action_update_ok );
helper.setAll( null );
if (bAll.getSelection()){
refreshJob.schedule();
}
}
});
}
});
} catch (Exception ioe){
HaskellUIPlugin.log(ioe);
lUpdate.setText( UITexts.cabalPackagesView_action_update_running );
}
}
}
@Override
public void setFocus() {
// TODO Auto-generated method stub
}
/**
* simple dialog to ask global/user and additional options
* @author JP Moresmau
*
*/
private class InstallDialog extends Dialog {
private boolean global=false;
private String options="";
public InstallDialog( final Shell parentShell ) {
super( parentShell );
}
@Override
protected int getShellStyle() {
return super.getShellStyle() | SWT.RESIZE;
}
@Override
protected void configureShell( final Shell newShell ) {
super.configureShell( newShell );
newShell.setText( UITexts.cabalPackagesView_action_install_options );
}
@Override
protected Control createDialogArea( final Composite parent1 ) {
Composite parent2=(Composite)super.createDialogArea( parent1 );
parent2.setLayout( new GridLayout(2,true) );
final Button bUser=new Button(parent2,SWT.RADIO);
bUser.setLayoutData( new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING) );
bUser.setText(NLS.bind(UITexts.cabalPackagesView_action_install_user,currentName));
bUser.setSelection( true );
final Button bGlobal=new Button(parent2,SWT.RADIO);
bGlobal.setLayoutData( new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING) );
bGlobal.setText(NLS.bind(UITexts.cabalPackagesView_action_install_global,currentName));
bGlobal.setSelection( false );
Label lOptions=new Label(parent2,SWT.NONE);
GridData gd=new GridData(GridData.FILL_HORIZONTAL);
gd.horizontalSpan=2;
lOptions.setLayoutData(gd);
lOptions.setText( UITexts.cabalPackagesView_action_install_options );
final Text tOptions=new Text(parent2,SWT.BORDER);
gd= new GridData(GridData.FILL_HORIZONTAL);
gd.horizontalSpan=2;
tOptions.setLayoutData(gd);
bUser.addSelectionListener( new SelectionAdapter() {
@Override
public void widgetSelected( final SelectionEvent e ) {
global=false;
}
});
bGlobal.addSelectionListener( new SelectionAdapter() {
@Override
public void widgetSelected( final SelectionEvent e ) {
global=true;
}
});
tOptions.addModifyListener( new ModifyListener() {
@Override
public void modifyText( final ModifyEvent arg0 ) {
options=tOptions.getText();
}
});
return parent2;
}
@Override
protected void okPressed() {
// install does things in the UI thread, so close the dialog first
super.okPressed();
install(global,options);
}
}
}