/* * Copyright (C) 2014 by Array Systems Computing Inc. http://www.array.ca * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) * any later version. * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see http://www.gnu.org/licenses/ */ package org.esa.snap.graphbuilder.rcp.dialogs.support; import org.esa.snap.core.gpf.graph.NodeSource; import org.esa.snap.core.util.StringUtils; import org.esa.snap.graphbuilder.gpf.ui.OperatorUIRegistry; import javax.swing.*; import javax.swing.border.BevelBorder; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.Rectangle2D; import java.util.Arrays; import java.util.List; import java.util.Set; /** * Draws and Edits the graph graphically * User: lveci * Date: Jan 15, 2008 */ public class GraphPanel extends JPanel implements ActionListener, PopupMenuListener, MouseListener, MouseMotionListener { private final GraphExecuter graphEx; private JMenu addMenu; private Point lastMousePos = null; private final AddMenuListener addListener = new AddMenuListener(this); private final ConnectMenuListener connectListener = new ConnectMenuListener(this); private final RemoveSourceMenuListener removeSourceListener = new RemoveSourceMenuListener(this); private static final ImageIcon opIcon = new ImageIcon(GraphPanel.class.getClassLoader(). getResource("org/esa/snap/graphbuilder/icons/operator.png")); private static final ImageIcon folderIcon = new ImageIcon(GraphPanel.class.getClassLoader(). getResource("org/esa/snap/graphbuilder/icons/folder.png")); private static final Font font = new Font("Ariel", Font.BOLD, 10); private static final Color opColor = new Color(0, 177, 255, 128); private static final Color selColor = new Color(200, 255, 200, 150); private static final char[] folderDelim = new char[]{'/'};//'\\'}; private GraphNode selectedNode = null; private boolean showHeadHotSpot = false; private boolean showTailHotSpot = false; private boolean connectingSourceFromHead = false; private boolean connectingSourceFromTail = false; private Point connectingSourcePos = null; private GraphNode connectSourceTargetNode = null; private boolean showRightClickHelp = false; public GraphPanel(GraphExecuter graphExec) { graphEx = graphExec; CreateAddOpMenu(); addMouseListener(this); addMouseMotionListener(this); } /** * Creates a menu containing the list of operators to the addMenu */ private void CreateAddOpMenu() { addMenu = new JMenu("Add"); final SwingWorker<Boolean, Object> menuThread = new MenuThread(addMenu, addListener, graphEx); menuThread.execute(); } private static class MenuThread extends SwingWorker<Boolean, Object> { private final JMenu addMenu; private final AddMenuListener addListener; private final GraphExecuter graphEx; public MenuThread(final JMenu addMenu, final AddMenuListener addListener, final GraphExecuter graphEx) { this.addMenu = addMenu; this.addListener = addListener; this.graphEx = graphEx; } @Override protected Boolean doInBackground() throws Exception { // get operator list from graph executor final Set<String> gpfOperatorSet = graphEx.GetOperatorList(); final String[] gpfOperatorList = new String[gpfOperatorSet.size()]; gpfOperatorSet.toArray(gpfOperatorList); Arrays.sort(gpfOperatorList); // add operators for (String anAlias : gpfOperatorList) { if (!graphEx.isOperatorInternal(anAlias) && OperatorUIRegistry.showInGraphBuilder(anAlias)) { final String category = graphEx.getOperatorCategory(anAlias); JMenu menu = addMenu; if (!category.isEmpty()) { final String[] categoryPath = StringUtils.split(category, folderDelim, true); for (String folder : categoryPath) { menu = getMenuFolder(folder, menu); } } final JMenuItem item = new JMenuItem(anAlias, opIcon); item.setHorizontalTextPosition(JMenuItem.RIGHT); item.addActionListener(addListener); menu.add(item); } } return true; } } private static JMenu getMenuFolder(final String folderName, final JMenu currentMenu) { int insertPnt = 0; for (int i = 0; i < currentMenu.getItemCount(); ++i) { JMenuItem item = currentMenu.getItem(i); if (item instanceof JMenu) { int comp = item.getText().compareToIgnoreCase(folderName); if (comp == 0) { return (JMenu) item; } else if (comp < 0) { insertPnt++; } } } final JMenu newMenu = new JMenu(folderName); newMenu.setIcon(folderIcon); currentMenu.insert(newMenu, insertPnt); return newMenu; } void AddOperatorAction(String name) { final GraphNode newGraphNode = graphEx.addOperator(name); newGraphNode.setPos(lastMousePos); repaint(); } void RemoveSourceAction(String id) { if (selectedNode != null) { final GraphNode source = graphEx.getGraphNodeList().findGraphNode(id); selectedNode.disconnectOperatorSources(source.getID()); repaint(); } } void AutoConnectGraph() { if (!graphEx.getGraphNodeList().isGraphComplete()) { graphEx.autoConnectGraph(); repaint(); } } /** * Handles menu item pressed events * * @param event the action event */ public void actionPerformed(ActionEvent event) { final String name = event.getActionCommand(); if (name.equals("Delete")) { graphEx.removeOperator(selectedNode); repaint(); } } private void checkPopup(MouseEvent e) { if (e.isPopupTrigger()) { final JPopupMenu popup = new JPopupMenu(); popup.add(addMenu); if (selectedNode != null) { final JMenuItem item = new JMenuItem("Delete"); popup.add(item); item.setHorizontalTextPosition(JMenuItem.RIGHT); item.addActionListener(this); final NodeSource[] sources = selectedNode.getNode().getSources(); if (sources.length > 0) { final JMenu removeSourcedMenu = new JMenu("Remove Source"); for (NodeSource ns : sources) { final JMenuItem nsItem = new JMenuItem(ns.getSourceNodeId()); removeSourcedMenu.add(nsItem); nsItem.setHorizontalTextPosition(JMenuItem.RIGHT); nsItem.addActionListener(removeSourceListener); } popup.add(removeSourcedMenu); } } if (!graphEx.getGraphNodeList().isGraphComplete()) { final JMenuItem connectItem = new JMenuItem("Connect Graph", null); connectItem.setHorizontalTextPosition(JMenuItem.RIGHT); connectItem.addActionListener(connectListener); popup.add(connectItem); } popup.setLabel("Justification"); popup.setBorder(new BevelBorder(BevelBorder.RAISED)); popup.addPopupMenuListener(this); popup.show(this, e.getX(), e.getY()); showRightClickHelp = false; } } public void popupMenuWillBecomeVisible(PopupMenuEvent e) { } public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { } public void popupMenuCanceled(PopupMenuEvent e) { } /** * Paints the panel component * * @param g The Graphics */ @Override protected void paintComponent(java.awt.Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); DrawGraph(g2, graphEx.GetGraphNodes()); } /** * Draw the graphical representation of the Graph * * @param g the Graphics * @param nodeList the list of graphNodes */ private void DrawGraph(Graphics2D g, List<GraphNode> nodeList) { if(nodeList.isEmpty()) return; g.setFont(font); if (showRightClickHelp) { drawHelp(g); } for (GraphNode n : nodeList) { if (n == selectedNode) n.drawNode(g, selColor); else n.drawNode(g, opColor); } // first pass sets the Size in drawNode according to string length for (GraphNode n : nodeList) { // connect source nodes g.setColor(Color.red); final NodeSource[] nSources = n.getNode().getSources(); for (NodeSource nSource : nSources) { final GraphNode srcNode = graphEx.getGraphNodeList().findGraphNode(nSource.getSourceNodeId()); if (srcNode != null) n.drawConnectionLine(g, srcNode); } } if (showHeadHotSpot && selectedNode != null) { selectedNode.drawHeadHotspot(g, Color.red); } if (showTailHotSpot && selectedNode != null) { selectedNode.drawTailHotspot(g, Color.red); } if (connectingSourceFromHead && connectSourceTargetNode != null) { final Point p1 = connectSourceTargetNode.getPos(); final Point p2 = connectingSourcePos; if (p1 != null && p2 != null) { g.setColor(Color.red); g.drawLine(p1.x, p1.y + connectSourceTargetNode.getHalfNodeHeight(), p2.x, p2.y); } } else if (connectingSourceFromTail && connectSourceTargetNode != null) { final Point p1 = connectSourceTargetNode.getPos(); final Point p2 = connectingSourcePos; if (p1 != null && p2 != null) { g.setColor(Color.red); g.drawLine(p1.x + connectSourceTargetNode.getWidth(), p1.y + connectSourceTargetNode.getHalfNodeHeight(), p2.x, p2.y); } } } public void showRightClickHelp(boolean flag) { showRightClickHelp = flag; } private static void drawHelp(final Graphics g) { final int x = (int) (g.getClipBounds().getWidth() / 2); final int y = (int) (g.getClipBounds().getHeight() / 2); final FontMetrics metrics = g.getFontMetrics(); final String name = "Right click here to add an operator"; final Rectangle2D rect = metrics.getStringBounds(name, g); final int stringWidth = (int) rect.getWidth(); g.setColor(Color.black); g.drawString(name, x - stringWidth / 2, y); } /** * Handle mouse pressed event * * @param e the mouse event */ public void mousePressed(MouseEvent e) { checkPopup(e); if (showHeadHotSpot) { connectingSourceFromHead = true; } else if (showTailHotSpot) { connectingSourceFromTail = true; } lastMousePos = e.getPoint(); } /** * Handle mouse clicked event * * @param e the mouse event */ public void mouseClicked(MouseEvent e) { checkPopup(e); showRightClickHelp = false; if (e.getButton() == 1 && selectedNode != null) { graphEx.setSelectedNode(selectedNode); } } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } /** * Handle mouse released event * * @param e the mouse event */ public void mouseReleased(MouseEvent e) { checkPopup(e); if (connectingSourceFromHead) { final GraphNode n = findNode(e.getPoint()); if (n != null && selectedNode != n) { connectSourceTargetNode.connectOperatorSource(n.getID()); } } else if (connectingSourceFromTail) { final GraphNode n = findNode(e.getPoint()); if (n != null && selectedNode != n) { n.connectOperatorSource(connectSourceTargetNode.getID()); } } connectingSourceFromHead = false; connectingSourceFromTail = false; connectSourceTargetNode = null; if (graphEx.getGraphNodeList().isGraphComplete()) { graphEx.notifyConnection(); } repaint(); } /** * Handle mouse dragged event * * @param e the mouse event */ public void mouseDragged(MouseEvent e) { if (selectedNode != null && !connectingSourceFromHead && !connectingSourceFromTail) { final Point p = new Point(e.getX() - (lastMousePos.x - selectedNode.getPos().x), e.getY() - (lastMousePos.y - selectedNode.getPos().y)); selectedNode.setPos(p); lastMousePos = e.getPoint(); repaint(); } if (connectingSourceFromHead || connectingSourceFromTail) { connectingSourcePos = e.getPoint(); repaint(); } } /** * Handle mouse moved event * * @param e the mouse event */ public void mouseMoved(MouseEvent e) { final GraphNode n = findNode(e.getPoint()); if (selectedNode != n) { showHeadHotSpot = false; showTailHotSpot = false; selectedNode = n; repaint(); } if (selectedNode != null) { final int hotspotSize = GraphNode.getHotSpotSize(); final Point headPoint = new Point(n.getPos().x, n.getPos().y + selectedNode.getHotSpotOffset()); final Point tailPoint = new Point(n.getPos().x + n.getWidth() - hotspotSize, n.getPos().y + selectedNode.getHotSpotOffset()); if (isWithinRect(headPoint, hotspotSize, hotspotSize, e.getPoint())) { showHeadHotSpot = true; connectSourceTargetNode = selectedNode; repaint(); } else if (isWithinRect(tailPoint, hotspotSize, hotspotSize, e.getPoint())) { showTailHotSpot = true; connectSourceTargetNode = selectedNode; repaint(); } else if (showHeadHotSpot || showTailHotSpot) { showHeadHotSpot = false; showTailHotSpot = false; repaint(); } } } private GraphNode findNode(Point p) { for (GraphNode n : graphEx.GetGraphNodes()) { if (isWithinRect(n.getPos(), n.getWidth(), n.getHeight(), p)) return n; } return null; } private static boolean isWithinRect(Point o, int width, int height, Point p) { return p.x > o.x && p.y > o.y && p.x < o.x + width && p.y < o.y + height; } static class AddMenuListener implements ActionListener { final GraphPanel graphPanel; AddMenuListener(GraphPanel panel) { graphPanel = panel; } public void actionPerformed(java.awt.event.ActionEvent event) { graphPanel.AddOperatorAction(event.getActionCommand()); } } static class ConnectMenuListener implements ActionListener { final GraphPanel graphPanel; ConnectMenuListener(GraphPanel panel) { graphPanel = panel; } public void actionPerformed(java.awt.event.ActionEvent event) { graphPanel.AutoConnectGraph(); } } static class RemoveSourceMenuListener implements ActionListener { final GraphPanel graphPanel; RemoveSourceMenuListener(GraphPanel panel) { graphPanel = panel; } public void actionPerformed(java.awt.event.ActionEvent event) { graphPanel.RemoveSourceAction(event.getActionCommand()); } } }