JAL-629 add AF examples for FER1_SPIOL. Link and display PAE matrix annotation
[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 java.awt.event.ActionEvent;
25 import java.awt.event.ActionListener;
26 import java.awt.event.ItemEvent;
27 import java.io.File;
28 import java.io.IOException;
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.HashSet;
32 import java.util.LinkedHashSet;
33 import java.util.List;
34 import java.util.Locale;
35 import java.util.concurrent.Callable;
36 import java.util.concurrent.Executors;
37
38 import javax.swing.JCheckBox;
39 import javax.swing.JComboBox;
40 import javax.swing.JLabel;
41 import javax.swing.JMenuItem;
42 import javax.swing.JPopupMenu;
43 import javax.swing.JTable;
44 import javax.swing.SwingUtilities;
45 import javax.swing.table.AbstractTableModel;
46
47 import org.json.simple.parser.ParseException;
48
49 import jalview.api.structures.JalviewStructureDisplayI;
50 import jalview.bin.Cache;
51 import jalview.bin.Console;
52 import jalview.bin.Jalview;
53 import jalview.datamodel.AlignmentI;
54 import jalview.datamodel.PDBEntry;
55 import jalview.datamodel.SequenceI;
56 import jalview.fts.api.FTSData;
57 import jalview.fts.api.FTSDataColumnI;
58 import jalview.fts.api.FTSRestClientI;
59 import jalview.fts.core.FTSDataColumnPreferences;
60 import jalview.fts.core.FTSRestRequest;
61 import jalview.fts.core.FTSRestResponse;
62 import jalview.fts.service.pdb.PDBFTSRestClient;
63 import jalview.fts.service.threedbeacons.TDB_FTSData;
64 import jalview.gui.structurechooser.PDBStructureChooserQuerySource;
65 import jalview.gui.structurechooser.StructureChooserQuerySource;
66 import jalview.gui.structurechooser.ThreeDBStructureChooserQuerySource;
67 import jalview.io.DataSourceType;
68 import jalview.io.JalviewFileChooser;
69 import jalview.io.JalviewFileView;
70 import jalview.io.StructureFile;
71 import jalview.jbgui.FilterOption;
72 import jalview.jbgui.GStructureChooser;
73 import jalview.structure.StructureImportSettings.TFType;
74 import jalview.structure.StructureMapping;
75 import jalview.structure.StructureSelectionManager;
76 import jalview.util.MessageManager;
77 import jalview.util.Platform;
78 import jalview.util.StringUtils;
79 import jalview.ws.DBRefFetcher;
80 import jalview.ws.DBRefFetcher.FetchFinishedListenerI;
81 import jalview.ws.dbsources.EBIAlfaFold;
82 import jalview.ws.seqfetcher.DbSourceProxy;
83 import jalview.ws.sifts.SiftsSettings;
84
85 /**
86  * Provides the behaviors for the Structure chooser Panel
87  * 
88  * @author tcnofoegbu
89  *
90  */
91 @SuppressWarnings("serial")
92 public class StructureChooser extends GStructureChooser
93         implements IProgressIndicator
94 {
95   private static final String AUTOSUPERIMPOSE = "AUTOSUPERIMPOSE";
96
97   /**
98    * warn user if need to fetch more than this many uniprot records at once
99    */
100   private static final int THRESHOLD_WARN_UNIPROT_FETCH_NEEDED = 20;
101
102   private SequenceI selectedSequence;
103
104   private SequenceI[] selectedSequences;
105
106   private IProgressIndicator progressIndicator;
107
108   private Collection<FTSData> discoveredStructuresSet;
109
110   private StructureChooserQuerySource data;
111
112   @Override
113   protected FTSDataColumnPreferences getFTSDocFieldPrefs()
114   {
115     return data.getDocFieldPrefs();
116   }
117
118   private String selectedPdbFileName;
119
120   private TFType localPdbTempfacType;
121
122   private String localPdbPaeMatrixFileName;
123
124   private boolean isValidPBDEntry;
125
126   private boolean cachedPDBExists;
127
128   private Collection<FTSData> lastDiscoveredStructuresSet;
129
130   private boolean canQueryTDB = false;
131
132   private boolean notQueriedTDBYet = true;
133
134   List<SequenceI> seqsWithoutSourceDBRef = null;
135
136   private boolean showChooserGUI = true;
137
138   private static StructureViewer lastTargetedView = null;
139
140   public StructureChooser(SequenceI[] selectedSeqs, SequenceI selectedSeq,
141           AlignmentPanel ap)
142   {
143     this(selectedSeqs, selectedSeq, ap, true);
144   }
145
146   public StructureChooser(SequenceI[] selectedSeqs, SequenceI selectedSeq,
147           AlignmentPanel ap, boolean showGUI)
148   {
149     // which FTS engine to use
150     data = StructureChooserQuerySource.getQuerySourceFor(selectedSeqs);
151     initDialog();
152
153     this.ap = ap;
154     this.selectedSequence = selectedSeq;
155     this.selectedSequences = selectedSeqs;
156     this.progressIndicator = (ap == null) ? null : ap.alignFrame;
157     this.showChooserGUI = showGUI;
158     init();
159
160   }
161
162   /**
163    * sets canQueryTDB if protein sequences without a canonical uniprot ref or at
164    * least one structure are discovered.
165    */
166   private void populateSeqsWithoutSourceDBRef()
167   {
168     seqsWithoutSourceDBRef = new ArrayList<SequenceI>();
169     boolean needCanonical = false;
170     for (SequenceI seq : selectedSequences)
171     {
172       if (seq.isProtein())
173       {
174         int dbRef = ThreeDBStructureChooserQuerySource
175                 .checkUniprotRefs(seq.getDBRefs());
176         if (dbRef < 0)
177         {
178           if (dbRef == -1)
179           {
180             // need to retrieve canonicals
181             needCanonical = true;
182             seqsWithoutSourceDBRef.add(seq);
183           }
184           else
185           {
186             // could be a sequence with pdb ref
187             if (seq.getAllPDBEntries() == null
188                     || seq.getAllPDBEntries().size() == 0)
189             {
190               seqsWithoutSourceDBRef.add(seq);
191             }
192           }
193         }
194       }
195     }
196     // retrieve database refs for protein sequences
197     if (!seqsWithoutSourceDBRef.isEmpty())
198     {
199       canQueryTDB = true;
200       if (needCanonical)
201       {
202         // triggers display of the 'Query TDB' button
203         notQueriedTDBYet = true;
204       }
205     }
206   };
207
208   /**
209    * Initializes parameters used by the Structure Chooser Panel
210    */
211   protected void init()
212   {
213     if (!Jalview.isHeadlessMode())
214     {
215       progressBar = new ProgressBar(this.statusPanel, this.statusBar);
216     }
217
218     chk_superpose.setSelected(Cache.getDefault(AUTOSUPERIMPOSE, true));
219     btn_queryTDB.addActionListener(new ActionListener()
220     {
221
222       @Override
223       public void actionPerformed(ActionEvent e)
224       {
225         promptForTDBFetch(false);
226       }
227     });
228
229     Executors.defaultThreadFactory().newThread(new Runnable()
230     {
231       @Override
232       public void run()
233       {
234         populateSeqsWithoutSourceDBRef();
235         initialStructureDiscovery();
236       }
237
238     }).start();
239
240   }
241
242   // called by init
243   private void initialStructureDiscovery()
244   {
245     // check which FTS engine to use
246     data = StructureChooserQuerySource.getQuerySourceFor(selectedSequences);
247
248     // ensure a filter option is in force for search
249     populateFilterComboBox(true, cachedPDBExists);
250
251     // looks for any existing structures already loaded
252     // for the sequences (the cached ones)
253     // then queries the StructureChooserQuerySource to
254     // discover more structures.
255     //
256     // Possible optimisation is to only begin querying
257     // the structure chooser if there are no cached structures.
258
259     long startTime = System.currentTimeMillis();
260     updateProgressIndicator(
261             MessageManager.getString("status.loading_cached_pdb_entries"),
262             startTime);
263     loadLocalCachedPDBEntries();
264     updateProgressIndicator(null, startTime);
265     updateProgressIndicator(
266             MessageManager.getString("status.searching_for_pdb_structures"),
267             startTime);
268     fetchStructuresMetaData();
269     // revise filter options if no results were found
270     populateFilterComboBox(isStructuresDiscovered(), cachedPDBExists);
271     discoverStructureViews();
272     updateProgressIndicator(null, startTime);
273     mainFrame.setVisible(showChooserGUI);
274     updateCurrentView();
275   }
276
277   /**
278    * raises dialog for Uniprot fetch followed by 3D beacons search
279    * 
280    * @param ignoreGui
281    *          - when true, don't ask, just fetch
282    */
283   public void promptForTDBFetch(boolean ignoreGui)
284   {
285     final long progressId = System.currentTimeMillis();
286
287     // final action after prompting and discovering db refs
288     final Runnable strucDiscovery = new Runnable()
289     {
290       @Override
291       public void run()
292       {
293         mainFrame.setEnabled(false);
294         cmb_filterOption.setEnabled(false);
295         progressBar.setProgressBar(
296                 MessageManager.getString("status.searching_3d_beacons"),
297                 progressId);
298         btn_queryTDB.setEnabled(false);
299         // TODO: warn if no accessions discovered
300         populateSeqsWithoutSourceDBRef();
301         // redo initial discovery - this time with 3d beacons
302         // Executors.
303         previousWantedFields = null;
304         lastSelected = (FilterOption) cmb_filterOption.getSelectedItem();
305         cmb_filterOption.setSelectedItem(null);
306         cachedPDBExists = false; // reset to initial
307         initialStructureDiscovery();
308         if (!isStructuresDiscovered())
309         {
310           progressBar.setProgressBar(MessageManager.getString(
311                   "status.no_structures_discovered_from_3d_beacons"),
312                   progressId);
313           btn_queryTDB.setToolTipText(MessageManager.getString(
314                   "status.no_structures_discovered_from_3d_beacons"));
315           btn_queryTDB.setEnabled(false);
316           pnl_queryTDB.setVisible(false);
317         }
318         else
319         {
320           cmb_filterOption.setSelectedIndex(0); // select 'best'
321           btn_queryTDB.setVisible(false);
322           pnl_queryTDB.setVisible(false);
323           progressBar.setProgressBar(null, progressId);
324         }
325         mainFrame.setEnabled(true);
326         cmb_filterOption.setEnabled(true);
327       }
328     };
329
330     final FetchFinishedListenerI afterDbRefFetch = new FetchFinishedListenerI()
331     {
332
333       @Override
334       public void finished()
335       {
336         // filter has been selected, so we set flag to remove ourselves
337         notQueriedTDBYet = false;
338         // new thread to discover structures - via 3d beacons
339         Executors.defaultThreadFactory().newThread(strucDiscovery).start();
340
341       }
342     };
343
344     // fetch db refs if OK pressed
345     final Callable discoverCanonicalDBrefs = () -> {
346       btn_queryTDB.setEnabled(false);
347       populateSeqsWithoutSourceDBRef();
348
349       final int y = seqsWithoutSourceDBRef.size();
350       if (y > 0)
351       {
352         final SequenceI[] seqWithoutSrcDBRef = seqsWithoutSourceDBRef
353                 .toArray(new SequenceI[y]);
354         DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef,
355                 progressBar, new DbSourceProxy[]
356                 { new jalview.ws.dbsources.Uniprot() }, null, false);
357         dbRefFetcher.addListener(afterDbRefFetch);
358         // ideally this would also gracefully run with callbacks
359
360         dbRefFetcher.fetchDBRefs(true);
361       }
362       else
363       {
364         // call finished action directly
365         afterDbRefFetch.finished();
366       }
367       return null;
368     };
369     final Callable revertview = () -> {
370       if (lastSelected != null)
371       {
372         cmb_filterOption.setSelectedItem(lastSelected);
373       }
374       return null;
375     };
376     int threshold = Cache.getDefault("UNIPROT_AUTOFETCH_THRESHOLD",
377             THRESHOLD_WARN_UNIPROT_FETCH_NEEDED);
378     Console.debug("Using Uniprot fetch threshold of " + threshold);
379     if (ignoreGui || seqsWithoutSourceDBRef.size() < threshold)
380     {
381       Executors.newSingleThreadExecutor().submit(discoverCanonicalDBrefs);
382       return;
383     }
384     // need cancel and no to result in the discoverPDB action - mocked is
385     // 'cancel' TODO: mock should be OK
386
387     StructureChooser thisSC = this;
388     JvOptionPane.newOptionDialog(thisSC.getFrame())
389             .setResponseHandler(JvOptionPane.OK_OPTION,
390                     discoverCanonicalDBrefs)
391             .setResponseHandler(JvOptionPane.CANCEL_OPTION, revertview)
392             .setResponseHandler(JvOptionPane.NO_OPTION, revertview)
393             .showDialog(
394                     MessageManager.formatMessage(
395                             "label.fetch_references_for_3dbeacons",
396                             seqsWithoutSourceDBRef.size()),
397                     MessageManager.getString("label.3dbeacons"),
398                     JvOptionPane.YES_NO_OPTION, JvOptionPane.PLAIN_MESSAGE,
399                     null, new Object[]
400                     { MessageManager.getString("action.ok"),
401                         MessageManager.getString("action.cancel") },
402                     MessageManager.getString("action.ok"), false);
403   }
404
405   /**
406    * Builds a drop-down choice list of existing structure viewers to which new
407    * structures may be added. If this list is empty then it, and the 'Add'
408    * button, are hidden.
409    */
410   private void discoverStructureViews()
411   {
412     if (Desktop.instance != null)
413     {
414       targetView.removeAllItems();
415       if (lastTargetedView != null && !lastTargetedView.isVisible())
416       {
417         lastTargetedView = null;
418       }
419       int linkedViewsAt = 0;
420       for (StructureViewerBase view : Desktop.instance
421               .getStructureViewers(null, null))
422       {
423         StructureViewer viewHandler = (lastTargetedView != null
424                 && lastTargetedView.sview == view) ? lastTargetedView
425                         : StructureViewer.reconfigure(view);
426
427         if (view.isLinkedWith(ap))
428         {
429           targetView.insertItemAt(viewHandler, linkedViewsAt++);
430         }
431         else
432         {
433           targetView.addItem(viewHandler);
434         }
435       }
436
437       /*
438        * show option to Add to viewer if at least 1 viewer found
439        */
440       targetView.setVisible(false);
441       if (targetView.getItemCount() > 0)
442       {
443         targetView.setVisible(true);
444         if (lastTargetedView != null)
445         {
446           targetView.setSelectedItem(lastTargetedView);
447         }
448         else
449         {
450           targetView.setSelectedIndex(0);
451         }
452       }
453       btn_add.setVisible(targetView.isVisible());
454     }
455   }
456
457   /**
458    * Updates the progress indicator with the specified message
459    * 
460    * @param message
461    *          displayed message for the operation
462    * @param id
463    *          unique handle for this indicator
464    */
465   protected void updateProgressIndicator(String message, long id)
466   {
467     if (progressIndicator != null)
468     {
469       progressIndicator.setProgressBar(message, id);
470     }
471   }
472
473   /**
474    * Retrieve meta-data for all the structure(s) for a given sequence(s) in a
475    * selection group
476    */
477   void fetchStructuresMetaData()
478   {
479     long startTime = System.currentTimeMillis();
480     Collection<FTSDataColumnI> wantedFields = data.getDocFieldPrefs()
481             .getStructureSummaryFields();
482
483     discoveredStructuresSet = new LinkedHashSet<>();
484     HashSet<String> errors = new HashSet<>();
485
486     FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
487             .getSelectedItem());
488
489     for (SequenceI seq : selectedSequences)
490     {
491
492       FTSRestResponse resultList;
493       try
494       {
495         resultList = data.fetchStructuresMetaData(seq, wantedFields,
496                 selectedFilterOpt, !chk_invertFilter.isSelected());
497         // null response means the FTSengine didn't yield a query for this
498         // consider designing a special exception if we really wanted to be
499         // OOCrazy
500         if (resultList == null)
501         {
502           continue;
503         }
504       } catch (Exception e)
505       {
506         e.printStackTrace();
507         errors.add(e.getMessage());
508         continue;
509       }
510       if (resultList.getSearchSummary() != null
511               && !resultList.getSearchSummary().isEmpty())
512       {
513         discoveredStructuresSet.addAll(resultList.getSearchSummary());
514       }
515     }
516
517     int noOfStructuresFound = 0;
518     String totalTime = (System.currentTimeMillis() - startTime)
519             + " milli secs";
520     if (discoveredStructuresSet != null
521             && !discoveredStructuresSet.isEmpty())
522     {
523       getResultTable()
524               .setModel(data.getTableModel(discoveredStructuresSet));
525
526       noOfStructuresFound = discoveredStructuresSet.size();
527       lastDiscoveredStructuresSet = discoveredStructuresSet;
528       mainFrame.setTitle(MessageManager.formatMessage(
529               "label.structure_chooser_no_of_structures",
530               noOfStructuresFound, totalTime));
531     }
532     else
533     {
534       mainFrame.setTitle(MessageManager
535               .getString("label.structure_chooser_manual_association"));
536       if (errors.size() > 0)
537       {
538         StringBuilder errorMsg = new StringBuilder();
539         for (String error : errors)
540         {
541           errorMsg.append(error).append("\n");
542         }
543         JvOptionPane.showMessageDialog(this, errorMsg.toString(),
544                 MessageManager.getString("label.pdb_web-service_error"),
545                 JvOptionPane.ERROR_MESSAGE);
546       }
547     }
548   }
549
550   protected void loadLocalCachedPDBEntries()
551   {
552     ArrayList<CachedPDB> entries = new ArrayList<>();
553     for (SequenceI seq : selectedSequences)
554     {
555       if (seq.getDatasetSequence() != null
556               && seq.getDatasetSequence().getAllPDBEntries() != null)
557       {
558         for (PDBEntry pdbEntry : seq.getDatasetSequence()
559                 .getAllPDBEntries())
560         {
561           if (pdbEntry.getFile() != null)
562           {
563             entries.add(new CachedPDB(seq, pdbEntry));
564           }
565         }
566       }
567     }
568     cachedPDBExists = !entries.isEmpty();
569     PDBEntryTableModel tableModelx = new PDBEntryTableModel(entries);
570     tbl_local_pdb.setModel(tableModelx);
571   }
572
573   /**
574    * Filters a given list of discovered structures based on supplied argument
575    * 
576    * @param fieldToFilterBy
577    *          the field to filter by
578    */
579   void filterResultSet(final String fieldToFilterBy)
580   {
581     Thread filterThread = new Thread(new Runnable()
582     {
583
584       @Override
585       public void run()
586       {
587         long startTime = System.currentTimeMillis();
588         lbl_loading.setVisible(true);
589         Collection<FTSDataColumnI> wantedFields = data.getDocFieldPrefs()
590                 .getStructureSummaryFields();
591         Collection<FTSData> filteredResponse = new HashSet<>();
592         HashSet<String> errors = new HashSet<>();
593
594         for (SequenceI seq : selectedSequences)
595         {
596
597           FTSRestResponse resultList;
598           try
599           {
600             resultList = data.selectFirstRankedQuery(seq,
601                     discoveredStructuresSet, wantedFields, fieldToFilterBy,
602                     !chk_invertFilter.isSelected());
603
604           } catch (Exception e)
605           {
606             e.printStackTrace();
607             errors.add(e.getMessage());
608             continue;
609           }
610           if (resultList.getSearchSummary() != null
611                   && !resultList.getSearchSummary().isEmpty())
612           {
613             filteredResponse.addAll(resultList.getSearchSummary());
614           }
615         }
616
617         String totalTime = (System.currentTimeMillis() - startTime)
618                 + " milli secs";
619         if (!filteredResponse.isEmpty())
620         {
621           final int filterResponseCount = filteredResponse.size();
622           Collection<FTSData> reorderedStructuresSet = new LinkedHashSet<>();
623           reorderedStructuresSet.addAll(filteredResponse);
624           reorderedStructuresSet.addAll(discoveredStructuresSet);
625           getResultTable()
626                   .setModel(data.getTableModel(reorderedStructuresSet));
627
628           FTSRestResponse.configureTableColumn(getResultTable(),
629                   wantedFields, tempUserPrefs);
630           getResultTable().getColumn("Ref Sequence").setPreferredWidth(120);
631           getResultTable().getColumn("Ref Sequence").setMinWidth(100);
632           getResultTable().getColumn("Ref Sequence").setMaxWidth(200);
633           // Update table selection model here
634           getResultTable().addRowSelectionInterval(0,
635                   filterResponseCount - 1);
636           mainFrame.setTitle(MessageManager.formatMessage(
637                   "label.structure_chooser_filter_time", totalTime));
638         }
639         else
640         {
641           mainFrame.setTitle(MessageManager.formatMessage(
642                   "label.structure_chooser_filter_time", totalTime));
643           if (errors.size() > 0)
644           {
645             StringBuilder errorMsg = new StringBuilder();
646             for (String error : errors)
647             {
648               errorMsg.append(error).append("\n");
649             }
650             JvOptionPane.showMessageDialog(null, errorMsg.toString(),
651                     MessageManager.getString("label.pdb_web-service_error"),
652                     JvOptionPane.ERROR_MESSAGE);
653           }
654         }
655
656         lbl_loading.setVisible(false);
657
658         validateSelections();
659       }
660     });
661     filterThread.start();
662   }
663
664   /**
665    * Handles action event for btn_pdbFromFile
666    */
667   @Override
668   protected void pdbFromFile_actionPerformed()
669   {
670     // TODO: JAL-3048 not needed for Jalview-JS until JSmol dep and
671     // StructureChooser
672     // works
673     JalviewFileChooser chooser = new JalviewFileChooser(
674             Cache.getProperty("LAST_DIRECTORY"));
675     chooser.setFileView(new JalviewFileView());
676     chooser.setDialogTitle(
677             MessageManager.formatMessage("label.select_pdb_file_for",
678                     selectedSequence.getDisplayId(false)));
679     chooser.setToolTipText(MessageManager.formatMessage(
680             "label.load_pdb_file_associate_with_sequence",
681             selectedSequence.getDisplayId(false)));
682
683     int value = chooser.showOpenDialog(null);
684     if (value == JalviewFileChooser.APPROVE_OPTION)
685     {
686       selectedPdbFileName = chooser.getSelectedFile().getPath();
687       Cache.setProperty("LAST_DIRECTORY", selectedPdbFileName);
688       boolean guessTFType = localPdbPaeMatrixFileName == null;
689       localPdbPaeMatrixFileName = guessPAEFilename();
690       guessTFType |= localPdbPaeMatrixFileName != null;
691       if (guessTFType
692               && new File(selectedPdbFileName).getName().startsWith("AF-")
693               && !tempFacAsChanged)
694       {
695         // localPdbPaeMatrixFileName was null and now isn't and filename could
696         // well be AlphaFold and user hasn't adjusted the tempFacType
697         combo_tempFacAs.setSelectedItem(TFType.PLDDT);
698       }
699       validateSelections();
700     }
701   }
702
703   /**
704    * Handles action event for btn_pdbFromFile
705    */
706   @Override
707   protected void paeMatrixFile_actionPerformed()
708   {
709     File pdbFile = new File(selectedPdbFileName);
710     String setFile = Cache.getProperty("LAST_DIRECTORY");
711     if (localPdbPaeMatrixFileName != null)
712     {
713       File paeFile = new File(localPdbPaeMatrixFileName);
714       if (paeFile.exists())
715         setFile = paeFile.getAbsolutePath();
716       else if (paeFile.getParentFile().exists())
717         setFile = paeFile.getParentFile().getAbsolutePath();
718     }
719     else
720     {
721       String guess = guessPAEFilename();
722       if (guess != null)
723         setFile = guess;
724     }
725     JalviewFileChooser chooser = new JalviewFileChooser(setFile);
726     chooser.setFileView(new JalviewFileView());
727     chooser.setDialogTitle(MessageManager.formatMessage(
728             "label.select_pae_matrix_file_for", pdbFile.getName()));
729     chooser.setToolTipText(MessageManager.formatMessage(
730             "label.load_pae_matrix_file_associate_with_structure",
731             pdbFile.getName()));
732
733     int value = chooser.showOpenDialog(null);
734     if (value == JalviewFileChooser.APPROVE_OPTION)
735     {
736       localPdbPaeMatrixFileName = chooser.getSelectedFile().getPath();
737       Cache.setProperty("LAST_DIRECTORY", localPdbPaeMatrixFileName);
738     }
739     validateAssociationFromFile();
740   }
741
742   private String guessPAEFilename()
743   {
744     if (selectedPdbFileName.toLowerCase(Locale.ROOT).endsWith(".pdb")
745             || selectedPdbFileName.toLowerCase(Locale.ROOT)
746                     .endsWith(".cif"))
747     {
748       String jsonExt = selectedPdbFileName.substring(0,
749               selectedPdbFileName.length() - 4) + ".json";
750       // AlphaFold naming scheme
751       String guessFile1 = StringUtils.replaceLast(jsonExt, "model",
752               "predicted_aligned_error");
753       // nf-core mode naming scheme
754       String guessFile2 = StringUtils.replaceLast(jsonExt, ".json",
755               "_scores.json");
756       if (new File(guessFile1).exists())
757       {
758         return guessFile1;
759       }
760       else if (new File(jsonExt).exists())
761       {
762         return jsonExt;
763       }
764       else if (new File(guessFile2).exists())
765       {
766         return guessFile2;
767       }
768     }
769     return null;
770   }
771
772   /**
773    * Populates the filter combo-box options dynamically depending on discovered
774    * structures
775    */
776   protected void populateFilterComboBox(boolean haveData,
777           boolean cachedPDBExist)
778   {
779     populateFilterComboBox(haveData, cachedPDBExist, null);
780   }
781
782   /**
783    * Populates the filter combo-box options dynamically depending on discovered
784    * structures
785    */
786   protected void populateFilterComboBox(boolean haveData,
787           boolean cachedPDBExist, FilterOption lastSel)
788   {
789
790     /*
791      * temporarily suspend the change listener behaviour
792      */
793     cmb_filterOption.removeItemListener(this);
794     int selSet = -1;
795     cmb_filterOption.removeAllItems();
796     if (haveData)
797     {
798       List<FilterOption> filters = data
799               .getAvailableFilterOptions(VIEWS_FILTER);
800       data.updateAvailableFilterOptions(VIEWS_FILTER, filters,
801               lastDiscoveredStructuresSet);
802       int p = 0;
803       for (FilterOption filter : filters)
804       {
805         if (lastSel != null && filter.equals(lastSel))
806         {
807           selSet = p;
808         }
809         p++;
810         cmb_filterOption.addItem(filter);
811       }
812     }
813
814     cmb_filterOption.addItem(
815             new FilterOption(MessageManager.getString("label.enter_pdb_id"),
816                     "-", VIEWS_ENTER_ID, false, null));
817     cmb_filterOption.addItem(
818             new FilterOption(MessageManager.getString("label.from_file"),
819                     "-", VIEWS_FROM_FILE, false, null));
820     if (canQueryTDB && notQueriedTDBYet)
821     {
822       btn_queryTDB.setVisible(true);
823       pnl_queryTDB.setVisible(true);
824     }
825
826     if (cachedPDBExist)
827     {
828       FilterOption cachedOption = new FilterOption(
829               MessageManager.getString("label.cached_structures"), "-",
830               VIEWS_LOCAL_PDB, false, null);
831       cmb_filterOption.addItem(cachedOption);
832       if (selSet == -1)
833       {
834         cmb_filterOption.setSelectedItem(cachedOption);
835       }
836     }
837     if (selSet > -1)
838     {
839       cmb_filterOption.setSelectedIndex(selSet);
840     }
841     cmb_filterOption.addItemListener(this);
842   }
843
844   /**
845    * Updates the displayed view based on the selected filter option
846    */
847   protected void updateCurrentView()
848   {
849     FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
850             .getSelectedItem());
851
852     if (lastSelected == selectedFilterOpt)
853     {
854       // don't need to do anything, probably
855       return;
856     }
857     // otherwise, record selection
858     // and update the layout and dialog accordingly
859     lastSelected = selectedFilterOpt;
860
861     layout_switchableViews.show(pnl_switchableViews,
862             selectedFilterOpt.getView());
863     String filterTitle = mainFrame.getTitle();
864     mainFrame.setTitle(frameTitle);
865     chk_invertFilter.setVisible(false);
866
867     if (selectedFilterOpt.getView() == VIEWS_FILTER)
868     {
869       mainFrame.setTitle(filterTitle);
870       // TDB Query has no invert as yet
871       chk_invertFilter.setVisible(selectedFilterOpt
872               .getQuerySource() instanceof PDBStructureChooserQuerySource);
873
874       if (data != selectedFilterOpt.getQuerySource()
875               || data.needsRefetch(selectedFilterOpt))
876       {
877         data = selectedFilterOpt.getQuerySource();
878         // rebuild the views completely, since prefs will also change
879         tabRefresh();
880         return;
881       }
882       else
883       {
884         filterResultSet(selectedFilterOpt.getValue());
885       }
886     }
887     else if (selectedFilterOpt.getView() == VIEWS_ENTER_ID
888             || selectedFilterOpt.getView() == VIEWS_FROM_FILE)
889     {
890       mainFrame.setTitle(MessageManager
891               .getString("label.structure_chooser_manual_association"));
892       idInputAssSeqPanel.loadCmbAssSeq();
893       fileChooserAssSeqPanel.loadCmbAssSeq();
894     }
895     validateSelections();
896   }
897
898   /**
899    * Validates user selection and enables the 'Add' and 'New View' buttons if
900    * all parameters are correct (the Add button will only be visible if there is
901    * at least one existing structure viewer open). This basically means at least
902    * one structure selected and no error messages.
903    * <p>
904    * The 'Superpose Structures' option is enabled if either more than one
905    * structure is selected, or the 'Add' to existing view option is enabled, and
906    * disabled if the only option is to open a new view of a single structure.
907    */
908   @Override
909   protected void validateSelections()
910   {
911     FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
912             .getSelectedItem());
913     btn_add.setEnabled(false);
914     String currentView = selectedFilterOpt.getView();
915     int selectedCount = 0;
916     if (currentView == VIEWS_FILTER)
917     {
918       selectedCount = getResultTable().getSelectedRows().length;
919       if (selectedCount > 0)
920       {
921         btn_add.setEnabled(true);
922       }
923     }
924     else if (currentView == VIEWS_LOCAL_PDB)
925     {
926       selectedCount = tbl_local_pdb.getSelectedRows().length;
927       if (selectedCount > 0)
928       {
929         btn_add.setEnabled(true);
930       }
931     }
932     else if (currentView == VIEWS_ENTER_ID)
933     {
934       validateAssociationEnterPdb();
935     }
936     else if (currentView == VIEWS_FROM_FILE)
937     {
938       validateAssociationFromFile();
939     }
940
941     btn_newView.setEnabled(btn_add.isEnabled());
942
943     /*
944      * enable 'Superpose' option if more than one structure is selected,
945      * or there are view(s) available to add structure(s) to
946      */
947     chk_superpose
948             .setEnabled(selectedCount > 1 || targetView.getItemCount() > 0);
949   }
950
951   @Override
952   protected boolean showPopupFor(int selectedRow, int x, int y)
953   {
954     FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
955             .getSelectedItem());
956     String currentView = selectedFilterOpt.getView();
957
958     if (currentView == VIEWS_FILTER
959             && data instanceof ThreeDBStructureChooserQuerySource)
960     {
961
962       TDB_FTSData row = ((ThreeDBStructureChooserQuerySource) data)
963               .getFTSDataFor(getResultTable(), selectedRow,
964                       discoveredStructuresSet);
965       String pageUrl = row.getModelViewUrl();
966       JPopupMenu popup = new JPopupMenu("3D Beacons");
967       JMenuItem viewUrl = new JMenuItem("View model web page");
968       viewUrl.addActionListener(new ActionListener()
969       {
970         @Override
971         public void actionPerformed(ActionEvent e)
972         {
973           Desktop.showUrl(pageUrl);
974         }
975       });
976       popup.add(viewUrl);
977       SwingUtilities.invokeLater(new Runnable()
978       {
979         @Override
980         public void run()
981         {
982           popup.show(getResultTable(), x, y);
983         }
984       });
985       return true;
986     }
987     // event not handled by us
988     return false;
989   }
990
991   /**
992    * Validates inputs from the Manual PDB entry panel
993    */
994   protected void validateAssociationEnterPdb()
995   {
996     AssociateSeqOptions assSeqOpt = (AssociateSeqOptions) idInputAssSeqPanel
997             .getCmb_assSeq().getSelectedItem();
998     lbl_pdbManualFetchStatus.setIcon(errorImage);
999     lbl_pdbManualFetchStatus.setToolTipText("");
1000     if (txt_search.getText().length() > 0)
1001     {
1002       lbl_pdbManualFetchStatus.setToolTipText(JvSwingUtils.wrapTooltip(true,
1003               MessageManager.formatMessage("info.no_pdb_entry_found_for",
1004                       txt_search.getText())));
1005     }
1006
1007     if (errorWarning.length() > 0)
1008     {
1009       lbl_pdbManualFetchStatus.setIcon(warningImage);
1010       lbl_pdbManualFetchStatus.setToolTipText(
1011               JvSwingUtils.wrapTooltip(true, errorWarning.toString()));
1012     }
1013
1014     if (selectedSequences.length == 1 || !assSeqOpt.getName()
1015             .equalsIgnoreCase("-Select Associated Seq-"))
1016     {
1017       txt_search.setEnabled(true);
1018       if (isValidPBDEntry)
1019       {
1020         btn_add.setEnabled(true);
1021         lbl_pdbManualFetchStatus.setToolTipText("");
1022         lbl_pdbManualFetchStatus.setIcon(goodImage);
1023       }
1024     }
1025     else
1026     {
1027       txt_search.setEnabled(false);
1028       lbl_pdbManualFetchStatus.setIcon(errorImage);
1029     }
1030   }
1031
1032   /**
1033    * Validates inputs for the manual PDB file selection options
1034    */
1035   protected void validateAssociationFromFile()
1036   {
1037     AssociateSeqOptions assSeqOpt = (AssociateSeqOptions) fileChooserAssSeqPanel
1038             .getCmb_assSeq().getSelectedItem();
1039     // lbl_fromFileStatus.setIcon(errorImage);
1040     String pdbFileString = "";
1041     String pdbFileTooltip = "";
1042     if (selectedSequences.length == 1 || (assSeqOpt != null && !assSeqOpt
1043             .getName().equalsIgnoreCase("-Select Associated Seq-")))
1044     {
1045       btn_pdbFromFile.setEnabled(true);
1046       if (selectedPdbFileName != null && selectedPdbFileName.length() > 0)
1047       {
1048         btn_add.setEnabled(true);
1049         // lbl_fromFileStatus.setIcon(goodImage);
1050         pdbFileString = new File(selectedPdbFileName).getName();
1051         pdbFileTooltip = new File(selectedPdbFileName).getAbsolutePath();
1052         setPdbOptionsEnabled(true);
1053       }
1054       else
1055       {
1056         pdbFileString = MessageManager.getString("label.none");
1057         pdbFileTooltip = MessageManager.getString("label.nothing_selected");
1058       }
1059     }
1060     else
1061     {
1062       btn_pdbFromFile.setEnabled(false);
1063       // lbl_fromFileStatus.setIcon(errorImage);
1064       pdbFileString = MessageManager.getString("label.none");
1065       pdbFileTooltip = MessageManager.getString("label.nothing_selected");
1066     }
1067     lbl_pdbFile.setText(pdbFileString);
1068     lbl_pdbFile.setToolTipText(pdbFileTooltip);
1069
1070     // PAE file choice
1071     String paeFileString = "";
1072     String paeFileTooltip = "";
1073     if (localPdbPaeMatrixFileName != null
1074             && localPdbPaeMatrixFileName.length() > 0)
1075     {
1076       paeFileString = new File(localPdbPaeMatrixFileName).getName();
1077       paeFileTooltip = new File(localPdbPaeMatrixFileName)
1078               .getAbsolutePath();
1079     }
1080     else
1081     {
1082       paeFileString = MessageManager.getString("label.none");
1083       paeFileTooltip = MessageManager.getString("label.nothing_selected");
1084     }
1085     lbl_paeFile.setText(paeFileString);
1086     lbl_paeFile.setToolTipText(paeFileTooltip);
1087   }
1088
1089   @Override
1090   protected void cmbAssSeqStateChanged()
1091   {
1092     validateSelections();
1093   }
1094
1095   private FilterOption lastSelected = null;
1096
1097   /**
1098    * Handles the state change event for the 'filter' combo-box and 'invert'
1099    * check-box
1100    */
1101   @Override
1102   protected void stateChanged(ItemEvent e)
1103   {
1104     if (e.getSource() instanceof JCheckBox)
1105     {
1106       updateCurrentView();
1107     }
1108     else
1109     {
1110       if (e.getStateChange() == ItemEvent.SELECTED)
1111       {
1112         updateCurrentView();
1113       }
1114     }
1115
1116   }
1117
1118   /**
1119    * select structures for viewing by their PDB IDs
1120    * 
1121    * @param pdbids
1122    * @return true if structures were found and marked as selected
1123    */
1124   public boolean selectStructure(String... pdbids)
1125   {
1126     boolean found = false;
1127
1128     FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
1129             .getSelectedItem());
1130     String currentView = selectedFilterOpt.getView();
1131     JTable restable = (currentView == VIEWS_FILTER) ? getResultTable()
1132             : (currentView == VIEWS_LOCAL_PDB) ? tbl_local_pdb : null;
1133
1134     if (restable == null)
1135     {
1136       // can't select (enter PDB ID, or load file - need to also select which
1137       // sequence to associate with)
1138       return false;
1139     }
1140
1141     int pdbIdColIndex = restable.getColumn("PDB Id").getModelIndex();
1142     for (int r = 0; r < restable.getRowCount(); r++)
1143     {
1144       for (int p = 0; p < pdbids.length; p++)
1145       {
1146         if (String.valueOf(restable.getValueAt(r, pdbIdColIndex))
1147                 .equalsIgnoreCase(pdbids[p]))
1148         {
1149           restable.setRowSelectionInterval(r, r);
1150           found = true;
1151         }
1152       }
1153     }
1154     return found;
1155   }
1156
1157   /**
1158    * Handles the 'New View' action
1159    */
1160   @Override
1161   protected void newView_ActionPerformed()
1162   {
1163     targetView.setSelectedItem(null);
1164     showStructures(false);
1165   }
1166
1167   /**
1168    * Handles the 'Add to existing viewer' action
1169    */
1170   @Override
1171   protected void add_ActionPerformed()
1172   {
1173     showStructures(false);
1174   }
1175
1176   /**
1177    * structure viewer opened by this dialog, or null
1178    */
1179   private StructureViewer sViewer = null;
1180
1181   public void showStructures(boolean waitUntilFinished)
1182   {
1183
1184     final StructureSelectionManager ssm = ap.getStructureSelectionManager();
1185
1186     final int preferredHeight = pnl_filter.getHeight();
1187
1188     Runnable viewStruc = new Runnable()
1189     {
1190       @Override
1191       public void run()
1192       {
1193         FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
1194                 .getSelectedItem());
1195         String currentView = selectedFilterOpt.getView();
1196         JTable restable = (currentView == VIEWS_FILTER) ? getResultTable()
1197                 : tbl_local_pdb;
1198
1199         if (currentView == VIEWS_FILTER)
1200         {
1201           int[] selectedRows = restable.getSelectedRows();
1202           PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
1203           List<SequenceI> selectedSeqsToView = new ArrayList<>();
1204           pdbEntriesToView = data.collectSelectedRows(restable,
1205                   selectedRows, selectedSeqsToView);
1206
1207           SequenceI[] selectedSeqs = selectedSeqsToView
1208                   .toArray(new SequenceI[selectedSeqsToView.size()]);
1209           sViewer = launchStructureViewer(ssm, pdbEntriesToView, ap,
1210                   selectedSeqs);
1211         }
1212         else if (currentView == VIEWS_LOCAL_PDB)
1213         {
1214           int[] selectedRows = tbl_local_pdb.getSelectedRows();
1215           PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
1216           int count = 0;
1217           int pdbIdColIndex = tbl_local_pdb.getColumn("PDB Id")
1218                   .getModelIndex();
1219           int refSeqColIndex = tbl_local_pdb.getColumn("Ref Sequence")
1220                   .getModelIndex();
1221           List<SequenceI> selectedSeqsToView = new ArrayList<>();
1222           for (int row : selectedRows)
1223           {
1224             PDBEntry pdbEntry = ((PDBEntryTableModel) tbl_local_pdb
1225                     .getModel()).getPDBEntryAt(row).getPdbEntry();
1226
1227             pdbEntriesToView[count++] = pdbEntry;
1228             SequenceI selectedSeq = (SequenceI) tbl_local_pdb
1229                     .getValueAt(row, refSeqColIndex);
1230             selectedSeqsToView.add(selectedSeq);
1231           }
1232           SequenceI[] selectedSeqs = selectedSeqsToView
1233                   .toArray(new SequenceI[selectedSeqsToView.size()]);
1234           sViewer = launchStructureViewer(ssm, pdbEntriesToView, ap,
1235                   selectedSeqs);
1236         }
1237         else if (currentView == VIEWS_ENTER_ID)
1238         {
1239           SequenceI userSelectedSeq = ((AssociateSeqOptions) idInputAssSeqPanel
1240                   .getCmb_assSeq().getSelectedItem()).getSequence();
1241           if (userSelectedSeq != null)
1242           {
1243             selectedSequence = userSelectedSeq;
1244           }
1245           String pdbIdStr = txt_search.getText();
1246           PDBEntry pdbEntry = selectedSequence.getPDBEntry(pdbIdStr);
1247           if (pdbEntry == null)
1248           {
1249             pdbEntry = new PDBEntry();
1250             if (pdbIdStr.split(":").length > 1)
1251             {
1252               pdbEntry.setId(pdbIdStr.split(":")[0]);
1253               pdbEntry.setChainCode(
1254                       pdbIdStr.split(":")[1].toUpperCase(Locale.ROOT));
1255             }
1256             else
1257             {
1258               pdbEntry.setId(pdbIdStr);
1259             }
1260             pdbEntry.setType(PDBEntry.Type.PDB);
1261             selectedSequence.getDatasetSequence().addPDBId(pdbEntry);
1262           }
1263
1264           PDBEntry[] pdbEntriesToView = new PDBEntry[] { pdbEntry };
1265           sViewer = launchStructureViewer(ssm, pdbEntriesToView, ap,
1266                   new SequenceI[]
1267                   { selectedSequence });
1268         }
1269         else if (currentView == VIEWS_FROM_FILE)
1270         {
1271
1272           // TEMPFAC NOT WORKING
1273           TFType tft = (TFType) StructureChooser.this.combo_tempFacAs
1274                   .getSelectedItem();
1275           if (tft != null && tft != TFType.DEFAULT)
1276           {
1277             ssm.setAddTempFacAnnot(true);
1278           }
1279
1280           AssociateSeqOptions assSeqOpt = (AssociateSeqOptions) fileChooserAssSeqPanel
1281                   .getCmb_assSeq().getSelectedItem();
1282
1283           SequenceI userSelectedSeq = assSeqOpt.getSequence();
1284           if (userSelectedSeq != null)
1285           {
1286             selectedSequence = userSelectedSeq;
1287           }
1288           PDBEntry fileEntry = new AssociatePdbFileWithSeq()
1289                   .associatePdbWithSeq(selectedPdbFileName,
1290                           DataSourceType.FILE, selectedSequence, true,
1291                           Desktop.instance);
1292
1293           List<SequenceI> seqList = new ArrayList<>();
1294           seqList.add(selectedSequence);
1295           SequenceI[] seqArray = new SequenceI[] { selectedSequence };
1296           StructureFile sf = ssm.computeMapping(true, seqArray, null,
1297                   selectedPdbFileName, DataSourceType.FILE, null);
1298           // EBIAlfaFold.addAlphaFoldPAEToStructure(pdbAlignment, pae, index,
1299           // structIdOrFile, isStructId);
1300           StructureMapping[] sm = ssm.getMapping(fileEntry.getFile());
1301           // DO SOMETHING WITH
1302           String paeFilename = StructureChooser.this.localPdbPaeMatrixFileName;
1303           File paeFile = paeFilename == null ? null : new File(paeFilename);
1304           if (paeFilename != null && paeFile.exists())
1305           {
1306             AlignmentI al = StructureChooser.this.ap.getAlignment();
1307             try
1308             {
1309               EBIAlfaFold.importPaeJSONAsContactMatrixToSequence(al,
1310                       paeFile, -1, selectedSequence.getName());
1311             } catch (IOException | ParseException e)
1312             {
1313               // TODO Auto-generated catch block
1314               e.printStackTrace();
1315             }
1316           }
1317
1318           sViewer = launchStructureViewer(ssm, new PDBEntry[] { fileEntry },
1319                   ap, new SequenceI[]
1320                   { selectedSequence });
1321         }
1322         SwingUtilities.invokeLater(new Runnable()
1323         {
1324           @Override
1325           public void run()
1326           {
1327             closeAction(preferredHeight);
1328             mainFrame.dispose();
1329           }
1330         });
1331       }
1332     };
1333     Thread runner = new Thread(viewStruc);
1334     runner.start();
1335     if (waitUntilFinished)
1336     {
1337       while (sViewer == null ? runner.isAlive()
1338               : (sViewer.sview == null ? true
1339                       : !sViewer.sview.hasMapping()))
1340       {
1341         try
1342         {
1343           Thread.sleep(300);
1344         } catch (InterruptedException ie)
1345         {
1346
1347         }
1348       }
1349     }
1350   }
1351
1352   /**
1353    * Answers a structure viewer (new or existing) configured to superimpose
1354    * added structures or not according to the user's choice
1355    * 
1356    * @param ssm
1357    * @return
1358    */
1359   StructureViewer getTargetedStructureViewer(StructureSelectionManager ssm)
1360   {
1361     Object sv = targetView.getSelectedItem();
1362
1363     return sv == null ? new StructureViewer(ssm) : (StructureViewer) sv;
1364   }
1365
1366   /**
1367    * Adds PDB structures to a new or existing structure viewer
1368    * 
1369    * @param ssm
1370    * @param pdbEntriesToView
1371    * @param alignPanel
1372    * @param sequences
1373    * @return
1374    */
1375   private StructureViewer launchStructureViewer(
1376           StructureSelectionManager ssm, final PDBEntry[] pdbEntriesToView,
1377           final AlignmentPanel alignPanel, SequenceI[] sequences)
1378   {
1379     long progressId = sequences.hashCode();
1380     setProgressBar(MessageManager
1381             .getString("status.launching_3d_structure_viewer"), progressId);
1382     final StructureViewer theViewer = getTargetedStructureViewer(ssm);
1383     boolean superimpose = chk_superpose.isSelected();
1384     theViewer.setSuperpose(superimpose);
1385
1386     /*
1387      * remember user's choice of superimpose or not
1388      */
1389     Cache.setProperty(AUTOSUPERIMPOSE,
1390             Boolean.valueOf(superimpose).toString());
1391
1392     setProgressBar(null, progressId);
1393     if (SiftsSettings.isMapWithSifts())
1394     {
1395       List<SequenceI> seqsWithoutSourceDBRef = new ArrayList<>();
1396       int p = 0;
1397       // TODO: skip PDBEntry:Sequence pairs where PDBEntry doesn't look like a
1398       // real PDB ID. For moment, we can also safely do this if there is already
1399       // a known mapping between the PDBEntry and the sequence.
1400       for (SequenceI seq : sequences)
1401       {
1402         PDBEntry pdbe = pdbEntriesToView[p++];
1403         if (pdbe != null && pdbe.getFile() != null)
1404         {
1405           StructureMapping[] smm = ssm.getMapping(pdbe.getFile());
1406           if (smm != null && smm.length > 0)
1407           {
1408             for (StructureMapping sm : smm)
1409             {
1410               if (sm.getSequence() == seq)
1411               {
1412                 continue;
1413               }
1414             }
1415           }
1416         }
1417         if (seq.getPrimaryDBRefs().isEmpty())
1418         {
1419           seqsWithoutSourceDBRef.add(seq);
1420           continue;
1421         }
1422       }
1423       if (!seqsWithoutSourceDBRef.isEmpty())
1424       {
1425         int y = seqsWithoutSourceDBRef.size();
1426         setProgressBar(MessageManager.formatMessage(
1427                 "status.fetching_dbrefs_for_sequences_without_valid_refs",
1428                 y), progressId);
1429         SequenceI[] seqWithoutSrcDBRef = seqsWithoutSourceDBRef
1430                 .toArray(new SequenceI[y]);
1431         DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef);
1432         dbRefFetcher.fetchDBRefs(true);
1433
1434         setProgressBar("Fetch complete.", progressId); // todo i18n
1435       }
1436     }
1437     if (pdbEntriesToView.length > 1)
1438     {
1439       setProgressBar(
1440               MessageManager.getString(
1441                       "status.fetching_3d_structures_for_selected_entries"),
1442               progressId);
1443       theViewer.viewStructures(pdbEntriesToView, sequences, alignPanel);
1444     }
1445     else
1446     {
1447       setProgressBar(MessageManager.formatMessage(
1448               "status.fetching_3d_structures_for",
1449               pdbEntriesToView[0].getId()), progressId);
1450       theViewer.viewStructures(pdbEntriesToView[0], sequences, alignPanel);
1451     }
1452     setProgressBar(null, progressId);
1453     // remember the last viewer we used...
1454     lastTargetedView = theViewer;
1455     return theViewer;
1456   }
1457
1458   /**
1459    * Populates the combo-box used in associating manually fetched structures to
1460    * a unique sequence when more than one sequence selection is made.
1461    */
1462   @Override
1463   protected void populateCmbAssociateSeqOptions(
1464           JComboBox<AssociateSeqOptions> cmb_assSeq,
1465           JLabel lbl_associateSeq)
1466   {
1467     cmb_assSeq.removeAllItems();
1468     cmb_assSeq.addItem(
1469             new AssociateSeqOptions("-Select Associated Seq-", null));
1470     lbl_associateSeq.setVisible(false);
1471     if (selectedSequences.length > 1)
1472     {
1473       for (SequenceI seq : selectedSequences)
1474       {
1475         cmb_assSeq.addItem(new AssociateSeqOptions(seq));
1476       }
1477     }
1478     else
1479     {
1480       String seqName = selectedSequence.getDisplayId(false);
1481       seqName = seqName.length() <= 40 ? seqName : seqName.substring(0, 39);
1482       lbl_associateSeq.setText(seqName);
1483       lbl_associateSeq.setVisible(true);
1484       cmb_assSeq.setVisible(false);
1485     }
1486   }
1487
1488   protected boolean isStructuresDiscovered()
1489   {
1490     return discoveredStructuresSet != null
1491             && !discoveredStructuresSet.isEmpty();
1492   }
1493
1494   protected int PDB_ID_MIN = 3;// or: (Jalview.isJS() ? 3 : 1); // Bob proposes
1495                                // this.
1496   // Doing a search for "1" or "1c" is valuable?
1497   // Those work but are enormously slow.
1498
1499   @Override
1500   protected void txt_search_ActionPerformed()
1501   {
1502     String text = txt_search.getText().trim();
1503     if (text.length() >= PDB_ID_MIN)
1504       new Thread()
1505       {
1506
1507         @Override
1508         public void run()
1509         {
1510           errorWarning.setLength(0);
1511           isValidPBDEntry = false;
1512           if (text.length() > 0)
1513           {
1514             // TODO move this pdb id search into the PDB specific
1515             // FTSSearchEngine
1516             // for moment, it will work fine as is because it is self-contained
1517             String searchTerm = text.toLowerCase(Locale.ROOT);
1518             searchTerm = searchTerm.split(":")[0];
1519             // System.out.println(">>>>> search term : " + searchTerm);
1520             List<FTSDataColumnI> wantedFields = new ArrayList<>();
1521             FTSRestRequest pdbRequest = new FTSRestRequest();
1522             pdbRequest.setAllowEmptySeq(false);
1523             pdbRequest.setResponseSize(1);
1524             pdbRequest.setFieldToSearchBy("(pdb_id:");
1525             pdbRequest.setWantedFields(wantedFields);
1526             pdbRequest.setSearchTerm(searchTerm + ")");
1527             pdbRequest.setAssociatedSequence(selectedSequence);
1528             FTSRestClientI pdbRestClient = PDBFTSRestClient.getInstance();
1529             wantedFields.add(pdbRestClient.getPrimaryKeyColumn());
1530             FTSRestResponse resultList;
1531             try
1532             {
1533               resultList = pdbRestClient.executeRequest(pdbRequest);
1534             } catch (Exception e)
1535             {
1536               errorWarning.append(e.getMessage());
1537               return;
1538             } finally
1539             {
1540               validateSelections();
1541             }
1542             if (resultList.getSearchSummary() != null
1543                     && resultList.getSearchSummary().size() > 0)
1544             {
1545               isValidPBDEntry = true;
1546             }
1547           }
1548           validateSelections();
1549         }
1550       }.start();
1551   }
1552
1553   @Override
1554   protected void tabRefresh()
1555   {
1556     if (selectedSequences != null)
1557     {
1558       lbl_loading.setVisible(true);
1559       Thread refreshThread = new Thread(new Runnable()
1560       {
1561         @Override
1562         public void run()
1563         {
1564           fetchStructuresMetaData();
1565           // populateFilterComboBox(true, cachedPDBExists);
1566
1567           filterResultSet(
1568                   ((FilterOption) cmb_filterOption.getSelectedItem())
1569                           .getValue());
1570           lbl_loading.setVisible(false);
1571         }
1572       });
1573       refreshThread.start();
1574     }
1575   }
1576
1577   public class PDBEntryTableModel extends AbstractTableModel
1578   {
1579     String[] columns = { "Ref Sequence", "PDB Id", "Chain", "Type",
1580         "File" };
1581
1582     private List<CachedPDB> pdbEntries;
1583
1584     public PDBEntryTableModel(List<CachedPDB> pdbEntries)
1585     {
1586       this.pdbEntries = new ArrayList<>(pdbEntries);
1587     }
1588
1589     @Override
1590     public String getColumnName(int columnIndex)
1591     {
1592       return columns[columnIndex];
1593     }
1594
1595     @Override
1596     public int getRowCount()
1597     {
1598       return pdbEntries.size();
1599     }
1600
1601     @Override
1602     public int getColumnCount()
1603     {
1604       return columns.length;
1605     }
1606
1607     @Override
1608     public boolean isCellEditable(int row, int column)
1609     {
1610       return false;
1611     }
1612
1613     @Override
1614     public Object getValueAt(int rowIndex, int columnIndex)
1615     {
1616       Object value = "??";
1617       CachedPDB entry = pdbEntries.get(rowIndex);
1618       switch (columnIndex)
1619       {
1620       case 0:
1621         value = entry.getSequence();
1622         break;
1623       case 1:
1624         value = entry.getQualifiedId();
1625         break;
1626       case 2:
1627         value = entry.getPdbEntry().getChainCode() == null ? "_"
1628                 : entry.getPdbEntry().getChainCode();
1629         break;
1630       case 3:
1631         value = entry.getPdbEntry().getType();
1632         break;
1633       case 4:
1634         value = entry.getPdbEntry().getFile();
1635         break;
1636       }
1637       return value;
1638     }
1639
1640     @Override
1641     public Class<?> getColumnClass(int columnIndex)
1642     {
1643       return columnIndex == 0 ? SequenceI.class : PDBEntry.class;
1644     }
1645
1646     public CachedPDB getPDBEntryAt(int row)
1647     {
1648       return pdbEntries.get(row);
1649     }
1650
1651   }
1652
1653   private class CachedPDB
1654   {
1655     private SequenceI sequence;
1656
1657     private PDBEntry pdbEntry;
1658
1659     public CachedPDB(SequenceI sequence, PDBEntry pdbEntry)
1660     {
1661       this.sequence = sequence;
1662       this.pdbEntry = pdbEntry;
1663     }
1664
1665     public String getQualifiedId()
1666     {
1667       if (pdbEntry.hasProvider())
1668       {
1669         return pdbEntry.getProvider() + ":" + pdbEntry.getId();
1670       }
1671       return pdbEntry.toString();
1672     }
1673
1674     public SequenceI getSequence()
1675     {
1676       return sequence;
1677     }
1678
1679     public PDBEntry getPdbEntry()
1680     {
1681       return pdbEntry;
1682     }
1683
1684   }
1685
1686   private IProgressIndicator progressBar;
1687
1688   @Override
1689   public void setProgressBar(String message, long id)
1690   {
1691     if (!Platform.isHeadless())
1692       progressBar.setProgressBar(message, id);
1693   }
1694
1695   @Override
1696   public void registerHandler(long id, IProgressIndicatorHandler handler)
1697   {
1698     progressBar.registerHandler(id, handler);
1699   }
1700
1701   @Override
1702   public boolean operationInProgress()
1703   {
1704     return progressBar.operationInProgress();
1705   }
1706
1707   public JalviewStructureDisplayI getOpenedStructureViewer()
1708   {
1709     return sViewer == null ? null : sViewer.sview;
1710   }
1711
1712   @Override
1713   protected void setFTSDocFieldPrefs(FTSDataColumnPreferences newPrefs)
1714   {
1715     data.setDocFieldPrefs(newPrefs);
1716
1717   }
1718
1719   /**
1720    * 
1721    * @return true when all initialisation threads have finished and dialog is
1722    *         visible
1723    */
1724   public boolean isDialogVisible()
1725   {
1726     return mainFrame != null && data != null && cmb_filterOption != null
1727             && mainFrame.isVisible()
1728             && cmb_filterOption.getSelectedItem() != null;
1729   }
1730
1731   /**
1732    * 
1733    * @return true if the 3D-Beacons query button will/has been displayed
1734    */
1735   public boolean isCanQueryTDB()
1736   {
1737     return canQueryTDB;
1738   }
1739
1740   public boolean isNotQueriedTDBYet()
1741   {
1742     return notQueriedTDBYet;
1743   }
1744
1745   /**
1746    * Open a single structure file for a given sequence
1747    */
1748   public static void openStructureFileForSequence(AlignmentPanel ap,
1749           SequenceI seq, File sFile)
1750   {
1751     // Open the chooser headlessly. Not sure this is actually needed ?
1752     StructureChooser sc = new StructureChooser(new SequenceI[] { seq }, seq,
1753             ap, false);
1754     StructureSelectionManager ssm = ap.getStructureSelectionManager();
1755     PDBEntry fileEntry = null;
1756     try
1757     {
1758       fileEntry = new AssociatePdbFileWithSeq().associatePdbWithSeq(
1759               sFile.getAbsolutePath(), DataSourceType.FILE, seq, true,
1760               Desktop.instance);
1761     } catch (Exception e)
1762     {
1763       Console.error("Could not open structure file '"
1764               + sFile.getAbsolutePath() + "'");
1765       return;
1766     }
1767
1768     StructureViewer sViewer = sc.launchStructureViewer(ssm,
1769             new PDBEntry[]
1770             { fileEntry }, ap, new SequenceI[] { seq });
1771
1772     sc.mainFrame.dispose();
1773   }
1774 }