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