/* * Copyright (c) 2009 Genome Research Limited. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Library General Public License as published by the Free * Software Foundation; either version 2 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 Library General Public License for more * details. * * You should have received a copy of the GNU Library General Public License * along with this program; see the file COPYING.LIB. If not, write to the Free * Software Foundation Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307 * USA */ package org.genedb.jogra.plugins; import org.genedb.db.taxon.TaxonNode; import org.genedb.db.taxon.TaxonNodeManager; import org.genedb.jogra.domain.GeneDBMessage; import org.genedb.jogra.domain.Term; import org.genedb.jogra.drawing.Jogra; import org.genedb.jogra.drawing.JograPlugin; import org.genedb.jogra.drawing.JograProgressBar; import org.genedb.jogra.drawing.OpenWindowEvent; import org.genedb.jogra.services.RationaliserJList; import org.genedb.jogra.services.RationaliserResult; import org.genedb.jogra.services.TermService; import org.apache.log4j.Logger; import org.bushe.swing.event.EventBus; import org.springframework.util.StringUtils; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Cursor; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Image; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.concurrent.ExecutionException; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.ListModel; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import javax.swing.WindowConstants; import javax.swing.border.TitledBorder; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; /******************************************************************************************************** * The TermRationalier is a tool that allows curators to correct (rationalise) terms from a chosen * controlled vocabulary. It is most useful for curators wanting to work on a specific organism or a set * of organisms as these can be selected via the Organism tree in Jogra and they will be passed on to the * rationaliser which will then only display the relevant terms. ********************************************************************************************************/ public class TermRationaliser implements JograPlugin { private static final Logger logger = Logger.getLogger(TermRationaliser.class); /* Constants */ private static final String WINDOW_TITLE = "Term Rationaliser"; private static final String FROM_LIST_NAME = "From (selected terms)"; private static final String TO_LIST_NAME = "To (all terms)"; /* Variables for rationaliser functionality */ private TermService termService; //Interface to the SQLTermService private TaxonNodeManager taxonNodeManager; //TaxonNodeManager to get the organism phylotree private List<TaxonNode> selectedTaxons = new ArrayList<TaxonNode>(); //Taxons corresponding to the selected organism names private Jogra jogra; //Instance of Jogra private boolean showEVC; //Show Evidence codes? private String termType; //One of the CVs set in the Spring application context private List<Term> terms = new ArrayList<Term>(); //Terms specific to selected taxons (for JList) private List<Term> allTerms = new ArrayList<Term>(); //All the terms in the CV private HashMap<String, String> instances = new HashMap<String, String>(); //To hold the types of cvterms private String[] cvnames; //Holds the cv names passed in through Spring /*Variables related to the user interface */ private JFrame frame = new JFrame(); private RationaliserJList fromList = new RationaliserJList(); private RationaliserJList toList = new RationaliserJList(); private JTextArea textField; private JLabel productCountLabel = new JLabel(); private JLabel scopeLabel = new JLabel(); //Label showing user's selection. Default: All organisms private final JTextArea information = new JTextArea(10,10); /** * Supplies the JPanel which is displayed in the main Jogra window. */ public JPanel getMainWindowPlugin() { final JPanel ret = new JPanel(); final JButton loadButton = new JButton("Load Term Rationaliser"); final JLabel chooseType = new JLabel("Select term: "); final JComboBox termTypeBox = new JComboBox(instances.keySet().toArray()); final JCheckBox showEVCFilter = new JCheckBox("Highlight terms with evidence codes", true); loadButton.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent ae) { new SwingWorker<JFrame, Void>() { @Override protected JFrame doInBackground() throws Exception { ret.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); setTermType(instances.get((String)termTypeBox.getSelectedItem())); setShowEVC(showEVCFilter.isSelected()); return makeWindow(); } @Override public void done() { try { final GeneDBMessage e = new OpenWindowEvent(TermRationaliser.this, get()); EventBus.publish(e); } catch (final InterruptedException exp) { exp.printStackTrace(); } catch (final ExecutionException exp) { exp.printStackTrace(); } ret.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } }.execute(); } }); Box verticalBox = Box.createVerticalBox(); Box horizontalBox = Box.createHorizontalBox(); horizontalBox.add(chooseType); horizontalBox.add(termTypeBox); verticalBox.add(horizontalBox); verticalBox.add(loadButton); verticalBox.add(showEVCFilter); ret.add(verticalBox); return ret; } /** * Populates the JLists with data from the database * This happens right at the start to load the rationaliser * and then any time the user decides to refresh the * models from the database. We use two parallel threads * here to fetch the terms, but will eventually implement * some sort of caching mechanism. */ private void initModels() { JograProgressBar jpb = new JograProgressBar("Loading terms from database..."); //Progress bar added for better user information this.setSelectedTaxonsAndScopeLabel(jogra.getSelectedOrganismNames()); logger.info("Are we in EDT when loading data? " + SwingUtilities.isEventDispatchThread()); /* Loading the terms from the database */ SwingWorker<Void, Void> worker_1 = new SwingWorker<Void, Void>() { @Override public Void doInBackground() { try{ long startTime = System.nanoTime(); long endTime; terms = termService.getTerms(getSelectedTaxons(), getTermType()); /* Java Collections sort is a modified mergesort guaranteeing * n log(n) performance. The question is, is it slower or faster * than doing a sql order by in postgres? It appears postgres * uses qsort so the 'average' performance should also be n log(n). * After some experimentation, we use Java's sort here since it appeared * marginally faster. */ Collections.sort(terms); endTime = System.nanoTime(); logger.info("Doing sorting in SQL (specific) took : " + (endTime-startTime) + " ns."); for (Term term : terms) { if(isShowEVC()){ //Fetch the evidence codes for the terms if the user wants them term.setEvidenceCodes(termService.getEvidenceCodes(term)); } } }catch(SQLException se){ se.printStackTrace(); //How do we process this exception? } return null; } @Override public void done() { logger.info("Finished worker 1"); } }; SwingWorker<Void,Void> worker_2 = new SwingWorker<Void, Void>() { @Override public Void doInBackground() { try{ long startTime = System.nanoTime(); long endTime; allTerms = termService.getAllTerms(getTermType()); Collections.sort(allTerms); endTime = System.nanoTime(); logger.info("Doing sorting in SQL (general) took : " + (endTime-startTime) + " ns."); for (Term term : allTerms) { if(isShowEVC()){ //Fetch the evidence codes for the terms if the user wants them term.setEvidenceCodes(termService.getEvidenceCodes(term)); } } }catch(SQLException se){ se.printStackTrace(); } return null; } @Override public void done() { logger.info("Finished worker 2"); } }; logger.info("Inside swing worker 1 to fetch and sort the specific terms"); writeMessage("Fetching organism-specific terms from the database...\n"); worker_1.run(); logger.info("Inside swing worker 2 to fetch and sort all the cv terms"); writeMessage("Fetching all the terms in the cv from the database...\n"); worker_2.run(); //After the data is available... fromList.addAll(terms); toList.addAll(allTerms); productCountLabel.setText(String.format("Number of terms for selected organisms: %d terms found (%s)", terms.size(), getTermType())); textField.setText(""); //Re-set the editable text box fromList.clearSelection(); //Clear any previous selections toList.clearSelection(); fromList.repaint(); toList.repaint(); jpb.stop(); } /** * Return a new JFrame which is the main interface to the Rationaliser. */ public JFrame getMainPanel() { /* JFRAME */ frame.setTitle(WINDOW_TITLE); frame.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); frame.setLayout(new BorderLayout()); /* MENU */ JMenuBar menuBar = new JMenuBar(); JMenu actions_menu = new JMenu("Actions"); JMenuItem actions_mitem_1 = new JMenuItem("Refresh lists"); actions_mitem_1.addActionListener( new ActionListener(){ public void actionPerformed(ActionEvent actionEvent){ initModels(); } }); actions_menu.add(actions_mitem_1); JMenu about_menu = new JMenu("About"); JMenuItem about_mitem_1 = new JMenuItem("About"); about_mitem_1.addActionListener( new ActionListener(){ public void actionPerformed(ActionEvent actionEvent){ JOptionPane.showMessageDialog( null, "Term Rationaliser \n" + "Wellcome Trust Sanger Institute, UK \n" + "2009", "Term Rationaliser", JOptionPane.PLAIN_MESSAGE ); } }); about_menu.add(about_mitem_1); menuBar.add(about_menu); menuBar.add(actions_menu); frame.add(menuBar, BorderLayout.NORTH); /* MAIN BOX */ Box center = Box.createHorizontalBox(); //A box that displays contents from left to right center.add(Box.createHorizontalStrut(5)); //Invisible fixed-width component /* FROM LIST AND PANEL */ fromList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); //Allow multiple products to be selected fromList.addKeyListener(new KeyListener(){ @Override public void keyPressed(KeyEvent arg0) { if(arg0.getKeyCode()==KeyEvent.VK_RIGHT){ synchroniseLists(fromList, toList); //synchronise from left to right } } @Override public void keyReleased(KeyEvent arg0) {} @Override public void keyTyped(KeyEvent arg0) {} }); Box fromPanel = this.createRationaliserPanel(FROM_LIST_NAME, fromList); //Box on left hand side fromPanel.add(Box.createVerticalStrut(55)); //Add some space center.add(fromPanel); //Add to main box center.add(Box.createHorizontalStrut(3)); //Add some space /* MIDDLE PANE */ Box middlePane = Box.createVerticalBox(); ClassLoader classLoader = this.getClass().getClassLoader(); //Needed to access the images later on ImageIcon leftButtonIcon = new ImageIcon(classLoader.getResource("left_arrow.gif")); ImageIcon rightButtonIcon = new ImageIcon(classLoader.getResource("right_arrow.gif")); leftButtonIcon = new ImageIcon(leftButtonIcon.getImage().getScaledInstance(20, 20, Image.SCALE_SMOOTH)); //TODO: Investigate simpler way to resize an icon! rightButtonIcon = new ImageIcon(rightButtonIcon.getImage().getScaledInstance(20, 20, Image.SCALE_SMOOTH)); //TODO: Investigate simpler way to resize an icon! JButton rightSynch = new JButton(rightButtonIcon); rightSynch.setToolTipText("Synchronise TO list. \n Shortcut: Right-arrow key"); rightSynch.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent actionEvent){ synchroniseLists(fromList, toList); } }); JButton leftSynch = new JButton(leftButtonIcon); leftSynch.setToolTipText("Synchronise FROM list. \n Shortcut: Left-arrow key"); leftSynch.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent actionEvent){ synchroniseLists(toList, fromList); } }); middlePane.add(rightSynch); middlePane.add(leftSynch); center.add(middlePane); //Add middle pane to main box center.add(Box.createHorizontalStrut(3)); /* TO LIST AND PANEL */ toList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); //Single product selection in TO list toList.addKeyListener(new KeyListener(){ @Override public void keyPressed(KeyEvent arg0) { if(arg0.getKeyCode()==KeyEvent.VK_LEFT){ synchroniseLists(toList, fromList); //synchronise from right to left } } @Override public void keyReleased(KeyEvent arg0) {} @Override public void keyTyped(KeyEvent arg0) {} }); Box toPanel = this.createRationaliserPanel(TO_LIST_NAME, toList); Box newTerm = Box.createVerticalBox(); textField = new JTextArea(1,1); //textfield to let the user edit the name of an existing term textField.setMaximumSize(new Dimension(Toolkit.getDefaultToolkit().getScreenSize().height,10)); textField.setForeground(Color.BLUE); JScrollPane jsp = new JScrollPane(textField); //scroll pane so that there is a horizontal scrollbar jsp.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); newTerm.add(jsp); TitledBorder editBorder = BorderFactory.createTitledBorder("Edit term name"); editBorder.setTitleColor(Color.DARK_GRAY); newTerm.setBorder(editBorder); toPanel.add(newTerm); //add textfield to panel center.add(toPanel); //add panel to main box center.add(Box.createHorizontalStrut(5)); frame.add(center); //add the main panel to the frame initModels(); //load the lists with data /* BOTTOM HALF OF FRAME */ Box main = Box.createVerticalBox(); TitledBorder border = BorderFactory.createTitledBorder("Information"); border.setTitleColor(Color.DARK_GRAY); /* INFORMATION BOX */ Box info = Box.createVerticalBox(); Box scope = Box.createHorizontalBox(); scope.add(Box.createHorizontalStrut(5)); scope.add(scopeLabel); //label showing the scope of the terms scope.add(Box.createHorizontalGlue()); Box productCount = Box.createHorizontalBox(); productCount.add(Box.createHorizontalStrut(5)); productCount.add(productCountLabel); //display the label showing the number of terms productCount.add(Box.createHorizontalGlue()); info.add(scope); info.add(productCount); info.setBorder(border); /* ACTION BUTTONS */ Box actionButtons = Box.createHorizontalBox(); actionButtons.add(Box.createHorizontalGlue()); actionButtons.add(Box.createHorizontalStrut(10)); JButton findFix = new JButton(new FindClosestMatchAction()); actionButtons.add(findFix); actionButtons.add(Box.createHorizontalStrut(10)); RationaliserAction ra = new RationaliserAction(); // RationaliserAction2 ra2 = new RationaliserAction2(); JButton go = new JButton(ra); actionButtons.add(go); actionButtons.add(Box.createHorizontalGlue()); /* MORE INFORMATION TOGGLE */ Box buttonBox = Box.createHorizontalBox(); final JButton toggle = new JButton("Hide information <<"); buttonBox.add(Box.createHorizontalStrut(5)); buttonBox.add(toggle); buttonBox.add(Box.createHorizontalGlue()); Box textBox = Box.createHorizontalBox(); final JScrollPane scrollPane = new JScrollPane(information); scrollPane.setPreferredSize(new Dimension(frame.getWidth(),100)); scrollPane.setVisible(true); textBox.add(Box.createHorizontalStrut(5)); textBox.add(scrollPane); ActionListener actionListener = new ActionListener(){ public void actionPerformed(ActionEvent actionEvent){ if(toggle.getText().equals("Show information >>")){ scrollPane.setVisible(true); toggle.setText("Hide information <<"); frame.setPreferredSize(new Dimension(frame.getWidth(),frame.getHeight()+100)); frame.pack(); }else if(toggle.getText().equals("Hide information <<")){ scrollPane.setVisible(false); toggle.setText("Show information >>"); frame.setPreferredSize(new Dimension(frame.getWidth(),frame.getHeight()-100)); frame.pack(); } } }; toggle.addActionListener(actionListener); main.add(Box.createVerticalStrut(5)); main.add(info); main.add(Box.createVerticalStrut(5)); main.add(Box.createVerticalStrut(5)); main.add(actionButtons); main.add(Box.createVerticalStrut(10)); main.add(buttonBox); main.add(textBox); frame.add(main, BorderLayout.SOUTH); frame.pack(); frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); frame.setVisible(true); //initModels(); return frame; } /* */ public JFrame makeWindow() { System.err.println("Am I on EDT '" + EventQueue.isDispatchThread() + "' x"); JFrame lookup = Jogra.findNamedWindow(WINDOW_TITLE); if (lookup == null) { lookup = getMainPanel(); }else{ initModels(); lookup.pack(); lookup.repaint(); } //lookup = getMainPanel(); //Always getting a new frame since it has to pick up variable organism (improve efficiency later: NDS) return lookup; } /** * PRIVATE HELPER METHODS */ /* * Synchronise to the item selected in sourcelist */ private void synchroniseLists(JList sourceList, JList targetList){ Term term = (Term)sourceList.getSelectedValue(); targetList.setSelectedValue(term, true); targetList.ensureIndexIsVisible(targetList.getSelectedIndex()); } /* * Write a message in the information box * and set the position to the last line * (automatically scrolls down to that line) */ private void writeMessage(final String m){ information.append(m); //Bit of a fiddle to get the pane to autoscroll to end information.selectAll(); int x = information.getSelectionEnd(); information.select(x,x); } /* * Set the scope and taxon label */ private void setSelectedTaxonsAndScopeLabel(List<String> organismNames){ if(this.selectedTaxons!=null && this.selectedTaxons.size()>0){ this.selectedTaxons.clear(); //clear anything that already is in the list } if(organismNames!=null && organismNames.size()!=0 && !organismNames.contains("root")){ // 'root' with a simple r causes problems scopeLabel.setText("Organism(s): " + StringUtils.collectionToCommaDelimitedString(organismNames)); for(String s: organismNames){ this.selectedTaxons.add(taxonNodeManager.getTaxonNodeForLabel(s)); } }else{ //If there are no selections, get all terms scopeLabel.setText("Organism(s): All organisms"); this.selectedTaxons.add(taxonNodeManager.getTaxonNodeForLabel("Root")); } } /* * Since both the TO and FROM panels are so similar, we put all the common * drawing tasks inside the following method. */ private Box createRationaliserPanel(final String name, final RationaliserJList rjlist){ int preferredHeight = 500; //change accordingly int preferredWidth = 500; Toolkit tk = Toolkit.getDefaultToolkit(); Dimension size = tk.getScreenSize(); int textboxHeight = 10; //change accordingly int textboxWidth = size.width; Box box = Box.createVerticalBox(); box.add(new JLabel(name)); JTextField searchField = new JTextField(20); //Search field on top /* We don't want this textfield's height to expand when * the Rationaliser is dragged to exapnd. So we set it's * height to what we want and the width to the width of * the screen */ searchField.setMaximumSize(new Dimension(textboxWidth,textboxHeight)); rjlist.installJTextField(searchField); box.add(searchField); JScrollPane scrollPane = new JScrollPane(); //scroll pane scrollPane.setViewportView(rjlist); scrollPane.setPreferredSize(new Dimension(preferredWidth,preferredHeight)); box.add(scrollPane); TitledBorder sysidBorder = BorderFactory.createTitledBorder("Systematic IDs"); //systematic ID box sysidBorder.setTitleColor(Color.DARK_GRAY); final JTextArea idField = new JTextArea(1,1); idField.setMaximumSize(new Dimension(textboxWidth,textboxHeight)); idField.setEditable(false); idField.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY)); idField.setForeground(Color.DARK_GRAY); JScrollPane scroll = new JScrollPane(idField); scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); Box sysidBox = Box.createVerticalBox(); sysidBox.add(scroll /*idField*/); sysidBox.setBorder(sysidBorder); box.add(sysidBox); rjlist.addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { Term highlightedTerm = (Term) rjlist.getSelectedValue(); if(highlightedTerm!=null){ /* For each list, call the relevant methods * to get the systematic IDs. Then for the * right list, add the term name in the * text box below */ if(name.equals(FROM_LIST_NAME)){ idField.setText(StringUtils.collectionToCommaDelimitedString( termService.getSystematicIDs(highlightedTerm, selectedTaxons))); }else if(name.equals(TO_LIST_NAME)){ idField.setText(StringUtils.collectionToCommaDelimitedString( termService.getSystematicIDs(highlightedTerm, null))); /* We allow the user to edit the term name */ textField.setText(highlightedTerm.getName()); } } } }); return box; } /** * SETTER/GETTER METHODS */ public JFrame getFrame(){ return frame; } public List<TaxonNode> getSelectedTaxons(){ return this.selectedTaxons; } public void setTaxonNodeManager(TaxonNodeManager taxonNodeManager) { this.taxonNodeManager = taxonNodeManager; } public void setTermType(String type){ this.termType = type; } public String getTermType(){ return termType; } public String getName() { return WINDOW_TITLE; } public void setShowEVC(boolean value){ showEVC = value; } public boolean isShowEVC(){ return showEVC; } public boolean isSingletonByDefault() { return true; } public boolean isUnsaved() { // TODO return false; } public void setTermService(TermService termService) { this.termService = termService; } /************************************************************************************* * An action wrapping code which identifies the closest match in the right hand column * to the selected value in the left hand column. Closest is defined by the smallest * Levenshtein value. *************************************************************************************/ class FindClosestMatchAction extends AbstractAction implements ListSelectionListener { public FindClosestMatchAction() { putValue(Action.NAME, "Find possible fix"); ClassLoader classLoader = this.getClass().getClassLoader(); ImageIcon hammerIcon = new ImageIcon(classLoader.getResource("hammer_and_spanner.png")); hammerIcon = new ImageIcon(hammerIcon.getImage().getScaledInstance(35, 35, Image.SCALE_SMOOTH)); //TODO: Investigate simpler way to resize an icon! putValue(Action.SMALL_ICON, hammerIcon); fromList.addListSelectionListener(this); enableBasedOnSelection(); } @Override public void actionPerformed(ActionEvent e) { Term from = (Term) fromList.getSelectedValue(); int match = findClosestMatch(from.getName(), fromList.getSelectedIndex(), toList.getModel()); if (match != -1) { toList.setSelectedIndex(match); toList.ensureIndexIsVisible(match); } } int findClosestMatch(String in, int fromIndex, ListModel list) { int current = -1; int distance = Integer.MAX_VALUE; for (int i = 0; i < list.getSize(); i++) { if (i == fromIndex) { continue; } String element = ((Term)list.getElementAt(i)).getName(); if (in.equalsIgnoreCase(element)) { return i; } int d = org.apache.commons.lang.StringUtils.getLevenshteinDistance(in, element); if (d==1) { return i; } if ( d < distance) { distance = d; current = i; } } return current; } @Override public void valueChanged(ListSelectionEvent e) { enableBasedOnSelection(); } private void enableBasedOnSelection() { boolean selection = (fromList.getMinSelectionIndex()!=-1); if (this.isEnabled() != selection) { this.setEnabled(selection); } } } /******************************************************************************************** * Action which wraps the rationalise action in the TermService. ********************************************************************************************/ class RationaliserAction extends AbstractAction implements ListSelectionListener { public RationaliserAction() { putValue(Action.NAME, "Rationalise Terms"); ClassLoader classLoader = this.getClass().getClassLoader(); ImageIcon greenTickIcon = new ImageIcon(classLoader.getResource("green_tick.png")); greenTickIcon = new ImageIcon(greenTickIcon.getImage().getScaledInstance(35, 35, Image.SCALE_SMOOTH)); //TODO: Investigate simpler way to resize an icon! putValue(Action.SMALL_ICON, greenTickIcon); fromList.addListSelectionListener(this); toList.addListSelectionListener(this); enableBasedOnSelection(); } @Override public void actionPerformed(ActionEvent e) { List<Term> from = new ArrayList<Term>(); Object[] temp = fromList.getSelectedValues(); //terms selected on the left for (Object o: temp){ //TODO:is there an easier way to convert an array of objects to a list of **typed** objects? from.add((Term)o); } String text = textField.getText(); //Whatever is in the testfield at the bottom /* * Doing a bit of input validation and * getting user confirmation before sending * the values to be rationalised. If the user * chooses to cancel the operation, * we get out of here. */ if(toList.contains(text)==1){ //User chose to rationalise to a term already in the cv int userDecision = this.show_OK_Cancel_Dialogue("You are about to rationalise the following terms:\n" + StringUtils.collectionToDelimitedString(from, "\n") + "\n\n" + "to: \n" + text + "\n\n" + "Do you want to continue? ", "Confirm"); if (userDecision==JOptionPane.CANCEL_OPTION){ writeMessage("Request to rationalise cancelled.\n"); return; } }else if(toList.contains(text)==0) { //So, user has decided to rationalise to a new term that is currently not in the cv int userDecision = this.show_OK_Cancel_Dialogue("You are about to rationalise the following terms:\n" + StringUtils.collectionToDelimitedString(from, "\n") + "\n\n" + "to a new term: \n" + text + "\n\n" + "Do you want to continue? ", "Confirm"); if (userDecision==JOptionPane.CANCEL_OPTION){ writeMessage("Request to rationalise cancelled.\n"); return; } }else if(toList.contains(text)==2){ //So, the user is just changing the case of the terms /* The cvterm table does not allow multiple terms with the same name * even if they differ in case. So, in a case when the user wants to * change case, we have to do it across all organisms in the * database. */ int userDecision = this.show_OK_Cancel_Dialogue("You are about to change the case of this term:\n" + text + "\n\n" + " and this will be done across ALL the organisms. " + "Do you want to continue? ", "Confirm"); if (userDecision==JOptionPane.CANCEL_OPTION){ writeMessage("Request to rationalise cancelled.\n"); return; } } /* * Having validated the input, the rationaliseTerm method can be called. The result of this process is then used * to update the JLists. The changes in the terms are made in the jlists rather than fetching *all* the * terms again from the database as this was too slow.The user can opt to fetch the terms from the database by * selecting 'refresh models' which will call the init method */ try{ getFrame().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); writeMessage("Rationalising...\n"); /* The rationalising process is pretty fast so we * let it happen in the EDT */ final RationaliserResult result = termService.rationaliseTerm(from, text, selectedTaxons); logger.info(result.toString()); //All the rest we stick in a Swingworker so that the UI //does not freeze SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() { @Override public Void doInBackground(){ //Removing the terms that ought to be removed //from both the lists terms.removeAll(result.getTermsDeletedSpecific()); allTerms.removeAll(result.getTermsDeletedGeneral()); //We try to add the new terms in the right position //using the Collections binarysearch feature. When //an items does not exist, it can tell us where it //should be inserted. This saves doing a sort again //after this insert which can be expensive. for(Term t: result.getTermsAdded()){ int index = Collections.binarySearch(terms, t); if (index < 0) { //If index > 0 the term already exists terms.add(-index-1, t); } index = Collections.binarySearch(allTerms, t); if (index < 0) { //If index > 0 the term already exists allTerms.add(-index-1, t); } } writeMessage(result.getMessage()); fromList.addAll(terms); toList.addAll(allTerms); return null; } public void done(){ toList.setSelectedValue(result.getTermsAdded().iterator().next(), true); fromList.setSelectedValue(result.getTermsAdded().iterator().next(), true); toList.repaint(); fromList.repaint(); } }; worker.run(); }catch (Exception se){ //All other unexpected errors writeMessage("There was an error while trying to rationalise. " + "Please contact the WTSI Pathogens Informatics team with details of what you tried to do. \n"); writeMessage("Error:" + se.toString()); se.printStackTrace(); } getFrame().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } @Override public void valueChanged(ListSelectionEvent e) { enableBasedOnSelection(); } private void enableBasedOnSelection() { boolean selection = (fromList.getMinSelectionIndex()!=-1) && (toList.getMinSelectionIndex()!=-1); if (this.isEnabled() != selection) { this.setEnabled(selection); } } private int show_OK_Cancel_Dialogue(String message, String title){ return JOptionPane.showConfirmDialog ( null, message, title, JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE ); } } public void process(List<String> newArgs) { // TODO Auto-generated method stub } @Override public void setJogra(Jogra jogra) { this.jogra = jogra; } public String[] getCvnames() { return cvnames; } public void setCvnames(String[] cvnames) { this.cvnames = cvnames; for(String c: cvnames){ String[] splitparts = StringUtils.split(c, ","); instances.put(splitparts[1], splitparts[0]); } } }