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