/*******************************************************************************
* 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.trackList;
import java.awt.Color;
import java.awt.Component;
import java.awt.Point;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.io.Serializable;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.TransferHandler;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import edu.yu.einstein.genplay.core.manager.application.ConfigurationManager;
import edu.yu.einstein.genplay.exception.ExceptionManager;
import edu.yu.einstein.genplay.gui.action.track.TAPasteOrDrop;
import edu.yu.einstein.genplay.gui.action.track.TATrackSettings;
import edu.yu.einstein.genplay.gui.event.trackEvent.TrackEvent;
import edu.yu.einstein.genplay.gui.event.trackEvent.TrackEventType;
import edu.yu.einstein.genplay.gui.event.trackEvent.TrackListener;
import edu.yu.einstein.genplay.gui.mainFrame.MainFrame;
import edu.yu.einstein.genplay.gui.menu.TrackMenu;
import edu.yu.einstein.genplay.gui.track.Track;
import edu.yu.einstein.genplay.gui.track.TrackConstants;
import edu.yu.einstein.genplay.gui.track.layer.Layer;
import edu.yu.einstein.genplay.gui.track.layer.VersionedLayer;
import edu.yu.einstein.genplay.gui.track.layer.variantLayer.MultiGenomeDrawer;
import edu.yu.einstein.genplay.gui.track.layer.variantLayer.VariantLayer;
import edu.yu.einstein.genplay.gui.track.trackTransfer.TransferableTrack;
import edu.yu.einstein.genplay.util.Utils;
/**
* Scroll panel showing a list of tracks
* @author Julien Lajugie
*/
public class TrackListPanel extends JScrollPane implements Serializable, TrackListener, ListDataListener, DropTargetListener {
private final static int SCROLL_BAR_BLOCK_INCREMENT = 40; // block increment for the scroll bar
private final static int SCROLL_BAR_UNIT_INCREMENT = 15; // unit increment for the scroll bar
private static final long serialVersionUID = -5070245121955382857L; // generated serial ID
private transient Track draggedOverTrack = null; // track rolled over by the dragged track, null if none
private transient Track draggedTrack = null; // dragged track, null if none
private transient Track selectedTrack = null; // track selected
private final JPanel jpTrackList; // panel with the tracks
private final TrackMenu trackMenu; // menu for the track actions
private TrackListModel model; // model handling the tracks list showed in this panel
/**
* Creates an instance of {@link TrackListPanel}
* @param model {@link TrackListModel} handling the tracks showed in this panel
*/
public TrackListPanel(TrackListModel model) {
super();
this.model = model;
this.model.addListDataListener(this);
jpTrackList = new JPanel();
setBorder(BorderFactory.createMatteBorder(1, 0, 1, 0, Color.LIGHT_GRAY));
jpTrackList.setLayout(new BoxLayout(jpTrackList, BoxLayout.PAGE_AXIS));
new DropTarget(jpTrackList, this);
setActionMap(TrackListActionMap.getActionMap());
setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW, TrackListActionMap.getInputMap(this));
trackMenu = new TrackMenu();
getVerticalScrollBar().setUnitIncrement(SCROLL_BAR_UNIT_INCREMENT);
getVerticalScrollBar().setBlockIncrement(SCROLL_BAR_BLOCK_INCREMENT);
setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
rebuildPanel();
}
@Override
public void contentsChanged(ListDataEvent e) {
rebuildPanel();
}
/**
* Cuts the selected track
*/
public void cutTrack() {
if (selectedTrack != null) {
try {
getModel().deleteTrack(selectedTrack);
selectedTrack = null;
} catch (Exception e) {
ExceptionManager.getInstance().caughtException(Thread.currentThread(), e, "Error while cutting the track");
}
}
}
@Override
public void dragEnter(DropTargetDragEvent dtde) {
if (!MainFrame.getInstance().isLocked() &&
(dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor) || dtde.isDataFlavorSupported(TransferableTrack.uriListFlavor))) {
dtde.acceptDrag(TransferHandler.COPY);
trackDragged();
} else {
dtde.acceptDrag(TransferHandler.NONE);
}
}
@Override
public void dragExit(DropTargetEvent dte) {
if (draggedOverTrack != null) {
draggedOverTrack.setBorder(TrackConstants.REGULAR_BORDER);
draggedOverTrack = null;
unlockTrackHandles();
}
}
@Override
public void dragOver(DropTargetDragEvent dtde) {
if (!MainFrame.getInstance().isLocked() &&
(dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor) || dtde.isDataFlavorSupported(TransferableTrack.uriListFlavor))) {
trackDragged();
}
}
@Override
public void drop(DropTargetDropEvent dtde) {
if (draggedOverTrack != null) {
draggedOverTrack.setBorder(TrackConstants.REGULAR_BORDER);
unlockTrackHandles();
try {
dtde.acceptDrop(TransferHandler.COPY);
setSelectedTrack(draggedOverTrack);
new TAPasteOrDrop(dtde.getTransferable()).processAction();
dtde.dropComplete(true);
} catch (Exception e) {
JOptionPane.showMessageDialog(getRootPane(), "The selected file cannot be loaded in GenPlay", "Cannot Drop File", JOptionPane.WARNING_MESSAGE, null);
e.printStackTrace();
dtde.dropComplete(false);
}
draggedOverTrack = null;
}
}
@Override
public void dropActionChanged(DropTargetDragEvent dtde) {
// do nothing
}
/**
* @return the {@link JPanel} displaying the tracks
*/
public Component getJpTrackList() {
return jpTrackList;
}
/**
* @return the model containing the data displayed in this panel
*/
public TrackListModel getModel() {
return model;
}
/**
* @return the selected track
*/
public Track getSelectedTrack() {
return selectedTrack;
}
/**
* @param multiGenomeDrawer
* @return the track according to a {@link MultiGenomeDrawer}
*/
public Track getTrackFromGenomeDrawer(MultiGenomeDrawer multiGenomeDrawer) {
Track[] trackList = getModel().getTracks();
for (Track currentTrack: trackList) {
Layer<?>[] layers = currentTrack.getLayers().getLayers();
for (Layer<?> currentLayer: layers) {
if (currentLayer instanceof VariantLayer) {
if (((VariantLayer) currentLayer).getGenomeDrawer().equals(multiGenomeDrawer)) {
return currentTrack;
}
}
}
}
return null;
}
/**
* @return the track menu
*/
public TrackMenu getTrackMenu() {
return trackMenu;
}
@Override
public void intervalAdded(ListDataEvent e) {
rebuildPanel();
}
@Override
public void intervalRemoved(ListDataEvent e) {
rebuildPanel();
}
/**
* @return true if there is a track to paste
*/
public boolean isPasteEnable() {
Clipboard clipboard = Utils.getClipboard();
if (clipboard.isDataFlavorAvailable(TransferableTrack.TRACK_FLAVOR)) {
return true;
}
if (clipboard.isDataFlavorAvailable(DataFlavor.javaFileListFlavor)) {
return true;
}
if (clipboard.isDataFlavorAvailable(TransferableTrack.uriListFlavor)) {
return true;
}
return false;
}
/**
* Changes the legend display of the tracks
*/
public void legendChanged() {
for (Track currentTrack: getModel().getTracks()) {
currentTrack.repaint();
}
}
/**
* Locks the handles of all the tracks
*/
public void lockTrackHandles() {
for (Track currentTrack : getModel().getTracks()) {
if (currentTrack != null) {
currentTrack.lockHandle();
}
}
}
private void rebuildPanel() {
jpTrackList.removeAll();
Track[] trackList = getModel().getTracks();
int selectedTrackIndex = getModel().indexOf(selectedTrack);
for (int i = 0; i < trackList.length; i++) {
trackList[i].setNumber(i + 1);
jpTrackList.add(trackList[i].getTrackPanel());
trackList[i].addTrackListener(this);
}
if (selectedTrackIndex == -1) {
selectedTrack = null;
} else {
selectedTrack = getModel().getTrack(selectedTrackIndex);
}
jpTrackList.revalidate();
setViewportView(jpTrackList);
}
/**
* Called when a track is released after being dragged in the list
*/
public void releaseTrack() {
if ((draggedTrack != null) && (draggedOverTrack != null)) {
draggedOverTrack.setBorder(TrackConstants.REGULAR_BORDER);
if (getMousePosition() != null) {
int insertIndex = getModel().indexOf(draggedOverTrack);
getModel().deleteTrack(draggedTrack);
getModel().insertTrack(insertIndex, draggedTrack);
}
}
setSelectedTrack(draggedTrack);
draggedTrack = null;
draggedOverTrack = null;
unlockTrackHandles();
}
/**
* Changes the reset layer function of the {@link VersionedLayer}.
*/
public void resetLayerChanged() {
boolean hasToBeDisabled = !ConfigurationManager.getInstance().isResetTrack();
if (hasToBeDisabled) {
for (Track currentTrack: getModel().getTracks()) {
for (Layer<?> currentLayer: currentTrack.getLayers()) {
if (currentLayer instanceof VersionedLayer<?>) {
((VersionedLayer<?>) currentLayer).deactivateReset();
}
}
}
}
}
/**
* Sets the model handling the data
* @param model
*/
public void setModel(TrackListModel model) {
this.model = model;
this.model.addListDataListener(this);
rebuildPanel();
}
/**
* Sets the selected track in the track panel
* @param trackToSelect
*/
public void setSelectedTrack(Track trackToSelect) {
if (trackToSelect != null) {
// unselect the previous track
if (selectedTrack != null) {
selectedTrack.setSelected(false);
}
// select the dragged over track
selectedTrack = trackToSelect;
selectedTrack.setSelected(true);
}
}
@Override
public void trackChanged(TrackEvent evt) {
if (evt.getEventType() == TrackEventType.POPUP_TRIGGERED) {
selectedTrack = (Track) evt.getSource();
trackMenu.setTrack(selectedTrack);
Point mousePoint = getMousePosition();
if (mousePoint != null) {
trackMenu.show(this, mousePoint.x, mousePoint.y);
}
} else if (evt.getEventType() == TrackEventType.DRAGGED) {
// set the dragged track
if (draggedTrack == null) {
draggedTrack = ((Track) evt.getSource());
}
trackDragged();
} else if (evt.getEventType() == TrackEventType.RELEASED) {
releaseTrack();
} else if (evt.getEventType() == TrackEventType.SELECTED) {
// unselect the previously selected track (if any)
if ((selectedTrack != null) && (selectedTrack != evt.getSource())){
selectedTrack.setSelected(false);
}
// set the new selected track
selectedTrack = (Track) evt.getSource();
} else if (evt.getEventType() == TrackEventType.UNSELECTED) {
selectedTrack = null;
} else if (evt.getEventType() == TrackEventType.DOUBLE_CLICKED) {
TATrackSettings trackSettings = new TATrackSettings();
trackSettings.actionPerformed(null);
}
}
/**
* Changes the number of {@link Track} in the {@link TrackListPanel} according to
* the value specified in the {@link ConfigurationManager}
*/
public void trackCountChanged() {
int trackCount = ConfigurationManager.getInstance().getTrackCount();
int preferredHeight = ConfigurationManager.getInstance().getTrackHeight();
Track[] trackTmp = getModel().getTracks();
Track[] trackList = new Track[trackCount];
for (int i = 0; i < trackCount; i++) {
if (i < trackTmp.length) {
trackList[i] = trackTmp[i];
} else {
trackList[i] = new Track(i + 1);
trackList[i].setPreferredHeight(preferredHeight);
}
}
getModel().setTracks(trackList);
rebuildPanel();
}
/**
* Called when a track is being dragged
*/
public void trackDragged() {
lockTrackHandles();
if (getMousePosition() != null) {
for (Track currentTrack : getModel().getTracks()) {
JPanel currentTrackPanel = currentTrack.getTrackPanel();
int currentTrackTop = currentTrackPanel.getY() - getVerticalScrollBar().getValue();
int currentrackBottom = (currentTrackPanel.getY() + currentTrackPanel.getHeight()) - getVerticalScrollBar().getValue();
int mouseY = -1;
try {
mouseY = getMousePosition().y;
if ((mouseY != -1) && (mouseY > currentTrackTop) && (mouseY < currentrackBottom) && (currentTrack != draggedOverTrack)) {
if (draggedOverTrack != null) {
draggedOverTrack.setBorder(TrackConstants.REGULAR_BORDER);
}
draggedOverTrack = currentTrack;
if ((draggedTrack == null) || (draggedOverTrack == draggedTrack)) {
draggedOverTrack.setBorder(TrackConstants.DRAG_START_BORDER);
} else if (draggedOverTrack.getNumber() < draggedTrack.getNumber()) {
draggedOverTrack.setBorder(TrackConstants.DRAG_UP_BORDER);
} else {
draggedOverTrack.setBorder(TrackConstants.DRAG_DOWN_BORDER);
}
}
} catch (NullPointerException e) {
// do nothing, it's because the getMousePosition return null
}
}
}
}
/**
* Sets the height of each {@link Track} according to the value specified in the {@link ConfigurationManager}
*/
public void trackHeightChanged() {
int preferredHeight = ConfigurationManager.getInstance().getTrackHeight();
Track[] trackList = getModel().getTracks();
for(int i = 0; i < trackList.length; i++) {
trackList[i].setDefaultHeight(preferredHeight);
trackList[i].setPreferredHeight(preferredHeight);
}
revalidate();
}
/**
* Changes the undo count of the undoable layers
*/
public void undoCountChanged() {
int undoCount = ConfigurationManager.getInstance().getUndoCount();
for (Track currentTrack: getModel().getTracks()) {
for (Layer<?> currentLayer: currentTrack.getLayers()) {
if (currentLayer instanceof VersionedLayer<?>) {
((VersionedLayer<?>) currentLayer).setUndoCount(undoCount);
}
}
}
}
/**
* Unlocks the handles of all the tracks
*/
public void unlockTrackHandles() {
for (Track currentTrack : getModel().getTracks()) {
if (currentTrack != null) {
currentTrack.unlockHandle();
}
}
}
}