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.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();
79 private int myWidth = 500;
81 private int myHeight = 150;
84 * Initialise menu options.
87 protected void initMenus()
91 viewerActionMenu.setText(MessageManager.getString("label.chimera"));
94 .setText(MessageManager.getString("label.colour_with_chimera"));
95 viewerColour.setToolTipText(MessageManager
96 .getString("label.let_chimera_manage_structure_colours"));
98 helpItem.setText(MessageManager.getString("label.chimera_help"));
99 savemenu.setVisible(false); // not yet implemented
100 viewMenu.add(fitToWindow);
102 JMenuItem writeFeatures = new JMenuItem(
103 MessageManager.getString("label.create_chimera_attributes"));
104 writeFeatures.setToolTipText(MessageManager
105 .getString("label.create_chimera_attributes_tip"));
106 writeFeatures.addActionListener(new ActionListener()
109 public void actionPerformed(ActionEvent e)
111 sendFeaturesToChimera();
114 viewerActionMenu.add(writeFeatures);
116 final JMenu fetchAttributes = new JMenu(
117 MessageManager.getString("label.fetch_chimera_attributes"));
118 fetchAttributes.setToolTipText(
119 MessageManager.getString("label.fetch_chimera_attributes_tip"));
120 fetchAttributes.addMouseListener(new MouseAdapter()
124 public void mouseEntered(MouseEvent e)
126 buildAttributesMenu(fetchAttributes);
129 viewerActionMenu.add(fetchAttributes);
133 * Query Chimera for its residue attribute names and add them as items off the
136 * @param attributesMenu
138 protected void buildAttributesMenu(JMenu attributesMenu)
140 List<String> atts = jmb.getChimeraAttributes();
141 attributesMenu.removeAll();
142 Collections.sort(atts);
143 for (String attName : atts)
145 JMenuItem menuItem = new JMenuItem(attName);
146 menuItem.addActionListener(new ActionListener()
149 public void actionPerformed(ActionEvent e)
151 getChimeraAttributes(attName);
154 attributesMenu.add(menuItem);
159 * Read residues in Chimera with the given attribute name, and set as features
160 * on the corresponding sequence positions (if any)
164 protected void getChimeraAttributes(String attName)
166 jmb.copyStructureAttributesToFeatures(attName, getAlignmentPanel());
170 * Send a command to Chimera to create residue attributes for Jalview features
172 * The syntax is: setattr r <attName> <attValue> <atomSpec>
174 * For example: setattr r jv:chain "Ferredoxin-1, Chloroplastic" #0:94.A
176 protected void sendFeaturesToChimera()
178 int count = jmb.sendFeaturesToViewer(getAlignmentPanel());
180 MessageManager.formatMessage("label.attributes_set", count));
184 * open a single PDB structure in a new Chimera view
191 public ChimeraViewFrame(PDBEntry pdbentry, SequenceI[] seq,
192 String[] chains, final AlignmentPanel ap)
196 openNewChimera(ap, new PDBEntry[] { pdbentry },
202 * Create a helper to manage progress bar display
204 protected void createProgressBar()
206 if (progressBar == null)
208 progressBar = new ProgressBar(statusPanel, statusBar);
212 private void openNewChimera(AlignmentPanel ap, PDBEntry[] pdbentrys,
216 jmb = new JalviewChimeraBindingModel(this,
217 ap.getStructureSelectionManager(), pdbentrys, seqs, null);
218 addAlignmentPanel(ap);
219 useAlignmentPanelForColourbyseq(ap);
221 if (pdbentrys.length > 1)
223 useAlignmentPanelForSuperposition(ap);
225 jmb.setColourBySequence(true);
226 setSize(myWidth, myHeight);
229 addingStructures = false;
230 worker = new Thread(this);
233 this.addInternalFrameListener(new InternalFrameAdapter()
236 public void internalFrameClosing(
237 InternalFrameEvent internalFrameEvent)
246 * Create a new viewer from saved session state data including Chimera session
249 * @param chimeraSessionFile
253 * @param colourByChimera
254 * @param colourBySequence
257 public ChimeraViewFrame(String chimeraSessionFile,
258 AlignmentPanel alignPanel, PDBEntry[] pdbArray,
259 SequenceI[][] seqsArray, boolean colourByChimera,
260 boolean colourBySequence, String newViewId)
263 setViewId(newViewId);
264 this.chimeraSessionFile = chimeraSessionFile;
265 openNewChimera(alignPanel, pdbArray, seqsArray);
268 jmb.setColourBySequence(false);
269 seqColour.setSelected(false);
270 viewerColour.setSelected(true);
272 else if (colourBySequence)
274 jmb.setColourBySequence(true);
275 seqColour.setSelected(true);
276 viewerColour.setSelected(false);
281 * create a new viewer containing several structures, optionally superimposed
282 * using the given alignPanel.
288 public ChimeraViewFrame(PDBEntry[] pe, boolean alignAdded,
293 setAlignAddedStructures(alignAdded);
294 openNewChimera(ap, pe, seqs);
298 * Default constructor
300 public ChimeraViewFrame()
305 * closeViewer will decide whether or not to close this frame
306 * depending on whether user chooses to Cancel or not
308 setDefaultCloseOperation(JInternalFrame.DO_NOTHING_ON_CLOSE);
312 * Launch Chimera. If we have a chimera session file name, send Chimera the
313 * command to open its saved session file.
317 jmb.setFinishedInit(false);
318 Desktop.addInternalFrame(this,
319 jmb.getViewerTitle(getViewerName(), true), getBounds().width,
322 if (!jmb.launchChimera())
324 JvOptionPane.showMessageDialog(Desktop.desktop,
325 MessageManager.getString("label.chimera_failed"),
326 MessageManager.getString("label.error_loading_file"),
327 JvOptionPane.ERROR_MESSAGE);
332 if (this.chimeraSessionFile != null)
334 boolean opened = jmb.openSession(chimeraSessionFile);
337 System.err.println("An error occurred opening Chimera session file "
338 + chimeraSessionFile);
342 jmb.startChimeraListener();
346 * Show only the selected chain(s) in the viewer
349 void showSelectedChains()
351 List<String> toshow = new ArrayList<>();
352 for (int i = 0; i < chainMenu.getItemCount(); i++)
354 if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
356 JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
357 if (item.isSelected())
359 toshow.add(item.getText());
363 jmb.showChains(toshow);
367 * Close down this instance of Jalview's Chimera viewer, giving the user the
368 * option to close the associated Chimera window (process). They may wish to
369 * keep it open until they have had an opportunity to save any work.
371 * @param closeChimera
372 * if true, close any linked Chimera process; if false, prompt first
375 public void closeViewer(boolean closeChimera)
377 if (jmb != null && jmb.isChimeraRunning())
381 String prompt = MessageManager
382 .formatMessage("label.confirm_close_chimera", new Object[]
383 { jmb.getViewerTitle(getViewerName(), false) });
384 prompt = JvSwingUtils.wrapTooltip(true, prompt);
385 int confirm = JvOptionPane.showConfirmDialog(this, prompt,
386 MessageManager.getString("label.close_viewer"),
387 JvOptionPane.YES_NO_CANCEL_OPTION);
389 * abort closure if user hits escape or Cancel
391 if (confirm == JvOptionPane.CANCEL_OPTION
392 || confirm == JvOptionPane.CLOSED_OPTION)
396 closeChimera = confirm == JvOptionPane.YES_OPTION;
398 jmb.closeViewer(closeChimera);
400 setAlignmentPanel(null);
404 // TODO: check for memory leaks where instance isn't finalised because jmb
405 // holds a reference to the window
411 * Open any newly added PDB structures in Chimera, having first fetched data
412 * from PDB (if not already saved).
418 // todo - record which pdbids were successfully imported.
419 StringBuilder errormsgs = new StringBuilder(128);
420 StringBuilder files = new StringBuilder(128);
421 List<PDBEntry> filePDB = new ArrayList<>();
422 List<Integer> filePDBpos = new ArrayList<>();
423 PDBEntry thePdbEntry = null;
424 StructureFile pdb = null;
427 String[] curfiles = jmb.getStructureFiles(); // files currently in viewer
428 // TODO: replace with reference fetching/transfer code (validate PDBentry
430 for (int pi = 0; pi < jmb.getPdbCount(); pi++)
433 thePdbEntry = jmb.getPdbEntry(pi);
434 if (thePdbEntry.getFile() == null)
437 * Retrieve PDB data, save to file, attach to PDBEntry
439 file = fetchPdbFile(thePdbEntry);
442 errormsgs.append("'" + thePdbEntry.getId() + "' ");
448 * Got file already - ignore if already loaded in Chimera.
450 file = new File(thePdbEntry.getFile()).getAbsoluteFile()
452 if (curfiles != null && curfiles.length > 0)
454 addingStructures = true; // already files loaded.
455 for (int c = 0; c < curfiles.length; c++)
457 if (curfiles[c].equals(file))
467 filePDB.add(thePdbEntry);
468 filePDBpos.add(Integer.valueOf(pi));
469 files.append(" \"" + Platform.escapeBackslashes(file) + "\"");
472 } catch (OutOfMemoryError oomerror)
474 new OOMWarning("Retrieving PDB files: " + thePdbEntry.getId(),
476 } catch (Exception ex)
478 ex.printStackTrace();
480 "When retrieving pdbfiles for '" + thePdbEntry.getId() + "'");
482 if (errormsgs.length() > 0)
485 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
486 MessageManager.formatMessage(
487 "label.pdb_entries_couldnt_be_retrieved", new Object[]
488 { errormsgs.toString() }),
489 MessageManager.getString("label.couldnt_load_file"),
490 JvOptionPane.ERROR_MESSAGE);
493 if (files.length() > 0)
495 jmb.setFinishedInit(false);
496 if (!addingStructures)
501 } catch (Exception ex)
503 Cache.log.error("Couldn't open Chimera viewer!", ex);
507 for (PDBEntry pe : filePDB)
510 if (pe.getFile() != null)
514 int pos = filePDBpos.get(num).intValue();
515 long startTime = startProgressBar(getViewerName() + " "
516 + MessageManager.getString("status.opening_file_for")
519 jmb.addSequence(pos, jmb.getSequence()[pos]);
520 File fl = new File(pe.getFile());
521 DataSourceType protocol = DataSourceType.URL;
526 protocol = DataSourceType.FILE;
528 } catch (Throwable e)
532 stopProgressBar("", startTime);
534 // Explicitly map to the filename used by Chimera ;
536 pdb = jmb.getSsm().setMapping(jmb.getSequence()[pos],
537 jmb.getChains()[pos], pe.getFile(), protocol,
539 stashFoundChains(pdb, pe.getFile());
541 } catch (OutOfMemoryError oomerror)
544 "When trying to open and map structures from Chimera!",
546 } catch (Exception ex)
549 "Couldn't open " + pe.getFile() + " in Chimera viewer!",
553 Cache.log.debug("File locations are " + files);
559 jmb.setFinishedInit(true);
560 jmb.setLoadingFromArchive(false);
563 * ensure that any newly discovered features (e.g. RESNUM)
564 * are added to any open feature settings dialog
566 FeatureRenderer fr = getBinding().getFeatureRenderer(null);
572 // refresh the sequence colours for the new structure(s)
573 for (AlignmentPanel ap : _colourwith)
575 jmb.updateColours(ap);
577 // do superposition if asked to
578 if (alignAddedStructures)
580 new Thread(new Runnable()
585 alignStructs_withAllAlignPanels();
589 addingStructures = false;
596 * Fetch PDB data and save to a local file. Returns the full path to the file,
597 * or null if fetch fails. TODO: refactor to common with Jmol ? duplication
599 * @param processingEntry
604 private void stashFoundChains(StructureFile pdb, String file)
606 for (int i = 0; i < pdb.getChains().size(); i++)
608 String chid = new String(
609 pdb.getId() + ":" + pdb.getChains().elementAt(i).id);
610 jmb.getChainNames().add(chid);
611 jmb.getChainFile().put(chid, file);
615 private String fetchPdbFile(PDBEntry processingEntry) throws Exception
617 String filePath = null;
618 Pdb pdbclient = new Pdb();
619 AlignmentI pdbseq = null;
620 String pdbid = processingEntry.getId();
621 long handle = System.currentTimeMillis()
622 + Thread.currentThread().hashCode();
625 * Write 'fetching PDB' progress on AlignFrame as we are not yet visible
627 String msg = MessageManager.formatMessage("status.fetching_pdb",
630 getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
631 // long hdl = startProgressBar(MessageManager.formatMessage(
632 // "status.fetching_pdb", new Object[]
636 pdbseq = pdbclient.getSequenceRecords(pdbid);
637 } catch (OutOfMemoryError oomerror)
639 new OOMWarning("Retrieving PDB id " + pdbid, oomerror);
642 msg = pdbid + " " + MessageManager.getString("label.state_completed");
643 getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
644 // stopProgressBar(msg, hdl);
647 * If PDB data were saved and are not invalid (empty alignment), return the
650 if (pdbseq != null && pdbseq.getHeight() > 0)
652 // just use the file name from the first sequence's first PDBEntry
653 filePath = new File(pdbseq.getSequenceAt(0).getAllPDBEntries()
654 .elementAt(0).getFile()).getAbsolutePath();
655 processingEntry.setFile(filePath);
661 * Convenience method to update the progress bar if there is one. Be sure to
662 * call stopProgressBar with the returned handle to remove the message.
667 public long startProgressBar(String msg)
669 // TODO would rather have startProgress/stopProgress as the
670 // IProgressIndicator interface
671 long tm = random.nextLong();
672 if (progressBar != null)
674 progressBar.setProgressBar(msg, tm);
680 * End the progress bar with the specified handle, leaving a message (if not
681 * null) on the status bar
686 public void stopProgressBar(String msg, long handle)
688 if (progressBar != null)
690 progressBar.setProgressBar(msg, handle);
695 public void eps_actionPerformed(ActionEvent e)
697 throw new Error(MessageManager
698 .getString("error.eps_generation_not_implemented"));
702 public void png_actionPerformed(ActionEvent e)
704 throw new Error(MessageManager
705 .getString("error.png_generation_not_implemented"));
709 public void showHelp_actionPerformed(ActionEvent actionEvent)
714 .openURL("https://www.cgl.ucsf.edu/chimera/docs/UsersGuide");
715 } catch (IOException ex)
721 public AAStructureBindingModel getBinding()
727 * Ask Chimera to save its session to the designated file path, or to a
728 * temporary file if the path is null. Returns the file path if successful,
734 protected String saveSession(String filepath)
736 String pathUsed = filepath;
739 if (pathUsed == null)
741 String suffix = jmb.isChimeraX() ? ".cxs" : ".py";
742 File tempFile = File.createTempFile("chimera", suffix);
743 tempFile.deleteOnExit();
744 pathUsed = tempFile.getPath();
746 boolean result = jmb.saveSession(pathUsed);
749 this.chimeraSessionFile = pathUsed;
752 } catch (IOException e)
759 * Returns a string representing the state of the Chimera session. This is
760 * done by requesting Chimera to save its session to a temporary file, then
761 * reading the file contents. Returns an empty string on any error.
764 public String getStateInfo()
766 String sessionFile = saveSession(null);
767 if (sessionFile == null)
771 InputStream is = null;
774 File f = new File(sessionFile);
775 byte[] bytes = new byte[(int) f.length()];
776 is = new FileInputStream(sessionFile);
778 return new String(bytes);
779 } catch (IOException e)
789 } catch (IOException e)
798 protected void fitToWindow_actionPerformed()
804 public ViewerType getViewerType()
806 return ViewerType.CHIMERA;
810 protected String getViewerName()
816 * Sends commands to align structures according to associated alignment(s).
821 protected String alignStructs_withAllAlignPanels()
823 String reply = super.alignStructs_withAllAlignPanels();
826 statusBar.setText("Superposition failed: " + reply);
832 protected IProgressIndicator getIProgressIndicator()