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.bin.Cache;
24 import jalview.datamodel.AlignmentI;
25 import jalview.datamodel.PDBEntry;
26 import jalview.datamodel.SequenceI;
27 import jalview.ext.rbvi.chimera.ChimeraCommands;
28 import jalview.ext.rbvi.chimera.JalviewChimeraBinding;
29 import jalview.gui.StructureViewer.ViewerType;
30 import jalview.io.DataSourceType;
31 import jalview.io.StructureFile;
32 import jalview.structures.models.AAStructureBindingModel;
33 import jalview.util.BrowserLauncher;
34 import jalview.util.MessageManager;
35 import jalview.util.Platform;
36 import jalview.ws.dbsources.Pdb;
38 import java.awt.event.ActionEvent;
39 import java.awt.event.ActionListener;
40 import java.awt.event.MouseAdapter;
41 import java.awt.event.MouseEvent;
43 import java.io.FileInputStream;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.util.ArrayList;
47 import java.util.Collections;
48 import java.util.List;
49 import java.util.Random;
51 import javax.swing.JCheckBoxMenuItem;
52 import javax.swing.JInternalFrame;
53 import javax.swing.JMenu;
54 import javax.swing.JMenuItem;
55 import javax.swing.event.InternalFrameAdapter;
56 import javax.swing.event.InternalFrameEvent;
59 * GUI elements for handling an external chimera display
64 public class ChimeraViewFrame extends StructureViewerBase
66 private JalviewChimeraBinding jmb;
68 private IProgressIndicator progressBar = null;
71 * Path to Chimera session file. This is set when an open Jalview/Chimera
72 * session is saved, or on restore from a Jalview project (if it holds the
73 * filename of any saved Chimera sessions).
75 private String chimeraSessionFile = null;
77 private Random random = new Random();
80 * Initialise menu options.
83 protected void initMenus()
87 viewerActionMenu.setText(MessageManager.getString("label.chimera"));
89 viewerColour.setText(MessageManager
90 .getString("label.colour_with_chimera"));
91 viewerColour.setToolTipText(MessageManager
92 .getString("label.let_chimera_manage_structure_colours"));
94 helpItem.setText(MessageManager.getString("label.chimera_help"));
95 savemenu.setVisible(false); // not yet implemented
96 viewMenu.add(fitToWindow);
98 JMenuItem writeFeatures = new JMenuItem(
99 MessageManager.getString("label.create_chimera_attributes"));
100 writeFeatures.setToolTipText(MessageManager
101 .getString("label.create_chimera_attributes_tip"));
102 writeFeatures.addActionListener(new ActionListener()
105 public void actionPerformed(ActionEvent e)
107 sendFeaturesToChimera();
110 viewerActionMenu.add(writeFeatures);
112 final JMenu fetchAttributes = new JMenu(
113 MessageManager.getString("label.fetch_chimera_attributes"));
114 fetchAttributes.setToolTipText(MessageManager
115 .getString("label.fetch_chimera_attributes_tip"));
116 fetchAttributes.addMouseListener(new MouseAdapter()
120 public void mouseEntered(MouseEvent e)
122 buildAttributesMenu(fetchAttributes);
125 viewerActionMenu.add(fetchAttributes);
130 * Query Chimera for its residue attribute names and add them as items off the
133 * @param attributesMenu
135 protected void buildAttributesMenu(JMenu attributesMenu)
137 List<String> atts = jmb.sendChimeraCommand("list resattr", true);
142 attributesMenu.removeAll();
143 Collections.sort(atts);
144 for (String att : atts)
146 final String attName = att.split(" ")[1];
149 * ignore 'jv_*' attributes, as these are Jalview features that have
150 * been transferred to residue attributes in Chimera!
152 if (!attName.startsWith(ChimeraCommands.NAMESPACE_PREFIX))
154 JMenuItem menuItem = new JMenuItem(attName);
155 menuItem.addActionListener(new ActionListener()
158 public void actionPerformed(ActionEvent e)
160 getChimeraAttributes(attName);
163 attributesMenu.add(menuItem);
169 * Read residues in Chimera with the given attribute name, and set as features
170 * on the corresponding sequence positions (if any)
174 protected void getChimeraAttributes(String attName)
176 jmb.copyStructureAttributesToFeatures(attName, getAlignmentPanel());
180 * Send a command to Chimera to create residue attributes for Jalview features
182 * The syntax is: setattr r <attName> <attValue> <atomSpec>
184 * For example: setattr r jv:chain "Ferredoxin-1, Chloroplastic" #0:94.A
186 protected void sendFeaturesToChimera()
188 jmb.sendFeaturesToViewer(getAlignmentPanel());
192 * add a single PDB structure to a new or existing Chimera view
199 public ChimeraViewFrame(PDBEntry pdbentry, SequenceI[] seq,
200 String[] chains, final AlignmentPanel ap)
203 String pdbId = pdbentry.getId();
206 * If the PDB file is already loaded, the user may just choose to add to an
207 * existing viewer (or cancel)
209 if (addAlreadyLoadedFile(seq, chains, ap, pdbId))
215 * Check if there are other Chimera views involving this alignment and give
216 * user the option to add and align this molecule to one of them (or cancel)
218 if (addToExistingViewer(pdbentry, seq, chains, ap, pdbId))
224 * If the options above are declined or do not apply, show the structure in
227 openNewChimera(ap, new PDBEntry[] { pdbentry },
228 new SequenceI[][] { seq });
232 * Create a helper to manage progress bar display
234 protected void createProgressBar()
236 if (progressBar == null)
238 progressBar = new ProgressBar(statusPanel, statusBar);
242 private void openNewChimera(AlignmentPanel ap, PDBEntry[] pdbentrys,
246 jmb = new JalviewChimeraBindingModel(this,
247 ap.getStructureSelectionManager(), pdbentrys, seqs, null);
248 addAlignmentPanel(ap);
249 useAlignmentPanelForColourbyseq(ap);
251 if (pdbentrys.length > 1)
253 alignAddedStructures = true;
254 useAlignmentPanelForSuperposition(ap);
256 jmb.setColourBySequence(true);
257 setSize(400, 400); // probably should be a configurable/dynamic default here
260 addingStructures = false;
261 worker = new Thread(this);
264 this.addInternalFrameListener(new InternalFrameAdapter()
267 public void internalFrameClosing(InternalFrameEvent internalFrameEvent)
276 * Create a new viewer from saved session state data including Chimera session
279 * @param chimeraSessionFile
283 * @param colourByChimera
284 * @param colourBySequence
287 public ChimeraViewFrame(String chimeraSessionFile,
288 AlignmentPanel alignPanel, PDBEntry[] pdbArray,
289 SequenceI[][] seqsArray, boolean colourByChimera,
290 boolean colourBySequence, String newViewId)
293 setViewId(newViewId);
294 this.chimeraSessionFile = chimeraSessionFile;
295 openNewChimera(alignPanel, pdbArray, seqsArray);
298 jmb.setColourBySequence(false);
299 seqColour.setSelected(false);
300 viewerColour.setSelected(true);
302 else if (colourBySequence)
304 jmb.setColourBySequence(true);
305 seqColour.setSelected(true);
306 viewerColour.setSelected(false);
311 * create a new viewer containing several structures superimposed using the
318 public ChimeraViewFrame(PDBEntry[] pe, SequenceI[][] seqs,
322 openNewChimera(ap, pe, seqs);
326 * Default constructor
328 public ChimeraViewFrame()
333 * closeViewer will decide whether or not to close this frame
334 * depending on whether user chooses to Cancel or not
336 setDefaultCloseOperation(JInternalFrame.DO_NOTHING_ON_CLOSE);
340 * Returns a list of any Chimera viewers in the desktop. The list is
341 * restricted to those linked to the given alignment panel if it is not null.
344 protected List<StructureViewerBase> getViewersFor(AlignmentPanel ap)
346 List<StructureViewerBase> result = new ArrayList<StructureViewerBase>();
347 JInternalFrame[] frames = Desktop.instance.getAllFrames();
349 for (JInternalFrame frame : frames)
351 if (frame instanceof ChimeraViewFrame)
353 if (ap == null || ((StructureViewerBase) frame).isLinkedWith(ap))
355 result.add((StructureViewerBase) frame);
363 * Launch Chimera. If we have a chimera session file name, send Chimera the
364 * command to open its saved session file.
368 jmb.setFinishedInit(false);
369 Desktop.addInternalFrame(this,
370 jmb.getViewerTitle(getViewerName(), true), getBounds().width,
373 if (!jmb.launchChimera())
375 JvOptionPane.showMessageDialog(Desktop.desktop,
376 MessageManager.getString("label.chimera_failed"),
377 MessageManager.getString("label.error_loading_file"),
378 JvOptionPane.ERROR_MESSAGE);
383 if (this.chimeraSessionFile != null)
385 boolean opened = jmb.openSession(chimeraSessionFile);
389 .println("An error occurred opening Chimera session file "
390 + chimeraSessionFile);
394 jmb.startChimeraListener();
398 * Show only the selected chain(s) in the viewer
401 void showSelectedChains()
403 List<String> toshow = new ArrayList<String>();
404 for (int i = 0; i < chainMenu.getItemCount(); i++)
406 if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
408 JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
409 if (item.isSelected())
411 toshow.add(item.getText());
415 jmb.showChains(toshow);
419 * Close down this instance of Jalview's Chimera viewer, giving the user the
420 * option to close the associated Chimera window (process). They may wish to
421 * keep it open until they have had an opportunity to save any work.
423 * @param closeChimera
424 * if true, close any linked Chimera process; if false, prompt first
427 public void closeViewer(boolean closeChimera)
429 if (jmb != null && jmb.isChimeraRunning())
433 String prompt = MessageManager.formatMessage(
434 "label.confirm_close_chimera",
435 new Object[] { jmb.getViewerTitle(getViewerName(),
437 prompt = JvSwingUtils.wrapTooltip(true, prompt);
438 int confirm = JvOptionPane.showConfirmDialog(this, prompt,
439 MessageManager.getString("label.close_viewer"),
440 JvOptionPane.YES_NO_CANCEL_OPTION);
442 * abort closure if user hits escape or Cancel
444 if (confirm == JvOptionPane.CANCEL_OPTION
445 || confirm == JvOptionPane.CLOSED_OPTION)
449 closeChimera = confirm == JvOptionPane.YES_OPTION;
451 jmb.closeViewer(closeChimera);
453 setAlignmentPanel(null);
457 // TODO: check for memory leaks where instance isn't finalised because jmb
458 // holds a reference to the window
464 * Open any newly added PDB structures in Chimera, having first fetched data
465 * from PDB (if not already saved).
471 // todo - record which pdbids were successfully imported.
472 StringBuilder errormsgs = new StringBuilder(128);
473 StringBuilder files = new StringBuilder(128);
474 List<PDBEntry> filePDB = new ArrayList<PDBEntry>();
475 List<Integer> filePDBpos = new ArrayList<Integer>();
476 PDBEntry thePdbEntry = null;
477 StructureFile pdb = null;
480 String[] curfiles = jmb.getPdbFile(); // files currently in viewer
481 // TODO: replace with reference fetching/transfer code (validate PDBentry
483 for (int pi = 0; pi < jmb.getPdbCount(); pi++)
486 thePdbEntry = jmb.getPdbEntry(pi);
487 if (thePdbEntry.getFile() == null)
490 * Retrieve PDB data, save to file, attach to PDBEntry
492 file = fetchPdbFile(thePdbEntry);
495 errormsgs.append("'" + thePdbEntry.getId() + "' ");
501 * Got file already - ignore if already loaded in Chimera.
503 file = new File(thePdbEntry.getFile()).getAbsoluteFile()
505 if (curfiles != null && curfiles.length > 0)
507 addingStructures = true; // already files loaded.
508 for (int c = 0; c < curfiles.length; c++)
510 if (curfiles[c].equals(file))
520 filePDB.add(thePdbEntry);
521 filePDBpos.add(Integer.valueOf(pi));
522 files.append(" \"" + Platform.escapeString(file) + "\"");
525 } catch (OutOfMemoryError oomerror)
527 new OOMWarning("Retrieving PDB files: " + thePdbEntry.getId(),
529 } catch (Exception ex)
531 ex.printStackTrace();
532 errormsgs.append("When retrieving pdbfiles for '"
533 + thePdbEntry.getId() + "'");
535 if (errormsgs.length() > 0)
538 JvOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
539 .formatMessage("label.pdb_entries_couldnt_be_retrieved",
540 new Object[] { errormsgs.toString() }),
541 MessageManager.getString("label.couldnt_load_file"),
542 JvOptionPane.ERROR_MESSAGE);
545 if (files.length() > 0)
547 jmb.setFinishedInit(false);
548 if (!addingStructures)
553 } catch (Exception ex)
555 Cache.log.error("Couldn't open Chimera viewer!", ex);
559 for (PDBEntry pe : filePDB)
562 if (pe.getFile() != null)
566 int pos = filePDBpos.get(num).intValue();
567 long startTime = startProgressBar(getViewerName() + " "
568 + MessageManager.getString("status.opening_file_for")
571 jmb.addSequence(pos, jmb.getSequence()[pos]);
572 File fl = new File(pe.getFile());
573 DataSourceType protocol = DataSourceType.URL;
578 protocol = DataSourceType.FILE;
580 } catch (Throwable e)
584 stopProgressBar("", startTime);
586 // Explicitly map to the filename used by Chimera ;
587 pdb = jmb.getSsm().setMapping(jmb.getSequence()[pos],
588 jmb.getChains()[pos], pe.getFile(), protocol);
589 stashFoundChains(pdb, pe.getFile());
590 } catch (OutOfMemoryError oomerror)
593 "When trying to open and map structures from Chimera!",
595 } catch (Exception ex)
597 Cache.log.error("Couldn't open " + pe.getFile()
598 + " in Chimera viewer!", ex);
601 Cache.log.debug("File locations are " + files);
607 jmb.setFinishedInit(true);
608 jmb.setLoadingFromArchive(false);
610 // refresh the sequence colours for the new structure(s)
611 for (AlignmentPanel ap : _colourwith)
613 jmb.updateColours(ap);
615 // do superposition if asked to
616 if (Cache.getDefault("AUTOSUPERIMPOSE", true) && alignAddedStructures)
618 new Thread(new Runnable()
623 alignStructs_withAllAlignPanels();
626 alignAddedStructures = false;
628 addingStructures = false;
635 * Fetch PDB data and save to a local file. Returns the full path to the file,
636 * or null if fetch fails.
638 * @param processingEntry
643 private void stashFoundChains(StructureFile pdb, String file)
645 for (int i = 0; i < pdb.getChains().size(); i++)
647 String chid = new String(pdb.getId() + ":"
648 + pdb.getChains().elementAt(i).id);
649 jmb.getChainNames().add(chid);
650 jmb.getChainFile().put(chid, file);
653 private String fetchPdbFile(PDBEntry processingEntry) throws Exception
655 // FIXME: this is duplicated code with Jmol frame ?
656 String filePath = null;
657 Pdb pdbclient = new Pdb();
658 AlignmentI pdbseq = null;
659 String pdbid = processingEntry.getId();
660 long handle = System.currentTimeMillis()
661 + Thread.currentThread().hashCode();
664 * Write 'fetching PDB' progress on AlignFrame as we are not yet visible
666 String msg = MessageManager.formatMessage("status.fetching_pdb",
667 new Object[] { pdbid });
668 getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
669 // long hdl = startProgressBar(MessageManager.formatMessage(
670 // "status.fetching_pdb", new Object[]
674 pdbseq = pdbclient.getSequenceRecords(pdbid);
675 } catch (OutOfMemoryError oomerror)
677 new OOMWarning("Retrieving PDB id " + pdbid, oomerror);
680 msg = pdbid + " " + MessageManager.getString("label.state_completed");
681 getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
682 // stopProgressBar(msg, hdl);
685 * If PDB data were saved and are not invalid (empty alignment), return the
688 if (pdbseq != null && pdbseq.getHeight() > 0)
690 // just use the file name from the first sequence's first PDBEntry
691 filePath = new File(pdbseq.getSequenceAt(0).getAllPDBEntries()
692 .elementAt(0).getFile()).getAbsolutePath();
693 processingEntry.setFile(filePath);
699 * Convenience method to update the progress bar if there is one. Be sure to
700 * call stopProgressBar with the returned handle to remove the message.
705 public long startProgressBar(String msg)
707 // TODO would rather have startProgress/stopProgress as the
708 // IProgressIndicator interface
709 long tm = random.nextLong();
710 if (progressBar != null)
712 progressBar.setProgressBar(msg, tm);
718 * End the progress bar with the specified handle, leaving a message (if not
719 * null) on the status bar
724 public void stopProgressBar(String msg, long handle)
726 if (progressBar != null)
728 progressBar.setProgressBar(msg, handle);
733 public void eps_actionPerformed(ActionEvent e)
737 .getString("error.eps_generation_not_implemented"));
741 public void png_actionPerformed(ActionEvent e)
745 .getString("error.png_generation_not_implemented"));
749 public void showHelp_actionPerformed(ActionEvent actionEvent)
754 .openURL("https://www.cgl.ucsf.edu/chimera/docs/UsersGuide");
755 } catch (IOException ex)
761 public AAStructureBindingModel getBinding()
767 * Ask Chimera to save its session to the designated file path, or to a
768 * temporary file if the path is null. Returns the file path if successful,
774 protected String saveSession(String filepath)
776 String pathUsed = filepath;
779 if (pathUsed == null)
781 File tempFile = File.createTempFile("chimera", ".py");
782 tempFile.deleteOnExit();
783 pathUsed = tempFile.getPath();
785 boolean result = jmb.saveSession(pathUsed);
788 this.chimeraSessionFile = pathUsed;
791 } catch (IOException e)
798 * Returns a string representing the state of the Chimera session. This is
799 * done by requesting Chimera to save its session to a temporary file, then
800 * reading the file contents. Returns an empty string on any error.
803 public String getStateInfo()
805 String sessionFile = saveSession(null);
806 if (sessionFile == null)
810 InputStream is = null;
813 File f = new File(sessionFile);
814 byte[] bytes = new byte[(int) f.length()];
815 is = new FileInputStream(sessionFile);
817 return new String(bytes);
818 } catch (IOException e)
828 } catch (IOException e)
837 protected void fitToWindow_actionPerformed()
843 public ViewerType getViewerType()
845 return ViewerType.CHIMERA;
849 protected String getViewerName()
855 * Override superclass method to make the 'Chimera' menu always visible, but
856 * 'Superpose with...' only enabled if there is more than one structure shown
859 public void updateTitleAndMenus()
861 super.updateTitleAndMenus();
862 viewerActionMenu.setVisible(true);
863 viewSelectionMenu.setEnabled(false);
864 if (getBinding().getPdbFile().length > 1
865 && getBinding().getSequence().length > 1)
867 viewSelectionMenu.setEnabled(true);