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