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