/* * Licensed to "Neo Technology," Network Engine for Objects in Lund AB * (http://neotechnology.com) under one or more contributor license agreements. * See the NOTICE file distributed with this work for additional information * regarding copyright ownership. Neo Technology licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License * at (http://www.apache.org/licenses/LICENSE-2.0). Unless required by * applicable law or agreed to in writing, software distributed under the * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS * OF ANY KIND, either express or implied. See the License for the specific * language governing permissions and limitations under the License. */ package org.neo4j.neoclipse.reltype; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.eclipse.jface.action.Action; 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.dialogs.MessageDialog; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Menu; import org.eclipse.ui.IActionBars; import org.eclipse.ui.ISelectionListener; import org.eclipse.ui.IWorkbenchActionConstants; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.part.ViewPart; import org.eclipse.zest.core.viewers.GraphViewer; import org.neo4j.graphdb.Direction; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.RelationshipType; import org.neo4j.neoclipse.Activator; import org.neo4j.neoclipse.action.Actions; import org.neo4j.neoclipse.action.reltype.NewRelationshipTypeAction; import org.neo4j.neoclipse.decorate.UserIcons; import org.neo4j.neoclipse.event.NeoclipseEvent; import org.neo4j.neoclipse.event.NeoclipseEventListener; import org.neo4j.neoclipse.graphdb.GraphDbServiceEvent; import org.neo4j.neoclipse.graphdb.GraphDbServiceEventListener; import org.neo4j.neoclipse.graphdb.GraphDbServiceStatus; import org.neo4j.neoclipse.graphdb.GraphDbUtil; import org.neo4j.neoclipse.help.HelpContextConstants; import org.neo4j.neoclipse.view.NeoGraphLabelProvider; import org.neo4j.neoclipse.view.NeoGraphLabelProviderWrapper; import org.neo4j.neoclipse.view.NeoGraphViewPart; import org.neo4j.neoclipse.view.UiHelper; /** * View that shows the relationships of the database. * * @author anders */ public class RelationshipTypeView extends ViewPart implements ISelectionListener { public final static String ID = "org.neo4j.neoclipse.reltype.RelationshipTypeView"; private static final Separator SEPARATOR = new Separator(); private static final String[] EXT_FILTER; private static final String[] EXT_FILTER_NAMES; private final NeoGraphLabelProvider graphLabelProvider = NeoGraphLabelProviderWrapper.getInstance(); private TableViewer viewer; private RelationshipTypesProvider provider; private NeoGraphViewPart graphView = null; private Action markIncomingAction; private Action markOutgoingAction; private Action clearMarkedAction; private Action markRelationshipAction; private Action newAction; private Action addRelationship; private Action addOutgoingNode; private Action addIncomingNode; private Action addIncomingIcon; private Action addOutgoingIcon; private Action filterNone; private Action filterAll; private Action filterOutgoing; private Action filterIncoming; private final List<RelationshipType> currentSelectedRelTypes = new ArrayList<RelationshipType>(); private FileDialog iconFileDialog; private Action deleteIncomingIcon; private Action deleteOutgoingIcon; private IPreferenceStore preferenceStore; static { // build filters for file selection dialog. StringBuilder str = new StringBuilder( 128 ); for ( String ext : UserIcons.EXTENSIONS ) { str.append( "*." ).append( ext ).append( ';' ); } EXT_FILTER = new String[] { str.toString() }; EXT_FILTER_NAMES = new String[] { "Image files" }; } /** * The constructor. */ public RelationshipTypeView() { } /** * Set the current graph view. * * @param graphView */ private void setGraphView( final NeoGraphViewPart graphView ) { this.graphView = graphView; } /** * Get the current graph view. * * @return */ private NeoGraphViewPart getGraphView() { if ( graphView == null ) { graphView = (NeoGraphViewPart) PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().findView( NeoGraphViewPart.ID ); graphView.addRelColorChangeListener( new RelationshipColorChangeHandler() ); } return graphView; } /** * Initialization of the workbench part. */ @Override public void createPartControl( final Composite parent ) { viewer = new TableViewer( parent, SWT.MULTI | SWT.V_SCROLL ); provider = RelationshipTypesProviderWrapper.getInstance(); viewer.setContentProvider( provider ); provider.addFilterStatusListener( new ProviderFilterChangeHandler() ); provider.addTypeChangeListener( new ProviderTypesChangeHandler() ); NeoGraphLabelProvider labelProvider = NeoGraphLabelProviderWrapper.getInstance(); labelProvider.createTableColumns( viewer ); viewer.setLabelProvider( labelProvider ); viewer.setComparator( new ViewerComparator( new RelationshipTypeSorter() ) ); viewer.setInput( getViewSite() ); Activator.getDefault().getGraphDbServiceManager().addServiceEventListener( new ServiceChangeHandler() ); PlatformUI.getWorkbench().getHelpSystem().setHelp( viewer.getControl(), HelpContextConstants.NEO_RELATIONSHIP_TYPE_VIEW ); makeActions(); hookContextMenu(); hookDoubleClickAction(); contributeToActionBars(); getSite().getPage().addSelectionListener( NeoGraphViewPart.ID, this ); getSite().setSelectionProvider( viewer ); getSite().getPage().addSelectionListener( ID, this ); preferenceStore = Activator.getDefault().getPreferenceStore(); } /** * Hook the double click listener into the view. */ private void hookDoubleClickAction() { viewer.addDoubleClickListener( new IDoubleClickListener() { public void doubleClick( final DoubleClickEvent event ) { markRelationshipAction.run(); } } ); } /** * Create and hook up the context menu. */ private void hookContextMenu() { MenuManager menuMgr = new MenuManager( "#PopupMenu" ); menuMgr.setRemoveAllWhenShown( true ); menuMgr.addMenuListener( new IMenuListener() { public void menuAboutToShow( final IMenuManager manager ) { RelationshipTypeView.this.fillContextMenu( manager ); } } ); Menu menu = menuMgr.createContextMenu( viewer.getControl() ); viewer.getControl().setMenu( menu ); getSite().registerContextMenu( menuMgr, viewer ); } /** * Add contributions to the different actions bars. */ private void contributeToActionBars() { IActionBars bars = getViewSite().getActionBars(); fillLocalPullDown( bars.getMenuManager() ); fillLocalToolBar( bars.getToolBarManager() ); } /** * Add actions to the local pull down menu. * * @param manager the pul down menu manager */ private void fillLocalPullDown( final IMenuManager manager ) { manager.add( filterNone ); manager.add( filterIncoming ); manager.add( filterOutgoing ); manager.add( filterAll ); } /** * Add actions to the local tool bar menu. * * @param manager the tool bar manager */ private void fillLocalToolBar( final IToolBarManager manager ) { manager.add( markRelationshipAction ); manager.add( markIncomingAction ); manager.add( markOutgoingAction ); manager.add( clearMarkedAction ); manager.add( SEPARATOR ); manager.add( addRelationship ); manager.add( addOutgoingNode ); manager.add( addIncomingNode ); manager.add( SEPARATOR ); manager.add( newAction ); } /** * Add actions to the context menu. * * @param manager contect menu manager */ private void fillContextMenu( final IMenuManager manager ) { manager.add( addOutgoingIcon ); manager.add( addIncomingIcon ); manager.add( deleteOutgoingIcon ); manager.add( deleteIncomingIcon ); // Other plug-ins can contribute there actions here manager.add( new Separator( IWorkbenchActionConstants.MB_ADDITIONS ) ); } /** * Create all actions. */ private void makeActions() { makeHighlightingActions(); makeRelationshipTypeActions(); makeAddActions(); makeFilterActions(); } /** * Create actions to filter on relationship directions. */ private void makeFilterActions() { filterNone = new Action() { @Override public void run() { provider.setAllFilters( false, false ); viewer.refresh(); } }; Actions.FILTER_NONE.initialize( filterNone ); filterAll = new Action() { @Override public void run() { provider.setAllFilters( true, true ); viewer.refresh(); getGraphView().refreshPreserveLayout(); } }; Actions.FILTER_ALL.initialize( filterAll ); filterOutgoing = new Action() { @Override public void run() { provider.setAllFilters( false, true ); viewer.refresh(); getGraphView().refreshPreserveLayout(); } }; Actions.FILTER_OUTGOING.initialize( filterOutgoing ); filterIncoming = new Action() { @Override public void run() { provider.setAllFilters( true, false ); viewer.refresh(); getGraphView().refreshPreserveLayout(); } }; Actions.FILTER_INCOMING.initialize( filterIncoming ); } /** * Create actions that add something. */ private void makeAddActions() { addRelationship = new Action() { @Override public void run() { GraphDbUtil.addRelationshipAction( getCurrentSelectedRelTypes(), getGraphView() ); } }; Actions.ADD_RELATIONSHIP.initialize( addRelationship ); addOutgoingNode = new Action() { @Override public void run() { GraphDbUtil.addOutgoingNodeAction( getCurrentSelectedRelTypes(), getGraphView() ); } }; Actions.ADD_OUTGOING_NODE.initialize( addOutgoingNode ); addIncomingNode = new Action() { @Override public void run() { GraphDbUtil.addIncomingNodeAction( getCurrentSelectedRelTypes(), getGraphView() ); } }; Actions.ADD_INCOMING_NODE.initialize( addIncomingNode ); } /** * Create actions that affect relationship types. */ private void makeRelationshipTypeActions() { newAction = new NewRelationshipTypeAction( provider ); addIncomingIcon = new Action() { @Override public void run() { copyIcon( Direction.INCOMING ); } }; Actions.ADD_INCOMING_ICON.initialize( addIncomingIcon ); addOutgoingIcon = new Action() { @Override public void run() { copyIcon( Direction.OUTGOING ); } }; Actions.ADD_OUTGOING_ICON.initialize( addOutgoingIcon ); deleteIncomingIcon = new Action() { @Override public void run() { removeIcon( Direction.INCOMING ); } }; Actions.DELETE_INCOMING_ICON.initialize( deleteIncomingIcon ); deleteOutgoingIcon = new Action() { @Override public void run() { removeIcon( Direction.OUTGOING ); } }; Actions.DELETE_OUTGOING_ICON.initialize( deleteOutgoingIcon ); } /** * Copy icon file for current selected relationship types. * * @param direction direction of relationships */ private void copyIcon( final Direction direction ) { File dest = NodeIconUtil.getIconLocation(); if ( dest == null ) { return; } String src = getIconFileDialog().open(); if ( src == null ) { return; // cancel by user } int dot = src.lastIndexOf( '.' ); if ( dot == -1 ) { MessageDialog.openError( null, "Error message", "Could not find a file extension on the icon image file." ); return; } String ext = src.substring( dot ); // includes dot File inFile = new File( src ); FileChannel in = null; try { in = new FileInputStream( inFile ).getChannel(); } catch ( FileNotFoundException e1 ) { e1.printStackTrace(); return; } for ( RelationshipType relType : getCurrentSelectedRelTypes() ) { String destFilename = UserIcons.createFilename( relType, direction ) + ext; FileChannel out = null; try { out = new FileOutputStream( dest.getAbsolutePath() + File.separator + destFilename ).getChannel(); } catch ( FileNotFoundException e ) { e.printStackTrace(); continue; } try { in.transferTo( 0, in.size(), out ); out.close(); } catch ( IOException e ) { e.printStackTrace(); return; } } try { in.close(); } catch ( IOException e ) { e.printStackTrace(); } graphLabelProvider.readNodeIconLocation(); getGraphView().refreshPreserveLayout(); } /** * Remove icon file for current selected relationship types. * * @param direction direction of relationships */ private void removeIcon( final Direction direction ) { File dest = NodeIconUtil.getIconLocation(); if ( dest == null ) { return; } for ( RelationshipType relType : getCurrentSelectedRelTypes() ) { final String destFilename = UserIcons.createFilename( relType, direction ) + "."; File[] deleteFiles = dest.listFiles( new FilenameFilter() { public boolean accept( final File dir, final String name ) { return name.startsWith( destFilename ); } } ); for ( File file : deleteFiles ) { file.delete(); } } graphLabelProvider.readNodeIconLocation(); getGraphView().refreshPreserveLayout(); } private FileDialog getIconFileDialog() { if ( iconFileDialog == null ) { iconFileDialog = new FileDialog( RelationshipTypeView.this.getSite().getShell(), SWT.OPEN ); iconFileDialog.setFilterExtensions( EXT_FILTER ); iconFileDialog.setFilterNames( EXT_FILTER_NAMES ); } return iconFileDialog; } /** * Create actions working with highlighting. */ private void makeHighlightingActions() { markRelationshipAction = new Action() { @Override public void run() { List<RelationshipType> relTypes = getCurrentSelectedRelTypes(); for ( RelationshipType relType : relTypes ) { highlightRelationshipType( relType ); } setEnableHighlightingActions( true ); clearMarkedAction.setEnabled( true ); } }; Actions.HIGHLIGHT_RELATIONSHIPS.initialize( markRelationshipAction ); markIncomingAction = new Action() { @Override public void run() { List<RelationshipType> relTypes = getCurrentSelectedRelTypes(); for ( RelationshipType relType : relTypes ) { highlightNodes( relType, Direction.INCOMING ); } clearMarkedAction.setEnabled( true ); } }; Actions.HIGHLIGHT_INCOMING.initialize( markIncomingAction ); markIncomingAction.setEnabled( false ); markOutgoingAction = new Action() { @Override public void run() { List<RelationshipType> relTypes = getCurrentSelectedRelTypes(); for ( RelationshipType relType : relTypes ) { highlightNodes( relType, Direction.OUTGOING ); } clearMarkedAction.setEnabled( true ); } }; Actions.HIGHLIGHT_OUTGOING.initialize( markOutgoingAction ); markOutgoingAction.setEnabled( false ); clearMarkedAction = new Action() { @Override public void run() { graphLabelProvider.clearMarkedNodes(); graphLabelProvider.clearMarkedRels(); getGraphView().refresh( true ); setEnabled( false ); setEnableAddActions( false ); } }; Actions.HIGHLIGHT_CLEAR.initialize( clearMarkedAction ); clearMarkedAction.setEnabled( false ); } /** * Enable or disable highlighting actions. * * @param enabled */ private void setEnableHighlightingActions( final boolean enabled ) { markIncomingAction.setEnabled( enabled ); markOutgoingAction.setEnabled( enabled ); markRelationshipAction.setEnabled( enabled ); } /** * Enable or disable addition of a relationship. * * @param enabled */ private void setEnableAddRelationship( final boolean enabled ) { addRelationship.setEnabled( enabled ); } /** * Enable or disable setting of relationship type-dependent icons. * * @param enabled */ private void setEnableSetIcon( final boolean enabled ) { addIncomingIcon.setEnabled( enabled ); addOutgoingIcon.setEnabled( enabled ); } /** * Enable or disable to add a node. * * @param enabled */ private void setEnableAddNode( final boolean enabled ) { addOutgoingNode.setEnabled( enabled ); addIncomingNode.setEnabled( enabled ); } /** * Enable or disable all add actions. * * @param enabled */ private void setEnableAddActions( final boolean enabled ) { setEnableAddNode( enabled ); setEnableAddRelationship( enabled ); } /** * Get the currently first selected relationship type. * * @return */ public RelationshipType getCurrentSelectedRelType() { if ( currentSelectedRelTypes.size() < 1 ) { return null; } return currentSelectedRelTypes.get( 0 ); } /** * Get the currently selected relationship types. * * @return */ public List<RelationshipType> getCurrentSelectedRelTypes() { return currentSelectedRelTypes; } /** * Highlight a relationship type. * * @param relType */ private void highlightRelationshipType( final RelationshipType relType ) { if ( getGraphView() == null ) { return; } List<Relationship> rels = new ArrayList<Relationship>(); GraphViewer gViewer = getGraphView().getViewer(); for ( Object o : gViewer.getConnectionElements() ) { if ( o instanceof Relationship ) { Relationship rel = (Relationship) o; if ( rel.isType( relType ) ) { rels.add( rel ); } } } graphLabelProvider.addMarkedRels( rels ); getGraphView().refresh( true ); setEnableAddActions( false ); } /** * Highlight nodes that are connected to a relationship type. * * @param relType relationship type to use * @param direction direction in which nodes should be highlighted */ private void highlightNodes( final RelationshipType relType, final Direction direction ) { if ( getGraphView() == null ) { return; } GraphViewer gViewer = getGraphView().getViewer(); Set<Node> nodes = new HashSet<Node>(); for ( Object o : gViewer.getNodeElements() ) { if ( o instanceof Node ) { Node node = (Node) o; if ( node.hasRelationship( relType, direction ) ) { nodes.add( node ); } } } graphLabelProvider.addMarkedNodes( nodes ); getGraphView().refresh( true ); setEnableAddActions( false ); } /** * Passing the focus request to the viewer's control. */ @Override public void setFocus() { viewer.getControl().setFocus(); } /** * Keep track of the graph view selections. */ public void selectionChanged( final IWorkbenchPart part, final ISelection selection ) { if ( !( selection instanceof IStructuredSelection ) ) { return; } setEnableAddRelationship( false ); setEnableAddNode( false ); if ( part instanceof NeoGraphViewPart ) { setGraphView( (NeoGraphViewPart) part ); List<Relationship> currentSelectedRels = getGraphView().getCurrentSelectedRels(); Set<RelationshipType> relTypes = new RelationshipTypeHashSet(); for ( Relationship rel : currentSelectedRels ) { relTypes.add( rel.getType() ); } if ( !relTypes.isEmpty() ) { Collection<RelationshipTypeControl> relTypeCtrls = provider.getFilteredRelTypeControls( relTypes ); viewer.setSelection( new StructuredSelection( relTypeCtrls.toArray() ) ); setEnableHighlightingActions( true ); } } else if ( this.equals( part ) ) { if ( selection.isEmpty() ) { setEnableHighlightingActions( false ); } else { setEnableHighlightingActions( true ); } currentSelectedRelTypes.clear(); Iterator<?> iter = ( (IStructuredSelection) selection ).iterator(); while ( iter.hasNext() ) { Object o = iter.next(); if ( o instanceof RelationshipTypeControl ) { currentSelectedRelTypes.add( ( (DirectedRelationship) o ).getRelType() ); } } } List<Node> currentSelectedNodes = getGraphView().getCurrentSelectedNodes(); setEnableAddRelationship( getCurrentSelectedRelTypes().size() == 1 && currentSelectedNodes.size() == 2 ); setEnableAddNode( getCurrentSelectedRelTypes().size() == 1 && !currentSelectedNodes.isEmpty() ); setEnableSetIcon( !getCurrentSelectedRelTypes().isEmpty() ); getGraphView().updateMenuState(); } /** * Respond to changes in the relationship type provider filtering. */ private class ProviderFilterChangeHandler implements NeoclipseEventListener { /** * Respond to changes in the underlying relationship type provider. */ public void stateChanged( final NeoclipseEvent event ) { if ( getGraphView() != null ) { getGraphView().refreshPreserveLayout(); } } } /** * Respond to changes in the relationship type provider types. */ private class ProviderTypesChangeHandler implements NeoclipseEventListener { /** * Respond to changes in the underlying relationship type provider. */ public void stateChanged( final NeoclipseEvent event ) { viewer.refresh(); } } /** * Handle change in the Neo service. */ private class ServiceChangeHandler implements GraphDbServiceEventListener { public void serviceChanged( final GraphDbServiceEvent event ) { UiHelper.asyncExec( new Runnable() { public void run() { if ( event.getStatus() == GraphDbServiceStatus.STOPPED ) { provider.refresh(); viewer.refresh(); } else if ( event.getStatus() == GraphDbServiceStatus.STARTED ) { provider.refresh(); viewer.refresh( true ); } else if ( event.getStatus() == GraphDbServiceStatus.ROLLBACK ) { provider.refresh(); viewer.refresh( true ); } } } ); } } /** * Handle change in the relationship color settings. */ private class RelationshipColorChangeHandler implements NeoclipseEventListener { public void stateChanged( final NeoclipseEvent event ) { viewer.refresh( true ); } } }