/*******************************************************************************
* 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.FontMetrics;
import java.awt.Graphics;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import edu.yu.einstein.genplay.core.manager.project.ProjectManager;
import edu.yu.einstein.genplay.core.manager.project.ProjectWindow;
import edu.yu.einstein.genplay.dataStructure.chromosomeWindow.ChromosomeWindow;
import edu.yu.einstein.genplay.dataStructure.list.chromosomeWideList.repeatListView.RepeatFamilyListView;
import edu.yu.einstein.genplay.dataStructure.list.genomeWideList.repeatFamilyList.RepeatFamilyList;
import edu.yu.einstein.genplay.dataStructure.list.listView.ListView;
import edu.yu.einstein.genplay.gui.dataScalerForTrackDisplay.DataScalerManager;
import edu.yu.einstein.genplay.gui.track.Track;
import edu.yu.einstein.genplay.util.colors.Colors;
/**
* Layer displaying a mask
* @author Julien Lajugie
*/
public class RepeatLayer extends AbstractLayer<RepeatFamilyList> implements Layer<RepeatFamilyList>, MouseListener, MouseMotionListener, MouseWheelListener {
private static final long serialVersionUID = 3779631846077486596L; // generated ID
private static final short REPEAT_HEIGHT = 6; // height of a repeat in pixel
private static final short SPACE_HEIGHT = 3; // height of the space between two families of repeats
private int firstLineToDisplay = 0; // number of the first line to be displayed
private int repeatLinesCount = 0; // number of lines of repeats
private int mouseStartDragY = -1; // position of the mouse when start dragging
private List<String> familyNames; // list containing all the families of the repeat track
private String highlightedFamilyName = null; // name of the highlighted family (family with cursor over)
private String selectedFamilyName = null; // name of the selected family (family is selected if right clicked)
/**
* Creates an instance of {@link NucleotideLayer} with the same properties as the specified {@link NucleotideLayer}.
* The copy of the data is shallow.
* @param repeatLayer
*/
private RepeatLayer(RepeatLayer repeatLayer) {
super(repeatLayer);
firstLineToDisplay = 0;
repeatLinesCount = 0;
mouseStartDragY = -1;
familyNames = repeatLayer.familyNames;
highlightedFamilyName = null;
selectedFamilyName = null;
}
/**
* Creates an instance of a {@link RepeatLayer}
* @param track track containing the layer
* @param data data of the layer
* @param name name of the layer
*/
public RepeatLayer(Track track, RepeatFamilyList data, String name) {
super(track, data, name);
generateFamilyNameList();
}
@Override
public RepeatLayer clone() {
return new RepeatLayer(this);
}
@Override
public void draw(Graphics g, int width, int height) {
if (isVisible()) {
int currentHeight = SPACE_HEIGHT;
if (getData() != null) {
ProjectWindow projectWindow = ProjectManager.getInstance().getProjectWindow();
// Retrieve the repeats to print
List<RepeatFamilyListView> repeatFamilyList = DataScalerManager.getInstance().getScaledData(this);
// calculate how many lines are displayable
int displayedLineCount = ((height - SPACE_HEIGHT) / (REPEAT_HEIGHT + (2 * SPACE_HEIGHT))) + 1;
// calculate how many scroll on the Y axis are necessary to show all the repeats
repeatLinesCount = (familyNames.size() - displayedLineCount) + 2;
int currentColor = firstLineToDisplay;
// loop for each line of the track
for (int i = 0; i < displayedLineCount; i++) {
if ((i + firstLineToDisplay) < familyNames.size()) {
// retrieve the repeat associated to the current line to draw
String familyToDraw = familyNames.get(i + firstLineToDisplay);
RepeatFamilyListView currentFamily = null;
int indexFamilyToDisplay = 0;
if (repeatFamilyList == null) {
getTrack().drawLoadingAnimation(g);
} else {
while ((currentFamily == null) && (indexFamilyToDisplay < repeatFamilyList.size())) {
if (repeatFamilyList.get(indexFamilyToDisplay).getName().equals(familyToDraw)) {
currentFamily = repeatFamilyList.get(indexFamilyToDisplay);
}
indexFamilyToDisplay++;
}
}
// calculate if the background is white or gray
if ((currentColor % 2) == 1) {
g.setColor(Colors.LIGHT_GREY);
if (selectedFamilyName == null) {
g.fillRect(0, currentHeight, width, REPEAT_HEIGHT + (2 * SPACE_HEIGHT));
} else if ((selectedFamilyName != null) && (selectedFamilyName.equals(familyToDraw))) {
g.fillRect(0, 0, width, height);
}
}
if ((selectedFamilyName == null) && (highlightedFamilyName != null) && (familyToDraw.equals(highlightedFamilyName))) {
g.setColor(Colors.BLACK);
g.drawRect(0, currentHeight, width, (REPEAT_HEIGHT + (2 * SPACE_HEIGHT)) - 1);
}
currentHeight += SPACE_HEIGHT;
// calculate the color of the line
g.setColor(intToColor(currentColor));
// loop for each repeat of the current family
if (currentFamily != null) {
for(ChromosomeWindow currentRepeat : currentFamily) {
if (currentRepeat != null) {
int x = projectWindow.genomeToScreenPosition(currentRepeat.getStart());
int repeatWidth = projectWindow.genomeToScreenPosition(currentRepeat.getStop()) - x;
if (repeatWidth < 1) {
repeatWidth = 1;
}
if (selectedFamilyName == null) {
g.fillRect(x, currentHeight, repeatWidth, REPEAT_HEIGHT);
} else if ((selectedFamilyName != null) && (selectedFamilyName.equals(familyToDraw))) {
g.fillRect(x, 0, repeatWidth, height);
}
}
}
}
FontMetrics fm = g.getFontMetrics();
// calculate the witdh of the text of the repeat name
int textWidth = fm.stringWidth(familyToDraw);
// draw a rectangle under the text with the color of the background
if ((currentColor % 2) == 1) {
g.setColor(Colors.LIGHT_GREY);
} else {
g.setColor(Colors.WHITE);
}
if (selectedFamilyName == null) {
g.fillRect(1, currentHeight, textWidth + 2, REPEAT_HEIGHT);
} else if ((selectedFamilyName != null) && (selectedFamilyName.equals(familyToDraw))) {
int yTextRect = (height / 2) - (fm.getHeight());
int heightTextRect = fm.getHeight() + 5;
g.fillRect(1, yTextRect, textWidth + 2, heightTextRect);
}
currentHeight += REPEAT_HEIGHT;
// Write the repeat name
g.setColor(intToColor(currentColor));
if (selectedFamilyName == null) {
g.drawString(familyToDraw, 2, currentHeight);
} else if ((selectedFamilyName != null) && (selectedFamilyName.equals(familyToDraw))) {
g.drawString(familyToDraw, 2, height / 2);
}
currentHeight += SPACE_HEIGHT;
currentColor++;
}
}
}
}
}
/**
* Generates the sorted list of the repeat families of the track
*/
private void generateFamilyNameList() {
familyNames = new ArrayList<String>();
for (ListView<RepeatFamilyListView> currentChromoList: getData()) {
for (RepeatFamilyListView currentRepeatFamily: currentChromoList) {
String currentRepeatFamilyName = currentRepeatFamily.getName();
if (!familyNames.contains(currentRepeatFamilyName)) {
familyNames.add(currentRepeatFamilyName);
}
}
}
Collections.sort(familyNames);
}
/**
* @param yPosition a y position on the track
* @return the repeat family at this y position. Null if none
*/
private String getFamilyRolledOver(int yPosition) {
int repeatHeight = REPEAT_HEIGHT + (2 * SPACE_HEIGHT);
int highlightedFamilyIndex = (yPosition / repeatHeight) + firstLineToDisplay;
if (highlightedFamilyIndex >= familyNames.size()) {
return null;
} else {
return familyNames.get(highlightedFamilyIndex);
}
}
@Override
public LayerType getType() {
return LayerType.REPEAT_FAMILY_LAYER;
}
/**
* Associates a {@link Color} to an integer value
* @param i integer value
* @return a {@link Color}
*/
private Color intToColor(int i) {
Color[] colorArray = {Colors.BLACK, Colors.GREEN, Colors.BLUE, Colors.DARK_YELLOW, Colors.RED, Color.CYAN, Color.MAGENTA, Colors.ORANGE};
i = i % colorArray.length;
return colorArray[i];
}
/**
* Sets the selected family when a the user double right click on one
*/
@Override
public void mouseClicked(MouseEvent e) {
// handle right clicks
if (isVisible()) {
if ((e.getModifiers() == InputEvent.BUTTON3_MASK) && (e.getClickCount() == 2)){
if (selectedFamilyName != null) {
selectedFamilyName = null;
getTrack().repaint();
} else {
int mouseYPosition = e.getPoint().y;
String newSelectedFamilyName = getFamilyRolledOver(mouseYPosition);
if (newSelectedFamilyName != selectedFamilyName) {
selectedFamilyName = newSelectedFamilyName;
getTrack().repaint();
}
}
}
}
}
/**
* Changes the scroll position of the panel when mouse dragged with the right button
*/
@Override
public void mouseDragged(MouseEvent e) {
if (isVisible()) {
if (e.getModifiers() == InputEvent.BUTTON3_MASK) {
int distance = (mouseStartDragY - e.getY()) / (REPEAT_HEIGHT + (2 * SPACE_HEIGHT));
if (Math.abs(distance) > 0) {
if (((distance < 0) && ((distance + firstLineToDisplay) >= 0))
|| ((distance > 0) && ((distance + firstLineToDisplay) <= repeatLinesCount))) {
firstLineToDisplay += distance;
mouseStartDragY = e.getY();
getTrack().repaint();
}
}
}
}
}
@Override
public void mouseEntered(MouseEvent e) {}
@Override
public void mouseExited(MouseEvent e) {
highlightedFamilyName = null;
getTrack().repaint();
}
/**
* Sets the family with the cursor over when the cursor moves
*/
@Override
public void mouseMoved(MouseEvent e) {
if (isVisible()) {
int mouseYPosition = e.getPoint().y;
String newHighlightedFamilyName = getFamilyRolledOver(mouseYPosition);
if (newHighlightedFamilyName != highlightedFamilyName) {
highlightedFamilyName = newHighlightedFamilyName;
getTrack().repaint();
}
}
}
/**
* Sets the variable mouseStartDragY when the user press the right button of the mouse
*/
@Override
public void mousePressed(MouseEvent e) {
if (isVisible()) {
if (e.getModifiers() == InputEvent.BUTTON3_MASK) {
mouseStartDragY = e.getY();
}
}
}
@Override
public void mouseReleased(MouseEvent e) {}
/**
* Changes the scroll position of the panel when the wheel of the mouse is used with the right button
*/
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (isVisible()) {
if (e.getModifiers() == InputEvent.BUTTON3_MASK) {
if (((e.getWheelRotation() < 0) && ((e.getWheelRotation() + firstLineToDisplay) >= 0))
|| ((e.getWheelRotation() > 0) && ((e.getWheelRotation() + firstLineToDisplay) <= repeatLinesCount))) {
firstLineToDisplay += e.getWheelRotation();
getTrack().repaint();
}
}
}
}
}