/*
* ConcreteSplitViewer program for analazing splits.
* Copyright (C) 2006-2007 Mytinski Leonid (Leonid.Mytinski@gmail.com)
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
/*
* AthleteListModel.java
*
* Created on 3 Июль 2006 г., 16:15
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package ru.concretesoft.concretesplitviewer;
import java.awt.Color;
import java.awt.FontMetrics;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JList;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
/**
*
*
* @author Mytinski Leonid
*
* Класс описывающий модель данных для списка спортсменов.
* Модель содержит объекты типа AthleteIcon.
*/
public class AthleteListModel implements ListModel,ListSelectionModel,ListSelectionListener,AthleteListener{
private Collection<ListDataListener> listeners;
private List<AthleteIcon> athletes;
private Collection<ListSelectionListener> selectionListeners;
private Distance distance;
private int[] viewLaps;
private JList groupsList;
private int anchor;
private int lead;
private boolean[] selected;
private FontMetrics fontMetrics;
private static final Color[] colors = new Color[]{
Color.RED,
Color.BLUE,
Color.GREEN,
Color.PINK,
Color.CYAN,
Color.MAGENTA,
Color.ORANGE
};
public AthleteListModel(FontMetrics fM){
fontMetrics = fM;
listeners = new LinkedList<ListDataListener>();
selectionListeners = new LinkedList<ListSelectionListener>();
distance=null;
selected = new boolean[0];
}
public int getSize() {
if(athletes==null)return 0;
return athletes.size();
}
/**
* Метод получает набор спортсменов, формирует данные для модели
* добовляет эти данные в модель и оповещает всех слушателей изменений модели
*
* @param as <code>Collection</code> of athletes, whose sould be in model
*/
public void setAthletes(Collection<Athlete> as){
athletes = new ArrayList<AthleteIcon>();
distance = null;
if((as!=null)&&(as.size()>0)){
selected = new boolean[as.size()];
Iterator<Athlete> itA = as.iterator();
while(itA.hasNext()){
Athlete tempAthlete = itA.next();
if (distance == null)
distance = tempAthlete.getGroup().getDistance();
AthleteIcon temp = new AthleteIcon(tempAthlete, fontMetrics);
athletes.add(temp);
temp.getAthlete().addAthleteListener(this);
}
setDifferntColorsForAthletes(athletes);
allLaps();
}
else{
selected = null;
viewLaps=null;
}
Iterator<ListDataListener> it = listeners.iterator();
while(it.hasNext()){
it.next().contentsChanged(new ListDataEvent(this,ListDataEvent.CONTENTS_CHANGED,0,athletes.size()));
}
}
public Object getElementAt(int index) {
return athletes.get(index);
}
public void addListDataListener(ListDataListener l) {
listeners.add(l);
}
public void removeListDataListener(ListDataListener l) {
listeners.remove(l);
}
/**
* Метод возвращает всех спортсменов которые содержаться в моделе.
*
* @return all athletes from this models
*/
public List<Athlete> getAthletes(){
if(athletes==null) return null;
List<Athlete> at = new ArrayList<Athlete>();
Iterator<AthleteIcon> it = athletes.iterator();
while(it.hasNext()){
at.add(it.next().getAthlete());
}
return at;
}
private void allLaps(){
if(distance==null){
viewLaps = null;
} else{
viewLaps = new int[distance.getNumberOfCP()];
for(int i=0;i<distance.getNumberOfCP();i++){
viewLaps[i] = i+1;
}
}
}
/**
* Restores all splits from distance
*
* @return <code>true</code> if restoring succesfull else <code>false</code>. Not succesfull restoring if distance not set.
*/
public boolean restoreAllSplits(){
if(distance==null)
return false;
else{
setViewSplits(null);
return true;
}
}
/** Gets array of viewing laps
*
* @return array of viewing laps or <code>null</code> if all splits not restored
*
* @see restoreAllSplits();
*/
public int[] getViewingSplits(){
boolean ok = true;
if(viewLaps==null)
ok = restoreAllSplits();
else;
return ok ? (int[])viewLaps.clone() : null ;
}
/** Removes splits for control point. If only one lap in model, then do nothing
*
* @param n determin control point's number for lap that should be removed
*/
public void removeSplitsForN(int n){
int i = 0;
if(viewLaps.length<=1)//If only one lap in view than exit
return;
else;
//Find position of lap that should be removed
//Найти положение перегона, который нужно удалить. Ну и конструкция =)
try{
while(viewLaps[i] != n)i++;
} catch(java.lang.ArrayIndexOutOfBoundsException e){ return ;}
int[] splits = new int[viewLaps.length-1];
for(int j=0;j<i;j++) splits[j] = viewLaps[j];
for(int j=i;j<splits.length;j++) splits[j]=viewLaps[j+1];
setViewSplits(splits);
}
/** Added viewing lap for control point
*
* @param n contol point's number
*/
public void addSplitsForN(int n){
if(n>distance.getNumberOfCP()) return;//If CP's number greter than number of control points
for(int i=0;i<viewLaps.length;i++){
if(n<viewLaps[i]){
int[] splOld = viewLaps;
viewLaps = new int[splOld.length+1];
for(int j=0;j<i;j++) viewLaps[j] = splOld[j];
viewLaps[i] = n;
for(int j=i+1;j<viewLaps.length;j++) viewLaps[j]=splOld[j-1];
setViewSplits(viewLaps);
// Iterator<ListDataListener> it = list.iterator();
// while(it.hasNext())
// it.next().contentsChanged(new ListDataEvent(this,ListDataEvent.CONTENTS_CHANGED,0,athletes.size()));
return;
}else;
}
//If we not add lap yet than CP's number greter then the last selected CP's number
int[] splOld = viewLaps;
viewLaps = new int[splOld.length+1];
for(int j=0;j<splOld.length;j++) viewLaps[j] = splOld[j];
viewLaps[splOld.length] = n;
setViewSplits(viewLaps);
}
/**
* Метод устанавливает выбранные перегоны, и вызывает изменение отображаемой информации
*
* @param spl splits that should be viewed
*/
public void setViewSplits(int[] spl){
if(spl == null){
allLaps();
} else{
viewLaps = (int[]) spl.clone();
}
if(athletes != null){
Iterator<AthleteIcon> it = athletes.iterator();
while(it.hasNext()){
AthleteIcon aI = it.next();
aI.setSplits(viewLaps);
}
recolculateList();
}
}
private void recolculateList(){
if(athletes != null){
List<AthleteIcon> athletesNew = new ArrayList<AthleteIcon>();
Iterator<AthleteIcon> it = athletes.iterator();
while(it.hasNext()){
if(athletesNew.size()==0)
athletesNew.add(it.next());
else{
int j=0;
AthleteIcon first = athletesNew.get(j);
AthleteIcon cur = it.next();
int size = athletesNew.size();
int curTime = cur.getTotalTime().getTimeInSeconds();
while(curTime>=first.getTotalTime().getTimeInSeconds()){
j++;
if(j==size) break;
first = athletesNew.get(j);
}
athletesNew.add(j, cur);
}
}
athletes = athletesNew;
if(athletes.size()>0){
int timeBefore = athletes.get(0).getTotalTime().getTimeInSeconds();
athletes.get(0).setPosition(1);
int position=1;
for(int j=1;j<athletes.size();j++){
int curTime = athletes.get(j).getTotalTime().getTimeInSeconds();
if(curTime>timeBefore){
timeBefore = curTime;
position = j+1;
}
selected[j] = athletes.get(j).isSelected();
athletes.get(j).setPosition(position);
}
}
Iterator<ListDataListener> it2 = listeners.iterator();
while(it2.hasNext()){
it2.next().contentsChanged(new ListDataEvent(this,ListDataEvent.CONTENTS_CHANGED,0,athletes.size()));
}
}
}
/**
*
* @return distance that using in that model
*/
public Distance getDistance(){
return distance;
}
/** Method returns all athletes from this model
* Метод возвращает весь нобор данных модели
*
* @return all icons of athletes from this model
*/
public List<AthleteIcon> getValues(){
return athletes;
}
/**
* Метод возвращает выбранные данные
*
* @return all selected icons of athletes
*/
public Object[] getSelectedValues(){
List<AthleteIcon> value = new ArrayList<AthleteIcon>();
if(athletes != null){
Iterator<AthleteIcon> it = athletes.iterator();
while(it.hasNext()){
AthleteIcon aI = it.next();
if(aI.isSelected()) value.add(aI);
}
}
return value.toArray(new AthleteIcon[value.size()]);
}
public void setSelectionInterval(int index0, int index1) {
anchor = index0;
lead = index1;
for(int i=0;i<selected.length;i++){
if((i>=index0)&&(i<=index1)){
selected[i] = !selected[i];//invert status
athletes.get(i).setSelected(selected[i]);//set new status
} else;
}
Iterator<ListSelectionListener> it = selectionListeners.iterator();
while(it.hasNext()){
it.next().valueChanged(new ListSelectionEvent(this,0,selected.length-1,false));
}
}
public void addSelectionInterval(int index0, int index1) {
setSelectionInterval(index0, index1);
}
public void removeSelectionInterval(int index0, int index1) {
setSelectionInterval(index0, index1);
}
public int getMinSelectionIndex() {
for(int i=0;i<selected.length;i++){
if(selected[i]) return i;
}
return -1;
}
public int getMaxSelectionIndex() {
for(int i=selected.length-1;i>=0;i--){
if(selected[i]) return i;
}
return -1;
}
public boolean isSelectedIndex(int index) {
return selected[index];
}
public int getAnchorSelectionIndex() {
return anchor;
}
public void setAnchorSelectionIndex(int index) {
anchor = index;
}
public int getLeadSelectionIndex() {
return lead;
}
public void setLeadSelectionIndex(int index) {
lead = index;
}
public void clearSelection() {
if(selected==null){
}else{
for(int i=0;i<selected.length;i++){
selected[i]=false;
athletes.get(i).setSelected(false);
}
Iterator<ListSelectionListener> it = selectionListeners.iterator();
while(it.hasNext()){
it.next().valueChanged(new ListSelectionEvent(this,0,selected.length-1,false));
}
}
}
public boolean isSelectionEmpty() {
for(int i=0;i<selected.length;i++){
if(selected[i]) return false;
}
return true;
}
public void insertIndexInterval(int index, int length, boolean before) {
}
public void removeIndexInterval(int index0, int index1) {
}
public void setValueIsAdjusting(boolean valueIsAdjusting) {
}
public boolean getValueIsAdjusting() {
return false;
}
public void setSelectionMode(int selectionMode) {
}
public int getSelectionMode() {
return MULTIPLE_INTERVAL_SELECTION;
}
public void addListSelectionListener(ListSelectionListener x) {
selectionListeners.add(x);
}
public void removeListSelectionListener(ListSelectionListener x) {
selectionListeners.remove(x);
}
public void valueChanged(ListSelectionEvent e) {
valueChanged();
}
private void valueChanged(){
setAthletes(null);
Object[] g1 = groupsList.getSelectedValues();
List<Athlete> all = new ArrayList<Athlete>();
for(int i=0;i<g1.length;i++){
Iterator<Athlete> it = ((Group)g1[i]).getAthletes().iterator();
while(it.hasNext()){
if(all.size()==0)
all.add(it.next());
else{
int j=0;
Athlete first = all.get(j);
Athlete cur = it.next();
int size = all.size();
int curTime = cur.getTotalTime().getTimeInSeconds();
while(curTime>first.getTotalTime().getTimeInSeconds()){
j++;
if(j==size) break;
first = all.get(j);
}
all.add(j, cur);
}
}
}
clearSelection();
setAthletes(all);
setViewSplits(null);
}
public JList getGroupsList() {
return groupsList;
}
public void setGroupsList(JList groupsList) {
this.groupsList = groupsList;
}
/** Method for getting list atheletes for one lap
*
*
*
*/
public Collection<AthleteIcon> getOneLap(int number){
List<AthleteIcon> athletesByOneLap = new ArrayList<AthleteIcon>();
Iterator<AthleteIcon> iter = this.athletes.iterator();
while(iter.hasNext()){
AthleteIcon aI = iter.next();
int size = athletesByOneLap.size();
if(size==0){
athletesByOneLap.add(aI);
} else {
int i = findNearEl(athletesByOneLap, aI.getAthlete(), number, 0, size-1);
athletesByOneLap.add(i, aI);
}
}
return athletesByOneLap;
}
private int findNearEl(List<AthleteIcon> as, Athlete a, int number, int min, int max){
int size = max - min;
int d = size/2;
if(size==0){
Athlete atCur = as.get(min).getAthlete();
int c = atCur.getLap(number).compareTo(a.getLap(number));
if(c<0)
return min+1;
else
return min;
} else if(size==1){
Athlete atCur = as.get(min).getAthlete();
int c = atCur.getLap(number).compareTo(a.getLap(number));
if(c<0){
atCur = as.get(max).getAthlete();
c = atCur.getLap(number).compareTo(a.getLap(number));
if(c < 0)
return max+1;
else
return max;
} else{
return min;
}
}else{
Athlete atCur = as.get(min+d).getAthlete();
int c = atCur.getLap(number).compareTo(a.getLap(number));
if(c<0){
return findNearEl(as, a, number, min+d, max);
} else if(c>0){
return findNearEl(as, a, number, min, min+d);
} else{
return min+d;
}
}
}
/** Method sets for icons of athletes differnt colors
*
*
* @param athletes athletes whose icons should get new color
*
*/
public static void setDifferntColorsForAthletes(Collection<AthleteIcon> athletes){
int i=0;
Iterator<AthleteIcon> iterator = athletes.iterator();
while(iterator.hasNext()){
Color col;
if(i<colors.length){
col = colors[i];
}
else{
Color tmp = colors[i % colors.length];
int red = tmp.getRed()-(int)(tmp.getRed()/3*Math.random());
int green = tmp.getGreen()-(int)(tmp.getGreen()/3*Math.random());
int blue = tmp.getBlue()-(int)(tmp.getBlue()/3*Math.random());
col = new Color(red,green,blue);
}
iterator.next().setColor(col);
i++;
}
}
public void splitsChanged() {
recolculateList();
}
}