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