/**
* Copyright Copyright 2010-14 Simon Andrews
*
* This file is part of BamQC.
*
* BamQC 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.
*
* BamQC 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 BamQC; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* Changelog:
* - Piero Dalle Pezze: Added genome annotation, Statusbar, improved menu, overall class improvement.
* - Simon Andrews: Class creation.
*/
package uk.ac.babraham.BamQC;
import java.awt.BorderLayout;
import java.awt.GraphicsEnvironment;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.filechooser.FileFilter;
import org.apache.log4j.Logger;
import uk.ac.babraham.BamQC.Analysis.AnalysisRunner;
import uk.ac.babraham.BamQC.Analysis.OfflineRunner;
import uk.ac.babraham.BamQC.Dialogs.ProgressTextDialog;
import uk.ac.babraham.BamQC.Dialogs.WelcomePanel;
import uk.ac.babraham.BamQC.Menu.BamQCMenuBar;
import uk.ac.babraham.BamQC.Modules.ModuleFactory;
import uk.ac.babraham.BamQC.Modules.QCModule;
import uk.ac.babraham.BamQC.Report.HTMLReportArchive;
import uk.ac.babraham.BamQC.Results.ResultsPanel;
import uk.ac.babraham.BamQC.Sequence.SequenceFactory;
import uk.ac.babraham.BamQC.Sequence.SequenceFile;
import uk.ac.babraham.BamQC.Sequence.SequenceFormatException;
import uk.ac.babraham.BamQC.Utilities.FileFilters.BAMFileFilter;
import uk.ac.babraham.BamQC.Utilities.FileFilters.GFFFileFilter;
import uk.ac.babraham.BamQC.Dialogs.GenomeSelector;
import uk.ac.babraham.BamQC.Network.GenomeDownloader;
import uk.ac.babraham.BamQC.DataTypes.ProgressListener;
import uk.ac.babraham.BamQC.Displays.StatusPanel;
/**
* @author Simon Andrews
* @author Piero Dalle Pezze
*
*/
public class BamQCApplication extends JFrame implements ProgressListener {
private static Logger log = Logger.getLogger(BamQCApplication.class);
private static BamQCApplication application;
private static final long serialVersionUID = -1761781589885333860L;
public static final String VERSION = "0.1.0_devel";
private BamQCMenuBar menu;
/** This is the small strip at the bottom of the main display */
private StatusPanel statusPanel;
private JTabbedPane fileTabs;
private WelcomePanel welcomePanel;
private File lastUsedDir = null;
private String title = "BamQC";
/** Flag to check if anything substantial has changed since the file was last loaded/saved. **/
private boolean changesWereMade = false;
public BamQCApplication () {
setTitle(title);
setIconImage(new ImageIcon(ClassLoader.getSystemResource("uk/ac/babraham/BamQC/Resources/bamqc_icon.png")).getImage());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// setSize(1280, 720);
setSize(800,600);
// setMinimumSize(new Dimension(660, 440));
setLocationRelativeTo(null);
menu = new BamQCMenuBar(this);
setJMenuBar(menu);
fileTabs = new JTabbedPane(JTabbedPane.TOP);
getContentPane().setLayout(new BorderLayout());
welcomePanel = new WelcomePanel();
getContentPane().add(welcomePanel,BorderLayout.CENTER);
statusPanel = new StatusPanel();
getContentPane().add(statusPanel,BorderLayout.SOUTH);
}
public int close () {
if (fileTabs.getSelectedIndex() >=0) {
fileTabs.remove(fileTabs.getSelectedIndex());
}
if (fileTabs.getTabCount() == 0) {
closeAll();
}
return fileTabs.getTabCount();
}
public void closeAll () {
fileTabs.removeAll();
getContentPane().remove(fileTabs);
getContentPane().add(welcomePanel,BorderLayout.CENTER);
validate();
repaint();
}
public boolean openFile () {
statusPanel.progressUpdated(" ", 0, 100);
JFileChooser chooser;
if (lastUsedDir == null) {
chooser = new JFileChooser();
}
else {
chooser = new JFileChooser(lastUsedDir);
}
// Open the view by details
// Action details = chooser.getActionMap().get("viewTypeDetails");
// details.actionPerformed(null);
chooser.setMultiSelectionEnabled(true);
BAMFileFilter bff = new BAMFileFilter();
// remove default "All Files" filter
chooser.removeChoosableFileFilter(chooser.getFileFilter());
chooser.addChoosableFileFilter(bff);
chooser.setFileFilter(bff);
int result = chooser.showOpenDialog(this);
if (result == JFileChooser.CANCEL_OPTION) return false;
// See if they forced a file format
FileFilter chosenFilter = chooser.getFileFilter();
if (chosenFilter instanceof BAMFileFilter) {
System.setProperty("bamqc.sequence_format", "bam");
}
// If we're still showing the welcome panel switch this out for
// the file tabs panel
if (fileTabs.getTabCount() == 0) {
getContentPane().remove(welcomePanel);
getContentPane().add(fileTabs,BorderLayout.CENTER);
validate();
repaint();
}
File [] files = chooser.getSelectedFiles();
for (int i=0;i<files.length;i++) {
lastUsedDir = files[i].getParentFile();
SequenceFile sequenceFile;
try {
sequenceFile = SequenceFactory.getSequenceFile(files[i]);
AnalysisRunner runner = new AnalysisRunner(sequenceFile);
ResultsPanel rp = new ResultsPanel(sequenceFile);
runner.addProgressListener(rp);
runner.addAnalysisListener(rp);
fileTabs.addTab(sequenceFile.name(), rp);
QCModule [] moduleList = ModuleFactory.getStandardModuleList();
runner.startAnalysis(moduleList);
}
catch (SequenceFormatException e) {
JPanel errorPanel = new JPanel();
errorPanel.setLayout(new BorderLayout());
errorPanel.add(new JLabel("File format error: "+e.getLocalizedMessage(), JLabel.CENTER),BorderLayout.CENTER);
fileTabs.addTab(files[i].getName(), errorPanel);
log.error(e, e);
continue;
}
catch (IOException e) {
log.error("File " + files[i].getAbsolutePath() + " broken", e);
JOptionPane.showMessageDialog(this, "Couldn't read file: "+e.getLocalizedMessage(), "Error reading file", JOptionPane.ERROR_MESSAGE);
continue;
}
catch (Exception e) {
log.error("Failed to process the file " + files[i].getAbsolutePath(), e);
JOptionPane.showMessageDialog(this, "Failed to process the file: "+e.getLocalizedMessage(), "Error processing file", JOptionPane.ERROR_MESSAGE);
continue;
}
}
return true;
}
/**
* Launches the genome selector to begin a new project.
*/
public boolean openGFFFromNetwork () {
new GenomeSelector(this);
if(BamQCConfig.getInstance().genome == null) {
return false;
}
// for consistency, let's remove the file annotation if this was set.
unsetFileAnnotation();
//setTitle(title + " ~ " + BamQCConfig.getInstance().genome.getAbsolutePath());
statusPanel.progressUpdated(" ", 0, 100);
statusPanel.setText("Genome annotation : " + BamQCConfig.getInstance().genome.getAbsolutePath());
return true;
}
public boolean openGFF () {
JFileChooser chooser;
if (lastUsedDir == null) {
chooser = new JFileChooser();
}
else {
chooser = new JFileChooser(lastUsedDir);
}
// Action details = chooser.getActionMap().get("viewTypeDetails");
// details.actionPerformed(null);
chooser.setMultiSelectionEnabled(false);
GFFFileFilter gff = new GFFFileFilter();
// remove default "All Files" filter
chooser.removeChoosableFileFilter(chooser.getFileFilter());
chooser.addChoosableFileFilter(gff);
chooser.setFileFilter(gff);
int result = chooser.showOpenDialog(this);
if (result == JFileChooser.CANCEL_OPTION) return false;
File gff_file = chooser.getSelectedFile();
if (!(gff_file.exists() && gff_file.canRead())) {
JOptionPane.showMessageDialog(this, "GFF file "+gff_file+" doesn't exist or can't be read", "Invalid GFF file", JOptionPane.ERROR_MESSAGE);
return false;
}
BamQCConfig.getInstance().gff_file = gff_file;
// for consistency, let's remove the genome annotation if this was set.
unsetGenomeAnnotation();
//setTitle(title + " ~ " + BamQCConfig.getInstance().gff_file.getAbsolutePath());
statusPanel.progressUpdated(" ", 0, 100);
statusPanel.setText("Annotation file : " + BamQCConfig.getInstance().gff_file.getAbsolutePath());
return true;
}
/**
* Unset the file annotation.
*/
public void unsetFileAnnotation () {
BamQCConfig.getInstance().gff_file = null;
}
/**
* Unset the genome annotation.
*/
public void unsetGenomeAnnotation () {
BamQCConfig.getInstance().genome = null;
BamQCConfig.getInstance().species = null;
BamQCConfig.getInstance().assembly = null;
}
/**
* Unset the annotation.
*/
public void unsetAnnotation () {
unsetGenomeAnnotation();
unsetFileAnnotation();
//setTitle(title);
statusPanel.setText(" ");
}
public void saveReport () {
JFileChooser chooser;
if (lastUsedDir == null) {
chooser = new JFileChooser();
}
else {
chooser = new JFileChooser(lastUsedDir);
}
if (fileTabs.getSelectedComponent() == null) {
JOptionPane.showMessageDialog(this, "No SAM/BAM files are open yet", "Can't save report", JOptionPane.ERROR_MESSAGE);
return;
}
chooser.setSelectedFile(new File(((ResultsPanel)fileTabs.getSelectedComponent()).sequenceFile().getFile().getName().replaceAll(".gz$","").replaceAll(".bz2$","").replaceAll(".txt$","").replaceAll(".fastq$", "").replaceAll(".fq$", "").replaceAll(".sam$", "").replaceAll(".bam$", "")+"_bamqc.html"));
chooser.setMultiSelectionEnabled(false);
chooser.setFileFilter(new FileFilter() {
@Override
public String getDescription() {
return "HTML files";
}
@Override
public boolean accept(File f) {
if (f.isDirectory() || f.getName().toLowerCase().endsWith(".html")) {
return true;
}
return false;
}
});
File reportFile;
while (true) {
int result = chooser.showSaveDialog(this);
if (result == JFileChooser.CANCEL_OPTION) return;
reportFile = chooser.getSelectedFile();
if (! reportFile.getName().toLowerCase().endsWith(".html")) {
reportFile = new File(reportFile.getAbsoluteFile()+".html");
}
// Check if we're overwriting something
if (reportFile.exists()) {
int reply = JOptionPane.showConfirmDialog(this, reportFile.getName()+" already exists. Overwrite?", "Overwrite existing file?", JOptionPane.YES_NO_OPTION);
if (reply == JOptionPane.NO_OPTION) {
continue;
}
break;
}
break;
}
ResultsPanel selectedPanel = (ResultsPanel)fileTabs.getSelectedComponent();
try {
new HTMLReportArchive(selectedPanel.sequenceFile(), selectedPanel.modules(), reportFile);
}
catch (Exception e) {
JOptionPane.showMessageDialog(this, "Failed to create archive: "+e, "Error", JOptionPane.ERROR_MESSAGE);
log.error("Failed to create archive: " + e, e);
}
}
/**
* Unsets the changesWereMade flag so that the user will not be prompted
* to save even if the data has changed.
*/
public void resetChangesWereMade () {
changesWereMade = false;
if (getTitle().endsWith("*")) {
setTitle(getTitle().replaceAll("\\*$", ""));
}
}
/* (non-Javadoc)
* @see java.awt.Window#dispose()
*/
@Override
public void dispose () {
// We're overriding this so we can catch the application being
// closed by the X in the corner. We need to offer the opportunity
// to save if they've changed anything.
// We'll already have been made invisible by this stage, so make
// us visible again in case we're hanging around.
setVisible(true);
// Check to see if the user has made any changes they might
// want to save
if (changesWereMade) {
int answer = JOptionPane.showOptionDialog(this,"You have made changes which were not saved. Do you want to save before exiting?","Save before exit?",0,JOptionPane.QUESTION_MESSAGE,null,new String [] {"Save and Exit","Exit without Saving","Cancel"},"Save");
switch (answer){
case 0:
return;
case 1:
break;
case 2:
return;
}
}
setVisible(false);
super.dispose();
System.exit(0);
}
/**
* This method is usually called from data gathered by the genome selector
* which will provide the required values for the assembly name. This does
* not actually load the specified genome, but just downloads it from the
* online genome repository.
*
* @param species Species name
* @param assembly Assembly name
* @param size The size of the compressed genome file in bytes
*/
public void downloadGenome (String species, String assembly, int size) {
GenomeDownloader d = new GenomeDownloader();
d.addProgressListener(this);
// // using a text ProgressTextDialog
if(GraphicsEnvironment.isHeadless()) {
ProgressTextDialog ptd = new ProgressTextDialog("Downloading genome...");
d.addProgressListener(ptd);
}
// TODO
//update the status panel with the progress listener. Status bar must implement Progress listener. Copy this from
// Result panel
d.addProgressListener(statusPanel);
d.downloadGenome(species,assembly,size,true);
}
/**
* Select a genome.
*
* @param baseLocation The folder containing the requested genome.
*/
public void selectGenome (File baseLocation) {
BamQCConfig.getInstance().genome = baseLocation;
}
/* (non-Javadoc)
* @see uk.ac.babraham.BamQC.DataTypes.ProgressListener#progressComplete(java.lang.String, java.lang.Object)
*/
@Override
public void progressComplete(String command, Object result) {
// Many different operations can call this method and our actions
// depend on who called us and what they sent.
if (command == null) return;
if (command.equals("Genome downloaded!")) {
// use the menu controls for consistency instead of calling the method directly.
// in this way the menu item 'unset_annotation' is automatically enabled if a genome
// is set at this stage.
menu.actionPerformed(new ActionEvent(this, 0, "open_gff_from_network"));
}
else {
throw new IllegalArgumentException("Don't know how to handle progress command '"+command+"'");
}
}
/* (non-Javadoc)
* @see uk.ac.babraham.BamQC.DataTypes.ProgressListener#progressExceptionReceived(java.lang.Exception)
*/
@Override
public void progressExceptionReceived(Exception e) {
// Should be handled by specialised widgets
}
/* (non-Javadoc)
* @see uk.ac.babraham.BamQC.DataTypes.ProgressListener#progressUpdated(java.lang.String, int, int)
*/
@Override
public void progressUpdated(String message, int current, int max) {
// Should be handled by specialised widgets
}
/* (non-Javadoc)
* @see uk.ac.babraham.BamQC.DataTypes.ProgressListener#progressCancelled()
*/
@Override
public void progressCancelled () {
// Should be handled by specialised widgets
}
/* (non-Javadoc)
* @see uk.ac.babraham.BamQC.DataTypes.ProgressListener#progressWarningReceived(java.lang.Exception)
*/
@Override
public void progressWarningReceived(Exception e) {
// Should be handled by specialised widgets
}
/**
* Provides a static way to access the main instance of the BamQC
* Application so we don't need to keep passing references around
* through obscure paths.
*
* @return The currently running application instance.
*/
public static BamQCApplication getInstance () {
return application;
}
public static void main(String[] args) {
// See if we just have to print out the version
if (System.getProperty("bamqc.show_version") != null && System.getProperty("bamqc.show_version").equals("true")) {
System.out.println("BamQC v"+VERSION);
System.exit(0);
}
if (args.length > 0) {
// Set headless to true so we don't get problems
// with people working without an X display.
System.setProperty("java.awt.headless", "true");
// We used to default to unzipping the zip file in
// non-interactive runs. As we now save an HTML
// report at the top level we no longer do this
// so unzip is false unless explicitly set to be true.
if (BamQCConfig.getInstance().do_unzip == false) {
BamQCConfig.getInstance().do_unzip = false;
}
// // uncomment this if you need to run a profiler
// try { Thread.sleep(10000); }
// catch (InterruptedException e) {}
new OfflineRunner(args);
} else {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {}
// The interactive default is to not uncompress the
// reports after they have been generated
if (BamQCConfig.getInstance().do_unzip == false) {
BamQCConfig.getInstance().do_unzip = false;
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
application = new BamQCApplication();
application.setVisible(true);
}
});
}
}
}