/* * KAM Navigator Plugin * * URLs: http://openbel.org/ * Copyright (C) 2012, Selventa * * This program 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 3 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.openbel.cytoscape.navigator.dialog; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import javax.swing.DefaultComboBoxModel; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.RowFilter; import javax.swing.WindowConstants; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableRowSorter; import org.openbel.cytoscape.webservice.Configuration; import org.openbel.cytoscape.webservice.KamService; import org.openbel.cytoscape.webservice.KamServiceFactory; import org.openbel.cytoscape.navigator.EdgeOption; import org.openbel.cytoscape.navigator.KamOption; import org.openbel.cytoscape.navigator.KamIdentifier; import org.openbel.cytoscape.navigator.KamSession; import org.openbel.cytoscape.navigator.Utility; import org.openbel.cytoscape.navigator.task.AbstractSearchKamTask; import org.openbel.cytoscape.navigator.task.KamTasks; import org.openbel.framework.ws.model.EdgeDirectionType; import org.openbel.framework.ws.model.FunctionType; import org.openbel.framework.ws.model.Kam; import org.openbel.framework.ws.model.KamNode; import cytoscape.CyNetwork; import cytoscape.Cytoscape; import cytoscape.task.Task; /** * {@link SearchKamDialog} represents the UI for the Add KAM Nodes dialog. * * @author Anthony Bargnesi <abargnesi@selventa.com> */ public class SearchKamDialog extends JDialog implements ActionListener { private static final long serialVersionUID = -8900235008972637257L; private static final String DIALOG_TITLE = "Add KAM Nodes"; private TableRowSorter<ResultsTableModel> rowSorter; private CyNetwork lastSearchedNetwork = null; private KamIdentifier lastSearchedKamId = null; private final KamService kamService; // swing components private JTable resultsTable; private JComboBox kamCmb; private JComboBox functionCmb; private JButton cancelBtn; private JButton searchBtn; private JButton addBtn; private JTextField filterTxt; private JComboBox edgeCmb; private JLabel resultsCount; // really hacky way to not clear search results after add due to model change private boolean kamModelReload = false; /** * Construct the {@link JDialog dialog} and initialize the UI. * * @see #initUI() */ public SearchKamDialog() { super(Cytoscape.getDesktop(), DIALOG_TITLE, true); this.kamService = KamServiceFactory.getInstance().getKAMService(); initUI(); } /** * {@inheritDoc} * * <p> * Implementation Note: * Clean up resources used by {@link SearchKamDialog}. * </p> */ @Override public void dispose() { super.dispose(); final ResultsTableModel model = (ResultsTableModel) resultsTable .getModel(); model.clear(); } private void initUI() { // Initialize dialog with Search KAM and button panel. getContentPane().setLayout(new BorderLayout()); getContentPane().add(createSearchKAMPanel(), BorderLayout.CENTER); getContentPane().add(createButtonPanel(), BorderLayout.SOUTH); // init state if (functionCmb.getModel().getSize() > 0) { functionCmb.setSelectedIndex(0); searchBtn.setEnabled(true); } else { searchBtn.setEnabled(false); } addBtn.setEnabled(false); // Dialog settings final Dimension dialogDim = new Dimension(400, 600); setMinimumSize(dialogDim); setSize(dialogDim); setPreferredSize(dialogDim); setLocationRelativeTo(null); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); pack(); } private JPanel createSearchKAMPanel() { GridBagConstraints gridBagConstraints; JPanel searchPanel = new JPanel(); JLabel networkLbl = new JLabel(); JLabel functionLbl = new JLabel(); JPanel resultsPanel = new JPanel(); JScrollPane resultsPane = new JScrollPane(); functionCmb = new JComboBox(); kamCmb = new JComboBox(); resultsTable = new JTable(); resultsCount = new JLabel(); searchPanel.setLayout(new java.awt.GridBagLayout()); networkLbl.setText("KAM:"); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 0; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.weightx = 0.0; gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 0); searchPanel.add(networkLbl, gridBagConstraints); List<KamOption> kamOptions = buildKamOptions(); kamCmb.addActionListener(this); kamCmb.setModel(new DefaultComboBoxModel(kamOptions .toArray(new KamOption[kamOptions.size()]))); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 0; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH; gridBagConstraints.weightx = 10.0; searchPanel.add(kamCmb, gridBagConstraints); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 1; gridBagConstraints.gridwidth = 2; gridBagConstraints.gridheight = 1; gridBagConstraints.anchor = java.awt.GridBagConstraints.CENTER; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.weightx = 1.0; gridBagConstraints.weighty = 1.0; gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 5); searchPanel.add(new JSeparator(), gridBagConstraints); functionLbl.setText("Function Type:"); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 2; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.weightx = 0.0; gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 0); searchPanel.add(functionLbl, gridBagConstraints); functionCmb.addActionListener(this); functionCmb.setModel(new DefaultComboBoxModel(Utility.getFunctions())); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 2; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH; gridBagConstraints.weightx = 10.0; searchPanel.add(functionCmb, gridBagConstraints); resultsPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("KAM Node Results")); resultsPanel.setLayout(new java.awt.BorderLayout()); final ResultsTableModel resultsModel = new ResultsTableModel(); resultsTable.setModel(resultsModel); resultsTable.getSelectionModel().addListSelectionListener(new ResultsSelectionListener()); rowSorter = new TableRowSorter<ResultsTableModel>(resultsModel); resultsTable.setRowSorter(rowSorter); resultsPane.setViewportView(resultsTable); resultsPanel.add(resultsPane, java.awt.BorderLayout.CENTER); resultsCount.setText(""); // empty until search JPanel countPanel = new JPanel(new BorderLayout(5, 0)); countPanel.add(resultsCount, BorderLayout.WEST); resultsPanel.add(countPanel, BorderLayout.SOUTH); JPanel filterPanel = new JPanel(new BorderLayout(5, 0)); filterPanel.add(new JLabel("Filter:"), BorderLayout.WEST); filterTxt = new JTextField(); filterTxt.addKeyListener(new KeyListener() { @Override public void keyTyped(KeyEvent e) { rowSorter.setRowFilter(new LabelFilter()); } @Override public void keyPressed(KeyEvent e) { rowSorter.setRowFilter(new LabelFilter()); } @Override public void keyReleased(KeyEvent e) { rowSorter.setRowFilter(new LabelFilter()); } }); filterTxt.setEnabled(false); filterPanel.add(filterTxt, BorderLayout.CENTER); JPanel bottomPanel = new JPanel(new BorderLayout(0, 0)); bottomPanel.add(countPanel, BorderLayout.WEST); bottomPanel.add(filterPanel, BorderLayout.SOUTH); resultsPanel.add(bottomPanel, BorderLayout.SOUTH); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 3; gridBagConstraints.gridwidth = 2; gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.weighty = 25.0; searchPanel.add(resultsPanel, gridBagConstraints); final JPanel optionsPanel = new JPanel(new BorderLayout()); optionsPanel.setLayout(new GridBagLayout()); final JLabel edgeLabel = new JLabel("Expand Edge Options:"); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 0; gridBagConstraints.gridwidth = 1; gridBagConstraints.fill = java.awt.GridBagConstraints.NONE; gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST; gridBagConstraints.weightx = 0.0; gridBagConstraints.weighty = 1.0; gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 0); optionsPanel.add(edgeLabel, gridBagConstraints); edgeCmb = new JComboBox(); edgeCmb.setModel(new DefaultComboBoxModel(EdgeOption.values())); edgeCmb.getModel().setSelectedItem(EdgeOption.INTERCONNECT); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 0; gridBagConstraints.gridwidth = 1; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST; gridBagConstraints.weightx = 1.0; gridBagConstraints.weighty = 1.0; gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 0); optionsPanel.add(edgeCmb, gridBagConstraints); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 4; gridBagConstraints.gridwidth = 2; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH; gridBagConstraints.weighty = 1.0; searchPanel.add(optionsPanel, gridBagConstraints); return searchPanel; } private JPanel createButtonPanel() { // Button panel JPanel bp = new JPanel(); bp.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.RIGHT)); cancelBtn = new JButton(); cancelBtn.addActionListener(this); cancelBtn.setText("Cancel"); cancelBtn.setPreferredSize(new java.awt.Dimension(85, 25)); bp.add(cancelBtn); searchBtn = new JButton(); searchBtn.addActionListener(this); searchBtn.setText("Search"); searchBtn.setPreferredSize(new java.awt.Dimension(85, 25)); bp.add(searchBtn); addBtn = new JButton(); addBtn.addActionListener(this); addBtn.setText("Add"); addBtn.setPreferredSize(new java.awt.Dimension(85, 25)); bp.add(addBtn); return bp; } /** * {@inheritDoc} */ @Override public void actionPerformed(ActionEvent e) { if (e != null) { if (e.getSource() == functionCmb) { searchBtn.setEnabled(true); } else if (e.getSource() == kamCmb) { if (kamModelReload) { return; } resultsTable.getSelectionModel().clearSelection(); final ResultsTableModel model = (ResultsTableModel) resultsTable .getModel(); model.clear(); model.fireTableDataChanged(); } else if (e.getSource() == cancelBtn) { this.dispose(); } else if (e.getSource() == searchBtn) { FunctionType selfunc = (FunctionType) functionCmb.getSelectedItem(); this.lastSearchedNetwork = Cytoscape.getCurrentNetwork(); KamOption kamOpt = (KamOption) kamCmb.getSelectedItem(); this.lastSearchedKamId = new KamIdentifier(kamOpt.getKam(), Configuration.getInstance().getWSDLURL()); final Task task = new AbstractSearchKamTask(lastSearchedKamId, selfunc) { @Override protected void updateUI(Collection<KamNode> nodes) { ResultsTableModel rtm = (ResultsTableModel) resultsTable .getModel(); rtm.setData(nodes); int nodeCount = nodes.size(); filterTxt.setEnabled(nodeCount > 0); if (nodeCount == 1) { resultsCount.setText(nodes.size() + " node found."); } else { resultsCount .setText(nodes.size() + " nodes found."); } } }; Utility.executeTask(task); } else if (e.getSource() == addBtn) { ResultsTableModel rtm = (ResultsTableModel) resultsTable.getModel(); final List<KamNode> nodes = rtm.getNodes(); // hold the selected KAM nodes we're interested in final List<KamNode> selectedNodes = new ArrayList<KamNode>(); // determine selected rows from the filtered view int[] viewIndices = resultsTable.getSelectedRows(); for (int viewIndex : viewIndices) { int modelIndex = resultsTable.convertRowIndexToModel(viewIndex); KamNode selectedNode = nodes.get(modelIndex); selectedNodes.add(selectedNode); } // run add task, hook in edges based on selected option EdgeOption eeo = (EdgeOption) edgeCmb.getSelectedItem(); switch (eeo) { case ALL_EDGES: KamTasks.addNodesAndExpand(lastSearchedNetwork, lastSearchedKamId, selectedNodes, EdgeDirectionType.BOTH); break; case DOWNSTREAM: KamTasks.addNodesAndExpand(lastSearchedNetwork, lastSearchedKamId, selectedNodes, EdgeDirectionType.FORWARD); break; case INTERCONNECT: KamTasks.addNodesAndInterconnect(lastSearchedNetwork, lastSearchedKamId, selectedNodes); break; case NONE: KamTasks.addNodes(lastSearchedNetwork, lastSearchedKamId, selectedNodes); break; case UPSTREAM: KamTasks.addNodesAndExpand(lastSearchedNetwork, lastSearchedKamId, selectedNodes, EdgeDirectionType.REVERSE); break; } // replace kam options (kam must be in the session prior to this) List<KamOption> kamOptions = buildKamOptions(); DefaultComboBoxModel kamModel = (DefaultComboBoxModel) kamCmb .getModel(); kamModelReload = true; kamModel.removeAllElements(); for (KamOption kamOption : kamOptions) { kamModel.addElement(kamOption); } if (kamModel.getSize() > 0) { kamModel.setSelectedItem(kamModel.getElementAt(0)); } kamModelReload = false; } } } private List<KamOption> buildKamOptions() { List<Kam> kamCatalog = kamService.getCatalog(); // If there is a kam associated with the current network, it should // be the only kam shown KamIdentifier currentKamId = KamSession.getInstance() .getCurrentNetworkKamIdentifier(); // quick and dirty method to filter out kams that don't match // might not be that quick, but is very dirty if (currentKamId != null) { String wsdlUrl = Configuration.getInstance().getWSDLURL(); for (Iterator<Kam> it = kamCatalog.iterator(); it.hasNext();) { Kam kam = it.next(); // XXX creating an object each iteration is probably not the // best thing, but it will work in a pinch KamIdentifier kamId = new KamIdentifier(kam, wsdlUrl); if (!kamId.equals(currentKamId)) { it.remove(); } } } List<KamOption> kamOptions = new ArrayList<KamOption>(kamCatalog.size()); for (Kam kam : kamCatalog) { kamOptions.add(new KamOption(kam)); } Collections.sort(kamOptions); return kamOptions; } /** * The {@link AbstractTableModel table model} for the * {@link KamNode kam node} search results. * * @author Anthony Bargnesi <abargnesi@selventa.com> */ private class ResultsTableModel extends AbstractTableModel { private static final long serialVersionUID = -5744344001683506045L; private final String[] headers = new String[] { "Label" }; private final List<KamNode> nodes; private ResultsTableModel() { this.nodes = new ArrayList<KamNode>(); } private void setData(final Collection<KamNode> nodes) { this.nodes.clear(); this.nodes.addAll(nodes); fireTableDataChanged(); } private void clear() { nodes.clear(); } public List<KamNode> getNodes() { return nodes; } /** * {@inheritDoc} */ @Override public int getColumnCount() { return headers.length; } /** * {@inheritDoc} */ @Override public String getColumnName(int ci) { return headers[ci]; } /** * {@inheritDoc} */ @Override public int getRowCount() { return nodes.size(); } /** * {@inheritDoc} */ @Override public Object getValueAt(int ri, int ci) { final KamNode node = nodes.get(ri); switch (ci) { case 0: return node.getLabel(); } return null; } } /** * The {@link ListSelectionListener listener} fired when a table row is * selected in order to flip the state of the "Add" button. * * @author Anthony Bargnesi <abargnesi@selventa.com> */ private class ResultsSelectionListener implements ListSelectionListener { /** * {@inheritDoc} */ @Override public void valueChanged(ListSelectionEvent e) { ListSelectionModel selModel = (ListSelectionModel) e.getSource(); addBtn.setEnabled(!selModel.isSelectionEmpty()); } } /** * The {@link RowFilter row filter} that filters {@link KamNode kam nodes} * in the {@link ResultsTableModel search results table model} by an * arbitrary {@link String string}. The {@link String search string} is * applied to all table rows using a {@link String#contains(CharSequence)} * function. * * @author Anthony Bargnesi <abargnesi@selventa.com> */ private class LabelFilter extends RowFilter<ResultsTableModel, Integer> { /** * {@inheritDoc} */ @Override public boolean include( javax.swing.RowFilter.Entry<? extends ResultsTableModel, ? extends Integer> entry) { final ResultsTableModel rtm = entry.getModel(); KamNode node = rtm.getNodes().get(entry.getIdentifier()); final String lowerLabel = node.getLabel().toLowerCase(); return lowerLabel.contains(filterTxt.getText().toLowerCase()); } } }