#!/usr/bin/python -O """ # GAL/PAL chip emulator GUI # # Copyright (C) 2008 Michael Buesch # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ import galemu import sys import ConfigParser import math from PyQt4.QtCore import * from PyQt4.QtGui import * class GlobalTimer(QTimer): "A global timer for synchronized timekeeping" INTERVAL = 25 # milliseconds class Waiter: def __init__(self, nrIntervals): self.count = nrIntervals def decrement(self): if self.count > 0: self.count -= 1 def getCount(self): return self.count def __init__(self): QTimer.__init__(self) self.waiters = [] self.callbacks = [] self.connect(self, SIGNAL("timeout()"), self.timeout) self.setInterval(GlobalTimer.INTERVAL) self.start() def msleep(self, msec, relaxCallback=None): "Put the current thread to sleep for 'msec' milliseconds" nrIntervals = int(math.ceil(float(msec) / GlobalTimer.INTERVAL)) if nrIntervals <= 0: nrIntervals = 1 waiter = GlobalTimer.Waiter(nrIntervals) self.waiters.append(waiter) while waiter.getCount() > 0: if relaxCallback: relaxCallback() else: app.processEvents() self.waiters.remove(waiter) def registerCallback(self, callbackFunction): "Register a callback that's called every GlobalTimer.INVERVAL" if callbackFunction in self.callbacks: raise Exception("GlobalTimer: Callback already registered") self.callbacks.append(callbackFunction) def unregisterCallback(self, callbackFunction): "Unregister a callback" if callbackFunction in self.callbacks: self.callbacks.remove(callbackFunction) def timeout(self): for waiter in self.waiters: waiter.decrement() for callback in self.callbacks: callback() class AbstractInput(QObject): def __init__(self, parent, abstractDevice, idString): # QObject.__init__(self, parent) self.abstractDevice = abstractDevice self.idString = idString def changeState(self, newState): """Set the state of this input. Reimplement me in the subclass!""" raise Exception("AbstractInput.changeState() unimplemented") def outputDisconnected(self, output): "Notification. May be reimplemented." # Do nothing def getDevice(self): return self.abstractDevice def getIdString(self): return self.idString def connectToOutput(self, output): self.connect(output, SIGNAL("stateChanged(QObject, int)"), self.__connectedOutputChangedState) def disconnectFromOutput(self, output): self.disconnect(output, SIGNAL("stateChanged(QObject, int)"), self.__connectedOutputChangedState) self.outputDisconnected(output) output.inputDisconnected(self) def __connectedOutputChangedState(self, output, newState): self.changeState(newState) class AbstractOutput(QObject): def __init__(self, parent, abstractDevice): "Emits: stateChanged(self, newState)" # QObject.__init__(self, parent) self.abstractDevice = abstractDevice def inputDisconnected(self, output): "Notification. May be reimplemented." # Do nothing def getDevice(self): return self.abstractDevice def connectToInput(self, input): input.connectToOutput(self) def disconnectFromInput(self, input): input.disconnectFromOutput(self) def changeNotify(self, newState): self.emit(SIGNAL("stateChanged(QObject, int)"), self, newState) class AbstractDevice(QObject): def __init__(self, idString): QObject.__init__(self) "idString will globally identify the device" self.idString = idString def getIdString(self): return self.idString class ScopeInput(AbstractInput, QObject): "One input bit for a channel" def __init__(self, parent, trace, name): QObject.__init__(self) AbstractInput.__init__(self, parent, trace, name) self.name = name self.state = False def outputDisconnected(self, output): # Reset state, if an output disconnected from us self.state = False def getName(self): return self.name def getState(self): return self.state def changeState(self, newState): self.state = newState class ScopeTrace(AbstractDevice): "The trace for one scope channel" COLORS = [ QColor("#FF0000"), QColor("#00FF00"), QColor("#0000FF"), ] COUPLING_GND = 0, COUPLING_DC = 1, def __init__(self, oscilloscope, name, color): AbstractDevice.__init__(self, "SCOPETRACE_%s_%s" % (oscilloscope.getName(), name)) self.oscilloscope = oscilloscope self.name = name self.color = color self.inputs = [] self.coupling = ScopeTrace.COUPLING_DC self.yDivOffset = 0 self.nrBits = -1 self.setNrBits(8) self.updateConfig() def updateConfig(self): self.samples = [] self.prevSample = 0 self.signalDetected = False self.__signalDetected = False def setCoupling(self, coupling): self.coupling = coupling def getCoupling(self): return self.coupling def getName(self): return self.name def getColor(self): return self.color def getNrBits(self): return self.nrBits def setNrBits(self, nrBits): if nrBits == self.nrBits: return self.nrBits = nrBits self.inputs = [] for i in range(0, nrBits): name = "Bit %d" % i if i == 0: name += " (LSB)" elif i == nrBits - 1: name += " (MSB/sign)" inp = ScopeInput(self, self, name) self.inputs.append(inp) self.oscilloscope.mainwidget.updateAllOutputReferences() def getInputs(self): return self.inputs def __getCurrentAnalogTraceVoltage(self): "Returns the current trace 'voltage' composed of the input bits" if self.coupling == ScopeTrace.COUPLING_GND: return 0 sample = 0 for bit in range(0, self.nrBits): if self.inputs[bit].getState(): sample |= (1 << bit) # Fixup the sign signBitMask = (1 << (self.getNrBits() - 1)) if sample & signBitMask: # signed if sample >= 0: sample = (sample & ((1 << self.getNrBits()) - 1)) * -1 return sample def newTraceSample(self, crtRefresh): triggerConf = self.oscilloscope.getTriggerConfig() triggerMode = triggerConf.getTriggerMode() currentSample = self.__getCurrentAnalogTraceVoltage() prevSample = self.prevSample self.prevSample = currentSample if triggerMode == triggerConf.TRIGGER_FREERUNNING: # In freerunning mode we don't care about trigger and refresh. self.samples.append(currentSample) if len(self.samples) > self.oscilloscope.getNrSamplesPerScreen(): self.samples = self.samples[1:] return if crtRefresh: self.samples = [] self.signalDetected = self.__signalDetected self.__signalDetected = False if triggerMode != triggerConf.TRIGGER_NONE and\ not triggerConf.getTrigger() and\ triggerConf.getTriggerChannel() is not self: # We are not triggered and this is not the triggering channel. return if triggerMode == triggerConf.TRIGGER_NONE or\ triggerConf.getTrigger(): # We are triggered. Trace the signal. self.__signalDetected = True self.samples.append(currentSample) return # Wait for a triggering edge onRisingEdge = not triggerConf.triggerInverted() triggerLevel = triggerConf.getTriggerLevel() haveTrigger = False if onRisingEdge: if currentSample > prevSample and\ currentSample >= triggerLevel: haveTrigger = True else: if currentSample < prevSample and\ currentSample >= triggerLevel: haveTrigger = True if haveTrigger: triggerConf.setTrigger(True) self.__signalDetected = True self.samples.append(currentSample) self.oscilloscope.resetRefreshPeriodCounter() else: if triggerMode == triggerConf.TRIGGER_NORMAL and\ not self.signalDetected: self.samples.append(0) if len(self.samples) > self.oscilloscope.getNrSamplesPerScreen(): self.samples = self.samples[1:] def getTraceSamples(self): return self.samples def setYDivOffset(self, yDivOffset): self.yDivOffset = yDivOffset def getYDivOffset(self): "Get the offset of the trace in Y. Value = nr of Divs." return self.yDivOffset class ScopeCRT(QWidget): "The 'Cathode Ray Tube' of the scope" def __init__(self, oscilloscope): QWidget.__init__(self, oscilloscope) self.oscilloscope = oscilloscope self.nrXDivs = 10 # Number of divs in X self.nrYDivs = 8 # Number of divs in Y self.bgColor = QColor("#000000") # background self.gridColor = QColor("#7F7F7F") # grid color def resizeEvent(self, event): # Sizes must be dividable by divs x = (event.size().width() // self.nrXDivs) * self.nrXDivs y = (event.size().height() // self.nrYDivs) * self.nrYDivs self.resize(x, y) def paintEvent(self, event=None): size = self.size() p = QPainter(self) # Draw background p.fillRect(self.rect(), QBrush(self.bgColor)) # Draw the grid pen = QPen(self.gridColor) pen.setWidth(1) p.setPen(pen) xDivPixels = size.width() / self.nrXDivs # Pixels per X div yDivPixels = size.height() / self.nrYDivs # Pixels per Y div # vertical for i in range(0, self.nrXDivs): x = i * xDivPixels p.drawLine(x, 0, x, size.height()) # fine marks on vertical center line x = xDivPixels * self.nrXDivs / 2 for i in range(0, self.nrYDivs * 5): y = i * yDivPixels / 5 p.drawLine(x-3, y, x+3, y) # horizontal for i in range(0, self.nrYDivs): y = i * yDivPixels p.drawLine(0, y, size.width(), y) # fine marks on horizontal center line y = yDivPixels * self.nrYDivs / 2 for i in range(0, self.nrXDivs * 5): x = i * xDivPixels / 5 p.drawLine(x, y-3, x, y+3) # Draw the traces for trace in self.oscilloscope.getTraces(): pen = QPen(trace.getColor()) if self.oscilloscope.shouldHighlightSelectedTrace() and\ self.oscilloscope.getSelectedTrace() is trace: pen.setWidth(3) else: pen.setWidth(1) p.setPen(pen) path = QPainterPath() samples = trace.getTraceSamples() if len(samples) >= 2: count = 0 x, y = self.__sampleToCoord(trace, count, samples[0]) count += 1 x = 0 if self.oscilloscope.shouldShowTraceName(): p.drawText(x, y + 15, trace.getName()) path.moveTo(x, y) for sample in samples[1:]: x, y = self.__sampleToCoord(trace, count, sample) count += 1 path.lineTo(x, y) p.drawPath(path) def __sampleToCoord(self, trace, count, sample): "Returns the X and Y coordinates for the sample." size = self.size() yDivPixels = size.height() / self.nrYDivs # Pixels per Y div nrSamplesPerScreen = self.oscilloscope.getNrSamplesPerScreen() pixelPerSample = float(size.width()) / float(nrSamplesPerScreen - 1) x = int(pixelPerSample * count) yCenterline = size.height() // 2 yCenterline -= trace.getYDivOffset() * yDivPixels y = yCenterline + self.__sampleValueToOffset(sample) return (x, y) def __sampleValueToOffset(self, sample): size = self.size() mvSample = sample * 1000 # sample mV value mvPerDiv = self.oscilloscope.getMVPerDiv() # mV per div yDivPixels = size.height() / self.nrYDivs # Pixels per div nrDivs = float(mvSample) / float(mvPerDiv) yPixelOffset = int(yDivPixels * nrDivs) * -1 return yPixelOffset class ScopeTriggerConfig(QGroupBox): TRIGGER_NONE = 0, TRIGGER_NORMAL = 1, TRIGGER_AUTO = 2, TRIGGER_FREERUNNING = 3, def __init__(self, oscilloscope): QGroupBox.__init__(self, oscilloscope) self.setTitle("Trigger") self.oscilloscope = oscilloscope self.scopeIsTriggered = False layout = QGridLayout() self.typeCombo = QComboBox(self) self.typeCombo.addItem("Untriggered", QVariant(self.TRIGGER_NONE)) self.typeCombo.addItem("Normal", QVariant(self.TRIGGER_NORMAL)) self.typeCombo.addItem("Auto", QVariant(self.TRIGGER_AUTO)) self.typeCombo.addItem("Freerunning trace", QVariant(self.TRIGGER_FREERUNNING)) layout.addWidget(self.typeCombo, 0, 0) self.channelCombo = QComboBox(self) layout.addWidget(self.channelCombo, 1, 0) self.levelDial = QDial(self) self.levelDial.setNotchesVisible(True) self.levelDial.setWrapping(False) self.levelDial.setSliderPosition(1) self.recalcTriggerLevelRange() layout.addWidget(self.levelDial, 2, 0) self.invCheckbox = QCheckBox("Inv", self) layout.addWidget(self.invCheckbox, 3, 0) self.setLayout(layout) def setTrigger(self, newTriggerState): self.scopeIsTriggered = newTriggerState def getTrigger(self): return self.scopeIsTriggered def getTriggerMode(self): index = self.typeCombo.currentIndex() return self.typeCombo.itemData(index).toPyObject() def triggerInverted(self): return self.invCheckbox.checkState() != Qt.Unchecked def getTriggerLevel(self): return self.levelDial.sliderPosition() def getTriggerChannel(self): # Returns the trace that we trigger on. index = self.channelCombo.currentIndex() if index < 0: return None return self.channelCombo.itemData(index).toPyObject() def recalcTriggerLevelRange(self): # The trigger value is in Volts units. oldpos = self.getTriggerLevel() mvPerDiv = self.oscilloscope.getMVPerDiv() vPerDiv = float(mvPerDiv) / 1000 vPerScreen = int(vPerDiv * self.oscilloscope.getCRT().nrYDivs) self.levelDial.setRange((vPerScreen//2) * -1, vPerScreen//2) self.levelDial.setSliderPosition(oldpos) def updateTriggerChannelList(self): oldSelection = self.getTriggerChannel() self.channelCombo.clear() for trace in self.oscilloscope.getTraces(): self.channelCombo.addItem("On %s" % trace.getName(), QVariant(trace)) if oldSelection: index = self.channelCombo.findData(QVariant(oldSelection)) if index >= 0: self.channelCombo.setCurrentIndex(index) class TraceConfigDialog(QDialog): def __init__(self, oscilloscope, trace): QDialog.__init__(self, oscilloscope) self.oscilloscope = oscilloscope self.trace = trace self.setWindowTitle("Configure %s" % trace.getName()) layout = QGridLayout() self.nrBitsSpinbox = QSpinBox(self) self.nrBitsSpinbox.setRange(1, 0xFF) self.nrBitsSpinbox.setSingleStep(1) self.nrBitsSpinbox.setValue(trace.getNrBits()) self.nrBitsSpinbox.setSuffix(" input bits") layout.addWidget(self.nrBitsSpinbox, 0, 0) self.couplingCombobox = QComboBox(self) self.couplingCombobox.addItem("GND coupling", QVariant(ScopeTrace.COUPLING_GND)) self.couplingCombobox.addItem("DC coupling", QVariant(ScopeTrace.COUPLING_DC)) index = self.couplingCombobox.findData(QVariant(trace.getCoupling())) self.couplingCombobox.setCurrentIndex(index) layout.addWidget(self.couplingCombobox, 1, 0) self.okButton = QPushButton("Ok", self) self.connect(self.okButton, SIGNAL("clicked()"), self.okClicked) layout.addWidget(self.okButton, 2, 0) self.cancelButton = QPushButton("Cancel", self) self.connect(self.cancelButton, SIGNAL("clicked()"), self.cancelClicked) layout.addWidget(self.cancelButton, 2, 1) self.deleteButton = QPushButton("Delete trace", self) self.connect(self.deleteButton, SIGNAL("clicked()"), self.deleteClicked) layout.addWidget(self.deleteButton, 2, 2) self.setLayout(layout) def okClicked(self): self.trace.setNrBits(self.nrBitsSpinbox.value()) index = self.couplingCombobox.currentIndex() self.trace.setCoupling(self.couplingCombobox.itemData(index).toPyObject()) self.accept() def cancelClicked(self): self.reject() def deleteClicked(self): self.oscilloscope.deleteTrace(self.trace) self.oscilloscope.getTriggerConfig().updateTriggerChannelList() self.reject() class Oscilloscope(QWidget): def __init__(self, enum, parent, flags = Qt.Window): QWidget.__init__(self, parent, flags) self.mainwidget = parent self.traceEnum = 0 self.refreshCount = 0 self.setWindowTitle("Oscilloscope %d" % enum) layout = QGridLayout() self.crt = ScopeCRT(self) layout.addWidget(self.crt, 0, 0, 10, 4) self.ySlider = QSlider(Qt.Vertical, self) self.ySlider.setTickPosition(QSlider.TicksLeft) self.ySlider.setTickInterval(1) self.ySlider.setRange((self.crt.nrYDivs // 2) * -1, (self.crt.nrYDivs // 2)) self.ySlider.setSliderPosition(self.crt.nrYDivs // 2) self.connect(self.ySlider, SIGNAL("sliderMoved(int)"), self.ySliderMoved) layout.addWidget(self.ySlider, 0, 5, 10, 1) self.mVPerDivSpinbox = QSpinBox(self) self.mVPerDivSpinbox.setRange(1, 0xFFFF * 1000) self.mVPerDivSpinbox.setSingleStep(1000) self.mVPerDivSpinbox.setValue(1000) self.mVPerDivSpinbox.setSuffix(" mV/div") self.connect(self.mVPerDivSpinbox, SIGNAL("valueChanged(int)"), self.mVPerDivChanged) layout.addWidget(self.mVPerDivSpinbox, 0, 6) self.msPerDivSpinbox = QSpinBox(self) self.msPerDivSpinbox.setRange(GlobalTimer.INTERVAL * 2, 100000) self.msPerDivSpinbox.setSingleStep(GlobalTimer.INTERVAL) self.msPerDivSpinbox.setValue(100) self.msPerDivSpinbox.setSuffix(" ms/div") self.connect(self.msPerDivSpinbox, SIGNAL("valueChanged(int)"), self.msPerDivChanged) layout.addWidget(self.msPerDivSpinbox, 1, 6) self.trigger = ScopeTriggerConfig(self) layout.addWidget(self.trigger, 2, 6, 8, 1) self.tracesCombo = QComboBox(self) self.connect(self.tracesCombo, SIGNAL("currentIndexChanged(int)"), self.currentTraceChanged) layout.addWidget(self.tracesCombo, 11, 0) self.traceConfButton = QPushButton("Configure trace", self) self.connect(self.traceConfButton, SIGNAL("clicked()"), self.traceConfClicked) layout.addWidget(self.traceConfButton, 11, 1) self.traceAddButton = QPushButton("Add trace", self) self.connect(self.traceAddButton, SIGNAL("clicked()"), self.addDefaultTrace) layout.addWidget(self.traceAddButton, 11, 2) self.showTraceNameCheckbox = QCheckBox("Show trace name", self) layout.addWidget(self.showTraceNameCheckbox, 12, 0) self.highlightCurrentTraceCheckbox = QCheckBox("Highlight selected trace", self) self.highlightCurrentTraceCheckbox.setChecked(True) layout.addWidget(self.highlightCurrentTraceCheckbox, 12, 1) self.addDefaultTrace() self.setLayout(layout) self.resize(550, 400) globalTimer.registerCallback(self.timerCallback) def shutdown(self): globalTimer.unregisterCallback(self.timerCallback) def closeEvent(self, ev): self.shutdown() self.mainwidget.removeScope(self) self.mainwidget.updateAllOutputReferences() def currentTraceChanged(self, newIndex): if newIndex >= 0: trace = self.tracesCombo.itemData(newIndex).toPyObject() self.ySlider.setSliderPosition(trace.getYDivOffset()) def ySliderMoved(self, newPos): traceIndex = self.tracesCombo.currentIndex() trace = self.tracesCombo.itemData(traceIndex).toPyObject() trace.setYDivOffset(newPos) def getName(self): return self.windowTitle() def getTriggerConfig(self): return self.trigger def shouldShowTraceName(self): return self.showTraceNameCheckbox.checkState() != Qt.Unchecked def shouldHighlightSelectedTrace(self): return self.highlightCurrentTraceCheckbox.checkState() != Qt.Unchecked def getSelectedTrace(self): idx = self.tracesCombo.currentIndex() if idx < 0: return None return self.tracesCombo.itemData(idx).toPyObject() def getCRT(self): return self.crt def addDefaultTrace(self): name = "Trace %d" % self.traceEnum color = ScopeTrace.COLORS[self.traceEnum % len(ScopeTrace.COLORS)] trace = ScopeTrace(self, name, color) self.tracesCombo.addItem(name, QVariant(trace)) self.traceEnum += 1 self.getTriggerConfig().updateTriggerChannelList() def mVPerDivChanged(self, mV): self.getTriggerConfig().recalcTriggerLevelRange() for trace in self.getTraces(): trace.updateConfig() self.refreshCount = -99 def getMVPerDiv(self): return self.mVPerDivSpinbox.value() def getMsPerScreen(self): "Get the number of milliseconds that fit onto the Y axis of the CRT." return self.getMsPerDiv() * self.crt.nrYDivs def msPerDivChanged(self, ms): for trace in self.getTraces(): trace.updateConfig() self.refreshCount = -99 def getMsPerDiv(self): return self.msPerDivSpinbox.value() def traceConfClicked(self): idx = self.tracesCombo.currentIndex() if idx < 0: return trace = self.tracesCombo.itemData(idx).toPyObject() dlg = TraceConfigDialog(self, trace) dlg.exec_() # Trace may be deleted. Don't use anymore def getTraces(self): "Get a list of 'class ScopeTrace'" ret = [] for i in range(0, self.tracesCombo.count()): ret.append(self.tracesCombo.itemData(i).toPyObject()) return ret def deleteTrace(self, trace): for i in range(0, self.tracesCombo.count()): if self.tracesCombo.itemData(i).toPyObject() is trace: self.tracesCombo.removeItem(i) return True return False def getNrSamplesPerScreen(self): # Number of samples (timer callbacks) that happen between full CRT refreshes. return int(math.ceil(float(self.getMsPerScreen()) / GlobalTimer.INTERVAL)) def resetRefreshPeriodCounter(self): self.refreshCount = 0 def timerCallback(self): # This timer callback is called every GlobalTimer.INTERVAL milliseconds self.refreshCount += 1 refresh = False if self.refreshCount >= self.getNrSamplesPerScreen() or self.refreshCount < 0: self.refreshCount = 0 refresh = True self.getTriggerConfig().setTrigger(False) for trace in self.getTraces(): trace.newTraceSample(refresh) self.crt.repaint() def save(self): return ""#TODO class GeneratorOutput(QWidget, AbstractOutput): def __init__(self, signalGenerator, pinNr): AbstractOutput.__init__(self, signalGenerator, signalGenerator) QWidget.__init__(self, signalGenerator) self.pinNr = pinNr self.connectedInput = None layout = QHBoxLayout() self.cb = QCheckBox("Out %d" % pinNr, self) self.cb.setEnabled(False) layout.addWidget(self.cb) self.combo = QComboBox(self) self.connect(self.combo, SIGNAL("currentIndexChanged(int)"), self.outputRefChanged) layout.addWidget(self.combo) self.setLayout(layout) def setEnabled(self, enabled): self.combo.setEnabled(enabled) def setState(self, state): oldState = (self.cb.checkState() != Qt.Unchecked) if oldState == state: return if state: self.cb.setCheckState(Qt.Checked) else: self.cb.setCheckState(Qt.Unchecked) self.changeNotify(state) def getState(self): return self.cb.checkState() != Qt.Unchecked def setReferences(self, refs): selectedInput = None if self.combo.currentIndex() > 0: # Get the "class OneInput" of the old selection selectedInput = self.combo.itemData(self.combo.currentIndex()).toPyObject() self.combo.clear() for ref in refs: self.combo.addItem(ref[0], QVariant(ref[1])) self.combo.adjustSize() if selectedInput: idx = self.combo.findData(QVariant(selectedInput)) if idx >= 0: self.combo.setCurrentIndex(idx) def outputRefChanged(self, newIndex): if newIndex < 0: return if newIndex > 0: newInput = self.combo.itemData(newIndex).toPyObject() self.__disconnectCurrentRef() self.connectToInput(newInput) self.connectedInput = newInput self.connectedInput.changeState(self.getState()) else: self.__disconnectCurrentRef() def __disconnectCurrentRef(self): if not self.connectedInput: return curInput = self.connectedInput self.disconnectFromInput(curInput) self.connectedInput = None class GeneratorInterpreter: class KillException: pass def __init__(self, signalGenerator, script): self.signalGenerator = signalGenerator self.script = str(script) self.killRequest = False self.breakpointEnabled = False def setBreakpointEnabled(self, enabled): self.breakpointEnabled = enabled def callback_relax(self): "Just relax a bit and let the user interface refresh" if self.killRequest: raise GeneratorInterpreter.KillException app.processEvents() def callback_msleep(self, msec): "Suspend the generator script for a few microseconds" self.callback_relax() globalTimer.msleep(msec, self.callback_relax) def callback_out(self, pin, newState = None): "Set the state of an output pin" self.callback_relax() outputs = self.signalGenerator.getOutputs() try: generatorOutput = outputs[pin] except IndexError: QMessageBox.critical(None, "OUT invalid parameter", "pinNumber parameter to out(pinNumber, newState) out of range.") raise GeneratorInterpreter.KillException except TypeError: QMessageBox.critical(None, "OUT invalid parameter", "Invalid parameter to out(pinNumber, newState) function.") raise GeneratorInterpreter.KillException if newState is None: return generatorOutput.getState() else: generatorOutput.setState(newState) def callback_reset(self): "Clear all output signals (Reset to zero)" self.callback_relax() for output in self.signalGenerator.getOutputs(): output.setState(False) def callback_breakpoint(self, message = None): "Voluntary optional breakpoint" self.callback_relax() if not self.breakpointEnabled: return False if not message: message = "Unknown breakpoint" self.signalGenerator.breakpointNotify(message) while not self.signalGenerator.breakpointMayContinue: self.callback_msleep(1) self.signalGenerator.breakpointNotify(None) return True def run(self): #FIXME: Currently we can only run one interpreter at a time, because it runs in the main thread. context = { "relax" : self.callback_relax, "msleep" : self.callback_msleep, "out" : self.callback_out, "reset" : self.callback_reset, "breakpoint" : self.callback_breakpoint, } try: exec self.script in context except GeneratorInterpreter.KillException: pass except Exception, e: QMessageBox.critical(None, "Generator script failed", "The Generator script failed with an exception:\n" +\ e.message) def kill(self): self.killRequest = True class GeneratorScriptEdit(QTextEdit): def __init__(self, parent): QTextEdit.__init__(self, parent) self.setAcceptRichText(False) self.setAutoFormatting(QTextEdit.AutoNone) self.setLineWrapMode(QTextEdit.NoWrap) self.setFontFamily("Courier") self.setFontPointSize(12) code = "# Galemu signal generator script\n" +\ "# Available builtin functions:\n" for h in SignalGenerator.commandsHelp: code += "# %s -> %s\n" % (h[0], h[1]) code += "\n" +\ "reset()\n" +\ "while True:\n" +\ "\tout(0, not out(0))\n" +\ "\tmsleep(500)\n" self.setPlainText(code) def text(self): return self.toPlainText() class SignalGenerator(QWidget, AbstractDevice): commandsHelp = ( ("out(pinNr, newState)", "Set an output state"), ("out(pinNr)", "Get an output state"), ("msleep(msec)", "Sleep for msec milliseconds"), ("reset()", "Set all outputs to False"), ("breakpoint(messageString)", "Voluntary breakpoint"), ("relax()", "Update the GUI"), ) def __init__(self, enum, parent, nrOutputs, flags = Qt.Window): AbstractDevice.__init__(self, "GEN_%d" % (enum)) QWidget.__init__(self, parent, flags) self.mainwidget = parent self.running = False self.nrOutputs = nrOutputs self.breakpointMayContinue = False self.setWindowTitle("Signal generator %d" % enum) layout = QHBoxLayout() v = QVBoxLayout() h = QHBoxLayout() self.loadButton = QPushButton("Load file...", self) self.connect(self.loadButton, SIGNAL("clicked()"), self.loadFile) h.addWidget(self.loadButton) self.saveButton = QPushButton("Save to file...", self) self.connect(self.saveButton, SIGNAL("clicked()"), self.saveToFile) h.addWidget(self.saveButton) v.addLayout(h) self.script = GeneratorScriptEdit(self) v.addWidget(self.script) h = QHBoxLayout() self.runButton = QPushButton("Run...", self) self.connect(self.runButton, SIGNAL("clicked()"), self.runScript) h.addWidget(self.runButton) self.killButton = QPushButton("Kill", self) self.connect(self.killButton, SIGNAL("clicked()"), self.killScript) self.killButton.setEnabled(False) h.addWidget(self.killButton) self.helpButton = QPushButton("Quick-help", self) self.connect(self.helpButton, SIGNAL("clicked()"), self.showHelp) h.addWidget(self.helpButton) v.addLayout(h) h = QHBoxLayout() self.bpCheckbox = QCheckBox("Enable breakpoints", self) h.addWidget(self.bpCheckbox) self.bpContButton = QPushButton("Breakpoint continue", self) self.bpContButton.setEnabled(False) self.connect(self.bpContButton, SIGNAL("clicked()"), self.breakpointContinue) h.addWidget(self.bpContButton) v.addLayout(h) self.bpNotifyLabel = QLabel("", self) v.addWidget(self.bpNotifyLabel) layout.addLayout(v) v = QVBoxLayout() self.outputs = [] for i in range(0, nrOutputs): outp = GeneratorOutput(self, i) v.addWidget(outp) self.outputs.append(outp) layout.addLayout(v) self.setLayout(layout) self.updateOutputReferences() self.resize(800, 600) def getName(self): return self.windowTitle() def getOutputs(self): return self.outputs def closeEvent(self, ev): self.killScript() self.mainwidget.removeGenerator(self) self.mainwidget.updateAllOutputReferences() def loadFile(self): fn = QFileDialog.getOpenFileName(self, "Open Galemu generator script") if not fn: return try: self.script.setText(file(fn).read()) except IOError, e: QMessageBox.critical(self, "Failed to open generator script", "Failed to open generator script %s: %s" %\ (fn, e.strerror)) def saveToFile(self): fn = QFileDialog.getSaveFileName(self, "Save Galemu generator script") if not fn: return try: fd = file(fn, "w") fd.write(self.script.text()) except IOError, e: QMessageBox.critical(self, "Failed to save generator script", "Failed to save generator script %s: %s" %\ (fn, e.strerror)) def updateGui(self): self.runButton.setEnabled(not self.running) self.killButton.setEnabled(self.running) self.script.setEnabled(not self.running) self.loadButton.setEnabled(not self.running) self.bpCheckbox.setEnabled(not self.running) self.bpContButton.setEnabled(False) self.bpNotifyLabel.setText("") for output in self.getOutputs(): output.setEnabled(not self.running) def runScript(self): self.interp = GeneratorInterpreter(self, self.script.text()) self.interp.setBreakpointEnabled(self.bpCheckbox.checkState() != Qt.Unchecked) self.running = True self.updateGui() self.interp.run() self.running = False self.updateGui() def killScript(self): if not self.running: return self.interp.kill() def breakpointContinue(self): self.breakpointMayContinue = True def breakpointNotify(self, message): if message: self.bpNotifyLabel.setText("At breakpoint: " + message) self.bpContButton.setEnabled(True) else: self.bpNotifyLabel.setText("") self.bpContButton.setEnabled(False) self.breakpointMayContinue = False def showHelp(self): t = "The Galemu generator script is an ordinary python script with a few "+\ "additional special functions:\n\n" for h in SignalGenerator.commandsHelp: t += "%s -> %s\n" % (h[0], h[1]) t += "\nNote that all special functions implicitely call relax(). "+\ "So most of the time you do _not_ need to call relax() explicitely. "+\ "Not calling relax(), or a function that calls relax() implicitely, in a loop "+\ "will cause a GUI lockup. So in a loop you want to call at least one special "+\ "function. But usually this is not an issue, because a loop that does not "+\ "call a special function is useless anyway." QMessageBox.information(self, "Galemu generator script quickhelp", t) def updateOutputReferences(self): refs = [ ("=> No connection ", ":"), ] inputsList = self.mainwidget.getAllAbstractInputs([self]) for i in inputsList: devName = i[0] inpName = i[1] abstractInput = i[2] namestr = "=> %s - %s" % (devName, inpName) refs.append( (namestr, abstractInput) ) for output in self.getOutputs(): output.setReferences(refs) def save(self): return ""#TODO class OneInput(QWidget, AbstractInput): def __init__(self, parent, inpin): AbstractInput.__init__(self, parent, parent.deviceWidget, "%d"%inpin) QWidget.__init__(self, parent) self.inputsWidget = parent self.inpin = inpin layout = QHBoxLayout() self.cb = QCheckBox("In %d" % inpin, self) self.connect(self.cb, SIGNAL("stateChanged(int)"), self.stateChanged) layout.addWidget(self.cb) self.setLayout(layout) def getPinNumber(self): return self.inpin def getNameString(self): return self.cb.text() def stateChanged(self, newState): self.emit(SIGNAL("stateChanged(int)"), newState) def getState(self): if self.cb.checkState() == Qt.Unchecked: return False return True def __setState(self, newState): if newState: if self.cb.checkState() == Qt.Unchecked: self.cb.toggle() else: if self.cb.checkState() != Qt.Unchecked: self.cb.toggle() def changeState(self, newState): "Reimplements AbstractInput.changeState()" self.__setState(newState) class InputsWidget(QWidget): def __init__(self, parent): QWidget.__init__(self, parent) self.deviceWidget = parent self.setLayout(QVBoxLayout()) self.inputs = [] self.emu = None def getInputsList(self): return self.inputs def setup(self, emu): self.emu = emu if self.inputs: self.destroy() gal = emu.getGal() nrInput = len(gal.getInputList()) self.inputs = [] for i in range(0, nrInput): inpin = gal.getInputList()[i].getPin() inp = OneInput(self, inpin) self.connect(inp, SIGNAL("stateChanged(int)"), self.oneStateChanged) self.inputs.append(inp) self.layout().addWidget(inp) def destroy(self): for i in self.inputs: i.deleteLater() self.inputs = [] def oneStateChanged(self, state): devicewidget = self.parent() if devicewidget.isFrozen(): return self.commit() devicewidget.getOutputsWidget().update() def commit(self): inpStates = [] # We depend on self.inputs being sorted like emu expects it. for inp in self.inputs: inpStates.append(inp.getState()) try: self.emu.setInput(inpStates) except galemu.GalemuEx, e: QMessageBox.warning(self, "Emulator error", e.message) return def reset(self): devicewidget = self.parent() for inp in self.inputs: inp.changeState(False) if not devicewidget.isFrozen(): self.commit() def getInputByPinNumber(self, pinnr): for inp in self.inputs: if inp.getPinNumber() == pinnr: return inp return None def save(self): text = "" for inp in self.inputs: stat = 0 if inp.getState(): stat = 1 text += "input%d=%d\n" % (inp.getPinNumber(), stat) return text def setInputPinState(self, pinNr, newState): for inp in self.inputs: if inp.getPinNumber() == pinNr: inp.changeState(newState) break class OneOutput(QWidget, AbstractOutput): def __init__(self, parent, outpin): AbstractOutput.__init__(self, parent, parent.deviceWidget) QWidget.__init__(self, parent) self.outputsWidget = parent self.outpin = outpin self.connectedInput = None layout = QHBoxLayout() self.cb = QCheckBox("Out %d" % outpin, self) self.cb.setEnabled(False) layout.addWidget(self.cb) self.combo = QComboBox(self) self.connect(self.combo, SIGNAL("currentIndexChanged(int)"), self.outputRefChanged) layout.addWidget(self.combo) self.setLayout(layout) def getPinNumber(self): return self.outpin def getConnectedInput(self): return self.connectedInput def setState(self, state): oldState = (self.cb.checkState() != Qt.Unchecked) if oldState == state: return if state: self.cb.setCheckState(Qt.Checked) else: self.cb.setCheckState(Qt.Unchecked) self.changeNotify(state) def getState(self): return self.cb.checkState() != Qt.Unchecked def setReferences(self, refs): selectedInput = None if self.combo.currentIndex() > 0: # Get the "class OneInput" of the old selection selectedInput = self.combo.itemData(self.combo.currentIndex()).toPyObject() self.combo.clear() for ref in refs: self.combo.addItem(ref[0], QVariant(ref[1])) self.combo.adjustSize() if selectedInput: idx = self.combo.findData(QVariant(selectedInput)) if idx >= 0: self.combo.setCurrentIndex(idx) def outputRefChanged(self, newIndex): if newIndex < 0: return if newIndex > 0: newInput = self.combo.itemData(newIndex).toPyObject() self.__disconnectCurrentRef() self.connectToInput(newInput) self.connectedInput = newInput self.connectedInput.changeState(self.getState()) else: self.__disconnectCurrentRef() def __disconnectCurrentRef(self): if not self.connectedInput: return curInput = self.connectedInput self.disconnectFromInput(curInput) self.connectedInput = None def forceConnectReference(self, select_idstr): for i in range(0, self.combo.count()): idstr = str(self.combo.itemData(i).toString()) if idstr == select_idstr: self.combo.setCurrentIndex(i) break class OutputsWidget(QWidget): def __init__(self, parent): QWidget.__init__(self, parent) self.setLayout(QVBoxLayout()) self.outputs = [] self.emu = None self.deviceWidget = parent def getOutputsList(self): return self.outputs def setup(self, emu): self.emu = emu if self.outputs: self.destroy() gal = emu.getGal() nrOutput = len(gal.getOlmcList()) self.outputs = [] for i in range(0, nrOutput): outpin = gal.getOlmcList()[i].getOutputPin().getPin() o = OneOutput(self, outpin) self.outputs.append(o) self.layout().addWidget(o) self.update() def destroy(self): for o in self.outputs: o.deleteLater() self.outputs = [] def update(self): try: out = self.emu.getOutput() except galemu.GalemuEx, e: QMessageBox.warning(self, "Emulator error", e.message) return # We depend on emu.getOutput() being sorted like we expect it. for i in range(0, len(out)): self.outputs[i].setState(out[i]) def updateReferences(self): refs = [ ("=> No connection ", ":"), ] inputsList = self.deviceWidget.mainwidget.getAllAbstractInputs([self.deviceWidget]) for i in inputsList: devName = i[0] inpName = i[1] abstractInput = i[2] namestr = "=> %s - %s" % (devName, inpName) refs.append( (namestr, abstractInput) ) for output in self.outputs: output.setReferences(refs) def selectReference(self, pinNr, connTo_idstr): for output in self.outputs: if output.getPinNumber() == pinNr: output.forceConnectReference(connTo_idstr) def save(self): text = "" for output in self.outputs: connectedInput = output.getConnectedInput() if not connectedInput: continue text += self.deviceWidget.mainwidget.saveOutputConnection("%d" % output.getPinNumber(), connectedInput) return text class DeviceWidget(QWidget, AbstractDevice): def __init__(self, enum, parent, flags = Qt.Window): AbstractDevice.__init__(self, "GAL_%d" % (enum)) QWidget.__init__(self, parent, flags) self.mainwidget = parent self.setWindowTitle("GAL %d" % enum) layout = QVBoxLayout() self.infoLabel = QLabel(self) self.infoLabel.setFrameShape(QFrame.Panel) self.setInfoText("No JEDEC file loaded") layout.addWidget(self.infoLabel) devLayout = QHBoxLayout() devLayout.addStretch() self.inputsWidget = InputsWidget(self) devLayout.addWidget(self.inputsWidget) v = QVBoxLayout() v.addStretch() self.jedButton = QPushButton("\n\n\nL\no\na\nd\n \nJ\nE\nD\nE\nC\n\n\n", self) self.connect(self.jedButton, SIGNAL("clicked()"), self.loadJedec) v.addWidget(self.jedButton) v.addStretch() devLayout.addLayout(v) self.outputsWidget = OutputsWidget(self) devLayout.addWidget(self.outputsWidget) devLayout.addStretch() layout.addLayout(devLayout) self.setLayout(layout) self.running = False def isRunning(self): return self.running def closeEvent(self, ev): self.mainwidget.removeDevice(self) self.mainwidget.updateAllOutputReferences() def getNameString(self): return self.windowTitle() def getOutputsWidget(self): return self.outputsWidget def getInputsWidget(self): return self.inputsWidget def getEmulator(self): if self.running: return self.emu return None def loadJedec(self): fn = QFileDialog.getOpenFileName(self, "Open JEDEC file") if not fn: return try: jedecData = file(fn, "r").read() except IOError, e: QMessageBox.critical(self, "JEDEC load failed", "Failed to load JEDEC file: %s" % e.strerror) return self.loadJedecData(jedecData, fn) def loadJedecData(self, data, fileName): try: self.jedecData = data self.emulatorSetup() self.guiSetup() self.loadJedecHeader(data, fileName) self.running = True except galemu.GalemuEx: self.emu = None def emulatorSetup(self): try: array = galemu.FuseArray() array.readJedecData(self.jedecData) gal = galemu.selectGalClassObject(array.getGalType()) (array) self.emu = galemu.Emulator(gal) except galemu.GalemuEx, e: QMessageBox.critical(self, "Emulator startup failed", "Failed to initialize the emulator: %s" % e.message) raise def loadJedecHeader(self, data, fileName): lines = [] templates = ("title", "author", "pattern", "company", "revision", "date", ) for line in data.splitlines(): if line.find('\002') != -1: break line = line.strip().lower() if not line: continue showMe = False for template in templates: if template in line: showMe = True if showMe: lines.append(line) text = "JEDEC file: %s" % fileName if lines: text += "\n" + "\n".join(lines) self.setInfoText(text) def setInfoText(self, text): font = self.infoLabel.font() font.setFamily("Courier") font.setPointSize(10) self.infoLabel.setFont(font) self.infoLabel.setText(text) def guiSetup(self): self.inputsWidget.setup(self.emu) self.outputsWidget.setup(self.emu) self.mainwidget.updateAllOutputReferences() def isFrozen(self): return self.mainwidget.isFrozen() def reset(self): if not self.running: return self.inputsWidget.reset() self.emu.reset() if not self.mainwidget.isFrozen(): self.outputsWidget.update() def updateState(self): if not self.running: return self.inputsWidget.commit() self.outputsWidget.update() def save(self): text = "jedec=" + QByteArray(self.jedecData).toHex() + "\n" text += self.inputsWidget.save() text += self.outputsWidget.save() return text class MainWidget(QWidget): def __init__(self, parent): QWidget.__init__(self, parent) self.statusBar = parent.statusBar() self.devices = [] self.devicesEnum = 0 self.resetInProgress = False self.scopes = [] self.scopesEnum = 0 self.generators = [] self.generatorsEnum = 0 layout = QGridLayout() self.addButton = QPushButton("New GAL", self) self.connect(self.addButton, SIGNAL("clicked()"), self.addDevice) layout.addWidget(self.addButton, 0, 0) self.scopeButton = QPushButton("New Oscilloscope", self) self.connect(self.scopeButton, SIGNAL("clicked()"), self.addScope) layout.addWidget(self.scopeButton, 0, 1) self.generatorButton = QPushButton("New Signal Generator", self) self.connect(self.generatorButton, SIGNAL("clicked()"), self.addGenerator) layout.addWidget(self.generatorButton, 0, 2) self.freezeButton = QPushButton("Freeze all GALs", self) self.freezeButton.setCheckable(True) self.connect(self.freezeButton, SIGNAL("clicked()"), self.freezeButtonClicked) layout.addWidget(self.freezeButton, 1, 0) self.resetButton = QPushButton("Reset all GALs", self) self.connect(self.resetButton, SIGNAL("clicked()"), self.resetClicked) layout.addWidget(self.resetButton, 1, 1) self.setLayout(layout) def closeEvent(self): for generator in self.getGeneratorList(): generator.killScript() def isFrozen(self): if self.resetInProgress: return True return self.freezeButton.isChecked() def freezeButtonClicked(self): if not self.freezeButton.isChecked(): for dev in self.devices: dev.updateState() def resetClicked(self): self.resetInProgress = True if self.freezeButton.isChecked(): self.freezeButton.toggle() for dev in self.devices: dev.reset() self.resetInProgress = False for dev in self.devices: dev.updateState() def addScope(self): scope = Oscilloscope(self.scopesEnum, self) self.scopesEnum += 1 self.scopes.append(scope) self.updateAllOutputReferences() scope.show() return scope def removeScope(self, scope): self.scopes.remove(scope) scope.shutdown() scope.deleteLater() def getOscilloscopeList(self): return self.scopes def addGenerator(self, nrOutputs = -1): if self.generators: #FIXME We can only have one gen, because the gen code # is executed in the main thread. QMessageBox.information(self, "Only one generator", "Currently it's only possible to have one "+\ "Signal Generator at a time.") return None if nrOutputs <= 0: (nrOutputs, ok) = QInputDialog.getInteger(self, "Number of outputs", "How many outputs should the new generator have?", 12, 0, 100, 1) if not ok: return None generator = SignalGenerator(self.generatorsEnum, self, nrOutputs) self.generatorsEnum += 1 self.generators.append(generator) generator.show() return generator def removeGenerator(self, generator): self.generators.remove(generator) generator.deleteLater() def getGeneratorList(self): return self.generators def addDevice(self): dev = DeviceWidget(self.devicesEnum, self) self.devicesEnum += 1 self.devices.append(dev) dev.show() return dev def removeDevice(self, deviceWidget): self.devices.remove(deviceWidget) deviceWidget.deleteLater() def getDeviceList(self): return self.devices def getAllAbstractInputs(self, excludeDevs=[]): """Returns a list of all 'class AbstractInput' in the instance 'exceptions' is a list of 'AbstractDevice's that are excluded from the list. The returned object is a tuple: ("DeviceNameString", "InputNameString", AbstractInput_object)""" inputList = [] for dev in self.getDeviceList(): if dev in excludeDevs: continue inputs = dev.getInputsWidget().getInputsList() if not inputs: continue # Device is not initialized for input in inputs: inputList.append( (dev.getNameString(), input.getNameString(), input) ) for scope in self.getOscilloscopeList(): if scope in excludeDevs: continue for trace in scope.getTraces(): for input in trace.getInputs(): devName = scope.getName() inpName = "%s / %s" % (trace.getName(), input.getName()) inputList.append( (devName, inpName, input) ) return inputList def updateAllOutputReferences(self): for dev in self.devices: dev.getOutputsWidget().updateReferences() for generator in self.generators: generator.updateOutputReferences() def save(self): fn = QFileDialog.getSaveFileName(self, "Save emulator setup", QString(), "GAL emulator state file (*.galemu)") if not fn: return fn = str(fn) if not fn.endswith(".galemu"): fn = fn + ".galemu" text = "# GAL emulator state file\n" text += QDateTime.currentDateTime().toString("'# Date: 'ddd MMMM d yyyy hh:mm:ss\n") text += "\n" text += "[GLOBAL]\n" text += "devicesEnum=%d\n" % self.devicesEnum text += "generatorsEnum=%d\n" % self.generatorsEnum text += "scopesEnum=%d\n" % self.scopesEnum haveData = False for dev in self.devices: if not dev.isRunning(): continue haveData = True text += "\n" text += "[DEV_%s]\n" % dev.getNameString() text += dev.save() for scope in self.getOscilloscopeList(): haveData = True text += "\n" text += "[SCOPE_%s]\n" % scope.getName() text += scope.save() for generator in self.getGeneratorList(): haveData = True text += "\n" text += "[GENERATOR_%s]\n" % generator.getName() text += generator.save() if not haveData: QMessageBox.information(self, "Nothing to save", "There is nothing to save. First create and load a device.") return try: fd = file(fn, "w") fd.write(text) except IOError, e: QMessageBox.critical(self, "Failed to write file", "Failed to write file %s: %s" % (fn, e.strerror)) return self.statusBar.showMessage("Stored status to file %s" % fn) def saveOutputConnection(self, outputIdString, abstractInput): "Create a save-file entry for an output connection." inputIdString = "%s:%s" % (abstractInput.getDevice().getIdString(), abstractInput.getIdString()) return "outputconnection%s=%s\n" % (outputIdString, inputIdString) def loadOutputConnection(self, configString): "Returns a (outputIdString, abstractInput) tuple for the parsed config string." #TODO def load(self): haveRunningDev = False for dev in self.devices: if dev.isRunning(): haveRunningDev = True if haveRunningDev: pressed = QMessageBox.question(self, "Discard current setup?", "Loading a new setup will terminate all currently "+\ "running emulators. Do you want to continue?", QMessageBox.Yes | QMessageBox.No) if pressed != QMessageBox.Yes: return fn = QFileDialog.getOpenFileName(self, "Load emulator setup", QString(), "GAL emulator state file (*.galemu)\n" +\ "All files (*.*)") if not fn: return try: p = ConfigParser.ConfigParser() if not p.read([fn, ]): raise ConfigParser.Error() except ConfigParser.Error, e: QMessageBox.critical(self, "Failed to read file", "Failed to read the state file %s" % fn) return # First kill all existing devices for dev in self.devices: self.removeDevice(dev) for generator in self.getGeneratorList(): self.removeGenerator(generator) for scope in self.getOscilloscopeList(): self.removeScope(scope) try: self.devicesEnum = p.getint("GLOBAL", "devicesEnum") self.scopesEnum = p.getint("GLOBAL", "scopesEnum") self.generatorsEnum = p.getint("GLOBAL", "generatorsEnum") except ConfigParser.Error, e: QMessageBox.critical(self, "GLOBAL section error", "Failed to parse the [GLOBAL] section") return #TODO scopes #TODO generators # Create the devices for section in p.sections(): if not section.startswith("DEV_"): continue if not p.has_option(section, "jedec"): QMessageBox.critical(self, "No JEDEC data", "No JEDEC data for %s available!" % section) continue jedecHex = p.get(section, "jedec") jedec = QByteArray.fromHex(jedecHex).data() dev = self.addDevice() dev.loadJedecData(jedec, "XXX")#FIXME filename # Set the input pin states for option in p.options(section): if not option.startswith("input"): continue try: pinNr = int(option[5:]) state = p.getboolean(section, option) except ValueError: QMessageBox.critical(self, "Invalid input", "Invalid input pin definition: %s" % option) continue dev.getInputsWidget().setInputPinState(pinNr, state) # Setup the output connections for dev in self.devices: section = str("DEV_" + dev.getNameString()) for option in p.options(section): if not option.startswith("outputconnection"): continue try: pinNr = int(option[16:]) connTo = p.get(section, option).strip("\"") except ValueError: QMessageBox.critical(self, "Invalid connection", "Invalid output pin connection definition: %s"\ % option) continue dev.getOutputsWidget().selectReference(pinNr, connTo) self.statusBar.showMessage("Loaded status from file %s" % fn) class StatusBar(QStatusBar): def showMessage(self, msg): QStatusBar.showMessage(self, msg, 3000) class MainWindow(QMainWindow): def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.setWindowTitle("GAL/PAL emulator version %s" % galemu.VERSION_STRING) mb = QMenuBar(self) emen = QMenu("Emulator", mb) emen.addAction("Load from file...", self.load) emen.addAction("Save to file...", self.save) emen.addSeparator() emen.addAction("Exit", self.close) mb.addMenu(emen) helpmen = QMenu("Help", mb) helpmen.addAction("About", self.about) mb.addMenu(helpmen) self.setMenuBar(mb) self.setStatusBar(StatusBar()) self.setCentralWidget(MainWidget(self)) def save(self): self.centralWidget().save() def load(self): self.centralWidget().load() def about(self): QMessageBox.information(self, "About", ("GAL/PAL emulator version %s\n" "Copyright (c) 2008 Michael Buesch") %\ galemu.VERSION_STRING) def closeEvent(self, ev): self.centralWidget().closeEvent() global app app.quit() def main(argv): global app global mainwnd global globalTimer app = QApplication(argv) globalTimer = GlobalTimer() mainwnd = MainWindow() mainwnd.show() mainwnd.centralWidget().addDevice() return app.exec_() if __name__ == "__main__": sys.exit(main(sys.argv))