1 /* 2 * Copyright (C) 2009 Genome Research Limited 3 * 4 * This program is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU General Public License 6 * as published by the Free Software Foundation; either version 2 7 * of the License, or (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program; if not, write to the Free Software 16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 17 * 18 */ 19 package uk.ac.sanger.artemis.circular.digest; 20 21 import uk.ac.sanger.artemis.Entry; 22 import uk.ac.sanger.artemis.EntryGroup; 23 import uk.ac.sanger.artemis.Options; 24 import uk.ac.sanger.artemis.SimpleEntryGroup; 25 import uk.ac.sanger.artemis.circular.Block; 26 import uk.ac.sanger.artemis.circular.DNADraw; 27 import uk.ac.sanger.artemis.components.ArtemisMain; 28 import uk.ac.sanger.artemis.components.EntryEdit; 29 import uk.ac.sanger.artemis.components.EntryFileDialog; 30 import uk.ac.sanger.artemis.components.MessageDialog; 31 import uk.ac.sanger.artemis.components.StickyFileChooser; 32 import uk.ac.sanger.artemis.io.EntryInformation; 33 import uk.ac.sanger.artemis.io.Range; 34 import uk.ac.sanger.artemis.sequence.NoSequenceException; 35 import uk.ac.sanger.artemis.util.Document; 36 import uk.ac.sanger.artemis.util.DocumentFactory; 37 import uk.ac.sanger.artemis.util.OutOfRangeException; 38 39 import java.awt.Color; 40 import java.awt.Cursor; 41 import java.awt.Dimension; 42 import java.awt.FlowLayout; 43 44 import java.awt.event.ActionEvent; 45 import java.awt.event.ActionListener; 46 import java.awt.event.MouseAdapter; 47 import java.awt.event.MouseEvent; 48 import java.awt.event.MouseListener; 49 import java.awt.event.MouseMotionAdapter; 50 import java.awt.event.MouseMotionListener; 51 import java.awt.event.WindowAdapter; 52 import java.awt.event.WindowEvent; 53 import java.io.File; 54 import java.io.FileNotFoundException; 55 import java.io.FileReader; 56 import java.io.IOException; 57 import java.io.InputStream; 58 import java.util.List; 59 import java.util.Vector; 60 61 import javax.swing.Box; 62 import javax.swing.JCheckBox; 63 import javax.swing.JFrame; 64 import javax.swing.JMenu; 65 import javax.swing.JMenuBar; 66 import javax.swing.JMenuItem; 67 import javax.swing.JOptionPane; 68 import javax.swing.JPanel; 69 import javax.swing.JPopupMenu; 70 import javax.swing.JScrollPane; 71 import javax.swing.JSplitPane; 72 import javax.swing.JTabbedPane; 73 import javax.swing.JTextField; 74 75 /** 76 * 77 */ 78 public class CircularGenomeController 79 { 80 private Block lastBlock = null; 81 private JPanel gelPanel; 82 private int hgt; 83 private JFrame frame = new JFrame(); 84 private boolean methylation = false; 85 CircularGenomeController()86 public CircularGenomeController() 87 { 88 } 89 90 /** 91 * Create in-silico Pulse Field Gel Electrophoresis from a restriction enzyme 92 * digest and draw alongside DNAPlotter 93 * @param enzymes 94 * @param sequenceFiles 95 * @param restrictOutputs 96 * @param methylation if true then RE recognition sites will not match methylated bases 97 * @throws Exception 98 */ setup(String enzymes, List<File> sequenceFiles, List<File> restrictOutputs, boolean methylation)99 protected void setup(String enzymes, 100 List<File> sequenceFiles, 101 List<File> restrictOutputs, 102 boolean methylation) 103 throws Exception 104 { 105 this.methylation = methylation; 106 // add each sequence file to a different entry group 107 List<EntryGroup> entries = new Vector<EntryGroup>(); 108 if (sequenceFiles != null && sequenceFiles.size() > 0) 109 { 110 for (int i = 0; i < sequenceFiles.size(); i++) 111 { 112 EntryGroup entryGroup = getEntryGroupFromFile(sequenceFiles.get(i)); 113 entries.add(entryGroup); 114 } 115 } 116 else 117 { 118 EntryGroup entryGroup = getEntryGroupFromFile(null); 119 File sequenceFile = new File(((File) entryGroup.getSequenceEntry() 120 .getRootDocument().getLocation()).getAbsolutePath() 121 + File.separator + entryGroup.getSequenceEntry().getName()); 122 123 if(sequenceFiles == null) 124 sequenceFiles = new Vector<File>(); 125 sequenceFiles.add(sequenceFile); 126 entries.add(entryGroup); 127 } 128 129 if (enzymes == null) 130 enzymes = promptForEnzymes(); 131 132 // run restrict 133 if(restrictOutputs == null) 134 { 135 restrictOutputs = new Vector<File>(sequenceFiles.size()); 136 for (int i = 0; i < sequenceFiles.size(); i++) 137 { 138 File sequenceFile = sequenceFiles.get(i); 139 File restrictOutput = File.createTempFile("restrict_" 140 + sequenceFile.getName(), ".txt"); 141 restrictOutputs.add(restrictOutput); 142 runEmbossRestrict(sequenceFile.getAbsolutePath(), enzymes, 143 restrictOutput); 144 } 145 } 146 drawResults(restrictOutputs, entries, sequenceFiles, enzymes); 147 } 148 149 /** 150 * Run the EMBOSS application restrict. This uses the EMBOSS_ROOT property to 151 * define the location of EMBOSS. 152 * @param fileName 153 * @param cgcb 154 * @param restrictOutput 155 * @throws IOException 156 * @throws InterruptedException 157 */ runEmbossRestrict(final String fileName, final String enzymes, final File restrictOutput)158 private void runEmbossRestrict(final String fileName, 159 final String enzymes, 160 final File restrictOutput) throws IOException, InterruptedException 161 { 162 String[] args = { 163 System.getProperty("EMBOSS_ROOT") + "/bin/restrict", fileName, "-auto", 164 "-limit", "y", "-enzymes", enzymes, 165 methylation ? "-methylation" : "", 166 "-out", 167 restrictOutput.getCanonicalPath() }; 168 169 ProcessBuilder pb = new ProcessBuilder(args); 170 pb.redirectErrorStream(true); 171 Process p = pb.start(); 172 System.err.print("** Running restrict"); 173 try 174 { 175 InputStream is = p.getInputStream(); 176 int inchar; 177 while ((inchar = is.read()) != -1) 178 { 179 char c = (char) inchar; 180 System.err.print(c); 181 } 182 System.err.println("**"); 183 p.waitFor(); 184 System.err.println("Process exited with '" + p.exitValue() + "'"); 185 } 186 catch (InterruptedException exp) 187 { 188 exp.printStackTrace(); 189 } 190 p.waitFor(); 191 } 192 193 /** 194 * Display the result in DNAPlotter and the virtual digest. 195 * 196 * @param restrictOutput 197 * @param entryGroup 198 * @param fileName 199 * @param cgcb 200 * @throws FileNotFoundException 201 * @throws IOException 202 */ drawResults(final List<File> restrictOutputs, final List<EntryGroup> entries, final List<File> sequenceFiles, final String enzymes)203 private void drawResults(final List<File> restrictOutputs, 204 final List<EntryGroup> entries, final List<File> sequenceFiles, 205 final String enzymes) throws FileNotFoundException, IOException 206 { 207 JTabbedPane tabbedPane = new JTabbedPane(); 208 gelPanel= new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0)); 209 Dimension preferredSize = null; 210 for (int i = 0; i < entries.size(); i++) 211 { 212 final ReportDetails rd = Utils.findCutSitesFromEmbossReport( 213 new FileReader(restrictOutputs.get(i).getCanonicalPath())); 214 215 if(rd.cutSites.size() == 0) 216 { 217 JOptionPane.showMessageDialog(null, 218 "No cut site found for "+sequenceFiles.get(i).getName(), 219 "RE Digest Results", JOptionPane.INFORMATION_MESSAGE); 220 } 221 222 final DNADraw dna = Utils.createDNADrawFromReportDetails( 223 rd, entries.get(i)); 224 tabbedPane.add(sequenceFiles.get(i).getName(), dna); 225 226 hgt = dna.getHeight(); 227 final InSilicoGelPanel inSilicoGelPanel = new InSilicoGelPanel(rd.length, 228 rd.cutSites, hgt, restrictOutputs.get(i), 229 sequenceFiles.get(i).getName()); 230 gelPanel.add(inSilicoGelPanel); 231 preferredSize = inSilicoGelPanel.getPreferredSize(); 232 addMouseListener(rd, dna, inSilicoGelPanel); 233 } 234 235 frame.setTitle ("Sandpiper :: "+enzymes); 236 addMenuBar(frame); 237 Dimension d = frame.getToolkit().getScreenSize(); 238 239 JScrollPane jspGel = new JScrollPane(gelPanel); 240 241 Dimension dgel = new Dimension( 242 preferredSize.width* (entries.size()>1 ? 2 : 1), preferredSize.height); 243 jspGel.setPreferredSize(dgel); 244 gelPanel.setMinimumSize(dgel); 245 gelPanel.setBackground(Color.white); 246 247 JScrollPane jspTabbedPane = new JScrollPane(tabbedPane); 248 JSplitPane mainPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, false, 249 jspGel, jspTabbedPane); 250 mainPanel.setBackground(Color.white); 251 252 JScrollPane jsp = new JScrollPane(mainPanel); 253 jsp.getViewport().setBackground(Color.white); 254 frame.getContentPane().add(jsp); 255 256 frame.pack(); 257 frame.setLocation(((int) d.getWidth() - frame.getWidth()) / 4, 258 ((int) d.getHeight() - frame.getHeight()) / 2); 259 frame.setVisible(true); 260 } 261 262 /** 263 * Add the menu bar 264 * 265 * @param f 266 */ addMenuBar(final JFrame f)267 private void addMenuBar(final JFrame f) 268 { 269 f.addWindowListener(new WindowAdapter() 270 { 271 public void windowClosing(WindowEvent event) 272 { 273 exitApp(f); 274 } 275 }); 276 277 JMenuBar menuBar = new JMenuBar(); 278 279 JMenu fileMenu = new JMenu("File"); 280 menuBar.add(fileMenu); 281 282 JMenuItem loadExpData = new JMenuItem("Load experimental data..."); 283 loadExpData.addActionListener(new ActionListener() 284 { 285 public void actionPerformed(ActionEvent e) 286 { 287 StickyFileChooser fileChooser = new StickyFileChooser(); 288 int ret = fileChooser.showOpenDialog(null); 289 if(ret == StickyFileChooser.CANCEL_OPTION) 290 return; 291 292 File expFile = fileChooser.getSelectedFile(); 293 FileReader reader; 294 try 295 { 296 reader = new FileReader(expFile); 297 final List<FragmentBand> bands = Utils.findCutSitesFromExperiment(reader); 298 final InSilicoGelPanel inSilicoGelPanel = new InSilicoGelPanel( 299 bands, hgt, expFile, expFile.getName()); 300 gelPanel.add(inSilicoGelPanel); 301 frame.validate(); 302 } 303 catch (FileNotFoundException e1) 304 { 305 // TODO Auto-generated catch block 306 e1.printStackTrace(); 307 } 308 catch (IOException e2) 309 { 310 // TODO Auto-generated catch block 311 e2.printStackTrace(); 312 } 313 } 314 }); 315 fileMenu.add(loadExpData); 316 fileMenu.addSeparator(); 317 318 JMenuItem exitMenu = new JMenuItem("Exit"); 319 exitMenu.addActionListener(new ActionListener() 320 { 321 public void actionPerformed(ActionEvent e) 322 { 323 exitApp(f); 324 } 325 }); 326 fileMenu.add(exitMenu); 327 328 f.setJMenuBar(menuBar); 329 } 330 331 /** 332 * 333 * @param f 334 */ exitApp(JFrame f)335 private void exitApp(JFrame f) 336 { 337 f.dispose(); 338 System.exit(0); 339 } 340 341 /** 342 * Add mouse lister to highlight bands on the virtual digest when the mouse is 343 * over the corresponding feature in the circular plot. 344 * 345 * @param rd 346 * @param dna 347 * @param inSilicoGelPanel 348 */ addMouseListener(final ReportDetails rd, final DNADraw dna, final InSilicoGelPanel inSilicoGelPanel)349 private void addMouseListener(final ReportDetails rd, final DNADraw dna, 350 final InSilicoGelPanel inSilicoGelPanel) 351 { 352 final JPopupMenu popup = new JPopupMenu(); 353 final JMenuBar menuBar = dna.createMenuBar(); 354 355 JMenu[] menus = new JMenu[menuBar.getMenuCount()]; 356 for (int i = 0; i < menuBar.getMenuCount(); i++) 357 menus[i] = menuBar.getMenu(i); 358 359 for (int i = 0; i < menus.length; i++) 360 popup.add(menus[i]); 361 362 final JMenuItem openArtemis = new JMenuItem("Open in Artemis..."); 363 openArtemis.addActionListener(new ActionListener() 364 { 365 public void actionPerformed(ActionEvent e) 366 { 367 Range range = null; 368 try 369 { 370 if (lastBlock == null) 371 range = new Range(1, dna.getArtemisEntryGroup().getBases() 372 .getLength()); 373 else 374 range = new Range(lastBlock.getBstart(), lastBlock.getBend()); 375 } 376 catch (OutOfRangeException e1) 377 { 378 e1.printStackTrace(); 379 return; 380 } 381 final ArtemisMain main_window = new ArtemisMain(null); 382 main_window.setVisible(false); 383 final EntryGroup entryGroup = dna.getArtemisEntryGroup().truncate( 384 range); 385 final EntryEdit entryEdit = new EntryEdit(entryGroup); 386 entryEdit.setVisible(true); 387 } 388 }); 389 390 popup.add(openArtemis); 391 392 MouseMotionListener mouseMotionListener = new MouseMotionAdapter() 393 { 394 public void mouseMoved(MouseEvent me) 395 { 396 List<CutSite> cutSites = rd.cutSites; 397 final Block b = dna.getBlockAtLocation(me.getPoint()); 398 if (b != null && b.isOverMe(me.getX(), me.getY())) 399 { 400 int bend = b.getBend(); 401 // int bstart = b.getBstart(); 402 403 if (bend == rd.length) 404 { 405 ((CutSite) cutSites.get(0)).setHighlighted(true); 406 for (int i = 1; i < cutSites.size(); i++) 407 ((CutSite) cutSites.get(i)).setHighlighted(false); 408 } 409 else 410 { 411 for (int i = 0; i < cutSites.size(); i++) 412 { 413 CutSite cutSite = (CutSite) cutSites.get(i); 414 if (bend == cutSite.getFivePrime()) 415 cutSite.setHighlighted(true); 416 else 417 cutSite.setHighlighted(false); 418 } 419 } 420 } 421 else 422 { 423 for (int i = 0; i < cutSites.size(); i++) 424 { 425 CutSite cutSite = (CutSite) cutSites.get(i); 426 cutSite.setHighlighted(false); 427 } 428 } 429 inSilicoGelPanel.repaint(); 430 } 431 }; 432 dna.addMouseMotionListener(mouseMotionListener); 433 434 MouseListener popupListener = new MouseAdapter() 435 { 436 public void mousePressed(MouseEvent e) 437 { 438 maybeShowPopup(e); 439 } 440 441 public void mouseReleased(MouseEvent e) 442 { 443 maybeShowPopup(e); 444 } 445 446 private void maybeShowPopup(MouseEvent e) 447 { 448 if (e.isPopupTrigger()) 449 { 450 lastBlock = dna.getBlockAtLocation(e.getPoint()); 451 452 if (lastBlock == null) 453 openArtemis.setText("Open in Artemis..."); 454 else 455 openArtemis.setText("Open in Artemis [" + lastBlock.getBstart() 456 + ".." + lastBlock.getBend() + "]..."); 457 458 popup.show(e.getComponent(), e.getX(), e.getY()); 459 } 460 } 461 }; 462 dna.addMouseListener(popupListener); 463 464 MouseMotionListener gelMouseMotionListener = new MouseMotionAdapter() 465 { 466 Block lastBlock = null; 467 Color lastBlockColour = null; 468 public void mouseMoved(MouseEvent me) 469 { 470 final FragmentBand band = inSilicoGelPanel.getBandAtLocation(me.getPoint()); 471 if(lastBlock != null) 472 { 473 lastBlock.setColour(lastBlockColour); 474 dna.repaint(); 475 } 476 if(band != null) 477 { 478 CutSite cs = band.bandCutSite; 479 lastBlock = dna.getBlockAtBasePosition(cs.getFivePrime()); 480 lastBlockColour = lastBlock.getColour(); 481 lastBlock.setColour(Color.GREEN); 482 dna.repaint(); 483 } 484 else 485 lastBlock = null; 486 } 487 }; 488 inSilicoGelPanel.addMouseMotionListener(gelMouseMotionListener); 489 } 490 491 /** 492 * Create a DNADraw panel from a file 493 * 494 * @param dna_current 495 * @return 496 */ getEntryGroupFromFile(File fileName)497 private static EntryGroup getEntryGroupFromFile(File fileName) 498 { 499 Options.getOptions(); 500 final EntryGroup entryGroup; 501 502 try 503 { 504 Entry entry = getEntry(fileName); 505 506 if (entry.getBases() != null) 507 entryGroup = new SimpleEntryGroup(entry.getBases()); 508 else 509 entryGroup = new SimpleEntryGroup(); 510 entryGroup.add(entry); 511 return entryGroup; 512 } 513 catch (OutOfRangeException e) 514 { 515 e.printStackTrace(); 516 } 517 catch (NoSequenceException e) 518 { 519 JOptionPane.showMessageDialog(null, "No sequence found!", 520 "Sequence Missing", JOptionPane.WARNING_MESSAGE); 521 } 522 return null; 523 } 524 525 /** 526 * Return an Artemis entry from a file 527 * 528 * @param entryFileName 529 * @param entryGroup 530 * @return 531 * @throws NoSequenceException 532 * @throws OutOfRangeException 533 */ getEntry(final File entryFileName)534 private static Entry getEntry(final File entryFileName) 535 throws NoSequenceException, OutOfRangeException 536 { 537 538 if (entryFileName == null) 539 { 540 // no file - prompt for a file 541 uk.ac.sanger.artemis.components.FileDialogEntrySource entrySource = new uk.ac.sanger.artemis.components.FileDialogEntrySource( 542 null, null); 543 Entry entry = entrySource.getEntry(true); 544 return entry; 545 } 546 547 final Document entry_document = DocumentFactory.makeDocument(entryFileName 548 .getAbsolutePath()); 549 final EntryInformation artemis_entry_information = Options 550 .getArtemisEntryInformation(); 551 552 final uk.ac.sanger.artemis.io.Entry new_embl_entry = EntryFileDialog 553 .getEntryFromFile(null, entry_document, artemis_entry_information, 554 false); 555 556 if (new_embl_entry == null) // the read failed 557 return null; 558 559 Entry entry = null; 560 try 561 { 562 entry = new Entry(new_embl_entry); 563 } 564 catch (OutOfRangeException e) 565 { 566 new MessageDialog(null, "read failed: one of the features in " 567 + entryFileName + " has an out of range " + "location: " 568 + e.getMessage()); 569 } 570 return entry; 571 } 572 573 /** 574 * Prompt for the enzyme list. 575 * @return 576 */ promptForEnzymes()577 private String promptForEnzymes() 578 { 579 Box yBox = Box.createVerticalBox(); 580 JTextField enzymeList = new JTextField("HincII,hinfI,ppiI,hindiii"); 581 yBox.add(enzymeList); 582 JCheckBox methylationCheckBox = new JCheckBox( 583 "RE sites will not match methylated bases", methylation); 584 yBox.add(methylationCheckBox); 585 586 JOptionPane.showMessageDialog(null, yBox, "Enzyme", JOptionPane.QUESTION_MESSAGE); 587 methylation = methylationCheckBox.isSelected(); 588 return enzymeList.getText().trim(); 589 } 590 main(String args[])591 public static void main(String args[]) 592 { 593 if (System.getProperty("EMBOSS_ROOT") == null) 594 { 595 String embossRoot = JOptionPane.showInputDialog(null, 596 "Input the EMBOSS installation directory", "/usr/local/emboss"); 597 System.setProperty("EMBOSS_ROOT", embossRoot.trim()); 598 } 599 600 String enzymes = null; 601 final CircularGenomeController controller = new CircularGenomeController(); 602 boolean methylation = false; 603 604 List<File> fileNames = null; 605 List<File> restrictOutputs = null; 606 if (args != null && args.length > 0) 607 { 608 if (args.length == 1) 609 { 610 if (args[0].startsWith("-h")) 611 { 612 System.out.println("-h\t\tshow help"); 613 System.out 614 .println("-enz\t\tcomma separated list of digest enzymes (optional)"); 615 System.out 616 .println("-seq\t\tspace separated list of sequences (optional)"); 617 System.out 618 .println("-methylation\tif this is set then RE recognition sites " 619 + "will not match methylated bases."); 620 System.out 621 .println("-restrict\tspace separated lists of EMBOSS restrict output " 622 + "in the same order as the sequences (optional)."); 623 System.exit(0); 624 } 625 fileNames = new Vector<File>(); 626 fileNames.add(new File(args[0])); 627 } 628 629 for (int i = 0; i < args.length; i++) 630 { 631 if (args[i].startsWith("-enz")) 632 enzymes = args[i + 1]; 633 else if (args[i].startsWith("-meth")) 634 methylation = true; 635 else if (args[i].startsWith("-seq")) 636 { 637 if (fileNames == null) 638 fileNames = new Vector<File>(); 639 640 for (int j = i + 1; j < args.length; j++) 641 { 642 if (args[j].startsWith("-")) 643 break; 644 fileNames.add(new File(args[j])); 645 } 646 } 647 else if (args[i].startsWith("-restrict")) 648 { 649 if (restrictOutputs == null) 650 restrictOutputs = new Vector<File>(); 651 652 for (int j = i + 1; j < args.length; j++) 653 { 654 if (args[j].startsWith("-")) 655 break; 656 restrictOutputs.add(new File(args[j])); 657 } 658 } 659 } 660 } 661 662 final FileSelectionPanel selectionPanel = new FileSelectionPanel(enzymes, 663 fileNames, restrictOutputs, methylation); 664 final JFrame f = new JFrame("Options and File Selection"); 665 ActionListener displayButtonListener = new ActionListener() 666 { 667 public void actionPerformed(ActionEvent e) 668 { 669 try 670 { 671 f.setCursor(new Cursor(Cursor.WAIT_CURSOR)); 672 if(selectionPanel.getEmbossRootField() != null) 673 System.getProperties().put("EMBOSS_ROOT", 674 selectionPanel.getEmbossRootField().getText().trim()); 675 controller.setup(selectionPanel.getEnzymes(), 676 selectionPanel.getSequenceFiles(), 677 selectionPanel.getRestrictOutputs(), 678 selectionPanel.isMethylation()); 679 f.dispose(); 680 } 681 catch (Exception ex) 682 { 683 ex.printStackTrace(); 684 } 685 finally 686 { 687 f.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); 688 } 689 } 690 }; 691 selectionPanel.showJFrame(f, displayButtonListener); 692 693 } 694 }; 695