1 /* 2 * Jalview - A Sequence Alignment Editor and Viewer (2.11.1.4) 3 * Copyright (C) 2021 The Jalview Authors 4 * 5 * This file is part of Jalview. 6 * 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. 11 * 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. 16 * 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. 20 */ 21 package jalview.gui; 22 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.MessageManager; 36 import jalview.util.Platform; 37 import jalview.ws.dbsources.Pdb; 38 39 import java.awt.event.ActionEvent; 40 import java.awt.event.ActionListener; 41 import java.awt.event.MouseAdapter; 42 import java.awt.event.MouseEvent; 43 import java.io.File; 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; 50 import java.util.Random; 51 52 import javax.swing.JCheckBoxMenuItem; 53 import javax.swing.JInternalFrame; 54 import javax.swing.JMenu; 55 import javax.swing.JMenuItem; 56 import javax.swing.event.InternalFrameAdapter; 57 import javax.swing.event.InternalFrameEvent; 58 59 /** 60 * GUI elements for handling an external chimera display 61 * 62 * @author jprocter 63 * 64 */ 65 public class ChimeraViewFrame extends StructureViewerBase 66 { 67 private JalviewChimeraBinding jmb; 68 69 private IProgressIndicator progressBar = null; 70 71 /* 72 * Path to Chimera session file. This is set when an open Jalview/Chimera 73 * session is saved, or on restore from a Jalview project (if it holds the 74 * filename of any saved Chimera sessions). 75 */ 76 private String chimeraSessionFile = null; 77 78 private Random random = new Random(); 79 80 private int myWidth = 500; 81 82 private int myHeight = 150; 83 84 /** 85 * Initialise menu options. 86 */ 87 @Override initMenus()88 protected void initMenus() 89 { 90 super.initMenus(); 91 92 viewerActionMenu.setText(MessageManager.getString("label.chimera")); 93 94 viewerColour 95 .setText(MessageManager.getString("label.colour_with_chimera")); 96 viewerColour.setToolTipText(MessageManager 97 .getString("label.let_chimera_manage_structure_colours")); 98 99 helpItem.setText(MessageManager.getString("label.chimera_help")); 100 savemenu.setVisible(false); // not yet implemented 101 viewMenu.add(fitToWindow); 102 103 JMenuItem writeFeatures = new JMenuItem( 104 MessageManager.getString("label.create_chimera_attributes")); 105 writeFeatures.setToolTipText(MessageManager 106 .getString("label.create_chimera_attributes_tip")); 107 writeFeatures.addActionListener(new ActionListener() 108 { 109 @Override 110 public void actionPerformed(ActionEvent e) 111 { 112 sendFeaturesToChimera(); 113 } 114 }); 115 viewerActionMenu.add(writeFeatures); 116 117 final JMenu fetchAttributes = new JMenu( 118 MessageManager.getString("label.fetch_chimera_attributes")); 119 fetchAttributes.setToolTipText( 120 MessageManager.getString("label.fetch_chimera_attributes_tip")); 121 fetchAttributes.addMouseListener(new MouseAdapter() 122 { 123 124 @Override 125 public void mouseEntered(MouseEvent e) 126 { 127 buildAttributesMenu(fetchAttributes); 128 } 129 }); 130 viewerActionMenu.add(fetchAttributes); 131 } 132 133 /** 134 * Query Chimera for its residue attribute names and add them as items off the 135 * attributes menu 136 * 137 * @param attributesMenu 138 */ buildAttributesMenu(JMenu attributesMenu)139 protected void buildAttributesMenu(JMenu attributesMenu) 140 { 141 List<String> atts = jmb.sendChimeraCommand("list resattr", true); 142 if (atts == null) 143 { 144 return; 145 } 146 attributesMenu.removeAll(); 147 Collections.sort(atts); 148 for (String att : atts) 149 { 150 final String attName = att.split(" ")[1]; 151 152 /* 153 * ignore 'jv_*' attributes, as these are Jalview features that have 154 * been transferred to residue attributes in Chimera! 155 */ 156 if (!attName.startsWith(ChimeraCommands.NAMESPACE_PREFIX)) 157 { 158 JMenuItem menuItem = new JMenuItem(attName); 159 menuItem.addActionListener(new ActionListener() 160 { 161 @Override 162 public void actionPerformed(ActionEvent e) 163 { 164 getChimeraAttributes(attName); 165 } 166 }); 167 attributesMenu.add(menuItem); 168 } 169 } 170 } 171 172 /** 173 * Read residues in Chimera with the given attribute name, and set as features 174 * on the corresponding sequence positions (if any) 175 * 176 * @param attName 177 */ getChimeraAttributes(String attName)178 protected void getChimeraAttributes(String attName) 179 { 180 jmb.copyStructureAttributesToFeatures(attName, getAlignmentPanel()); 181 } 182 183 /** 184 * Send a command to Chimera to create residue attributes for Jalview features 185 * <p> 186 * The syntax is: setattr r <attName> <attValue> <atomSpec> 187 * <p> 188 * For example: setattr r jv:chain "Ferredoxin-1, Chloroplastic" #0:94.A 189 */ sendFeaturesToChimera()190 protected void sendFeaturesToChimera() 191 { 192 int count = jmb.sendFeaturesToViewer(getAlignmentPanel()); 193 statusBar.setText( 194 MessageManager.formatMessage("label.attributes_set", count)); 195 } 196 197 /** 198 * open a single PDB structure in a new Chimera view 199 * 200 * @param pdbentry 201 * @param seq 202 * @param chains 203 * @param ap 204 */ ChimeraViewFrame(PDBEntry pdbentry, SequenceI[] seq, String[] chains, final AlignmentPanel ap)205 public ChimeraViewFrame(PDBEntry pdbentry, SequenceI[] seq, 206 String[] chains, final AlignmentPanel ap) 207 { 208 this(); 209 210 openNewChimera(ap, new PDBEntry[] { pdbentry }, 211 new SequenceI[][] 212 { seq }); 213 } 214 215 /** 216 * Create a helper to manage progress bar display 217 */ createProgressBar()218 protected void createProgressBar() 219 { 220 if (progressBar == null) 221 { 222 progressBar = new ProgressBar(statusPanel, statusBar); 223 } 224 } 225 openNewChimera(AlignmentPanel ap, PDBEntry[] pdbentrys, SequenceI[][] seqs)226 private void openNewChimera(AlignmentPanel ap, PDBEntry[] pdbentrys, 227 SequenceI[][] seqs) 228 { 229 createProgressBar(); 230 jmb = new JalviewChimeraBindingModel(this, 231 ap.getStructureSelectionManager(), pdbentrys, seqs, null); 232 addAlignmentPanel(ap); 233 useAlignmentPanelForColourbyseq(ap); 234 235 if (pdbentrys.length > 1) 236 { 237 useAlignmentPanelForSuperposition(ap); 238 } 239 jmb.setColourBySequence(true); 240 setSize(myWidth, myHeight); 241 initMenus(); 242 243 addingStructures = false; 244 worker = new Thread(this); 245 worker.start(); 246 247 this.addInternalFrameListener(new InternalFrameAdapter() 248 { 249 @Override 250 public void internalFrameClosing( 251 InternalFrameEvent internalFrameEvent) 252 { 253 closeViewer(false); 254 } 255 }); 256 257 } 258 259 /** 260 * Create a new viewer from saved session state data including Chimera session 261 * file 262 * 263 * @param chimeraSessionFile 264 * @param alignPanel 265 * @param pdbArray 266 * @param seqsArray 267 * @param colourByChimera 268 * @param colourBySequence 269 * @param newViewId 270 */ ChimeraViewFrame(String chimeraSessionFile, AlignmentPanel alignPanel, PDBEntry[] pdbArray, SequenceI[][] seqsArray, boolean colourByChimera, boolean colourBySequence, String newViewId)271 public ChimeraViewFrame(String chimeraSessionFile, 272 AlignmentPanel alignPanel, PDBEntry[] pdbArray, 273 SequenceI[][] seqsArray, boolean colourByChimera, 274 boolean colourBySequence, String newViewId) 275 { 276 this(); 277 setViewId(newViewId); 278 this.chimeraSessionFile = chimeraSessionFile; 279 openNewChimera(alignPanel, pdbArray, seqsArray); 280 if (colourByChimera) 281 { 282 jmb.setColourBySequence(false); 283 seqColour.setSelected(false); 284 viewerColour.setSelected(true); 285 } 286 else if (colourBySequence) 287 { 288 jmb.setColourBySequence(true); 289 seqColour.setSelected(true); 290 viewerColour.setSelected(false); 291 } 292 } 293 294 /** 295 * create a new viewer containing several structures, optionally superimposed 296 * using the given alignPanel. 297 * 298 * @param pe 299 * @param seqs 300 * @param ap 301 */ ChimeraViewFrame(PDBEntry[] pe, boolean alignAdded, SequenceI[][] seqs, AlignmentPanel ap)302 public ChimeraViewFrame(PDBEntry[] pe, boolean alignAdded, 303 SequenceI[][] seqs, 304 AlignmentPanel ap) 305 { 306 this(); 307 setAlignAddedStructures(alignAdded); 308 openNewChimera(ap, pe, seqs); 309 } 310 311 /** 312 * Default constructor 313 */ ChimeraViewFrame()314 public ChimeraViewFrame() 315 { 316 super(); 317 318 /* 319 * closeViewer will decide whether or not to close this frame 320 * depending on whether user chooses to Cancel or not 321 */ 322 setDefaultCloseOperation(JInternalFrame.DO_NOTHING_ON_CLOSE); 323 } 324 325 /** 326 * Launch Chimera. If we have a chimera session file name, send Chimera the 327 * command to open its saved session file. 328 */ initChimera()329 void initChimera() 330 { 331 jmb.setFinishedInit(false); 332 Desktop.addInternalFrame(this, 333 jmb.getViewerTitle(getViewerName(), true), getBounds().width, 334 getBounds().height); 335 336 if (!jmb.launchChimera()) 337 { 338 JvOptionPane.showMessageDialog(Desktop.desktop, 339 MessageManager.getString("label.chimera_failed"), 340 MessageManager.getString("label.error_loading_file"), 341 JvOptionPane.ERROR_MESSAGE); 342 this.dispose(); 343 return; 344 } 345 346 if (this.chimeraSessionFile != null) 347 { 348 boolean opened = jmb.openSession(chimeraSessionFile); 349 if (!opened) 350 { 351 System.err.println("An error occurred opening Chimera session file " 352 + chimeraSessionFile); 353 } 354 } 355 356 jmb.startChimeraListener(); 357 } 358 359 /** 360 * Show only the selected chain(s) in the viewer 361 */ 362 @Override showSelectedChains()363 void showSelectedChains() 364 { 365 List<String> toshow = new ArrayList<>(); 366 for (int i = 0; i < chainMenu.getItemCount(); i++) 367 { 368 if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem) 369 { 370 JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i); 371 if (item.isSelected()) 372 { 373 toshow.add(item.getText()); 374 } 375 } 376 } 377 jmb.showChains(toshow); 378 } 379 380 /** 381 * Close down this instance of Jalview's Chimera viewer, giving the user the 382 * option to close the associated Chimera window (process). They may wish to 383 * keep it open until they have had an opportunity to save any work. 384 * 385 * @param closeChimera 386 * if true, close any linked Chimera process; if false, prompt first 387 */ 388 @Override closeViewer(boolean closeChimera)389 public void closeViewer(boolean closeChimera) 390 { 391 if (jmb != null && jmb.isChimeraRunning()) 392 { 393 if (!closeChimera) 394 { 395 String prompt = MessageManager 396 .formatMessage("label.confirm_close_chimera", new Object[] 397 { jmb.getViewerTitle(getViewerName(), false) }); 398 prompt = JvSwingUtils.wrapTooltip(true, prompt); 399 int confirm = JvOptionPane.showConfirmDialog(this, prompt, 400 MessageManager.getString("label.close_viewer"), 401 JvOptionPane.YES_NO_CANCEL_OPTION); 402 /* 403 * abort closure if user hits escape or Cancel 404 */ 405 if (confirm == JvOptionPane.CANCEL_OPTION 406 || confirm == JvOptionPane.CLOSED_OPTION) 407 { 408 return; 409 } 410 closeChimera = confirm == JvOptionPane.YES_OPTION; 411 } 412 jmb.closeViewer(closeChimera); 413 } 414 setAlignmentPanel(null); 415 _aps.clear(); 416 _alignwith.clear(); 417 _colourwith.clear(); 418 // TODO: check for memory leaks where instance isn't finalised because jmb 419 // holds a reference to the window 420 jmb = null; 421 dispose(); 422 } 423 424 /** 425 * Open any newly added PDB structures in Chimera, having first fetched data 426 * from PDB (if not already saved). 427 */ 428 @Override run()429 public void run() 430 { 431 _started = true; 432 // todo - record which pdbids were successfully imported. 433 StringBuilder errormsgs = new StringBuilder(128); 434 StringBuilder files = new StringBuilder(128); 435 List<PDBEntry> filePDB = new ArrayList<>(); 436 List<Integer> filePDBpos = new ArrayList<>(); 437 PDBEntry thePdbEntry = null; 438 StructureFile pdb = null; 439 try 440 { 441 String[] curfiles = jmb.getStructureFiles(); // files currently in viewer 442 // TODO: replace with reference fetching/transfer code (validate PDBentry 443 // as a DBRef?) 444 for (int pi = 0; pi < jmb.getPdbCount(); pi++) 445 { 446 String file = null; 447 thePdbEntry = jmb.getPdbEntry(pi); 448 if (thePdbEntry.getFile() == null) 449 { 450 /* 451 * Retrieve PDB data, save to file, attach to PDBEntry 452 */ 453 file = fetchPdbFile(thePdbEntry); 454 if (file == null) 455 { 456 errormsgs.append("'" + thePdbEntry.getId() + "' "); 457 } 458 } 459 else 460 { 461 /* 462 * Got file already - ignore if already loaded in Chimera. 463 */ 464 file = new File(thePdbEntry.getFile()).getAbsoluteFile() 465 .getPath(); 466 if (curfiles != null && curfiles.length > 0) 467 { 468 addingStructures = true; // already files loaded. 469 for (int c = 0; c < curfiles.length; c++) 470 { 471 if (curfiles[c].equals(file)) 472 { 473 file = null; 474 break; 475 } 476 } 477 } 478 } 479 if (file != null) 480 { 481 filePDB.add(thePdbEntry); 482 filePDBpos.add(Integer.valueOf(pi)); 483 files.append(" \"" + Platform.escapeBackslashes(file) + "\""); 484 } 485 } 486 } catch (OutOfMemoryError oomerror) 487 { 488 new OOMWarning("Retrieving PDB files: " + thePdbEntry.getId(), 489 oomerror); 490 } catch (Exception ex) 491 { 492 ex.printStackTrace(); 493 errormsgs.append( 494 "When retrieving pdbfiles for '" + thePdbEntry.getId() + "'"); 495 } 496 if (errormsgs.length() > 0) 497 { 498 499 JvOptionPane.showInternalMessageDialog(Desktop.desktop, 500 MessageManager.formatMessage( 501 "label.pdb_entries_couldnt_be_retrieved", new Object[] 502 { errormsgs.toString() }), 503 MessageManager.getString("label.couldnt_load_file"), 504 JvOptionPane.ERROR_MESSAGE); 505 } 506 507 if (files.length() > 0) 508 { 509 jmb.setFinishedInit(false); 510 if (!addingStructures) 511 { 512 try 513 { 514 initChimera(); 515 } catch (Exception ex) 516 { 517 Cache.log.error("Couldn't open Chimera viewer!", ex); 518 } 519 } 520 int num = -1; 521 for (PDBEntry pe : filePDB) 522 { 523 num++; 524 if (pe.getFile() != null) 525 { 526 try 527 { 528 int pos = filePDBpos.get(num).intValue(); 529 long startTime = startProgressBar(getViewerName() + " " 530 + MessageManager.getString("status.opening_file_for") 531 + " " + pe.getId()); 532 jmb.openFile(pe); 533 jmb.addSequence(pos, jmb.getSequence()[pos]); 534 File fl = new File(pe.getFile()); 535 DataSourceType protocol = DataSourceType.URL; 536 try 537 { 538 if (fl.exists()) 539 { 540 protocol = DataSourceType.FILE; 541 } 542 } catch (Throwable e) 543 { 544 } finally 545 { 546 stopProgressBar("", startTime); 547 } 548 // Explicitly map to the filename used by Chimera ; 549 550 pdb = jmb.getSsm().setMapping(jmb.getSequence()[pos], 551 jmb.getChains()[pos], pe.getFile(), protocol, 552 progressBar); 553 stashFoundChains(pdb, pe.getFile()); 554 555 } catch (OutOfMemoryError oomerror) 556 { 557 new OOMWarning( 558 "When trying to open and map structures from Chimera!", 559 oomerror); 560 } catch (Exception ex) 561 { 562 Cache.log.error( 563 "Couldn't open " + pe.getFile() + " in Chimera viewer!", 564 ex); 565 } finally 566 { 567 Cache.log.debug("File locations are " + files); 568 } 569 } 570 } 571 572 jmb.refreshGUI(); 573 jmb.setFinishedInit(true); 574 jmb.setLoadingFromArchive(false); 575 576 /* 577 * ensure that any newly discovered features (e.g. RESNUM) 578 * are notified to the FeatureRenderer (and added to any 579 * open feature settings dialog) 580 */ 581 FeatureRenderer fr = getBinding().getFeatureRenderer(null); 582 if (fr != null) 583 { 584 fr.featuresAdded(); 585 } 586 587 // refresh the sequence colours for the new structure(s) 588 for (AlignmentPanel ap : _colourwith) 589 { 590 jmb.updateColours(ap); 591 } 592 // do superposition if asked to 593 if (alignAddedStructures) 594 { 595 new Thread(new Runnable() 596 { 597 @Override 598 public void run() 599 { 600 alignStructs_withAllAlignPanels(); 601 } 602 }).start(); 603 } 604 addingStructures = false; 605 } 606 _started = false; 607 worker = null; 608 } 609 610 /** 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 613 * 614 * @param processingEntry 615 * @return 616 * @throws Exception 617 */ 618 stashFoundChains(StructureFile pdb, String file)619 private void stashFoundChains(StructureFile pdb, String file) 620 { 621 for (int i = 0; i < pdb.getChains().size(); i++) 622 { 623 String chid = new String( 624 pdb.getId() + ":" + pdb.getChains().elementAt(i).id); 625 jmb.getChainNames().add(chid); 626 jmb.getChainFile().put(chid, file); 627 } 628 } 629 fetchPdbFile(PDBEntry processingEntry)630 private String fetchPdbFile(PDBEntry processingEntry) throws Exception 631 { 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(); 638 639 /* 640 * Write 'fetching PDB' progress on AlignFrame as we are not yet visible 641 */ 642 String msg = MessageManager.formatMessage("status.fetching_pdb", 643 new Object[] 644 { pdbid }); 645 getAlignmentPanel().alignFrame.setProgressBar(msg, handle); 646 // long hdl = startProgressBar(MessageManager.formatMessage( 647 // "status.fetching_pdb", new Object[] 648 // { pdbid })); 649 try 650 { 651 pdbseq = pdbclient.getSequenceRecords(pdbid); 652 } catch (OutOfMemoryError oomerror) 653 { 654 new OOMWarning("Retrieving PDB id " + pdbid, oomerror); 655 } finally 656 { 657 msg = pdbid + " " + MessageManager.getString("label.state_completed"); 658 getAlignmentPanel().alignFrame.setProgressBar(msg, handle); 659 // stopProgressBar(msg, hdl); 660 } 661 /* 662 * If PDB data were saved and are not invalid (empty alignment), return the 663 * file path. 664 */ 665 if (pdbseq != null && pdbseq.getHeight() > 0) 666 { 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); 671 } 672 return filePath; 673 } 674 675 /** 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. 678 * 679 * @param msg 680 * @param handle 681 */ startProgressBar(String msg)682 public long startProgressBar(String msg) 683 { 684 // TODO would rather have startProgress/stopProgress as the 685 // IProgressIndicator interface 686 long tm = random.nextLong(); 687 if (progressBar != null) 688 { 689 progressBar.setProgressBar(msg, tm); 690 } 691 return tm; 692 } 693 694 /** 695 * End the progress bar with the specified handle, leaving a message (if not 696 * null) on the status bar 697 * 698 * @param msg 699 * @param handle 700 */ stopProgressBar(String msg, long handle)701 public void stopProgressBar(String msg, long handle) 702 { 703 if (progressBar != null) 704 { 705 progressBar.setProgressBar(msg, handle); 706 } 707 } 708 709 @Override eps_actionPerformed(ActionEvent e)710 public void eps_actionPerformed(ActionEvent e) 711 { 712 throw new Error(MessageManager 713 .getString("error.eps_generation_not_implemented")); 714 } 715 716 @Override png_actionPerformed(ActionEvent e)717 public void png_actionPerformed(ActionEvent e) 718 { 719 throw new Error(MessageManager 720 .getString("error.png_generation_not_implemented")); 721 } 722 723 @Override showHelp_actionPerformed(ActionEvent actionEvent)724 public void showHelp_actionPerformed(ActionEvent actionEvent) 725 { 726 try 727 { 728 BrowserLauncher 729 .openURL("https://www.cgl.ucsf.edu/chimera/docs/UsersGuide"); 730 } catch (IOException ex) 731 { 732 } 733 } 734 735 @Override getBinding()736 public AAStructureBindingModel getBinding() 737 { 738 return jmb; 739 } 740 741 /** 742 * Ask Chimera to save its session to the designated file path, or to a 743 * temporary file if the path is null. Returns the file path if successful, 744 * else null. 745 * 746 * @param filepath 747 * @see getStateInfo 748 */ saveSession(String filepath)749 protected String saveSession(String filepath) 750 { 751 String pathUsed = filepath; 752 try 753 { 754 if (pathUsed == null) 755 { 756 File tempFile = File.createTempFile("chimera", ".py"); 757 tempFile.deleteOnExit(); 758 pathUsed = tempFile.getPath(); 759 } 760 boolean result = jmb.saveSession(pathUsed); 761 if (result) 762 { 763 this.chimeraSessionFile = pathUsed; 764 return pathUsed; 765 } 766 } catch (IOException e) 767 { 768 } 769 return null; 770 } 771 772 /** 773 * Returns a string representing the state of the Chimera session. This is 774 * done by requesting Chimera to save its session to a temporary file, then 775 * reading the file contents. Returns an empty string on any error. 776 */ 777 @Override getStateInfo()778 public String getStateInfo() 779 { 780 String sessionFile = saveSession(null); 781 if (sessionFile == null) 782 { 783 return ""; 784 } 785 InputStream is = null; 786 try 787 { 788 File f = new File(sessionFile); 789 byte[] bytes = new byte[(int) f.length()]; 790 is = new FileInputStream(sessionFile); 791 is.read(bytes); 792 return new String(bytes); 793 } catch (IOException e) 794 { 795 return ""; 796 } finally 797 { 798 if (is != null) 799 { 800 try 801 { 802 is.close(); 803 } catch (IOException e) 804 { 805 // ignore 806 } 807 } 808 } 809 } 810 811 @Override fitToWindow_actionPerformed()812 protected void fitToWindow_actionPerformed() 813 { 814 jmb.focusView(); 815 } 816 817 @Override getViewerType()818 public ViewerType getViewerType() 819 { 820 return ViewerType.CHIMERA; 821 } 822 823 @Override getViewerName()824 protected String getViewerName() 825 { 826 return "Chimera"; 827 } 828 829 /** 830 * Sends commands to align structures according to associated alignment(s). 831 * 832 * @return 833 */ 834 @Override alignStructs_withAllAlignPanels()835 protected String alignStructs_withAllAlignPanels() 836 { 837 String reply = super.alignStructs_withAllAlignPanels(); 838 if (reply != null) 839 { 840 statusBar.setText("Superposition failed: " + reply); 841 } 842 return reply; 843 } 844 845 @Override getIProgressIndicator()846 protected IProgressIndicator getIProgressIndicator() 847 { 848 return progressBar; 849 } 850 } 851