/******************************************************************************* * GenPlay, Einstein Genome Analyzer * Copyright (C) 2009, 2014 Albert Einstein College of Medicine * * This program 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. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. * Authors: Julien Lajugie <julien.lajugie@einstein.yu.edu> * Nicolas Fourel <nicolas.fourel@einstein.yu.edu> * Eric Bouhassira <eric.bouhassira@einstein.yu.edu> * * Website: <http://genplay.einstein.yu.edu> ******************************************************************************/ package edu.yu.einstein.genplay.gui.track.layer; import java.awt.Color; import java.awt.Component; import java.awt.Graphics; import java.awt.Point; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.ObjectInputStream; import javax.swing.JOptionPane; import edu.yu.einstein.genplay.core.manager.project.ProjectManager; import edu.yu.einstein.genplay.core.manager.project.ProjectWindow; import edu.yu.einstein.genplay.dataStructure.enums.Nucleotide; import edu.yu.einstein.genplay.dataStructure.list.genomeWideList.nucleotideList.NucleotideList; import edu.yu.einstein.genplay.dataStructure.list.genomeWideList.nucleotideList.TwoBitSequenceList; import edu.yu.einstein.genplay.gui.dataScalerForTrackDisplay.DataScalerManager; import edu.yu.einstein.genplay.gui.mainFrame.MainFrame; import edu.yu.einstein.genplay.gui.track.ScrollingManager; import edu.yu.einstein.genplay.gui.track.Track; import edu.yu.einstein.genplay.gui.track.TrackConstants; import edu.yu.einstein.genplay.util.FileChooser; import edu.yu.einstein.genplay.util.Utils; import edu.yu.einstein.genplay.util.colors.Colors; /** * Layer displaying a mask * * @author Julien Lajugie */ public class NucleotideLayer extends AbstractLayer<NucleotideList> implements Layer<NucleotideList>, MouseMotionListener, MouseListener { private static final long serialVersionUID = 3779631846077486596L;// generated ID private static final int NUCLEOTIDE_HEIGHT = 10; // y position of the nucleotides on the track private transient Integer maxBaseWidth = null; // size on the screen of the widest base to display (in pixels) private transient Integer baseUnderMouseIndex = null; // index of the base under the mouse private transient boolean nucleotidePrinted = false; // true if the nucleotide are printed /** * Creates an instance of {@link NucleotideLayer} with the same properties as the specified {@link NucleotideLayer}. The copy of the data is shallow. * * @param nucleotideLayer */ private NucleotideLayer(NucleotideLayer nucleotideLayer) { super(nucleotideLayer); maxBaseWidth = nucleotideLayer.maxBaseWidth; baseUnderMouseIndex = null; nucleotidePrinted = nucleotideLayer.nucleotidePrinted; } /** * Creates an instance of a {@link NucleotideLayer} * * @param track * track containing the layer * @param data * data of the layer * @param name * name of the layer */ public NucleotideLayer(Track track, NucleotideList data, String name) { super(track, data, name); maxBaseWidth = computeMaximumBaseWidth(); } @Override public NucleotideLayer clone() { return new NucleotideLayer(this); } /** * @return the maximum width in pixel that a base can take up */ private int computeMaximumBaseWidth() { int maxWidth = 0; // compute the length in pixels of the widest base to display String[] bases = { "N", "A", "C", "G", "T", "X"}; for (String currBase : bases) { maxWidth = Math.max(maxWidth, getTrack().getFontMetrics(TrackConstants.FONT_DEFAULT).stringWidth(currBase)); } return maxWidth; } @Override public void draw(Graphics g, int width, int height) { if (isVisible()) { if (maxBaseWidth == null) { // should be null after unserialization and need to be reinitialized maxBaseWidth = computeMaximumBaseWidth(); } ProjectWindow projectWindow = ProjectManager.getInstance().getProjectWindow(); long baseToPrintCount = projectWindow.getGenomeWindow().getSize(); // if there is enough room to print something nucleotidePrinted = (baseToPrintCount <= width); drawNucleotideBackgrounds(g, width, height); drawNucleotideLetters(g, width, height); } } /** * Draws the backgrounds of the nucleotide * * @param g * {@link Graphics} * @param width * with of the {@link Graphics} to paint * @param height * height of the {@link Graphics} to paint */ private void drawNucleotideBackgrounds(Graphics g, int width, int height) { if (getData() != null) { if (nucleotidePrinted) { ProjectWindow projectWindow = ProjectManager.getInstance().getProjectWindow(); Nucleotide[] nucleotides = DataScalerManager.getInstance().getScaledData(this); for (int position = projectWindow.getGenomeWindow().getStart(); position <= projectWindow.getGenomeWindow().getStop(); position++) { int index = position - projectWindow.getGenomeWindow().getStart(); if ((index >= 0) && (nucleotides[index] != null)) { Nucleotide nucleotide = nucleotides[index]; // compute the position on the screen int x = projectWindow.genomeToScreenPosition(position); int nucleoWith = projectWindow.genomeToScreenPosition(position + 1) - x; // select a different color for each type of base Color nucleoColor = Colors.nucleotideToColor(nucleotide); if ((baseUnderMouseIndex != null) && (index == baseUnderMouseIndex)) { nucleoColor = Colors.addTransparency(nucleoColor, Colors.ROLLED_OVER_NUCLEOTIDE_TRANSPARENCY); } g.setColor(nucleoColor); g.fillRect(x, 0, nucleoWith, height); if (nucleoWith >= 5) { g.setColor(Colors.TRACK_BACKGROUND); g.drawRect(x, 0, nucleoWith, height - 1); } } } } g.setColor(Color.WHITE); g.drawLine(0, 0, width, 0); g.drawLine(0, height - 1, width, height - 1); } } /** * Draws the letter of the nucleotides * * @param g * @param width * with of the {@link Graphics} to paint * @param height * height of the {@link Graphics} to paint */ private void drawNucleotideLetters(Graphics g, int width, int height) { if (getData() != null) { if (nucleotidePrinted) { ProjectWindow projectWindow = ProjectManager.getInstance().getProjectWindow(); Nucleotide[] nucleotides = DataScalerManager.getInstance().getScaledData(this); for (int position = projectWindow.getGenomeWindow().getStart(); position <= projectWindow.getGenomeWindow().getStop(); position++) { int index = position - projectWindow.getGenomeWindow().getStart(); if ((index >= 0) && (nucleotides[index] != null)) { Nucleotide nucleotide = nucleotides[index]; long baseToPrintCount = projectWindow.getGenomeWindow().getSize(); if ((maxBaseWidth * baseToPrintCount) <= width) { // compute the position on the screen int x = projectWindow.genomeToScreenPosition(position); // select a different color for each type of base if ((baseUnderMouseIndex != null) && (index == baseUnderMouseIndex)) { g.setColor(Colors.BLACK); } else { g.setColor(Colors.TRACK_BACKGROUND); } g.drawString(String.valueOf(nucleotide.getCode()), x, height - NUCLEOTIDE_HEIGHT); } } } } else { // if we can't print all the bases we just print a message for the user g.setColor(Color.black); g.drawString("Can't display DNA sequence at this zoom level.", 0, height - NUCLEOTIDE_HEIGHT); } } } @Override public LayerType getType() { return LayerType.NUCLEOTIDE_LAYER; } @Override public void mouseClicked(MouseEvent e) { } /** * Resets the tooltip and the highlighted base when the mouse is dragged */ @Override public void mouseDragged(MouseEvent e) { if (isVisible()) { if (baseUnderMouseIndex != null) { baseUnderMouseIndex = null; getTrack().getGraphicsPanel().setToolTipText(null); getTrack().repaint(); } } } @Override public void mouseEntered(MouseEvent e) { } /** * Resets the tooltip and the highlighted base when the mouse exits the track */ @Override public void mouseExited(MouseEvent e) { if (baseUnderMouseIndex != null) { baseUnderMouseIndex = null; getTrack().getGraphicsPanel().setToolTipText(null); getTrack().repaint(); } } /** * Sets the tooltip and the base with the mouse over when the mouse move */ @Override public void mouseMoved(MouseEvent e) { if (isVisible() && (getData() != null)) { getTrack().getGraphicsPanel().setToolTipText(""); ProjectWindow projectWindow = ProjectManager.getInstance().getProjectWindow(); Integer oldBaseUnderMouseIndex = baseUnderMouseIndex; baseUnderMouseIndex = null; if (!ScrollingManager.getInstance().isScrollingEnabled()) { // if the zoom is too out we can't print the bases and so there is none under the mouse if (nucleotidePrinted) { // retrieve the position of the mouse Point mousePosition = e.getPoint(); // retrieve the list of the printed nucleotides Nucleotide[] printedBases = DataScalerManager.getInstance().getScaledData(this); // do nothing if there is no nucleotides if (printedBases != null) { baseUnderMouseIndex = projectWindow.screenToGenomeWidth(mousePosition.x); // we repaint the track only if the gene under the mouse changed if (((oldBaseUnderMouseIndex == null) && (baseUnderMouseIndex != null)) || ((oldBaseUnderMouseIndex != null) && (!oldBaseUnderMouseIndex.equals(baseUnderMouseIndex)))) { getTrack().repaint(); } } } else { getTrack().getGraphicsPanel().setToolTipText(null); } if (baseUnderMouseIndex != null) { Nucleotide nucleotide = DataScalerManager.getInstance().getScaledData(this)[baseUnderMouseIndex]; if (nucleotide != null) { getTrack().getGraphicsPanel().setToolTipText(nucleotide.name()); } else { getTrack().getGraphicsPanel().setToolTipText(null); } } } } } @Override public void mousePressed(MouseEvent e) { } @Override public void mouseReleased(MouseEvent e) { } /** * Method used for unserialization * * @param in * @throws IOException * @throws ClassNotFoundException */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { maxBaseWidth = null; baseUnderMouseIndex = null; nucleotidePrinted = false; twoBitSequenceListUnserialization(); } /** * Handle the unserialization of a {@link TwoBitSequenceList}. */ private void twoBitSequenceListUnserialization() { // if the data is a TwoBitSequenceList we want to make sure // that the file is still at the same location than when // the save was made. If not we need to ask the user for the new location. if (getData() instanceof TwoBitSequenceList) { TwoBitSequenceList twoBitData = ((TwoBitSequenceList) getData()); try { // restore the connection to the file containing the 2 bit sequences twoBitData.reinitDataFile(); } catch (FileNotFoundException e) { String filePath = twoBitData.getDataFilePath(); File projectDir = ProjectManager.getInstance().getProjectDirectory(); if (projectDir != null) { File twoBitFile = new File(projectDir, Utils.getFileName(filePath)); try { TwoBitSequenceList new2BitData = new TwoBitSequenceList(twoBitData, twoBitFile); setData(new2BitData); } catch (FileNotFoundException e1) { // if the file is not found we ask the user to specify the path // since the track can be null we need to get the project root pane Component rootPane = MainFrame.getInstance().getRootPane(); int dialogRes = JOptionPane.showConfirmDialog(rootPane, "The file " + filePath + " cannot be found\nPlease locate the file or press cancel to delete the Sequence Track", "File Not Found", JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE); if (dialogRes == JOptionPane.OK_OPTION) { File selectedFile = FileChooser.chooseFile(rootPane, FileChooser.OPEN_FILE_MODE, "Load Sequence Track", Utils.getReadableSequenceFileFilters(), true); if (selectedFile != null) { try { TwoBitSequenceList new2BitData = new TwoBitSequenceList(twoBitData, selectedFile); setData(new2BitData); } catch (FileNotFoundException e2) { twoBitSequenceListUnserialization(); } } else { twoBitSequenceListUnserialization(); } } else { setData(null); } } } } } } }