/* * Copyright 2013 Serdar. * * Licensed 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 de.fub.maps.project.aggregator.graph; import de.fub.maps.project.aggregator.pipeline.AbstractAggregationProcess; import de.fub.maps.project.models.Aggregator; import java.awt.Point; import java.awt.Rectangle; import java.awt.dnd.DropTarget; import java.awt.dnd.DropTargetAdapter; import java.awt.dnd.DropTargetDropEvent; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.netbeans.api.visual.graph.layout.GraphLayout; import org.netbeans.api.visual.graph.layout.GraphLayoutSupport; import org.netbeans.api.visual.graph.layout.UniversalGraph; import org.netbeans.api.visual.layout.LayoutFactory; import org.netbeans.api.visual.layout.SceneLayout; import org.netbeans.api.visual.widget.Widget; import org.openide.util.ChangeSupport; import org.openide.util.Lookup; /** * * @author Serdar */ public class GraphPanel extends javax.swing.JPanel implements ChangeListener, Lookup.Provider { private static final Logger LOG = Logger.getLogger(GraphPanel.class.getName()); private static final long serialVersionUID = 1L; private transient final ProcessGraph graph = new ProcessGraph(); private transient Aggregator aggregator = null; private transient final ChangeSupport cs = new ChangeSupport(this); private boolean reinitProcess = false; /** * Creates new form GraphPanel */ public GraphPanel() { initComponents(); jScrollPane1.setViewportView(graph.createView()); jScrollPane1.setDropTarget(new DropTarget(this, new DropHandler())); } public void addChangeListener(ChangeListener listener) { cs.addChangeListener(listener); } public void removeChangeListener(ChangeListener listener) { cs.removeChangeListener(listener); } public Aggregator getAggregator() { return aggregator; } public void setAggregator(Aggregator aggregator) { graph.removeChangeListener(GraphPanel.this); this.aggregator = aggregator; reinitGraph(); graph.addChangeListener(GraphPanel.this); } protected Widget attachNodeWidget(AbstractAggregationProcess<?, ?> node) { return graph.attachNodeWidget(node); } protected Widget attachEdgeWidget(String edge) { return graph.attachEdgeWidget(edge); } protected void attachEdgeSourceAnchor(String edge, AbstractAggregationProcess<?, ?> oldSourceNode, AbstractAggregationProcess<?, ?> sourceNode) { graph.attachEdgeSourceAnchor(edge, oldSourceNode, sourceNode); } protected void attachEdgeTargetAnchor(String edge, AbstractAggregationProcess<?, ?> oldTargetNode, AbstractAggregationProcess<?, ?> targetNode) { graph.attachEdgeTargetAnchor(edge, oldTargetNode, targetNode); } public void layoutGraph() { Collection<AbstractAggregationProcess<?, ?>> processes = aggregator.getPipeline().getProcesses(); if (!processes.isEmpty()) { final GraphLayout<AbstractAggregationProcess<?, ?>, String> layout = new GraphLayoutImpl(); AbstractAggregationProcess rootProcess = processes.iterator().next(); GraphLayoutSupport.setTreeGraphLayoutRootNode(layout, rootProcess); SceneLayout sceneLayout = LayoutFactory.createSceneGraphLayout(graph, layout); layout.setAnimated(false); sceneLayout.invokeLayoutImmediately(); graph.revalidate(); repaint(); } } private void reinitGraph() { reinitProcess = true; try { graph.validate(); // collected all processes that are currently in the gaphscene List<AbstractAggregationProcess<?, ?>> collectPipeline = collectPipeline(); // remove all process for (AbstractAggregationProcess<?, ?> process : collectPipeline) { graph.removeNodeWithEdges(process); graph.validate(); } Collection<AbstractAggregationProcess<?, ?>> processes = aggregator.getPipeline().getProcesses(); Widget lastNodeWidget = null; AbstractAggregationProcess<?, ?> lastProcess = null; int i = 1; for (AbstractAggregationProcess<?, ?> process : processes) { if (process != null) { Widget nodeWidget = graph.addNode(process); graph.validate(); if (lastNodeWidget != null && lastProcess != null) { String edgeID = graph.createEdge(); graph.addEdge(edgeID); graph.setEdgeSource(edgeID, lastProcess); graph.setEdgeTarget(edgeID, process); graph.validate(); } i++; lastNodeWidget = nodeWidget; lastProcess = process; } else { LOG.log(Level.FINE, "Error"); } } layoutGraph(); graph.repaint(); repaint(); } finally { reinitProcess = false; } } @Override public void stateChanged(ChangeEvent e) { if (!reinitProcess) { cs.fireChange(); } } /** * Returns a chain of process that start with an process that has as input * type Void. * * @return */ public List<AbstractAggregationProcess<?, ?>> collectPipeline() { return graph.collectPipeline(); } /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always * regenerated by the Form Editor. */ @SuppressWarnings("unchecked") // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { jScrollPane1 = new javax.swing.JScrollPane(); setLayout(new java.awt.BorderLayout()); add(jScrollPane1, java.awt.BorderLayout.CENTER); }// </editor-fold>//GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JScrollPane jScrollPane1; // End of variables declaration//GEN-END:variables @Override public Lookup getLookup() { return graph.getLookup(); } static class DropHandler extends DropTargetAdapter { public DropHandler() { } @Override public void drop(DropTargetDropEvent dtde) { } } private class GraphLayoutImpl extends GraphLayout<AbstractAggregationProcess<?, ?>, String> { private static final int BOUNDS_MARGIN = 20; public GraphLayoutImpl() { } @Override protected void performGraphLayout(UniversalGraph<AbstractAggregationProcess<?, ?>, String> graph) { int horizontalOffset = BOUNDS_MARGIN; int verticalOffset = BOUNDS_MARGIN; int row = 0; int column = 0; Rectangle viewRect = jScrollPane1.getViewport().getViewRect(); Collection<AbstractAggregationProcess<?, ?>> nodes = graph.getNodes(); ArrayList<AbstractAggregationProcess<?, ?>> rootNodeList = new ArrayList<AbstractAggregationProcess<?, ?>>(); // find root nodes; for (AbstractAggregationProcess<?, ?> node : nodes) { Collection<String> inputEdges = graph.findNodeEdges(node, false, true); if (inputEdges.isEmpty()) { rootNodeList.add(node); } } ArrayList<ArrayList<AbstractAggregationProcess<?, ?>>> chainLists = new ArrayList<ArrayList<AbstractAggregationProcess<?, ?>>>(); for (AbstractAggregationProcess<?, ?> rootNode : rootNodeList) { ArrayList<AbstractAggregationProcess<?, ?>> chainList = new ArrayList<AbstractAggregationProcess<?, ?>>(); while (rootNode != null) { chainList.add(rootNode); Collection<String> findNodeEdges = graph.findNodeEdges(rootNode, true, false); if (findNodeEdges.isEmpty()) { rootNode = null; } else { for (String nodeEdge : findNodeEdges) { rootNode = graph.getEdgeTarget(nodeEdge); break; } } } chainLists.add(chainList); } if (viewRect != null) { for (ArrayList<AbstractAggregationProcess<?, ?>> chainList : chainLists) { for (AbstractAggregationProcess<?, ?> node : chainList) { Widget widget = graph.getScene().findWidget(node); if (widget != null && widget.getBounds() != null) { Rectangle bounds = widget.getBounds(); if ((horizontalOffset + bounds.width) >= (viewRect.width - BOUNDS_MARGIN)) { column++; horizontalOffset = (column % 2 == 0) ? BOUNDS_MARGIN : BOUNDS_MARGIN * 3 + bounds.width / 2; verticalOffset += bounds.height + bounds.width / 2 + BOUNDS_MARGIN; } Point convertLocalToScene = graph.getScene().convertLocalToScene(new Point(horizontalOffset, verticalOffset)); widget.setPreferredLocation(convertLocalToScene); // LOG.info(widget.getLocation().toString()); graph.getScene().validate(); horizontalOffset += bounds.width + bounds.width / 2 + BOUNDS_MARGIN; } } horizontalOffset = BOUNDS_MARGIN; verticalOffset += 250 + BOUNDS_MARGIN; } } } @Override protected void performNodesLayout(UniversalGraph<AbstractAggregationProcess<?, ?>, String> graph, Collection<AbstractAggregationProcess<?, ?>> nodes) { performGraphLayout(graph); } } }