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.
23 import jalview.api.FeatureRenderer;
24 import jalview.bin.Cache;
25 import jalview.datamodel.AlignmentI;
26 import jalview.datamodel.PDBEntry;
27 import jalview.datamodel.SequenceI;
28 import jalview.ext.rbvi.chimera.ChimeraCommands;
29 import jalview.ext.rbvi.chimera.JalviewChimeraBinding;
30 import jalview.gui.StructureViewer.ViewerType;
31 import jalview.io.DataSourceType;
32 import jalview.io.StructureFile;
33 import jalview.structures.models.AAStructureBindingModel;
34 import jalview.util.BrowserLauncher;
35 import jalview.util.MessageManager;
36 import jalview.util.Platform;
37 import jalview.ws.dbsources.Pdb;
39 import java.awt.event.ActionEvent;
40 import java.awt.event.ActionListener;
41 import java.awt.event.MouseAdapter;
42 import java.awt.event.MouseEvent;
44 import java.io.FileInputStream;
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.util.ArrayList;
48 import java.util.Collections;
49 import java.util.List;
50 import java.util.Random;
52 import javax.swing.JCheckBoxMenuItem;
53 import javax.swing.JInternalFrame;
54 import javax.swing.JMenu;
55 import javax.swing.JMenuItem;
56 import javax.swing.event.InternalFrameAdapter;
57 import javax.swing.event.InternalFrameEvent;
60 * GUI elements for handling an external chimera display
65 public class ChimeraViewFrame extends StructureViewerBase
67 private JalviewChimeraBinding jmb;
69 private IProgressIndicator progressBar = null;
72 * Path to Chimera session file. This is set when an open Jalview/Chimera
73 * session is saved, or on restore from a Jalview project (if it holds the
74 * filename of any saved Chimera sessions).
76 private String chimeraSessionFile = null;
78 private Random random = new Random();
80 private int myWidth = 500;
82 private int myHeight = 150;
85 * Initialise menu options.
88 protected void initMenus()
92 viewerActionMenu.setText(MessageManager.getString("label.chimera"));
95 .setText(MessageManager.getString("label.colour_with_chimera"));
96 viewerColour.setToolTipText(MessageManager
97 .getString("label.let_chimera_manage_structure_colours"));
99 helpItem.setText(MessageManager.getString("label.chimera_help"));
100 savemenu.setVisible(false); // not yet implemented
101 viewMenu.add(fitToWindow);
104 * exchange of Jalview features and Chimera attributes is for now
105 * an optionally enabled experimental feature
107 if (Desktop.instance.showExperimental())
109 JMenuItem writeFeatures = new JMenuItem(
110 MessageManager.getString("label.create_chimera_attributes"));
111 writeFeatures.setToolTipText(MessageManager
112 .getString("label.create_chimera_attributes_tip"));
113 writeFeatures.addActionListener(new ActionListener()
116 public void actionPerformed(ActionEvent e)
118 sendFeaturesToChimera();
121 viewerActionMenu.add(writeFeatures);
123 final JMenu fetchAttributes = new JMenu(
124 MessageManager.getString("label.fetch_chimera_attributes"));
125 fetchAttributes.setToolTipText(MessageManager
126 .getString("label.fetch_chimera_attributes_tip"));
127 fetchAttributes.addMouseListener(new MouseAdapter()
131 public void mouseEntered(MouseEvent e)
133 buildAttributesMenu(fetchAttributes);
136 viewerActionMenu.add(fetchAttributes);
141 * Query Chimera for its residue attribute names and add them as items off the
144 * @param attributesMenu
146 protected void buildAttributesMenu(JMenu attributesMenu)
148 List<String> atts = jmb.sendChimeraCommand("list resattr", true);
153 attributesMenu.removeAll();
154 Collections.sort(atts);
155 for (String att : atts)
157 final String attName = att.split(" ")[1];
160 * ignore 'jv_*' attributes, as these are Jalview features that have
161 * been transferred to residue attributes in Chimera!
163 if (!attName.startsWith(ChimeraCommands.NAMESPACE_PREFIX))
165 JMenuItem menuItem = new JMenuItem(attName);
166 menuItem.addActionListener(new ActionListener()
169 public void actionPerformed(ActionEvent e)
171 getChimeraAttributes(attName);
174 attributesMenu.add(menuItem);
180 * Read residues in Chimera with the given attribute name, and set as features
181 * on the corresponding sequence positions (if any)
185 protected void getChimeraAttributes(String attName)
187 jmb.copyStructureAttributesToFeatures(attName, getAlignmentPanel());
191 * Send a command to Chimera to create residue attributes for Jalview features
193 * The syntax is: setattr r <attName> <attValue> <atomSpec>
195 * For example: setattr r jv:chain "Ferredoxin-1, Chloroplastic" #0:94.A
197 protected void sendFeaturesToChimera()
199 int count = jmb.sendFeaturesToViewer(getAlignmentPanel());
201 MessageManager.formatMessage("label.attributes_set", count));
205 * add a single PDB structure to a new or existing Chimera view
212 public ChimeraViewFrame(PDBEntry pdbentry, SequenceI[] seq,
213 String[] chains, final AlignmentPanel ap)
216 String pdbId = pdbentry.getId();
219 * If the PDB file is already loaded, the user may just choose to add to an
220 * existing viewer (or cancel)
222 if (addAlreadyLoadedFile(seq, chains, ap, pdbId))
228 * Check if there are other Chimera views involving this alignment and give
229 * user the option to add and align this molecule to one of them (or cancel)
231 if (addToExistingViewer(pdbentry, seq, chains, ap, pdbId))
237 * If the options above are declined or do not apply, show the structure in
240 openNewChimera(ap, new PDBEntry[] { pdbentry },
246 * Create a helper to manage progress bar display
248 protected void createProgressBar()
250 if (progressBar == null)
252 progressBar = new ProgressBar(statusPanel, statusBar);
256 private void openNewChimera(AlignmentPanel ap, PDBEntry[] pdbentrys,
260 jmb = new JalviewChimeraBindingModel(this,
261 ap.getStructureSelectionManager(), pdbentrys, seqs, null);
262 addAlignmentPanel(ap);
263 useAlignmentPanelForColourbyseq(ap);
265 if (pdbentrys.length > 1)
267 alignAddedStructures = true;
268 useAlignmentPanelForSuperposition(ap);
270 jmb.setColourBySequence(true);
271 setSize(myWidth, myHeight);
274 addingStructures = false;
275 worker = new Thread(this);
278 this.addInternalFrameListener(new InternalFrameAdapter()
281 public void internalFrameClosing(
282 InternalFrameEvent internalFrameEvent)
291 * Create a new viewer from saved session state data including Chimera session
294 * @param chimeraSessionFile
298 * @param colourByChimera
299 * @param colourBySequence
302 public ChimeraViewFrame(String chimeraSessionFile,
303 AlignmentPanel alignPanel, PDBEntry[] pdbArray,
304 SequenceI[][] seqsArray, boolean colourByChimera,
305 boolean colourBySequence, String newViewId)
308 setViewId(newViewId);
309 this.chimeraSessionFile = chimeraSessionFile;
310 openNewChimera(alignPanel, pdbArray, seqsArray);
313 jmb.setColourBySequence(false);
314 seqColour.setSelected(false);
315 viewerColour.setSelected(true);
317 else if (colourBySequence)
319 jmb.setColourBySequence(true);
320 seqColour.setSelected(true);
321 viewerColour.setSelected(false);
326 * create a new viewer containing several structures superimposed using the
333 public ChimeraViewFrame(PDBEntry[] pe, SequenceI[][] seqs,
337 openNewChimera(ap, pe, seqs);
341 * Default constructor
343 public ChimeraViewFrame()
348 * closeViewer will decide whether or not to close this frame
349 * depending on whether user chooses to Cancel or not
351 setDefaultCloseOperation(JInternalFrame.DO_NOTHING_ON_CLOSE);
355 * Returns a list of any Chimera viewers in the desktop. The list is
356 * restricted to those linked to the given alignment panel if it is not null.
359 protected List<StructureViewerBase> getViewersFor(AlignmentPanel ap)
361 List<StructureViewerBase> result = new ArrayList<>();
362 JInternalFrame[] frames = Desktop.instance.getAllFrames();
364 for (JInternalFrame frame : frames)
366 if (frame instanceof ChimeraViewFrame)
368 if (ap == null || ((StructureViewerBase) frame).isLinkedWith(ap))
370 result.add((StructureViewerBase) frame);
378 * Launch Chimera. If we have a chimera session file name, send Chimera the
379 * command to open its saved session file.
383 jmb.setFinishedInit(false);
384 Desktop.addInternalFrame(this,
385 jmb.getViewerTitle(getViewerName(), true), getBounds().width,
388 if (!jmb.launchChimera())
390 JvOptionPane.showMessageDialog(Desktop.desktop,
391 MessageManager.getString("label.chimera_failed"),
392 MessageManager.getString("label.error_loading_file"),
393 JvOptionPane.ERROR_MESSAGE);
398 if (this.chimeraSessionFile != null)
400 boolean opened = jmb.openSession(chimeraSessionFile);
403 System.err.println("An error occurred opening Chimera session file "
404 + chimeraSessionFile);
408 jmb.startChimeraListener();
412 * Show only the selected chain(s) in the viewer
415 void showSelectedChains()
417 List<String> toshow = new ArrayList<>();
418 for (int i = 0; i < chainMenu.getItemCount(); i++)
420 if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
422 JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
423 if (item.isSelected())
425 toshow.add(item.getText());
429 jmb.showChains(toshow);
433 * Close down this instance of Jalview's Chimera viewer, giving the user the
434 * option to close the associated Chimera window (process). They may wish to
435 * keep it open until they have had an opportunity to save any work.
437 * @param closeChimera
438 * if true, close any linked Chimera process; if false, prompt first
441 public void closeViewer(boolean closeChimera)
443 if (jmb != null && jmb.isChimeraRunning())
447 String prompt = MessageManager
448 .formatMessage("label.confirm_close_chimera", new Object[]
449 { jmb.getViewerTitle(getViewerName(), false) });
450 prompt = JvSwingUtils.wrapTooltip(true, prompt);
451 int confirm = JvOptionPane.showConfirmDialog(this, prompt,
452 MessageManager.getString("label.close_viewer"),
453 JvOptionPane.YES_NO_CANCEL_OPTION);
455 * abort closure if user hits escape or Cancel
457 if (confirm == JvOptionPane.CANCEL_OPTION
458 || confirm == JvOptionPane.CLOSED_OPTION)
462 closeChimera = confirm == JvOptionPane.YES_OPTION;
464 jmb.closeViewer(closeChimera);
466 setAlignmentPanel(null);
470 // TODO: check for memory leaks where instance isn't finalised because jmb
471 // holds a reference to the window
477 * Open any newly added PDB structures in Chimera, having first fetched data
478 * from PDB (if not already saved).
484 // todo - record which pdbids were successfully imported.
485 StringBuilder errormsgs = new StringBuilder(128);
486 StringBuilder files = new StringBuilder(128);
487 List<PDBEntry> filePDB = new ArrayList<>();
488 List<Integer> filePDBpos = new ArrayList<>();
489 PDBEntry thePdbEntry = null;
490 StructureFile pdb = null;
493 String[] curfiles = jmb.getStructureFiles(); // files currently in viewer
494 // TODO: replace with reference fetching/transfer code (validate PDBentry
496 for (int pi = 0; pi < jmb.getPdbCount(); pi++)
499 thePdbEntry = jmb.getPdbEntry(pi);
500 if (thePdbEntry.getFile() == null)
503 * Retrieve PDB data, save to file, attach to PDBEntry
505 file = fetchPdbFile(thePdbEntry);
508 errormsgs.append("'" + thePdbEntry.getId() + "' ");
514 * Got file already - ignore if already loaded in Chimera.
516 file = new File(thePdbEntry.getFile()).getAbsoluteFile()
518 if (curfiles != null && curfiles.length > 0)
520 addingStructures = true; // already files loaded.
521 for (int c = 0; c < curfiles.length; c++)
523 if (curfiles[c].equals(file))
533 filePDB.add(thePdbEntry);
534 filePDBpos.add(Integer.valueOf(pi));
535 files.append(" \"" + Platform.escapeString(file) + "\"");
538 } catch (OutOfMemoryError oomerror)
540 new OOMWarning("Retrieving PDB files: " + thePdbEntry.getId(),
542 } catch (Exception ex)
544 ex.printStackTrace();
546 "When retrieving pdbfiles for '" + thePdbEntry.getId() + "'");
548 if (errormsgs.length() > 0)
551 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
552 MessageManager.formatMessage(
553 "label.pdb_entries_couldnt_be_retrieved", new Object[]
554 { errormsgs.toString() }),
555 MessageManager.getString("label.couldnt_load_file"),
556 JvOptionPane.ERROR_MESSAGE);
559 if (files.length() > 0)
561 jmb.setFinishedInit(false);
562 if (!addingStructures)
567 } catch (Exception ex)
569 Cache.log.error("Couldn't open Chimera viewer!", ex);
573 for (PDBEntry pe : filePDB)
576 if (pe.getFile() != null)
580 int pos = filePDBpos.get(num).intValue();
581 long startTime = startProgressBar(getViewerName() + " "
582 + MessageManager.getString("status.opening_file_for")
585 jmb.addSequence(pos, jmb.getSequence()[pos]);
586 File fl = new File(pe.getFile());
587 DataSourceType protocol = DataSourceType.URL;
592 protocol = DataSourceType.FILE;
594 } catch (Throwable e)
598 stopProgressBar("", startTime);
600 // Explicitly map to the filename used by Chimera ;
602 pdb = jmb.getSsm().setMapping(jmb.getSequence()[pos],
603 jmb.getChains()[pos], pe.getFile(), protocol,
605 stashFoundChains(pdb, pe.getFile());
607 } catch (OutOfMemoryError oomerror)
610 "When trying to open and map structures from Chimera!",
612 } catch (Exception ex)
615 "Couldn't open " + pe.getFile() + " in Chimera viewer!",
619 Cache.log.debug("File locations are " + files);
625 jmb.setFinishedInit(true);
626 jmb.setLoadingFromArchive(false);
629 * ensure that any newly discovered features (e.g. RESNUM)
630 * are added to any open feature settings dialog
632 FeatureRenderer fr = getBinding().getFeatureRenderer(null);
638 // refresh the sequence colours for the new structure(s)
639 for (AlignmentPanel ap : _colourwith)
641 jmb.updateColours(ap);
643 // do superposition if asked to
644 if (Cache.getDefault("AUTOSUPERIMPOSE", true) && alignAddedStructures)
646 new Thread(new Runnable()
651 alignStructs_withAllAlignPanels();
654 alignAddedStructures = false;
656 addingStructures = false;
663 * Fetch PDB data and save to a local file. Returns the full path to the file,
664 * or null if fetch fails. TODO: refactor to common with Jmol ? duplication
666 * @param processingEntry
671 private void stashFoundChains(StructureFile pdb, String file)
673 for (int i = 0; i < pdb.getChains().size(); i++)
675 String chid = new String(
676 pdb.getId() + ":" + pdb.getChains().elementAt(i).id);
677 jmb.getChainNames().add(chid);
678 jmb.getChainFile().put(chid, file);
682 private String fetchPdbFile(PDBEntry processingEntry) throws Exception
684 // FIXME: this is duplicated code with Jmol frame ?
685 String filePath = null;
686 Pdb pdbclient = new Pdb();
687 AlignmentI pdbseq = null;
688 String pdbid = processingEntry.getId();
689 long handle = System.currentTimeMillis()
690 + Thread.currentThread().hashCode();
693 * Write 'fetching PDB' progress on AlignFrame as we are not yet visible
695 String msg = MessageManager.formatMessage("status.fetching_pdb",
698 getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
699 // long hdl = startProgressBar(MessageManager.formatMessage(
700 // "status.fetching_pdb", new Object[]
704 pdbseq = pdbclient.getSequenceRecords(pdbid);
705 } catch (OutOfMemoryError oomerror)
707 new OOMWarning("Retrieving PDB id " + pdbid, oomerror);
710 msg = pdbid + " " + MessageManager.getString("label.state_completed");
711 getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
712 // stopProgressBar(msg, hdl);
715 * If PDB data were saved and are not invalid (empty alignment), return the
718 if (pdbseq != null && pdbseq.getHeight() > 0)
720 // just use the file name from the first sequence's first PDBEntry
721 filePath = new File(pdbseq.getSequenceAt(0).getAllPDBEntries()
722 .elementAt(0).getFile()).getAbsolutePath();
723 processingEntry.setFile(filePath);
729 * Convenience method to update the progress bar if there is one. Be sure to
730 * call stopProgressBar with the returned handle to remove the message.
735 public long startProgressBar(String msg)
737 // TODO would rather have startProgress/stopProgress as the
738 // IProgressIndicator interface
739 long tm = random.nextLong();
740 if (progressBar != null)
742 progressBar.setProgressBar(msg, tm);
748 * End the progress bar with the specified handle, leaving a message (if not
749 * null) on the status bar
754 public void stopProgressBar(String msg, long handle)
756 if (progressBar != null)
758 progressBar.setProgressBar(msg, handle);
763 public void eps_actionPerformed(ActionEvent e)
765 throw new Error(MessageManager
766 .getString("error.eps_generation_not_implemented"));
770 public void png_actionPerformed(ActionEvent e)
772 throw new Error(MessageManager
773 .getString("error.png_generation_not_implemented"));
777 public void showHelp_actionPerformed(ActionEvent actionEvent)
782 .openURL("https://www.cgl.ucsf.edu/chimera/docs/UsersGuide");
783 } catch (IOException ex)
789 public AAStructureBindingModel getBinding()
795 * Ask Chimera to save its session to the designated file path, or to a
796 * temporary file if the path is null. Returns the file path if successful,
802 protected String saveSession(String filepath)
804 String pathUsed = filepath;
807 if (pathUsed == null)
809 File tempFile = File.createTempFile("chimera", ".py");
810 tempFile.deleteOnExit();
811 pathUsed = tempFile.getPath();
813 boolean result = jmb.saveSession(pathUsed);
816 this.chimeraSessionFile = pathUsed;
819 } catch (IOException e)
826 * Returns a string representing the state of the Chimera session. This is
827 * done by requesting Chimera to save its session to a temporary file, then
828 * reading the file contents. Returns an empty string on any error.
831 public String getStateInfo()
833 String sessionFile = saveSession(null);
834 if (sessionFile == null)
838 InputStream is = null;
841 File f = new File(sessionFile);
842 byte[] bytes = new byte[(int) f.length()];
843 is = new FileInputStream(sessionFile);
845 return new String(bytes);
846 } catch (IOException e)
856 } catch (IOException e)
865 protected void fitToWindow_actionPerformed()
871 public ViewerType getViewerType()
873 return ViewerType.CHIMERA;
877 protected String getViewerName()
883 * Sends commands to align structures according to associated alignment(s).
888 protected String alignStructs_withAllAlignPanels()
890 String reply = super.alignStructs_withAllAlignPanels();
893 statusBar.setText("Superposition failed: " + reply);
899 protected IProgressIndicator getIProgressIndicator()