package aliview.sequencelist;
import java.awt.Point;
import java.awt.Rectangle;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.EventListener;
import java.util.List;
import javax.swing.DefaultListSelectionModel;
import javax.swing.ListSelectionModel;
import javax.swing.event.EventListenerList;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import jebl.evolution.sequences.Sequences;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import aliview.AliView;
import aliview.alignment.AlignmentMeta;
import aliview.sequences.Sequence;
import aliview.sequencelist.Interval;
import aliview.utils.Utils;
/**
*
* This model is delegating to the sequences themself to keep track of selection
*
*
*/
public class AlignmentSelectionModel{
private static final Logger logger = Logger.getLogger(AlignmentSelectionModel.class);
private static final String LF = System.getProperty("line.separator");
private AlignmentListModel sequences;
protected EventListenerList listenerList = new EventListenerList();
private Rectangle tempSelectionRect;
private Rectangle tempSelectionMaxRect;
private SequenceListSelectionModel sequenceListSelectionModel;
public AlignmentSelectionModel(AlignmentListModel sequenceListModel) {
this.sequences = sequenceListModel;
this.sequenceListSelectionModel = new SequenceListSelectionModel(this);
}
public SequenceListSelectionModel getSequenceListSelectionModel() {
return sequenceListSelectionModel;
}
public ArrayList<Integer> getIndicesOfSequencesWithSelection() {
ArrayList<Integer> indices = new ArrayList<Integer>();
for(int n = 0; n < sequences.size(); n++){
if(sequences.get(n).hasSelection()){
indices.add(new Integer(n));
}
}
return indices;
}
public ArrayList<Integer> getIndicesOfSequencesWithAllSelected() {
ArrayList<Integer> indices = new ArrayList<Integer>();
for(int n = 0; n < sequences.size(); n++){
if(sequences.get(n).isAllSelected()){
indices.add(new Integer(n));
}
}
return indices;
}
public boolean isBaseSelected(int x, int y) {
//logger.info("isBaseSel" + sequences.get(y));
boolean isSel = sequences.get(y).isBaseSelected(x);
// if(isSel){ logger.info("isSel" + sequences.get(y));};
return sequences.get(y).isBaseSelected(x);
}
public void selectSequenceWithIndex(int index){
setSequenceSelection(index, index);
}
public void selectSequencesWithIndex(List<Integer> listVals){
Integer[] array = listVals.toArray(new Integer[listVals.size()]);
int[] intVals = ArrayUtils.toPrimitive(array, 0);
selectSequencesWithIndex(intVals);
}
public void selectSequencesWithIndex(int[] selectedIndex){
List<Sequence> seqs = new ArrayList<Sequence>(selectedIndex.length);
for(int index: selectedIndex){
seqs.add(sequences.get(index));
}
changeSelection(seqs, true);
}
public void setSelectionAt(int xPos, int yPos) {
setSelectionAt(xPos, yPos, false);
}
public void setSelectionAt(int xPos, int yPos, boolean clearFirst) {
//delegateLSM.setAnchorSelectionIndex(yPos);
//delegateLSM.setLeadSelectionIndex(yPos);
changeSelection(xPos, yPos, xPos, yPos, clearFirst);
}
private void changeSelection(int x1, int y1, int x2, int y2, boolean clearFirst){
Rectangle newRect = new Rectangle(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1-x2), Math.abs(y1-y2));
logger.info("newRect" + newRect);
// Get current selection so we can fire a correct bounding box
Rectangle oldSelectRect = getSelectionBounds();
// value is adjusting true
for(int n = 0; n < sequences.size(); n++){
if(n>= newRect.getMinY() && n <= newRect.getMaxY()){
sequences.get(n).setSelection(newRect.x, newRect.x + newRect.width, clearFirst);
}else{
sequences.get(n).clearAllSelection();
}
}
// affected area is old selection plus new
if(clearFirst && oldSelectRect != null){
logger.info("newRect" + newRect);
logger.info("oldRect" + oldSelectRect);
if(Utils.hasSameBounds(newRect, oldSelectRect)){
// do nothing
}else{
Rectangle both = Utils.addRects(oldSelectRect, newRect);
fireSelectionChanged(both, false);
}
}
else{
fireSelectionChanged(newRect, false);
}
}
public void setSequenceSelection(int index0, int index1) {
changeSelection(index0, index1, true);
}
public void addSequenceSelection(int index0, int index1) {
changeSelection(index0, index1, false);
}
public void removeSequenceSelection(int index0, int index1) {
// set value is adjusting true
int minIndex = Math.min(index0, index1);
int maxIndex = Math.max(index0, index1);
for(int n = 0; n < sequences.size(); n++){
if(n>= minIndex && n<= maxIndex){
sequences.get(n).clearAllSelection();
}else{
// nothing
}
}
// fire all because on insert and delete indexes might change
fireSelectionChanged(0, sequences.size());
}
public void setSequenceSelection(List<Sequence> moreSeqs) {
changeSelection(moreSeqs, true);
}
private void changeSelection(List<Sequence> toSelect, boolean clearFirst){
Rectangle oldSelect = getSelectionBounds();
logger.info("toSelect.size()" + toSelect.size());
for(Sequence seq: sequences){
if(toSelect.contains(seq)){
logger.debug("select" + seq);
seq.selectAllBases();
}
else if(clearFirst){
seq.clearAllSelection();
}
}
Rectangle newSelect = getSelectionBounds();
logger.info("newSelect" + newSelect);
Rectangle addedSelection = Utils.addRects(oldSelect, newSelect);
if(addedSelection != null){
fireSelectionChanged(addedSelection, false);
}
}
private void changeSelection(int index0, int index1, boolean clearFirst){
// set value is adjusting true
int minIndex = Math.min(index0, index1);
int maxIndex = Math.max(index0, index1);
for(int n = 0; n < sequences.size(); n++){
if(n>= minIndex && n<= maxIndex){
sequences.get(n).selectAllBases();
}else{
if(clearFirst){
sequences.get(n).clearAllSelection();
}
}
}
// Update all
fireSelectionChanged(0, sequences.size());
// set value is adjusting false
// if(clearFirst || updateAll){
// fireSelectionChanged(0, sequences.size());
// }else{
// fireSelectionChanged(minIndex, maxIndex);
// }
}
public long getSelectionSize(){
long size = 0;
for(Sequence sequence : this.sequences){
size += sequence.countSelectedPositions(0, sequence.getLength());
}
return size;
}
public Rectangle getSelectionBounds(){
if(! hasSelection()){
return null;
}
// TODO this could be changed if non rectangular selections
Rectangle bounds = new Rectangle(getFirstSelectedPos());
bounds.add(getLastSelectedPos());
return bounds;
}
public List<Sequence> getSelectedSequences() {
ArrayList<Sequence> selection = new ArrayList<Sequence>();
for(Sequence sequence : sequences){
if(sequence.hasSelection()){
selection.add(sequence);
}
}
return selection;
}
public List<Sequence> getUnSelectedSequences() {
ArrayList<Sequence> selection = new ArrayList<Sequence>();
for(Sequence sequence : sequences){
if(! sequence.hasSelection()){
selection.add(sequence);
}
}
return selection;
}
public void selectBases(Sequence seq, Interval foundPos) {
seq.setSelection(foundPos.startPos, foundPos.endPos, false);
fireSelectionChanged(seq, true);
}
public void selectAllBasesUntilGapInThisSequence(Sequence sequence, int x){
sequence.selectAllBasesUntilGap(x);
fireSelectionChanged(sequence, false);
}
public void selectSequences(List<Sequence> seqs) {
changeSelection(seqs, true);
}
public String getSelectionAsNucleotides() {
StringBuilder selection = new StringBuilder();
for(Sequence sequence : sequences){
if(sequence.getSelectedBasesAsString() != null && sequence.getSelectedBasesAsString().length() > 0){
selection.append(sequence.getSelectedBasesAsString());
selection.append(LF);
}
}
return selection.toString();
}
public String getFirstSelectedName() {
String name = null;
if(getFirstSelected() != null){
name = getFirstSelected().getName();
}
return name;
}
public List<Sequence> setFirstSelectedName(String newName) {
List<Sequence> editedSequences = new ArrayList<Sequence>();
if(newName == null){
return editedSequences;
}
if(getFirstSelected() != null){
editedSequences.add(getFirstSelected().getCopy());
getFirstSelected().setName(newName);
}
return editedSequences;
}
public Sequence getFirstSelected() {
for(int n = 0; n < sequences.size(); n++){
if(sequences.get(n).hasSelection()){
return sequences.get(n);
}
}
return null;
}
public boolean hasSelection() {
long startTime = System.currentTimeMillis();
for(Sequence sequence : sequences){
if(sequence.hasSelection()){
return true;
}
}
long endTime = System.currentTimeMillis();
logger.info("hasSelection false " + (endTime - startTime) + " milliseconds");
return false;
}
public void selectAll() {
for(Sequence seq: sequences){
seq.selectAllBases();
}
fireSelectionChangedAll();
}
public void selectionExtendRight(){
if(!hasSelection()){
return;
}
Rectangle oldSelect = getSelectionBounds();
for(Sequence seq: sequences){
seq.selectionExtendRight();
}
Rectangle newSelect = getSelectionBounds();
newSelect.add(oldSelect);
fireSelectionChanged(newSelect, false);
}
public void selectionExtendLeft() {
if(!hasSelection()){
return;
}
Rectangle oldSelect = getSelectionBounds();
for(Sequence seq: sequences){
seq.selectionExtendLeft();
}
Rectangle newSelect = getSelectionBounds();
newSelect.add(oldSelect);
fireSelectionChanged(newSelect, false);
}
public void selectionExtendDown() {
if(!hasSelection()){
return;
}
Rectangle oldSelect = getSelectionBounds();
// start one above bottom
for(int n = sequences.size()-2; n >= 1; n--){
Sequence seq = sequences.get(n);
if(seq.hasSelection()){
int[] selected = seq.getSelectedPositions();
for(int index: selected){
sequences.get(n+1).setSelection(index,index, true);
}
}
}
Rectangle newSelect = getSelectionBounds();
newSelect.add(oldSelect);
fireSelectionChanged(newSelect, false);
}
public void invertSelection() {
for(Sequence seq: sequences){
seq.invertSelection();
}
fireSelectionChangedAll();
}
public void copySelectionFromInto(int indexFrom, int indexTo) {
Sequence seqFrom = sequences.get(indexFrom);
Sequence seqTo = sequences.get(indexTo);
for(int x = 0; x < seqFrom.getLength() || x < seqTo.getLength(); x++){
if(seqFrom.isBaseSelected(x)){
seqTo.setSelectionAt(x);
}
else{
seqTo.clearSelectionAt(x);
}
}
fireSelectionChanged(indexFrom, indexTo, false);
}
public void selectColumn(int columnIndex) {
for(Sequence seq: sequences){
seq.setSelectionAt(columnIndex);
}
fireSelectionChanged(new Rectangle(columnIndex,0,columnIndex,sequences.size()), false);
}
public void selectColumns(List<Integer> columns){
int maxIndex = 0;
int minIndex = sequences.getLongestSequenceLength();
for(Integer col: columns){
maxIndex = Math.max(maxIndex, col.intValue());
minIndex = Math.min(minIndex, col.intValue());
for(Sequence seq: sequences){
seq.setSelectionAt(col.intValue());
}
}
fireSelectionChanged(new Rectangle(minIndex,0,maxIndex,sequences.size()), false);
}
public void clearColumnSelection(int columnIndex) {
for(Sequence seq: sequences){
seq.clearSelectionAt(columnIndex);
}
fireSelectionChanged(new Rectangle(columnIndex,0,0,sequences.size()), false);
}
public void copySelectionFromPosX1toX2(int x1, int x2) {
for(Sequence seq: sequences){
if(seq.isBaseSelected(x1)){
seq.setSelectionAt(x2);
}
else{
seq.clearSelectionAt(x2);
}
}
fireSelectionChanged(new Rectangle(x1,0,x2,sequences.size()), false);
}
public Point getFirstSelectedPos() {
int n = getFirstSelectedSequenceIndex();
if(n != -1){
return new Point(sequences.get(n).getFirstSelectedPosition(), n);
}else{
return null;
}
}
public int getFirstSelectedSequenceIndex() {
for(int n = 0; n < sequences.size(); n++){
if(sequences.get(n).hasSelection()){
return n;
}
}
return -1;
}
public int getLastSelectedSequenceIndex() {
for(int n = sequences.size() - 1; n >= 0; n--){
if(sequences.get(n).hasSelection()){
return n;
}
}
return -1;
}
public Point getLastSelectedPos() {
int n = getLastSelectedSequenceIndex();
if(n != -1){
return new Point(sequences.get(n).getLastSelectedPosition(), n);
}else{
return null;
}
}
public Point getFirstSelectedUngapedPos() {
for(int n = 0; n < sequences.size(); n++){
if(sequences.get(n).hasSelection()){
Sequence firstSelected = sequences.get(n);
int position = firstSelected.getFirstSelectedPosition();
int ungaped = firstSelected.getUngapedPos(position);
return new Point(ungaped, n);
}
}
return null;
}
public void setSelectionWithin(Rectangle bounds) {
changeSelection(bounds.x, bounds.y, bounds.x + bounds.width, bounds.y + bounds.height, true);
}
public boolean hasFullySelectedSequences() {
for(Sequence seq: sequences){
if(seq.isAllSelected()){
return true;
}
}
return false;
}
public int getSelectedColumnCount() {
BitSet colSelect = new BitSet();
for(Sequence seq: sequences){
if(seq.hasSelection()){
int[] allSelected = seq.getSelectedPositions();
for(int nextInt: allSelected){
colSelect.set(nextInt);
}
}
}
return colSelect.cardinality();
}
public int getSelectedSequencesCount() {
int count = 0;
for(Sequence seq: sequences){
if(seq.hasSelection()){
count ++;
}
}
return count;
}
public String getSelectionNames() {
String names = "";
for(Sequence seq: sequences){
if(seq.hasSelection()){
names += seq.getName() + LF;
}
}
// remove last LF
if(names.length() > 0){
names = StringUtils.removeEnd(names, LF);
}
return names;
}
public int getFirstSelectedWholeColumn() {
Point pos = getFirstSelectedPos();
if(pos == null){
return -1;
}
else{
for(Sequence seq: sequences){
// seq has to be long enough
if(seq.getLength() > pos.x && !seq.hasSelection()){
return -1;
}
}
return pos.x;
}
}
public int getLastSelectedWholeColumn() {
Point pos = getLastSelectedPos();
if(pos == null){
return -1;
}
else{
for(Sequence seq: sequences){
// seq has to be long enough
if(seq.getLength() > pos.x && !seq.hasSelection()){
return -1;
}
}
return pos.x;
}
}
public void clearAllSelectionInSequenceWithIndex(int index) {
sequences.get(index).clearAllSelection();
fireSelectionChanged(index, index);
}
public void clearSequenceSelection() {
Rectangle oldSelectRectangle = getSelectionBounds();
for(Sequence seq: sequences){
seq.clearAllSelection();
}
if(oldSelectRectangle != null){
fireSelectionChanged(oldSelectRectangle, false);
}
}
/** {@inheritDoc} */
public void addAlignmentSelectionListener(AlignmentSelectionListener l) {
listenerList.add(AlignmentSelectionListener.class, l);
}
/** {@inheritDoc} */
public void removeSequenceListSelectionListener(AlignmentSelectionListener l) {
listenerList.remove(AlignmentSelectionListener.class, l);
}
/**
* Returns an array of all the list selection listeners
* registered on this <code>DefaultListSelectionModel</code>.
*
* @return all of this model's <code>ListSelectionListener</code>s
* or an empty
* array if no list selection listeners are currently registered
*
* @see #addListSelectionListener
* @see #removeListSelectionListener
*
* @since 1.4
*/
public AlignmentSelectionListener[] getAlignmentSelectionListeners() {
return listenerList.getListeners(AlignmentSelectionListener.class);
}
/**
* Notifies <code>ListSelectionListeners</code> that the value
* of the selection, in the closed interval <code>firstIndex</code>,
* <code>lastIndex</code>, has changed.
*/
protected void fireSelectionChanged(int firstIndex, int lastIndex) {
fireSelectionChanged(firstIndex, lastIndex, getValueIsAdjusting());
}
protected void fireSelectionChangedAll() {
fireSelectionChanged(0, sequences.size(), getValueIsAdjusting());
}
private boolean getValueIsAdjusting() {
// TODO Auto-generated method stub
return false;
}
/**
* @param firstIndex the first index in the interval
* @param lastIndex the last index in the interval
* @param isAdjusting true if this is the final change in a series of
* adjustments
* @see EventListenerList
*/
protected void fireSelectionChanged(int index0, int index1, boolean isAdjusting)
{
Rectangle rect = new Rectangle(0, Math.min(index0, index1), sequences.getLongestSequenceLength(), Math.abs(index0 - index1));
fireSelectionChanged(rect, isAdjusting);
}
private void fireSelectionChanged(Sequence sequence, boolean isAdjusting) {
int index = sequences.indexOf(sequence);
fireSelectionChanged(index, index, isAdjusting);
}
/*
private void fireSelectionChanged(int x1, int y1, int x2, int y2, boolean isAdjusting) {
*/
private void fireSelectionChanged(Rectangle rect, boolean isAdjusting) {
logger.info("fire Selection changed + rect" + rect);
Object[] listeners = listenerList.getListenerList();
AlignmentSelectionEvent e = null;
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == AlignmentSelectionListener.class) {
if (e == null) {
e = new AlignmentSelectionEvent(this, rect, isAdjusting);
}
((AlignmentSelectionListener)listeners[i+1]).selectionChanged(e);
}
}
// Also let ListView know
// delegateLSM.fireValueChanged(rect.y, rect.y + rect.height, isAdjusting);
if(AliView.getActiveWindow() != null){
logger.info("Time from last endTim " + (System.currentTimeMillis() - AliView.getActiveWindow().getLastPaneEndTime()) + " milliseconds");
}
}
/**
* Returns an array of all the objects currently registered as
* <code><em>Foo</em>Listener</code>s
* upon this model.
* <code><em>Foo</em>Listener</code>s
* are registered using the <code>add<em>Foo</em>Listener</code> method.
* <p>
* You can specify the <code>listenerType</code> argument
* with a class literal, such as <code><em>Foo</em>Listener.class</code>.
* For example, you can query a <code>DefaultListSelectionModel</code>
* instance <code>m</code>
* for its list selection listeners
* with the following code:
*
* <pre>ListSelectionListener[] lsls = (ListSelectionListener[])(m.getListeners(ListSelectionListener.class));</pre>
*
* If no such listeners exist,
* this method returns an empty array.
*
* @param listenerType the type of listeners requested;
* this parameter should specify an interface
* that descends from <code>java.util.EventListener</code>
* @return an array of all objects registered as
* <code><em>Foo</em>Listener</code>s
* on this model,
* or an empty array if no such
* listeners have been added
* @exception ClassCastException if <code>listenerType</code> doesn't
* specify a class or interface that implements
* <code>java.util.EventListener</code>
*
* @see #getListSelectionListeners
*
* @since 1.3
*/
public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
return listenerList.getListeners(listenerType);
}
public void setTempSelection(Rectangle newSelectRect){
if(tempSelectionRect != null && tempSelectionRect.equals(newSelectRect)){
// old selection is the same - do nothing
}
if(tempSelectionRect == null){
tempSelectionRect = newSelectRect;
tempSelectionMaxRect = tempSelectionRect;
}
else{
tempSelectionRect = newSelectRect;
tempSelectionMaxRect.add(tempSelectionRect);
fireSelectionChanged(tempSelectionMaxRect, false);
}
}
public Rectangle getTempSelection() {
return tempSelectionRect;
}
public void clearTempSelection() {
this.tempSelectionRect = null;
}
public boolean isSequenceAtLeastPartlyAffectedByTempSelection(int index){
if(tempSelectionRect == null){
return false;
}
if(index >= tempSelectionRect.getMinY() && index <= tempSelectionRect.getMaxY()){
return true;
}
else{
return false;
}
}
public boolean isSequenceAtLeastPartlySelected(int index) {
if(! sequences.rangeCheck(0, index)){
return false;
}
if(isSequenceAtLeastPartlyAffectedByTempSelection(index)){
return true;
}
return sequences.get(index).hasSelection();
}
public void translateSelection(AlignmentMeta aliMeta) {
for(Sequence seq: sequences){
if(seq.hasSelection()){
if(! seq.isAllSelected()){
int[] selection = seq.getSelectedPositions();
int[] translated = aliMeta.translatePositions(selection);
seq.clearAllSelection();
for(int pos: translated){
seq.setSelectionAt(pos);
}
}
}
}
fireSelectionChangedAll();
}
public void reTranslateSelection(AlignmentMeta aliMeta) {
for(Sequence seq: sequences){
if(seq.hasSelection()){
if(! seq.isAllSelected()){
int[] selection = seq.getSelectedPositions();
int[] translated = aliMeta.reTranslatePositions(selection);
seq.clearAllSelection();
for(int pos: translated){
seq.setSelectionAt(pos);
}
}
}
}
fireSelectionChangedAll();
}
}