JAL-2780 JAL-2781 programmatically select structures for retrieval via Structure...
[jalview.git] / src / jalview / gui / StructureChooser.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ 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.bin.Jalview;
25 import jalview.datamodel.DBRefEntry;
26 import jalview.datamodel.DBRefSource;
27 import jalview.datamodel.PDBEntry;
28 import jalview.datamodel.SequenceI;
29 import jalview.fts.api.FTSData;
30 import jalview.fts.api.FTSDataColumnI;
31 import jalview.fts.api.FTSRestClientI;
32 import jalview.fts.core.FTSRestRequest;
33 import jalview.fts.core.FTSRestResponse;
34 import jalview.fts.service.pdb.PDBFTSRestClient;
35 import jalview.io.DataSourceType;
36 import jalview.jbgui.GStructureChooser;
37 import jalview.structure.StructureMapping;
38 import jalview.structure.StructureSelectionManager;
39 import jalview.util.MessageManager;
40 import jalview.ws.DBRefFetcher;
41 import jalview.ws.sifts.SiftsSettings;
42
43 import java.awt.event.ItemEvent;
44 import java.util.ArrayList;
45 import java.util.Collection;
46 import java.util.HashSet;
47 import java.util.LinkedHashSet;
48 import java.util.List;
49 import java.util.Objects;
50 import java.util.Set;
51 import java.util.Vector;
52
53 import javax.swing.JCheckBox;
54 import javax.swing.JComboBox;
55 import javax.swing.JLabel;
56 import javax.swing.JTable;
57 import javax.swing.table.AbstractTableModel;
58
59 /**
60  * Provides the behaviors for the Structure chooser Panel
61  * 
62  * @author tcnofoegbu
63  *
64  */
65 @SuppressWarnings("serial")
66 public class StructureChooser extends GStructureChooser
67         implements IProgressIndicator
68 {
69   private static int MAX_QLENGTH = 7820;
70
71   private SequenceI selectedSequence;
72
73   private SequenceI[] selectedSequences;
74
75   private IProgressIndicator progressIndicator;
76
77   private Collection<FTSData> discoveredStructuresSet;
78
79   private FTSRestRequest lastPdbRequest;
80
81   private FTSRestClientI pdbRestCleint;
82
83   private String selectedPdbFileName;
84
85   private boolean isValidPBDEntry;
86
87   private boolean cachedPDBExists;
88
89   public StructureChooser(SequenceI[] selectedSeqs, SequenceI selectedSeq,
90           AlignmentPanel ap)
91   {
92     this.ap = ap;
93     this.selectedSequence = selectedSeq;
94     this.selectedSequences = selectedSeqs;
95     this.progressIndicator = (ap == null) ? null : ap.alignFrame;
96     init();
97   }
98
99   /**
100    * Initializes parameters used by the Structure Chooser Panel
101    */
102   public void init()
103   {
104     if (!Jalview.isHeadlessMode())
105     {
106       progressBar = new ProgressBar(this.statusPanel, this.statusBar);
107     }
108
109     // ensure a filter option is in force for search
110     populateFilterComboBox(true, cachedPDBExists);
111     Thread discoverPDBStructuresThread = new Thread(new Runnable()
112     {
113       @Override
114       public void run()
115       {
116         long startTime = System.currentTimeMillis();
117         updateProgressIndicator(MessageManager
118                 .getString("status.loading_cached_pdb_entries"), startTime);
119         loadLocalCachedPDBEntries();
120         updateProgressIndicator(null, startTime);
121         updateProgressIndicator(MessageManager.getString(
122                 "status.searching_for_pdb_structures"), startTime);
123         fetchStructuresMetaData();
124         // revise filter options if no results were found
125         populateFilterComboBox(isStructuresDiscovered(), cachedPDBExists);
126         updateProgressIndicator(null, startTime);
127         mainFrame.setVisible(true);
128         updateCurrentView();
129       }
130     });
131     discoverPDBStructuresThread.start();
132   }
133
134   /**
135    * Updates the progress indicator with the specified message
136    * 
137    * @param message
138    *          displayed message for the operation
139    * @param id
140    *          unique handle for this indicator
141    */
142   public void updateProgressIndicator(String message, long id)
143   {
144     if (progressIndicator != null)
145     {
146       progressIndicator.setProgressBar(message, id);
147     }
148   }
149
150   /**
151    * Retrieve meta-data for all the structure(s) for a given sequence(s) in a
152    * selection group
153    */
154   public void fetchStructuresMetaData()
155   {
156     long startTime = System.currentTimeMillis();
157     pdbRestCleint = PDBFTSRestClient.getInstance();
158     Collection<FTSDataColumnI> wantedFields = pdbDocFieldPrefs
159             .getStructureSummaryFields();
160
161     discoveredStructuresSet = new LinkedHashSet<>();
162     HashSet<String> errors = new HashSet<>();
163     for (SequenceI seq : selectedSequences)
164     {
165       FTSRestRequest pdbRequest = new FTSRestRequest();
166       pdbRequest.setAllowEmptySeq(false);
167       pdbRequest.setResponseSize(500);
168       pdbRequest.setFieldToSearchBy("(");
169       FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
170               .getSelectedItem());
171       pdbRequest.setFieldToSortBy(selectedFilterOpt.getValue(),
172               !chk_invertFilter.isSelected());
173       pdbRequest.setWantedFields(wantedFields);
174       pdbRequest.setSearchTerm(buildQuery(seq) + ")");
175       pdbRequest.setAssociatedSequence(seq);
176       FTSRestResponse resultList;
177       try
178       {
179         resultList = pdbRestCleint.executeRequest(pdbRequest);
180       } catch (Exception e)
181       {
182         e.printStackTrace();
183         errors.add(e.getMessage());
184         continue;
185       }
186       lastPdbRequest = pdbRequest;
187       if (resultList.getSearchSummary() != null
188               && !resultList.getSearchSummary().isEmpty())
189       {
190         discoveredStructuresSet.addAll(resultList.getSearchSummary());
191       }
192     }
193
194     int noOfStructuresFound = 0;
195     String totalTime = (System.currentTimeMillis() - startTime)
196             + " milli secs";
197     if (discoveredStructuresSet != null
198             && !discoveredStructuresSet.isEmpty())
199     {
200       getResultTable().setModel(FTSRestResponse
201               .getTableModel(lastPdbRequest, discoveredStructuresSet));
202       noOfStructuresFound = discoveredStructuresSet.size();
203       mainFrame.setTitle(MessageManager.formatMessage(
204               "label.structure_chooser_no_of_structures",
205               noOfStructuresFound, totalTime));
206     }
207     else
208     {
209       mainFrame.setTitle(MessageManager
210               .getString("label.structure_chooser_manual_association"));
211       if (errors.size() > 0)
212       {
213         StringBuilder errorMsg = new StringBuilder();
214         for (String error : errors)
215         {
216           errorMsg.append(error).append("\n");
217         }
218         JvOptionPane.showMessageDialog(this, errorMsg.toString(),
219                 MessageManager.getString("label.pdb_web-service_error"),
220                 JvOptionPane.ERROR_MESSAGE);
221       }
222     }
223   }
224
225   public void loadLocalCachedPDBEntries()
226   {
227     ArrayList<CachedPDB> entries = new ArrayList<>();
228     for (SequenceI seq : selectedSequences)
229     {
230       if (seq.getDatasetSequence() != null
231               && seq.getDatasetSequence().getAllPDBEntries() != null)
232       {
233         for (PDBEntry pdbEntry : seq.getDatasetSequence()
234                 .getAllPDBEntries())
235         {
236           if (pdbEntry.getFile() != null)
237           {
238             entries.add(new CachedPDB(seq, pdbEntry));
239           }
240         }
241       }
242     }
243     cachedPDBExists = !entries.isEmpty();
244     PDBEntryTableModel tableModelx = new PDBEntryTableModel(entries);
245     tbl_local_pdb.setModel(tableModelx);
246   }
247
248   /**
249    * Builds a query string for a given sequences using its DBRef entries
250    * 
251    * @param seq
252    *          the sequences to build a query for
253    * @return the built query string
254    */
255
256   public static String buildQuery(SequenceI seq)
257   {
258     boolean isPDBRefsFound = false;
259     boolean isUniProtRefsFound = false;
260     StringBuilder queryBuilder = new StringBuilder();
261     Set<String> seqRefs = new LinkedHashSet<>();
262
263     if (seq.getAllPDBEntries() != null
264             && queryBuilder.length() < MAX_QLENGTH)
265     {
266       for (PDBEntry entry : seq.getAllPDBEntries())
267       {
268         if (isValidSeqName(entry.getId()))
269         {
270           queryBuilder.append("pdb_id:").append(entry.getId().toLowerCase())
271                   .append(" OR ");
272           isPDBRefsFound = true;
273         }
274       }
275     }
276
277     if (seq.getDBRefs() != null && seq.getDBRefs().length != 0)
278     {
279       for (DBRefEntry dbRef : seq.getDBRefs())
280       {
281         if (isValidSeqName(getDBRefId(dbRef))
282                 && queryBuilder.length() < MAX_QLENGTH)
283         {
284           if (dbRef.getSource().equalsIgnoreCase(DBRefSource.UNIPROT))
285           {
286             queryBuilder.append("uniprot_accession:")
287                     .append(getDBRefId(dbRef)).append(" OR ");
288             queryBuilder.append("uniprot_id:").append(getDBRefId(dbRef))
289                     .append(" OR ");
290             isUniProtRefsFound = true;
291           }
292           else if (dbRef.getSource().equalsIgnoreCase(DBRefSource.PDB))
293           {
294
295             queryBuilder.append("pdb_id:")
296                     .append(getDBRefId(dbRef).toLowerCase()).append(" OR ");
297             isPDBRefsFound = true;
298           }
299           else
300           {
301             seqRefs.add(getDBRefId(dbRef));
302           }
303         }
304       }
305     }
306
307     if (!isPDBRefsFound && !isUniProtRefsFound)
308     {
309       String seqName = seq.getName();
310       seqName = sanitizeSeqName(seqName);
311       String[] names = seqName.toLowerCase().split("\\|");
312       for (String name : names)
313       {
314         // System.out.println("Found name : " + name);
315         name.trim();
316         if (isValidSeqName(name))
317         {
318           seqRefs.add(name);
319         }
320       }
321
322       for (String seqRef : seqRefs)
323       {
324         queryBuilder.append("text:").append(seqRef).append(" OR ");
325       }
326     }
327
328     int endIndex = queryBuilder.lastIndexOf(" OR ");
329     if (queryBuilder.toString().length() < 6)
330     {
331       return null;
332     }
333     String query = queryBuilder.toString().substring(0, endIndex);
334     return query;
335   }
336
337   /**
338    * Remove the following special characters from input string +, -, &, !, (, ),
339    * {, }, [, ], ^, ", ~, *, ?, :, \
340    * 
341    * @param seqName
342    * @return
343    */
344   static String sanitizeSeqName(String seqName)
345   {
346     Objects.requireNonNull(seqName);
347     return seqName.replaceAll("\\[\\d*\\]", "")
348             .replaceAll("[^\\dA-Za-z|_]", "").replaceAll("\\s+", "+");
349   }
350
351   /**
352    * Ensures sequence ref names are not less than 3 characters and does not
353    * contain a database name
354    * 
355    * @param seqName
356    * @return
357    */
358   public static boolean isValidSeqName(String seqName)
359   {
360     // System.out.println("seqName : " + seqName);
361     String ignoreList = "pdb,uniprot,swiss-prot";
362     if (seqName.length() < 3)
363     {
364       return false;
365     }
366     if (seqName.contains(":"))
367     {
368       return false;
369     }
370     seqName = seqName.toLowerCase();
371     for (String ignoredEntry : ignoreList.split(","))
372     {
373       if (seqName.contains(ignoredEntry))
374       {
375         return false;
376       }
377     }
378     return true;
379   }
380
381   public static String getDBRefId(DBRefEntry dbRef)
382   {
383     String ref = dbRef.getAccessionId().replaceAll("GO:", "");
384     return ref;
385   }
386
387   /**
388    * Filters a given list of discovered structures based on supplied argument
389    * 
390    * @param fieldToFilterBy
391    *          the field to filter by
392    */
393   public void filterResultSet(final String fieldToFilterBy)
394   {
395     Thread filterThread = new Thread(new Runnable()
396     {
397       @Override
398       public void run()
399       {
400         long startTime = System.currentTimeMillis();
401         pdbRestCleint = PDBFTSRestClient.getInstance();
402         lbl_loading.setVisible(true);
403         Collection<FTSDataColumnI> wantedFields = pdbDocFieldPrefs
404                 .getStructureSummaryFields();
405         Collection<FTSData> filteredResponse = new HashSet<>();
406         HashSet<String> errors = new HashSet<>();
407
408         for (SequenceI seq : selectedSequences)
409         {
410           FTSRestRequest pdbRequest = new FTSRestRequest();
411           if (fieldToFilterBy.equalsIgnoreCase("uniprot_coverage"))
412           {
413             pdbRequest.setAllowEmptySeq(false);
414             pdbRequest.setResponseSize(1);
415             pdbRequest.setFieldToSearchBy("(");
416             pdbRequest.setSearchTerm(buildQuery(seq) + ")");
417             pdbRequest.setWantedFields(wantedFields);
418             pdbRequest.setAssociatedSequence(seq);
419             pdbRequest.setFacet(true);
420             pdbRequest.setFacetPivot(fieldToFilterBy + ",entry_entity");
421             pdbRequest.setFacetPivotMinCount(1);
422           }
423           else
424           {
425             pdbRequest.setAllowEmptySeq(false);
426             pdbRequest.setResponseSize(1);
427             pdbRequest.setFieldToSearchBy("(");
428             pdbRequest.setFieldToSortBy(fieldToFilterBy,
429                     !chk_invertFilter.isSelected());
430             pdbRequest.setSearchTerm(buildQuery(seq) + ")");
431             pdbRequest.setWantedFields(wantedFields);
432             pdbRequest.setAssociatedSequence(seq);
433           }
434           FTSRestResponse resultList;
435           try
436           {
437             resultList = pdbRestCleint.executeRequest(pdbRequest);
438           } catch (Exception e)
439           {
440             e.printStackTrace();
441             errors.add(e.getMessage());
442             continue;
443           }
444           lastPdbRequest = pdbRequest;
445           if (resultList.getSearchSummary() != null
446                   && !resultList.getSearchSummary().isEmpty())
447           {
448             filteredResponse.addAll(resultList.getSearchSummary());
449           }
450         }
451
452         String totalTime = (System.currentTimeMillis() - startTime)
453                 + " milli secs";
454         if (!filteredResponse.isEmpty())
455         {
456           final int filterResponseCount = filteredResponse.size();
457           Collection<FTSData> reorderedStructuresSet = new LinkedHashSet<>();
458           reorderedStructuresSet.addAll(filteredResponse);
459           reorderedStructuresSet.addAll(discoveredStructuresSet);
460           getResultTable().setModel(FTSRestResponse
461                   .getTableModel(lastPdbRequest, reorderedStructuresSet));
462
463           FTSRestResponse.configureTableColumn(getResultTable(),
464                   wantedFields, tempUserPrefs);
465           getResultTable().getColumn("Ref Sequence").setPreferredWidth(120);
466           getResultTable().getColumn("Ref Sequence").setMinWidth(100);
467           getResultTable().getColumn("Ref Sequence").setMaxWidth(200);
468           // Update table selection model here
469           getResultTable().addRowSelectionInterval(0,
470                   filterResponseCount - 1);
471           mainFrame.setTitle(MessageManager.formatMessage(
472                   "label.structure_chooser_filter_time", totalTime));
473         }
474         else
475         {
476           mainFrame.setTitle(MessageManager.formatMessage(
477                   "label.structure_chooser_filter_time", totalTime));
478           if (errors.size() > 0)
479           {
480             StringBuilder errorMsg = new StringBuilder();
481             for (String error : errors)
482             {
483               errorMsg.append(error).append("\n");
484             }
485             JvOptionPane.showMessageDialog(null, errorMsg.toString(),
486                     MessageManager.getString("label.pdb_web-service_error"),
487                     JvOptionPane.ERROR_MESSAGE);
488           }
489         }
490
491         lbl_loading.setVisible(false);
492
493         validateSelections();
494       }
495     });
496     filterThread.start();
497   }
498
499   /**
500    * Handles action event for btn_pdbFromFile
501    */
502   @Override
503   public void pdbFromFile_actionPerformed()
504   {
505     jalview.io.JalviewFileChooser chooser = new jalview.io.JalviewFileChooser(
506             jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
507     chooser.setFileView(new jalview.io.JalviewFileView());
508     chooser.setDialogTitle(
509             MessageManager.formatMessage("label.select_pdb_file_for",
510                     selectedSequence.getDisplayId(false)));
511     chooser.setToolTipText(MessageManager.formatMessage(
512             "label.load_pdb_file_associate_with_sequence",
513             selectedSequence.getDisplayId(false)));
514
515     int value = chooser.showOpenDialog(null);
516     if (value == jalview.io.JalviewFileChooser.APPROVE_OPTION)
517     {
518       selectedPdbFileName = chooser.getSelectedFile().getPath();
519       jalview.bin.Cache.setProperty("LAST_DIRECTORY", selectedPdbFileName);
520       validateSelections();
521     }
522   }
523
524   /**
525    * Populates the filter combo-box options dynamically depending on discovered
526    * structures
527    */
528   protected void populateFilterComboBox(boolean haveData,
529           boolean cachedPDBExist)
530   {
531     /*
532      * temporarily suspend the change listener behaviour
533      */
534     cmb_filterOption.removeItemListener(this);
535
536     cmb_filterOption.removeAllItems();
537     if (haveData)
538     {
539       cmb_filterOption.addItem(new FilterOption(
540               MessageManager.getString("label.best_quality"),
541               "overall_quality", VIEWS_FILTER, false));
542       cmb_filterOption.addItem(new FilterOption(
543               MessageManager.getString("label.best_resolution"),
544               "resolution", VIEWS_FILTER, false));
545       cmb_filterOption.addItem(new FilterOption(
546               MessageManager.getString("label.most_protein_chain"),
547               "number_of_protein_chains", VIEWS_FILTER, false));
548       cmb_filterOption.addItem(new FilterOption(
549               MessageManager.getString("label.most_bound_molecules"),
550               "number_of_bound_molecules", VIEWS_FILTER, false));
551       cmb_filterOption.addItem(new FilterOption(
552               MessageManager.getString("label.most_polymer_residues"),
553               "number_of_polymer_residues", VIEWS_FILTER, true));
554     }
555     cmb_filterOption.addItem(
556             new FilterOption(MessageManager.getString("label.enter_pdb_id"),
557                     "-", VIEWS_ENTER_ID, false));
558     cmb_filterOption.addItem(
559             new FilterOption(MessageManager.getString("label.from_file"),
560                     "-", VIEWS_FROM_FILE, false));
561
562     if (cachedPDBExist)
563     {
564       FilterOption cachedOption = new FilterOption(
565               MessageManager.getString("label.cached_structures"),
566               "-", VIEWS_LOCAL_PDB, false);
567       cmb_filterOption.addItem(cachedOption);
568       cmb_filterOption.setSelectedItem(cachedOption);
569     }
570
571     cmb_filterOption.addItemListener(this);
572   }
573
574   /**
575    * Updates the displayed view based on the selected filter option
576    */
577   protected void updateCurrentView()
578   {
579     FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
580             .getSelectedItem());
581     layout_switchableViews.show(pnl_switchableViews,
582             selectedFilterOpt.getView());
583     String filterTitle = mainFrame.getTitle();
584     mainFrame.setTitle(frameTitle);
585     chk_invertFilter.setVisible(false);
586     if (selectedFilterOpt.getView() == VIEWS_FILTER)
587     {
588       mainFrame.setTitle(filterTitle);
589       chk_invertFilter.setVisible(true);
590       filterResultSet(selectedFilterOpt.getValue());
591     }
592     else if (selectedFilterOpt.getView() == VIEWS_ENTER_ID
593             || selectedFilterOpt.getView() == VIEWS_FROM_FILE)
594     {
595       mainFrame.setTitle(MessageManager
596               .getString("label.structure_chooser_manual_association"));
597       idInputAssSeqPanel.loadCmbAssSeq();
598       fileChooserAssSeqPanel.loadCmbAssSeq();
599     }
600     validateSelections();
601   }
602
603   /**
604    * Validates user selection and activates the view button if all parameters
605    * are correct
606    */
607   @Override
608   public void validateSelections()
609   {
610     FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
611             .getSelectedItem());
612     btn_view.setEnabled(false);
613     String currentView = selectedFilterOpt.getView();
614     if (currentView == VIEWS_FILTER)
615     {
616       if (getResultTable().getSelectedRows().length > 0)
617       {
618         btn_view.setEnabled(true);
619       }
620     }
621     else if (currentView == VIEWS_LOCAL_PDB)
622     {
623       if (tbl_local_pdb.getSelectedRows().length > 0)
624       {
625         btn_view.setEnabled(true);
626       }
627     }
628     else if (currentView == VIEWS_ENTER_ID)
629     {
630       validateAssociationEnterPdb();
631     }
632     else if (currentView == VIEWS_FROM_FILE)
633     {
634       validateAssociationFromFile();
635     }
636   }
637
638   /**
639    * Validates inputs from the Manual PDB entry panel
640    */
641   public void validateAssociationEnterPdb()
642   {
643     AssociateSeqOptions assSeqOpt = (AssociateSeqOptions) idInputAssSeqPanel
644             .getCmb_assSeq().getSelectedItem();
645     lbl_pdbManualFetchStatus.setIcon(errorImage);
646     lbl_pdbManualFetchStatus.setToolTipText("");
647     if (txt_search.getText().length() > 0)
648     {
649       lbl_pdbManualFetchStatus.setToolTipText(JvSwingUtils.wrapTooltip(true,
650               MessageManager.formatMessage("info.no_pdb_entry_found_for",
651                       txt_search.getText())));
652     }
653
654     if (errorWarning.length() > 0)
655     {
656       lbl_pdbManualFetchStatus.setIcon(warningImage);
657       lbl_pdbManualFetchStatus.setToolTipText(
658               JvSwingUtils.wrapTooltip(true, errorWarning.toString()));
659     }
660
661     if (selectedSequences.length == 1 || !assSeqOpt.getName()
662             .equalsIgnoreCase("-Select Associated Seq-"))
663     {
664       txt_search.setEnabled(true);
665       if (isValidPBDEntry)
666       {
667         btn_view.setEnabled(true);
668         lbl_pdbManualFetchStatus.setToolTipText("");
669         lbl_pdbManualFetchStatus.setIcon(goodImage);
670       }
671     }
672     else
673     {
674       txt_search.setEnabled(false);
675       lbl_pdbManualFetchStatus.setIcon(errorImage);
676     }
677   }
678
679   /**
680    * Validates inputs for the manual PDB file selection options
681    */
682   public void validateAssociationFromFile()
683   {
684     AssociateSeqOptions assSeqOpt = (AssociateSeqOptions) fileChooserAssSeqPanel
685             .getCmb_assSeq().getSelectedItem();
686     lbl_fromFileStatus.setIcon(errorImage);
687     if (selectedSequences.length == 1 || (assSeqOpt != null && !assSeqOpt
688             .getName().equalsIgnoreCase("-Select Associated Seq-")))
689     {
690       btn_pdbFromFile.setEnabled(true);
691       if (selectedPdbFileName != null && selectedPdbFileName.length() > 0)
692       {
693         btn_view.setEnabled(true);
694         lbl_fromFileStatus.setIcon(goodImage);
695       }
696     }
697     else
698     {
699       btn_pdbFromFile.setEnabled(false);
700       lbl_fromFileStatus.setIcon(errorImage);
701     }
702   }
703
704   @Override
705   public void cmbAssSeqStateChanged()
706   {
707     validateSelections();
708   }
709
710   /**
711    * Handles the state change event for the 'filter' combo-box and 'invert'
712    * check-box
713    */
714   @Override
715   protected void stateChanged(ItemEvent e)
716   {
717     if (e.getSource() instanceof JCheckBox)
718     {
719       updateCurrentView();
720     }
721     else
722     {
723       if (e.getStateChange() == ItemEvent.SELECTED)
724       {
725         updateCurrentView();
726       }
727     }
728
729   }
730
731   public void selectStructure(String...pdbids)
732   {
733     FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
734             .getSelectedItem());
735     String currentView = selectedFilterOpt.getView();
736     JTable restable = (currentView == VIEWS_FILTER) ? getResultTable()
737             : tbl_local_pdb;
738
739     if (currentView == VIEWS_FILTER)
740     {
741      
742       int pdbIdColIndex = restable.getColumn("PDB Id")
743               .getModelIndex();
744       for (int r = 0; r < restable.getRowCount(); r++)
745       {
746         for (int p=0;p<pdbids.length;p++)
747         {
748           if (String.valueOf(restable.getValueAt(r, pdbIdColIndex))
749                   .equalsIgnoreCase(pdbids[p]))
750         {
751             restable.setRowSelectionInterval(r, r);
752         }
753         }
754       }
755     }
756   }
757   /**
758    * Handles action event for btn_ok
759    */
760   @Override
761   public void ok_ActionPerformed()
762   {
763     final StructureSelectionManager ssm = ap.getStructureSelectionManager();
764
765     final int preferredHeight = pnl_filter.getHeight();
766
767     new Thread(new Runnable()
768     {
769       @Override
770       public void run()
771       {
772         FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
773                 .getSelectedItem());
774         String currentView = selectedFilterOpt.getView();
775         JTable restable = (currentView == VIEWS_FILTER) ? getResultTable()
776                 : tbl_local_pdb;
777
778         if (currentView == VIEWS_FILTER)
779         {
780           int pdbIdColIndex = restable.getColumn("PDB Id")
781                   .getModelIndex();
782           int refSeqColIndex = restable.getColumn("Ref Sequence")
783                   .getModelIndex();
784           int[] selectedRows = restable.getSelectedRows();
785           PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
786           int count = 0;
787           List<SequenceI> selectedSeqsToView = new ArrayList<>();
788           for (int row : selectedRows)
789           {
790             String pdbIdStr = restable
791                     .getValueAt(row, pdbIdColIndex).toString();
792             SequenceI selectedSeq = (SequenceI) restable
793                     .getValueAt(row, refSeqColIndex);
794             selectedSeqsToView.add(selectedSeq);
795             PDBEntry pdbEntry = selectedSeq.getPDBEntry(pdbIdStr);
796             if (pdbEntry == null)
797             {
798               pdbEntry = getFindEntry(pdbIdStr,
799                       selectedSeq.getAllPDBEntries());
800             }
801
802             if (pdbEntry == null)
803             {
804               pdbEntry = new PDBEntry();
805               pdbEntry.setId(pdbIdStr);
806               pdbEntry.setType(PDBEntry.Type.PDB);
807               selectedSeq.getDatasetSequence().addPDBId(pdbEntry);
808             }
809             pdbEntriesToView[count++] = pdbEntry;
810           }
811           SequenceI[] selectedSeqs = selectedSeqsToView
812                   .toArray(new SequenceI[selectedSeqsToView.size()]);
813           launchStructureViewer(ssm, pdbEntriesToView, ap, selectedSeqs);
814         }
815         else if (currentView == VIEWS_LOCAL_PDB)
816         {
817           int[] selectedRows = tbl_local_pdb.getSelectedRows();
818           PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
819           int count = 0;
820           int pdbIdColIndex = tbl_local_pdb.getColumn("PDB Id")
821                   .getModelIndex();
822           int refSeqColIndex = tbl_local_pdb.getColumn("Ref Sequence")
823                   .getModelIndex();
824           List<SequenceI> selectedSeqsToView = new ArrayList<>();
825           for (int row : selectedRows)
826           {
827             PDBEntry pdbEntry = (PDBEntry) tbl_local_pdb.getValueAt(row,
828                     pdbIdColIndex);
829             pdbEntriesToView[count++] = pdbEntry;
830             SequenceI selectedSeq = (SequenceI) tbl_local_pdb
831                     .getValueAt(row, refSeqColIndex);
832             selectedSeqsToView.add(selectedSeq);
833           }
834           SequenceI[] selectedSeqs = selectedSeqsToView
835                   .toArray(new SequenceI[selectedSeqsToView.size()]);
836           launchStructureViewer(ssm, pdbEntriesToView, ap, selectedSeqs);
837         }
838         else if (currentView == VIEWS_ENTER_ID)
839         {
840           SequenceI userSelectedSeq = ((AssociateSeqOptions) idInputAssSeqPanel
841                   .getCmb_assSeq().getSelectedItem()).getSequence();
842           if (userSelectedSeq != null)
843           {
844             selectedSequence = userSelectedSeq;
845           }
846           String pdbIdStr = txt_search.getText();
847           PDBEntry pdbEntry = selectedSequence.getPDBEntry(pdbIdStr);
848           if (pdbEntry == null)
849           {
850             pdbEntry = new PDBEntry();
851             if (pdbIdStr.split(":").length > 1)
852             {
853               pdbEntry.setId(pdbIdStr.split(":")[0]);
854               pdbEntry.setChainCode(pdbIdStr.split(":")[1].toUpperCase());
855             }
856             else
857             {
858               pdbEntry.setId(pdbIdStr);
859             }
860             pdbEntry.setType(PDBEntry.Type.PDB);
861             selectedSequence.getDatasetSequence().addPDBId(pdbEntry);
862           }
863
864           PDBEntry[] pdbEntriesToView = new PDBEntry[] { pdbEntry };
865           launchStructureViewer(ssm, pdbEntriesToView, ap,
866                   new SequenceI[]
867                   { selectedSequence });
868         }
869         else if (currentView == VIEWS_FROM_FILE)
870         {
871           SequenceI userSelectedSeq = ((AssociateSeqOptions) fileChooserAssSeqPanel
872                   .getCmb_assSeq().getSelectedItem()).getSequence();
873           if (userSelectedSeq != null)
874           {
875             selectedSequence = userSelectedSeq;
876           }
877           PDBEntry fileEntry = new AssociatePdbFileWithSeq()
878                   .associatePdbWithSeq(selectedPdbFileName,
879                           DataSourceType.FILE, selectedSequence, true,
880                           Desktop.instance);
881
882           launchStructureViewer(ssm, new PDBEntry[] { fileEntry }, ap,
883                   new SequenceI[]
884                   { selectedSequence });
885         }
886         closeAction(preferredHeight);
887         mainFrame.dispose();
888       }
889     }).start();
890   }
891
892   private PDBEntry getFindEntry(String id, Vector<PDBEntry> pdbEntries)
893   {
894     Objects.requireNonNull(id);
895     Objects.requireNonNull(pdbEntries);
896     PDBEntry foundEntry = null;
897     for (PDBEntry entry : pdbEntries)
898     {
899       if (entry.getId().equalsIgnoreCase(id))
900       {
901         return entry;
902       }
903     }
904     return foundEntry;
905   }
906
907   private void launchStructureViewer(StructureSelectionManager ssm,
908           final PDBEntry[] pdbEntriesToView,
909           final AlignmentPanel alignPanel, SequenceI[] sequences)
910   {
911     long progressId = sequences.hashCode();
912     setProgressBar(MessageManager
913             .getString("status.launching_3d_structure_viewer"), progressId);
914     final StructureViewer sViewer = new StructureViewer(ssm);
915     setProgressBar(null, progressId);
916
917     if (SiftsSettings.isMapWithSifts())
918     {
919       List<SequenceI> seqsWithoutSourceDBRef = new ArrayList<>();
920       int p = 0;
921       // TODO: skip PDBEntry:Sequence pairs where PDBEntry doesn't look like a
922       // real PDB ID. For moment, we can also safely do this if there is already
923       // a known mapping between the PDBEntry and the sequence.
924       for (SequenceI seq : sequences)
925       {
926         PDBEntry pdbe = pdbEntriesToView[p++];
927         if (pdbe != null && pdbe.getFile() != null)
928         {
929           StructureMapping[] smm = ssm.getMapping(pdbe.getFile());
930           if (smm != null && smm.length > 0)
931           {
932             for (StructureMapping sm : smm)
933             {
934               if (sm.getSequence() == seq)
935               {
936                 continue;
937               }
938             }
939           }
940         }
941         if (seq.getPrimaryDBRefs().size() == 0)
942         {
943           seqsWithoutSourceDBRef.add(seq);
944           continue;
945         }
946       }
947       if (!seqsWithoutSourceDBRef.isEmpty())
948       {
949         int y = seqsWithoutSourceDBRef.size();
950         setProgressBar(MessageManager.formatMessage(
951                 "status.fetching_dbrefs_for_sequences_without_valid_refs",
952                 y), progressId);
953         SequenceI[] seqWithoutSrcDBRef = new SequenceI[y];
954         int x = 0;
955         for (SequenceI fSeq : seqsWithoutSourceDBRef)
956         {
957           seqWithoutSrcDBRef[x++] = fSeq;
958         }
959
960         DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef);
961         dbRefFetcher.fetchDBRefs(true);
962
963         setProgressBar("Fetch complete.", progressId); // todo i18n
964       }
965     }
966     if (pdbEntriesToView.length > 1)
967     {
968       setProgressBar(MessageManager.getString(
969               "status.fetching_3d_structures_for_selected_entries"),
970               progressId);
971       sViewer.viewStructures(pdbEntriesToView, sequences, alignPanel);
972     }
973     else
974     {
975       setProgressBar(MessageManager.formatMessage(
976               "status.fetching_3d_structures_for",
977               pdbEntriesToView[0].getId()),progressId);
978       sViewer.viewStructures(pdbEntriesToView[0], sequences, alignPanel);
979     }
980     setProgressBar(null, progressId);
981   }
982
983   /**
984    * Populates the combo-box used in associating manually fetched structures to
985    * a unique sequence when more than one sequence selection is made.
986    */
987   @Override
988   public void populateCmbAssociateSeqOptions(
989           JComboBox<AssociateSeqOptions> cmb_assSeq,
990           JLabel lbl_associateSeq)
991   {
992     cmb_assSeq.removeAllItems();
993     cmb_assSeq.addItem(
994             new AssociateSeqOptions("-Select Associated Seq-", null));
995     lbl_associateSeq.setVisible(false);
996     if (selectedSequences.length > 1)
997     {
998       for (SequenceI seq : selectedSequences)
999       {
1000         cmb_assSeq.addItem(new AssociateSeqOptions(seq));
1001       }
1002     }
1003     else
1004     {
1005       String seqName = selectedSequence.getDisplayId(false);
1006       seqName = seqName.length() <= 40 ? seqName : seqName.substring(0, 39);
1007       lbl_associateSeq.setText(seqName);
1008       lbl_associateSeq.setVisible(true);
1009       cmb_assSeq.setVisible(false);
1010     }
1011   }
1012
1013   public boolean isStructuresDiscovered()
1014   {
1015     return discoveredStructuresSet != null
1016             && !discoveredStructuresSet.isEmpty();
1017   }
1018
1019   public Collection<FTSData> getDiscoveredStructuresSet()
1020   {
1021     return discoveredStructuresSet;
1022   }
1023
1024   @Override
1025   protected void txt_search_ActionPerformed()
1026   {
1027     new Thread()
1028     {
1029       @Override
1030       public void run()
1031       {
1032         errorWarning.setLength(0);
1033         isValidPBDEntry = false;
1034         if (txt_search.getText().length() > 0)
1035         {
1036           String searchTerm = txt_search.getText().toLowerCase();
1037           searchTerm = searchTerm.split(":")[0];
1038           // System.out.println(">>>>> search term : " + searchTerm);
1039           List<FTSDataColumnI> wantedFields = new ArrayList<>();
1040           FTSRestRequest pdbRequest = new FTSRestRequest();
1041           pdbRequest.setAllowEmptySeq(false);
1042           pdbRequest.setResponseSize(1);
1043           pdbRequest.setFieldToSearchBy("(pdb_id:");
1044           pdbRequest.setWantedFields(wantedFields);
1045           pdbRequest.setSearchTerm(searchTerm + ")");
1046           pdbRequest.setAssociatedSequence(selectedSequence);
1047           pdbRestCleint = PDBFTSRestClient.getInstance();
1048           wantedFields.add(pdbRestCleint.getPrimaryKeyColumn());
1049           FTSRestResponse resultList;
1050           try
1051           {
1052             resultList = pdbRestCleint.executeRequest(pdbRequest);
1053           } catch (Exception e)
1054           {
1055             errorWarning.append(e.getMessage());
1056             return;
1057           } finally
1058           {
1059             validateSelections();
1060           }
1061           if (resultList.getSearchSummary() != null
1062                   && resultList.getSearchSummary().size() > 0)
1063           {
1064             isValidPBDEntry = true;
1065           }
1066         }
1067         validateSelections();
1068       }
1069     }.start();
1070   }
1071
1072   @Override
1073   public void tabRefresh()
1074   {
1075     if (selectedSequences != null)
1076     {
1077       Thread refreshThread = new Thread(new Runnable()
1078       {
1079         @Override
1080         public void run()
1081         {
1082           fetchStructuresMetaData();
1083           filterResultSet(
1084                   ((FilterOption) cmb_filterOption.getSelectedItem())
1085                           .getValue());
1086         }
1087       });
1088       refreshThread.start();
1089     }
1090   }
1091
1092   public class PDBEntryTableModel extends AbstractTableModel
1093   {
1094     String[] columns = { "Ref Sequence", "PDB Id", "Chain", "Type",
1095         "File" };
1096
1097     private List<CachedPDB> pdbEntries;
1098
1099     public PDBEntryTableModel(List<CachedPDB> pdbEntries)
1100     {
1101       this.pdbEntries = new ArrayList<>(pdbEntries);
1102     }
1103
1104     @Override
1105     public String getColumnName(int columnIndex)
1106     {
1107       return columns[columnIndex];
1108     }
1109
1110     @Override
1111     public int getRowCount()
1112     {
1113       return pdbEntries.size();
1114     }
1115
1116     @Override
1117     public int getColumnCount()
1118     {
1119       return columns.length;
1120     }
1121
1122     @Override
1123     public boolean isCellEditable(int row, int column)
1124     {
1125       return false;
1126     }
1127
1128     @Override
1129     public Object getValueAt(int rowIndex, int columnIndex)
1130     {
1131       Object value = "??";
1132       CachedPDB entry = pdbEntries.get(rowIndex);
1133       switch (columnIndex)
1134       {
1135       case 0:
1136         value = entry.getSequence();
1137         break;
1138       case 1:
1139         value = entry.getPdbEntry();
1140         break;
1141       case 2:
1142         value = entry.getPdbEntry().getChainCode() == null ? "_"
1143                 : entry.getPdbEntry().getChainCode();
1144         break;
1145       case 3:
1146         value = entry.getPdbEntry().getType();
1147         break;
1148       case 4:
1149         value = entry.getPdbEntry().getFile();
1150         break;
1151       }
1152       return value;
1153     }
1154
1155     @Override
1156     public Class<?> getColumnClass(int columnIndex)
1157     {
1158       return columnIndex == 0 ? SequenceI.class : PDBEntry.class;
1159     }
1160
1161     public CachedPDB getPDBEntryAt(int row)
1162     {
1163       return pdbEntries.get(row);
1164     }
1165
1166   }
1167
1168   private class CachedPDB
1169   {
1170     private SequenceI sequence;
1171
1172     private PDBEntry pdbEntry;
1173
1174     public CachedPDB(SequenceI sequence, PDBEntry pdbEntry)
1175     {
1176       this.sequence = sequence;
1177       this.pdbEntry = pdbEntry;
1178     }
1179
1180     public SequenceI getSequence()
1181     {
1182       return sequence;
1183     }
1184
1185     public PDBEntry getPdbEntry()
1186     {
1187       return pdbEntry;
1188     }
1189
1190   }
1191
1192   private IProgressIndicator progressBar;
1193
1194   @Override
1195   public void setProgressBar(String message, long id)
1196   {
1197     progressBar.setProgressBar(message, id);
1198   }
1199
1200   @Override
1201   public void registerHandler(long id, IProgressIndicatorHandler handler)
1202   {
1203     progressBar.registerHandler(id, handler);
1204   }
1205
1206   @Override
1207   public boolean operationInProgress()
1208   {
1209     return progressBar.operationInProgress();
1210   }
1211 }