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.AlignmentViewPanel;
24 import jalview.api.FeatureRenderer;
25 import jalview.bin.Cache;
26 import jalview.datamodel.AlignmentI;
27 import jalview.datamodel.PDBEntry;
28 import jalview.datamodel.SequenceI;
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;
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;
69 * Path to Chimera session file. This is set when an open Jalview/Chimera
70 * session is saved, or on restore from a Jalview project (if it holds the
71 * filename of any saved Chimera sessions).
73 private String chimeraSessionFile = null;
75 private int myWidth = 500;
77 private int myHeight = 150;
80 * Initialise menu options.
83 protected void initMenus()
87 viewerActionMenu.setText(MessageManager.getString("label.chimera"));
90 .setText(MessageManager.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(
115 MessageManager.getString("label.fetch_chimera_attributes_tip"));
116 fetchAttributes.addMouseListener(new MouseAdapter()
120 public void mouseEntered(MouseEvent e)
122 buildAttributesMenu(fetchAttributes);
125 viewerActionMenu.add(fetchAttributes);
129 * Query Chimera for its residue attribute names and add them as items off the
132 * @param attributesMenu
134 protected void buildAttributesMenu(JMenu attributesMenu)
136 List<String> atts = jmb.getChimeraAttributes();
137 attributesMenu.removeAll();
138 Collections.sort(atts);
139 for (String attName : atts)
141 JMenuItem menuItem = new JMenuItem(attName);
142 menuItem.addActionListener(new ActionListener()
145 public void actionPerformed(ActionEvent e)
147 getChimeraAttributes(attName);
150 attributesMenu.add(menuItem);
155 * Read residues in Chimera with the given attribute name, and set as features
156 * on the corresponding sequence positions (if any)
160 protected void getChimeraAttributes(String attName)
162 jmb.copyStructureAttributesToFeatures(attName, getAlignmentPanel());
166 * Send a command to Chimera to create residue attributes for Jalview features
168 * The syntax is: setattr r <attName> <attValue> <atomSpec>
170 * For example: setattr r jv:chain "Ferredoxin-1, Chloroplastic" #0:94.A
172 protected void sendFeaturesToChimera()
174 int count = jmb.sendFeaturesToViewer(getAlignmentPanel());
176 MessageManager.formatMessage("label.attributes_set", count));
180 * open a single PDB structure in a new Chimera view
187 public ChimeraViewFrame(PDBEntry pdbentry, SequenceI[] seq,
188 String[] chains, final AlignmentPanel ap)
192 openNewChimera(ap, new PDBEntry[] { pdbentry },
198 * Create a helper to manage progress bar display
200 protected void createProgressBar()
202 if (getProgressIndicator() == null)
204 setProgressIndicator(new ProgressBar(statusPanel, statusBar));
208 private void openNewChimera(AlignmentPanel ap, PDBEntry[] pdbentrys,
212 jmb = new JalviewChimeraBindingModel(this,
213 ap.getStructureSelectionManager(), pdbentrys, seqs, null);
214 addAlignmentPanel(ap);
215 useAlignmentPanelForColourbyseq(ap);
217 if (pdbentrys.length > 1)
219 useAlignmentPanelForSuperposition(ap);
221 jmb.setColourBySequence(true);
222 setSize(myWidth, myHeight);
225 addingStructures = false;
226 worker = new Thread(this);
229 this.addInternalFrameListener(new InternalFrameAdapter()
232 public void internalFrameClosing(
233 InternalFrameEvent internalFrameEvent)
242 * Create a new viewer from saved session state data including Chimera session
245 * @param chimeraSessionFile
249 * @param colourByChimera
250 * @param colourBySequence
253 public ChimeraViewFrame(String chimeraSessionFile,
254 AlignmentPanel alignPanel, PDBEntry[] pdbArray,
255 SequenceI[][] seqsArray, boolean colourByChimera,
256 boolean colourBySequence, String newViewId)
259 setViewId(newViewId);
260 this.chimeraSessionFile = chimeraSessionFile;
261 openNewChimera(alignPanel, pdbArray, seqsArray);
264 jmb.setColourBySequence(false);
265 seqColour.setSelected(false);
266 viewerColour.setSelected(true);
268 else if (colourBySequence)
270 jmb.setColourBySequence(true);
271 seqColour.setSelected(true);
272 viewerColour.setSelected(false);
277 * create a new viewer containing several structures, optionally superimposed
278 * using the given alignPanel.
284 public ChimeraViewFrame(PDBEntry[] pe, boolean alignAdded,
289 setAlignAddedStructures(alignAdded);
290 openNewChimera(ap, pe, seqs);
294 * Default constructor
296 public ChimeraViewFrame()
301 * closeViewer will decide whether or not to close this frame
302 * depending on whether user chooses to Cancel or not
304 setDefaultCloseOperation(JInternalFrame.DO_NOTHING_ON_CLOSE);
308 * Launch Chimera. If we have a chimera session file name, send Chimera the
309 * command to open its saved session file.
313 jmb.setFinishedInit(false);
314 Desktop.addInternalFrame(this,
315 jmb.getViewerTitle(getViewerName(), true), getBounds().width,
318 if (!jmb.launchChimera())
320 JvOptionPane.showMessageDialog(Desktop.desktop,
321 MessageManager.getString("label.chimera_failed"),
322 MessageManager.getString("label.error_loading_file"),
323 JvOptionPane.ERROR_MESSAGE);
328 if (this.chimeraSessionFile != null)
330 boolean opened = jmb.openSession(chimeraSessionFile);
333 System.err.println("An error occurred opening Chimera session file "
334 + chimeraSessionFile);
338 jmb.startChimeraListener();
342 * Show only the selected chain(s) in the viewer
345 void showSelectedChains()
347 List<String> toshow = new ArrayList<>();
348 for (int i = 0; i < chainMenu.getItemCount(); i++)
350 if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
352 JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
353 if (item.isSelected())
355 toshow.add(item.getText());
359 jmb.showChains(toshow);
363 * Close down this instance of Jalview's Chimera viewer, giving the user the
364 * option to close the associated Chimera window (process). They may wish to
365 * keep it open until they have had an opportunity to save any work.
367 * @param closeChimera
368 * if true, close any linked Chimera process; if false, prompt first
371 public void closeViewer(boolean closeChimera)
373 if (jmb != null && jmb.isChimeraRunning())
377 String prompt = MessageManager
378 .formatMessage("label.confirm_close_chimera", new Object[]
379 { jmb.getViewerTitle(getViewerName(), false) });
380 prompt = JvSwingUtils.wrapTooltip(true, prompt);
381 int confirm = JvOptionPane.showConfirmDialog(this, prompt,
382 MessageManager.getString("label.close_viewer"),
383 JvOptionPane.YES_NO_CANCEL_OPTION);
385 * abort closure if user hits escape or Cancel
387 if (confirm == JvOptionPane.CANCEL_OPTION
388 || confirm == JvOptionPane.CLOSED_OPTION)
392 closeChimera = confirm == JvOptionPane.YES_OPTION;
394 jmb.closeViewer(closeChimera);
396 setAlignmentPanel(null);
400 // TODO: check for memory leaks where instance isn't finalised because jmb
401 // holds a reference to the window
407 * Open any newly added PDB structures in Chimera, having first fetched data
408 * from PDB (if not already saved).
414 // todo - record which pdbids were successfully imported.
415 StringBuilder errormsgs = new StringBuilder(128);
416 StringBuilder files = new StringBuilder(128);
417 List<PDBEntry> filePDB = new ArrayList<>();
418 List<Integer> filePDBpos = new ArrayList<>();
419 PDBEntry thePdbEntry = null;
420 StructureFile pdb = null;
423 String[] curfiles = jmb.getStructureFiles(); // files currently in viewer
424 // TODO: replace with reference fetching/transfer code (validate PDBentry
426 for (int pi = 0; pi < jmb.getPdbCount(); pi++)
429 thePdbEntry = jmb.getPdbEntry(pi);
430 if (thePdbEntry.getFile() == null)
433 * Retrieve PDB data, save to file, attach to PDBEntry
435 file = fetchPdbFile(thePdbEntry);
438 errormsgs.append("'" + thePdbEntry.getId() + "' ");
444 * Got file already - ignore if already loaded in Chimera.
446 file = new File(thePdbEntry.getFile()).getAbsoluteFile()
448 if (curfiles != null && curfiles.length > 0)
450 addingStructures = true; // already files loaded.
451 for (int c = 0; c < curfiles.length; c++)
453 if (curfiles[c].equals(file))
463 filePDB.add(thePdbEntry);
464 filePDBpos.add(Integer.valueOf(pi));
465 files.append(" \"" + Platform.escapeBackslashes(file) + "\"");
468 } catch (OutOfMemoryError oomerror)
470 new OOMWarning("Retrieving PDB files: " + thePdbEntry.getId(),
472 } catch (Exception ex)
474 ex.printStackTrace();
476 "When retrieving pdbfiles for '" + thePdbEntry.getId() + "'");
478 if (errormsgs.length() > 0)
481 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
482 MessageManager.formatMessage(
483 "label.pdb_entries_couldnt_be_retrieved", new Object[]
484 { errormsgs.toString() }),
485 MessageManager.getString("label.couldnt_load_file"),
486 JvOptionPane.ERROR_MESSAGE);
489 if (files.length() > 0)
491 jmb.setFinishedInit(false);
492 if (!addingStructures)
497 } catch (Exception ex)
499 Cache.log.error("Couldn't open Chimera viewer!", ex);
503 for (PDBEntry pe : filePDB)
506 if (pe.getFile() != null)
510 int pos = filePDBpos.get(num).intValue();
511 long startTime = startProgressBar(getViewerName() + " "
512 + MessageManager.getString("status.opening_file_for")
515 jmb.addSequence(pos, jmb.getSequence()[pos]);
516 File fl = new File(pe.getFile());
517 DataSourceType protocol = DataSourceType.URL;
522 protocol = DataSourceType.FILE;
524 } catch (Throwable e)
528 stopProgressBar("", startTime);
530 // Explicitly map to the filename used by Chimera ;
532 pdb = jmb.getSsm().setMapping(jmb.getSequence()[pos],
533 jmb.getChains()[pos], pe.getFile(), protocol,
534 getProgressIndicator());
535 stashFoundChains(pdb, pe.getFile());
537 } catch (OutOfMemoryError oomerror)
540 "When trying to open and map structures from Chimera!",
542 } catch (Exception ex)
545 "Couldn't open " + pe.getFile() + " in Chimera viewer!",
549 Cache.log.debug("File locations are " + files);
555 jmb.setFinishedInit(true);
556 jmb.setLoadingFromArchive(false);
559 * ensure that any newly discovered features (e.g. RESNUM)
560 * are added to any open feature settings dialog
562 FeatureRenderer fr = getBinding().getFeatureRenderer(null);
568 // refresh the sequence colours for the new structure(s)
569 for (AlignmentViewPanel ap : _colourwith)
571 jmb.updateColours(ap);
573 // do superposition if asked to
574 if (alignAddedStructures)
576 new Thread(new Runnable()
581 alignStructs_withAllAlignPanels();
585 addingStructures = false;
592 * Fetch PDB data and save to a local file. Returns the full path to the file,
593 * or null if fetch fails. TODO: refactor to common with Jmol ? duplication
595 * @param processingEntry
600 private void stashFoundChains(StructureFile pdb, String file)
602 for (int i = 0; i < pdb.getChains().size(); i++)
604 String chid = new String(
605 pdb.getId() + ":" + pdb.getChains().elementAt(i).id);
606 jmb.getChainNames().add(chid);
607 jmb.getChainFile().put(chid, file);
611 private String fetchPdbFile(PDBEntry processingEntry) throws Exception
613 String filePath = null;
614 Pdb pdbclient = new Pdb();
615 AlignmentI pdbseq = null;
616 String pdbid = processingEntry.getId();
617 long handle = System.currentTimeMillis()
618 + Thread.currentThread().hashCode();
621 * Write 'fetching PDB' progress on AlignFrame as we are not yet visible
623 String msg = MessageManager.formatMessage("status.fetching_pdb",
626 getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
627 // long hdl = startProgressBar(MessageManager.formatMessage(
628 // "status.fetching_pdb", new Object[]
632 pdbseq = pdbclient.getSequenceRecords(pdbid);
633 } catch (OutOfMemoryError oomerror)
635 new OOMWarning("Retrieving PDB id " + pdbid, oomerror);
638 msg = pdbid + " " + MessageManager.getString("label.state_completed");
639 getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
640 // stopProgressBar(msg, hdl);
643 * If PDB data were saved and are not invalid (empty alignment), return the
646 if (pdbseq != null && pdbseq.getHeight() > 0)
648 // just use the file name from the first sequence's first PDBEntry
649 filePath = new File(pdbseq.getSequenceAt(0).getAllPDBEntries()
650 .elementAt(0).getFile()).getAbsolutePath();
651 processingEntry.setFile(filePath);
657 public void eps_actionPerformed(ActionEvent e)
659 throw new Error(MessageManager
660 .getString("error.eps_generation_not_implemented"));
664 public void png_actionPerformed(ActionEvent e)
666 throw new Error(MessageManager
667 .getString("error.png_generation_not_implemented"));
671 public void showHelp_actionPerformed(ActionEvent actionEvent)
675 String url = jmb.isChimeraX()
676 ? "http://www.rbvi.ucsf.edu/chimerax/docs/user/index.html"
677 : "https://www.cgl.ucsf.edu/chimera/docs/UsersGuide";
678 BrowserLauncher.openURL(url);
679 } catch (IOException ex)
682 .println("Show Chimera help failed with: " + ex.getMessage());
687 public AAStructureBindingModel getBinding()
693 * Ask Chimera to save its session to the designated file path, or to a
694 * temporary file if the path is null. Returns the file path if successful,
700 protected String saveSession(String filepath)
702 String pathUsed = filepath;
705 if (pathUsed == null)
707 String suffix = jmb.isChimeraX() ? ".cxs" : ".py";
708 File tempFile = File.createTempFile("chimera", suffix);
709 tempFile.deleteOnExit();
710 pathUsed = tempFile.getPath();
712 boolean result = jmb.saveSession(pathUsed);
715 this.chimeraSessionFile = pathUsed;
718 } catch (IOException e)
725 * Returns a string representing the state of the Chimera session. This is
726 * done by requesting Chimera to save its session to a temporary file, then
727 * reading the file contents. Returns an empty string on any error.
730 public String getStateInfo()
732 String sessionFile = saveSession(null);
733 if (sessionFile == null)
737 InputStream is = null;
740 File f = new File(sessionFile);
741 byte[] bytes = new byte[(int) f.length()];
742 is = new FileInputStream(sessionFile);
744 return new String(bytes);
745 } catch (IOException e)
755 } catch (IOException e)
764 protected void fitToWindow_actionPerformed()
770 public ViewerType getViewerType()
772 return ViewerType.CHIMERA;
776 protected String getViewerName()
782 * Sends commands to align structures according to associated alignment(s).
787 protected String alignStructs_withAllAlignPanels()
789 String reply = super.alignStructs_withAllAlignPanels();
792 statusBar.setText("Superposition failed: " + reply);