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