package ca.bcgsc.abyssexplorer.gui; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Random; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import ca.bcgsc.abyssexplorer.graph.AbyssGraph; import ca.bcgsc.abyssexplorer.graph.ContigLabel; import ca.bcgsc.abyssexplorer.graph.Edge; import ca.bcgsc.abyssexplorer.graph.Vertex; import ca.bcgsc.abyssexplorer.parsers.GraphLoader; import edu.uci.ics.jung.algorithms.layout.KKLayout; import edu.uci.ics.jung.algorithms.layout.util.VisRunner; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; import edu.uci.ics.jung.visualization.layout.PersistentLayoutImpl; /** * Main class for the ABySS-Explorer application. * Controls communication between all GUI * components and the underlying graph * data structure. * * @author Cydney Nielsen * @author Jason Chang * */ public class AbyssExplorer { protected JFrame rootFrame; protected final int minWidth = 800; protected final int minHeight = 600; // GUI components protected AbyssVisualizationViewer vv; protected Menu menu; protected ControlPanel panel; protected OutputPanel output; // Data file handling protected GraphLoader graphLoader; // Keep track of the graph display protected boolean activeDisplay = false; protected void launch() { String title = "ABySS-Explorer"; rootFrame = new JFrame(title); Toolkit tk = Toolkit.getDefaultToolkit(); Dimension screen = tk.getScreenSize(); screen.height = Math.max(0, screen.height); screen.width = Math.max(0, screen.width); rootFrame.setPreferredSize(new Dimension(screen.width, screen.height)); addGraphLoader(); addMenu(); addControlPanel(); addOutputPanel(); // to control frame resizing rootFrame.addComponentListener(new ComponentListener() { public void componentHidden(ComponentEvent e) { } public void componentMoved(ComponentEvent e) { } public void componentResized(ComponentEvent e) { Dimension d = rootFrame.getSize(); if (d.getWidth() < minWidth || d.getHeight() < minHeight) { rootFrame.setSize(new Dimension(minWidth,minHeight)); } } public void componentShown(ComponentEvent e) { } }); rootFrame.pack(); rootFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); rootFrame.setVisible(true); } /** * Initialize graph loader and hook-up necessary * listeners */ protected void addGraphLoader() { graphLoader = new GraphLoader(); graphLoader.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { // update the panel text with graph loader status output.setText(graphLoader.getStatus(), "regular"); if (graphLoader.getStatus().equals("complete")) { AbyssGraph g = graphLoader.getParsedGraph(); try { initializeGraph(g); // set the parser to null } catch (IllegalArgumentException ex) { JOptionPane.showMessageDialog(new JFrame(), ex, "ERROR", JOptionPane.ERROR_MESSAGE); } } } }); } /** * Builds the Menu component and hooks up all * necessary listeners */ protected void addMenu() { menu = new Menu(); // listeners for menu items menu.addOpenListener(new ActionListener() { public void actionPerformed(ActionEvent e) { File f = menu.chooseFileToOpen(); if (f == null) { return; } try { graphLoader.load(f);; } catch (IOException ex) { JOptionPane.showMessageDialog(new JFrame(), ex, "ERROR", JOptionPane.ERROR_MESSAGE); } } }); /*menu.addExportListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (vv == null) { return; } File f = menu.chooseExportFile(); if (f == null) { return; } try { FileOutputStream outputStream = new FileOutputStream(f); // Create a new document with bounding box 0 <= x <= width and // 0 <= y <= height. EpsGraphics2D g = new EpsGraphics2D("Abyss-Explorer", outputStream, 0, 0, vv.getWidth(),vv.getHeight()); vv.paint(g); // Flush and close the document (don't forget to do this!) g.flush(); g.close(); } catch (FileNotFoundException e1) { JOptionPane.showMessageDialog(new JFrame(), e1, "ERROR", JOptionPane.ERROR_MESSAGE); } catch (IOException e2) { JOptionPane.showMessageDialog(new JFrame(), e2, "ERROR", JOptionPane.ERROR_MESSAGE); } } });*/ menu.addQuitListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(0); } }); rootFrame.getContentPane().add(menu.getMenuBar(), BorderLayout.PAGE_START); } /** * Builds the Control Panel component and hooks up all * necessary listeners */ protected void addControlPanel() { panel = new ControlPanel(); // listeners for mouse-over in the inbound paired-end partner list panel.addInPartnerListener(new MouseMotionListener() { public void mouseMoved(MouseEvent e) { String pePartner = panel.selectInPartnerListItem(e); if (activeDisplay) { vv.setInHighlightedPartner(pePartner); vv.repaint(); } } public void mouseDragged(MouseEvent e) {} }); panel.addInPartnerListener(new MouseAdapter() { public void mouseExited(MouseEvent e) { panel.clearInSelectedPartner(); if (activeDisplay) { vv.setInHighlightedPartner(null); vv.repaint(); } } }); // listeners for mouse-over in the outbound paired-end partner list panel.addOutPartnerListener(new MouseMotionListener() { public void mouseMoved(MouseEvent e) { String pePartner = panel.selectOutPartnerListItem(e); if (activeDisplay) { vv.setOutHighlightedPartner(pePartner); vv.repaint(); } } public void mouseDragged(MouseEvent e) {} }); panel.addOutPartnerListener(new MouseAdapter() { public void mouseExited(MouseEvent e) { panel.clearOutSelectedPartner(); if (activeDisplay) { vv.setOutHighlightedPartner(null); vv.repaint(); } } }); // listeners for paired-end contig ID list panel.addPeListListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { String peLabel = panel.selectPeListItem(e); if (peLabel != null) { vv.selectPath(peLabel); } } }); panel.addPeListToggleListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { boolean pOn = panel.setPeListToggle(e); if (pOn) { vv.activatePePathDisplay(); } else { vv.deactivatePePathDisplay(); } updatePathInfo(); // refresh path list vv.repaint(); } }); // listener for paired-end partner list panel.addPartnerToggleListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { boolean pOn = panel.setPePartnerListToggle(e); if (pOn) { vv.activatePartnerDisplay(); } else { vv.deactivatePartnerDisplay(); } updatePartnerInfo(); // refresh partner list vv.repaint(); } }); // listeners for length control panel.addLenToggleListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { boolean lenOn = panel.setLenToggle(e); if (activeDisplay) { if (lenOn) { vv.turnContigLenOn(); } else { vv.turnContigLenOff(); } } vv.repaint(); } }); panel.addLenSliderListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { int value = panel.getLenSliderValue(); vv.setLenScale(value); vv.repaint(); } }); // listener for edge label control panel.addLabelToggleListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if (activeDisplay) { if (e.getStateChange() == ItemEvent.DESELECTED) { vv.turnContigLabelsOff(); } else { vv.turnContigLabelsOn(); } } vv.repaint(); } }); // listeners for search box panel.addSearchMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { panel.clearSearch(); } }); panel.addSearchListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { if (activeDisplay == false) { output.setText("Please first open a DOT file.", "regular"); } else { String eLabel = panel.getQueryId(); AbyssGraph g = vv.getGraph(); try { if (g.hasEdge(eLabel)) { Edge edge = g.getEdge(eLabel); vv.setVisible(true); PersistentLayoutImpl pLayout = getQueryLayout(g,edge); vv.setGraphLayout(pLayout); vv.selectEdge(edge); } } catch (IllegalArgumentException e1) { JOptionPane.showMessageDialog(new JFrame(), e1, "ERROR", JOptionPane.ERROR_MESSAGE); } catch (IndexOutOfBoundsException e2) { if (g.hasPeContig(eLabel)) { vv.setVisible(true); ContigLabel peLabel = new ContigLabel(eLabel); // seed the graph layout on the first single-end contig in this path Edge firstEdge = g.getPairedEndContigMembers(peLabel).get(0); PersistentLayoutImpl pLayout = getQueryLayout(g,firstEdge); vv.setGraphLayout(pLayout); // only list this paired-end contig in the Control Panel list List mLabels = new ArrayList(1); mLabels.add(peLabel); panel.clear(); panel.setPeListToggleOn(); panel.setPeList(mLabels); panel.setSelectedPeId(eLabel); vv.selectQueryPath(peLabel); } else { output.setText("No contig with id: '" + eLabel + "'", "regular"); vv.setVisible(false); return; } } } } catch (Exception ex) { JOptionPane.showMessageDialog(new JFrame(), ex, "ERROR", JOptionPane.ERROR_MESSAGE); } } }); rootFrame.getContentPane().add(panel.getControlPanel(), BorderLayout.LINE_END); } /** * Builds the Output Panel component */ public void addOutputPanel() { output = new OutputPanel(); output.getScrollPane().setMinimumSize(new Dimension(500,200)); rootFrame.getContentPane().add(output.getScrollPane(), BorderLayout.PAGE_END); } protected PersistentLayoutImpl getRandomQueryLayout(AbyssGraph g) { Random generator = new Random(); Integer seed = generator.nextInt(g.getEdgeCount()); return getQueryLayout(g, seed); } protected PersistentLayoutImpl getQueryLayout(AbyssGraph g, int i) { return getQueryLayout(g, g.getEdge(i)); } protected PersistentLayoutImpl getQueryLayout(AbyssGraph g, Edge e) { DirectedSparseMultigraph subGraph = getSubGraph(g,e); KKLayout layout = new KKLayout(subGraph); layout.setMaxIterations(40); PersistentLayoutImpl pLayout = new PersistentLayoutImpl(layout); return pLayout; } protected DirectedSparseMultigraph getSubGraph(AbyssGraph g, Edge e) { DirectedSparseMultigraph subGraph = g.getQuerySubgraph(e, 60); return subGraph; } /** * Use edge selection state from the VisualizationViewer to update * GUI component displays. */ protected void updateEdgeInfo() { output.clear(); String selectedEdge = vv.getSelectedEdgeString(); if (selectedEdge == null) { return; } output.setText(selectedEdge, vv.getSelectedEdgeFormats()); output.getScrollPane().repaint(); } protected void updatePartnerInfo() { // only modify the partner list if toggle is on if (panel.partnerListOn()) { List iLabels = vv.getInboundPartnerLabels(); if (iLabels != null) { panel.setInboundList(iLabels); } else { panel.clearInboundList(); } List oLabels = vv.getOutboundPartnerLabels(); if (oLabels != null) { panel.setOutboundList(oLabels); } else { panel.clearOutboundList(); } panel.getControlPanel().repaint(); } vv.repaint(); } protected void updatePathInfo() { updateEdgeInfo(); if (panel.peListOn()) { String selectedPath = vv.getSelectedPathLabel(); if (selectedPath == null) { panel.clearPeList(); vv.repaint(); return; } panel.setPeList(vv.getPossiblePathLabels()); panel.setSelectedPeId(selectedPath); output.addText(vv.getSelectedPathString(), vv.getSelectedPathStringFormats()); } vv.repaint(); output.getScrollPane().repaint(); } /** * Called once the graph parse is complete. Generates * a layout for a randomly seeded subgraph, and sets * a graph layout and mouse listeners * @param g */ protected void initializeGraph(AbyssGraph g) { PersistentLayoutImpl pLayout = getQueryLayout(g, 0); if (vv == null) { Dimension d = rootFrame.getPreferredSize(); pLayout.setSize(new Dimension(d.width - 300, d.height-200)); vv = new AbyssVisualizationViewer(pLayout); vv.setGraph(g); panel.enableComponenents(); rootFrame.getContentPane().add(vv); rootFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); vv.addGraphMouseChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { if (!vv.isEdgeSelected()) { panel.clear(); output.clear(); } else { // always clear the search panel on a graph mouse click panel.readyForSearch(); } } }); // edge selection can be changed via mouse or search box, so listen directly to state class vv.addEdgeItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { updateEdgeInfo(); } }); // partners can be changed via mouse or search box, so listen directly to state class vv.addPartnerChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { updatePartnerInfo(); } }); // path can be changed via mouse or search box, so listen directly to state class vv.addPathChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { updatePathInfo(); } }); vv.addModelChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { PersistentLayoutImpl l = (PersistentLayoutImpl) vv.getGraphLayout(); KKLayout trueLayout = (KKLayout) l.getDelegate(); // only display vv if layout calculation is complete if (trueLayout.done() && !activeDisplay) { // initial display stopLayout(); rootFrame.setVisible(true); activeDisplay = true; } else if (trueLayout.done()) { // subsequent display updates stopLayout(); } } }); } else { vv.setGraphLayout(pLayout); vv.setGraph(g); } } /** * turn off the layout thread */ protected void stopLayout() { VisRunner relaxer = (VisRunner) vv.getModel().getRelaxer(); relaxer.stop(); } protected void checkJavaVersion() { String version = System.getProperty("java.version"); if (version.compareTo("1.5") < 0) { String m = "It is recommended that you upgrade to Java 1.5 "; m += "(you are running Java " + version + ")"; JOptionPane.showMessageDialog(new JFrame(), m, "Warning", JOptionPane.WARNING_MESSAGE); } } public static void main(String[] args) throws IOException { try { // Set cross-platform Java L&F (also called "Metal") UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (UnsupportedLookAndFeelException e) { // handle exception } catch (ClassNotFoundException e) { // handle exception } catch (InstantiationException e) { // handle exception } catch (IllegalAccessException e) { // handle exception } AbyssExplorer e = new AbyssExplorer(); e.checkJavaVersion(); e.launch(); } }