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.ImageMaker.TYPE;
36 import jalview.util.MessageManager;
37 import jalview.util.Platform;
38 import jalview.ws.dbsources.Pdb;
40 import java.awt.event.ActionEvent;
41 import java.awt.event.ActionListener;
42 import java.awt.event.MouseAdapter;
43 import java.awt.event.MouseEvent;
45 import java.io.FileInputStream;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.util.ArrayList;
49 import java.util.Collections;
50 import java.util.List;
51 import java.util.Random;
53 import javax.swing.JCheckBoxMenuItem;
54 import javax.swing.JInternalFrame;
55 import javax.swing.JMenu;
56 import javax.swing.JMenuItem;
57 import javax.swing.event.InternalFrameAdapter;
58 import javax.swing.event.InternalFrameEvent;
61 * GUI elements for handling an external chimera display
66 public class ChimeraViewFrame extends StructureViewerBase
68 private JalviewChimeraBinding jmb;
70 private IProgressIndicator progressBar = null;
73 * Path to Chimera session file. This is set when an open Jalview/Chimera
74 * session is saved, or on restore from a Jalview project (if it holds the
75 * filename of any saved Chimera sessions).
77 private String chimeraSessionFile = null;
79 private Random random = new Random();
81 private int myWidth = 500;
83 private int myHeight = 150;
86 * Initialise menu options.
89 protected void initMenus()
93 viewerActionMenu.setText(MessageManager.getString("label.chimera"));
96 .setText(MessageManager.getString("label.colour_with_chimera"));
97 viewerColour.setToolTipText(MessageManager
98 .getString("label.let_chimera_manage_structure_colours"));
100 helpItem.setText(MessageManager.getString("label.chimera_help"));
101 savemenu.setVisible(false); // not yet implemented
102 viewMenu.add(fitToWindow);
104 JMenuItem writeFeatures = new JMenuItem(
105 MessageManager.getString("label.create_chimera_attributes"));
106 writeFeatures.setToolTipText(MessageManager
107 .getString("label.create_chimera_attributes_tip"));
108 writeFeatures.addActionListener(new ActionListener()
111 public void actionPerformed(ActionEvent e)
113 sendFeaturesToChimera();
116 viewerActionMenu.add(writeFeatures);
118 final JMenu fetchAttributes = new JMenu(
119 MessageManager.getString("label.fetch_chimera_attributes"));
120 fetchAttributes.setToolTipText(
121 MessageManager.getString("label.fetch_chimera_attributes_tip"));
122 fetchAttributes.addMouseListener(new MouseAdapter()
126 public void mouseEntered(MouseEvent e)
128 buildAttributesMenu(fetchAttributes);
131 viewerActionMenu.add(fetchAttributes);
135 * Query Chimera for its residue attribute names and add them as items off the
138 * @param attributesMenu
140 protected void buildAttributesMenu(JMenu attributesMenu)
142 List<String> atts = jmb.sendChimeraCommand("list resattr", true);
147 attributesMenu.removeAll();
148 Collections.sort(atts);
149 for (String att : atts)
151 final String attName = att.split(" ")[1];
154 * ignore 'jv_*' attributes, as these are Jalview features that have
155 * been transferred to residue attributes in Chimera!
157 if (!attName.startsWith(ChimeraCommands.NAMESPACE_PREFIX))
159 JMenuItem menuItem = new JMenuItem(attName);
160 menuItem.addActionListener(new ActionListener()
163 public void actionPerformed(ActionEvent e)
165 getChimeraAttributes(attName);
168 attributesMenu.add(menuItem);
174 * Read residues in Chimera with the given attribute name, and set as features
175 * on the corresponding sequence positions (if any)
179 protected void getChimeraAttributes(String attName)
181 jmb.copyStructureAttributesToFeatures(attName, getAlignmentPanel());
185 * Send a command to Chimera to create residue attributes for Jalview features
187 * The syntax is: setattr r <attName> <attValue> <atomSpec>
189 * For example: setattr r jv:chain "Ferredoxin-1, Chloroplastic" #0:94.A
191 protected void sendFeaturesToChimera()
193 int count = jmb.sendFeaturesToViewer(getAlignmentPanel());
195 MessageManager.formatMessage("label.attributes_set", count));
199 * open a single PDB structure in a new Chimera view
206 public ChimeraViewFrame(PDBEntry pdbentry, SequenceI[] seq,
207 String[] chains, final AlignmentPanel ap)
211 openNewChimera(ap, new PDBEntry[] { pdbentry },
217 * Create a helper to manage progress bar display
219 protected void createProgressBar()
221 if (progressBar == null)
223 progressBar = new ProgressBar(statusPanel, statusBar);
227 private void openNewChimera(AlignmentPanel ap, PDBEntry[] pdbentrys,
231 jmb = new JalviewChimeraBindingModel(this,
232 ap.getStructureSelectionManager(), pdbentrys, seqs, null);
233 addAlignmentPanel(ap);
234 useAlignmentPanelForColourbyseq(ap);
236 if (pdbentrys.length > 1)
238 useAlignmentPanelForSuperposition(ap);
240 jmb.setColourBySequence(true);
241 setSize(myWidth, myHeight);
244 addingStructures = false;
245 worker = new Thread(this);
248 this.addInternalFrameListener(new InternalFrameAdapter()
251 public void internalFrameClosing(
252 InternalFrameEvent internalFrameEvent)
261 * Create a new viewer from saved session state data including Chimera session
264 * @param chimeraSessionFile
268 * @param colourByChimera
269 * @param colourBySequence
272 public ChimeraViewFrame(String chimeraSessionFile,
273 AlignmentPanel alignPanel, PDBEntry[] pdbArray,
274 SequenceI[][] seqsArray, boolean colourByChimera,
275 boolean colourBySequence, String newViewId)
278 setViewId(newViewId);
279 this.chimeraSessionFile = chimeraSessionFile;
280 openNewChimera(alignPanel, pdbArray, seqsArray);
283 jmb.setColourBySequence(false);
284 seqColour.setSelected(false);
285 viewerColour.setSelected(true);
287 else if (colourBySequence)
289 jmb.setColourBySequence(true);
290 seqColour.setSelected(true);
291 viewerColour.setSelected(false);
296 * create a new viewer containing several structures, optionally superimposed
297 * using the given alignPanel.
303 public ChimeraViewFrame(PDBEntry[] pe, boolean alignAdded,
308 setAlignAddedStructures(alignAdded);
309 openNewChimera(ap, pe, seqs);
313 * Default constructor
315 public ChimeraViewFrame()
320 * closeViewer will decide whether or not to close this frame
321 * depending on whether user chooses to Cancel or not
323 setDefaultCloseOperation(JInternalFrame.DO_NOTHING_ON_CLOSE);
327 * Launch Chimera. If we have a chimera session file name, send Chimera the
328 * command to open its saved session file.
332 jmb.setFinishedInit(false);
333 Desktop.addInternalFrame(this,
334 jmb.getViewerTitle(getViewerName(), true), getBounds().width,
337 if (!jmb.launchChimera())
339 JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
340 MessageManager.getString("label.chimera_failed"),
341 MessageManager.getString("label.error_loading_file"),
342 JvOptionPane.ERROR_MESSAGE);
347 if (this.chimeraSessionFile != null)
349 boolean opened = jmb.openSession(chimeraSessionFile);
352 System.err.println("An error occurred opening Chimera session file "
353 + chimeraSessionFile);
357 jmb.startChimeraListener();
361 * Show only the selected chain(s) in the viewer
364 void showSelectedChains()
366 List<String> toshow = new ArrayList<>();
367 for (int i = 0; i < chainMenu.getItemCount(); i++)
369 if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
371 JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
372 if (item.isSelected())
374 toshow.add(item.getText());
378 jmb.showChains(toshow);
382 * Close down this instance of Jalview's Chimera viewer, giving the user the
383 * option to close the associated Chimera window (process). They may wish to
384 * keep it open until they have had an opportunity to save any work.
386 * @param closeChimera
387 * if true, close any linked Chimera process; if false, prompt first
390 public void closeViewer(boolean closeChimera)
392 if (jmb != null && jmb.isChimeraRunning())
396 String prompt = MessageManager
397 .formatMessage("label.confirm_close_chimera", new Object[]
398 { jmb.getViewerTitle(getViewerName(), false) });
399 prompt = JvSwingUtils.wrapTooltip(true, prompt);
400 int confirm = JvOptionPane.showConfirmDialog(this, prompt,
401 MessageManager.getString("label.close_viewer"),
402 JvOptionPane.YES_NO_CANCEL_OPTION);
404 * abort closure if user hits escape or Cancel
406 if (confirm == JvOptionPane.CANCEL_OPTION
407 || confirm == JvOptionPane.CLOSED_OPTION)
411 closeChimera = confirm == JvOptionPane.YES_OPTION;
413 jmb.closeViewer(closeChimera);
415 setAlignmentPanel(null);
419 // TODO: check for memory leaks where instance isn't finalised because jmb
420 // holds a reference to the window
426 * Open any newly added PDB structures in Chimera, having first fetched data
427 * from PDB (if not already saved).
433 // todo - record which pdbids were successfully imported.
434 StringBuilder errormsgs = new StringBuilder(128);
435 StringBuilder files = new StringBuilder(128);
436 List<PDBEntry> filePDB = new ArrayList<>();
437 List<Integer> filePDBpos = new ArrayList<>();
438 PDBEntry thePdbEntry = null;
439 StructureFile pdb = null;
442 String[] curfiles = jmb.getStructureFiles(); // files currently in viewer
443 // TODO: replace with reference fetching/transfer code (validate PDBentry
445 for (int pi = 0; pi < jmb.getPdbCount(); pi++)
448 thePdbEntry = jmb.getPdbEntry(pi);
449 if (thePdbEntry.getFile() == null)
452 * Retrieve PDB data, save to file, attach to PDBEntry
454 file = fetchPdbFile(thePdbEntry);
457 errormsgs.append("'" + thePdbEntry.getId() + "' ");
463 * Got file already - ignore if already loaded in Chimera.
465 file = new File(thePdbEntry.getFile()).getAbsoluteFile()
467 if (curfiles != null && curfiles.length > 0)
469 addingStructures = true; // already files loaded.
470 for (int c = 0; c < curfiles.length; c++)
472 if (curfiles[c].equals(file))
482 filePDB.add(thePdbEntry);
483 filePDBpos.add(Integer.valueOf(pi));
484 files.append(" \"" + Platform.escapeBackslashes(file) + "\"");
487 } catch (OutOfMemoryError oomerror)
489 new OOMWarning("Retrieving PDB files: " + thePdbEntry.getId(),
491 } catch (Exception ex)
493 ex.printStackTrace();
495 "When retrieving pdbfiles for '" + thePdbEntry.getId() + "'");
497 if (errormsgs.length() > 0)
500 JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
501 MessageManager.formatMessage(
502 "label.pdb_entries_couldnt_be_retrieved", new Object[]
503 { errormsgs.toString() }),
504 MessageManager.getString("label.couldnt_load_file"),
505 JvOptionPane.ERROR_MESSAGE);
508 if (files.length() > 0)
510 jmb.setFinishedInit(false);
511 if (!addingStructures)
516 } catch (Exception ex)
518 Cache.log.error("Couldn't open Chimera viewer!", ex);
522 for (PDBEntry pe : filePDB)
525 if (pe.getFile() != null)
529 int pos = filePDBpos.get(num).intValue();
530 long startTime = startProgressBar(getViewerName() + " "
531 + MessageManager.getString("status.opening_file_for")
534 jmb.addSequence(pos, jmb.getSequence()[pos]);
535 File fl = new File(pe.getFile());
536 DataSourceType protocol = DataSourceType.URL;
541 protocol = DataSourceType.FILE;
543 } catch (Throwable e)
547 stopProgressBar("", startTime);
549 // Explicitly map to the filename used by Chimera ;
551 pdb = jmb.getSsm().setMapping(jmb.getSequence()[pos],
552 jmb.getChains()[pos], pe.getFile(), protocol,
554 stashFoundChains(pdb, pe.getFile());
556 } catch (OutOfMemoryError oomerror)
559 "When trying to open and map structures from Chimera!",
561 } catch (Exception ex)
564 "Couldn't open " + pe.getFile() + " in Chimera viewer!",
568 Cache.log.debug("File locations are " + files);
574 jmb.setFinishedInit(true);
575 jmb.setLoadingFromArchive(false);
578 * ensure that any newly discovered features (e.g. RESNUM)
579 * are added to any open feature settings dialog
581 FeatureRenderer fr = getBinding().getFeatureRenderer(null);
587 // refresh the sequence colours for the new structure(s)
588 for (AlignmentPanel ap : _colourwith)
590 jmb.updateColours(ap);
592 // do superposition if asked to
593 if (alignAddedStructures)
595 new Thread(new Runnable()
600 alignStructs_withAllAlignPanels();
604 addingStructures = false;
611 * Fetch PDB data and save to a local file. Returns the full path to the file,
612 * or null if fetch fails. TODO: refactor to common with Jmol ? duplication
614 * @param processingEntry
619 private void stashFoundChains(StructureFile pdb, String file)
621 for (int i = 0; i < pdb.getChains().size(); i++)
623 String chid = new String(
624 pdb.getId() + ":" + pdb.getChains().elementAt(i).id);
625 jmb.getChainNames().add(chid);
626 jmb.getChainFile().put(chid, file);
630 private String fetchPdbFile(PDBEntry processingEntry) throws Exception
632 String filePath = null;
633 Pdb pdbclient = new Pdb();
634 AlignmentI pdbseq = null;
635 String pdbid = processingEntry.getId();
636 long handle = System.currentTimeMillis()
637 + Thread.currentThread().hashCode();
640 * Write 'fetching PDB' progress on AlignFrame as we are not yet visible
642 String msg = MessageManager.formatMessage("status.fetching_pdb",
645 getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
646 // long hdl = startProgressBar(MessageManager.formatMessage(
647 // "status.fetching_pdb", new Object[]
651 pdbseq = pdbclient.getSequenceRecords(pdbid);
652 } catch (OutOfMemoryError oomerror)
654 new OOMWarning("Retrieving PDB id " + pdbid, oomerror);
657 msg = pdbid + " " + MessageManager.getString("label.state_completed");
658 getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
659 // stopProgressBar(msg, hdl);
662 * If PDB data were saved and are not invalid (empty alignment), return the
665 if (pdbseq != null && pdbseq.getHeight() > 0)
667 // just use the file name from the first sequence's first PDBEntry
668 filePath = new File(pdbseq.getSequenceAt(0).getAllPDBEntries()
669 .elementAt(0).getFile()).getAbsolutePath();
670 processingEntry.setFile(filePath);
676 * Convenience method to update the progress bar if there is one. Be sure to
677 * call stopProgressBar with the returned handle to remove the message.
682 public long startProgressBar(String msg)
684 // TODO would rather have startProgress/stopProgress as the
685 // IProgressIndicator interface
686 long tm = random.nextLong();
687 if (progressBar != null)
689 progressBar.setProgressBar(msg, tm);
695 * End the progress bar with the specified handle, leaving a message (if not
696 * null) on the status bar
701 public void stopProgressBar(String msg, long handle)
703 if (progressBar != null)
705 progressBar.setProgressBar(msg, handle);
710 public void makePDBImage(TYPE imageType)
712 throw new UnsupportedOperationException(
713 "Image export for Chimera is not implemented");
717 public void showHelp_actionPerformed(ActionEvent actionEvent)
722 .openURL("https://www.cgl.ucsf.edu/chimera/docs/UsersGuide");
723 } catch (IOException ex)
729 public AAStructureBindingModel getBinding()
735 * Ask Chimera to save its session to the designated file path, or to a
736 * temporary file if the path is null. Returns the file path if successful,
742 protected String saveSession(String filepath)
744 String pathUsed = filepath;
747 if (pathUsed == null)
749 File tempFile = File.createTempFile("chimera", ".py");
750 tempFile.deleteOnExit();
751 pathUsed = tempFile.getPath();
753 boolean result = jmb.saveSession(pathUsed);
756 this.chimeraSessionFile = pathUsed;
759 } catch (IOException e)
766 * Returns a string representing the state of the Chimera session. This is
767 * done by requesting Chimera to save its session to a temporary file, then
768 * reading the file contents. Returns an empty string on any error.
771 public String getStateInfo()
773 String sessionFile = saveSession(null);
774 if (sessionFile == null)
778 InputStream is = null;
781 File f = new File(sessionFile);
782 byte[] bytes = new byte[(int) f.length()];
783 is = new FileInputStream(sessionFile);
785 return new String(bytes);
786 } catch (IOException e)
796 } catch (IOException e)
805 protected void fitToWindow_actionPerformed()
811 public ViewerType getViewerType()
813 return ViewerType.CHIMERA;
817 protected String getViewerName()
823 * Sends commands to align structures according to associated alignment(s).
828 protected String alignStructs_withAllAlignPanels()
830 String reply = super.alignStructs_withAllAlignPanels();
833 statusBar.setText("Superposition failed: " + reply);
839 protected IProgressIndicator getIProgressIndicator()