package org.projectusus.ui.dependencygraph.common;
import static java.util.Collections.emptyList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.ui.IContextMenuConstants;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jdt.ui.actions.ConvertingSelectionProvider;
import org.eclipse.jdt.ui.actions.RefactorActionGroup;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FillLayout;
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.ui.IActionBars;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.part.IShowInTarget;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.zest.core.viewers.AbstractZoomableViewer;
import org.eclipse.zest.core.viewers.IZoomableWorkbenchPart;
import org.projectusus.core.IUsusModelListener;
import org.projectusus.core.filerelations.model.Packagename;
import org.projectusus.core.statistics.UsusModelProvider;
import org.projectusus.jfeet.selection.ElementFrom;
import org.projectusus.ui.dependencygraph.filters.DirectNeighboursFilter;
import org.projectusus.ui.dependencygraph.filters.HideNodesFilter;
import org.projectusus.ui.dependencygraph.filters.IRestrictNodesFilterProvider;
import org.projectusus.ui.dependencygraph.filters.LimitNodeFilter;
import org.projectusus.ui.dependencygraph.filters.NodeAndEdgeFilter;
import org.projectusus.ui.dependencygraph.filters.PackagenameNodeFilter;
import org.projectusus.ui.dependencygraph.nodes.GraphNode;
import org.projectusus.ui.dependencygraph.nodes.IEdgeColorProvider;
public abstract class DependencyGraphView extends ViewPart implements IRestrictNodesFilterProvider, IShowInTarget, IZoomableWorkbenchPart, IMenuListener, IRefreshable {
private final DependencyGraphModel model;
private DependencyGraphViewer graphViewer;
private RefactorActionGroup refactorAction;
private IUsusModelListener listener;
private NodeAndEdgeFilter customFilter; // selection of package, edge, package cycle, etc.
private final WorkbenchContext customFilterContext; // this is connected to the eraser icon (activates and deactivates it)
private final HideNodesFilter hideNodesFilter = new HideNodesFilter(); // the nodes the user manually X-ed out
private boolean restricting = false;
private DependencyGraphSelectionListener selectionListener;
private final IPartListener2 selectionSynchronizationListener = new SelectionSynchronizationListener( this );
private final IEdgeColorProvider edgeColorProvider;
public DependencyGraphView( String viewId, DependencyGraphModel model, IEdgeColorProvider edgeColorProvider ) {
this.model = model;
this.edgeColorProvider = edgeColorProvider;
customFilterContext = new WorkbenchContext( viewId + ".context.customFilter" );
initUsusModelListener();
}
@Override
public void createPartControl( Composite parent ) {
Composite composite = createComposite( parent );
createFilterArea( composite );
createGraphViewer( new DependencyGraphViewer( createGraphArea( composite ), edgeColorProvider ) );
IViewSite site = getViewSite();
site.setSelectionProvider( graphViewer );
contributeTo( site.getActionBars() );
refresh();
extendSelectionBehavior();
registerContextMenu( site );
refactorAction = new RefactorActionGroup( getSite(), new ConvertingSelectionProvider( site.getSelectionProvider() ) );
}
private void registerContextMenu( IViewSite site ) {
MenuManager menuManager = new MenuManager();
menuManager.add( new Separator( "nodeActions" ) );
graphViewer.getGraphControl().setMenu( menuManager.createContextMenu( graphViewer.getControl() ) );
site.registerContextMenu( menuManager, graphViewer );
createRefactoringMenu( menuManager );
}
private void createRefactoringMenu( MenuManager menuManager ) {
menuManager.add( new Separator( IContextMenuConstants.GROUP_REORGANIZE ) );
menuManager.addMenuListener( this );
}
private static Composite createComposite( Composite parent ) {
Composite composite = new Composite( parent, SWT.NONE );
GridLayout layout = new GridLayout( 1, false );
layout.marginHeight = 0;
layout.marginWidth = 0;
composite.setLayout( layout );
return composite;
}
protected void contributeTo( IActionBars actionBars ) {
IToolBarManager toolBar = actionBars.getToolBarManager();
Separator separator = new Separator( "zoom" );
separator.setVisible( false );
toolBar.add( separator );
toolBar.add( new ChangeZoom( this ) );
}
public void menuAboutToShow( IMenuManager manager ) {
if( manager.find( RefactorActionGroup.MENU_ID ) == null ) {
refactorAction.fillContextMenu( manager );
}
}
public AbstractZoomableViewer getZoomableViewer() {
return graphViewer;
}
private Composite createFilterArea( Composite composite ) {
Composite filterArea = new Composite( composite, SWT.NONE );
GridLayout layout = new GridLayout( 5, false );
layout.marginHeight = 0;
layout.marginTop = 5;
filterArea.setLayout( layout );
Control additionalWidgets = createAdditionalWidgets( filterArea );
createLayoutComboViewer( filterArea );
additionalWidgets.setLayoutData( new GridData( SWT.LEFT, SWT.CENTER, true, true ) );
filterArea.setLayoutData( new GridData( SWT.FILL, SWT.CENTER, false, false ) );
return filterArea;
}
private void createLayoutComboViewer( Composite filterArea ) {
new GraphLayoutComboViewer( filterArea, new ISelectionChangedListener() {
public void selectionChanged( final SelectionChangedEvent event ) {
Display.getDefault().asyncExec( new Runnable() {
public void run() {
GraphLayout newLayout = new ElementFrom( event.getSelection() ).as( GraphLayout.class );
switchLayout( newLayout );
}
} );
}
} );
}
protected Control createAdditionalWidgets( Composite filterArea ) {
return createRestrictingCheckBox( filterArea );
}
protected final Button createRestrictingCheckBox( Composite parent ) {
final Button checkbox = new Button( parent, SWT.CHECK );
checkbox.setText( getRestrictingCheckboxLabelName() );
checkbox.addSelectionListener( new SelectionAdapter() {
@Override
public void widgetSelected( SelectionEvent e ) {
Display.getDefault().asyncExec( new Runnable() {
public void run() {
setRestricting( checkbox.getSelection() );
refresh();
}
} );
}
} );
setRestricting( false );
return checkbox;
}
private static Composite createGraphArea( Composite composite ) {
Composite graphArea = new Composite( composite, SWT.BORDER );
graphArea.setLayoutData( new GridData( SWT.FILL, SWT.FILL, true, true ) );
graphArea.setLayout( new FillLayout() );
return graphArea;
}
private void createGraphViewer( DependencyGraphViewer dependencyGraphViewer ) {
graphViewer = dependencyGraphViewer;
List<ViewerFilter> filters = new ArrayList<ViewerFilter>();
filters.add( new LimitNodeFilter( this ) );
filters.add( hideNodesFilter );
filters.addAll( createAdditionalFilters() );
graphViewer.setFilters( filters.toArray( new ViewerFilter[filters.size()] ) );
}
protected Collection<? extends ViewerFilter> createAdditionalFilters() {
return emptyList();
}
public synchronized void replaceCustomFilter( NodeAndEdgeFilter newCustomFilter ) {
newCustomFilter.setFilterLimitProvider( this );
graphViewer.replaceFilter( customFilter, newCustomFilter );
customFilter = newCustomFilter;
setContentDescription( newCustomFilter.getDescription() );
customFilterContext.activate();
drawGraphUnconditionally();
}
public synchronized void clearCustomFilter() {
if( customFilter != null ) {
graphViewer.removeFilter( customFilter );
customFilter = null;
clearContentDescription();
}
customFilterContext.deactivate();
}
private void clearContentDescription() {
setContentDescription( "" );
}
public void refresh() {
Display.getDefault().asyncExec( new Runnable() {
public void run() {
drawGraphConditionally();
applyLayout();
}
} );
}
public void drawGraphConditionally() {
if( model.isChanged() ) {
drawGraphUnconditionally();
} else {
updateEdgeColorProvider();
graphViewer.refresh();
}
graphViewer.fireSelectionChanged();
}
private void drawGraphUnconditionally() {
graphViewer.setInput( model.getGraphNodes() );
updateEdgeColorProvider();
graphViewer.refresh();
model.resetChanged();
}
private void updateEdgeColorProvider() {
Set<Packagename> visibleNodes = graphViewer.getVisibleNodes();
edgeColorProvider.recalculateColors( visibleNodes );
}
@Override
public void setFocus() {
graphViewer.getControl().setFocus();
}
@Override
public void dispose() {
UsusModelProvider.ususModel().removeUsusModelListener( listener );
graphViewer.getGraphControl().removeSelectionListener( selectionListener );
registerEditorSynchronizationListener( false );
if( refactorAction != null ) {
refactorAction.dispose();
}
super.dispose();
}
public void registerEditorSynchronizationListener( boolean register ) {
IWorkbenchPage page = getSite().getPage();
if( register ) {
page.addPartListener( selectionSynchronizationListener );
selectNodeFromActiveEditor( page.getActiveEditor() );
} else {
page.removePartListener( selectionSynchronizationListener );
}
}
private void initUsusModelListener() {
listener = new IUsusModelListener() {
public void ususModelChanged() {
Display.getDefault().asyncExec( new Runnable() {
public void run() {
model.invalidate();
drawGraphConditionally();
}
} );
}
};
UsusModelProvider.ususModel().addUsusModelListener( listener );
}
private void extendSelectionBehavior() {
selectionListener = new DependencyGraphSelectionListener( graphViewer );
graphViewer.getGraphControl().addSelectionListener( selectionListener );
}
public boolean show( ShowInContext context ) {
PackagenameNodeFilter filter = PackagenameNodeFilter.from( context.getSelection() );
if( filter.isEmpty() ) {
return false;
}
replaceCustomFilter( filter );
return true;
}
// adds the selected nodes to the list of hidden nodes
public void hideSelectedNodes() {
Set<GraphNode> selectedNodes = graphViewer.getSelectedNodes();
if( !selectedNodes.isEmpty() ) {
hideNodesFilter.addNodesToHide( selectedNodes );
drawGraphConditionally();
}
customFilterContext.activate();
}
public void selectAllNodesInSamePackage( GraphNode selectedNode ) {
// this only modifies the selection, so we do not adjust the neighboursFilter!
Set<GraphNode> allNodes = graphViewer.getAllNodes();
List<GraphNode> nodesInSamePackage = new LinkedList<GraphNode>();
for( GraphNode node : allNodes ) {
if( !node.isInDifferentPackageThan( selectedNode ) ) {
nodesInSamePackage.add( node );
}
}
graphViewer.selectNodes( nodesInSamePackage );
}
public void showAllDirectNeighbours() {
replaceCustomFilter( new DirectNeighboursFilter( graphViewer.getSelectedNodes() ) );
// TODO Why resetHiddenNodes()? (which is not called by other callers of replaceCustomFilter)? -> 2x drawGraph...
resetHiddenNodes(); // do this last because it redraws
}
private void switchLayout( final GraphLayout newLayout ) {
graphViewer.setLayout( newLayout );
applyLayout();
}
public void applyLayout() {
graphViewer.applyLayout(); // redraw
}
public Set<GraphNode> getAllNodesToHide( Set<GraphNode> directNeighbours ) {
HashSet<GraphNode> hideNodes = new HashSet<GraphNode>( model.getGraphNodes() );
hideNodes.removeAll( directNeighbours );
return hideNodes;
}
void selectNodeFromActiveEditor( IEditorPart editorPart ) {
IJavaElement javaElement = JavaUI.getEditorInputJavaElement( editorPart.getEditorInput() );
List<GraphNode> nodesToSelect = new ArrayList<GraphNode>();
for( GraphNode node : graphViewer.getAllNodes() ) {
if( node.represents( javaElement ) ) {
nodesToSelect.add( node );
}
}
graphViewer.selectNodes( nodesToSelect );
}
public void resetHiddenNodes() {
hideNodesFilter.reset();
drawGraphConditionally();
}
public Image takeScreenshot() {
return graphViewer.takeScreenshot();
}
public abstract String getFilenameForScreenshot();
public boolean isRestricting() {
return restricting;
}
private void setRestricting( boolean restricting ) {
this.restricting = restricting;
}
protected abstract String getRestrictingCheckboxLabelName();
}