JAL-1668 added PDBDocFieldPreference for configuring rest response summary fields
[jalview.git] / src / jalview / gui / StructureChooser.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21
22 package jalview.gui;
23
24 import jalview.datamodel.DBRefEntry;
25 import jalview.datamodel.PDBEntry;
26 import jalview.datamodel.SequenceI;
27 import jalview.jbgui.GStructureChooser;
28 import jalview.jbgui.PDBDocFieldPreferences;
29 import jalview.util.MessageManager;
30 import jalview.ws.dbsources.PDBRestClient;
31 import jalview.ws.dbsources.PDBRestClient.PDBDocField;
32 import jalview.ws.uimodel.PDBRestRequest;
33 import jalview.ws.uimodel.PDBRestResponse;
34 import jalview.ws.uimodel.PDBRestResponse.PDBResponseSummary;
35
36 import java.awt.event.ItemEvent;
37 import java.util.ArrayList;
38 import java.util.Collection;
39 import java.util.HashSet;
40 import java.util.List;
41 import java.util.Vector;
42
43 import javax.swing.JCheckBox;
44 import javax.swing.JComboBox;
45 import javax.swing.ListSelectionModel;
46
47 /**
48  * Provides the behaviors for the Structure chooser Panel
49  * 
50  * @author tcnofoegbu
51  *
52  */
53 @SuppressWarnings("serial")
54 public class StructureChooser extends GStructureChooser
55 {
56   private boolean structuresDiscovered = false;
57
58   private SequenceI selectedSequence;
59
60   private SequenceI[] selectedSequences;
61
62   private IProgressIndicator progressIndicator;
63
64   private Collection<PDBResponseSummary> discoveredStructuresSet = new HashSet<PDBResponseSummary>();
65
66   private PDBRestRequest pdbRequest;
67
68   private PDBRestClient pdbRestCleint;
69
70   private String selectedPdbFileName;
71
72   private boolean isValidPBDEntry;
73
74   public StructureChooser(SequenceI[] selectedSeqs, SequenceI selectedSeq,
75           AlignmentPanel ap)
76   {
77     this.ap = ap;
78     this.selectedSequence = selectedSeq;
79     this.selectedSequences = selectedSeqs;
80     this.progressIndicator = (ap == null) ? null : ap.alignFrame;
81     init();
82   }
83
84   /**
85    * Initializes parameters used by the Structure Chooser Panel
86    */
87   public void init()
88   {
89     Thread discoverPDBStructuresThread = new Thread(new Runnable()
90     {
91       @Override
92       public void run()
93       {
94         long startTime = System.currentTimeMillis();
95         String msg = MessageManager.getString("status.fetching_db_refs");
96         updateProgressIndicator(msg, startTime);
97         fetchStructuresMetaData();
98         populateFilterComboBox();
99         updateProgressIndicator(null, startTime);
100         mainFrame.setVisible(true);
101         updateCurrentView();
102       }
103     });
104     discoverPDBStructuresThread.start();
105   }
106
107   /**
108    * Updates the progress indicator with the specified message
109    * 
110    * @param message
111    *          displayed message for the operation
112    * @param id
113    *          unique handle for this indicator
114    */
115   public void updateProgressIndicator(String message, long id)
116   {
117     if (progressIndicator != null)
118     {
119       progressIndicator.setProgressBar(message, id);
120     }
121   }
122
123   /**
124    * Retrieve meta-data for all the structure(s) for a given sequence(s) in a
125    * selection group
126    */
127   public void fetchStructuresMetaData()
128   {
129     long startTime = System.currentTimeMillis();
130     Collection<PDBDocField> wantedFields = PDBDocFieldPreferences
131             .getStructureSummaryFields();
132
133     pdbRequest = new PDBRestRequest();
134     pdbRequest.setAllowEmptySeq(false);
135     pdbRequest.setResponseSize(500);
136     pdbRequest.setFieldToSearchBy("(text:");
137     pdbRequest.setWantedFields(wantedFields);
138     for (SequenceI seq : selectedSequences)
139     {
140       pdbRequest.setSearchTerm(buildQuery(seq) + ")");
141       pdbRequest.setAssociatedSequence(seq.getName());
142       pdbRestCleint = new PDBRestClient();
143       PDBRestResponse resultList = pdbRestCleint.executeRequest(pdbRequest);
144       if (resultList.getSearchSummary() != null
145               && !resultList.getSearchSummary().isEmpty())
146       {
147         discoveredStructuresSet.addAll(resultList.getSearchSummary());
148         updateSequenceDbRef(seq, resultList.getSearchSummary());
149       }
150     }
151
152     int noOfStructuresFound = 0;
153     if (discoveredStructuresSet != null
154             && !discoveredStructuresSet.isEmpty())
155     {
156       tbl_summary.setModel(PDBRestResponse.getTableModel(pdbRequest,
157               discoveredStructuresSet));
158       structuresDiscovered = true;
159       noOfStructuresFound = discoveredStructuresSet.size();
160     }
161     String totalTime = (System.currentTimeMillis() - startTime)
162             + " milli secs";
163     mainFrame.setTitle("Structure Chooser - " + noOfStructuresFound
164             + " Found (" + totalTime + ")");
165   }
166
167   /**
168    * Update the DBRef entry for a given sequence with values retrieved from
169    * PDBResponseSummary
170    * 
171    * @param seq
172    *          the Sequence to update its DBRef entry
173    * @param responseSummaries
174    *          a collection of PDBResponseSummary
175    */
176   public void updateSequenceDbRef(SequenceI seq,
177           Collection<PDBResponseSummary> responseSummaries)
178   {
179     for (PDBResponseSummary response : responseSummaries)
180     {
181       PDBEntry newEntry = new PDBEntry();
182       newEntry.setId(response.getPdbId());
183       newEntry.setType("PDB");
184       seq.getDatasetSequence().addPDBId(newEntry);
185     }
186   }
187
188   /**
189    * Builds a query string for a given sequences using its DBRef entries
190    * 
191    * @param seq
192    *          the sequences to build a query for
193    * @return the built query string
194    */
195   @SuppressWarnings("unchecked")
196   public static String buildQuery(SequenceI seq)
197   {
198     String query = seq.getName();
199     StringBuilder queryBuilder = new StringBuilder();
200     int count = 0;
201
202     if (seq.getPDBId() != null)
203     {
204       for (PDBEntry entry : (Vector<PDBEntry>) seq.getPDBId())
205       {
206         queryBuilder.append("text:").append(entry.getId()).append(" OR ");
207       }
208     }
209
210     if (seq.getDBRef() != null && seq.getDBRef().length != 0)
211     {
212       for (DBRefEntry dbRef : seq.getDBRef())
213       {
214         queryBuilder.append("text:")
215                 .append(dbRef.getAccessionId().replaceAll("GO:", ""))
216                 .append(" OR ");
217         ++count;
218         if (count > 10)
219         {
220           break;
221         }
222       }
223       int endIndex = queryBuilder.lastIndexOf(" OR ");
224       query = queryBuilder.toString().substring(5, endIndex);
225     }
226     return query;
227   }
228
229   /**
230    * Filters a given list of discovered structures based on supplied argument
231    * 
232    * @param fieldToFilterBy
233    *          the field to filter by
234    */
235   public void filterResultSet(final String fieldToFilterBy)
236   {
237     Thread filterThread = new Thread(new Runnable()
238     {
239       @Override
240       public void run()
241       {
242         long startTime = System.currentTimeMillis();
243         try
244         {
245           lbl_loading.setVisible(true);
246           pdbRequest.setResponseSize(1);
247           pdbRequest.setFieldToSearchBy("(text:");
248           pdbRequest.setFieldToSortBy(fieldToFilterBy,
249                   !chk_invertFilter.isSelected());
250
251           Collection<PDBResponseSummary> filteredResponse = new HashSet<PDBResponseSummary>();
252           for (SequenceI seq : selectedSequences)
253           {
254             pdbRequest.setSearchTerm(buildQuery(seq) + ")");
255             pdbRequest.setAssociatedSequence(seq.getName());
256             pdbRestCleint = new PDBRestClient();
257             PDBRestResponse resultList = pdbRestCleint
258                     .executeRequest(pdbRequest);
259             if (resultList.getSearchSummary() != null
260                     && !resultList.getSearchSummary().isEmpty())
261             {
262               filteredResponse.addAll(resultList.getSearchSummary());
263             }
264           }
265
266           if (filteredResponse != null)
267           {
268             int filterResponseCount = filteredResponse.size();
269             List<PDBResponseSummary> originalDiscoveredStructuresList = new ArrayList<PDBResponseSummary>(
270                     discoveredStructuresSet);
271             originalDiscoveredStructuresList.removeAll(filteredResponse);
272             Collection<PDBResponseSummary> reorderedStructuresSet = new ArrayList<PDBResponseSummary>();
273             reorderedStructuresSet.addAll(filteredResponse);
274             reorderedStructuresSet.addAll(originalDiscoveredStructuresList);
275
276             tbl_summary.setModel(PDBRestResponse.getTableModel(pdbRequest,
277                     reorderedStructuresSet));
278
279             ListSelectionModel model = tbl_summary.getSelectionModel();
280             model.clearSelection();
281             for (int x = 0; x < filterResponseCount; x++)
282             {
283               model.addSelectionInterval(x, x);
284             }
285
286             // Discard unwanted objects to make them eligible for garbage
287             // collection
288             originalDiscoveredStructuresList = null;
289             reorderedStructuresSet = null;
290           }
291
292           lbl_loading.setVisible(false);
293         } catch (Exception e)
294         {
295           e.printStackTrace();
296         }
297         String totalTime = (System.currentTimeMillis() - startTime)
298                 + " milli secs";
299         mainFrame.setTitle("Structure Chooser - Filter time (" + totalTime
300                 + ")");
301
302         validateSelections();
303       }
304     });
305     filterThread.start();
306   }
307
308
309
310   /**
311    * Handles action event for btn_pdbFromFile
312    */
313   public void pdbFromFile_actionPerformed()
314   {
315     jalview.io.JalviewFileChooser chooser = new jalview.io.JalviewFileChooser(
316             jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
317     chooser.setFileView(new jalview.io.JalviewFileView());
318     chooser.setDialogTitle(MessageManager.formatMessage(
319             "label.select_pdb_file_for", new String[]
320             { selectedSequence.getDisplayId(false) }));
321     chooser.setToolTipText(MessageManager.formatMessage(
322             "label.load_pdb_file_associate_with_sequence", new String[]
323             { selectedSequence.getDisplayId(false) }));
324
325     int value = chooser.showOpenDialog(null);
326     if (value == jalview.io.JalviewFileChooser.APPROVE_OPTION)
327     {
328       selectedPdbFileName = chooser.getSelectedFile().getPath();
329       jalview.bin.Cache.setProperty("LAST_DIRECTORY", selectedPdbFileName);
330       validateSelections();
331     }
332   }
333
334   /**
335    * Populates the filter combo-box options dynamically depending on discovered
336    * structures
337    */
338   protected void populateFilterComboBox()
339   {
340     if (isStructuresDiscovered())
341     {
342       cmb_filterOption.addItem(new FilterOption("Best Quality",
343               PDBDocField.OVERALL_QUALITY.getCode(), VIEWS_FILTER));
344       cmb_filterOption.addItem(new FilterOption("Best UniProt Coverage",
345               PDBDocField.UNIPROT_COVERAGE.getCode(), VIEWS_FILTER));
346       cmb_filterOption.addItem(new FilterOption("Highest Resolution",
347               PDBDocField.RESOLUTION.getCode(), VIEWS_FILTER));
348       cmb_filterOption.addItem(new FilterOption("Highest Protein Chain",
349               PDBDocField.PROTEIN_CHAIN_COUNT.getCode(), VIEWS_FILTER));
350       cmb_filterOption.addItem(new FilterOption("Highest Bound Molecules",
351               PDBDocField.BOUND_MOLECULE_COUNT.getCode(), VIEWS_FILTER));
352       cmb_filterOption.addItem(new FilterOption("Highest Polymer Residues",
353               PDBDocField.POLYMER_RESIDUE_COUNT.getCode(), VIEWS_FILTER));
354     }
355     cmb_filterOption.addItem(new FilterOption("Enter PDB Id", "-",
356             VIEWS_ENTER_ID));
357     cmb_filterOption.addItem(new FilterOption("From File", "-",
358             VIEWS_FROM_FILE));
359   }
360
361   /**
362    * Updates the displayed view based on the selected filter option
363    */
364   protected void updateCurrentView()
365   {
366     FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
367             .getSelectedItem());
368     layout_switchableViews.show(pnl_switchableViews,
369             selectedFilterOpt.getView());
370     String filterTitle = mainFrame.getTitle();
371     mainFrame.setTitle(frameTitle);
372     chk_invertFilter.setVisible(false);
373     if (selectedFilterOpt.getView() == VIEWS_FILTER)
374     {
375       mainFrame.setTitle(filterTitle);
376       chk_invertFilter.setVisible(true);
377       filterResultSet(selectedFilterOpt.getValue());
378     }
379     else
380     {
381       idInputAssSeqPanel.loadCmbAssSeq();
382       fileChooserAssSeqPanel.loadCmbAssSeq();
383     }
384     validateSelections();
385   }
386
387   /**
388    * Validates user selection and activates the view button if all parameters
389    * are correct
390    */
391   public void validateSelections()
392   {
393     FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
394             .getSelectedItem());
395     btn_view.setEnabled(false);
396     String currentView = selectedFilterOpt.getView();
397     if (currentView == VIEWS_FILTER)
398     {
399       if (tbl_summary.getSelectedRows().length > 0)
400       {
401         btn_view.setEnabled(true);
402       }
403     }
404     else if (currentView == VIEWS_ENTER_ID)
405     {
406       validateAssociationEnterPdb();
407     }
408     else if (currentView == VIEWS_FROM_FILE)
409     {
410       validateAssociationFromFile();
411     }
412
413   }
414
415   /**
416    * Validates inputs from the Manual PDB entry panel
417    */
418   public void validateAssociationEnterPdb()
419   {
420     AssociateSeqOptions assSeqOpt = (AssociateSeqOptions) idInputAssSeqPanel
421             .getCmb_assSeq().getSelectedItem();
422     lbl_pdbManualFetchStatus.setIcon(errorImage);
423     if (selectedSequences.length == 1
424             || !assSeqOpt.getName().equalsIgnoreCase(
425                     "-Select Associated Seq-"))
426     {
427       txt_search.setEnabled(true);
428       if (isValidPBDEntry)
429       {
430         btn_view.setEnabled(true);
431         lbl_pdbManualFetchStatus.setIcon(goodImage);
432       }
433     }
434     else
435     {
436       txt_search.setEnabled(false);
437       lbl_pdbManualFetchStatus.setIcon(errorImage);
438     }
439   }
440
441   /**
442    * Validates inputs for the manual PDB file selection options
443    */
444   public void validateAssociationFromFile()
445   {
446     AssociateSeqOptions assSeqOpt = (AssociateSeqOptions) fileChooserAssSeqPanel
447             .getCmb_assSeq().getSelectedItem();
448     lbl_fromFileStatus.setIcon(errorImage);
449     if (selectedSequences.length == 1
450             || (assSeqOpt != null
451             && !assSeqOpt.getName().equalsIgnoreCase(
452                     "-Select Associated Seq-")))
453     {
454       btn_pdbFromFile.setEnabled(true);
455       if (selectedPdbFileName != null && selectedPdbFileName.length() > 0)
456       {
457         btn_view.setEnabled(true);
458         lbl_fromFileStatus.setIcon(goodImage);
459       }
460     }
461     else
462     {
463       btn_pdbFromFile.setEnabled(false);
464       lbl_fromFileStatus.setIcon(errorImage);
465     }
466   }
467
468   @Override
469   public void cmbAssSeqStateChanged()
470   {
471     validateSelections();
472   }
473
474   /**
475    * Handles the state change event for the 'filter' combo-box and 'invert'
476    * check-box
477    */
478   @Override
479   protected void stateChanged(ItemEvent e)
480   {
481     if (e.getSource() instanceof JCheckBox)
482     {
483       updateCurrentView();
484     }
485     else
486     {
487       if (e.getStateChange() == ItemEvent.SELECTED)
488       {
489         updateCurrentView();
490       }
491     }
492
493   }
494
495   /**
496    * Handles action event for btn_ok
497    */
498   @Override
499   public void ok_ActionPerformed()
500   {
501     FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
502             .getSelectedItem());
503     String currentView = selectedFilterOpt.getView();
504     if (currentView == VIEWS_FILTER)
505     {
506       int pdbIdCol = PDBRestClient.getPDBIdColumIndex(
507               pdbRequest.getWantedFields(), true);
508       int[] selectedRows = tbl_summary.getSelectedRows();
509       PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
510       int count = 0;
511       for (int summaryRow : selectedRows)
512       {
513         String pdbIdStr = tbl_summary.getValueAt(summaryRow, pdbIdCol)
514                 .toString();
515         PDBEntry pdbEntry = new PDBEntry();
516         pdbEntry.setId(pdbIdStr);
517         pdbEntry.setType("PDB");
518         pdbEntriesToView[count++] = pdbEntry;
519       }
520       new StructureViewer(ap.getStructureSelectionManager())
521               .viewStructures(ap, pdbEntriesToView,
522                       ap.av.collateForPDB(pdbEntriesToView));
523     }
524     else if (currentView == VIEWS_ENTER_ID)
525     {
526       selectedSequence = ((AssociateSeqOptions) idInputAssSeqPanel
527               .getCmb_assSeq().getSelectedItem()).getSequence();
528       PDBEntry pdbEntry = new PDBEntry();
529       pdbEntry.setId(txt_search.getText());
530       pdbEntry.setType("PDB");
531       selectedSequence.getDatasetSequence().addPDBId(pdbEntry);
532       PDBEntry[] pdbEntriesToView = new PDBEntry[]
533       { pdbEntry };
534       new StructureViewer(ap.getStructureSelectionManager())
535               .viewStructures(ap, pdbEntriesToView,
536                       ap.av.collateForPDB(pdbEntriesToView));
537     }
538     else if (currentView == VIEWS_FROM_FILE)
539     {
540       selectedSequence = ((AssociateSeqOptions) fileChooserAssSeqPanel
541               .getCmb_assSeq().getSelectedItem()).getSequence();
542       new AssociatePdbFileWithSeq().associatePdbWithSeq(
543               selectedPdbFileName, jalview.io.AppletFormatAdapter.FILE,
544               selectedSequence, true, Desktop.instance);
545     }
546     mainFrame.dispose();
547   }
548
549   /**
550    * Populates the combo-box used in associating manually fetched structures to
551    * a unique sequence when more than one sequence selection is made.
552    */
553   public void populateCmbAssociateSeqOptions(
554           JComboBox<AssociateSeqOptions> cmb_assSeq)
555   {
556     cmb_assSeq.removeAllItems();
557     cmb_assSeq.addItem(new AssociateSeqOptions("-Select Associated Seq-",
558             null));
559     // cmb_assSeq.addItem(new AssociateSeqOptions("Auto Detect", null));
560     if (selectedSequences.length > 1)
561     {
562       for (SequenceI seq : selectedSequences)
563       {
564         cmb_assSeq.addItem(new AssociateSeqOptions(seq));
565       }
566     }
567     else
568     {
569       cmb_assSeq.setVisible(false);
570     }
571   }
572
573   public boolean isStructuresDiscovered()
574   {
575     return structuresDiscovered;
576   }
577
578   public void setStructuresDiscovered(boolean structuresDiscovered)
579   {
580     this.structuresDiscovered = structuresDiscovered;
581   }
582
583   public Collection<PDBResponseSummary> getDiscoveredStructuresSet()
584   {
585     return discoveredStructuresSet;
586   }
587
588   @Override
589   protected void txt_search_ActionPerformed()
590   {
591     isValidPBDEntry = false;
592     if (txt_search.getText().length() > 0)
593     {
594       List<PDBDocField> wantedFields = new ArrayList<PDBDocField>();
595       wantedFields.add(PDBDocField.PDB_ID);
596       pdbRequest = new PDBRestRequest();
597       pdbRequest.setAllowEmptySeq(false);
598       pdbRequest.setResponseSize(1);
599       pdbRequest.setFieldToSearchBy("(pdb_id:");
600       pdbRequest.setWantedFields(wantedFields);
601       pdbRequest.setSearchTerm(txt_search.getText() + ")");
602       pdbRequest.setAssociatedSequence(selectedSequence.getName());
603       pdbRestCleint = new PDBRestClient();
604       PDBRestResponse resultList = pdbRestCleint.executeRequest(pdbRequest);
605       if (resultList.getSearchSummary() != null
606               && resultList.getSearchSummary().size() > 0)
607       {
608         isValidPBDEntry = true;
609       }
610     }
611     validateSelections();
612   }
613
614 }