JAL-1894 update year/version in copyright
[jalview.git] / src / jalview / gui / StructureChooser.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (2.9.0b1)
3  * Copyright (C) 2015 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.structure.StructureSelectionManager;
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 import javax.swing.JOptionPane;
48 import javax.swing.table.AbstractTableModel;
49
50 /**
51  * Provides the behaviors for the Structure chooser Panel
52  * 
53  * @author tcnofoegbu
54  *
55  */
56 @SuppressWarnings("serial")
57 public class StructureChooser extends GStructureChooser
58 {
59   private boolean structuresDiscovered = false;
60
61   private SequenceI selectedSequence;
62
63   private SequenceI[] selectedSequences;
64
65   private IProgressIndicator progressIndicator;
66
67   private Collection<PDBResponseSummary> discoveredStructuresSet;
68
69   private PDBRestRequest lastPdbRequest;
70
71   private PDBRestClient pdbRestCleint;
72
73   private String selectedPdbFileName;
74
75   private boolean isValidPBDEntry;
76
77   public StructureChooser(SequenceI[] selectedSeqs, SequenceI selectedSeq,
78           AlignmentPanel ap)
79   {
80     this.ap = ap;
81     this.selectedSequence = selectedSeq;
82     this.selectedSequences = selectedSeqs;
83     this.progressIndicator = (ap == null) ? null : ap.alignFrame;
84     init();
85   }
86
87   /**
88    * Initializes parameters used by the Structure Chooser Panel
89    */
90   public void init()
91   {
92     Thread discoverPDBStructuresThread = new Thread(new Runnable()
93     {
94       @Override
95       public void run()
96       {
97         long startTime = System.currentTimeMillis();
98         updateProgressIndicator(MessageManager
99                 .getString("status.loading_cached_pdb_entries"), startTime);
100         loadLocalCachedPDBEntries();
101         updateProgressIndicator(null, startTime);
102         updateProgressIndicator(MessageManager
103                 .getString("status.searching_for_pdb_structures"),
104                 startTime);
105         fetchStructuresMetaData();
106         populateFilterComboBox();
107         updateProgressIndicator(null, startTime);
108         mainFrame.setVisible(true);
109         updateCurrentView();
110       }
111     });
112     discoverPDBStructuresThread.start();
113   }
114
115   /**
116    * Updates the progress indicator with the specified message
117    * 
118    * @param message
119    *          displayed message for the operation
120    * @param id
121    *          unique handle for this indicator
122    */
123   public void updateProgressIndicator(String message, long id)
124   {
125     if (progressIndicator != null)
126     {
127       progressIndicator.setProgressBar(message, id);
128     }
129   }
130
131   /**
132    * Retrieve meta-data for all the structure(s) for a given sequence(s) in a
133    * selection group
134    */
135   public void fetchStructuresMetaData()
136   {
137     long startTime = System.currentTimeMillis();
138     Collection<PDBDocField> wantedFields = PDBDocFieldPreferences
139             .getStructureSummaryFields();
140
141     discoveredStructuresSet = new LinkedHashSet<PDBResponseSummary>();
142     HashSet<String> errors = new HashSet<String>();
143     for (SequenceI seq : selectedSequences)
144     {
145       PDBRestRequest pdbRequest = new PDBRestRequest();
146       pdbRequest.setAllowEmptySeq(false);
147       pdbRequest.setResponseSize(500);
148       pdbRequest.setFieldToSearchBy("(text:");
149       pdbRequest.setWantedFields(wantedFields);
150       pdbRequest.setSearchTerm(buildQuery(seq) + ")");
151       pdbRequest.setAssociatedSequence(seq);
152       pdbRestCleint = new PDBRestClient();
153       PDBRestResponse resultList;
154       try
155       {
156         resultList = pdbRestCleint.executeRequest(pdbRequest);
157       } catch (Exception e)
158       {
159         e.printStackTrace();
160         errors.add(e.getMessage());
161         continue;
162       }
163       lastPdbRequest = pdbRequest;
164       if (resultList.getSearchSummary() != null
165               && !resultList.getSearchSummary().isEmpty())
166       {
167         discoveredStructuresSet.addAll(resultList.getSearchSummary());
168       }
169     }
170
171     int noOfStructuresFound = 0;
172     String totalTime = (System.currentTimeMillis() - startTime)
173             + " milli secs";
174     if (discoveredStructuresSet != null
175             && !discoveredStructuresSet.isEmpty())
176     {
177       tbl_summary.setModel(PDBRestResponse.getTableModel(lastPdbRequest,
178               discoveredStructuresSet));
179       structuresDiscovered = true;
180       noOfStructuresFound = discoveredStructuresSet.size();
181       mainFrame.setTitle(MessageManager.formatMessage(
182               "label.structure_chooser_no_of_structures",
183               noOfStructuresFound, totalTime));
184     }
185     else
186     {
187       mainFrame.setTitle(MessageManager
188               .getString("label.structure_chooser_manual_association"));
189       if (errors.size() > 0)
190       {
191         StringBuilder errorMsg = new StringBuilder();
192         for (String error : errors)
193         {
194           errorMsg.append(error).append("\n");
195         }
196         JOptionPane.showMessageDialog(this, errorMsg.toString(),
197                 MessageManager.getString("label.pdb_web-service_error"),
198                 JOptionPane.ERROR_MESSAGE);
199       }
200     }
201   }
202
203   public void loadLocalCachedPDBEntries()
204   {
205     ArrayList<CachedPDB> entries = new ArrayList<CachedPDB>();
206     for (SequenceI seq : selectedSequences)
207     {
208       if (seq.getDatasetSequence() != null
209               && seq.getDatasetSequence().getAllPDBEntries() != null)
210       {
211         for (PDBEntry pdbEntry : seq.getDatasetSequence()
212                 .getAllPDBEntries())
213         {
214           if (pdbEntry.getFile() != null)
215           {
216             entries.add(new CachedPDB(seq, pdbEntry));
217           }
218         }
219       }
220     }
221
222     PDBEntryTableModel tableModelx = new PDBEntryTableModel(entries);
223     tbl_local_pdb.setModel(tableModelx);
224   }
225
226   /**
227    * Builds a query string for a given sequences using its DBRef entries
228    * 
229    * @param seq
230    *          the sequences to build a query for
231    * @return the built query string
232    */
233
234   public static String buildQuery(SequenceI seq)
235   {
236     HashSet<String> seqRefs = new LinkedHashSet<String>();
237     String seqName = seq.getName();
238     String[] names = seqName.toLowerCase().split("\\|");
239     for (String name : names)
240     {
241       // System.out.println("Found name : " + name);
242       name.trim();
243       if (isValidSeqName(name))
244       {
245         seqRefs.add(name);
246       }
247     }
248
249     if (seq.getAllPDBEntries() != null)
250     {
251       for (PDBEntry entry : seq.getAllPDBEntries())
252       {
253         if (isValidSeqName(entry.getId()))
254         {
255           seqRefs.add(entry.getId());
256         }
257       }
258     }
259
260     if (seq.getDBRef() != null && seq.getDBRef().length != 0)
261     {
262       int count = 0;
263       for (DBRefEntry dbRef : seq.getDBRef())
264       {
265         if (isValidSeqName(getDBRefId(dbRef)))
266         {
267           seqRefs.add(getDBRefId(dbRef));
268         }
269         ++count;
270         if (count > 10)
271         {
272           break;
273         }
274       }
275     }
276
277     StringBuilder queryBuilder = new StringBuilder();
278     for (String seqRef : seqRefs)
279     {
280       queryBuilder.append("text:").append(seqRef).append(" OR ");
281     }
282     int endIndex = queryBuilder.lastIndexOf(" OR ");
283
284     if (queryBuilder.toString().length() < 6)
285     {
286       return null;
287     }
288     String query = queryBuilder.toString().substring(5, endIndex);
289     return query;
290   }
291
292   /**
293    * Ensures sequence ref names are not less than 3 characters and does not
294    * contain a database name
295    * 
296    * @param seqName
297    * @return
298    */
299   public static boolean isValidSeqName(String seqName)
300   {
301     // System.out.println("seqName : " + seqName);
302     String ignoreList = "pdb,uniprot,swiss-prot";
303     if (seqName.length() < 3)
304     {
305       return false;
306     }
307     if (seqName.contains(":"))
308     {
309       return false;
310     }
311     seqName = seqName.toLowerCase();
312     for (String ignoredEntry : ignoreList.split(","))
313     {
314       if (seqName.contains(ignoredEntry))
315       {
316         return false;
317       }
318     }
319     return true;
320   }
321
322   public static String getDBRefId(DBRefEntry dbRef)
323   {
324     String ref = dbRef.getAccessionId().replaceAll("GO:", "");
325     return ref;
326   }
327
328   /**
329    * Filters a given list of discovered structures based on supplied argument
330    * 
331    * @param fieldToFilterBy
332    *          the field to filter by
333    */
334   public void filterResultSet(final String fieldToFilterBy)
335   {
336     Thread filterThread = new Thread(new Runnable()
337     {
338       @Override
339       public void run()
340       {
341         long startTime = System.currentTimeMillis();
342         lbl_loading.setVisible(true);
343         Collection<PDBDocField> wantedFields = PDBDocFieldPreferences
344                 .getStructureSummaryFields();
345         Collection<PDBResponseSummary> filteredResponse = new HashSet<PDBResponseSummary>();
346         HashSet<String> errors = new HashSet<String>();
347         for (SequenceI seq : selectedSequences)
348         {
349           PDBRestRequest pdbRequest = new PDBRestRequest();
350           pdbRequest.setAllowEmptySeq(false);
351           pdbRequest.setResponseSize(1);
352           pdbRequest.setFieldToSearchBy("(text:");
353           pdbRequest.setFieldToSortBy(fieldToFilterBy,
354                   !chk_invertFilter.isSelected());
355           pdbRequest.setSearchTerm(buildQuery(seq) + ")");
356           pdbRequest.setWantedFields(wantedFields);
357           pdbRequest.setAssociatedSequence(seq);
358           pdbRestCleint = new PDBRestClient();
359           PDBRestResponse resultList;
360           try
361           {
362             resultList = pdbRestCleint.executeRequest(pdbRequest);
363           } catch (Exception e)
364           {
365             e.printStackTrace();
366             errors.add(e.getMessage());
367             continue;
368           }
369           lastPdbRequest = pdbRequest;
370           if (resultList.getSearchSummary() != null
371                   && !resultList.getSearchSummary().isEmpty())
372           {
373             filteredResponse.addAll(resultList.getSearchSummary());
374           }
375         }
376
377         String totalTime = (System.currentTimeMillis() - startTime)
378                 + " milli secs";
379         if (!filteredResponse.isEmpty())
380         {
381           final int filterResponseCount = filteredResponse.size();
382           Collection<PDBResponseSummary> reorderedStructuresSet = new LinkedHashSet<PDBResponseSummary>();
383           reorderedStructuresSet.addAll(filteredResponse);
384           reorderedStructuresSet.addAll(discoveredStructuresSet);
385           tbl_summary.setModel(PDBRestResponse.getTableModel(
386                   lastPdbRequest, reorderedStructuresSet));
387
388           // Update table selection model here
389           tbl_summary.addRowSelectionInterval(0, filterResponseCount - 1);
390           mainFrame.setTitle(MessageManager.formatMessage(
391                   "label.structure_chooser_filter_time", totalTime));
392         }
393         else
394         {
395           mainFrame.setTitle(MessageManager.formatMessage(
396                   "label.structure_chooser_filter_time", totalTime));
397           if (errors.size() > 0)
398           {
399             StringBuilder errorMsg = new StringBuilder();
400             for (String error : errors)
401             {
402               errorMsg.append(error).append("\n");
403             }
404             JOptionPane.showMessageDialog(
405                     null,
406                     errorMsg.toString(),
407                     MessageManager.getString("label.pdb_web-service_error"),
408                     JOptionPane.ERROR_MESSAGE);
409           }
410         }
411
412         lbl_loading.setVisible(false);
413
414         validateSelections();
415       }
416     });
417     filterThread.start();
418   }
419
420   /**
421    * Handles action event for btn_pdbFromFile
422    */
423   public void pdbFromFile_actionPerformed()
424   {
425     jalview.io.JalviewFileChooser chooser = new jalview.io.JalviewFileChooser(
426             jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
427     chooser.setFileView(new jalview.io.JalviewFileView());
428     chooser.setDialogTitle(MessageManager.formatMessage(
429             "label.select_pdb_file_for",
430             selectedSequence.getDisplayId(false)));
431     chooser.setToolTipText(MessageManager.formatMessage(
432             "label.load_pdb_file_associate_with_sequence",
433             selectedSequence.getDisplayId(false)));
434
435     int value = chooser.showOpenDialog(null);
436     if (value == jalview.io.JalviewFileChooser.APPROVE_OPTION)
437     {
438       selectedPdbFileName = chooser.getSelectedFile().getPath();
439       jalview.bin.Cache.setProperty("LAST_DIRECTORY", selectedPdbFileName);
440       validateSelections();
441     }
442   }
443
444   /**
445    * Populates the filter combo-box options dynamically depending on discovered
446    * structures
447    */
448   protected void populateFilterComboBox()
449   {
450     if (isStructuresDiscovered())
451     {
452       cmb_filterOption.addItem(new FilterOption("Best Quality",
453               PDBDocField.OVERALL_QUALITY.getCode(), VIEWS_FILTER));
454       cmb_filterOption.addItem(new FilterOption("Best UniProt Coverage",
455               PDBDocField.UNIPROT_COVERAGE.getCode(), VIEWS_FILTER));
456       cmb_filterOption.addItem(new FilterOption("Highest Resolution",
457               PDBDocField.RESOLUTION.getCode(), VIEWS_FILTER));
458       cmb_filterOption.addItem(new FilterOption("Highest Protein Chain",
459               PDBDocField.PROTEIN_CHAIN_COUNT.getCode(), VIEWS_FILTER));
460       cmb_filterOption.addItem(new FilterOption("Highest Bound Molecules",
461               PDBDocField.BOUND_MOLECULE_COUNT.getCode(), VIEWS_FILTER));
462       cmb_filterOption.addItem(new FilterOption("Highest Polymer Residues",
463               PDBDocField.POLYMER_RESIDUE_COUNT.getCode(), VIEWS_FILTER));
464     }
465     cmb_filterOption.addItem(new FilterOption("Enter PDB Id", "-",
466             VIEWS_ENTER_ID));
467     cmb_filterOption.addItem(new FilterOption("From File", "-",
468             VIEWS_FROM_FILE));
469     cmb_filterOption.addItem(new FilterOption("Cached PDB Entries", "-",
470             VIEWS_LOCAL_PDB));
471   }
472
473   /**
474    * Updates the displayed view based on the selected filter option
475    */
476   protected void updateCurrentView()
477   {
478     FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
479             .getSelectedItem());
480     layout_switchableViews.show(pnl_switchableViews,
481             selectedFilterOpt.getView());
482     String filterTitle = mainFrame.getTitle();
483     mainFrame.setTitle(frameTitle);
484     chk_invertFilter.setVisible(false);
485     if (selectedFilterOpt.getView() == VIEWS_FILTER)
486     {
487       mainFrame.setTitle(filterTitle);
488       chk_invertFilter.setVisible(true);
489       filterResultSet(selectedFilterOpt.getValue());
490     }
491     else if (selectedFilterOpt.getView() == VIEWS_ENTER_ID
492             || selectedFilterOpt.getView() == VIEWS_FROM_FILE)
493     {
494       mainFrame.setTitle(MessageManager
495               .getString("label.structure_chooser_manual_association"));
496       idInputAssSeqPanel.loadCmbAssSeq();
497       fileChooserAssSeqPanel.loadCmbAssSeq();
498     }
499     validateSelections();
500   }
501
502   /**
503    * Validates user selection and activates the view button if all parameters
504    * are correct
505    */
506   public void validateSelections()
507   {
508     FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
509             .getSelectedItem());
510     btn_view.setEnabled(false);
511     String currentView = selectedFilterOpt.getView();
512     if (currentView == VIEWS_FILTER)
513     {
514       if (tbl_summary.getSelectedRows().length > 0)
515       {
516         btn_view.setEnabled(true);
517       }
518     }
519     else if (currentView == VIEWS_LOCAL_PDB)
520     {
521       if (tbl_local_pdb.getSelectedRows().length > 0)
522       {
523         btn_view.setEnabled(true);
524       }
525     }
526     else if (currentView == VIEWS_ENTER_ID)
527     {
528       validateAssociationEnterPdb();
529     }
530     else if (currentView == VIEWS_FROM_FILE)
531     {
532       validateAssociationFromFile();
533     }
534   }
535
536   /**
537    * Validates inputs from the Manual PDB entry panel
538    */
539   public void validateAssociationEnterPdb()
540   {
541     AssociateSeqOptions assSeqOpt = (AssociateSeqOptions) idInputAssSeqPanel
542             .getCmb_assSeq().getSelectedItem();
543     lbl_pdbManualFetchStatus.setIcon(errorImage);
544     lbl_pdbManualFetchStatus.setToolTipText("");
545     if (txt_search.getText().length() > 0)
546     {
547       lbl_pdbManualFetchStatus
548               .setToolTipText(JvSwingUtils.wrapTooltip(true, MessageManager
549                       .formatMessage("info.no_pdb_entry_found_for",
550                               txt_search.getText())));
551     }
552
553     if (errorWarning.length() > 0)
554     {
555       lbl_pdbManualFetchStatus.setIcon(warningImage);
556       lbl_pdbManualFetchStatus.setToolTipText(JvSwingUtils.wrapTooltip(
557               true, errorWarning.toString()));
558     }
559
560     if (selectedSequences.length == 1
561             || !assSeqOpt.getName().equalsIgnoreCase(
562                     "-Select Associated Seq-"))
563     {
564       txt_search.setEnabled(true);
565       if (isValidPBDEntry)
566       {
567         btn_view.setEnabled(true);
568         lbl_pdbManualFetchStatus.setToolTipText("");
569         lbl_pdbManualFetchStatus.setIcon(goodImage);
570       }
571     }
572     else
573     {
574       txt_search.setEnabled(false);
575       lbl_pdbManualFetchStatus.setIcon(errorImage);
576     }
577   }
578
579   /**
580    * Validates inputs for the manual PDB file selection options
581    */
582   public void validateAssociationFromFile()
583   {
584     AssociateSeqOptions assSeqOpt = (AssociateSeqOptions) fileChooserAssSeqPanel
585             .getCmb_assSeq().getSelectedItem();
586     lbl_fromFileStatus.setIcon(errorImage);
587     if (selectedSequences.length == 1
588             || (assSeqOpt != null && !assSeqOpt.getName().equalsIgnoreCase(
589                     "-Select Associated Seq-")))
590     {
591       btn_pdbFromFile.setEnabled(true);
592       if (selectedPdbFileName != null && selectedPdbFileName.length() > 0)
593       {
594         btn_view.setEnabled(true);
595         lbl_fromFileStatus.setIcon(goodImage);
596       }
597     }
598     else
599     {
600       btn_pdbFromFile.setEnabled(false);
601       lbl_fromFileStatus.setIcon(errorImage);
602     }
603   }
604
605   @Override
606   public void cmbAssSeqStateChanged()
607   {
608     validateSelections();
609   }
610
611   /**
612    * Handles the state change event for the 'filter' combo-box and 'invert'
613    * check-box
614    */
615   @Override
616   protected void stateChanged(ItemEvent e)
617   {
618     if (e.getSource() instanceof JCheckBox)
619     {
620       updateCurrentView();
621     }
622     else
623     {
624       if (e.getStateChange() == ItemEvent.SELECTED)
625       {
626         updateCurrentView();
627       }
628     }
629
630   }
631
632   /**
633    * Handles action event for btn_ok
634    */
635   @Override
636   public void ok_ActionPerformed()
637   {
638     FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
639             .getSelectedItem());
640     String currentView = selectedFilterOpt.getView();
641     if (currentView == VIEWS_FILTER)
642     {
643       int pdbIdColIndex = tbl_summary.getColumn(
644               PDBRestClient.PDBDocField.PDB_ID.getName()).getModelIndex();
645       int refSeqColIndex = tbl_summary.getColumn("Ref Sequence")
646               .getModelIndex();
647       int[] selectedRows = tbl_summary.getSelectedRows();
648       PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
649       int count = 0;
650       ArrayList<SequenceI> selectedSeqsToView = new ArrayList<SequenceI>();
651       for (int row : selectedRows)
652       {
653         String pdbIdStr = tbl_summary.getValueAt(row, pdbIdColIndex)
654                 .toString();
655         SequenceI selectedSeq = (SequenceI) tbl_summary.getValueAt(row,
656                 refSeqColIndex);
657         selectedSeqsToView.add(selectedSeq);
658         PDBEntry pdbEntry = selectedSeq.getPDBEntry(pdbIdStr);
659         if (pdbEntry == null)
660         {
661           pdbEntry = new PDBEntry();
662           pdbEntry.setId(pdbIdStr);
663           pdbEntry.setType(PDBEntry.Type.PDB);
664           selectedSeq.getDatasetSequence().addPDBId(pdbEntry);
665         }
666         pdbEntriesToView[count++] = pdbEntry;
667       }
668       SequenceI[] selectedSeqs = selectedSeqsToView
669               .toArray(new SequenceI[selectedSeqsToView.size()]);
670       launchStructureViewer(ap.getStructureSelectionManager(),
671               pdbEntriesToView, ap, selectedSeqs);
672     }
673     else if (currentView == VIEWS_LOCAL_PDB)
674     {
675       int[] selectedRows = tbl_local_pdb.getSelectedRows();
676       PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
677       int count = 0;
678       int pdbIdColIndex = tbl_local_pdb.getColumn(
679               PDBRestClient.PDBDocField.PDB_ID.getName()).getModelIndex();
680       int refSeqColIndex = tbl_local_pdb.getColumn("Ref Sequence")
681               .getModelIndex();
682       ArrayList<SequenceI> selectedSeqsToView = new ArrayList<SequenceI>();
683       for (int row : selectedRows)
684       {
685         PDBEntry pdbEntry = (PDBEntry) tbl_local_pdb.getValueAt(row,
686                 pdbIdColIndex);
687         pdbEntriesToView[count++] = pdbEntry;
688         SequenceI selectedSeq = (SequenceI) tbl_local_pdb.getValueAt(row,
689                 refSeqColIndex);
690         selectedSeqsToView.add(selectedSeq);
691       }
692       SequenceI[] selectedSeqs = selectedSeqsToView
693               .toArray(new SequenceI[selectedSeqsToView.size()]);
694       launchStructureViewer(ap.getStructureSelectionManager(),
695               pdbEntriesToView, ap, selectedSeqs);
696     }
697     else if (currentView == VIEWS_ENTER_ID)
698     {
699       SequenceI userSelectedSeq = ((AssociateSeqOptions) idInputAssSeqPanel
700               .getCmb_assSeq().getSelectedItem()).getSequence();
701       if (userSelectedSeq != null)
702       {
703         selectedSequence = userSelectedSeq;
704       }
705
706       String pdbIdStr = txt_search.getText();
707       PDBEntry pdbEntry = selectedSequence.getPDBEntry(pdbIdStr);
708       if (pdbEntry == null)
709       {
710         pdbEntry = new PDBEntry();
711         pdbEntry.setId(pdbIdStr);
712         pdbEntry.setType(PDBEntry.Type.PDB);
713         selectedSequence.getDatasetSequence().addPDBId(pdbEntry);
714       }
715
716       PDBEntry[] pdbEntriesToView = new PDBEntry[] { pdbEntry };
717       launchStructureViewer(ap.getStructureSelectionManager(),
718               pdbEntriesToView, ap, new SequenceI[] { selectedSequence });
719     }
720     else if (currentView == VIEWS_FROM_FILE)
721     {
722       SequenceI userSelectedSeq = ((AssociateSeqOptions) fileChooserAssSeqPanel
723               .getCmb_assSeq().getSelectedItem()).getSequence();
724       if (userSelectedSeq != null)
725       {
726         selectedSequence = userSelectedSeq;
727       }
728       PDBEntry fileEntry = new AssociatePdbFileWithSeq()
729               .associatePdbWithSeq(selectedPdbFileName,
730                       jalview.io.AppletFormatAdapter.FILE,
731                       selectedSequence, true, Desktop.instance);
732
733       launchStructureViewer(ap.getStructureSelectionManager(),
734               new PDBEntry[] { fileEntry }, ap,
735               new SequenceI[] { selectedSequence });
736     }
737     mainFrame.dispose();
738   }
739
740   private void launchStructureViewer(final StructureSelectionManager ssm,
741           final PDBEntry[] pdbEntriesToView,
742           final AlignmentPanel alignPanel, final SequenceI[] sequences)
743   {
744     final StructureViewer sViewer = new StructureViewer(ssm);
745     new Thread(new Runnable()
746     {
747       public void run()
748       {
749         if (pdbEntriesToView.length > 1)
750         {
751           ArrayList<SequenceI[]> seqsMap = new ArrayList<SequenceI[]>();
752           for (SequenceI seq : sequences)
753           {
754             seqsMap.add(new SequenceI[] { seq });
755           }
756           SequenceI[][] collatedSeqs = seqsMap.toArray(new SequenceI[0][0]);
757           sViewer.viewStructures(pdbEntriesToView, collatedSeqs, alignPanel);
758         }
759         else
760         {
761           sViewer.viewStructures(pdbEntriesToView[0], sequences, alignPanel);
762         }
763       }
764     }).start();
765   }
766
767   /**
768    * Populates the combo-box used in associating manually fetched structures to
769    * a unique sequence when more than one sequence selection is made.
770    */
771   public void populateCmbAssociateSeqOptions(
772           JComboBox<AssociateSeqOptions> cmb_assSeq, JLabel lbl_associateSeq)
773   {
774     cmb_assSeq.removeAllItems();
775     cmb_assSeq.addItem(new AssociateSeqOptions("-Select Associated Seq-",
776             null));
777     lbl_associateSeq.setVisible(false);
778     if (selectedSequences.length > 1)
779     {
780       for (SequenceI seq : selectedSequences)
781       {
782         cmb_assSeq.addItem(new AssociateSeqOptions(seq));
783       }
784     }
785     else
786     {
787       String seqName = selectedSequence.getDisplayId(false);
788       seqName = seqName.length() <= 40 ? seqName : seqName.substring(0, 39);
789       lbl_associateSeq.setText(seqName);
790       lbl_associateSeq.setVisible(true);
791       cmb_assSeq.setVisible(false);
792     }
793   }
794
795   public boolean isStructuresDiscovered()
796   {
797     return structuresDiscovered;
798   }
799
800   public void setStructuresDiscovered(boolean structuresDiscovered)
801   {
802     this.structuresDiscovered = structuresDiscovered;
803   }
804
805   public Collection<PDBResponseSummary> getDiscoveredStructuresSet()
806   {
807     return discoveredStructuresSet;
808   }
809
810   @Override
811   protected void txt_search_ActionPerformed()
812   {
813     new Thread()
814     {
815       public void run()
816       {
817         errorWarning.setLength(0);
818         isValidPBDEntry = false;
819         if (txt_search.getText().length() > 0)
820         {
821           List<PDBDocField> wantedFields = new ArrayList<PDBDocField>();
822           wantedFields.add(PDBDocField.PDB_ID);
823           PDBRestRequest pdbRequest = new PDBRestRequest();
824           pdbRequest.setAllowEmptySeq(false);
825           pdbRequest.setResponseSize(1);
826           pdbRequest.setFieldToSearchBy("(pdb_id:");
827           pdbRequest.setWantedFields(wantedFields);
828           pdbRequest.setSearchTerm(txt_search.getText() + ")");
829           pdbRequest.setAssociatedSequence(selectedSequence);
830           pdbRestCleint = new PDBRestClient();
831           PDBRestResponse resultList;
832           try
833           {
834             resultList = pdbRestCleint.executeRequest(pdbRequest);
835           } catch (Exception e)
836           {
837             errorWarning.append(e.getMessage());
838             return;
839           } finally
840           {
841             validateSelections();
842           }
843           if (resultList.getSearchSummary() != null
844                   && resultList.getSearchSummary().size() > 0)
845           {
846             isValidPBDEntry = true;
847           }
848         }
849         validateSelections();
850       }
851     }.start();
852   }
853
854   @Override
855   public void tabRefresh()
856   {
857     if (selectedSequences != null)
858     {
859       Thread refreshThread = new Thread(new Runnable()
860       {
861         @Override
862         public void run()
863         {
864           fetchStructuresMetaData();
865           filterResultSet(((FilterOption) cmb_filterOption
866                   .getSelectedItem()).getValue());
867         }
868       });
869       refreshThread.start();
870     }
871   }
872
873   public class PDBEntryTableModel extends AbstractTableModel
874   {
875     String[] columns = { "Ref Sequence", "PDB Id", "Chain", "Type", "File" };
876
877     private List<CachedPDB> pdbEntries;
878
879     public PDBEntryTableModel(List<CachedPDB> pdbEntries)
880     {
881       this.pdbEntries = new ArrayList<CachedPDB>(pdbEntries);
882     }
883
884     @Override
885     public String getColumnName(int columnIndex)
886     {
887       return columns[columnIndex];
888     }
889
890     @Override
891     public int getRowCount()
892     {
893       return pdbEntries.size();
894     }
895
896     @Override
897     public int getColumnCount()
898     {
899       return columns.length;
900     }
901
902     @Override
903     public boolean isCellEditable(int row, int column)
904     {
905       return false;
906     }
907
908     @Override
909     public Object getValueAt(int rowIndex, int columnIndex)
910     {
911       Object value = "??";
912       CachedPDB entry = pdbEntries.get(rowIndex);
913       switch (columnIndex)
914       {
915       case 0:
916         value = entry.getSequence();
917         break;
918       case 1:
919         value = entry.getPdbEntry();
920         break;
921       case 2:
922         value = entry.getPdbEntry().getChainCode() == null ? "_" : entry
923                 .getPdbEntry().getChainCode();
924         break;
925       case 3:
926         value = entry.getPdbEntry().getType();
927         break;
928       case 4:
929         value = entry.getPdbEntry().getFile();
930         break;
931       }
932       return value;
933     }
934
935     @Override
936     public Class<?> getColumnClass(int columnIndex)
937     {
938       return columnIndex == 0 ? SequenceI.class : PDBEntry.class;
939     }
940
941     public CachedPDB getPDBEntryAt(int row)
942     {
943       return pdbEntries.get(row);
944     }
945
946   }
947
948   private class CachedPDB
949   {
950     private SequenceI sequence;
951
952     private PDBEntry pdbEntry;
953
954     public CachedPDB(SequenceI sequence, PDBEntry pdbEntry)
955     {
956       this.sequence = sequence;
957       this.pdbEntry = pdbEntry;
958     }
959
960     public SequenceI getSequence()
961     {
962       return sequence;
963     }
964
965     public PDBEntry getPdbEntry()
966     {
967       return pdbEntry;
968     }
969
970   }
971 }