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