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