X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fgui%2FChimeraViewFrame.java;h=a89af2c56c2992d0cd16add3f333c48da63e2121;hb=a7ea1ba47dae32481aa6390a29cef6c7c5a403c4;hp=02865e1d2c85b36226fa25056ad90190a0297e11;hpb=5720f2cea034f014f366886b3cb9a170a909e988;p=jalview.git diff --git a/src/jalview/gui/ChimeraViewFrame.java b/src/jalview/gui/ChimeraViewFrame.java index 02865e1..a89af2c 100644 --- a/src/jalview/gui/ChimeraViewFrame.java +++ b/src/jalview/gui/ChimeraViewFrame.java @@ -1,6 +1,6 @@ /* - * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2) - * Copyright (C) 2014 The Jalview Authors + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors * * This file is part of Jalview. * @@ -20,33 +20,6 @@ */ package jalview.gui; -import jalview.api.SequenceStructureBinding; -import jalview.api.structures.JalviewStructureDisplayI; -import jalview.bin.Cache; -import jalview.datamodel.Alignment; -import jalview.datamodel.AlignmentI; -import jalview.datamodel.ColumnSelection; -import jalview.datamodel.PDBEntry; -import jalview.datamodel.SequenceI; -import jalview.gui.ViewSelectionMenu.ViewSetProvider; -import jalview.io.AppletFormatAdapter; -import jalview.io.JalviewFileChooser; -import jalview.io.JalviewFileView; -import jalview.jbgui.GStructureViewer; -import jalview.schemes.BuriedColourScheme; -import jalview.schemes.ColourSchemeI; -import jalview.schemes.HelixColourScheme; -import jalview.schemes.HydrophobicColourScheme; -import jalview.schemes.PurinePyrimidineColourScheme; -import jalview.schemes.StrandColourScheme; -import jalview.schemes.TaylorColourScheme; -import jalview.schemes.TurnColourScheme; -import jalview.schemes.ZappoColourScheme; -import jalview.util.MessageManager; -import jalview.util.Platform; -import jalview.ws.dbsources.Pdb; - -import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; @@ -59,6 +32,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.Random; import java.util.Vector; import javax.swing.JCheckBoxMenuItem; @@ -72,40 +46,44 @@ import javax.swing.event.InternalFrameEvent; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; +import jalview.bin.Cache; +import jalview.datamodel.Alignment; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.ColumnSelection; +import jalview.datamodel.PDBEntry; +import jalview.datamodel.SequenceI; +import jalview.ext.rbvi.chimera.JalviewChimeraBinding; +import jalview.io.AppletFormatAdapter; +import jalview.io.JalviewFileChooser; +import jalview.io.JalviewFileView; +import jalview.schemes.BuriedColourScheme; +import jalview.schemes.ColourSchemeI; +import jalview.schemes.HelixColourScheme; +import jalview.schemes.HydrophobicColourScheme; +import jalview.schemes.PurinePyrimidineColourScheme; +import jalview.schemes.StrandColourScheme; +import jalview.schemes.TaylorColourScheme; +import jalview.schemes.TurnColourScheme; +import jalview.schemes.ZappoColourScheme; +import jalview.structures.models.AAStructureBindingModel; +import jalview.util.MessageManager; +import jalview.util.Platform; +import jalview.ws.dbsources.Pdb; + /** * GUI elements for handlnig an external chimera display * * @author jprocter * */ -public class ChimeraViewFrame extends GStructureViewer implements Runnable, - ViewSetProvider, JalviewStructureDisplayI - +public class ChimeraViewFrame extends StructureViewerBase { - private JalviewChimeraBindingModel jmb; - - /* - * list of sequenceSet ids associated with the view - */ - private ArrayList _aps = new ArrayList(); - - /* - * list of alignment panels to use for superposition - */ - private Vector _alignwith = new Vector(); - - /* - * list of alignment panels that are used for colouring structures by aligned - * sequences - */ - private Vector _colourwith = new Vector(); + private JalviewChimeraBinding jmb; private boolean allChainsSelected = false; private boolean alignAddedStructures = false; - AlignmentPanel ap; - /* * state flag for PDB retrieval thread */ @@ -115,13 +93,20 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, private IProgressIndicator progressBar = null; - private String viewId = null; - /* * pdb retrieval thread. */ private Thread worker = null; + /* + * Path to Chimera session file. This is set when an open Jalview/Chimera + * session is saved, or on restore from a Jalview project (if it holds the + * filename of any saved Chimera sessions). + */ + private String chimeraSessionFile = null; + + private Random random = new Random(); + /** * Initialise menu options. */ @@ -244,9 +229,9 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, // TODO : Fix multiple seq to one chain issue here. ap.getStructureSelectionManager().setMapping(seq, chains, alreadyMapped, AppletFormatAdapter.FILE); - if (ap.seqPanel.seqCanvas.fr != null) + if (ap.getSeqPanel().seqCanvas.fr != null) { - ap.seqPanel.seqCanvas.fr.featuresAdded(); + ap.getSeqPanel().seqCanvas.fr.featuresAdded(); ap.paintAlignment(true); } @@ -261,15 +246,17 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, final ChimeraViewFrame topView = ((ChimeraViewFrame) frame); // JBPNOTE: this looks like a binding routine, rather than a gui // routine - for (int pe = 0; pe < topView.jmb.pdbentry.length; pe++) + for (int pe = 0; pe < topView.jmb.getPdbCount(); pe++) { - if (topView.jmb.pdbentry[pe].getFile().equals(alreadyMapped)) + if (topView.jmb.getPdbEntry(pe).getFile() + .equals( + alreadyMapped)) { topView.jmb.addSequence(pe, seq); topView.addAlignmentPanel(ap); // add it to the set used for colouring topView.useAlignmentPanelForColourbyseq(ap); - topView.buildChimeraActionMenu(); + topView.buildActionMenu(); ap.getStructureSelectionManager() .sequenceColoursChanged(ap); break; @@ -288,6 +275,14 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, for (ChimeraViewFrame topView : existingViews) { // TODO: highlight topView in view somehow + /* + * JAL-1742 exclude view with this structure already mapped (don't offer + * to align chain B to chain A of the same structure) + */ + if (topView.hasPdbId(pdbentry.getId())) + { + continue; + } int option = JOptionPane.showInternalConfirmDialog(Desktop.desktop, MessageManager.formatMessage("label.add_pdbentry_to_view", new Object[] @@ -312,12 +307,31 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, { seq }); } + /** + * Create a helper to manage progress bar display + */ + protected void createProgressBar() + { + if (progressBar == null) + { + progressBar = new ProgressBar(statusPanel, statusBar); + } + } + + protected boolean hasPdbId(String pdbId) + { + return jmb.hasPdbId(pdbId); + } + private void openNewChimera(AlignmentPanel ap, PDBEntry[] pdbentrys, SequenceI[][] seqs) { - progressBar = ap.alignFrame; + createProgressBar(); + + String[][] chains = extractChains(seqs); jmb = new JalviewChimeraBindingModel(this, - ap.getStructureSelectionManager(), pdbentrys, seqs, null, null); + ap.getStructureSelectionManager(), pdbentrys, seqs, chains, + null); addAlignmentPanel(ap); useAlignmentPanelForColourbyseq(ap); if (pdbentrys.length > 1) @@ -328,145 +342,99 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, jmb.setColourBySequence(true); setSize(400, 400); // probably should be a configurable/dynamic default here initMenus(); - worker = null; - { - addingStructures = false; - worker = new Thread(this); - worker.start(); - } + + addingStructures = false; + worker = new Thread(this); + worker.start(); + this.addInternalFrameListener(new InternalFrameAdapter() { public void internalFrameClosing(InternalFrameEvent internalFrameEvent) { - closeViewer(); + closeViewer(false); } }); } /** - * create a new viewer containing several structures superimposed using the - * given alignPanel. + * Retrieve chains for sequences by inspecting their PDB refs. The hope is + * that the first will be to the sequence's own chain. Really need a more + * managed way of doing this. * - * @param ap - * @param pe * @param seqs + * @return */ - public ChimeraViewFrame(AlignmentPanel ap, PDBEntry[] pe, - SequenceI[][] seqs) - { - super(); - openNewChimera(ap, pe, seqs); - } - - public AlignmentPanel[] getAllAlignmentPanels() + protected String[][] extractChains(SequenceI[][] seqs) { - AlignmentPanel[] t, list = new AlignmentPanel[0]; - for (String setid : _aps) + String[][] chains = new String[seqs.length][]; + for (int i = 0; i < seqs.length; i++) { - AlignmentPanel[] panels = PaintRefresher.getAssociatedPanels(setid); - if (panels != null) + chains[i] = new String[seqs[i].length]; + int seqno = 0; + for (SequenceI seq : seqs[i]) { - t = new AlignmentPanel[list.length + panels.length]; - System.arraycopy(list, 0, t, 0, list.length); - System.arraycopy(panels, 0, t, list.length, panels.length); - list = t; + String chain = null; + if (seq.getDatasetSequence() != null) + { + Vector pdbrefs = seq.getDatasetSequence().getPDBId(); + if (pdbrefs != null && pdbrefs.size() > 0) + { + chain = pdbrefs.get(0).getChainCode(); + } + } + chains[i][seqno++] = chain; } } - - return list; + return chains; } /** - * set the primary alignmentPanel reference and add another alignPanel to the - * list of ones to use for colouring and aligning + * create a new viewer containing several structures superimposed using the + * given alignPanel. * - * @param nap + * @param ap + * @param pe + * @param seqs */ - public void addAlignmentPanel(AlignmentPanel nap) + public ChimeraViewFrame(AlignmentPanel ap, PDBEntry[] pe, + SequenceI[][] seqs) { - if (ap == null) - { - ap = nap; - } - if (!_aps.contains(nap.av.getSequenceSetId())) - { - _aps.add(nap.av.getSequenceSetId()); - } + super(); + openNewChimera(ap, pe, seqs); } /** - * remove any references held to the given alignment panel + * Create a new viewer from saved session state data including Chimera session + * file. * - * @param nap + * @param chimeraSession + * + * @param alignPanel + * @param pdbArray + * @param seqsArray + * @param colourByChimera + * @param colourBySequence */ - public void removeAlignmentPanel(AlignmentPanel nap) - { - try - { - _alignwith.remove(nap); - _colourwith.remove(nap); - if (ap == nap) - { - ap = null; - for (AlignmentPanel aps : getAllAlignmentPanels()) - { - if (aps != nap) - { - ap = aps; - break; - } - } - } - } catch (Exception ex) - { - } - if (ap != null) - { - buildChimeraActionMenu(); - } - } - - public void useAlignmentPanelForSuperposition(AlignmentPanel nap) - { - addAlignmentPanel(nap); - if (!_alignwith.contains(nap)) - { - _alignwith.add(nap); - } - } - - public void excludeAlignmentPanelForSuperposition(AlignmentPanel nap) + public ChimeraViewFrame(String chimeraSession, AlignmentPanel alignPanel, + PDBEntry[] pdbArray, + SequenceI[][] seqsArray, boolean colourByChimera, + boolean colourBySequence) { - if (_alignwith.contains(nap)) - { - _alignwith.remove(nap); - } - } - - public void useAlignmentPanelForColourbyseq(AlignmentPanel nap, - boolean enableColourBySeq) - { - useAlignmentPanelForColourbyseq(nap); - jmb.setColourBySequence(enableColourBySeq); - seqColour.setSelected(enableColourBySeq); - viewerColour.setSelected(!enableColourBySeq); - } - - public void useAlignmentPanelForColourbyseq(AlignmentPanel nap) - { - addAlignmentPanel(nap); - if (!_colourwith.contains(nap)) + super(); + this.chimeraSessionFile = chimeraSession; + openNewChimera(alignPanel, pdbArray, seqsArray); + if (colourByChimera) { - _colourwith.add(nap); + jmb.setColourBySequence(false); + seqColour.setSelected(false); + viewerColour.setSelected(true); } - } - - public void excludeAlignmentPanelForColourbyseq(AlignmentPanel nap) - { - if (_colourwith.contains(nap)) + else if (colourBySequence) { - _colourwith.remove(nap); + jmb.setColourBySequence(true); + seqColour.setSelected(true); + viewerColour.setSelected(false); } } @@ -535,7 +503,7 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, { if (frame instanceof ChimeraViewFrame) { - if (((ChimeraViewFrame) frame).isLinkedWith(apanel)) + if (((StructureViewerBase) frame).isLinkedWith(apanel)) { result.add((ChimeraViewFrame) frame); } @@ -544,19 +512,34 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, return result; } - void initChimera(String command) + /** + * Launch Chimera. If we have a chimera session file name, send Chimera the + * command to open its saved session file. + */ + void initChimera() { jmb.setFinishedInit(false); - // TODO: consider waiting until the structure/view is fully loaded before - // displaying - jalview.gui.Desktop.addInternalFrame(this, jmb.getViewerTitle(true), + jalview.gui.Desktop.addInternalFrame(this, jmb.getViewerTitle("Chimera", true), getBounds().width, getBounds().height); - if (command == null) + + /* + * Pass an empty 'command' to launch Chimera + */ + jmb.evalStateCommand("", false); + + if (this.chimeraSessionFile != null) { - command = ""; + boolean opened = jmb.openSession(chimeraSessionFile); + if (!opened) + { + System.err + .println("An error occurred opening Chimera session file " + + chimeraSessionFile); + } } - jmb.evalStateCommand(command, false); jmb.setFinishedInit(true); + + jmb.startChimeraListener(); } void setChainMenuItems(List chainNames) @@ -626,21 +609,28 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, * Close down this instance of Jalview's Chimera viewer, giving the user the * option to close the associated Chimera window (process). They may wish to * keep it open until they have had an opportunity to save any work. + * + * @param closeChimera + * if true, close any linked Chimera process; if false, prompt first */ - public void closeViewer() + public void closeViewer(boolean closeChimera) { if (jmb.isChimeraRunning()) { - String prompt = MessageManager - .formatMessage("label.confirm_close_chimera", new Object[] - { jmb.getViewerTitle(false) }); - prompt = JvSwingUtils.wrapTooltip(true, prompt); - int confirm = JOptionPane.showConfirmDialog(this, prompt, - MessageManager.getString("label.close_viewer"), - JOptionPane.YES_NO_OPTION); - jmb.closeViewer(confirm == JOptionPane.YES_OPTION); + if (!closeChimera) + { + String prompt = MessageManager.formatMessage( + "label.confirm_close_chimera", new Object[] + { jmb.getViewerTitle("Chimera", false) }); + prompt = JvSwingUtils.wrapTooltip(true, prompt); + int confirm = JOptionPane.showConfirmDialog(this, prompt, + MessageManager.getString("label.close_viewer"), + JOptionPane.YES_NO_OPTION); + closeChimera = confirm == JOptionPane.YES_OPTION; + } + jmb.closeViewer(closeChimera); } - ap = null; + setAlignmentPanel(null); _aps.clear(); _alignwith.clear(); _colourwith.clear(); @@ -667,10 +657,10 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, String[] curfiles = jmb.getPdbFile(); // files currently in viewer // TODO: replace with reference fetching/transfer code (validate PDBentry // as a DBRef?) - for (int pi = 0; pi < jmb.pdbentry.length; pi++) + for (int pi = 0; pi < jmb.getPdbCount(); pi++) { String file = null; - thePdbEntry = jmb.pdbentry[pi]; + thePdbEntry = jmb.getPdbEntry(pi); if (thePdbEntry.getFile() == null) { /* @@ -736,7 +726,7 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, { try { - initChimera(""); + initChimera(); } catch (Exception ex) { Cache.log.error("Couldn't open Chimera viewer!", ex); @@ -751,8 +741,10 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, try { int pos = filePDBpos.get(num).intValue(); + long startTime = startProgressBar("Chimera " + + MessageManager.getString("status.opening_file")); jmb.openFile(pe); - jmb.addSequence(pos, jmb.sequence[pos]); + jmb.addSequence(pos, jmb.getSequence()[pos]); File fl = new File(pe.getFile()); String protocol = AppletFormatAdapter.URL; try @@ -763,10 +755,14 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, } } catch (Throwable e) { + } finally + { + stopProgressBar("", startTime); } // Explicitly map to the filename used by Chimera ; // TODO: use pe.getId() instead of pe.getFile() ? - jmb.ssm.setMapping(jmb.sequence[pos], null, pe.getFile(), + jmb.getSsm().setMapping(jmb.getSequence()[pos], null, + pe.getFile(), protocol); } catch (OutOfMemoryError oomerror) { @@ -823,13 +819,9 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, Pdb pdbclient = new Pdb(); AlignmentI pdbseq = null; String pdbid = processingEntry.getId(); - long hdl = pdbid.hashCode() - System.currentTimeMillis(); - if (progressBar != null) - { - progressBar.setProgressBar(MessageManager.formatMessage( - "status.fetching_pdb", new Object[] - { pdbid }), hdl); - } + long hdl = startProgressBar(MessageManager.formatMessage( + "status.fetching_pdb", new Object[] + { pdbid })); try { pdbseq = pdbclient.getSequenceRecords(pdbid); @@ -838,11 +830,9 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, new OOMWarning("Retrieving PDB id " + pdbid, oomerror); } finally { - if (progressBar != null) - { - progressBar.setProgressBar( - MessageManager.getString("label.state_completed"), hdl); - } + String msg = pdbid + " " + + MessageManager.getString("label.state_completed"); + stopProgressBar(msg, hdl); } /* * If PDB data were saved and are not invalid (empty alignment), return the @@ -851,13 +841,40 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, if (pdbseq != null && pdbseq.getHeight() > 0) { // just use the file name from the first sequence's first PDBEntry - filePath = new File(((PDBEntry) pdbseq.getSequenceAt(0).getPDBId() - .elementAt(0)).getFile()).getAbsolutePath(); + filePath = new File(pdbseq.getSequenceAt(0).getPDBId() + .elementAt(0).getFile()).getAbsolutePath(); processingEntry.setFile(filePath); } return filePath; } + /** + * Convenience method to update the progress bar if there is one. Be sure to + * call stopProgressBar with the returned handle to remove the message. + * + * @param msg + * @param handle + */ + public long startProgressBar(String msg) + { + // TODO would rather have startProgress/stopProgress as the + // IProgressIndicator interface + long tm = random.nextLong(); + if (progressBar != null) + { + progressBar.setProgressBar(msg, tm); + } + return tm; + } + + public void stopProgressBar(String msg, long handle) + { + if (progressBar != null) + { + progressBar.setProgressBar(msg, handle); + } + } + @Override public void pdbFile_actionPerformed(ActionEvent actionEvent) { @@ -914,11 +931,7 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, jalview.gui.CutAndPasteTransfer cap = new jalview.gui.CutAndPasteTransfer(); try { - for (int pdbe = 0; pdbe < jmb.pdbentry.length; pdbe++) - { - cap.appendText(jmb.printMapping(jmb.pdbentry[pdbe].getFile())); - cap.appendText("\n"); - } + cap.appendText(jmb.printMappings()); } catch (OutOfMemoryError e) { new OOMWarning( @@ -970,16 +983,16 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, { if (!jmb.isLoadingFromArchive()) { - if (_colourwith.size() == 0 && ap != null) + if (_colourwith.size() == 0 && getAlignmentPanel() != null) { // Make the currently displayed alignment panel the associated view - _colourwith.add(ap.alignFrame.alignPanel); + _colourwith.add(getAlignmentPanel().alignFrame.alignPanel); } } // Set the colour using the current view for the associated alignframe for (AlignmentPanel ap : _colourwith) { - jmb.colourBySequence(ap.av.showSequenceFeatures, ap); + jmb.colourBySequence(ap.av.isShowSequenceFeatures(), ap); } } } @@ -1084,15 +1097,6 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, } } - public String getViewId() - { - if (viewId == null) - { - viewId = System.currentTimeMillis() + "." + this.hashCode(); - } - return viewId; - } - public void updateTitleAndMenus() { if (jmb.fileLoadingError != null && jmb.fileLoadingError.length() > 0) @@ -1100,10 +1104,10 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, repaint(); return; } - setChainMenuItems(jmb.chainNames); + setChainMenuItems(jmb.getChainNames()); - this.setTitle(jmb.getViewerTitle(true)); - if (jmb.getPdbFile().length > 1 && jmb.sequence.length > 1) + this.setTitle(jmb.getViewerTitle("Chimera", true)); + if (jmb.getPdbFile().length > 1 && jmb.getSequence().length > 1) { viewerActionMenu.setVisible(true); } @@ -1113,26 +1117,6 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, } } - protected void buildChimeraActionMenu() - { - if (_alignwith == null) - { - _alignwith = new Vector(); - } - if (_alignwith.size() == 0 && ap != null) - { - _alignwith.add(ap); - } - ; - for (Component c : viewerActionMenu.getMenuComponents()) - { - if (c != alignStructs) - { - viewerActionMenu.remove((JMenuItem) c); - } - } - } - /* * (non-Javadoc) * @@ -1148,14 +1132,14 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, private void alignStructs_withAllAlignPanels() { - if (ap == null) + if (getAlignmentPanel() == null) { return; } ; if (_alignwith.size() == 0) { - _alignwith.add(ap); + _alignwith.add(getAlignmentPanel()); } ; try @@ -1207,42 +1191,46 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable, return ap; } } - return ap; + return getAlignmentPanel(); } - /** - * - * @param ap2 - * @return true if this Chimera instance is linked with the given alignPanel - */ - public boolean isLinkedWith(AlignmentPanel ap2) - { - return _aps.contains(ap2.av.getSequenceSetId()); - } - - public boolean isUsedforaligment(AlignmentPanel ap2) + @Override + public AAStructureBindingModel getBinding() { - - return (_alignwith != null) && _alignwith.contains(ap2); + return jmb; } - public boolean isUsedforcolourby(AlignmentPanel ap2) + /** + * Ask Chimera to save its session to the designated file path. Returns true + * if successful, else false. + * + * @param filepath + * @see getStateInfo + */ + public boolean saveSession(String filepath) { - return (_colourwith != null) && _colourwith.contains(ap2); + boolean result = jmb.saveSession(filepath); + if (result) + { + this.chimeraSessionFile = filepath; + } + return result; } /** - * - * @return TRUE if the view is NOT being coloured by sequence associations. + * Returns the file path of the Chimera session file the last time it was + * saved. If it was never saved, returns an empty string. There is no + * guarantee that the Chimera session has not changed since it was saved. */ - public boolean isColouredByChimera() + @Override + public String getStateInfo() { - return !jmb.isColourBySequence(); + return this.chimeraSessionFile; } - public SequenceStructureBinding getBinding() + @Override + protected void fitToWindow_actionPerformed() { - return jmb; + jmb.focusView(); } - }