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