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