X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fgui%2FStructureViewerBase.java;h=bd757e8946173d36939a047136014a09a4d197c9;hb=HEAD;hp=0c5c5f0554dc74ed471d73664177ad62916993c5;hpb=1dd1e3cd8272449af3fe4dfa3f69239ca4ae471b;p=jalview.git diff --git a/src/jalview/gui/StructureViewerBase.java b/src/jalview/gui/StructureViewerBase.java index 0c5c5f0..bd757e8 100644 --- a/src/jalview/gui/StructureViewerBase.java +++ b/src/jalview/gui/StructureViewerBase.java @@ -26,6 +26,7 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; +import java.beans.PropertyVetoException; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; @@ -34,12 +35,12 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Random; import java.util.Vector; import javax.swing.ButtonGroup; import javax.swing.JCheckBoxMenuItem; -import javax.swing.JColorChooser; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JRadioButtonMenuItem; @@ -47,10 +48,13 @@ import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; import jalview.api.AlignmentViewPanel; +import jalview.api.structures.JalviewStructureDisplayI; import jalview.bin.Cache; +import jalview.bin.Console; import jalview.datamodel.AlignmentI; import jalview.datamodel.PDBEntry; import jalview.datamodel.SequenceI; +import jalview.gui.JalviewColourChooser.ColourChooserListener; import jalview.gui.StructureViewer.ViewerType; import jalview.gui.ViewSelectionMenu.ViewSetProvider; import jalview.io.DataSourceType; @@ -61,8 +65,11 @@ import jalview.schemes.ColourSchemeI; import jalview.schemes.ColourSchemes; import jalview.structure.StructureMapping; import jalview.structures.models.AAStructureBindingModel; +import jalview.util.BrowserLauncher; import jalview.util.MessageManager; +import jalview.ws.dbsources.EBIAlfaFold; import jalview.ws.dbsources.Pdb; +import jalview.ws.utils.UrlDownloadClient; /** * Base class with common functionality for JMol, Chimera or other structure @@ -83,6 +90,30 @@ public abstract class StructureViewerBase extends GStructureViewer } /** + * Singleton list of all (open) instances of structureViewerBase TODO: + * JAL-3362 - review and adopt the swingJS-safe singleton pattern so each + * structure viewer base instance is kept to its own JalviewJS parent + */ + private static List svbs = new ArrayList<>(); + + /** + * + * @return list with all existing StructureViewers instance + */ + public static List getAllStructureViewerBases() + { + List goodSvbs = new ArrayList<>(); + for (JalviewStructureDisplayI s : svbs) + { + if (s != null && !goodSvbs.contains(s)) + { + goodSvbs.add(s); + } + } + return goodSvbs; + } + + /** * list of sequenceSet ids associated with the view */ protected List _aps = new ArrayList<>(); @@ -112,6 +143,8 @@ public abstract class StructureViewerBase extends GStructureViewer protected boolean allChainsSelected = false; + protected boolean allHetatmBeingSelected = false; + protected JMenu viewSelectionMenu; /** @@ -131,6 +164,8 @@ public abstract class StructureViewerBase extends GStructureViewer public StructureViewerBase() { super(); + setFrameIcon(null); + svbs.add(this); } /** @@ -154,6 +189,17 @@ public abstract class StructureViewerBase extends GStructureViewer } /** + * called by the binding model to indicate when adding structures is happening + * or has been completed + * + * @param addingStructures + */ + public synchronized void setAddingStructures(boolean addingStructures) + { + this.addingStructures = addingStructures; + } + + /** * * @param ap2 * @return true if this Jmol instance is linked with the given alignPanel @@ -163,6 +209,7 @@ public abstract class StructureViewerBase extends GStructureViewer return _aps.contains(ap2.av.getSequenceSetId()); } + @Override public boolean isUsedforaligment(AlignmentViewPanel ap2) { @@ -209,6 +256,10 @@ public abstract class StructureViewerBase extends GStructureViewer _alignwith.add(ap); } ; + // TODO: refactor to allow concrete classes to register buttons for adding + // here + // currently have to override to add buttons back in after they are cleared + // in this loop for (Component c : viewerActionMenu.getMenuComponents()) { if (c != alignStructs) @@ -426,8 +477,9 @@ public abstract class StructureViewerBase extends GStructureViewer { return; } - AlignmentPanel alignPanel = (AlignmentPanel) apanel; // Implementation error if this - // cast fails + AlignmentPanel alignPanel = (AlignmentPanel) apanel; // Implementation error + // if this + // cast fails useAlignmentPanelForSuperposition(alignPanel); addStructure(pdbentry, seq, chains, alignPanel.alignFrame); } @@ -557,6 +609,88 @@ public abstract class StructureViewerBase extends GStructureViewer } } + void setHetatmMenuItems(Map hetatmNames) + { + hetatmMenu.removeAll(); + if (hetatmNames == null || hetatmNames.isEmpty()) + { + hetatmMenu.setVisible(false); + return; + } + hetatmMenu.setVisible(true); + allHetatmBeingSelected = false; + JMenuItem allMenuItem = new JMenuItem( + MessageManager.getString("label.all")); + JMenuItem noneMenuItem = new JMenuItem( + MessageManager.getString("label.none")); + allMenuItem.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + { + allHetatmBeingSelected = true; + // Toggle state of everything - on + for (int i = 0; i < hetatmMenu.getItemCount(); i++) + { + if (hetatmMenu.getItem(i) instanceof JCheckBoxMenuItem) + { + ((JCheckBoxMenuItem) hetatmMenu.getItem(i)).setSelected(true); + } + } + allHetatmBeingSelected = false; + showSelectedHetatms(); + } + } + }); + + noneMenuItem.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + { + allHetatmBeingSelected = true; + // Toggle state of everything off + for (int i = 0; i < hetatmMenu.getItemCount(); i++) + { + if (hetatmMenu.getItem(i) instanceof JCheckBoxMenuItem) + { + ((JCheckBoxMenuItem) hetatmMenu.getItem(i)) + .setSelected(false); + } + } + allHetatmBeingSelected = false; + showSelectedHetatms(); + } + } + }); + hetatmMenu.add(noneMenuItem); + hetatmMenu.add(allMenuItem); + + for (Map.Entry chain : hetatmNames.entrySet()) + { + JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(chain.getKey(), + false); + menuItem.setToolTipText(chain.getValue()); + menuItem.addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent evt) + { + if (!allHetatmBeingSelected) + { + // update viewer only when we were clicked, not programmatically + // checked/unchecked + showSelectedHetatms(); + } + } + }); + + hetatmMenu.add(menuItem); + } + } + /** * Action on selecting one of Jalview's registered colour schemes */ @@ -564,9 +698,8 @@ public abstract class StructureViewerBase extends GStructureViewer public void changeColour_actionPerformed(String colourSchemeName) { AlignmentI al = getAlignmentPanel().av.getAlignment(); - ColourSchemeI cs = ColourSchemes.getInstance() - .getColourScheme(colourSchemeName, getAlignmentPanel().av, al, - null); + ColourSchemeI cs = ColourSchemes.getInstance().getColourScheme( + colourSchemeName, getAlignmentPanel().av, al, null); getBinding().colourByJalviewColourScheme(cs); } @@ -677,7 +810,10 @@ public abstract class StructureViewerBase extends GStructureViewer }); viewerColour = new JRadioButtonMenuItem(); - // text is set in overrides of this method + viewerColour + .setText(MessageManager.getString("label.colour_with_viewer")); + viewerColour.setToolTipText(MessageManager + .getString("label.let_viewer_manage_structure_colours")); viewerColour.setName(ViewerColour.ByViewer.name()); viewerColour.setSelected(!binding.isColourBySequence()); @@ -703,7 +839,7 @@ public abstract class StructureViewerBase extends GStructureViewer } else { - // update the Chimera display now. + // update the viewer display now. seqColour_actionPerformed(); } } @@ -715,10 +851,18 @@ public abstract class StructureViewerBase extends GStructureViewer @Override public void itemStateChanged(ItemEvent e) { - alignStructs.setEnabled(!_alignwith.isEmpty()); - alignStructs.setToolTipText(MessageManager.formatMessage( - "label.align_structures_using_linked_alignment_views", - _alignwith.size())); + if (_alignwith.isEmpty()) + { + alignStructs.setEnabled(false); + alignStructs.setToolTipText(null); + } + else + { + alignStructs.setEnabled(true); + alignStructs.setToolTipText(MessageManager.formatMessage( + "label.align_structures_using_linked_alignment_views", + _alignwith.size())); + } } }; viewSelectionMenu = new ViewSelectionMenu( @@ -745,6 +889,10 @@ public abstract class StructureViewerBase extends GStructureViewer } }); + viewerActionMenu.setText(getViewerName()); + helpItem.setText(MessageManager.formatMessage("label.viewer_help", + getViewerName())); + buildColourMenu(); } @@ -783,22 +931,29 @@ public abstract class StructureViewerBase extends GStructureViewer { sp.append("'" + alignPanel.getViewName() + "' "); } - Cache.log.info("Couldn't align structures with the " + sp.toString() + Console.info("Couldn't align structures with the " + sp.toString() + "associated alignment panels.", e); } return reply; } + /** + * Opens a colour chooser dialog, and applies the chosen colour to the + * background of the structure viewer + */ @Override public void background_actionPerformed() { - Color col = JColorChooser.showDialog(this, - MessageManager.getString("label.select_background_colour"), - null); - if (col != null) + String ttl = MessageManager.getString("label.select_background_colour"); + ColourChooserListener listener = new ColourChooserListener() { - getBinding().setBackgroundColour(col); - } + @Override + public void colourSelected(Color c) + { + getBinding().setBackgroundColour(c); + } + }; + JalviewColourChooser.showColourChooser(this, ttl, null, listener); } @Override @@ -856,6 +1011,7 @@ public abstract class StructureViewerBase extends GStructureViewer @Override public void pdbFile_actionPerformed() { + // TODO: JAL-3048 not needed for Jalview-JS - save PDB file JalviewFileChooser chooser = new JalviewFileChooser( Cache.getProperty("LAST_DIRECTORY")); @@ -939,6 +1095,7 @@ public abstract class StructureViewerBase extends GStructureViewer return; } setChainMenuItems(binding.getChainNames()); + setHetatmMenuItems(binding.getHetatmNames()); this.setTitle(binding.getViewerTitle(getViewerName(), true)); @@ -946,7 +1103,7 @@ public abstract class StructureViewerBase extends GStructureViewer * enable 'Superpose with' if more than one mapped structure */ viewSelectionMenu.setEnabled(false); - if (getBinding().getStructureFiles().length > 1 + if (getBinding().getMappedStructureCount() > 1 && getBinding().getSequence().length > 1) { viewSelectionMenu.setEnabled(true); @@ -998,14 +1155,17 @@ public abstract class StructureViewerBase extends GStructureViewer { return false; } - int p=0; - for (String pdbid:pdbids) { + int p = 0; + for (String pdbid : pdbids) + { StructureMapping sm[] = getBinding().getSsm().getMapping(pdbid); - if (sm!=null && sm.length>0 && sm[0]!=null) { + if (sm != null && sm.length > 0 && sm[0] != null) + { p++; } } - // only return true if there is a mapping for every structure file we have loaded + // only return true if there is a mapping for every structure file we have + // loaded if (p == 0 || p != pdbids.length) { return false; @@ -1052,7 +1212,7 @@ public abstract class StructureViewerBase extends GStructureViewer progressBar = pi; } - protected void setProgressMessage(String message, long id) + public void setProgressMessage(String message, long id) { if (progressBar != null) { @@ -1087,6 +1247,26 @@ public abstract class StructureViewerBase extends GStructureViewer } /** + * Display selected hetatms in viewer + */ + protected void showSelectedHetatms() + { + List toshow = new ArrayList<>(); + for (int i = 0; i < hetatmMenu.getItemCount(); i++) + { + if (hetatmMenu.getItem(i) instanceof JCheckBoxMenuItem) + { + JCheckBoxMenuItem item = (JCheckBoxMenuItem) hetatmMenu.getItem(i); + if (item.isSelected()) + { + toshow.add(item.getText()); + } + } + } + getBinding().showHetatms(toshow); + } + + /** * Tries to fetch a PDB file and save to a temporary local file. Returns the * saved file path if successful, or null if not. * @@ -1097,11 +1277,12 @@ public abstract class StructureViewerBase extends GStructureViewer { String filePath = null; Pdb pdbclient = new Pdb(); + EBIAlfaFold afclient = new EBIAlfaFold(); AlignmentI pdbseq = null; String pdbid = processingEntry.getId(); long handle = System.currentTimeMillis() + Thread.currentThread().hashCode(); - + /* * Write 'fetching PDB' progress on AlignFrame as we are not yet visible */ @@ -1114,10 +1295,42 @@ public abstract class StructureViewerBase extends GStructureViewer // { pdbid })); try { - pdbseq = pdbclient.getSequenceRecords(pdbid); + if (afclient.isValidReference(pdbid)) + { + pdbseq = afclient.getSequenceRecords(pdbid, + processingEntry.getRetrievalUrl()); + } + else + { + if (processingEntry.hasRetrievalUrl()) + { + String safePDBId = java.net.URLEncoder.encode(pdbid, "UTF-8") + .replace("%", "__"); + + // retrieve from URL to new local tmpfile + File tmpFile = File.createTempFile(safePDBId, + "." + (PDBEntry.Type.MMCIF.toString().equals( + processingEntry.getType().toString()) ? "cif" + : "pdb")); + String fromUrl = processingEntry.getRetrievalUrl(); + UrlDownloadClient.download(fromUrl, tmpFile); + + // may not need this check ? + String file = tmpFile.getAbsolutePath(); + if (file != null) + { + pdbseq = EBIAlfaFold.importDownloadedStructureFromUrl(fromUrl, + tmpFile, pdbid, null, null, null); + } + } + else + { + pdbseq = pdbclient.getSequenceRecords(pdbid); + } + } } catch (Exception e) { - System.err.println( + jalview.bin.Console.errPrintln( "Error retrieving PDB id " + pdbid + ": " + e.getMessage()); } finally { @@ -1147,8 +1360,167 @@ public abstract class StructureViewerBase extends GStructureViewer */ public File saveSession() { - // TODO: a wait loop to ensure the file is written fully before returning? - return getBinding() == null ? null : getBinding().saveSession(); + if (getBinding() == null) + { + return null; + } + File session = getBinding().saveSession(); + long l = session.length(); + int wait = 50; + do + { + try + { + Thread.sleep(5); + } catch (InterruptedException e) + { + } + long nextl = session.length(); + if (nextl != l) + { + wait = 50; + l = nextl; + } + } while (--wait > 0); + return session; + } + + private static boolean quitClose = false; + + public static void setQuitClose(boolean b) + { + quitClose = b; + } + + @Override + public boolean stillRunning() + { + AAStructureBindingModel binding = getBinding(); + return binding != null && binding.isViewerRunning(); + } + + /** + * Close down this instance of Jalview's Chimera viewer, giving the user the + * option to close the associated Chimera window (process). They may wish to + * keep it open until they have had an opportunity to save any work. + * + * @param forceClose + * if true, close any linked Chimera process; if false, prompt first + */ + @Override + public void closeViewer(boolean forceClose) + { + AAStructureBindingModel binding = getBinding(); + if (stillRunning()) + { + if (!forceClose) + { + String viewerName = getViewerName(); + + int confirm = JvOptionPane.CANCEL_OPTION; + if (QuitHandler.quitting()) + { + // already asked about closing external windows + confirm = quitClose ? JvOptionPane.YES_OPTION + : JvOptionPane.NO_OPTION; + } + else + { + String prompt = MessageManager + .formatMessage("label.confirm_close_viewer", new Object[] + { binding.getViewerTitle(viewerName, false), + viewerName }); + prompt = JvSwingUtils.wrapTooltip(true, prompt); + String title = MessageManager.getString("label.close_viewer"); + confirm = showCloseDialog(title, prompt); + } + + /* + * abort closure if user hits escape or Cancel + */ + if (confirm == JvOptionPane.CANCEL_OPTION + || confirm == JvOptionPane.CLOSED_OPTION) + { + // abort possible quit handling if CANCEL chosen + if (confirm == JvOptionPane.CANCEL_OPTION) + { + try + { + // this is a bit futile + this.setClosed(false); + } catch (PropertyVetoException e) + { + } + QuitHandler.abortQuit(); + } + return; + } + forceClose = confirm == JvOptionPane.YES_OPTION; + } + } + if (binding != null) + { + binding.closeViewer(forceClose); + } + setAlignmentPanel(null); + _aps.clear(); + _alignwith.clear(); + _colourwith.clear(); + // TODO: check for memory leaks where instance isn't finalised because jmb + // holds a reference to the window + // jmb = null; + + try + { + svbs.remove(this); + } catch (Throwable t) + { + Console.info( + "Unexpected exception when deregistering structure viewer", + t); + } + dispose(); + } + + private int showCloseDialog(final String title, final String prompt) + { + int confirmResponse = JvOptionPane.CANCEL_OPTION; + confirmResponse = JvOptionPane.showConfirmDialog(this, prompt, + MessageManager.getString("label.close_viewer"), + JvOptionPane.YES_NO_CANCEL_OPTION, + JvOptionPane.WARNING_MESSAGE); + return confirmResponse; + } + + @Override + public void showHelp_actionPerformed() + { + /* + try + { + */ + String url = getBinding().getHelpURL(); + if (url != null) + { + BrowserLauncher.openURL(url); + } + /* + } + catch (IOException ex) + { + System.err + .println("Show " + getViewerName() + " failed with: " + + ex.getMessage()); + } + */ + } + + @Override + public boolean hasViewerActionsMenu() + { + return viewerActionMenu != null && viewerActionMenu.isEnabled() + && viewerActionMenu.getItemCount() > 0 + && viewerActionMenu.isVisible(); } }