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