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.JalviewChimeraBinding;
28 import jalview.gui.StructureViewer.ViewerType;
29 import jalview.io.DataSourceType;
30 import jalview.io.StructureFile;
31 import jalview.structures.models.AAStructureBindingModel;
32 import jalview.util.BrowserLauncher;
33 import jalview.util.MessageManager;
34 import jalview.util.Platform;
35 import jalview.ws.dbsources.Pdb;
37 import java.awt.event.ActionEvent;
39 import java.io.FileInputStream;
40 import java.io.IOException;
41 import java.io.InputStream;
42 import java.util.ArrayList;
43 import java.util.List;
44 import java.util.Random;
46 import javax.swing.JCheckBoxMenuItem;
47 import javax.swing.JInternalFrame;
48 import javax.swing.event.InternalFrameAdapter;
49 import javax.swing.event.InternalFrameEvent;
52 * GUI elements for handling an external chimera display
57 public class ChimeraViewFrame extends StructureViewerBase
59 private JalviewChimeraBinding jmb;
61 private boolean allChainsSelected = false;
63 private IProgressIndicator progressBar = null;
66 * Path to Chimera session file. This is set when an open Jalview/Chimera
67 * session is saved, or on restore from a Jalview project (if it holds the
68 * filename of any saved Chimera sessions).
70 private String chimeraSessionFile = null;
72 private Random random = new Random();
75 * Initialise menu options.
78 protected void initMenus()
82 viewerActionMenu.setText(MessageManager.getString("label.chimera"));
84 viewerColour.setText(MessageManager
85 .getString("label.colour_with_chimera"));
86 viewerColour.setToolTipText(MessageManager
87 .getString("label.let_chimera_manage_structure_colours"));
89 helpItem.setText(MessageManager.getString("label.chimera_help"));
90 savemenu.setVisible(false); // not yet implemented
91 viewMenu.add(fitToWindow);
95 * add a single PDB structure to a new or existing Chimera view
102 public ChimeraViewFrame(PDBEntry pdbentry, SequenceI[] seq,
103 String[] chains, final AlignmentPanel ap)
106 String pdbId = pdbentry.getId();
109 * If the PDB file is already loaded, the user may just choose to add to an
110 * existing viewer (or cancel)
112 if (addAlreadyLoadedFile(seq, chains, ap, pdbId))
118 * Check if there are other Chimera views involving this alignment and give
119 * user the option to add and align this molecule to one of them (or cancel)
121 if (addToExistingViewer(pdbentry, seq, chains, ap, pdbId))
127 * If the options above are declined or do not apply, show the structure in
130 openNewChimera(ap, new PDBEntry[] { pdbentry },
131 new SequenceI[][] { seq });
135 * Create a helper to manage progress bar display
137 protected void createProgressBar()
139 if (progressBar == null)
141 progressBar = new ProgressBar(statusPanel, statusBar);
145 private void openNewChimera(AlignmentPanel ap, PDBEntry[] pdbentrys,
149 // FIXME extractChains needs pdbentries to match IDs to PDBEntry(s) on seqs
150 jmb = new JalviewChimeraBindingModel(this,
151 ap.getStructureSelectionManager(), pdbentrys, seqs, null);
152 addAlignmentPanel(ap);
153 useAlignmentPanelForColourbyseq(ap);
155 if (pdbentrys.length > 1)
157 alignAddedStructures = true;
158 useAlignmentPanelForSuperposition(ap);
160 jmb.setColourBySequence(true);
161 setSize(400, 400); // probably should be a configurable/dynamic default here
164 addingStructures = false;
165 worker = new Thread(this);
168 this.addInternalFrameListener(new InternalFrameAdapter()
171 public void internalFrameClosing(InternalFrameEvent internalFrameEvent)
180 * Create a new viewer from saved session state data including Chimera session
183 * @param chimeraSessionFile
187 * @param colourByChimera
188 * @param colourBySequence
191 public ChimeraViewFrame(String chimeraSessionFile,
192 AlignmentPanel alignPanel, PDBEntry[] pdbArray,
193 SequenceI[][] seqsArray, boolean colourByChimera,
194 boolean colourBySequence, String newViewId)
197 setViewId(newViewId);
198 this.chimeraSessionFile = chimeraSessionFile;
199 openNewChimera(alignPanel, pdbArray, seqsArray);
202 jmb.setColourBySequence(false);
203 seqColour.setSelected(false);
204 viewerColour.setSelected(true);
206 else if (colourBySequence)
208 jmb.setColourBySequence(true);
209 seqColour.setSelected(true);
210 viewerColour.setSelected(false);
215 * create a new viewer containing several structures superimposed using the
222 public ChimeraViewFrame(PDBEntry[] pe, SequenceI[][] seqs,
226 openNewChimera(ap, pe, seqs);
230 * Default constructor
232 public ChimeraViewFrame()
237 * closeViewer will decide whether or not to close this frame
238 * depending on whether user chooses to Cancel or not
240 setDefaultCloseOperation(JInternalFrame.DO_NOTHING_ON_CLOSE);
244 * Returns a list of any Chimera viewers in the desktop. The list is
245 * restricted to those linked to the given alignment panel if it is not null.
248 protected List<StructureViewerBase> getViewersFor(AlignmentPanel ap)
250 List<StructureViewerBase> result = new ArrayList<StructureViewerBase>();
251 JInternalFrame[] frames = Desktop.instance.getAllFrames();
253 for (JInternalFrame frame : frames)
255 if (frame instanceof ChimeraViewFrame)
257 if (ap == null || ((StructureViewerBase) frame).isLinkedWith(ap))
259 result.add((StructureViewerBase) frame);
267 * Launch Chimera. If we have a chimera session file name, send Chimera the
268 * command to open its saved session file.
272 jmb.setFinishedInit(false);
273 jalview.gui.Desktop.addInternalFrame(this,
274 jmb.getViewerTitle(getViewerName(), true), getBounds().width,
277 if (!jmb.launchChimera())
279 JvOptionPane.showMessageDialog(Desktop.desktop,
280 MessageManager.getString("label.chimera_failed"),
281 MessageManager.getString("label.error_loading_file"),
282 JvOptionPane.ERROR_MESSAGE);
287 if (this.chimeraSessionFile != null)
289 boolean opened = jmb.openSession(chimeraSessionFile);
293 .println("An error occurred opening Chimera session file "
294 + chimeraSessionFile);
297 jmb.setFinishedInit(true);
299 jmb.startChimeraListener();
304 * Show only the selected chain(s) in the viewer
307 void showSelectedChains()
309 List<String> toshow = new ArrayList<String>();
310 for (int i = 0; i < chainMenu.getItemCount(); i++)
312 if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
314 JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
315 if (item.isSelected())
317 toshow.add(item.getText());
321 jmb.showChains(toshow);
325 * Close down this instance of Jalview's Chimera viewer, giving the user the
326 * option to close the associated Chimera window (process). They may wish to
327 * keep it open until they have had an opportunity to save any work.
329 * @param closeChimera
330 * if true, close any linked Chimera process; if false, prompt first
333 public void closeViewer(boolean closeChimera)
335 if (jmb != null && jmb.isChimeraRunning())
339 String prompt = MessageManager.formatMessage(
340 "label.confirm_close_chimera",
341 new Object[] { jmb.getViewerTitle(getViewerName(),
343 prompt = JvSwingUtils.wrapTooltip(true, prompt);
344 int confirm = JvOptionPane.showConfirmDialog(this, prompt,
345 MessageManager.getString("label.close_viewer"),
346 JvOptionPane.YES_NO_CANCEL_OPTION);
348 * abort closure if user hits escape or Cancel
350 if (confirm == JvOptionPane.CANCEL_OPTION
351 || confirm == JvOptionPane.CLOSED_OPTION)
355 closeChimera = confirm == JvOptionPane.YES_OPTION;
357 jmb.closeViewer(closeChimera);
359 setAlignmentPanel(null);
363 // TODO: check for memory leaks where instance isn't finalised because jmb
364 // holds a reference to the window
370 * Open any newly added PDB structures in Chimera, having first fetched data
371 * from PDB (if not already saved).
377 // todo - record which pdbids were successfully imported.
378 StringBuilder errormsgs = new StringBuilder(128);
379 StringBuilder files = new StringBuilder(128);
380 List<PDBEntry> filePDB = new ArrayList<PDBEntry>();
381 List<Integer> filePDBpos = new ArrayList<Integer>();
382 PDBEntry thePdbEntry = null;
383 StructureFile pdb = null;
386 String[] curfiles = jmb.getPdbFile(); // files currently in viewer
387 // TODO: replace with reference fetching/transfer code (validate PDBentry
389 for (int pi = 0; pi < jmb.getPdbCount(); pi++)
392 thePdbEntry = jmb.getPdbEntry(pi);
393 if (thePdbEntry.getFile() == null)
396 * Retrieve PDB data, save to file, attach to PDBEntry
398 file = fetchPdbFile(thePdbEntry);
401 errormsgs.append("'" + thePdbEntry.getId() + "' ");
407 * Got file already - ignore if already loaded in Chimera.
409 file = new File(thePdbEntry.getFile()).getAbsoluteFile()
411 if (curfiles != null && curfiles.length > 0)
413 addingStructures = true; // already files loaded.
414 for (int c = 0; c < curfiles.length; c++)
416 if (curfiles[c].equals(file))
426 filePDB.add(thePdbEntry);
427 filePDBpos.add(Integer.valueOf(pi));
428 files.append(" \"" + Platform.escapeString(file) + "\"");
431 } catch (OutOfMemoryError oomerror)
433 new OOMWarning("Retrieving PDB files: " + thePdbEntry.getId(),
435 } catch (Exception ex)
437 ex.printStackTrace();
438 errormsgs.append("When retrieving pdbfiles for '"
439 + thePdbEntry.getId() + "'");
441 if (errormsgs.length() > 0)
444 JvOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
445 .formatMessage("label.pdb_entries_couldnt_be_retrieved",
446 new Object[] { errormsgs.toString() }),
447 MessageManager.getString("label.couldnt_load_file"),
448 JvOptionPane.ERROR_MESSAGE);
451 if (files.length() > 0)
453 if (!addingStructures)
458 } catch (Exception ex)
460 Cache.log.error("Couldn't open Chimera viewer!", ex);
464 for (PDBEntry pe : filePDB)
467 if (pe.getFile() != null)
471 int pos = filePDBpos.get(num).intValue();
472 long startTime = startProgressBar(getViewerName() + " "
473 + MessageManager.getString("status.opening_file_for")
476 jmb.addSequence(pos, jmb.getSequence()[pos]);
477 File fl = new File(pe.getFile());
478 DataSourceType protocol = DataSourceType.URL;
483 protocol = DataSourceType.FILE;
485 } catch (Throwable e)
489 stopProgressBar("", startTime);
491 // Explicitly map to the filename used by Chimera ;
492 pdb = jmb.getSsm().setMapping(jmb.getSequence()[pos],
493 jmb.getChains()[pos], pe.getFile(), protocol);
494 stashFoundChains(pdb, pe.getFile());
495 } catch (OutOfMemoryError oomerror)
498 "When trying to open and map structures from Chimera!",
500 } catch (Exception ex)
502 Cache.log.error("Couldn't open " + pe.getFile()
503 + " in Chimera viewer!", ex);
506 Cache.log.debug("File locations are " + files);
511 jmb.setFinishedInit(true);
512 jmb.setLoadingFromArchive(false);
514 // refresh the sequence colours for the new structure(s)
515 for (AlignmentPanel ap : _colourwith)
517 jmb.updateColours(ap);
519 // do superposition if asked to
520 if (Cache.getDefault("AUTOSUPERIMPOSE", true) && alignAddedStructures)
522 new Thread(new Runnable()
527 alignStructs_withAllAlignPanels();
530 alignAddedStructures = false;
532 addingStructures = false;
539 * Fetch PDB data and save to a local file. Returns the full path to the file,
540 * or null if fetch fails.
542 * @param processingEntry
547 private void stashFoundChains(StructureFile pdb, String file)
549 for (int i = 0; i < pdb.getChains().size(); i++)
551 String chid = new String(pdb.getId() + ":"
552 + pdb.getChains().elementAt(i).id);
553 jmb.getChainNames().add(chid);
554 jmb.getChainFile().put(chid, file);
557 private String fetchPdbFile(PDBEntry processingEntry) throws Exception
559 // FIXME: this is duplicated code with Jmol frame ?
560 String filePath = null;
561 Pdb pdbclient = new Pdb();
562 AlignmentI pdbseq = null;
563 String pdbid = processingEntry.getId();
564 long handle = System.currentTimeMillis()
565 + Thread.currentThread().hashCode();
568 * Write 'fetching PDB' progress on AlignFrame as we are not yet visible
570 String msg = MessageManager.formatMessage("status.fetching_pdb",
571 new Object[] { pdbid });
572 getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
573 // long hdl = startProgressBar(MessageManager.formatMessage(
574 // "status.fetching_pdb", new Object[]
578 pdbseq = pdbclient.getSequenceRecords(pdbid);
579 } catch (OutOfMemoryError oomerror)
581 new OOMWarning("Retrieving PDB id " + pdbid, oomerror);
584 msg = pdbid + " " + MessageManager.getString("label.state_completed");
585 getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
586 // stopProgressBar(msg, hdl);
589 * If PDB data were saved and are not invalid (empty alignment), return the
592 if (pdbseq != null && pdbseq.getHeight() > 0)
594 // just use the file name from the first sequence's first PDBEntry
595 filePath = new File(pdbseq.getSequenceAt(0).getAllPDBEntries()
596 .elementAt(0).getFile()).getAbsolutePath();
597 processingEntry.setFile(filePath);
603 * Convenience method to update the progress bar if there is one. Be sure to
604 * call stopProgressBar with the returned handle to remove the message.
609 public long startProgressBar(String msg)
611 // TODO would rather have startProgress/stopProgress as the
612 // IProgressIndicator interface
613 long tm = random.nextLong();
614 if (progressBar != null)
616 progressBar.setProgressBar(msg, tm);
622 * End the progress bar with the specified handle, leaving a message (if not
623 * null) on the status bar
628 public void stopProgressBar(String msg, long handle)
630 if (progressBar != null)
632 progressBar.setProgressBar(msg, handle);
637 public void eps_actionPerformed(ActionEvent e)
641 .getString("error.eps_generation_not_implemented"));
645 public void png_actionPerformed(ActionEvent e)
649 .getString("error.png_generation_not_implemented"));
653 public void showHelp_actionPerformed(ActionEvent actionEvent)
658 .openURL("https://www.cgl.ucsf.edu/chimera/docs/UsersGuide");
659 } catch (Exception ex)
665 public AAStructureBindingModel getBinding()
671 * Ask Chimera to save its session to the designated file path, or to a
672 * temporary file if the path is null. Returns the file path if successful,
678 protected String saveSession(String filepath)
680 String pathUsed = filepath;
683 if (pathUsed == null)
685 File tempFile = File.createTempFile("chimera", ".py");
686 tempFile.deleteOnExit();
687 pathUsed = tempFile.getPath();
689 boolean result = jmb.saveSession(pathUsed);
692 this.chimeraSessionFile = pathUsed;
695 } catch (IOException e)
702 * Returns a string representing the state of the Chimera session. This is
703 * done by requesting Chimera to save its session to a temporary file, then
704 * reading the file contents. Returns an empty string on any error.
707 public String getStateInfo()
709 String sessionFile = saveSession(null);
710 if (sessionFile == null)
714 InputStream is = null;
717 File f = new File(sessionFile);
718 byte[] bytes = new byte[(int) f.length()];
719 is = new FileInputStream(sessionFile);
721 return new String(bytes);
722 } catch (IOException e)
732 } catch (IOException e)
741 protected void fitToWindow_actionPerformed()
747 public ViewerType getViewerType()
749 return ViewerType.CHIMERA;
753 protected String getViewerName()