package view; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JLayeredPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.JTree; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreePath; import org.eclipse.elk.graph.ElkNode; import animation.Action; import animation.AnimationController; import animation.PseudoCodeNode; import bk.BKNodePlacement; import bk.ExtremalLayoutCalc.LayoutType; import graph.InitializeNodePositions; import graph.LayeredGraphEdge; import graph.LayeredGraphNode; import graph.LayeredNode; import graph.io.Reader; import graph.io.Writer; import lib.TextLayoutHelper; /** * The main window of the application. * There should only be one instance of this class at the same time. * The JFrame of that single instance can be accessed by the static field {code MainView.frame}. * @author kolja * */ public class MainView { /** * The 'frame' of the main window. * The reason why there can only be one instance of this class. */ private static int frameCounter = 0; /** * Random Graph Generator should olny exist once for all windows (so the values will be stored) */ private static final RandomGraphDialog randomDialog = new RandomGraphDialog(); private JFrame frame; private AnimationController controller; private JButton stepForward; private JButton stepForwardInto; private JButton stepForwardOut; private JButton stepBackward; private JButton stepBackwardInto; private JButton stepBackwardOut; private JButton runForward; private JButton runBackward; private JButton pause; private JButton load; private JButton save; private JButton debug; private JButton options; private JButton randomGraph; private JLabel delayText; private JTextField delay; private JTree pseudoTree; private LayeredGraphNode graph; private OptionsDialog optionsDialog; private String debugInfo() { String info = "Debug Information Table: \n"; info += "_______________________________________________________________________________________________________________________________________________________________________________________________________________________\n"; info += "|" + TextLayoutHelper.strToLen( "Top -> Bottom :> Left", 51 ) + "| |" + TextLayoutHelper.strToLen( "Top -> Bottom :> Right", 51 ) + "| |" + TextLayoutHelper.strToLen( "Bottom -> Top :> Left", 51 ) + "| |" + TextLayoutHelper.strToLen( "Bottom -> Top :> Right", 51 ) + "|\n"; info += "|___________________________________________________| |___________________________________________________| |___________________________________________________| |___________________________________________________|\n"; info += "| Node | Shift | Sink | Root | Align | x | xDef | | Node | Shift | Sink | Root | Align | x | xDef | | Node | Shift | Sink | Root | Align | x | xDef | | Node | Shift | Sink | Root | Align | x | xDef |\n"; for( LayeredGraphNode n : graph.getContainedNodes() ) { info += "|" + TextLayoutHelper.strToLen( n.getName(), 6 ) + "|" + TextLayoutHelper.strToLen( n.getShift( LayoutType.TOP_BOTTOM_LEFT ) + "", 7 ) + "|" + TextLayoutHelper.strToLen( n.getSink( LayoutType.TOP_BOTTOM_LEFT ).getName(), 6 ) + "|" + TextLayoutHelper.strToLen( n.getRoot( LayoutType.TOP_BOTTOM_LEFT ).getName(), 6 ) + "|" + TextLayoutHelper.strToLen( n.getAlign( LayoutType.TOP_BOTTOM_LEFT ).getName(), 7 ) + "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.TOP_BOTTOM_LEFT ) + "", 5 ) + "|" + TextLayoutHelper.strToLen( !n.isXUndefined( LayoutType.TOP_BOTTOM_LEFT ) + "", 8 ) + "| " + "|" + TextLayoutHelper.strToLen( n.getName(), 6 ) + "|" + TextLayoutHelper.strToLen( n.getShift( LayoutType.TOP_BOTTOM_RIGHT ) + "", 7 ) + "|" + TextLayoutHelper.strToLen( n.getSink( LayoutType.TOP_BOTTOM_RIGHT ).getName(), 6 ) + "|" + TextLayoutHelper.strToLen( n.getRoot( LayoutType.TOP_BOTTOM_RIGHT ).getName(), 6 ) + "|" + TextLayoutHelper.strToLen( n.getAlign( LayoutType.TOP_BOTTOM_RIGHT ).getName(), 7 ) + "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.TOP_BOTTOM_RIGHT ) + "", 5 ) + "|" + TextLayoutHelper.strToLen( !n.isXUndefined( LayoutType.TOP_BOTTOM_RIGHT ) + "", 8 ) + "| " + "|" + TextLayoutHelper.strToLen( n.getName(), 6 ) + "|" + TextLayoutHelper.strToLen( n.getShift( LayoutType.BOTTOM_TOP_LEFT ) + "", 7 ) + "|" + TextLayoutHelper.strToLen( n.getSink( LayoutType.BOTTOM_TOP_LEFT ).getName(), 6 ) + "|" + TextLayoutHelper.strToLen( n.getRoot( LayoutType.BOTTOM_TOP_LEFT ).getName(), 6 ) + "|" + TextLayoutHelper.strToLen( n.getAlign( LayoutType.BOTTOM_TOP_LEFT ).getName(), 7 ) + "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.BOTTOM_TOP_LEFT ) + "", 5 ) + "|" + TextLayoutHelper.strToLen( !n.isXUndefined( LayoutType.BOTTOM_TOP_LEFT ) + "", 8 ) + "| " + "|" + TextLayoutHelper.strToLen( n.getName(), 6 ) + "|" + TextLayoutHelper.strToLen( n.getShift( LayoutType.BOTTOM_TOP_RIGHT ) + "", 7 ) + "|" + TextLayoutHelper.strToLen( n.getSink( LayoutType.BOTTOM_TOP_RIGHT ).getName(), 6 ) + "|" + TextLayoutHelper.strToLen( n.getRoot( LayoutType.BOTTOM_TOP_RIGHT ).getName(), 6 ) + "|" + TextLayoutHelper.strToLen( n.getAlign( LayoutType.BOTTOM_TOP_RIGHT ).getName(), 7 ) + "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.BOTTOM_TOP_RIGHT ) + "", 5 ) + "|" + TextLayoutHelper.strToLen( !n.isXUndefined( LayoutType.BOTTOM_TOP_RIGHT ) + "", 8 ) + "|\n"; } info += "-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"; return info; } private void showDebugInfo() { JFrame debugFrame = new JFrame(); JTextArea info = new JTextArea(); info.setEditable( false ); info.setFont( new Font( Font.MONOSPACED, Font.PLAIN, 11 ) ); String infoS = debugInfo(); info.setText( infoS ); JScrollPane view = new JScrollPane( info ); debugFrame.add( view ); debugFrame.setSize( frame.getWidth(), frame.getHeight() ); debugFrame.setVisible( true ); if (infoS.trim().equals("")) { System.out.println( "" ); System.out.println( "Debug info:" ); System.out.println( infoS ); System.out.println( "" ); } } public MainView( ElkNode graph ) { this( LayeredNode.convertToLayeredGraph( graph ) ); } /** * Initialize the window and its contents. * There is good reason not to split up this method to smaller methods: * Imagine a tree with a fixed number of nodes, but limited degree of branching. * The the height of the tree is at least inversely proportional to the degree of branching. * This means halving the maximum method size by splitting methods would make the call stack twice as high * and this way debugging twice as time-consuming. * @param graph the graph that is displayed in this window. */ public MainView( LayeredGraphNode graph ) { frameCounter++; this.graph = graph; controller = new AnimationController(); frame = new JFrame( "NodeShuffler" ); frame.addWindowListener(new java.awt.event.WindowAdapter() { @Override public void windowClosing(java.awt.event.WindowEvent windowEvent) { frameCounter--; if( frameCounter == 0 ) System.exit( 0 ); } }); BKNodePlacement algorithm = new BKNodePlacement( controller, graph, frame ); // Create Menu GUI stepForward = new NiceButton( "stepForward" ); stepForward.setLocation( 10, 10 ); stepForward.setMnemonic( KeyEvent.VK_DOWN ); stepForward.setToolTipText( "Forward step over (alt + down arrow key)" ); stepForward.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { controller.setContinuous( false ); controller.setNextAction( Action.FORWARD_OVER ); } }); stepForwardInto = new NiceButton( "stepForwardInto" ); stepForwardInto.setLocation( 60, 10 ); stepForwardInto.setMnemonic( KeyEvent.VK_RIGHT ); stepForwardInto.setToolTipText( "Forward step into (alt + right arrow key)" ); stepForwardInto.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { controller.setContinuous( false ); controller.setNextAction( Action.FORWARD ); } }); stepForwardOut = new NiceButton( "stepForwardOut" ); stepForwardOut.setLocation( 110, 10 ); stepForwardOut.setMnemonic( KeyEvent.VK_PAGE_DOWN ); stepForwardOut.setToolTipText( "Forward step out (alt + page down key)" ); stepForwardOut.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { controller.setContinuous( false ); controller.setNextAction( Action.FORWARD_OUT ); } }); runForward = new NiceButton( "runForward" ); runForward.setLocation( 160, 10 ); runForward.setMnemonic( KeyEvent.VK_P ); runForward.setToolTipText( "Run forwards (alt + p)" ); runForward.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { controller.setContinuous( true ); controller.setNextAction( Action.FORWARD ); } }); runBackward = new NiceButton( "runBackward" ); runBackward.setLocation( 160, 60 ); runBackward.setMnemonic( KeyEvent.VK_R ); runBackward.setToolTipText( "Run backwards (alt + r)" ); runBackward.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { controller.setContinuous( true ); controller.setNextAction( Action.BACKWARD ); } }); options = new NiceButton( "settings" ); options.setLocation( 210, 60 ); options.setMnemonic( KeyEvent.VK_O ); options.setToolTipText( "Preferences (alt + o)" ); options.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { optionsDialog.setVisible( true ); } }); stepBackward = new NiceButton( "stepBackward" ); stepBackward.setLocation( 10, 60 ); stepBackward.setMnemonic( KeyEvent.VK_UP ); stepBackward.setToolTipText( "Backward step over (alt + up arrow key)" ); stepBackward.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { controller.setContinuous( false ); controller.setNextAction( Action.BACKWARD_OVER ); } }); stepBackwardInto = new NiceButton( "stepBackwardInto" ); stepBackwardInto.setLocation( 60, 60 ); stepBackwardInto.setMnemonic( KeyEvent.VK_LEFT ); stepBackwardInto.setToolTipText( "Backward step into (alt + left arrow key)" ); stepBackwardInto.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { controller.setContinuous( false ); controller.setNextAction( Action.BACKWARD ); } }); stepBackwardOut = new NiceButton( "stepBackwardOut" ); stepBackwardOut.setLocation( 110, 60 ); stepBackwardOut.setMnemonic( KeyEvent.VK_PAGE_UP ); stepBackwardOut.setToolTipText( "Backward step out (alt + page up)" ); stepBackwardOut.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { controller.setContinuous( false ); controller.setNextAction( Action.BACKWARD_OUT ); } }); pause = new NiceButton( "pause" ); pause.setLocation( 210, 10 ); pause.setMnemonic( KeyEvent.VK_PAUSE ); pause.setToolTipText( "Pause (alt + pause)" ); pause.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { controller.setContinuous( false ); } }); debug = new NiceButton( "debug" ); debug.setLocation( 360, 10 ); debug.setMnemonic( KeyEvent.VK_D ); debug.setToolTipText( "Show debug info (alt + d)" ); debug.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { showDebugInfo(); } }); randomGraph = new NiceButton( "random" ); randomGraph.setLocation( 360, 60 ); randomGraph.setMnemonic( KeyEvent.VK_G ); randomGraph.setToolTipText( "Generate random graph (alt + g)" ); randomGraph.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { randomDialog.setVisible( true ); } }); delayText = new JLabel( "Delay (ms)" ); delayText.setBounds( 260, 10, 80, 20 ); delay = new JTextField( String.valueOf(AnimationController.DEFAULT_DELAY) ); delay.setBounds( 260, 30, 90, 20 ); delay.getDocument().addDocumentListener( new DocumentListener() { @Override public void insertUpdate(DocumentEvent e) { try { controller.setDelay( Integer.parseInt( delay.getText() ) ); delay.setBackground( Color.WHITE ); } catch( Exception e1 ) { delay.setBackground( Color.RED ); } } @Override public void removeUpdate(DocumentEvent e) { try { controller.setDelay( Integer.parseInt( delay.getText() ) ); delay.setBackground( Color.WHITE ); } catch( Exception e1 ) { delay.setBackground( Color.RED ); } } @Override public void changedUpdate(DocumentEvent e) { try { controller.setDelay( Integer.parseInt( delay.getText() ) ); delay.setBackground( Color.WHITE ); } catch( Exception e1 ) { delay.setBackground( Color.RED ); } } }); load = new NiceButton( "load" ); load.setLocation( 260, 60 ); load.setMnemonic( KeyEvent.VK_L ); load.setToolTipText( "Load a graph (alt + l)" ); load.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JFileChooser chooser = new JFileChooser(); chooser.setFileFilter( new FileNameExtensionFilter("Json Graph", "json") ); chooser.showOpenDialog( frame ); if( chooser.getSelectedFile() != null ) { Reader r = new Reader( chooser.getSelectedFile().getAbsolutePath() ); LayeredGraphNode graph = r.readInputGraph(); InitializeNodePositions.placeNodes( graph ); new MainView( graph ); } } }); save = new NiceButton( "save" ); save.setLocation( 310, 60 ); save.setMnemonic( KeyEvent.VK_S ); save.setToolTipText( "Save graph (alt + s)" ); save.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JFileChooser chooser = new JFileChooser(); chooser.setFileFilter( new FileNameExtensionFilter("Json Graph", "json") ); chooser.showSaveDialog( frame ); if( chooser.getSelectedFile() != null ) { Writer w = new Writer( chooser.getSelectedFile().getAbsolutePath() ); w.writeOutputGraph( graph ); } } }); pseudoTree = new JTree(); pseudoTree.setBackground(RenderHelper.BACKGROUND_COLOR); PseudoCodeNode tree = algorithm.createPseudocodeTree( pseudoTree ); tree.setController( controller ); pseudoTree.setModel( new DefaultTreeModel( tree ) ); pseudoTree.setCellRenderer( new PseudoCodeRenderer() ); pseudoTree.setSelectionModel( null ); pseudoTree.addMouseListener( new MouseAdapter() { public void mousePressed(MouseEvent e) { TreePath selPath = pseudoTree.getPathForLocation(e.getX(), e.getY()); if( selPath != null && e.getClickCount() == 3 ) { ((PseudoCodeNode)selPath.getLastPathComponent()).setBreakPoint( !((PseudoCodeNode)selPath.getLastPathComponent()).hasBreakPoint() ); if( !pseudoTree.isExpanded( selPath ) ) { pseudoTree.collapsePath( selPath ); pseudoTree.expandPath( selPath ); } else { pseudoTree.expandPath( selPath ); pseudoTree.collapsePath( selPath ); } pseudoTree.repaint(); frame.repaint(); } } } ); pseudoTree.setRowHeight(15); JScrollPane treeView = new JScrollPane( pseudoTree ); treeView.setBounds( 10, 110, 390, 380 ); JTextArea debugText = new JTextArea(); debugText.setFont( new Font( "Monospaced", Font.PLAIN, 12 ) ); debugText.setEditable( false ); debugText.setBackground( RenderHelper.BACKGROUND_COLOR ); debugText.setForeground( RenderHelper.FOREGROUND_COLOR ); JScrollPane debugView = new JScrollPane( debugText ); debugView.setBounds( treeView.getX(), treeView.getY() + 500, treeView.getWidth(), 250 ); frame.setSize( (int)graph.getWidth( LayoutType.TOP_BOTTOM_LEFT ) * 2 + 575, (int)graph.getHeight( LayoutType.TOP_BOTTOM_LEFT ) * 2 + 200 ); frame.setLocation( 100, 100 ); frame.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE ); frame.setVisible( true ); JLayeredPane layne = new JLayeredPane(); layne.setLayout( new BorderLayout() ); JPanel pl = new JPanel(); GridLayout grout = new GridLayout( 2, 2, 10, 10 ); pl.setLayout( grout ); pl.setLocation( 0, 0 ); pl.setSize( frame.getSize() ); NodeView topLeft = createNodeView( graph, LayoutType.TOP_BOTTOM_LEFT ); NodeView topRight = createNodeView( graph, LayoutType.TOP_BOTTOM_RIGHT ); NodeView bottomLeft = createNodeView( graph, LayoutType.BOTTOM_TOP_LEFT ); NodeView bottomRight = createNodeView( graph, LayoutType.BOTTOM_TOP_RIGHT ); pl.add( topLeft ); pl.add( topRight ); pl.add( bottomLeft ); pl.add( bottomRight ); layne.add( pl, 1 ); NodeView combined = createNodeView( graph, LayoutType.COMBINED ); combined.setSize( 500, 500 ); layne.add( combined, 0 ); JSplitPane spane = new JSplitPane( JSplitPane.HORIZONTAL_SPLIT ); spane.setLeftComponent( layne ); spane.setResizeWeight(0.5); //JPanel onlyCurrentLayout = new JPanel(); //onlyCurrentLayout.setLayout( new BorderLayout() ); JPanel menue = new JPanel(); menue.setLayout( null ); menue.setPreferredSize( new Dimension( 410, 500 ) ); menue.add( stepForward ); menue.add( stepForwardInto ); menue.add( stepForwardOut ); menue.add( runForward ); menue.add( pause ); menue.add( debug ); menue.add( stepBackward ); menue.add( delayText ); menue.add( delay ); menue.add( stepBackwardInto ); menue.add( stepBackwardOut ); menue.add( runBackward ); menue.add( randomGraph ); menue.add( save ); menue.add( load ); menue.add( options ); JSplitPane spane2 = new JSplitPane( JSplitPane.VERTICAL_SPLIT ); spane2.setBounds( 10, 110, 390, 650 ); spane2.setTopComponent( treeView ); spane2.setBottomComponent( debugView ); spane2.setDividerLocation( 390 ); spane2.setResizeWeight(0.5); menue.add( spane2 ); spane.setRightComponent( menue); spane.setContinuousLayout(true); spane.setDividerLocation( frame.getWidth() - 430 ); spane.addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { frame.getComponentListeners()[ 0 ].componentResized( null ); } }); frame.add( spane ); frame.setSize( frame.getWidth() + 1, frame.getHeight() ); frame.setSize( frame.getWidth() - 1, frame.getHeight() ); frame.validate(); frame.repaint(); frame.addComponentListener(new ComponentAdapter() { public void componentResized(ComponentEvent evt) { menue.setSize( menue.getWidth(), layne.getHeight() ); spane2.setSize( menue.getWidth() - 20, menue.getHeight() - 120 ); if( graph.getColor( LayoutType.COMBINED ) == null ) { grout.setHgap( 10 ); grout.setVgap( 10 ); } else { grout.setHgap( layne.getWidth() / 3 ); grout.setVgap( layne.getHeight() / 3 ); } combined.setSize( layne.getWidth() / 3, layne.getHeight() / 3 ); combined.setLocation( layne.getWidth() / 3, layne.getHeight() / 3 ); debugText.setText( algorithm.getDebugString().trim() ); layne.remove( pl ); layne.add( pl, 1 ); if( optionsDialog != null && optionsDialog.getLayerDisplayOption() == 1 ) { pl.remove( topLeft ); pl.remove( topRight ); pl.remove( bottomLeft ); pl.remove( bottomRight ); pl.remove( combined ); switch( algorithm.getAlgorithmState() ) { case CONFLICTS: pl.add( topLeft ); break; case LAYOUT1: pl.add( topLeft ); break; case LAYOUT2: pl.add( topRight ); break; case LAYOUT3: pl.add( bottomLeft ); break; case LAYOUT4: pl.add( bottomRight ); break; case COMBINE: pl.add( combined ); break; } pl.revalidate(); } treeView.revalidate(); frame.repaint(); } }); optionsDialog = new OptionsDialog(); optionsDialog.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { pl.remove( topLeft ); pl.remove( topRight ); pl.remove( bottomLeft ); pl.remove( bottomRight ); pl.remove( combined ); layne.remove( combined ); controller.setStepOption( optionsDialog.getRunStepsOption() ); if( optionsDialog.getLayerDisplayOption() == 0 ) { pl.setLayout( grout ); pl.add( topLeft ); pl.add( topRight ); pl.add( bottomLeft ); pl.add( bottomRight ); layne.add( combined, 0 ); pl.revalidate(); } else { pl.setLayout( new BorderLayout() ); switch( algorithm.getAlgorithmState() ) { case CONFLICTS: pl.add( topLeft ); break; case LAYOUT1: pl.add( topLeft ); break; case LAYOUT2: pl.add( topRight ); break; case LAYOUT3: pl.add( bottomLeft ); break; case LAYOUT4: pl.add( bottomRight ); break; case COMBINE: pl.add( combined ); break; } pl.revalidate(); } frame.repaint(); } }); algorithm.start(); } private NodeView createNodeView( LayeredGraphNode gNode, LayoutType lt ) { NodeView graphView = new NodeView( gNode, lt ); ((LayeredNode)gNode).setView( graphView, lt ); graphView.setLayout( null ); graphView.setOpaque( true ); for( LayeredGraphNode n : gNode.getContainedNodes() ) { NodeView nv = createNodeView( n, lt ); nv.setBounds( nv.getX(), nv.getY(), nv.getWidth(), nv.getHeight() ); graphView.add( nv ); } for( LayeredGraphEdge e : gNode.getContainedEdges() ) { EdgeView ev = new EdgeView( e, lt ); ev.setOpaque( true ); graphView.add( ev ); } return graphView; } }