2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
24 import java.awt.event.ActionEvent;
25 import java.awt.event.ActionListener;
26 import java.awt.event.ItemEvent;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.HashSet;
30 import java.util.LinkedHashSet;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.concurrent.Callable;
34 import java.util.concurrent.Executors;
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;
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.ws.DBRefFetcher;
69 import jalview.ws.DBRefFetcher.FetchFinishedListenerI;
70 import jalview.ws.seqfetcher.DbSourceProxy;
71 import jalview.ws.sifts.SiftsSettings;
74 * Provides the behaviors for the Structure chooser Panel
79 @SuppressWarnings("serial")
80 public class StructureChooser extends GStructureChooser
81 implements IProgressIndicator
83 private static final String AUTOSUPERIMPOSE = "AUTOSUPERIMPOSE";
86 * warn user if need to fetch more than this many uniprot records at once
88 private static final int THRESHOLD_WARN_UNIPROT_FETCH_NEEDED = 20;
90 private SequenceI selectedSequence;
92 private SequenceI[] selectedSequences;
94 private IProgressIndicator progressIndicator;
96 private Collection<FTSData> discoveredStructuresSet;
98 private StructureChooserQuerySource data;
101 protected FTSDataColumnPreferences getFTSDocFieldPrefs()
103 return data.getDocFieldPrefs();
106 private String selectedPdbFileName;
108 private boolean isValidPBDEntry;
110 private boolean cachedPDBExists;
112 private Collection<FTSData> lastDiscoveredStructuresSet;
114 private boolean canQueryTDB = false;
116 private boolean notQueriedTDBYet = true;
118 List<SequenceI> seqsWithoutSourceDBRef = null;
120 private static StructureViewer lastTargetedView = null;
122 public StructureChooser(SequenceI[] selectedSeqs, SequenceI selectedSeq,
125 // which FTS engine to use
126 data = StructureChooserQuerySource.getQuerySourceFor(selectedSeqs);
130 this.selectedSequence = selectedSeq;
131 this.selectedSequences = selectedSeqs;
132 this.progressIndicator = (ap == null) ? null : ap.alignFrame;
138 * sets canQueryTDB if protein sequences without a canonical uniprot ref or at
139 * least one structure are discovered.
141 private void populateSeqsWithoutSourceDBRef()
143 seqsWithoutSourceDBRef = new ArrayList<SequenceI>();
144 boolean needCanonical = false;
145 for (SequenceI seq : selectedSequences)
149 int dbRef = ThreeDBStructureChooserQuerySource
150 .checkUniprotRefs(seq.getDBRefs());
155 // need to retrieve canonicals
156 needCanonical = true;
157 seqsWithoutSourceDBRef.add(seq);
161 // could be a sequence with pdb ref
162 if (seq.getAllPDBEntries() == null
163 || seq.getAllPDBEntries().size() == 0)
165 seqsWithoutSourceDBRef.add(seq);
171 // retrieve database refs for protein sequences
172 if (!seqsWithoutSourceDBRef.isEmpty())
177 // triggers display of the 'Query TDB' button
178 notQueriedTDBYet = true;
184 * Initializes parameters used by the Structure Chooser Panel
186 protected void init()
188 if (!Jalview.isHeadlessMode())
190 progressBar = new ProgressBar(this.statusPanel, this.statusBar);
193 chk_superpose.setSelected(Cache.getDefault(AUTOSUPERIMPOSE, true));
194 btn_queryTDB.addActionListener(new ActionListener()
198 public void actionPerformed(ActionEvent e)
200 promptForTDBFetch(false);
204 Executors.defaultThreadFactory().newThread(new Runnable()
209 populateSeqsWithoutSourceDBRef();
210 initialStructureDiscovery();
218 private void initialStructureDiscovery()
220 // check which FTS engine to use
221 data = StructureChooserQuerySource.getQuerySourceFor(selectedSequences);
223 // ensure a filter option is in force for search
224 populateFilterComboBox(true, cachedPDBExists);
226 // looks for any existing structures already loaded
227 // for the sequences (the cached ones)
228 // then queries the StructureChooserQuerySource to
229 // discover more structures.
231 // Possible optimisation is to only begin querying
232 // the structure chooser if there are no cached structures.
234 long startTime = System.currentTimeMillis();
235 updateProgressIndicator(
236 MessageManager.getString("status.loading_cached_pdb_entries"),
238 loadLocalCachedPDBEntries();
239 updateProgressIndicator(null, startTime);
240 updateProgressIndicator(
241 MessageManager.getString("status.searching_for_pdb_structures"),
243 fetchStructuresMetaData();
244 // revise filter options if no results were found
245 populateFilterComboBox(isStructuresDiscovered(), cachedPDBExists);
246 discoverStructureViews();
247 updateProgressIndicator(null, startTime);
248 mainFrame.setVisible(true);
253 * raises dialog for Uniprot fetch followed by 3D beacons search
256 * - when true, don't ask, just fetch
258 public void promptForTDBFetch(boolean ignoreGui)
260 final long progressId = System.currentTimeMillis();
262 // final action after prompting and discovering db refs
263 final Runnable strucDiscovery = new Runnable()
268 mainFrame.setEnabled(false);
269 cmb_filterOption.setEnabled(false);
270 progressBar.setProgressBar(
271 MessageManager.getString("status.searching_3d_beacons"),
273 btn_queryTDB.setEnabled(false);
274 // TODO: warn if no accessions discovered
275 populateSeqsWithoutSourceDBRef();
276 // redo initial discovery - this time with 3d beacons
278 previousWantedFields = null;
279 lastSelected = (FilterOption) cmb_filterOption.getSelectedItem();
280 cmb_filterOption.setSelectedItem(null);
281 cachedPDBExists = false; // reset to initial
282 initialStructureDiscovery();
283 if (!isStructuresDiscovered())
285 progressBar.setProgressBar(MessageManager.getString(
286 "status.no_structures_discovered_from_3d_beacons"),
288 btn_queryTDB.setToolTipText(MessageManager.getString(
289 "status.no_structures_discovered_from_3d_beacons"));
290 btn_queryTDB.setEnabled(false);
291 pnl_queryTDB.setVisible(false);
295 cmb_filterOption.setSelectedIndex(0); // select 'best'
296 btn_queryTDB.setVisible(false);
297 pnl_queryTDB.setVisible(false);
298 progressBar.setProgressBar(null, progressId);
300 mainFrame.setEnabled(true);
301 cmb_filterOption.setEnabled(true);
305 final FetchFinishedListenerI afterDbRefFetch = new FetchFinishedListenerI()
309 public void finished()
311 // filter has been selected, so we set flag to remove ourselves
312 notQueriedTDBYet = false;
313 // new thread to discover structures - via 3d beacons
314 Executors.defaultThreadFactory().newThread(strucDiscovery).start();
319 // fetch db refs if OK pressed
320 final Callable discoverCanonicalDBrefs = () -> {
321 btn_queryTDB.setEnabled(false);
322 populateSeqsWithoutSourceDBRef();
324 final int y = seqsWithoutSourceDBRef.size();
327 final SequenceI[] seqWithoutSrcDBRef = seqsWithoutSourceDBRef
328 .toArray(new SequenceI[y]);
329 DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef,
330 progressBar, new DbSourceProxy[]
331 { new jalview.ws.dbsources.Uniprot() }, null, false);
332 dbRefFetcher.addListener(afterDbRefFetch);
333 // ideally this would also gracefully run with callbacks
335 dbRefFetcher.fetchDBRefs(true);
339 // call finished action directly
340 afterDbRefFetch.finished();
344 final Callable revertview = () -> {
345 if (lastSelected != null)
347 cmb_filterOption.setSelectedItem(lastSelected);
351 int threshold = Cache.getDefault("UNIPROT_AUTOFETCH_THRESHOLD",
352 THRESHOLD_WARN_UNIPROT_FETCH_NEEDED);
353 Console.debug("Using Uniprot fetch threshold of " + threshold);
354 if (ignoreGui || seqsWithoutSourceDBRef.size() < threshold)
356 Executors.newSingleThreadExecutor().submit(discoverCanonicalDBrefs);
359 // need cancel and no to result in the discoverPDB action - mocked is
360 // 'cancel' TODO: mock should be OK
362 StructureChooser thisSC = this;
363 JvOptionPane.newOptionDialog(thisSC.getFrame())
364 .setResponseHandler(JvOptionPane.OK_OPTION,
365 discoverCanonicalDBrefs)
366 .setResponseHandler(JvOptionPane.CANCEL_OPTION, revertview)
367 .setResponseHandler(JvOptionPane.NO_OPTION, revertview)
369 MessageManager.formatMessage(
370 "label.fetch_references_for_3dbeacons",
371 seqsWithoutSourceDBRef.size()),
372 MessageManager.getString("label.3dbeacons"),
373 JvOptionPane.YES_NO_OPTION, JvOptionPane.PLAIN_MESSAGE,
375 { MessageManager.getString("action.ok"),
376 MessageManager.getString("action.cancel") },
377 MessageManager.getString("action.ok"), false);
381 * Builds a drop-down choice list of existing structure viewers to which new
382 * structures may be added. If this list is empty then it, and the 'Add'
383 * button, are hidden.
385 private void discoverStructureViews()
387 if (Desktop.instance != null)
389 targetView.removeAllItems();
390 if (lastTargetedView != null && !lastTargetedView.isVisible())
392 lastTargetedView = null;
394 int linkedViewsAt = 0;
395 for (StructureViewerBase view : Desktop.instance
396 .getStructureViewers(null, null))
398 StructureViewer viewHandler = (lastTargetedView != null
399 && lastTargetedView.sview == view) ? lastTargetedView
400 : StructureViewer.reconfigure(view);
402 if (view.isLinkedWith(ap))
404 targetView.insertItemAt(viewHandler, linkedViewsAt++);
408 targetView.addItem(viewHandler);
413 * show option to Add to viewer if at least 1 viewer found
415 targetView.setVisible(false);
416 if (targetView.getItemCount() > 0)
418 targetView.setVisible(true);
419 if (lastTargetedView != null)
421 targetView.setSelectedItem(lastTargetedView);
425 targetView.setSelectedIndex(0);
428 btn_add.setVisible(targetView.isVisible());
433 * Updates the progress indicator with the specified message
436 * displayed message for the operation
438 * unique handle for this indicator
440 protected void updateProgressIndicator(String message, long id)
442 if (progressIndicator != null)
444 progressIndicator.setProgressBar(message, id);
449 * Retrieve meta-data for all the structure(s) for a given sequence(s) in a
452 void fetchStructuresMetaData()
454 long startTime = System.currentTimeMillis();
455 Collection<FTSDataColumnI> wantedFields = data.getDocFieldPrefs()
456 .getStructureSummaryFields();
458 discoveredStructuresSet = new LinkedHashSet<>();
459 HashSet<String> errors = new HashSet<>();
461 FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
464 for (SequenceI seq : selectedSequences)
467 FTSRestResponse resultList;
470 resultList = data.fetchStructuresMetaData(seq, wantedFields,
471 selectedFilterOpt, !chk_invertFilter.isSelected());
472 // null response means the FTSengine didn't yield a query for this
473 // consider designing a special exception if we really wanted to be
475 if (resultList == null)
479 } catch (Exception e)
482 errors.add(e.getMessage());
485 if (resultList.getSearchSummary() != null
486 && !resultList.getSearchSummary().isEmpty())
488 discoveredStructuresSet.addAll(resultList.getSearchSummary());
492 int noOfStructuresFound = 0;
493 String totalTime = (System.currentTimeMillis() - startTime)
495 if (discoveredStructuresSet != null
496 && !discoveredStructuresSet.isEmpty())
499 .setModel(data.getTableModel(discoveredStructuresSet));
501 noOfStructuresFound = discoveredStructuresSet.size();
502 lastDiscoveredStructuresSet = discoveredStructuresSet;
503 mainFrame.setTitle(MessageManager.formatMessage(
504 "label.structure_chooser_no_of_structures",
505 noOfStructuresFound, totalTime));
509 mainFrame.setTitle(MessageManager
510 .getString("label.structure_chooser_manual_association"));
511 if (errors.size() > 0)
513 StringBuilder errorMsg = new StringBuilder();
514 for (String error : errors)
516 errorMsg.append(error).append("\n");
518 JvOptionPane.showMessageDialog(this, errorMsg.toString(),
519 MessageManager.getString("label.pdb_web-service_error"),
520 JvOptionPane.ERROR_MESSAGE);
525 protected void loadLocalCachedPDBEntries()
527 ArrayList<CachedPDB> entries = new ArrayList<>();
528 for (SequenceI seq : selectedSequences)
530 if (seq.getDatasetSequence() != null
531 && seq.getDatasetSequence().getAllPDBEntries() != null)
533 for (PDBEntry pdbEntry : seq.getDatasetSequence()
536 if (pdbEntry.getFile() != null)
538 entries.add(new CachedPDB(seq, pdbEntry));
543 cachedPDBExists = !entries.isEmpty();
544 PDBEntryTableModel tableModelx = new PDBEntryTableModel(entries);
545 tbl_local_pdb.setModel(tableModelx);
549 * Filters a given list of discovered structures based on supplied argument
551 * @param fieldToFilterBy
552 * the field to filter by
554 void filterResultSet(final String fieldToFilterBy)
556 Thread filterThread = new Thread(new Runnable()
562 long startTime = System.currentTimeMillis();
563 lbl_loading.setVisible(true);
564 Collection<FTSDataColumnI> wantedFields = data.getDocFieldPrefs()
565 .getStructureSummaryFields();
566 Collection<FTSData> filteredResponse = new HashSet<>();
567 HashSet<String> errors = new HashSet<>();
569 for (SequenceI seq : selectedSequences)
572 FTSRestResponse resultList;
575 resultList = data.selectFirstRankedQuery(seq,
576 discoveredStructuresSet, wantedFields, fieldToFilterBy,
577 !chk_invertFilter.isSelected());
579 } catch (Exception e)
582 errors.add(e.getMessage());
585 if (resultList.getSearchSummary() != null
586 && !resultList.getSearchSummary().isEmpty())
588 filteredResponse.addAll(resultList.getSearchSummary());
592 String totalTime = (System.currentTimeMillis() - startTime)
594 if (!filteredResponse.isEmpty())
596 final int filterResponseCount = filteredResponse.size();
597 Collection<FTSData> reorderedStructuresSet = new LinkedHashSet<>();
598 reorderedStructuresSet.addAll(filteredResponse);
599 reorderedStructuresSet.addAll(discoveredStructuresSet);
601 .setModel(data.getTableModel(reorderedStructuresSet));
603 FTSRestResponse.configureTableColumn(getResultTable(),
604 wantedFields, tempUserPrefs);
605 getResultTable().getColumn("Ref Sequence").setPreferredWidth(120);
606 getResultTable().getColumn("Ref Sequence").setMinWidth(100);
607 getResultTable().getColumn("Ref Sequence").setMaxWidth(200);
608 // Update table selection model here
609 getResultTable().addRowSelectionInterval(0,
610 filterResponseCount - 1);
611 mainFrame.setTitle(MessageManager.formatMessage(
612 "label.structure_chooser_filter_time", totalTime));
616 mainFrame.setTitle(MessageManager.formatMessage(
617 "label.structure_chooser_filter_time", totalTime));
618 if (errors.size() > 0)
620 StringBuilder errorMsg = new StringBuilder();
621 for (String error : errors)
623 errorMsg.append(error).append("\n");
625 JvOptionPane.showMessageDialog(null, errorMsg.toString(),
626 MessageManager.getString("label.pdb_web-service_error"),
627 JvOptionPane.ERROR_MESSAGE);
631 lbl_loading.setVisible(false);
633 validateSelections();
636 filterThread.start();
640 * Handles action event for btn_pdbFromFile
643 protected void pdbFromFile_actionPerformed()
645 // TODO: JAL-3048 not needed for Jalview-JS until JSmol dep and
648 jalview.io.JalviewFileChooser chooser = new jalview.io.JalviewFileChooser(
649 Cache.getProperty("LAST_DIRECTORY"));
650 chooser.setFileView(new jalview.io.JalviewFileView());
651 chooser.setDialogTitle(
652 MessageManager.formatMessage("label.select_pdb_file_for",
653 selectedSequence.getDisplayId(false)));
654 chooser.setToolTipText(MessageManager.formatMessage(
655 "label.load_pdb_file_associate_with_sequence",
656 selectedSequence.getDisplayId(false)));
658 int value = chooser.showOpenDialog(null);
659 if (value == jalview.io.JalviewFileChooser.APPROVE_OPTION)
661 selectedPdbFileName = chooser.getSelectedFile().getPath();
662 Cache.setProperty("LAST_DIRECTORY", selectedPdbFileName);
663 validateSelections();
668 * Populates the filter combo-box options dynamically depending on discovered
671 protected void populateFilterComboBox(boolean haveData,
672 boolean cachedPDBExist)
674 populateFilterComboBox(haveData, cachedPDBExist, null);
678 * Populates the filter combo-box options dynamically depending on discovered
681 protected void populateFilterComboBox(boolean haveData,
682 boolean cachedPDBExist, FilterOption lastSel)
686 * temporarily suspend the change listener behaviour
688 cmb_filterOption.removeItemListener(this);
690 cmb_filterOption.removeAllItems();
693 List<FilterOption> filters = data
694 .getAvailableFilterOptions(VIEWS_FILTER);
695 data.updateAvailableFilterOptions(VIEWS_FILTER, filters,
696 lastDiscoveredStructuresSet);
698 for (FilterOption filter : filters)
700 if (lastSel != null && filter.equals(lastSel))
705 cmb_filterOption.addItem(filter);
709 cmb_filterOption.addItem(
710 new FilterOption(MessageManager.getString("label.enter_pdb_id"),
711 "-", VIEWS_ENTER_ID, false, null));
712 cmb_filterOption.addItem(
713 new FilterOption(MessageManager.getString("label.from_file"),
714 "-", VIEWS_FROM_FILE, false, null));
715 if (canQueryTDB && notQueriedTDBYet)
717 btn_queryTDB.setVisible(true);
718 pnl_queryTDB.setVisible(true);
723 FilterOption cachedOption = new FilterOption(
724 MessageManager.getString("label.cached_structures"), "-",
725 VIEWS_LOCAL_PDB, false, null);
726 cmb_filterOption.addItem(cachedOption);
729 cmb_filterOption.setSelectedItem(cachedOption);
734 cmb_filterOption.setSelectedIndex(selSet);
736 cmb_filterOption.addItemListener(this);
740 * Updates the displayed view based on the selected filter option
742 protected void updateCurrentView()
744 FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
747 if (lastSelected == selectedFilterOpt)
749 // don't need to do anything, probably
752 // otherwise, record selection
753 // and update the layout and dialog accordingly
754 lastSelected = selectedFilterOpt;
756 layout_switchableViews.show(pnl_switchableViews,
757 selectedFilterOpt.getView());
758 String filterTitle = mainFrame.getTitle();
759 mainFrame.setTitle(frameTitle);
760 chk_invertFilter.setVisible(false);
762 if (selectedFilterOpt.getView() == VIEWS_FILTER)
764 mainFrame.setTitle(filterTitle);
765 // TDB Query has no invert as yet
766 chk_invertFilter.setVisible(selectedFilterOpt
767 .getQuerySource() instanceof PDBStructureChooserQuerySource);
769 if (data != selectedFilterOpt.getQuerySource()
770 || data.needsRefetch(selectedFilterOpt))
772 data = selectedFilterOpt.getQuerySource();
773 // rebuild the views completely, since prefs will also change
779 filterResultSet(selectedFilterOpt.getValue());
782 else if (selectedFilterOpt.getView() == VIEWS_ENTER_ID
783 || selectedFilterOpt.getView() == VIEWS_FROM_FILE)
785 mainFrame.setTitle(MessageManager
786 .getString("label.structure_chooser_manual_association"));
787 idInputAssSeqPanel.loadCmbAssSeq();
788 fileChooserAssSeqPanel.loadCmbAssSeq();
790 validateSelections();
794 * Validates user selection and enables the 'Add' and 'New View' buttons if
795 * all parameters are correct (the Add button will only be visible if there is
796 * at least one existing structure viewer open). This basically means at least
797 * one structure selected and no error messages.
799 * The 'Superpose Structures' option is enabled if either more than one
800 * structure is selected, or the 'Add' to existing view option is enabled, and
801 * disabled if the only option is to open a new view of a single structure.
804 protected void validateSelections()
806 FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
808 btn_add.setEnabled(false);
809 String currentView = selectedFilterOpt.getView();
810 int selectedCount = 0;
811 if (currentView == VIEWS_FILTER)
813 selectedCount = getResultTable().getSelectedRows().length;
814 if (selectedCount > 0)
816 btn_add.setEnabled(true);
819 else if (currentView == VIEWS_LOCAL_PDB)
821 selectedCount = tbl_local_pdb.getSelectedRows().length;
822 if (selectedCount > 0)
824 btn_add.setEnabled(true);
827 else if (currentView == VIEWS_ENTER_ID)
829 validateAssociationEnterPdb();
831 else if (currentView == VIEWS_FROM_FILE)
833 validateAssociationFromFile();
836 btn_newView.setEnabled(btn_add.isEnabled());
839 * enable 'Superpose' option if more than one structure is selected,
840 * or there are view(s) available to add structure(s) to
843 .setEnabled(selectedCount > 1 || targetView.getItemCount() > 0);
847 protected boolean showPopupFor(int selectedRow, int x, int y)
849 FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
851 String currentView = selectedFilterOpt.getView();
853 if (currentView == VIEWS_FILTER
854 && data instanceof ThreeDBStructureChooserQuerySource)
857 TDB_FTSData row = ((ThreeDBStructureChooserQuerySource) data)
858 .getFTSDataFor(getResultTable(), selectedRow,
859 discoveredStructuresSet);
860 String pageUrl = row.getModelViewUrl();
861 JPopupMenu popup = new JPopupMenu("3D Beacons");
862 JMenuItem viewUrl = new JMenuItem("View model web page");
863 viewUrl.addActionListener(new ActionListener()
866 public void actionPerformed(ActionEvent e)
868 Desktop.showUrl(pageUrl);
872 SwingUtilities.invokeLater(new Runnable()
877 popup.show(getResultTable(), x, y);
882 // event not handled by us
887 * Validates inputs from the Manual PDB entry panel
889 protected void validateAssociationEnterPdb()
891 AssociateSeqOptions assSeqOpt = (AssociateSeqOptions) idInputAssSeqPanel
892 .getCmb_assSeq().getSelectedItem();
893 lbl_pdbManualFetchStatus.setIcon(errorImage);
894 lbl_pdbManualFetchStatus.setToolTipText("");
895 if (txt_search.getText().length() > 0)
897 lbl_pdbManualFetchStatus.setToolTipText(JvSwingUtils.wrapTooltip(true,
898 MessageManager.formatMessage("info.no_pdb_entry_found_for",
899 txt_search.getText())));
902 if (errorWarning.length() > 0)
904 lbl_pdbManualFetchStatus.setIcon(warningImage);
905 lbl_pdbManualFetchStatus.setToolTipText(
906 JvSwingUtils.wrapTooltip(true, errorWarning.toString()));
909 if (selectedSequences.length == 1 || !assSeqOpt.getName()
910 .equalsIgnoreCase("-Select Associated Seq-"))
912 txt_search.setEnabled(true);
915 btn_add.setEnabled(true);
916 lbl_pdbManualFetchStatus.setToolTipText("");
917 lbl_pdbManualFetchStatus.setIcon(goodImage);
922 txt_search.setEnabled(false);
923 lbl_pdbManualFetchStatus.setIcon(errorImage);
928 * Validates inputs for the manual PDB file selection options
930 protected void validateAssociationFromFile()
932 AssociateSeqOptions assSeqOpt = (AssociateSeqOptions) fileChooserAssSeqPanel
933 .getCmb_assSeq().getSelectedItem();
934 lbl_fromFileStatus.setIcon(errorImage);
935 if (selectedSequences.length == 1 || (assSeqOpt != null && !assSeqOpt
936 .getName().equalsIgnoreCase("-Select Associated Seq-")))
938 btn_pdbFromFile.setEnabled(true);
939 if (selectedPdbFileName != null && selectedPdbFileName.length() > 0)
941 btn_add.setEnabled(true);
942 lbl_fromFileStatus.setIcon(goodImage);
947 btn_pdbFromFile.setEnabled(false);
948 lbl_fromFileStatus.setIcon(errorImage);
953 protected void cmbAssSeqStateChanged()
955 validateSelections();
958 private FilterOption lastSelected = null;
961 * Handles the state change event for the 'filter' combo-box and 'invert'
965 protected void stateChanged(ItemEvent e)
967 if (e.getSource() instanceof JCheckBox)
973 if (e.getStateChange() == ItemEvent.SELECTED)
982 * select structures for viewing by their PDB IDs
985 * @return true if structures were found and marked as selected
987 public boolean selectStructure(String... pdbids)
989 boolean found = false;
991 FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
993 String currentView = selectedFilterOpt.getView();
994 JTable restable = (currentView == VIEWS_FILTER) ? getResultTable()
995 : (currentView == VIEWS_LOCAL_PDB) ? tbl_local_pdb : null;
997 if (restable == null)
999 // can't select (enter PDB ID, or load file - need to also select which
1000 // sequence to associate with)
1004 int pdbIdColIndex = restable.getColumn("PDB Id").getModelIndex();
1005 for (int r = 0; r < restable.getRowCount(); r++)
1007 for (int p = 0; p < pdbids.length; p++)
1009 if (String.valueOf(restable.getValueAt(r, pdbIdColIndex))
1010 .equalsIgnoreCase(pdbids[p]))
1012 restable.setRowSelectionInterval(r, r);
1021 * Handles the 'New View' action
1024 protected void newView_ActionPerformed()
1026 targetView.setSelectedItem(null);
1027 showStructures(false);
1031 * Handles the 'Add to existing viewer' action
1034 protected void add_ActionPerformed()
1036 showStructures(false);
1040 * structure viewer opened by this dialog, or null
1042 private StructureViewer sViewer = null;
1044 public void showStructures(boolean waitUntilFinished)
1047 final StructureSelectionManager ssm = ap.getStructureSelectionManager();
1049 final int preferredHeight = pnl_filter.getHeight();
1051 Runnable viewStruc = new Runnable()
1056 FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
1057 .getSelectedItem());
1058 String currentView = selectedFilterOpt.getView();
1059 JTable restable = (currentView == VIEWS_FILTER) ? getResultTable()
1062 if (currentView == VIEWS_FILTER)
1064 int[] selectedRows = restable.getSelectedRows();
1065 PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
1066 List<SequenceI> selectedSeqsToView = new ArrayList<>();
1067 pdbEntriesToView = data.collectSelectedRows(restable,
1068 selectedRows, selectedSeqsToView);
1070 SequenceI[] selectedSeqs = selectedSeqsToView
1071 .toArray(new SequenceI[selectedSeqsToView.size()]);
1072 sViewer = launchStructureViewer(ssm, pdbEntriesToView, ap,
1075 else if (currentView == VIEWS_LOCAL_PDB)
1077 int[] selectedRows = tbl_local_pdb.getSelectedRows();
1078 PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
1080 int pdbIdColIndex = tbl_local_pdb.getColumn("PDB Id")
1082 int refSeqColIndex = tbl_local_pdb.getColumn("Ref Sequence")
1084 List<SequenceI> selectedSeqsToView = new ArrayList<>();
1085 for (int row : selectedRows)
1087 PDBEntry pdbEntry = ((PDBEntryTableModel) tbl_local_pdb
1088 .getModel()).getPDBEntryAt(row).getPdbEntry();
1090 pdbEntriesToView[count++] = pdbEntry;
1091 SequenceI selectedSeq = (SequenceI) tbl_local_pdb
1092 .getValueAt(row, refSeqColIndex);
1093 selectedSeqsToView.add(selectedSeq);
1095 SequenceI[] selectedSeqs = selectedSeqsToView
1096 .toArray(new SequenceI[selectedSeqsToView.size()]);
1097 sViewer = launchStructureViewer(ssm, pdbEntriesToView, ap,
1100 else if (currentView == VIEWS_ENTER_ID)
1102 SequenceI userSelectedSeq = ((AssociateSeqOptions) idInputAssSeqPanel
1103 .getCmb_assSeq().getSelectedItem()).getSequence();
1104 if (userSelectedSeq != null)
1106 selectedSequence = userSelectedSeq;
1108 String pdbIdStr = txt_search.getText();
1109 PDBEntry pdbEntry = selectedSequence.getPDBEntry(pdbIdStr);
1110 if (pdbEntry == null)
1112 pdbEntry = new PDBEntry();
1113 if (pdbIdStr.split(":").length > 1)
1115 pdbEntry.setId(pdbIdStr.split(":")[0]);
1116 pdbEntry.setChainCode(
1117 pdbIdStr.split(":")[1].toUpperCase(Locale.ROOT));
1121 pdbEntry.setId(pdbIdStr);
1123 pdbEntry.setType(PDBEntry.Type.PDB);
1124 selectedSequence.getDatasetSequence().addPDBId(pdbEntry);
1127 PDBEntry[] pdbEntriesToView = new PDBEntry[] { pdbEntry };
1128 sViewer = launchStructureViewer(ssm, pdbEntriesToView, ap,
1130 { selectedSequence });
1132 else if (currentView == VIEWS_FROM_FILE)
1134 SequenceI userSelectedSeq = ((AssociateSeqOptions) fileChooserAssSeqPanel
1135 .getCmb_assSeq().getSelectedItem()).getSequence();
1136 if (userSelectedSeq != null)
1138 selectedSequence = userSelectedSeq;
1140 PDBEntry fileEntry = new AssociatePdbFileWithSeq()
1141 .associatePdbWithSeq(selectedPdbFileName,
1142 DataSourceType.FILE, selectedSequence, true,
1145 sViewer = launchStructureViewer(ssm, new PDBEntry[] { fileEntry },
1147 { selectedSequence });
1149 SwingUtilities.invokeLater(new Runnable()
1154 closeAction(preferredHeight);
1155 mainFrame.dispose();
1160 Thread runner = new Thread(viewStruc);
1162 if (waitUntilFinished)
1164 while (sViewer == null ? runner.isAlive()
1165 : (sViewer.sview == null ? true
1166 : !sViewer.sview.hasMapping()))
1171 } catch (InterruptedException ie)
1180 * Answers a structure viewer (new or existing) configured to superimpose
1181 * added structures or not according to the user's choice
1186 StructureViewer getTargetedStructureViewer(StructureSelectionManager ssm)
1188 Object sv = targetView.getSelectedItem();
1190 return sv == null ? new StructureViewer(ssm) : (StructureViewer) sv;
1194 * Adds PDB structures to a new or existing structure viewer
1197 * @param pdbEntriesToView
1202 private StructureViewer launchStructureViewer(
1203 StructureSelectionManager ssm, final PDBEntry[] pdbEntriesToView,
1204 final AlignmentPanel alignPanel, SequenceI[] sequences)
1206 long progressId = sequences.hashCode();
1207 setProgressBar(MessageManager
1208 .getString("status.launching_3d_structure_viewer"), progressId);
1209 final StructureViewer theViewer = getTargetedStructureViewer(ssm);
1210 boolean superimpose = chk_superpose.isSelected();
1211 theViewer.setSuperpose(superimpose);
1214 * remember user's choice of superimpose or not
1216 Cache.setProperty(AUTOSUPERIMPOSE,
1217 Boolean.valueOf(superimpose).toString());
1219 setProgressBar(null, progressId);
1220 if (SiftsSettings.isMapWithSifts())
1222 List<SequenceI> seqsWithoutSourceDBRef = new ArrayList<>();
1224 // TODO: skip PDBEntry:Sequence pairs where PDBEntry doesn't look like a
1225 // real PDB ID. For moment, we can also safely do this if there is already
1226 // a known mapping between the PDBEntry and the sequence.
1227 for (SequenceI seq : sequences)
1229 PDBEntry pdbe = pdbEntriesToView[p++];
1230 if (pdbe != null && pdbe.getFile() != null)
1232 StructureMapping[] smm = ssm.getMapping(pdbe.getFile());
1233 if (smm != null && smm.length > 0)
1235 for (StructureMapping sm : smm)
1237 if (sm.getSequence() == seq)
1244 if (seq.getPrimaryDBRefs().isEmpty())
1246 seqsWithoutSourceDBRef.add(seq);
1250 if (!seqsWithoutSourceDBRef.isEmpty())
1252 int y = seqsWithoutSourceDBRef.size();
1253 setProgressBar(MessageManager.formatMessage(
1254 "status.fetching_dbrefs_for_sequences_without_valid_refs",
1256 SequenceI[] seqWithoutSrcDBRef = seqsWithoutSourceDBRef
1257 .toArray(new SequenceI[y]);
1258 DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef);
1259 dbRefFetcher.fetchDBRefs(true);
1261 setProgressBar("Fetch complete.", progressId); // todo i18n
1264 if (pdbEntriesToView.length > 1)
1267 MessageManager.getString(
1268 "status.fetching_3d_structures_for_selected_entries"),
1270 theViewer.viewStructures(pdbEntriesToView, sequences, alignPanel);
1274 setProgressBar(MessageManager.formatMessage(
1275 "status.fetching_3d_structures_for",
1276 pdbEntriesToView[0].getId()), progressId);
1277 theViewer.viewStructures(pdbEntriesToView[0], sequences, alignPanel);
1279 setProgressBar(null, progressId);
1280 // remember the last viewer we used...
1281 lastTargetedView = theViewer;
1286 * Populates the combo-box used in associating manually fetched structures to
1287 * a unique sequence when more than one sequence selection is made.
1290 protected void populateCmbAssociateSeqOptions(
1291 JComboBox<AssociateSeqOptions> cmb_assSeq,
1292 JLabel lbl_associateSeq)
1294 cmb_assSeq.removeAllItems();
1296 new AssociateSeqOptions("-Select Associated Seq-", null));
1297 lbl_associateSeq.setVisible(false);
1298 if (selectedSequences.length > 1)
1300 for (SequenceI seq : selectedSequences)
1302 cmb_assSeq.addItem(new AssociateSeqOptions(seq));
1307 String seqName = selectedSequence.getDisplayId(false);
1308 seqName = seqName.length() <= 40 ? seqName : seqName.substring(0, 39);
1309 lbl_associateSeq.setText(seqName);
1310 lbl_associateSeq.setVisible(true);
1311 cmb_assSeq.setVisible(false);
1315 protected boolean isStructuresDiscovered()
1317 return discoveredStructuresSet != null
1318 && !discoveredStructuresSet.isEmpty();
1321 protected int PDB_ID_MIN = 3;// or: (Jalview.isJS() ? 3 : 1); // Bob proposes
1323 // Doing a search for "1" or "1c" is valuable?
1324 // Those work but are enormously slow.
1327 protected void txt_search_ActionPerformed()
1329 String text = txt_search.getText().trim();
1330 if (text.length() >= PDB_ID_MIN)
1337 errorWarning.setLength(0);
1338 isValidPBDEntry = false;
1339 if (text.length() > 0)
1341 // TODO move this pdb id search into the PDB specific
1343 // for moment, it will work fine as is because it is self-contained
1344 String searchTerm = text.toLowerCase(Locale.ROOT);
1345 searchTerm = searchTerm.split(":")[0];
1346 // System.out.println(">>>>> search term : " + searchTerm);
1347 List<FTSDataColumnI> wantedFields = new ArrayList<>();
1348 FTSRestRequest pdbRequest = new FTSRestRequest();
1349 pdbRequest.setAllowEmptySeq(false);
1350 pdbRequest.setResponseSize(1);
1351 pdbRequest.setFieldToSearchBy("(pdb_id:");
1352 pdbRequest.setWantedFields(wantedFields);
1353 pdbRequest.setSearchTerm(searchTerm + ")");
1354 pdbRequest.setAssociatedSequence(selectedSequence);
1355 FTSRestClientI pdbRestClient = PDBFTSRestClient.getInstance();
1356 wantedFields.add(pdbRestClient.getPrimaryKeyColumn());
1357 FTSRestResponse resultList;
1360 resultList = pdbRestClient.executeRequest(pdbRequest);
1361 } catch (Exception e)
1363 errorWarning.append(e.getMessage());
1367 validateSelections();
1369 if (resultList.getSearchSummary() != null
1370 && resultList.getSearchSummary().size() > 0)
1372 isValidPBDEntry = true;
1375 validateSelections();
1381 protected void tabRefresh()
1383 if (selectedSequences != null)
1385 lbl_loading.setVisible(true);
1386 Thread refreshThread = new Thread(new Runnable()
1391 fetchStructuresMetaData();
1392 // populateFilterComboBox(true, cachedPDBExists);
1395 ((FilterOption) cmb_filterOption.getSelectedItem())
1397 lbl_loading.setVisible(false);
1400 refreshThread.start();
1404 public class PDBEntryTableModel extends AbstractTableModel
1406 String[] columns = { "Ref Sequence", "PDB Id", "Chain", "Type",
1409 private List<CachedPDB> pdbEntries;
1411 public PDBEntryTableModel(List<CachedPDB> pdbEntries)
1413 this.pdbEntries = new ArrayList<>(pdbEntries);
1417 public String getColumnName(int columnIndex)
1419 return columns[columnIndex];
1423 public int getRowCount()
1425 return pdbEntries.size();
1429 public int getColumnCount()
1431 return columns.length;
1435 public boolean isCellEditable(int row, int column)
1441 public Object getValueAt(int rowIndex, int columnIndex)
1443 Object value = "??";
1444 CachedPDB entry = pdbEntries.get(rowIndex);
1445 switch (columnIndex)
1448 value = entry.getSequence();
1451 value = entry.getQualifiedId();
1454 value = entry.getPdbEntry().getChainCode() == null ? "_"
1455 : entry.getPdbEntry().getChainCode();
1458 value = entry.getPdbEntry().getType();
1461 value = entry.getPdbEntry().getFile();
1468 public Class<?> getColumnClass(int columnIndex)
1470 return columnIndex == 0 ? SequenceI.class : PDBEntry.class;
1473 public CachedPDB getPDBEntryAt(int row)
1475 return pdbEntries.get(row);
1480 private class CachedPDB
1482 private SequenceI sequence;
1484 private PDBEntry pdbEntry;
1486 public CachedPDB(SequenceI sequence, PDBEntry pdbEntry)
1488 this.sequence = sequence;
1489 this.pdbEntry = pdbEntry;
1492 public String getQualifiedId()
1494 if (pdbEntry.hasProvider())
1496 return pdbEntry.getProvider() + ":" + pdbEntry.getId();
1498 return pdbEntry.toString();
1501 public SequenceI getSequence()
1506 public PDBEntry getPdbEntry()
1513 private IProgressIndicator progressBar;
1516 public void setProgressBar(String message, long id)
1518 progressBar.setProgressBar(message, id);
1522 public void registerHandler(long id, IProgressIndicatorHandler handler)
1524 progressBar.registerHandler(id, handler);
1528 public boolean operationInProgress()
1530 return progressBar.operationInProgress();
1533 public JalviewStructureDisplayI getOpenedStructureViewer()
1535 return sViewer == null ? null : sViewer.sview;
1539 protected void setFTSDocFieldPrefs(FTSDataColumnPreferences newPrefs)
1541 data.setDocFieldPrefs(newPrefs);
1547 * @return true when all initialisation threads have finished and dialog is
1550 public boolean isDialogVisible()
1552 return mainFrame != null && data != null && cmb_filterOption != null
1553 && mainFrame.isVisible()
1554 && cmb_filterOption.getSelectedItem() != null;
1559 * @return true if the 3D-Beacons query button will/has been displayed
1561 public boolean isCanQueryTDB()
1566 public boolean isNotQueriedTDBYet()
1568 return notQueriedTDBYet;