/* BaseSwtJavaClient.java (c) 2010-2017 Edward Swartz All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at http://www.eclipse.org/legal/epl-v10.html */ package v9t9.gui.client.swt; import java.util.HashSet; import java.util.Set; import java.util.TimerTask; import org.eclipse.jface.wizard.WizardDialog; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTError; import org.eclipse.swt.SWTException; import org.eclipse.swt.events.ShellAdapter; import org.eclipse.swt.events.ShellEvent; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import v9t9.common.client.IClient; import v9t9.common.client.IEmulatorContentHandler; import v9t9.common.client.IEmulatorContentSource; import v9t9.common.client.IKeyboardHandler; import v9t9.common.client.ISettingsHandler; import v9t9.common.client.ISoundHandler; import v9t9.common.client.IVideoRenderer; import v9t9.common.demos.DemoContentSource; import v9t9.common.dsr.IPrinterImageEngine; import v9t9.common.dsr.IPrinterImageHandler; import v9t9.common.events.IEventNotifier; import v9t9.common.files.EmulatedDiskContentSource; import v9t9.common.files.IFileExecutionHandler; import v9t9.common.files.IFileExecutor; import v9t9.common.hardware.IVdpChip; import v9t9.common.hardware.VdpTMS9918AConsts; import v9t9.common.machine.IMachine; import v9t9.common.machine.IRegisterAccess.IRegisterWriteListener; import v9t9.common.machine.TerminatedException; import v9t9.common.modules.ModuleContentSource; import v9t9.common.settings.Settings; import v9t9.common.speech.ISpeechDataSender; import v9t9.common.video.VideoThread; import v9t9.gui.client.swt.fileimport.DoNothingFileExecutor; import v9t9.gui.client.swt.handlers.DemoContentHandler; import v9t9.gui.client.swt.handlers.FileExecutorContentHandler; import v9t9.gui.client.swt.handlers.ModuleContentHandler; import v9t9.gui.client.swt.shells.PrinterImageShell; import v9t9.gui.client.swt.wizards.SetupWizard; import v9t9.gui.sound.JavaSoundHandler; import v9t9.server.client.EmulatorServerBase; import ejs.base.timer.FastTimer; /** * @author ejs * */ public abstract class BaseSwtJavaClient implements IClient { static { // set early for GNOME Shell Display.setAppName("V9t9"); } protected IVdpChip video; protected IMachine machine; protected ISwtVideoRenderer videoRenderer; protected Display display; protected final int QUANTUM = 1000 / 60; protected final int soundTick = 100; protected final int clientTick = 100; protected IEventNotifier eventNotifier; protected final ISettingsHandler settingsHandler; protected ISoundHandler soundHandler; protected FastTimer keyTimer; private FastTimer soundTimer; protected SwtWindow window; protected IKeyboardHandler keyboardHandler; private VideoThread videoThread; private EmulatorServerBase server; /** * @param machine * */ public BaseSwtJavaClient(EmulatorServerBase server) { this.server = server; this.machine = server.getMachine(); this.settingsHandler = Settings.getSettings(machine); this.display = Display.getDefault(); this.video = machine.getVdp(); machine.setClient(this); this.keyTimer = new FastTimer("Keyboard/Joystick Timer"); this.soundTimer = new FastTimer("Sound Timer"); setupRenderer(); soundHandler = new JavaSoundHandler(machine); window = new SwtWindow(display, server, machine, (ISwtVideoRenderer) videoRenderer, settingsHandler, soundHandler); eventNotifier = window.getEventNotifier(); keyboardHandler.setEventNotifier(eventNotifier); Shell shell = window.getShell(); shell.addShellListener(new ShellAdapter() { @Override public void shellClosed(ShellEvent e) { if (machine.isAlive()) { machine.stop(); } } }); keyboardHandler.init(videoRenderer); videoThread = new VideoThread(videoRenderer); videoThread.start(); videoRenderer.getVdpHandler().addWriteListener(new IRegisterWriteListener() { @Override public void registerChanged(int reg, int value) { // VDP interrupt if (reg == VdpTMS9918AConsts.REG_SCANLINE && value == -1) { triggerVideoRefresh(); } } }); // update sound as often as registers change final TimerTask soundGenerateTask = new TimerTask() { @Override public void run() { int pos; int total; synchronized (machine.getCpu()) { pos = machine.getCpu().getCurrentCycleCount(); total = machine.getCpu().getCurrentTargetCycleCount(); } soundHandler.generateSound(pos, total); } }; machine.getSound().addWriteListener(new IRegisterWriteListener() { @Override public void registerChanged(int reg, int value) { soundTimer.invoke(soundGenerateTask); } }); // update speech as soon as possible final TimerTask speechGenerateTask = new TimerTask() { @Override public void run() { soundTimer.invoke(soundGenerateTask); } }; final TimerTask speechDoneTask = new TimerTask() { @Override public void run() { int pos; int total; synchronized (machine.getCpu()) { pos = machine.getCpu().getCurrentCycleCount(); total = machine.getCpu().getCurrentTargetCycleCount(); } soundHandler.flushAudio(pos, total); } }; machine.getSpeech().addSpeechListener(new ISpeechDataSender() { @Override public void sendSample(short val, int pos, int length) { soundTimer.invoke(speechGenerateTask); } @Override public void speechDone() { soundTimer.invoke(speechDoneTask); } }); // TODO Set<IPrinterImageEngine> engines = new HashSet<IPrinterImageEngine>(); for (IPrinterImageHandler handler : machine.getPrinterImageHandlers()) { IPrinterImageEngine engine = ((IPrinterImageHandler) handler).getEngine(); if (engines.add(engine)) { @SuppressWarnings("unused") PrinterImageShell rsshell = new PrinterImageShell(eventNotifier, settingsHandler, engine); } } } /** * @return the server */ public EmulatorServerBase getServer() { return server; } /** * */ protected void triggerVideoRefresh() { Object sync = videoThread.getSync(); synchronized (sync) { sync.notifyAll(); } } /* (non-Javadoc) * @see v9t9.common.client.IClient#tick() */ @Override public void tick() { // flush sound each tick int pos; int total; synchronized (machine.getCpu()) { pos = machine.getCpu().getCurrentCycleCount(); total = machine.getCpu().getCurrentTargetCycleCount(); } //System.out.println(pos + " / " + total); soundHandler.flushAudio(pos, total); } abstract protected void setupRenderer(); /** * @param display2 * @return */ protected ISwtVideoRenderer createSwtVideoRenderer(Display display2) { ISwtVideoRenderer videoRenderer = null; if (false && videoRenderer == null && SWT.getPlatform().equals("gtk")) { // try OpenGL first ? try { Class<?> klass = Class.forName( SwtVideoRenderer.class.getName() + "OGL"); videoRenderer = (ISwtVideoRenderer) klass.getConstructor().newInstance(); } catch (Exception e) { System.err.println("Cannot load OpenGL/GTK-specific support: " +e.getMessage()); } } if (false && videoRenderer == null) { // try J3D first ? try { Class<?> klass = Class.forName( SwtVideoRenderer.class.getName() + "J3D"); videoRenderer = (ISwtVideoRenderer) klass.getConstructor().newInstance(); } catch (Exception e) { System.err.println("Cannot load J3D support: " +e.getMessage()); } } /* if (videoRenderer == null && SWT.getPlatform().equals("gtk")) { try { Class<?> klass = Class.forName( SwtVideoRenderer.class.getName() + "GTK"); videoRenderer = (ISwtVideoRenderer) klass.getConstructor().newInstance(); } catch (Exception e) { System.err.println("Cannot load GTK-specific support: " +e.getMessage()); } }*/ if (videoRenderer == null) videoRenderer = new SwtVideoRenderer(machine); return videoRenderer; } @Override public IEventNotifier getEventNotifier() { return eventNotifier; } /* (non-Javadoc) * @see v9t9.common.client.IClient#start() */ @Override public void start() { if (settingsHandler.get(settingNewConfiguration).getBoolean()) { // ROMSetupDialog dialog = ROMSetupDialog.createDialog(window.getShell(), machine, window); // dialog.open(); SetupWizard wizard = new SetupWizard(machine, window, SetupWizard.Page.INTRO); WizardDialog dialog = new WizardDialog(window.getShell(), wizard); dialog.open(); } } public void close() { soundTimer.cancel(); if (soundHandler != null) soundHandler.dispose(); if (videoThread != null) { videoThread.interrupt(); try { videoThread.join(); } catch (InterruptedException e) { } } keyTimer.cancel(); try { machine.stop(); } catch (TerminatedException e) { // expected } display.asyncExec(new Runnable() { public void run() { if (videoRenderer != null) videoRenderer.dispose(); } }); } @Override protected void finalize() throws Throwable { close(); super.finalize(); } public void handleEvents() { if (display.isDisposed()) return; display.syncExec(new Runnable() { public void run() { try { while (!display.isDisposed() && display.readAndDispatch()) { // loop } } catch (SWTException e) { e.printStackTrace(); } catch (SWTError e) { e.printStackTrace(); } } }); } public boolean isAlive() { return !display.isDisposed(); } /* (non-Javadoc) * @see v9t9.common.client.IClient#asyncExecInUI(java.lang.Runnable) */ @Override public void asyncExecInUI(final Runnable runnable) { machine.asyncExec(new Runnable() { public void run() { display.syncExec(runnable); } }); } /* (non-Javadoc) * @see v9t9.common.client.IClient#getVideoRenderer() */ @Override public IVideoRenderer getVideoRenderer() { return videoRenderer; } /* (non-Javadoc) * @see v9t9.common.client.IClient#getSoundHandler() */ @Override public ISoundHandler getSoundHandler() { return soundHandler; } /* (non-Javadoc) * @see v9t9.common.client.IClient#getEmulatorContentHandlers(v9t9.common.client.IEmulatorContentSource) */ @Override public IEmulatorContentHandler[] getEmulatorContentHandlers( IEmulatorContentSource source) { if (source instanceof DemoContentSource) return new IEmulatorContentHandler[] { new DemoContentHandler((DemoContentSource) source) }; if (source instanceof ModuleContentSource) return new IEmulatorContentHandler[] { new ModuleContentHandler((ModuleContentSource) source) }; if (source instanceof EmulatedDiskContentSource) { EmulatedDiskContentSource diskSource = (EmulatedDiskContentSource) source; IFileExecutionHandler execHandler = machine.getEmulatedFileHandler().getFileExecutionHandler(); diskSource.getCatalog().deviceName = diskSource.getDevice(); IFileExecutor[] execs = execHandler.analyze(machine, diskSource.getDrive(), diskSource.getCatalog()); IFileExecutor[] allExecs = new IFileExecutor[execs.length + 1]; allExecs[0] = new DoNothingFileExecutor(diskSource.getContent(), "Load disk"); System.arraycopy(execs, 0, allExecs, 1, execs.length); execs = allExecs; IEmulatorContentHandler[] handlers = new IEmulatorContentHandler[execs.length]; for (int i = 0; i < execs.length; i++) { handlers[i] = new FileExecutorContentHandler(window.getShell(), machine, diskSource, execs[i]); } return handlers; } return IEmulatorContentHandler.NONE; } }