Merge branch 'features/r2_11_2_alphafold/JAL-629' into features/JAL-3858_PAEsInProjects
[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 noChooserGUI = false;
123
124   private static StructureViewer lastTargetedView = null;
125
126   public StructureChooser(SequenceI[] selectedSeqs, SequenceI selectedSeq,
127           AlignmentPanel ap)
128   {
129     this(selectedSeqs, selectedSeq, ap, false);
130   }
131
132   public StructureChooser(SequenceI[] selectedSeqs, SequenceI selectedSeq,
133           AlignmentPanel ap, boolean noChooserGUI)
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.noChooserGUI = noChooserGUI;
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(noChooserGUI);
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     if (selectedSequences.length == 1 || (assSeqOpt != null && !assSeqOpt
947             .getName().equalsIgnoreCase("-Select Associated Seq-")))
948     {
949       btn_pdbFromFile.setEnabled(true);
950       if (selectedPdbFileName != null && selectedPdbFileName.length() > 0)
951       {
952         btn_add.setEnabled(true);
953         lbl_fromFileStatus.setIcon(goodImage);
954       }
955     }
956     else
957     {
958       btn_pdbFromFile.setEnabled(false);
959       lbl_fromFileStatus.setIcon(errorImage);
960     }
961   }
962
963   @Override
964   protected void cmbAssSeqStateChanged()
965   {
966     validateSelections();
967   }
968
969   private FilterOption lastSelected = null;
970
971   /**
972    * Handles the state change event for the 'filter' combo-box and 'invert'
973    * check-box
974    */
975   @Override
976   protected void stateChanged(ItemEvent e)
977   {
978     if (e.getSource() instanceof JCheckBox)
979     {
980       updateCurrentView();
981     }
982     else
983     {
984       if (e.getStateChange() == ItemEvent.SELECTED)
985       {
986         updateCurrentView();
987       }
988     }
989
990   }
991
992   /**
993    * select structures for viewing by their PDB IDs
994    * 
995    * @param pdbids
996    * @return true if structures were found and marked as selected
997    */
998   public boolean selectStructure(String... pdbids)
999   {
1000     boolean found = false;
1001
1002     FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
1003             .getSelectedItem());
1004     String currentView = selectedFilterOpt.getView();
1005     JTable restable = (currentView == VIEWS_FILTER) ? getResultTable()
1006             : (currentView == VIEWS_LOCAL_PDB) ? tbl_local_pdb : null;
1007
1008     if (restable == null)
1009     {
1010       // can't select (enter PDB ID, or load file - need to also select which
1011       // sequence to associate with)
1012       return false;
1013     }
1014
1015     int pdbIdColIndex = restable.getColumn("PDB Id").getModelIndex();
1016     for (int r = 0; r < restable.getRowCount(); r++)
1017     {
1018       for (int p = 0; p < pdbids.length; p++)
1019       {
1020         if (String.valueOf(restable.getValueAt(r, pdbIdColIndex))
1021                 .equalsIgnoreCase(pdbids[p]))
1022         {
1023           restable.setRowSelectionInterval(r, r);
1024           found = true;
1025         }
1026       }
1027     }
1028     return found;
1029   }
1030
1031   /**
1032    * Handles the 'New View' action
1033    */
1034   @Override
1035   protected void newView_ActionPerformed()
1036   {
1037     targetView.setSelectedItem(null);
1038     showStructures(false);
1039   }
1040
1041   /**
1042    * Handles the 'Add to existing viewer' action
1043    */
1044   @Override
1045   protected void add_ActionPerformed()
1046   {
1047     showStructures(false);
1048   }
1049
1050   /**
1051    * structure viewer opened by this dialog, or null
1052    */
1053   private StructureViewer sViewer = null;
1054
1055   public void showStructures(boolean waitUntilFinished)
1056   {
1057
1058     final StructureSelectionManager ssm = ap.getStructureSelectionManager();
1059
1060     final int preferredHeight = pnl_filter.getHeight();
1061
1062     Runnable viewStruc = new Runnable()
1063     {
1064       @Override
1065       public void run()
1066       {
1067         FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
1068                 .getSelectedItem());
1069         String currentView = selectedFilterOpt.getView();
1070         JTable restable = (currentView == VIEWS_FILTER) ? getResultTable()
1071                 : tbl_local_pdb;
1072
1073         if (currentView == VIEWS_FILTER)
1074         {
1075           int[] selectedRows = restable.getSelectedRows();
1076           PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
1077           List<SequenceI> selectedSeqsToView = new ArrayList<>();
1078           pdbEntriesToView = data.collectSelectedRows(restable,
1079                   selectedRows, selectedSeqsToView);
1080
1081           SequenceI[] selectedSeqs = selectedSeqsToView
1082                   .toArray(new SequenceI[selectedSeqsToView.size()]);
1083           sViewer = launchStructureViewer(ssm, pdbEntriesToView, ap,
1084                   selectedSeqs);
1085         }
1086         else if (currentView == VIEWS_LOCAL_PDB)
1087         {
1088           int[] selectedRows = tbl_local_pdb.getSelectedRows();
1089           PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
1090           int count = 0;
1091           int pdbIdColIndex = tbl_local_pdb.getColumn("PDB Id")
1092                   .getModelIndex();
1093           int refSeqColIndex = tbl_local_pdb.getColumn("Ref Sequence")
1094                   .getModelIndex();
1095           List<SequenceI> selectedSeqsToView = new ArrayList<>();
1096           for (int row : selectedRows)
1097           {
1098             PDBEntry pdbEntry = ((PDBEntryTableModel) tbl_local_pdb
1099                     .getModel()).getPDBEntryAt(row).getPdbEntry();
1100
1101             pdbEntriesToView[count++] = pdbEntry;
1102             SequenceI selectedSeq = (SequenceI) tbl_local_pdb
1103                     .getValueAt(row, refSeqColIndex);
1104             selectedSeqsToView.add(selectedSeq);
1105           }
1106           SequenceI[] selectedSeqs = selectedSeqsToView
1107                   .toArray(new SequenceI[selectedSeqsToView.size()]);
1108           sViewer = launchStructureViewer(ssm, pdbEntriesToView, ap,
1109                   selectedSeqs);
1110         }
1111         else if (currentView == VIEWS_ENTER_ID)
1112         {
1113           SequenceI userSelectedSeq = ((AssociateSeqOptions) idInputAssSeqPanel
1114                   .getCmb_assSeq().getSelectedItem()).getSequence();
1115           if (userSelectedSeq != null)
1116           {
1117             selectedSequence = userSelectedSeq;
1118           }
1119           String pdbIdStr = txt_search.getText();
1120           PDBEntry pdbEntry = selectedSequence.getPDBEntry(pdbIdStr);
1121           if (pdbEntry == null)
1122           {
1123             pdbEntry = new PDBEntry();
1124             if (pdbIdStr.split(":").length > 1)
1125             {
1126               pdbEntry.setId(pdbIdStr.split(":")[0]);
1127               pdbEntry.setChainCode(
1128                       pdbIdStr.split(":")[1].toUpperCase(Locale.ROOT));
1129             }
1130             else
1131             {
1132               pdbEntry.setId(pdbIdStr);
1133             }
1134             pdbEntry.setType(PDBEntry.Type.PDB);
1135             selectedSequence.getDatasetSequence().addPDBId(pdbEntry);
1136           }
1137
1138           PDBEntry[] pdbEntriesToView = new PDBEntry[] { pdbEntry };
1139           sViewer = launchStructureViewer(ssm, pdbEntriesToView, ap,
1140                   new SequenceI[]
1141                   { selectedSequence });
1142         }
1143         else if (currentView == VIEWS_FROM_FILE)
1144         {
1145           SequenceI userSelectedSeq = ((AssociateSeqOptions) fileChooserAssSeqPanel
1146                   .getCmb_assSeq().getSelectedItem()).getSequence();
1147           if (userSelectedSeq != null)
1148           {
1149             selectedSequence = userSelectedSeq;
1150           }
1151           PDBEntry fileEntry = new AssociatePdbFileWithSeq()
1152                   .associatePdbWithSeq(selectedPdbFileName,
1153                           DataSourceType.FILE, selectedSequence, true,
1154                           Desktop.instance);
1155
1156           sViewer = launchStructureViewer(ssm, new PDBEntry[] { fileEntry },
1157                   ap, new SequenceI[]
1158                   { selectedSequence });
1159         }
1160         SwingUtilities.invokeLater(new Runnable()
1161         {
1162           @Override
1163           public void run()
1164           {
1165             closeAction(preferredHeight);
1166             mainFrame.dispose();
1167           }
1168         });
1169       }
1170     };
1171     Thread runner = new Thread(viewStruc);
1172     runner.start();
1173     if (waitUntilFinished)
1174     {
1175       while (sViewer == null ? runner.isAlive()
1176               : (sViewer.sview == null ? true
1177                       : !sViewer.sview.hasMapping()))
1178       {
1179         try
1180         {
1181           Thread.sleep(300);
1182         } catch (InterruptedException ie)
1183         {
1184
1185         }
1186       }
1187     }
1188   }
1189
1190   /**
1191    * Answers a structure viewer (new or existing) configured to superimpose
1192    * added structures or not according to the user's choice
1193    * 
1194    * @param ssm
1195    * @return
1196    */
1197   StructureViewer getTargetedStructureViewer(StructureSelectionManager ssm)
1198   {
1199     Object sv = targetView.getSelectedItem();
1200
1201     return sv == null ? new StructureViewer(ssm) : (StructureViewer) sv;
1202   }
1203
1204   /**
1205    * Adds PDB structures to a new or existing structure viewer
1206    * 
1207    * @param ssm
1208    * @param pdbEntriesToView
1209    * @param alignPanel
1210    * @param sequences
1211    * @return
1212    */
1213   private StructureViewer launchStructureViewer(
1214           StructureSelectionManager ssm, final PDBEntry[] pdbEntriesToView,
1215           final AlignmentPanel alignPanel, SequenceI[] sequences)
1216   {
1217     long progressId = sequences.hashCode();
1218     setProgressBar(MessageManager
1219             .getString("status.launching_3d_structure_viewer"), progressId);
1220     final StructureViewer theViewer = getTargetedStructureViewer(ssm);
1221     boolean superimpose = chk_superpose.isSelected();
1222     theViewer.setSuperpose(superimpose);
1223
1224     /*
1225      * remember user's choice of superimpose or not
1226      */
1227     Cache.setProperty(AUTOSUPERIMPOSE,
1228             Boolean.valueOf(superimpose).toString());
1229
1230     setProgressBar(null, progressId);
1231     if (SiftsSettings.isMapWithSifts())
1232     {
1233       List<SequenceI> seqsWithoutSourceDBRef = new ArrayList<>();
1234       int p = 0;
1235       // TODO: skip PDBEntry:Sequence pairs where PDBEntry doesn't look like a
1236       // real PDB ID. For moment, we can also safely do this if there is already
1237       // a known mapping between the PDBEntry and the sequence.
1238       for (SequenceI seq : sequences)
1239       {
1240         PDBEntry pdbe = pdbEntriesToView[p++];
1241         if (pdbe != null && pdbe.getFile() != null)
1242         {
1243           StructureMapping[] smm = ssm.getMapping(pdbe.getFile());
1244           if (smm != null && smm.length > 0)
1245           {
1246             for (StructureMapping sm : smm)
1247             {
1248               if (sm.getSequence() == seq)
1249               {
1250                 continue;
1251               }
1252             }
1253           }
1254         }
1255         if (seq.getPrimaryDBRefs().isEmpty())
1256         {
1257           seqsWithoutSourceDBRef.add(seq);
1258           continue;
1259         }
1260       }
1261       if (!seqsWithoutSourceDBRef.isEmpty())
1262       {
1263         int y = seqsWithoutSourceDBRef.size();
1264         setProgressBar(MessageManager.formatMessage(
1265                 "status.fetching_dbrefs_for_sequences_without_valid_refs",
1266                 y), progressId);
1267         SequenceI[] seqWithoutSrcDBRef = seqsWithoutSourceDBRef
1268                 .toArray(new SequenceI[y]);
1269         DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef);
1270         dbRefFetcher.fetchDBRefs(true);
1271
1272         setProgressBar("Fetch complete.", progressId); // todo i18n
1273       }
1274     }
1275     if (pdbEntriesToView.length > 1)
1276     {
1277       setProgressBar(
1278               MessageManager.getString(
1279                       "status.fetching_3d_structures_for_selected_entries"),
1280               progressId);
1281       theViewer.viewStructures(pdbEntriesToView, sequences, alignPanel);
1282     }
1283     else
1284     {
1285       setProgressBar(MessageManager.formatMessage(
1286               "status.fetching_3d_structures_for",
1287               pdbEntriesToView[0].getId()), progressId);
1288       theViewer.viewStructures(pdbEntriesToView[0], sequences, alignPanel);
1289     }
1290     setProgressBar(null, progressId);
1291     // remember the last viewer we used...
1292     lastTargetedView = theViewer;
1293     return theViewer;
1294   }
1295
1296   /**
1297    * Populates the combo-box used in associating manually fetched structures to
1298    * a unique sequence when more than one sequence selection is made.
1299    */
1300   @Override
1301   protected void populateCmbAssociateSeqOptions(
1302           JComboBox<AssociateSeqOptions> cmb_assSeq,
1303           JLabel lbl_associateSeq)
1304   {
1305     cmb_assSeq.removeAllItems();
1306     cmb_assSeq.addItem(
1307             new AssociateSeqOptions("-Select Associated Seq-", null));
1308     lbl_associateSeq.setVisible(false);
1309     if (selectedSequences.length > 1)
1310     {
1311       for (SequenceI seq : selectedSequences)
1312       {
1313         cmb_assSeq.addItem(new AssociateSeqOptions(seq));
1314       }
1315     }
1316     else
1317     {
1318       String seqName = selectedSequence.getDisplayId(false);
1319       seqName = seqName.length() <= 40 ? seqName : seqName.substring(0, 39);
1320       lbl_associateSeq.setText(seqName);
1321       lbl_associateSeq.setVisible(true);
1322       cmb_assSeq.setVisible(false);
1323     }
1324   }
1325
1326   protected boolean isStructuresDiscovered()
1327   {
1328     return discoveredStructuresSet != null
1329             && !discoveredStructuresSet.isEmpty();
1330   }
1331
1332   protected int PDB_ID_MIN = 3;// or: (Jalview.isJS() ? 3 : 1); // Bob proposes
1333                                // this.
1334   // Doing a search for "1" or "1c" is valuable?
1335   // Those work but are enormously slow.
1336
1337   @Override
1338   protected void txt_search_ActionPerformed()
1339   {
1340     String text = txt_search.getText().trim();
1341     if (text.length() >= PDB_ID_MIN)
1342       new Thread()
1343       {
1344
1345         @Override
1346         public void run()
1347         {
1348           errorWarning.setLength(0);
1349           isValidPBDEntry = false;
1350           if (text.length() > 0)
1351           {
1352             // TODO move this pdb id search into the PDB specific
1353             // FTSSearchEngine
1354             // for moment, it will work fine as is because it is self-contained
1355             String searchTerm = text.toLowerCase(Locale.ROOT);
1356             searchTerm = searchTerm.split(":")[0];
1357             // System.out.println(">>>>> search term : " + searchTerm);
1358             List<FTSDataColumnI> wantedFields = new ArrayList<>();
1359             FTSRestRequest pdbRequest = new FTSRestRequest();
1360             pdbRequest.setAllowEmptySeq(false);
1361             pdbRequest.setResponseSize(1);
1362             pdbRequest.setFieldToSearchBy("(pdb_id:");
1363             pdbRequest.setWantedFields(wantedFields);
1364             pdbRequest.setSearchTerm(searchTerm + ")");
1365             pdbRequest.setAssociatedSequence(selectedSequence);
1366             FTSRestClientI pdbRestClient = PDBFTSRestClient.getInstance();
1367             wantedFields.add(pdbRestClient.getPrimaryKeyColumn());
1368             FTSRestResponse resultList;
1369             try
1370             {
1371               resultList = pdbRestClient.executeRequest(pdbRequest);
1372             } catch (Exception e)
1373             {
1374               errorWarning.append(e.getMessage());
1375               return;
1376             } finally
1377             {
1378               validateSelections();
1379             }
1380             if (resultList.getSearchSummary() != null
1381                     && resultList.getSearchSummary().size() > 0)
1382             {
1383               isValidPBDEntry = true;
1384             }
1385           }
1386           validateSelections();
1387         }
1388       }.start();
1389   }
1390
1391   @Override
1392   protected void tabRefresh()
1393   {
1394     if (selectedSequences != null)
1395     {
1396       lbl_loading.setVisible(true);
1397       Thread refreshThread = new Thread(new Runnable()
1398       {
1399         @Override
1400         public void run()
1401         {
1402           fetchStructuresMetaData();
1403           // populateFilterComboBox(true, cachedPDBExists);
1404
1405           filterResultSet(
1406                   ((FilterOption) cmb_filterOption.getSelectedItem())
1407                           .getValue());
1408           lbl_loading.setVisible(false);
1409         }
1410       });
1411       refreshThread.start();
1412     }
1413   }
1414
1415   public class PDBEntryTableModel extends AbstractTableModel
1416   {
1417     String[] columns = { "Ref Sequence", "PDB Id", "Chain", "Type",
1418         "File" };
1419
1420     private List<CachedPDB> pdbEntries;
1421
1422     public PDBEntryTableModel(List<CachedPDB> pdbEntries)
1423     {
1424       this.pdbEntries = new ArrayList<>(pdbEntries);
1425     }
1426
1427     @Override
1428     public String getColumnName(int columnIndex)
1429     {
1430       return columns[columnIndex];
1431     }
1432
1433     @Override
1434     public int getRowCount()
1435     {
1436       return pdbEntries.size();
1437     }
1438
1439     @Override
1440     public int getColumnCount()
1441     {
1442       return columns.length;
1443     }
1444
1445     @Override
1446     public boolean isCellEditable(int row, int column)
1447     {
1448       return false;
1449     }
1450
1451     @Override
1452     public Object getValueAt(int rowIndex, int columnIndex)
1453     {
1454       Object value = "??";
1455       CachedPDB entry = pdbEntries.get(rowIndex);
1456       switch (columnIndex)
1457       {
1458       case 0:
1459         value = entry.getSequence();
1460         break;
1461       case 1:
1462         value = entry.getQualifiedId();
1463         break;
1464       case 2:
1465         value = entry.getPdbEntry().getChainCode() == null ? "_"
1466                 : entry.getPdbEntry().getChainCode();
1467         break;
1468       case 3:
1469         value = entry.getPdbEntry().getType();
1470         break;
1471       case 4:
1472         value = entry.getPdbEntry().getFile();
1473         break;
1474       }
1475       return value;
1476     }
1477
1478     @Override
1479     public Class<?> getColumnClass(int columnIndex)
1480     {
1481       return columnIndex == 0 ? SequenceI.class : PDBEntry.class;
1482     }
1483
1484     public CachedPDB getPDBEntryAt(int row)
1485     {
1486       return pdbEntries.get(row);
1487     }
1488
1489   }
1490
1491   private class CachedPDB
1492   {
1493     private SequenceI sequence;
1494
1495     private PDBEntry pdbEntry;
1496
1497     public CachedPDB(SequenceI sequence, PDBEntry pdbEntry)
1498     {
1499       this.sequence = sequence;
1500       this.pdbEntry = pdbEntry;
1501     }
1502
1503     public String getQualifiedId()
1504     {
1505       if (pdbEntry.hasProvider())
1506       {
1507         return pdbEntry.getProvider() + ":" + pdbEntry.getId();
1508       }
1509       return pdbEntry.toString();
1510     }
1511
1512     public SequenceI getSequence()
1513     {
1514       return sequence;
1515     }
1516
1517     public PDBEntry getPdbEntry()
1518     {
1519       return pdbEntry;
1520     }
1521
1522   }
1523
1524   private IProgressIndicator progressBar;
1525
1526   @Override
1527   public void setProgressBar(String message, long id)
1528   {
1529     if (!Platform.isHeadless())
1530       progressBar.setProgressBar(message, id);
1531   }
1532
1533   @Override
1534   public void registerHandler(long id, IProgressIndicatorHandler handler)
1535   {
1536     progressBar.registerHandler(id, handler);
1537   }
1538
1539   @Override
1540   public boolean operationInProgress()
1541   {
1542     return progressBar.operationInProgress();
1543   }
1544
1545   public JalviewStructureDisplayI getOpenedStructureViewer()
1546   {
1547     return sViewer == null ? null : sViewer.sview;
1548   }
1549
1550   @Override
1551   protected void setFTSDocFieldPrefs(FTSDataColumnPreferences newPrefs)
1552   {
1553     data.setDocFieldPrefs(newPrefs);
1554
1555   }
1556
1557   /**
1558    * 
1559    * @return true when all initialisation threads have finished and dialog is
1560    *         visible
1561    */
1562   public boolean isDialogVisible()
1563   {
1564     return mainFrame != null && data != null && cmb_filterOption != null
1565             && mainFrame.isVisible()
1566             && cmb_filterOption.getSelectedItem() != null;
1567   }
1568
1569   /**
1570    * 
1571    * @return true if the 3D-Beacons query button will/has been displayed
1572    */
1573   public boolean isCanQueryTDB()
1574   {
1575     return canQueryTDB;
1576   }
1577
1578   public boolean isNotQueriedTDBYet()
1579   {
1580     return notQueriedTDBYet;
1581   }
1582
1583   /**
1584    * Open a single structure file for a given sequence
1585    */
1586   public static void openStructureFileForSequence(AlignmentPanel ap,
1587           SequenceI seq, File sFile)
1588   {
1589     StructureChooser sc = new StructureChooser(new SequenceI[] { seq }, seq,
1590             ap, true);
1591     StructureSelectionManager ssm = ap.getStructureSelectionManager();
1592     PDBEntry fileEntry = null;
1593     try
1594     {
1595       fileEntry = new AssociatePdbFileWithSeq().associatePdbWithSeq(
1596               sFile.getAbsolutePath(), DataSourceType.FILE, seq, true,
1597               Desktop.instance);
1598     } catch (Exception e)
1599     {
1600       Console.error("Could not open structure file '"
1601               + sFile.getAbsolutePath() + "'");
1602       return;
1603     }
1604
1605     StructureViewer sViewer = sc.launchStructureViewer(ssm,
1606             new PDBEntry[]
1607             { fileEntry }, ap, new SequenceI[] { seq });
1608
1609     sc.mainFrame.dispose();
1610     sc.noChooserGUI = false;
1611   }
1612 }