package uk.ac.rhul.cs.cl1.ui;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.JToolTip;
import javax.swing.ListSelectionModel;
import javax.swing.RowSorter;
import javax.swing.SortOrder;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import uk.ac.rhul.cs.cl1.NodeSet;
import uk.ac.rhul.cs.cl1.ValuedNodeSet;
import uk.ac.rhul.cs.utils.StringUtils;
/**
* A Swing panel that shows the results of a ClusterONE run
*
* @author tamas
*/
public class ResultViewerPanel extends JPanel implements TableModelListener {
/**
* Information label showing the number of elements in the resulting nodeset
*/
protected JLabel countLabel;
/**
* The top toolbar
*/
protected JToolBar topToolBar;
/**
* The table shown within the panel
*/
protected JTable table;
/**
* The scroll pane encapsulating the table
*/
protected JScrollPane scrollPane;
/**
* Constructor
*/
public ResultViewerPanel() {
this(null);
}
/**
* Constructor
*/
public ResultViewerPanel(List<ValuedNodeSet> nodeSets) {
this.setLayout(new BorderLayout());
/* Create the label showing the number of clusters */
countLabel = new JLabel();
countLabel.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
/* Add the result table */
table = new JTable() {
@Override
public JToolTip createToolTip() {
JMultiLineToolTip toolTip = new JMultiLineToolTip();
toolTip.setFixedWidth(300);
return toolTip;
}
@Override
public String getToolTipText(MouseEvent e) {
TableModel model = getTableModel();
if (!(model instanceof NodeSetTableModel))
return "";
Point p = e.getPoint();
int rowIndex = convertRowIndexToModel(rowAtPoint(p));
return StringUtils.join(
((NodeSetTableModel)model).getMemberNames(rowIndex), ", ");
}
};
table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
table.setIntercellSpacing(new Dimension(0, 4));
table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
scrollPane = new JScrollPane(table);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
this.add(scrollPane, BorderLayout.CENTER);
if (nodeSets != null)
this.setNodeSets(nodeSets);
/* Add a toolbar to the top */
topToolBar = new JToolBar();
topToolBar.add(countLabel);
topToolBar.add(Box.createHorizontalGlue());
topToolBar.add(new JToggleButton(constructShowDetailedResultsAction()));
topToolBar.setFloatable(false);
topToolBar.setRollover(false);
topToolBar.setBorderPainted(false);
topToolBar.setOpaque(false);
this.add(topToolBar, BorderLayout.NORTH);
}
/**
* Adds a new action to the toolbar
*/
public JButton addAction(Action action) {
JButton button = topToolBar.add(action);
button.setContentAreaFilled(false);
button.setBorderPainted(false);
button.setRolloverEnabled(true);
return button;
}
/**
* Constructs the progress icon that will be shown in the result viewer for
* clusters whose layout is being generated.
*/
protected Icon constructProgressIcon() {
URL url = this.getClass().getResource("../resources/wait.jpg");
return (url != null) ? new ImageIcon(url) : new EmptyIcon(32, 32);
}
/**
* Constructs the Show Detailed Results action in the top toolbar.
*/
protected ShowDetailedResultsAction constructShowDetailedResultsAction() {
return new ShowDetailedResultsAction(this);
};
/**
* Retrieves all {@link NodeSet} instances in this result viewer.
*
* The result will be sorted according to the current ordering of the
* result viewer table.
*/
public List<NodeSet> getAllNodeSets() {
NodeSetTableModel model = this.getTableModel();
int numRows = model.getRowCount();
List<NodeSet> result = new ArrayList<NodeSet>();
for (int i = 0; i < numRows; i++) {
result.add(model.getNodeSetByIndex(this.table.convertRowIndexToModel(i)));
}
return result;
}
/**
* Retrieves the selected {@link NodeSet}
*/
public NodeSet getSelectedNodeSet() {
Integer selectedIndex = this.getSelectedNodeSetIndex();
if (selectedIndex == null)
return null;
return this.getTableModel().getNodeSetByIndex(selectedIndex);
}
/**
* Retrieves the selected {@link NodeSet}s
*/
public List<NodeSet> getSelectedNodeSets() {
NodeSetTableModel model = this.getTableModel();
List<NodeSet> result = new ArrayList<NodeSet>();
for (int idx: this.getSelectedNodeSetIndices()) {
result.add(model.getNodeSetByIndex(idx));
}
return result;
}
/**
* Retrieves the index of the selected {@link NodeSet} in the original result set.
*/
public Integer getSelectedNodeSetIndex() {
int selectedRow = this.table.getSelectedRow();
if (selectedRow == -1)
return null;
return this.table.convertRowIndexToModel(selectedRow);
}
/**
* Retrieves the indices of the selected {@link NodeSet}s in the original result set.
*/
public int[] getSelectedNodeSetIndices() {
int[] selectedRows = this.table.getSelectedRows();
int[] selectedRowsInModel = new int[selectedRows.length];
int i = 0;
for (int idx: selectedRows) {
selectedRowsInModel[i] = this.table.convertRowIndexToModel(idx);
i++;
}
return selectedRowsInModel;
}
/**
* Gets the scroll pane shown within the panel
*/
public JScrollPane getScrollPane() {
return scrollPane;
}
/**
* Gets the table shown within the panel
*/
public JTable getTable() {
return table;
}
/**
* Gets the table model of the panel
*/
public NodeSetTableModel getTableModel() {
return (NodeSetTableModel)this.table.getModel();
}
/**
* Sets the list of nodesets to be shown in this result viewer panel
*/
public void setNodeSets(List<ValuedNodeSet> set) {
/* Set up the table model, ensure that the table's columns are reformatted when
* the model is updated (i.e. when switching detailed mode on/off)
*/
NodeSetTableModel model = new NodeSetTableModel(set);
model.setProgressIcon(constructProgressIcon());
model.addTableModelListener(this);
table.setModel(model);
tableChanged(null);
scrollPane.setPreferredSize(table.getPreferredSize());
/* Try to make the table sortable. If the JRE is too old, simply leave it as is */
try {
TableRowSorter<NodeSetTableModel> rowSorter;
rowSorter = new TableRowSorter<NodeSetTableModel>(model);
/* The cluster column is never sortable */
rowSorter.setSortable(0, false);
/* Sort on the details column by default if not in detailed mode */
if (!model.isInDetailedMode()) {
List<RowSorter.SortKey> list = new ArrayList<RowSorter.SortKey>();
list.add(new RowSorter.SortKey(1, SortOrder.DESCENDING));
rowSorter.setSortKeys(list);
}
table.setRowSorter(rowSorter);
} catch (Exception ex) {
/* well, meh */
}
}
/**
* Makes some necessary adjustments to a freshy created column model of the table
*/
private void setupTableColumnModel() {
NodeSetTableModel model = (NodeSetTableModel)table.getModel();
TableColumnModel colModel = table.getColumnModel();
/* First column: preferred width and height is 60 pixels */
colModel.getColumn(0).setPreferredWidth(60);
colModel.getColumn(0).setMaxWidth(60);
if (!model.isInDetailedMode()) {
/* Don't set row heights, let the cell renderer for the details column do it */
colModel.getColumn(1).setPreferredWidth(120);
colModel.getColumn(1).setCellRenderer(new HeightLimitedJLabelRenderer(50));
} else {
/* Set a unique row height */
table.setRowHeight(60);
/* Set a special renderer for P-values */
colModel.getColumn(6).setCellRenderer(new PValueRenderer());
}
}
/**
* Adjusts the selection by selecting the given indices and deselecting everything else
*/
public void setSelectedNodeSetIndices(int[] indices) {
ListSelectionModel model = this.table.getSelectionModel();
model.setValueIsAdjusting(true);
model.clearSelection();
for (int i: indices)
model.addSelectionInterval(i, i);
model.setValueIsAdjusting(false);
}
/**
* Adjusts the selection by selecting the given indices and deselecting everything else
*/
public void setSelectedNodeSetIndices(List<Integer> indices) {
ListSelectionModel model = this.table.getSelectionModel();
model.setValueIsAdjusting(true);
model.clearSelection();
for (int i: indices) {
int j = this.table.convertRowIndexToView(i);
model.addSelectionInterval(j, j);
}
model.setValueIsAdjusting(false);
}
/**
* Updates the counter label that specifies the number of clusters
*/
private void updateCounterLabel() {
int n = getTableModel().getRowCount();
if (n == 0)
countLabel.setText("No clusters");
else if (n == 1)
countLabel.setText("1 cluster");
else
countLabel.setText(n+" clusters");
}
/**
* Method fired when the table model changes
*
* @param event the event that triggered the invocation of this method
*/
public void tableChanged(TableModelEvent event) {
setupTableColumnModel();
updateCounterLabel();
}
}