package util;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeSelectionListener;
import javax.swing.table.TableColumnModel;
import javax.swing.tree.TreePath;
import model.ErrorLogger;
import org.jdesktop.swingx.JXTreeTable;
import org.jdesktop.swingx.action.AbstractActionExt;
import org.jdesktop.swingx.table.ColumnControlButton;
import org.jdesktop.swingx.table.ColumnControlPopup;
import org.jdesktop.swingx.treetable.DefaultTreeTableModel;
/**
* Class the represents a treetable containing experiment and file data
*/
public class TreeTable extends JPanel {
private static final long serialVersionUID = 6033511181052590303L;
private JXTreeTable table;
private ArrayList<String> headings;
private ArrayList<ExperimentData> experiments;
private HashMap<String, Boolean> sortingOrders;
private CopyOnWriteArrayList<String> hiddenHeadings;
private ArrayList<String> visibleHeadings;
private ArrayList<JCheckBox> columnCheckBoxes;
/**
* Tree Table empty constructor
*/
public TreeTable() {
this.setLayout(new BorderLayout());
initiateJXTreeTable();
}
public void addTreeSelectionListener(TreeSelectionListener tsl) {
table.addTreeSelectionListener(tsl);
}
/**
* Tree Table constructor with input content
*
* @param experimentData
* - the tree table content
*/
public TreeTable(ArrayList<ExperimentData> experimentData) {
this.setLayout(new BorderLayout());
initiateJXTreeTable();
setContent(experimentData);
}
/**
* Method for initating the JXTreeTable
*/
private void initiateJXTreeTable() {
table = new JXTreeTable() {
private static final long serialVersionUID = -5027164951558722985L;
public boolean getScrollableTracksViewportWidth() {
return getPreferredSize().width < getParent().getWidth();
}
};
table.setOpaque(true);
table.setLeafIcon(null);
table.setClosedIcon(null);
table.setOpenIcon(null);
/* Custom column control for hiding columns */
ColumnControlButton controlButton = new ColumnControlButton(table) {
private static final long serialVersionUID = 1022478445883845370L;
@Override
protected ColumnControlPopup createColumnControlPopup() {
return (new NFColumnControlPopup());
}
class NFColumnControlPopup extends DefaultColumnControlPopup {
@Override
public synchronized void addVisibilityActionItems(
List<? extends AbstractActionExt> actions) {
if (!actions.isEmpty()) {
JPopupMenu popupMenu = getPopupMenu();
/* Hide all columns button */
JButton deselectButton = new JButton(
"Hide all annotations");
deselectButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
for (JCheckBox checkBox : columnCheckBoxes) {
if (checkBox.isEnabled()
&& checkBox.isSelected()) {
checkBox.setSelected(false);
}
}
}
});
JButton selectButton = new JButton(
"Show all annotations");
selectButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
for (JCheckBox checkBox : columnCheckBoxes) {
if (checkBox.isEnabled()
&& !checkBox.isSelected()) {
checkBox.setSelected(true);
}
}
}
});
popupMenu.add(selectButton);
popupMenu.add(deselectButton);
// Add some space between the checkboxes and the
// buttons.
popupMenu.add(new JLabel("\n"));
/* Add hide column checkboxes */
for (JCheckBox checkBox : columnCheckBoxes) {
getPopupMenu().add(checkBox);
}
// Add some space between the checkboxes and the
// buttons.
popupMenu.add(new JLabel("\n"));
// Add expand all button with listener.
JButton expandAllButton = new JButton("Expand all");
expandAllButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
table.expandAll();
}
});
popupMenu.add(expandAllButton);
// Add collapse all button with listener.
JButton collapseAllButton = new JButton("Collapse all");
collapseAllButton
.addActionListener(new ActionListener() {
@Override
public void actionPerformed(
ActionEvent actionEvent) {
table.collapseAll();
}
});
popupMenu.add(collapseAllButton);
popupMenu.repaint();
popupMenu.revalidate();
}
}
public synchronized void addAdditionalActionItems(
List<? extends Action> actions) {
/*
* Dummy method to prevent treetable from adding unwanted
* alternatives from controlPopup
*/
}
}
};
table.setColumnControl(controlButton);
table.setColumnControlVisible(true);
table.setShowGrid(true, true);
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
/* Add a mouse listener to check for column sorting */
table.getTableHeader().addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 1 && experiments != null) {
TableColumnModel cModel = table.getColumnModel();
int column = cModel.getColumnIndexAtX(e.getX());
sortData(column);
createTreeStructure();
}
}
});
/* Add a scroll pane */
JScrollPane scrollPane = new JScrollPane(table,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollPane.getViewport().setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
add(scrollPane, BorderLayout.CENTER);
}
/**
* set new content for the tree table
*
* @param experimentData
* - new content
*/
public synchronized void setContent(ArrayList<ExperimentData> experimentData) {
/* Initiate the column sorting orders */
sortingOrders = new HashMap<>();
headings = new ArrayList<>();
/* If the input experiment data is not null or empty */
if (experimentData != null && experimentData.size() > 0) {
experiments = experimentData;
/* Retreive the headings from the experiment data */
int nrOfColumns = 1;
headings.add("ExpID");
for (int i = 0; i < experiments.size(); i++) {
for (AnnotationDataValue annotation : experiments.get(i).annotations) {
if (!headings.contains(annotation.name)) {
headings.add(annotation.name);
nrOfColumns++;
}
}
}
/* Initate the sorting orders as descending */
for (int i = 0; i < nrOfColumns; i++) {
sortingOrders.put(headings.get(i), true);
}
}
columnCheckBoxes = new ArrayList<>();
for (final String heading : headings) {
JCheckBox checkBox = new JCheckBox(heading);
checkBox.setSelected(true);
if (heading.equals("ExpID")) {
checkBox.setEnabled(false);
}
checkBox.addItemListener(new ItemListener() {
@Override
public synchronized void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.DESELECTED) {
hiddenHeadings.add(heading);
} else {
hiddenHeadings.remove(heading);
}
createTreeStructure();
}
});
columnCheckBoxes.add(checkBox);
}
/* Create the tree structure */
hiddenHeadings = new CopyOnWriteArrayList<>();
createTreeStructure();
}
/**
* Sort the treetable data by column index
*
* @param sortByColumn
* - column index
*/
private synchronized void sortData(final int sortByColumn) {
this.updateVisibleHeadings();
/* update the sorting orders for the columns */
final String heading = table.getColumnName(sortByColumn);
for (String key : sortingOrders.keySet()) {
if (key.equals(heading)) {
sortingOrders.put(key, !sortingOrders.get(key));
} else {
sortingOrders.put(key, true);
}
}
if (heading.equals("")) {
return;
}
Collections.sort(experiments, new Comparator<ExperimentData>() {
public synchronized int compare(ExperimentData a, ExperimentData b) {
final Pattern PATTERN = Pattern.compile("(\\D*)(\\d*)");
ArrayList<String> entry1 = a
.getAnnotationValueList(visibleHeadings);
ArrayList<String> entry2 = b
.getAnnotationValueList(visibleHeadings);
if (sortByColumn > entry1.size() - 1
|| sortByColumn > entry2.size() - 1) {
return 1;
} else if (sortByColumn < 0) {
return 1;
}
if ((entry1.get(sortByColumn) == null || entry1.get(
sortByColumn).equals(""))
&& (entry2.get(sortByColumn) == null || entry2.get(
sortByColumn).equals(""))) {
return 0;
} else if ((entry1.get(sortByColumn) == null || entry1.get(
sortByColumn).equals(""))
&& !(entry2.get(sortByColumn) == null || entry2.get(
sortByColumn).equals(""))) {
return -1;
} else if (!(entry1.get(sortByColumn) == null || entry1.get(
sortByColumn).equals(""))
&& (entry2.get(sortByColumn) == null || entry2.get(
sortByColumn).equals(""))) {
return 1;
}
Matcher m1 = PATTERN.matcher(entry1.get(sortByColumn)
.toLowerCase());
Matcher m2 = PATTERN.matcher(entry2.get(sortByColumn)
.toLowerCase());
/* The only way find() could fail is at the end of a string */
while (m1.find() && m2.find()) {
/*
* matcher.group(1) fetches any non-digits captured by the
* first parentheses in PATTERN.
*/
int nonDigitCompare;
if (sortingOrders.get(heading)) {
nonDigitCompare = m2.group(1).compareTo(m1.group(1));
} else {
nonDigitCompare = m1.group(1).compareTo(m2.group(1));
}
if (0 != nonDigitCompare) {
return nonDigitCompare;
}
/*
* matcher.group(2) fetches any digits captured by the
* second parentheses in PATTERN.
*/
if (m1.group(2).isEmpty()) {
return m2.group(2).isEmpty() ? 0 : -1;
} else if (m2.group(2).isEmpty()) {
return +1;
}
BigInteger n1 = new BigInteger(m1.group(2));
BigInteger n2 = new BigInteger(m2.group(2));
int numberCompare;
if (sortingOrders.get(heading)) {
numberCompare = n2.compareTo(n1);
} else {
numberCompare = n1.compareTo(n2);
}
if (0 != numberCompare) {
return numberCompare;
}
}
/*
* Handle if one string is a prefix of the other. Nothing comes
* before something.
*/
return m1.hitEnd() && m2.hitEnd() ? 0 : m1.hitEnd() ? -1 : +1;
}
});
}
public ExperimentData getSelectedExperiment() {
int row = table.getSelectedRow();
TreePath path = table.getPathForRow(row);
// if (nodeObject instanceof ExperimentNode) {
// System.out.println("ExperimentNode");
// } else if (nodeObject instanceof FileNode) {
// System.out.println("FileNode");
// }
while (path.getParentPath().getParentPath() != null) {
path = path.getParentPath();
// System.out.println(path.getPathCount());
}
Object nodeObject = path.getLastPathComponent();
if (nodeObject instanceof ExperimentNode) {
//System.out.println("ExperimentNode");
}
ExperimentNode expNode = (ExperimentNode) nodeObject;
ExperimentData exp = expNode.getExperiment();
return exp;
}
/**
* Return the selected data in the tree table
*
* @return
*/
public synchronized ArrayList<ExperimentData> getSelectedData() {
/* Get the data that are selected in the table */
int[] rows = table.getSelectedRows();
ArrayList<ExperimentData> selectedExperiments = new ArrayList<>();
/* For each selected row */
for (int i = 0; i < rows.length; i++) {
/* Get the node of the selected row */
TreePath path = table.getPathForRow(rows[i]);
Object nodeObject = path.getLastPathComponent();
/* Check type of node */
if (nodeObject instanceof ExperimentNode) {
/* If experiment node */
ExperimentNode expNode = (ExperimentNode) nodeObject;
ExperimentData exp = expNode.getExperiment();
ExperimentData newExp = new ExperimentData(exp.name,
(ArrayList<FileData>) exp.files.clone(),
(ArrayList<AnnotationDataValue>) exp.annotations
.clone());
if (!selectedExperiments.contains(exp)) {
selectedExperiments.add(newExp);
}
} else if (nodeObject instanceof FileNode) {
/* If file node */
FileNode fileNode = (FileNode) nodeObject;
ArrayList<FileData> newFile = new ArrayList<>();
newFile.add(fileNode.getFile());
Object parentNode = fileNode.getParent().getParent();
if (parentNode instanceof ExperimentNode) {
ExperimentNode expNode = (ExperimentNode) parentNode;
ExperimentData tempExp = expNode.getExperiment();
if (selectedExperiments.contains(tempExp)) {
int index = selectedExperiments.indexOf(tempExp);
if (!selectedExperiments.get(index).files
.contains(fileNode.getFile())) {
selectedExperiments.get(index).addFiles(newFile);
}
} else {
ExperimentData exp = new ExperimentData(tempExp.name,
newFile, tempExp.annotations);
selectedExperiments.add(exp);
}
}
} else {
/*
* If support node (file headings node or raw/region/profile
* node
*/
SupportNode node = (SupportNode) nodeObject;
ArrayList<FileData> newFiles = new ArrayList<>();
for (int j = 0; j < node.getChildCount(); j++) {
if (node.getChildAt(j) instanceof FileNode) {
FileNode fileNode = (FileNode) node.getChildAt(j);
newFiles.add(fileNode.getFile());
}
}
Object parentNode = node.getParent();
if (parentNode instanceof ExperimentNode) {
ExperimentNode expNode = (ExperimentNode) parentNode;
ExperimentData tempExp = expNode.getExperiment();
if (selectedExperiments.contains(tempExp)) {
int index = selectedExperiments.indexOf(tempExp);
for (FileData file : newFiles) {
if (!selectedExperiments.get(index).files
.contains(file)) {
ArrayList<FileData> newFile = new ArrayList<>();
newFile.add(file);
selectedExperiments.get(index)
.addFiles(newFile);
}
}
} else {
ExperimentData exp = new ExperimentData(
tempExp.name,
newFiles,
(ArrayList<AnnotationDataValue>) tempExp.annotations
.clone());
selectedExperiments.add(exp);
}
}
}
}
return selectedExperiments;
}
public synchronized ArrayList<ExperimentData> getSelectedExperiments() {
/* Get the data that are selected in the table */
int[] rows = table.getSelectedRows();
ArrayList<ExperimentData> selectedExperiments = new ArrayList<>();
/* For each selected row */
for (int i = 0; i < rows.length; i++) {
/* Get the node of the selected row */
TreePath path = table.getPathForRow(rows[i]);
Object nodeObject = path.getLastPathComponent();
/* Check type of node */
if (nodeObject instanceof ExperimentNode) {
/* If experiment node */
ExperimentNode expNode = (ExperimentNode) nodeObject;
ExperimentData exp = expNode.getExperiment();
ExperimentData newExp = new ExperimentData(exp.name,
(ArrayList<FileData>) exp.files.clone(),
(ArrayList<AnnotationDataValue>) exp.annotations
.clone());
if (!selectedExperiments.contains(exp)) {
selectedExperiments.add(newExp);
}
}
}
return selectedExperiments;
}
/**
* remove the currently selected files
*/
public synchronized void removeSelectedData() {
ArrayList<ExperimentData> selectedData = getSelectedData();
ArrayList<FileData> selectedFiles = new ArrayList<>();
for (ExperimentData experiment : selectedData) {
for (FileData file : experiment.files) {
if (!selectedFiles.contains(file)) {
selectedFiles.add(file);
}
}
}
try {
for (ExperimentData data : experiments) {
for (FileData file : selectedFiles) {
if (data.files.contains(file)) {
Thread.sleep(10);
data.removeFile(file);
}
}
}
selectedData = getSelectedExperiments();
for (ExperimentData data : selectedData) {
experiments.remove(data);
Thread.sleep(10);
}
} catch (NullPointerException e) {
ErrorLogger.log(e);
JOptionPane.showMessageDialog(null, "No files to remove.");
} catch (InterruptedException e) {
ErrorLogger.log(e);
e.printStackTrace();
}
createTreeStructure();
}
/**
* Get the tree table content
*
* @return
*/
public ArrayList<ExperimentData> getContent() {
return experiments;
}
public void deselectTreeTable() {
table.clearSelection();
}
/**
* Update the visible headings in the table (with column order intact)
*/
private synchronized void updateVisibleHeadings() {
try {
visibleHeadings = new ArrayList<>();
int columnCount = table.getColumnCount();
if (columnCount > 0) {
visibleHeadings = new ArrayList<>();
for (int i = 0; i < columnCount; i++) {
visibleHeadings.add(table.getColumnName(i));
}
for (String heading : headings) {
if (!visibleHeadings.contains(heading)
&& !hiddenHeadings.contains(heading)) {
visibleHeadings.add(heading);
}
}
visibleHeadings.removeAll(hiddenHeadings);
} else {
visibleHeadings.addAll(headings);
}
} catch (NullPointerException e) {
ErrorLogger.log(e);
// TODO: Where does the Null come from ? (OO)
// TODO Hantera exception CF
// System.out.println("Couldn't update visible headings");
}
}
/**
* Create the tree structure of the tree table
*/
private synchronized void createTreeStructure() {
/* Create the tree root */
updateVisibleHeadings();
final SupportNode root = new SupportNode(new Object[] { "Root" });
try {
while (visibleHeadings.contains("")) {
visibleHeadings.remove("");
}
/* 4 st�r f�r antal kolumner som visas f�r en fil */
while (visibleHeadings.size() < 4) {
visibleHeadings.add("");
}
for (ExperimentData experiment : experiments) {
/* Create experiment node and add to root */
ExperimentNode experimentNode = new ExperimentNode(experiment,
visibleHeadings);
root.add(experimentNode);
}
/* Create the model and add it to the table */
SwingUtilities.invokeLater(new Runnable() {
public void run() {
DefaultTreeTableModel model = new DefaultTreeTableModel(
root,
Arrays.asList(visibleHeadings
.toArray(new String[visibleHeadings.size()])));
table.setTreeTableModel(model);
table.packAll();
}
});
} catch (NullPointerException e) {
ErrorLogger.log(e);
// TODO: Where does the Null come from ? (OO)
// TODO Hantera exception CF
}
}
public int getNumberOfSelected() {
return table.getSelectedRows().length;
}
}