package uk.ac.rhul.cs.cl1.ui.cytoscape3;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import javax.swing.AbstractAction;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JPopupMenu;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.cytoscape.application.swing.CytoPanel;
import org.cytoscape.application.swing.CytoPanelComponent;
import org.cytoscape.application.swing.CytoPanelName;
import org.cytoscape.application.swing.CytoPanelState;
import org.cytoscape.model.CyNetwork;
import org.cytoscape.model.CyNode;
import org.cytoscape.view.model.CyNetworkView;
import org.cytoscape.view.model.events.NetworkViewAboutToBeDestroyedEvent;
import org.cytoscape.view.model.events.NetworkViewAboutToBeDestroyedListener;
import org.cytoscape.view.model.events.NetworkViewAddedEvent;
import org.cytoscape.view.model.events.NetworkViewAddedListener;
import uk.ac.rhul.cs.cl1.ClusterONE;
import uk.ac.rhul.cs.cl1.NodeSet;
import uk.ac.rhul.cs.cl1.ui.EmptyIcon;
import uk.ac.rhul.cs.cl1.ui.NodeSetTableModel;
import uk.ac.rhul.cs.cl1.ui.PopupMenuTrigger;
import uk.ac.rhul.cs.cl1.ui.RemoveClusterFromResultAction;
import uk.ac.rhul.cs.cl1.ui.ResultViewerPanel;
import uk.ac.rhul.cs.cl1.ui.ShowDetailedResultsAction;
import uk.ac.rhul.cs.cl1.ui.cytoscape3.ClusterONECytoscapeTask.Result;
/**
* Result viewer panel with some added functionality to ensure better integration
* with Cytoscape
*
* @author tamas
*/
public class CytoscapeResultViewerPanel extends ResultViewerPanel implements
CytoPanelComponent, ListSelectionListener,
NetworkViewAddedListener, NetworkViewAboutToBeDestroyedListener {
/**
* The ClusterONE Cytoscape application in which this panel lives.
*/
private ClusterONECytoscapeApp app = null;
/**
* Serial number of this result viewer panel.
*/
private Integer serialNumber = null;
/**
* Last used serial number of result viewer panels.
*
* This is used to assign unique numbers to each result panel in Cytoscape
*/
static private int lastUsedSerialNumber = 1;
/**
* Mapping from node IDs to real Cytoscape {@link CyNode} objects
*/
protected List<CyNode> nodeMapping;
/** Reference to the original Cytoscape network from which the results were calculated */
protected WeakReference<CyNetwork> networkRef;
/** Reference to a Cytoscape network view that will be used to highlight nodes in the selected nodeset */
protected WeakReference<CyNetworkView> networkViewRef;
/**
* The popup menu that comes up when right clicking on a cluster
*/
protected JPopupMenu clusterPopup;
/**
* The "Copy to clipboard" element of the popup menu
*/
protected AbstractAction copyToClipboardAction;
/**
* The "Extract selected cluster" element of the popup menu
*/
protected AbstractAction extractClusterAction;
/**
* The "Save selected cluster..." element of the popup menu
*/
protected AbstractAction saveClusterAction;
/**
* The "Remove" element of the popup menu
*/
protected AbstractAction removeClusterAction;
/**
* The "Convert to Cytoscape group..." element of the popup menu
*/
protected AbstractAction saveClusterAsCyGroupAction;
/**
* The "Show detailed results" action
*/
protected ShowDetailedResultsAction showDetailedResultsAction;
// --------------------------------------------------------------------
// Constructor
// --------------------------------------------------------------------
/**
* Creates a result viewer panel associated to the given {@link CyNetwork}
*
* It will be assumed that the results shown in this panel were generated
* from the given network, and that there is no network view to adjust when
* a cluster is selected in the table.
*
* @param app reference to the global CytoscapeApp object
* @param network the network from which the clusters were generated
*/
public CytoscapeResultViewerPanel(ClusterONECytoscapeApp app, CyNetwork network) {
super();
this.app = app;
this.networkRef = new WeakReference<CyNetwork>(network);
initializeClusterPopup();
/* Listen to table selection changes */
this.table.getSelectionModel().addListSelectionListener(this);
/* Listen to double click events on the table */
this.table.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
extractClusterAction.actionPerformed(null);
}
}
});
/* Listen for network view events */
app.registerService(this, NetworkViewAddedListener.class);
app.registerService(this, NetworkViewAboutToBeDestroyedListener.class);
/* Add popup menu to the cluster selection table */
this.table.addMouseListener(new PopupMenuTrigger(clusterPopup));
/* Add the bottom buttons */
/* JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 0));
JButton closeButton = new JButton(new CloseAction(this));
buttonPanel.add(closeButton);
this.add(buttonPanel, BorderLayout.SOUTH); */
this.addAction(new FindAction(this));
this.addAction(new SaveClusteringAction(this));
this.addAction(new CloseAction(this));
/* Fix the icon of the "Show detailed results" action */
showDetailedResultsAction.setIconURL(
app.getResource(app.getResourcePathName() + "/details.png"));
}
/**
* Creates a result viewer panel associated to the given {@link CyNetworkView}
* and its {@link CyNetwork}
*
* It will be assumed that the results shown in this panel were generated
* from the given network, and the given view will be used to update the
* selection based on the current nodeset in the table.
*
* @param app reference to the global CytoscapeApp object
* @param networkView a network view that will be used to show the clusters
*/
public CytoscapeResultViewerPanel(ClusterONECytoscapeApp app, CyNetworkView networkView) {
this(app, networkView.getModel());
this.setNetworkView(networkView);
}
// --------------------------------------------------------------------
// Query methods
// --------------------------------------------------------------------
/**
* Returns the ClusterONE app in which this result viewer panel lives.
*/
public ClusterONECytoscapeApp getCytoscapeApp() {
return app;
}
/**
* Retrieves the Cytoscape network associated to this panel
*/
public CyNetwork getNetwork() {
if (networkRef == null)
return null;
return networkRef.get();
}
/**
* Retrieves the Cytoscape network view associated to this panel
*/
public CyNetworkView getNetworkView() {
if (networkViewRef == null)
return null;
return networkViewRef.get();
}
/**
* Retrieves the mapping from integer node IDs to real Cytoscape {@link CyNode} objects
*/
public List<CyNode> getNodeMapping() {
return this.nodeMapping;
}
/**
* Retrieves the set of Cytoscape nodes associated to the selected {@link NodeSet}.
*
* If multiple {@link NodeSet}s are selected, the corresponding Cytoscape nodes will be
* merged into a single set.
*
* If nothing is selected in the table, an empty set will be returned.
*/
public List<CyNode> getSelectedCytoscapeNodeSet() {
Set<Integer> selectedIndices = new TreeSet<Integer>();
/* Take the union of all indices. This step is necessary because CyNodes are not hashable */
for (NodeSet selectedNodeSet: this.getSelectedNodeSets()) {
for (Integer idx: selectedNodeSet) {
selectedIndices.add(idx);
}
}
/* Convert indices to CyNodes */
return this.convertIterableToCytoscapeNodeList(selectedIndices);
}
/**
* Retrieves the set of Cytoscape nodes associated to the selected {@link NodeSet}.
*
* If multiple {@link NodeSet}s are selected, the corresponding Cytoscape nodes will be
* returned as individual lists.
*
* If nothing is selected in the table, an empty list will be returned.
*/
public List<List<CyNode>> getSelectedCytoscapeNodeSets() {
List<List<CyNode>> result = new ArrayList<List<CyNode>>();
for (NodeSet selectedNodeSet: this.getSelectedNodeSets()) {
result.add(this.convertIterableToCytoscapeNodeList(selectedNodeSet));
}
return result;
}
/**
* Retrieves the set of Cytoscape nodes associated to all {@link NodeSet} instances
* in this result viewer.
*/
public List<List<CyNode>> getAllCytoscapeNodeSets() {
NodeSetTableModel model = this.getTableModel();
int numRows = model.getRowCount();
List<List<CyNode>> result = new ArrayList<List<CyNode>>();
for (int i = 0; i < numRows; i++) {
result.add(this.convertIterableToCytoscapeNodeList(model.getNodeSetByIndex(i)));
}
return result;
}
protected void setNetworkView(CyNetworkView networkView) {
if (networkView == null) {
this.networkViewRef = null;
return;
}
if (networkView.getModel() != getNetwork()) {
throw new RuntimeException("network view is associated to a different network");
}
this.networkViewRef = new WeakReference<CyNetworkView>(networkView);
}
// --------------------------------------------------------------------
// Manipulation methods
// --------------------------------------------------------------------
/**
* Adds the result panel to Cytoscape's designated result panel area
*/
public void addToCytoscapeResultPanel() {
if (serialNumber == null) {
serialNumber = lastUsedSerialNumber;
lastUsedSerialNumber++;
}
/* Register the panel */
app.registerService(this, CytoPanelComponent.class);
/* Ensure that the panel is visible */
CytoPanel cytoPanel = app.getCySwingApplication().getCytoPanel(getCytoPanelName());
if (cytoPanel.getState() == CytoPanelState.HIDE) {
cytoPanel.setState(CytoPanelState.DOCK);
}
setVisible(true);
/* Activate the panel */
cytoPanel.setSelectedIndex(cytoPanel.indexOfComponent(getComponent()));
}
/**
* @inheritDoc
*/
@Override
protected Icon constructProgressIcon() {
URL url = app.getResource(app.getResourcePathName() + "/wait.jpg");
return (url != null) ? new ImageIcon(url) : new EmptyIcon(32, 32);
}
/**
* @inheritDoc
*/
@Override
protected ShowDetailedResultsAction constructShowDetailedResultsAction() {
showDetailedResultsAction = super.constructShowDetailedResultsAction();
return showDetailedResultsAction;
};
/**
* Converts an integer iterable yielding node IDs to a list of Cytoscape nodes
*
* As {@link NodeSet}s are iterable, this method works with {@link NodeSet}s directly.
*/
protected List<CyNode> convertIterableToCytoscapeNodeList(Iterable<Integer> iterable) {
List<CyNode> result = new ArrayList<CyNode>();
for (int idx: iterable) {
CyNode node = this.nodeMapping.get(idx);
if (node == null)
continue;
result.add(node);
}
return result;
}
/**
* Closes the result panel.
*/
public void close() {
app.unregisterService(this, CytoPanelComponent.class);
/* Ensure that the panel is hidden if this was the last one */
CytoPanel cytoPanel = app.getCySwingApplication().getCytoPanel(getCytoPanelName());
if (cytoPanel.getCytoPanelComponentCount() == 0) {
cytoPanel.setState(CytoPanelState.HIDE);
}
}
/**
* Initializes the cluster popup menu
*/
private void initializeClusterPopup() {
clusterPopup = new JPopupMenu();
copyToClipboardAction = new CopyClusterToClipboardAction(this);
copyToClipboardAction.setEnabled(false);
clusterPopup.add(copyToClipboardAction);
extractClusterAction = new ExtractClusterAction(this);
extractClusterAction.setEnabled(false);
clusterPopup.add(extractClusterAction);
saveClusterAction = new SaveClusterAction(this);
saveClusterAction.setEnabled(false);
clusterPopup.add(saveClusterAction);
removeClusterAction = new RemoveClusterFromResultAction(this);
removeClusterAction.setEnabled(false);
clusterPopup.add(removeClusterAction);
/*
clusterPopup.addSeparator();
saveClusterAsCyGroupAction = new SaveClusterAsCyGroupAction(this);
saveClusterAsCyGroupAction.setEnabled(false);
clusterPopup.add(saveClusterAsCyGroupAction);
*/
}
/**
* Selects the given set of nodes in the associated network and redraws its view.
*/
public void selectNodes(Collection<? extends CyNode> nodes) {
selectNodes(nodes, true);
}
/**
* Selects the given set of nodes in the associated network and optionally
* redraws its view.
*/
public void selectNodes(Collection<? extends CyNode> nodes, boolean redraw) {
CyNetwork network = this.getNetwork();
if (network == null)
return;
// Unselect all nodes and edges
CyNetworkUtil.unselectAllNodes(network);
CyNetworkUtil.unselectAllEdges(network);
// Select the nodes of the cluster and the connecting edges
CyNetworkUtil.setSelectedState(network, nodes, true);
CyNetworkUtil.setSelectedState(network, CyNetworkUtil.getConnectingEdges(network, nodes), true);
// Redraw the network
CyNetworkView networkView = this.getNetworkView();
if (networkView != null) {
networkView.updateView();
}
}
/**
* Sets the mapping from integer node IDs to real Cytoscape {@link Node} objects
*/
public void setNodeMapping(List<CyNode> mapping) {
this.nodeMapping = mapping;
}
/**
* Sets the results to be shown in this panel.
*/
public void setResult(Result result) {
this.setNodeSets(result.clusters);
this.setNodeMapping(result.nodeMapping);
}
/**
* Method called when the table selection changes
* @param event event describing how the selection changed
*/
public void valueChanged(ListSelectionEvent event) {
CyNetwork network = this.getNetwork();
if (network == null) {
copyToClipboardAction.setEnabled(false);
extractClusterAction.setEnabled(false);
saveClusterAction.setEnabled(false);
return;
}
List<CyNode> nodes = this.getSelectedCytoscapeNodeSet();
selectNodes(nodes);
boolean enabled = nodes.size() > 0;
extractClusterAction.setEnabled(enabled);
copyToClipboardAction.setEnabled(enabled);
saveClusterAction.setEnabled(enabled);
removeClusterAction.setEnabled(enabled);
// saveClusterAsCyGroupAction.setEnabled(enabled);
}
// --------------------------------------------------------------------
// Private methods
// --------------------------------------------------------------------
// --------------------------------------------------------------------
// CloseAction class
// --------------------------------------------------------------------
class CloseAction extends AbstractAction {
CytoscapeResultViewerPanel panel;
public CloseAction(CytoscapeResultViewerPanel panel) {
super("Close");
this.panel = panel;
this.putValue(AbstractAction.SHORT_DESCRIPTION,
"Close this result panel");
URL url = app.getResource(app.getResourcePathName() + "/close.png");
if (url != null) {
this.putValue(AbstractAction.SMALL_ICON, new ImageIcon(url));
}
}
public void actionPerformed(ActionEvent event) {
panel.close();
}
}
// --------------------------------------------------------------------
// CytoPanelComponent implementation
// --------------------------------------------------------------------
public Component getComponent() {
return this;
}
public CytoPanelName getCytoPanelName() {
return CytoPanelName.EAST;
}
public Icon getIcon() {
return null;
}
public String getTitle() {
return ClusterONE.applicationName + " result " + serialNumber;
}
// --------------------------------------------------------------------
// NetworkViewAboutToBeDestroyedListener implementation
// --------------------------------------------------------------------
public void handleEvent(NetworkViewAboutToBeDestroyedEvent event) {
if (this.getNetworkView() != event.getNetworkView())
return;
// Detach ourselves from the network view
this.setNetworkView(null);
}
// --------------------------------------------------------------------
// NetworkViewAddedListener implementation
// --------------------------------------------------------------------
public void handleEvent(NetworkViewAddedEvent event) {
if (this.getNetworkView() != null)
return;
// Attach ourselves to the network view if it corresponds to our
// network
CyNetworkView newNetworkView = event.getNetworkView();
if (this.getNetwork() != null && newNetworkView != null &&
newNetworkView.getModel() == this.getNetwork()) {
this.setNetworkView(newNetworkView);
}
}
}