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