/*
* Copyright (c) 2007-2013 The Broad Institute, Inc.
* SOFTWARE COPYRIGHT NOTICE
* This software and its documentation are the copyright of the Broad Institute, Inc. All rights are reserved.
*
* This software is supplied without any warranty or guaranteed support whatsoever. The Broad Institute is not responsible for its use, misuse, or functionality.
*
* This software is licensed under the terms of the GNU Lesser General Public License (LGPL),
* Version 2.1 which is available at http://www.opensource.org/licenses/lgpl-2.1.php.
*/
package org.broad.igv.track;
import org.broad.igv.feature.LocusScore;
import org.broad.igv.renderer.ContinuousColorScale;
import org.broad.igv.renderer.DataRange;
import org.broad.igv.session.IGVSessionReader;
import org.broad.igv.session.SubtlyImportant;
import org.broad.igv.ui.IGV;
import org.broad.igv.ui.color.ColorUtilities;
import org.broad.igv.ui.panel.IGVPopupMenu;
import org.broad.igv.ui.panel.ReferenceFrame;
import org.broad.igv.ui.panel.TrackPanel;
import org.broad.igv.util.ResourceLocator;
import javax.swing.*;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* Track to serve as a container for several tracks rendered on top of each other
*
* @author jacob
* @date 2013-Nov-05
*/
@XmlType(factoryMethod = "getNextTrack")
public class MergedTracks extends DataTrack{
@XmlAttribute
protected Class clazz = MergedTracks.class;
private Collection<DataTrack> memberTracks;
/**
* This is a session tag, changing it will break backwards compatibility
* with sessions!
*/
public static final String MEMBER_TRACK_TAG_NAME = "Track";
public MergedTracks(String id, String name, Collection<DataTrack> inputTracks){
super(null, id, name);
initTrackList(inputTracks);
this.autoScale = this.getAutoScale();
setTrackAlphas(120);
}
private void initTrackList(Collection<DataTrack> inputTracks) {
this.memberTracks = new ArrayList<DataTrack>(inputTracks.size());
for(DataTrack inputTrack: inputTracks){
if(inputTrack instanceof MergedTracks){
this.memberTracks.addAll(((MergedTracks) inputTrack).getMemberTracks());
}else{
this.memberTracks.add(inputTrack);
}
}
}
@Override
public Collection<ResourceLocator> getResourceLocators(){
Collection<ResourceLocator> locators = new ArrayList<ResourceLocator>(memberTracks.size());
for(DataTrack memTrack: memberTracks){
locators.addAll(memTrack.getResourceLocators());
}
return locators;
}
@XmlElement(name = MEMBER_TRACK_TAG_NAME)
Collection<DataTrack> getMemberTracks(){
return this.memberTracks;
}
public void setTrackAlphas(int alpha) {
for(Track track: memberTracks){
track.setColor(ColorUtilities.modifyAlpha(track.getColor(), alpha));
track.setAltColor(ColorUtilities.modifyAlpha(track.getAltColor(), alpha));
}
}
@Override
public void render(RenderContext context, Rectangle rect) {
resetLastY();
for(Track track: memberTracks){
if(isRepeatY(rect)){
track.overlay(context, rect);
}else{
track.render(context, rect);
lastRenderY = rect.y;
}
}
}
@Override
public int getHeight() {
int height = super.getHeight();
for(Track track: memberTracks){
height = Math.max(height, track.getHeight());
}
return height;
}
@Override
public void setHeight(int height) {
super.setHeight(height);
for(Track track: memberTracks){
track.setHeight(height);
}
}
@Override
public void setDataRange(DataRange axisDefinition) {
super.setDataRange(axisDefinition);
for(Track track: memberTracks){
track.setDataRange(axisDefinition);
}
}
@Override
public DataRange getDataRange() {
if(this.dataRange == null){
this.dataRange = DataRange.getFromTracks(memberTracks);
}
return this.dataRange;
}
@Override
public String getValueStringAt(String chr, double position, int y, ReferenceFrame frame) {
StringBuilder builder = new StringBuilder(memberTracks.size() + 2);
builder.append(getName());
builder.append("<br/>--------------<br/>");
for(Track track: memberTracks){
String curS = track.getValueStringAt(chr, position, y, frame);
if (curS != null) {
builder.append(curS);
builder.append("<br/>--------------<br/>");
}
}
return builder.toString();
}
@Override
public List<LocusScore> getSummaryScores(String chr, int startLocation, int endLocation, int zoom) {
return null;
}
@Override
public void setRendererClass(Class rc) {
super.setRendererClass(rc);
for(Track track: memberTracks){
track.setRendererClass(rc);
}
}
@Override
public boolean getAutoScale() {
boolean autoScale = super.getAutoScale();
for(Track track: memberTracks){
autoScale &= track.getAutoScale();
}
return autoScale;
}
@Override
public void setAutoScale(boolean autoScale) {
super.setAutoScale(autoScale);
for(Track track: memberTracks){
track.setAutoScale(autoScale);
}
}
@Override
public ContinuousColorScale getColorScale() {
return super.getColorScale();
}
@Override
public void setColorScale(ContinuousColorScale colorScale) {
super.setColorScale(colorScale);
for(Track track: memberTracks){
track.setColorScale(colorScale);
}
}
@Override
public void setWindowFunction(WindowFunction type) {
super.setWindowFunction(type);
for(Track track: memberTracks){
track.setWindowFunction(type);
}
}
@Override
public void setShowDataRange(boolean showDataRange) {
super.setShowDataRange(showDataRange);
for(DataTrack track: memberTracks){
track.setShowDataRange(showDataRange);
}
}
@Override
public IGVPopupMenu getPopupMenu(TrackClickEvent te) {
IGVPopupMenu menu = new IGVPopupMenu();
final List<Track> selfAsList = Arrays.asList((Track) this);
menu.add(TrackMenuUtils.getTrackRenameItem(selfAsList));
//Give users the ability to set the color of each track individually
JMenu setPosColorMenu = new JMenu("Change Track Color (Positive Values)");
JMenu setNegColorMenu = new JMenu("Change Track Color (Negative Values)");
for(DataTrack track: memberTracks){
Icon posColorIcon = new ColorIcon(track.getColor());
JMenuItem posItem = new JMenuItem(track.getName(), posColorIcon);
posItem.addActionListener(new ChangeTrackColorActionListener(track, ChangeTrackMethod.POSITIVE));
setPosColorMenu.add(posItem);
Icon negColorIcon = new ColorIcon(track.getAltColor());
JMenuItem negItem = new JMenuItem(track.getName(), negColorIcon);
negItem.addActionListener(new ChangeTrackColorActionListener(track, ChangeTrackMethod.NEGATIVE));
setNegColorMenu.add(negItem);
}
menu.add(setPosColorMenu);
menu.add(setNegColorMenu);
menu.add(TrackMenuUtils.getChangeTrackHeightItem(selfAsList));
menu.add(TrackMenuUtils.getChangeFontSizeItem(selfAsList));
menu.addSeparator();
TrackMenuUtils.addDataItems(menu, selfAsList);
for(Component c: menu.getComponents()){
if(c instanceof JMenuItem){
String text = ((JMenuItem) c).getText();
text = text != null ? text.toLowerCase() : "null";
if(text.contains("heatmap")){
c.setEnabled(false);
}
}
}
menu.addSeparator();
menu.add(TrackMenuUtils.getRemoveMenuItem(selfAsList));
return menu;
}
//Not clear whether this is necessary, don't think it is, the real problem is loading not saving
// public void marshalMemberTracks(Marshaller m, Element trackElement) throws JAXBException{
// for(DataTrack memberTrack: memberTracks){
// JAXBElement element = new JAXBElement<DataTrack>(new QName("", DataTrack.DATA_TRACK), DataTrack.class, memberTrack);
// m.marshal(element, trackElement);
// }
// }
private enum ChangeTrackMethod{
POSITIVE, NEGATIVE
}
private static class ChangeTrackColorActionListener implements ActionListener{
private Track mTrack;
private ChangeTrackMethod method;
private ChangeTrackColorActionListener(Track track, ChangeTrackMethod method){
this.mTrack=track;
this.method = method;
}
@Override
public void actionPerformed(ActionEvent e) {
switch(this.method){
case POSITIVE:
TrackMenuUtils.changeTrackColor(Arrays.asList(this.mTrack));
break;
case NEGATIVE:
TrackMenuUtils.changeAltTrackColor(Arrays.asList(this.mTrack));
break;
default:
throw new IllegalStateException("Method not understood: " + this.method);
}
}
}
/**
* A square, solid color Icon
*/
private static class ColorIcon implements Icon{
private Color color;
private int iconSize;
ColorIcon(Color color){
this(color, 16);
}
ColorIcon(Color color, int iconSize){
this.color = color;
this.iconSize = iconSize;
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
Graphics cg = g.create();
cg.setColor(this.color);
if(this.iconSize > c.getHeight()){
this.iconSize = c.getHeight();
}
cg.fillRect(x, y, this.iconSize, this.iconSize);
}
@Override
public int getIconWidth() {
return this.iconSize;
}
@Override
public int getIconHeight() {
return this.iconSize;
}
}
@SubtlyImportant
private MergedTracks(){
super(null, null, null);
}
@SubtlyImportant
private static MergedTracks getNextTrack(){
MergedTracks out = (MergedTracks) IGVSessionReader.getNextTrack();
if (out == null){
out = new MergedTracks();
}
return out;
}
}