package alma.acs.gui.widgets;
import java.awt.BorderLayout;
import java.awt.FontMetrics;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.table.AbstractTableModel;
import alma.acs.gui.util.threadsupport.EDTExecutor;
/*
ALMA - Atacama Large Millimiter Array
* Copyright (c) European Southern Observatory, 2013
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/**
* <CODE>CheckList</CODE> is widget that shows a list of entries and a check box to enable/disable
* each of them.
* It has been written to replace <CODE>com.cosylab.gui.components.r2.JCheckList</CODE> that is not
* maintained anymore.
* <P>
* The user is presented with a list of items each of which has a associated check box:
* each item in the list can be selectively enabled/disabled by using the checkbox.
* <P>
* <CODE>CheckList</CODE> is a table of 2 columns: the first one is a boolean whose state
* says if the item is selected or not. The second one is a not editable string.
* <P>
* The model of this widget accepts any not <code>null</code> java {@link Object}'s.
* The text shown in the second column is generated by invoking {@link Object#toString()}.
*
* @author acaproni
* @since ACS 12.1
*/
public class CheckList extends JComponent {
/**
* Each entry in the list is composed of a {@link Boolean} and a {@link Object}
* @author acaproni
*
*/
public class CheckListTableEntry {
/**
* The activation state
*/
private boolean active;
/**
* The not <code>null</code> entry
*/
private Object item;
/**
* Constructor
*
* @param active The activation state
* @param item The item in the list
*/
public CheckListTableEntry(boolean active, Object item) {
if (item==null) {
throw new IllegalArgumentException("Null entries are not allowed");
}
this.active=active;
this.item=item;
}
/**
* Activate/deactivate the entry.
*
* @param active if <code>true</code> the entry is active
*/
public void activate(boolean active) {
this.active=active;
}
/**
* @return the activation state of the entry
*/
public boolean isActive() {
return active;
}
/**
* Getter
*
* @return the item
*/
public Object getItem() {
return item;
}
/**
* Setter
*
* @param item The not <code>null</code> item in the list
*/
public void setItem(Object item) {
if (item==null) {
throw new IllegalArgumentException("Null entries are not allowed");
}
this.item=item;
}
}
/**
* The table model for the <CODE>CheckList</CODE> widget.
* <P>
* Each entry is composed of the {@link Object} added to the widget
* and a {@link Boolean} representing is activation state.
*
* @author acaproni
*
*/
private class CheckListTableModel extends AbstractTableModel {
/**
* The items in the widget.
*/
private final List<CheckListTableEntry> items = Collections.synchronizedList(new Vector<CheckListTableEntry>());
/**
* Add the passed object to the items of the list.
* <P>
* The object can't be <code>null</code>.
*
* @param active The activation state
* @param obj The not <code>null</code> object to add
* @return the added entry
*/
public CheckListTableEntry addElement(boolean active, Object obj) {
CheckListTableEntry ret=new CheckListTableEntry(active,obj);
items.add(ret);
EDTExecutor.instance().execute(new Runnable() {
@Override
public void run() {
fireTableDataChanged();
}
});
return ret;
}
/**
* @see javax.swing.table.TableModel#getRowCount()
*/
@Override
public int getRowCount() {
return items.size();
}
/**
* @see javax.swing.table.TableModel#getColumnCount()
*/
@Override
public int getColumnCount() {
return 2;
}
/**
* @see javax.swing.table.TableModel#getValueAt(int, int)
*/
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
synchronized (items) {
if (rowIndex<0 || rowIndex>=items.size()) {
throw new IllegalArgumentException("Row index ("+rowIndex+") out of range (size is "+items.size()+")");
}
if (columnIndex==0) {
return items.get(rowIndex).isActive();
} else {
return items.get(rowIndex).getItem().toString();
}
}
}
/**
* @see javax.swing.table.TableModel#isCellEditable(int, int)
*/
@Override
public boolean isCellEditable(int row, int column) {
return (column==0);
}
/**
* @see javax.swing.table.TableModel#getColumnClass(int)
*/
@Override
public Class getColumnClass(int column) {
return (column == 0) ? Boolean.class : String.class;
}
/**
*
* @return A array of activation states
*/
public boolean[] getActivationStates() {
boolean[] ret;
synchronized (items) {
ret = new boolean[items.size()];
}
for (int t=0; t<ret.length; t++) {
ret[t]=items.get(t).isActive();
}
return ret;
}
/**
* @return A array with all the items in the list
*/
public List<CheckListTableEntry> getEntries() {
return items;
}
/**
* @return A list with all the active items in the list
*/
public List<CheckListTableEntry> getCheckedEntries() {
Vector<CheckListTableEntry> ret = new Vector<CheckList.CheckListTableEntry>();
synchronized (items) {
for (CheckListTableEntry entry: items) {
if (entry.isActive()) {
ret.add(entry);
}
}
}
return ret;
}
/**
* @return A list with all the non-active items in the list
*/
public List<CheckListTableEntry> getUnCheckedEntries() {
Vector<CheckListTableEntry> ret = new Vector<CheckList.CheckListTableEntry>();
synchronized (items) {
for (CheckListTableEntry entry: items) {
if (!entry.isActive()) {
ret.add(entry);
}
}
}
return ret;
}
/**
* Remove all the entries from the widget
*/
public void clear() {
EDTExecutor.instance().execute(new Runnable() {
@Override
public void run() {
items.clear();
fireTableDataChanged();
}
});
}
/**
* @return The number of items whose state is active
*/
public int getActiveItemsNumber() {
int ret=0;
synchronized (items) {
for (CheckListTableEntry entry: items) {
if (entry.isActive()) {
ret++;
}
}
}
return ret;
}
/**
*
* @param index The index of the entry in the list
* @return the entry in the passed position
*
* @see java.util.List#get(int)
*/
public CheckListTableEntry get(int index) {
synchronized (items) {
if (index<0 || index>=items.size()) {
throw new IllegalArgumentException("Invalid index of item to get: "+index+"; size of list is "+items.size());
}
}
return items.get(index);
}
/**
* Activate or deactivate all the items in the widget
*
* @param active if <code>true</code> activate the items, otherwise deactivate
*/
public void activateAll(final boolean active) {
EDTExecutor.instance().execute(new Runnable() {
@Override
public void run() {
synchronized (items) {
for (CheckListTableEntry entry: items) {
entry.activate(active);
}
}
fireTableDataChanged();
}
});
}
/**
* @param index the index of the item to remove
* @return The removed item or <code>null</code> if the item was not in the list
* @see java.util.List#remove(int)
*/
public CheckListTableEntry removeEntry(final int index) {
final class Remover implements Runnable {
public CheckListTableEntry ret=null;
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
ret=items.remove(index);
fireTableDataChanged();
}
}
Remover remover = new Remover();
try {
EDTExecutor.instance().executeSync(remover);
} catch (Throwable t) {
System.err.println("Error remvoving item "+index);
t.printStackTrace(System.err);
return null;
}
return remover.ret;
}
public void refreshEntry(final int index) {
try {
EDTExecutor.instance().executeSync(new Runnable() {
@Override
public void run() {
fireTableRowsUpdated(index, index);
}
});
} catch (Throwable t) {
System.err.println("Error refreshing entry in position "+index);
t.printStackTrace();
}
}
/* (non-Javadoc)
* @see javax.swing.table.AbstractTableModel#getColumnName(int)
*/
@Override
public String getColumnName(int column) {
if (column==0) {
return "Activation state";
} else {
return "Object";
}
}
/* (non-Javadoc)
* @see javax.swing.table.AbstractTableModel#setValueAt(java.lang.Object, int, int)
*/
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
items.get(rowIndex).activate((Boolean)aValue);
super.setValueAt(aValue, rowIndex, columnIndex);
}
}
/**
* The model
*/
private final CheckListTableModel model = new CheckListTableModel();
/**
* The table showing the entries
*/
private final JTable table = new JTable(model);
/**
* Constructor
*/
public CheckList() {
initGUI();
}
/**
* Initialize the GUI
*/
private void initGUI() {
EDTExecutor.instance().execute(new Runnable() {
public void run() {
setLayout(new BorderLayout());
add(new JScrollPane(table), BorderLayout.CENTER);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
FontMetrics fm = table.getFontMetrics(table.getFont());
int strw = fm.stringWidth(model.getColumnName(0))+10;
table.getColumnModel().getColumn(0).setPreferredWidth(strw);
table.getColumnModel().getColumn(0).setMaxWidth(strw);
table.getColumnModel().getColumn(0).setMinWidth(strw);
}
});
}
/**
*
* @return The activation states of the items in the widget
*/
public boolean[] getActivationStates() {
return model.getActivationStates();
}
/**
* @return The entries in the list
*
* @see alma.acs.gui.widgets.CheckList.CheckListTableModel#getEntries()
*/
public List<CheckListTableEntry> getEntries() {
return model.getEntries();
}
/**
* @return
* @see alma.acs.gui.widgets.CheckList.CheckListTableModel#getCheckedEntries()
*/
public List<CheckListTableEntry> getCheckedEntries() {
return model.getCheckedEntries();
}
/**
* @return
* @see alma.acs.gui.widgets.CheckList.CheckListTableModel#getUnCheckedEntries()
*/
public List<CheckListTableEntry> getUnCheckedEntries() {
return model.getUnCheckedEntries();
}
/**
* Remove all the entries from the widget
*/
public void clear() {
model.clear();
}
/**
* Add a element to the list with its initial activation state.
*
* @param active The activation state of the item
* @param obj The not <code>null</code> item to add
* @return the added entry
* @see alma.acs.gui.widgets.CheckList.CheckListTableModel#addElement(boolean, java.lang.Object)
*/
public CheckListTableEntry addElement(boolean active, Object obj) {
return model.addElement(active, obj);
}
/**
* @return The number of elements (regardless of their activation state) in the widget
*/
public int getItemsSize() {
return model.getRowCount();
}
/**
* @return
* @see alma.acs.gui.widgets.CheckList.CheckListTableModel#getActiveItemsNumber()
*/
public int getActiveItemsNumber() {
return model.getActiveItemsNumber();
}
/**
* @return The item selected by the user
*/
public CheckListTableEntry getSelectedEntry() {
int selectedRow=table.getSelectedRow();
if (selectedRow==-1) {
return null;
}
return model.get(selectedRow);
}
/**
*
* @return The number of the row selected by the user
*/
public int getSelectedIndex() {
return table.getSelectedRow();
}
/**
* Activate ar deactivate all the itemes of the widget
*
* @param active if <code>true</code> activate the items
* @see alma.acs.gui.widgets.CheckList.CheckListTableModel#activateAll(boolean)
*/
public void activateAll(boolean active) {
model.activateAll(active);
}
/**
* Remove the entry in the passed position from the widget.
*
* @param index The index of the item to remove
* @return The removed entry
* @see alma.acs.gui.widgets.CheckList.CheckListTableModel#removeEntry(int)
*/
public CheckListTableEntry removeEntry(int index) {
return model.removeEntry(index);
}
/**
* Remove the selected entry from the widget
*
* @return The removed entry or <code>null</code> if the entry was not found
*/
public CheckListTableEntry removeSelectedEntry() {
int selectedRow=table.getSelectedRow();
if (selectedRow==-1) {
return null;
}
return removeEntry(selectedRow);
}
/**
* Update the selected entry.
* <P>
* This method is meant to be called when the selected entry in the model
* has been changed to trigger a refresh of the list.
*/
public void updateSelectedEntry() {
int selectedRow=table.getSelectedRow();
if (selectedRow>=0) {
model.refreshEntry(selectedRow);
}
}
}