Java tutorial
/****************************************************************************** * Copyright (c) 2000-2017 Ericsson Telecom AB * 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 ******************************************************************************/ package org.eclipse.titanium.graph.gui.windows; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Frame; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.util.Collection; import java.util.Deque; import java.util.HashSet; import java.util.Set; import javax.swing.ButtonGroup; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPanel; import org.apache.commons.collections15.Transformer; import org.eclipse.core.commands.AbstractHandler; import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.swt.SWT; import org.eclipse.swt.awt.SWT_AWT; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Listener; import org.eclipse.titan.common.logging.ErrorReporter; import org.eclipse.titan.designer.properties.data.ProjectBuildPropertyData; import org.eclipse.titanium.error.GUIErrorHandler; import org.eclipse.titanium.graph.components.EdgeDescriptor; import org.eclipse.titanium.graph.components.NodeColours; import org.eclipse.titanium.graph.components.NodeDescriptor; import org.eclipse.titanium.graph.generators.GraphGenerator; import org.eclipse.titanium.graph.gui.common.CustomVisualizationViewer; import org.eclipse.titanium.graph.gui.common.Layouts; import org.eclipse.titanium.graph.gui.dialogs.ExportPreferencesDialog; import org.eclipse.titanium.graph.gui.utils.LayoutEntry; import org.eclipse.titanium.graph.utils.CheckParallelPaths; import org.eclipse.titanium.graph.utils.CircleCheck; import org.eclipse.titanium.graph.visualization.BadLayoutException; import org.eclipse.titanium.graph.visualization.ErrorType; import org.eclipse.titanium.graph.visualization.GraphHandler; import org.eclipse.titanium.graph.visualization.GraphHandler.ImageExportType; import org.eclipse.titanium.gui.FindWindow; import org.eclipse.titanium.gui.Searchable; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorReference; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.contexts.IContextService; import org.eclipse.ui.handlers.IHandlerService; import org.eclipse.ui.part.EditorPart; import edu.uci.ics.jung.graph.DirectedSparseGraph; /** * This class is an {@link EditorPart} that shows the dependency graph.<br /> * <b>Important note: </b> You must set the {@link #handler} and * {@link #generator} attributes in the {@link #createPartControl(Composite)} * method of subclasses, otherwise <b>you may have null pointer exceptions</b> * * @author Gabor Jenei */ public abstract class GraphEditor extends EditorPart implements Searchable<NodeDescriptor> { protected DirectedSparseGraph<NodeDescriptor, EdgeDescriptor> graph; protected GraphHandler handler; protected Transformer<NodeDescriptor, String> labeler; protected JPanel drawArea; protected Frame window; protected Dimension windowSize; protected LayoutEntry chosenLayout; protected IProject project; protected SatelliteView satView = null; protected Composite editorComposite = null; protected FindWindow<NodeDescriptor> wndFind = null; protected IHandlerService handlerService = null; protected Set<AbstractHandler> handlers = null; protected JMenu layoutMenu; protected ActionListener layoutListener; protected ButtonGroup layoutGroup; protected JMenuBar menuBar; protected GraphGenerator generator; protected final GUIErrorHandler errorHandler; private static final String ID = "org.eclipse.titanium.graph.editors.GraphEditor"; public static final String GRAPH_CONTEXT_ID = "org.eclipse.titanium.contexts.GraphContext"; public static final String GRAPH_SAVECMD_ID = "org.eclipse.titanium.commands.GraphSave"; public static final String GRAPH_EXPORTCMD_ID = "org.eclipse.titanium.commands.GraphExport"; public static final String GRAPH_SEARCHCMD_ID = "org.eclipse.titanium.commands.GraphSearch"; protected static final String LOGENTRYNOTE = " (see error log for further information)"; /** * It creates a module dependency graph window, this method mustn't be * called invidually. */ public GraphEditor() { super(); handlers = new HashSet<AbstractHandler>(); chosenLayout = Layouts.LAYOUT_TDAG.newInstance(); errorHandler = new GUIErrorHandler(); } /** * We have to dispose the windows shown on our Editor */ @Override public void dispose() { super.dispose(); EventQueue.invokeLater(new Runnable() { @Override public void run() { if (window != null) { window.dispose(); } } }); Display.getDefault().asyncExec(new Runnable() { @Override public void run() { if (wndFind != null) { wndFind.close(); } } }); final IWorkbenchWindow wind = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); if (wind == null) { return; } final IEditorReference[] editors = wind.getActivePage().findEditors(null, GraphEditor.ID, IWorkbenchPage.MATCH_ID); if (editors != null && editors.length == 0 && satView != null) { satView.setEditor(null); satView.clear(); } for (final AbstractHandler hnd : handlers) { hnd.dispose(); } } /** * {@link #doSave(IProgressMonitor)} method is empty, not used to do save */ @Override public void doSave(final IProgressMonitor monitor) { } /** * {@link #doSaveAs()} method is empty, not used for saving operations */ @Override public void doSaveAs() { } /** * We just store the input and site to catch events * * @param input * : the input to set * @param site * : the site to set */ @Override public void init(final IEditorSite site, final IEditorInput input) throws PartInitException { final IContextService contextService = (IContextService) site.getService(IContextService.class); contextService.activateContext(GRAPH_CONTEXT_ID); handlerService = (IHandlerService) site.getService(IHandlerService.class); setSite(site); setInput(input); } /** * This {@link EditorPart} is never dirty, so return value is constant false */ @Override public boolean isDirty() { return false; } /** * As the save methods are empty this functions returns constant false */ @Override public boolean isSaveAsAllowed() { return false; } /** * This method generates the dependency graph and sets an {@link EditorPart} * to show the graph's window. */ @Override public void createPartControl(final Composite parent) { initGeneratorAndHandler(parent); editorComposite = new Composite(parent, SWT.NO_BACKGROUND | SWT.EMBEDDED); window = SWT_AWT.new_Frame(editorComposite); windowSize = new Dimension(parent.getSize().x, parent.getSize().y); parent.addListener(SWT.Resize, new Listener() { @Override public void handleEvent(final org.eclipse.swt.widgets.Event event) { final Point tmpSize = parent.getSize(); windowSize = new Dimension(tmpSize.x, tmpSize.y); if (handler != null) { handler.changeWindowSize(windowSize); } if (window != null && drawArea != null) { drawArea.setPreferredSize(windowSize); window.setPreferredSize(windowSize); window.repaint(); } } }); project = ((IFileEditorInput) getEditorInput()).getFile().getProject(); setPartName(getPartName() + " - " + project.getName()); // get a reference to the satellite viewer satView = (SatelliteView) PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage() .findView(SatelliteView.ID); if (satView != null) { satView.setEditor(this); } else { try { satView = (SatelliteView) PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage() .showView(SatelliteView.ID); satView.setEditor(this); } catch (PartInitException e) { errorHandler.reportException("Error while opening the view", e); } } initWindow(); } /** * This method does the necessary activities to make the editor window * active */ @Override public void setFocus() { final IWorkbenchWindow tmpWnd = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); if (tmpWnd != null) { satView = (SatelliteView) tmpWnd.getActivePage().findView(SatelliteView.ID); } final GraphEditor thisEditor = this; Display.getDefault().asyncExec(new Runnable() { @Override public void run() { if (handler != null && satView != null) { satView.add(handler.getSatelliteViewer()); satView.setEditor(thisEditor); } } }); } /** * This method creates the items to show on the {@link Frame} , and adds * actions */ protected void initWindow() { drawArea = new JPanel(); window.add(drawArea, BorderLayout.CENTER); drawArea.setSize(windowSize.width, windowSize.height); drawArea.setPreferredSize(new Dimension(windowSize.width, windowSize.height)); menuBar = new JMenuBar(); window.add(menuBar, BorderLayout.NORTH); final JMenu mnFile = new JMenu("File"); final ActionListener saveGraph = new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { String path = ""; try { path = project.getPersistentProperty( new QualifiedName(ProjectBuildPropertyData.QUALIFIER, "Graph_Save_Path")); } catch (CoreException exc) { errorHandler.reportException("Error while reading persistent property", exc); } final String oldPath = path; Display.getDefault().asyncExec(new Runnable() { @Override public void run() { FileDialog dialog = new FileDialog(editorComposite.getShell(), SWT.SAVE); dialog.setText("Save Pajek file"); dialog.setFilterPath(oldPath); dialog.setFilterExtensions(new String[] { "*.net", "*.dot" }); String graphFilePath = dialog.open(); if (graphFilePath == null) { return; } String newPath = graphFilePath.substring(0, graphFilePath.lastIndexOf(File.separator) + 1); try { QualifiedName name = new QualifiedName(ProjectBuildPropertyData.QUALIFIER, "Graph_Save_Path"); project.setPersistentProperty(name, newPath); if ("dot".equals(graphFilePath.substring(graphFilePath.lastIndexOf('.') + 1, graphFilePath.length()))) { GraphHandler.saveGraphToDot(graph, graphFilePath, project.getName()); } else { GraphHandler.saveGraphToPajek(graph, graphFilePath); } } catch (BadLayoutException be) { ErrorReporter.logExceptionStackTrace("Error while saving image to " + newPath, be); errorHandler.reportErrorMessage("Bad layout\n\n" + be.getMessage()); } catch (Exception ce) { ErrorReporter.logExceptionStackTrace("Error while saving image to " + newPath, ce); errorHandler.reportException("Error while setting persistent property", ce); } } }); } }; final JMenuItem mntmSave = new JMenuItem("Save (Ctrl+S)"); mntmSave.addActionListener(saveGraph); mnFile.add(mntmSave); final ActionListener exportImage = new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { String path = ""; try { path = project.getPersistentProperty( new QualifiedName(ProjectBuildPropertyData.QUALIFIER, "Graph_Save_Path")); } catch (CoreException exc) { errorHandler.reportException("Error while reading persistent property", exc); } final String oldPath = path; Display.getDefault().asyncExec(new Runnable() { @Override public void run() { ExportPreferencesDialog prefDialog = new ExportPreferencesDialog( editorComposite.getShell()); ImageExportType mode = prefDialog.open(); FileDialog dialog = new FileDialog(editorComposite.getShell(), SWT.SAVE); dialog.setText("Export image"); dialog.setFilterPath(oldPath); dialog.setFilterExtensions(new String[] { "*.png" }); String graphFilePath = dialog.open(); if (graphFilePath == null) { return; } String newPath = graphFilePath.substring(0, graphFilePath.lastIndexOf(File.separator) + 1); try { QualifiedName name = new QualifiedName(ProjectBuildPropertyData.QUALIFIER, "Graph_Save_Path"); project.setPersistentProperty(name, newPath); handler.saveToImage(graphFilePath, mode); } catch (BadLayoutException be) { errorHandler.reportException("Error while saving image", be); errorHandler.reportErrorMessage(be.getMessage()); } catch (CoreException ce) { errorHandler.reportException("Error while setting persistent property", ce); } } }); } }; final JMenuItem mntmExportToImage = new JMenuItem("Export to image file (Ctrl+E)"); mntmExportToImage.addActionListener(exportImage); mnFile.add(mntmExportToImage); layoutMenu = new JMenu("Layout"); layoutGroup = new ButtonGroup(); layoutListener = new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { final IProgressMonitor monitor = Job.getJobManager().createProgressGroup(); monitor.beginTask("Change layout", 100); if (!(e.getSource() instanceof LayoutEntry)) { errorHandler.reportErrorMessage( "Unexpected error\n\nAn unusual error has been logged" + LOGENTRYNOTE); ErrorReporter.logError("The layout changing event's source is not of type \"LayoutEntry\"!"); return; } final LayoutEntry layout = (LayoutEntry) e.getSource(); if (handler.getVisualizator() != null) { drawArea.remove(handler.getVisualizator()); } try { handler.changeLayout(layout, windowSize); drawArea.add(handler.getVisualizator()); if (satView != null) { satView.add(handler.getSatelliteViewer()); } window.pack(); } catch (BadLayoutException exc) { layout.setSelected(false); chosenLayout.setSelected(true); if (exc.getType() == ErrorType.EMPTY_GRAPH || exc.getType() == ErrorType.NO_OBJECT) { return; } try { handler.changeLayout(chosenLayout, windowSize); drawArea.add(handler.getVisualizator()); if (satView != null) { satView.add(handler.getSatelliteViewer()); } window.pack(); monitor.done(); } catch (BadLayoutException exc2) { monitor.done(); if (exc2.getType() != ErrorType.CYCLIC_GRAPH && exc2.getType() != ErrorType.EMPTY_GRAPH) { errorHandler.reportException("Error while creating layout", exc2); } else { errorHandler.reportErrorMessage(exc2.getMessage()); } } catch (IllegalStateException exc3) { monitor.done(); errorHandler.reportException("Error while creating layout", exc3); } if (exc.getType() != ErrorType.CYCLIC_GRAPH && exc.getType() != ErrorType.EMPTY_GRAPH) { errorHandler.reportException("Error while creating layout", exc); } else { errorHandler.reportErrorMessage(exc.getMessage()); } } catch (IllegalStateException exc) { layout.setSelected(false); chosenLayout.setSelected(true); try { handler.changeLayout(chosenLayout, windowSize); drawArea.add(handler.getVisualizator()); if (satView != null) { satView.add(handler.getSatelliteViewer()); } window.pack(); monitor.done(); } catch (BadLayoutException exc2) { monitor.done(); if (exc2.getType() != ErrorType.CYCLIC_GRAPH && exc2.getType() != ErrorType.EMPTY_GRAPH) { errorHandler.reportException("Error while creating layout", exc2); } else { errorHandler.reportErrorMessage(exc2.getMessage()); } } catch (IllegalStateException exc3) { monitor.done(); errorHandler.reportException("Error while creating layout", exc3); } errorHandler.reportException("Error while creating layout", exc); } chosenLayout = layout.newInstance(); monitor.done(); } }; final JMenu findMenu = new JMenu("Find"); final JMenuItem nodeByName = new JMenuItem("Node by name (Ctrl+F)"); final GraphEditor thisEditor = this; nodeByName.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { Display.getDefault().asyncExec(new Runnable() { @Override public void run() { if (wndFind != null) { wndFind.close(); } try { wndFind = new FindWindow<NodeDescriptor>(editorComposite.getShell(), thisEditor, graph.getVertices()); wndFind.open(); } catch (IllegalArgumentException e) { errorHandler.reportException("", e); } } }); } }); findMenu.add(nodeByName); final JMenu tools = new JMenu("Tools"); final JMenuItem findCircles = new JMenuItem("Show circles"); final JMenuItem findPaths = new JMenuItem("Show parallel paths"); final JMenuItem clearResults = new JMenuItem("Clear Results"); findCircles.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent ev) { final Job circlesJob = new Job("Searching for circles") { @Override protected IStatus run(final IProgressMonitor monitor) { if (graph == null) { return null; } CircleCheck<NodeDescriptor, EdgeDescriptor> checker = new CircleCheck<NodeDescriptor, EdgeDescriptor>( graph); if (checker.isCyclic()) { for (EdgeDescriptor e : graph.getEdges()) { e.setColour(Color.lightGray); } for (Deque<EdgeDescriptor> st : checker.getCircles()) { for (EdgeDescriptor e : st) { e.setColour(NodeColours.DARK_RED); } } refresh(); } else { errorHandler.reportInformation("Result:\n\nThis graph is not cyclic!"); } return Status.OK_STATUS; } // end run }; // end job circlesJob.schedule(); } // end actionPerformed }); findPaths.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent ev) { final Job pathsJob = new Job("Searching for parallel paths") { @Override protected IStatus run(final IProgressMonitor monitor) { if (graph == null) { return null; } CheckParallelPaths<NodeDescriptor, EdgeDescriptor> checker = null; checker = new CheckParallelPaths<NodeDescriptor, EdgeDescriptor>(graph); if (checker.hasParallelPaths()) { for (EdgeDescriptor e : graph.getEdges()) { e.setColour(Color.lightGray); } for (Deque<EdgeDescriptor> list : checker.getPaths()) { for (EdgeDescriptor e : list) { e.setColour(NodeColours.DARK_RED); } } refresh(); } else { errorHandler.reportInformation("Result:\n\nThere are no parallel paths in this graph!"); } return Status.OK_STATUS; } // end run }; // end job pathsJob.schedule(); } // end actionPerformed }); clearResults.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent ev) { for (final EdgeDescriptor e : graph.getEdges()) { e.setColour(Color.black); } refresh(); } }); tools.add(findCircles); tools.add(findPaths); tools.add(clearResults); menuBar.add(mnFile); menuBar.add(findMenu); menuBar.add(tools); menuBar.add(layoutMenu); // TODO implement refresh action /* * JMenuItem RefreshMenu=new JMenuItem("Refresh"); ActionListener * RefreshAction=new ActionListener() { public void * actionPerformed(ActionEvent ev) { GraphGenerator.schedule(); } }; * RefreshMenu.addActionListener(RefreshAction); * * menuBar.add(RefreshMenu); */ handlerService.activateHandler(GRAPH_SEARCHCMD_ID, new AbstractHandler() { @Override public Object execute(final ExecutionEvent event) throws ExecutionException { nodeByName.getActionListeners()[0].actionPerformed(null); handlers.add(this); return null; } }); handlerService.activateHandler(GRAPH_SAVECMD_ID, new AbstractHandler() { @Override public Object execute(final ExecutionEvent event) throws ExecutionException { mntmSave.getActionListeners()[0].actionPerformed(null); handlers.add(this); return null; } }); handlerService.activateHandler(GRAPH_EXPORTCMD_ID, new AbstractHandler() { @Override public Object execute(final ExecutionEvent event) throws ExecutionException { mntmExportToImage.getActionListeners()[0].actionPerformed(null); handlers.add(this); return null; } }); try { generator.generateGraph(); setLabeller(generator.getLabeler()); setGraph(generator.getGraph()); } catch (InterruptedException ex) { errorHandler.reportException("Error while creating the graph", ex); } } /** * This method is used to set the initial graph, or set the refreshed graph * * @param g * : The graph to set */ public void setGraph(final DirectedSparseGraph<NodeDescriptor, EdgeDescriptor> g) { this.graph = g; Display.getDefault().asyncExec(new Runnable() { @Override public void run() { if (drawArea == null) { return; } drawArea.setPreferredSize(windowSize); try { drawArea.removeAll(); handler.drawGraph(graph, windowSize, chosenLayout); drawArea.add(handler.getVisualizator()); if (satView != null) { satView.add(handler.getSatelliteViewer()); } window.pack(); recolour(graph.getVertices()); } catch (BadLayoutException be) { ErrorReporter.logExceptionStackTrace("Error while drawing graph", be); errorHandler.reportErrorMessage(be.getMessage()); } } }); } /** * sets a new labeler * * @param labeler * : the labeler to set */ public void setLabeller(final Transformer<NodeDescriptor, String> labeler) { this.labeler = labeler; } /** * This method is called to set the {@link SatelliteView}, this is needed to * be able to run modifying actions. * * @param sat * : The satellite viewer (<b>this should be unique in every * workspace</b>) */ public void setSatellite(final SatelliteView sat) { satView = sat; if (satView != null && handler != null) { satView.add(handler.getSatelliteViewer()); } } /** * @return returns the graph shown in the editor */ public DirectedSparseGraph<NodeDescriptor, EdgeDescriptor> getGraph() { return graph; } /** * This method causes both the main graph window and the satellite view to * refresh. This method is thread safe! */ public void refresh() { EventQueue.invokeLater(new Runnable() { @Override public void run() { drawArea.repaint(); if (satView != null) { satView.repaint(); } } }); } /** * This method should make the changes visible in the inner representation * of the graph. Currently it generates a totally new graph from an empty * one. */ public void refreshGraph() { if (generator == null) { return; } try { generator.generateGraph(); setLabeller(generator.getLabeler()); setGraph(generator.getGraph()); } catch (InterruptedException ex) { errorHandler.reportException("Error while refreshing the graph", ex); } } /** * @return Returns the shown project */ public IProject getProject() { return project; } /** * @return Returns the graph handler used for drawing the graph */ public GraphHandler getHandler() { return handler; } @Override public void setResults(final Collection<NodeDescriptor> results) { clearResults(); addResults(results); } @Override public void clearResults() { recolour(graph.getVertices()); } @Override public void addResults(final Collection<NodeDescriptor> results) { for (final NodeDescriptor node : results) { node.setNodeColour(NodeColours.RESULT_COLOUR); } } @Override public void elemChosen(final NodeDescriptor element) { final CustomVisualizationViewer visualisator = handler.getVisualizator(); visualisator.jumpToPlace(visualisator.getGraphLayout().transform(element)); for (final NodeDescriptor node : graph.getVertices()) { node.setNodeColour(NodeColours.NOT_RESULT_COLOUR); } element.setNodeColour(NodeColours.RESULT_COLOUR); } /** * This method recolours a given set of nodes inside the graph * * @param nodeSet * : The set of nodes */ public abstract void recolour(Collection<NodeDescriptor> nodeSet); /** * This method implements the initialization of generator and handler * attributes, for further details see {@link GraphGenerator} and * {@link GraphHandler} * * @param parent * : A reference to the parent shell */ protected abstract void initGeneratorAndHandler(final Composite parent); }