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