package de.codesourcery.jasm16.ide.ui.views; /** * Copyright 2012 Tobias Gierke <tobias.gierke@code-sourcery.de> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.SwingUtilities; import de.codesourcery.jasm16.Address; import de.codesourcery.jasm16.emulator.EmulationListener; import de.codesourcery.jasm16.emulator.IEmulator; import de.codesourcery.jasm16.emulator.IEmulator.EmulationSpeed; import de.codesourcery.jasm16.ide.ui.utils.UIUtils; import de.codesourcery.jasm16.ide.ui.viewcontainers.DebuggingPerspective; import de.codesourcery.jasm16.ide.ui.viewcontainers.IViewContainer; public class EmulatorControllerView extends AbstractView { public static final String VIEW_ID = "emulator-controller-view"; private static final AtomicBoolean showDialog = new AtomicBoolean(false); // @GuardedBy( showDialog ) private static DialogHelper worker = null; private static final AtomicLong LISTENER_REGISTRATION_COUNT = new AtomicLong(0); private JPanel panel; private final JButton singleStepButton = new JButton("Step"); private final JButton stepReturnButton = new JButton("Step return"); private final JButton skipButton = new JButton("Skip"); private final JButton runButton = new JButton("Run"); private final JButton stopButton = new JButton("Stop"); private final JButton resetButton = new JButton("Reset"); private JCheckBox runAtRealSpeed; private final DebuggingPerspective perspective; private IEmulator emulator; private final MyEmulationListener listener; private final class DialogHelper { private final AtomicReference<JFrame> dialog = new AtomicReference<JFrame>(); private final IViewContainer parent; public DialogHelper() { parent = getViewContainer(); if ( parent == null ) { throw new IllegalStateException("NULL parent ?"); } } public void showDialog() { UIUtils.invokeLater( new Runnable() { @Override public void run() { System.out.println("Creating dialog (EDT="+SwingUtilities.isEventDispatchThread()+")"); final JFrame tmp = UIUtils.createMessageFrame( "Calibrating emulation speed" , "Please wait, benchmarking your system..."); tmp.setVisible( true ); parent.setBlockAllUserInput( true ); dialog.set(tmp); } }); } public void closeDialog() { UIUtils.invokeLater( new Runnable() { @Override public void run() { System.out.println("Disposing dialog (EDT="+SwingUtilities.isEventDispatchThread()+")"); while( dialog.get() == null ) {} dialog.get().dispose(); parent.setBlockAllUserInput( false ); } }); } } protected final class MyEmulationListener extends EmulationListener { // this view may be visible in multiple instances (and thus this register may be registered more than once), this flag // is used to make sure only exactly one of the listeners responds to beforeCalibration() / afterCalibration() messages private final boolean isFirstListener; public MyEmulationListener(boolean isFirstListener) { this.isFirstListener = isFirstListener; } public void beforeCalibration(IEmulator emulator) { if ( ! isFirstListener ) { return; } synchronized( showDialog ) { if ( showDialog.compareAndSet(false,true) && worker == null ) { worker = new DialogHelper(); worker.showDialog(); } } } public void afterCalibration(IEmulator emulator) { if ( ! isFirstListener ) { return; } synchronized( showDialog ) { if ( showDialog.compareAndSet(true,false) && worker != null ) { worker.closeDialog(); worker = null; } } } public void onEmulationSpeedChange(EmulationSpeed oldSpeed, EmulationSpeed newSpeed) { if ( runAtRealSpeed != null ) { runAtRealSpeed.setSelected( newSpeed == EmulationSpeed.REAL_SPEED ); } } public void afterMemoryLoad(IEmulator emulator, Address startAddress, int lengthInBytes) { updateButtonStates(false); } @Override public void afterReset(IEmulator emulator) { updateButtonStates( false ); } @Override protected void beforeContinuousExecutionHook() { updateButtonStates( true ); } @Override public void onStopHook(IEmulator emulator, Address previousPC, Throwable emulationError) { updateButtonStates( false ); } }; // helper interface for invoking IEmulator methods from a non-EDT thread protected abstract class Invoker implements Runnable { public abstract void invoke(IEmulator emulator); @Override public final void run() { invoke(emulator); } } public EmulatorControllerView(DebuggingPerspective perspective, IEmulator emulator) { if ( perspective == null ) { throw new IllegalArgumentException("perspective must not be null"); } this.perspective = perspective; this.emulator = emulator; listener = new MyEmulationListener( LISTENER_REGISTRATION_COUNT.incrementAndGet() == 1 ); emulator.addEmulationListener( listener ); } private void updateButtonStates(final boolean emulatorRunningContinously) { final Runnable runnable = new Runnable() { @Override public void run() { skipButton.setEnabled( ! emulatorRunningContinously ); singleStepButton.setEnabled( ! emulatorRunningContinously ); runButton.setEnabled( ! emulatorRunningContinously ); stopButton.setEnabled( emulatorRunningContinously ); if ( emulatorRunningContinously ) { stepReturnButton.setEnabled( false ); } else { stepReturnButton.setEnabled( true ); } resetButton.setEnabled( true ); } }; UIUtils.invokeLater( runnable ); } @Override public void refreshDisplay() { } @Override public void disposeHook() { if ( this.emulator != null ) { this.emulator.removeEmulationListener( listener ); this.emulator = null; } } protected JPanel createPanel() { // setup top panel final JPanel buttonBar = new JPanel(); buttonBar.setLayout( new GridBagLayout() ); int x = 0; // =========== "SINGLE STEP" button ============ singleStepButton.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { executeAsynchronously( new Invoker() { @Override public void invoke(IEmulator emulator) { emulator.executeOneInstruction(); updateButtonStates( false ); } }); } }); GridBagConstraints cnstrs = constraints( x++ , 0 , false , true , GridBagConstraints.NONE ); buttonBar.add( singleStepButton , cnstrs ); // =========== "STEP RETURN" button ============ stepReturnButton.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { executeAsynchronously( new Invoker() { @Override public void invoke(IEmulator emulator) { if ( emulator.canStepReturn() ) { emulator.stepReturn(); } else { emulator.executeOneInstruction(); } } }); } }); cnstrs = constraints( x++ , 0 , false , true , GridBagConstraints.NONE ); buttonBar.add( stepReturnButton , cnstrs ); // =========== "Skip" button ============ skipButton.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { executeAsynchronously( new Invoker() { @Override public void invoke(IEmulator emulator) { emulator.skipCurrentInstruction(); } }); } }); cnstrs = constraints( x++ , 0 , false , true , GridBagConstraints.NONE ); buttonBar.add( skipButton , cnstrs ); // =========== "RUN" button ============ runButton.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { executeAsynchronously( new Invoker() { @Override public void invoke(IEmulator emulator) { emulator.start(); } }); } }); cnstrs = constraints( x++ , 0 , false , true , GridBagConstraints.NONE ); buttonBar.add( runButton , cnstrs ); // =========== "STOP" button ============ stopButton.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { executeAsynchronously( new Invoker() { @Override public void invoke(IEmulator emulator) { emulator.stop(); } }); } }); cnstrs = constraints( x++ , 0 , false , true , GridBagConstraints.NONE ); buttonBar.add( stopButton , cnstrs ); // =========== "RESET" button ============ resetButton.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { executeAsynchronously( new Invoker() { @Override public void invoke(IEmulator emulator) { perspective.resetEmulator(); } }); } }); cnstrs = constraints( x++ , 0 , false , true , GridBagConstraints.NONE ); buttonBar.add( resetButton , cnstrs ); // =========== "Run at full speed" checkbox ============ runAtRealSpeed = new JCheckBox("Run at real speed",emulator.getEmulationSpeed() == IEmulator.EmulationSpeed.REAL_SPEED); runAtRealSpeed.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { final boolean isSelected = runAtRealSpeed.isSelected(); executeAsynchronously( new Invoker() { @Override public void invoke(IEmulator emulator) { if ( isSelected ) { emulator.setEmulationSpeed( IEmulator.EmulationSpeed.REAL_SPEED ); } else { emulator.setEmulationSpeed( isSelected ? EmulationSpeed.REAL_SPEED : EmulationSpeed.MAX_SPEED ); } }} ); } }); cnstrs = constraints( x++ , 0 , true , true , GridBagConstraints.NONE ); buttonBar.add( runAtRealSpeed , cnstrs ); updateButtonStates( false ); return buttonBar; } @Override protected JPanel getPanel() { if ( panel == null ) { panel = createPanel(); } return panel; } @Override public String getTitle() { return "Emulator control"; } @Override public String getID() { return VIEW_ID; } }