#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # AWL simulator - GUI # Copyright 2012-2013 Michael Buesch # # Licensed under the terms of the GNU General Public License version 2. # import sys from awlsim.util import * if isPyPy: # PySide does not work on PyPy, yet. printError("Running awlsimgui on the PyPy interpreter is not supported.") printError("Please use CPython 2.7 or CPython 3.x") sys.exit(1) import os import time import getopt try: from PySide.QtCore import * from PySide.QtGui import * except ImportError as e: printError("PLEASE INSTALL PySide (http://www.pyside.org/)") input("Press enter to continue.") sys.exit(1) from awlsim import * from awlsim.insntrans import * opt_awlSource = None opt_extInsns = False class StateWindow(QWidget): closed = Signal() cpuStateChanged = Signal() def __init__(self, sim, parent=None): QWidget.__init__(self, parent) self.setLayout(QGridLayout(self)) pixmap = QPixmap(16, 16) pixmap.fill(QColor(0, 0, 192)) self.setWindowIcon(QIcon(pixmap)) self.sim = sim def update(self): size, hint = self.size(), self.minimumSizeHint() if size.width() < hint.width() or\ size.height() < hint.height(): self.resize(self.minimumSizeHint()) def closeEvent(self, ev): self.closed.emit() ev.accept() class State_CPU(StateWindow): def __init__(self, sim, parent=None): StateWindow.__init__(self, sim, parent) self.setWindowTitle("CPU Details") self.label = QLabel(self) font = self.label.font() font.setFamily("Mono") font.setFixedPitch(True) font.setKerning(False) self.label.setFont(font) self.layout().addWidget(self.label, 0, 0) self.label.setText("No CPU status available, yet.") def update(self): newText = str(self.sim.getCPU()) if newText: self.label.setText(newText) StateWindow.update(self) class AbstractDisplayWidget(QWidget): ADDRSPACE_E = AwlOperator.MEM_E ADDRSPACE_A = AwlOperator.MEM_A ADDRSPACE_M = AwlOperator.MEM_M addrspace2name = { ADDRSPACE_E : "E", ADDRSPACE_A : "A", ADDRSPACE_M : "M", } changed = Signal() def __init__(self, sim, addrSpace, addr, width, parent=None): QWidget.__init__(self, parent) self.setLayout(QGridLayout(self)) self.sim = sim self.addrSpace = addrSpace self.addr = addr self.width = width def get(self): pass def update(self): pass class BitDisplayWidget(AbstractDisplayWidget): def __init__(self, sim, addrSpace, addr, width, parent=None): AbstractDisplayWidget.__init__(self, sim, addrSpace, addr, width, parent) self.cbs = [] for i in range(self.width): cb = QCheckBox(str(i), self) self.layout().addWidget(cb, 0, self.width - i - 1) self.cbs.append(cb) cb.stateChanged.connect(self.changed) self.update() def get(self): value = 0 for i in range(self.width): if self.cbs[i].checkState() == Qt.Checked: value |= (1 << i) return value def update(self): try: oper = AwlOperator(self.addrSpace, self.width, AwlOffset(self.addr)) value = self.sim.getCPU().fetch(oper) except AwlSimError as e: QMessageBox.critical(self, "Failed to fetch", "Failed to fetch memory:\n" + str(e)) return for i in range(self.width): if value & (1 << i): self.cbs[i].setCheckState(Qt.Checked) else: self.cbs[i].setCheckState(Qt.Unchecked) class NumberDisplayWidget(AbstractDisplayWidget): def __init__(self, sim, base, addrSpace, addr, width, parent=None): AbstractDisplayWidget.__init__(self, sim, addrSpace, addr, width, parent) self.base = base self.displayedValue = -1 self.line = QLineEdit(self) self.line.setAlignment(Qt.AlignRight) self.layout().addWidget(self.line) self.line.returnPressed.connect(self.__returnPressed) self.line.textChanged.connect(self.__textChanged) self.update() def __returnPressed(self): self.changed.emit() def __convertValue(self): try: value = int(self.line.text(), self.base) if self.base == 10: if value > (1 << (self.width - 1)) - 1 or\ value < -(1 << (self.width - 1)): raise ValueError else: if value > (1 << self.width) - 1: raise ValueError except ValueError as e: return None return value def __textChanged(self): value = self.__convertValue() if value is None: pal = self.palette() pal.setColor(QPalette.Text, Qt.red) self.setPalette(pal) else: pal = self.palette() pal.setColor(QPalette.Text, Qt.black) self.setPalette(pal) def get(self): value = self.__convertValue() if value is None: return self.displayedValue return value def update(self): try: oper = AwlOperator(self.addrSpace, self.width, AwlOffset(self.addr)) value = self.sim.getCPU().fetch(oper) except AwlSimError as e: QMessageBox.critical(self, "Failed to fetch", "Failed to fetch memory:\n" + str(e)) return if value == self.displayedValue: return self.displayedValue = value if self.base == 2: string = "".join( '1' if (value & (1 << bitnr)) else '0' for bitnr in range(self.width - 1, -1, -1) ) elif self.base == 10: if self.width == 8: value &= 0xFF if value & 0x80: value = -((~value + 1) & 0xFF) string = "%d" % value elif self.width == 16: value &= 0xFFFF if value & 0x8000: value = -((~value + 1) & 0xFFFF) string = "%d" % value elif self.width == 32: value &= 0xFFFFFFFF if value & 0x80000000: value = -((~value + 1) & 0xFFFFFFFF) string = "%d" % value else: assert(0) elif self.base == 16: if self.width == 8: string = "%02X" % (value & 0xFF) elif self.width == 16: string = "%04X" % (value & 0xFFFF) elif self.width == 32: string = "%08X" % (value & 0xFFFFFFFF) else: assert(0) else: assert(0) self.line.setText(string) class HexDisplayWidget(NumberDisplayWidget): def __init__(self, sim, addrSpace, addr, width, parent=None): NumberDisplayWidget.__init__(self, sim, 16, addrSpace, addr, width, parent) class DecDisplayWidget(NumberDisplayWidget): def __init__(self, sim, addrSpace, addr, width, parent=None): NumberDisplayWidget.__init__(self, sim, 10, addrSpace, addr, width, parent) class BinDisplayWidget(NumberDisplayWidget): def __init__(self, sim, addrSpace, addr, width, parent=None): NumberDisplayWidget.__init__(self, sim, 2, addrSpace, addr, width, parent) class State_Mem(StateWindow): def __init__(self, sim, addrSpace, parent=None): StateWindow.__init__(self, sim, parent) name = AbstractDisplayWidget.addrspace2name[addrSpace] self.setWindowTitle(name) self.addrSpace = addrSpace self.addrSpin = QSpinBox(self) self.addrSpin.setPrefix(name + " ") self.layout().addWidget(self.addrSpin, 0, 0) self.widthCombo = QComboBox(self) self.widthCombo.addItem("Byte", 8) self.widthCombo.addItem("Word", 16) self.widthCombo.addItem("DWord", 32) self.layout().addWidget(self.widthCombo, 0, 1) self.fmtCombo = QComboBox(self) self.fmtCombo.addItem("Checkboxes", "cb") self.fmtCombo.addItem("Hexadecimal", "hex") self.fmtCombo.addItem("Decimal", "dec") self.fmtCombo.addItem("Dual", "bin") self.layout().addWidget(self.fmtCombo, 0, 2) self.contentLayout = QGridLayout() self.contentLayout.setContentsMargins(QMargins()) self.layout().addLayout(self.contentLayout, 1, 0, 1, 3) self.contentWidget = None self.addrSpin.valueChanged.connect(self.rebuild) self.widthCombo.currentIndexChanged.connect(self.rebuild) self.fmtCombo.currentIndexChanged.connect(self.rebuild) self.__changeBlocked = 0 self.rebuild() def rebuild(self): if self.contentWidget: self.contentLayout.removeWidget(self.contentWidget) self.contentWidget.deleteLater() self.contentWidget = None addr = self.addrSpin.value() index = self.fmtCombo.currentIndex() fmt = self.fmtCombo.itemData(index) index = self.widthCombo.currentIndex() width = self.widthCombo.itemData(index) if fmt == "cb": self.contentWidget = BitDisplayWidget(self.sim, self.addrSpace, addr, width, self) self.contentLayout.addWidget(self.contentWidget) elif fmt == "hex": self.contentWidget = HexDisplayWidget(self.sim, self.addrSpace, addr, width, self) self.contentLayout.addWidget(self.contentWidget) elif fmt == "dec": self.contentWidget = DecDisplayWidget(self.sim, self.addrSpace, addr, width, self) self.contentLayout.addWidget(self.contentWidget) elif fmt == "bin": self.contentWidget = BinDisplayWidget(self.sim, self.addrSpace, addr, width, self) self.contentLayout.addWidget(self.contentWidget) else: assert(0) self.contentWidget.changed.connect(self.__changed) self.update() def __changed(self): if self.__changeBlocked or not self.contentWidget: return value = self.contentWidget.get() addr = self.addrSpin.value() index = self.widthCombo.currentIndex() width = self.widthCombo.itemData(index) try: oper = AwlOperator(self.addrSpace, width, AwlOffset(addr)) self.sim.getCPU().store(oper, value) except AwlSimError as e: QMessageBox.critical(self, "Failed to store", "Failed to store memory:\n" + str(e)) return self.cpuStateChanged.emit() def update(self): if self.contentWidget: self.__changeBlocked += 1 self.contentWidget.update() self.__changeBlocked -= 1 StateWindow.update(self) class State_LCD(StateWindow): def __init__(self, sim, parent=None): StateWindow.__init__(self, sim, parent) self.setWindowTitle("LCD") self.addrSpin = QSpinBox(self) self.addrSpin.setPrefix("A ") self.layout().addWidget(self.addrSpin, 0, 0) self.widthCombo = QComboBox(self) self.widthCombo.addItem("Byte", 8) self.widthCombo.addItem("Word", 16) self.widthCombo.addItem("DWord", 32) self.layout().addWidget(self.widthCombo, 0, 1) self.endianCombo = QComboBox(self) self.endianCombo.addItem("Big-endian", "be") self.endianCombo.addItem("Little-endian", "le") self.layout().addWidget(self.endianCombo, 1, 0) self.fmtCombo = QComboBox(self) self.fmtCombo.addItem("BCD", "bcd") self.fmtCombo.addItem("Signed BCD", "signed-bcd") self.fmtCombo.addItem("Binary", "bin") self.fmtCombo.addItem("Signed binary", "signed-bin") self.layout().addWidget(self.fmtCombo, 1, 1) self.lcd = QLCDNumber(self) self.lcd.setMinimumHeight(50) self.layout().addWidget(self.lcd, 2, 0, 1, 2) self.addrSpin.valueChanged.connect(self.rebuild) self.widthCombo.currentIndexChanged.connect(self.rebuild) self.endianCombo.currentIndexChanged.connect(self.rebuild) self.fmtCombo.currentIndexChanged.connect(self.rebuild) self.__changeBlocked = 0 self.rebuild() def getDataWidth(self): index = self.widthCombo.currentIndex() return self.widthCombo.itemData(index) def getFormat(self): index = self.fmtCombo.currentIndex() return self.fmtCombo.itemData(index) def getEndian(self): index = self.endianCombo.currentIndex() return self.endianCombo.itemData(index) def rebuild(self): self.update() def update(self): addr = self.addrSpin.value() width = self.getDataWidth() fmt = self.getFormat() endian = self.getEndian() try: oper = AwlOperator(AwlOperator.MEM_A, width, AwlOffset(addr)) value = self.sim.getCPU().fetch(oper) except AwlSimError as e: QMessageBox.critical(self, "Failed to fetch", "Failed to fetch memory:\n" + str(e)) return if endian == "le": if width == 16: value = swapEndianWord(value) elif width == 32: value = swapEndianDWord(value) if fmt == "bcd": if width == 8: value = "%02X" % (value & 0xFF) elif width == 16: value = "%04X" % (value & 0xFFFF) elif width == 32: value = "%08X" % (value & 0xFFFFFFFF) else: assert(0) elif fmt == "signed-bcd": if width == 8: sign = '-' if (value & 0xF0) else '' value = "%s%01X" % (sign, value & 0x0F) elif width == 16: sign = '-' if (value & 0xF000) else '' value = "%s%03X" % (sign, value & 0x0FFF) elif width == 32: sign = '-' if (value & 0xF0000000) else '' value = "%s%07X" % (sign, value & 0x0FFFFFFF) else: assert(0) elif fmt == "bin": if width == 8: value = "%d" % (value & 0xFF) elif width == 16: value = "%d" % (value & 0xFFFF) elif width == 32: value = "%d" % (value & 0xFFFFFFFF) else: assert(0) elif fmt == "signed-bin": if width == 8: value = "%d" % byteToSignedPyInt(value) elif width == 16: value = "%d" % wordToSignedPyInt(value) elif width == 32: value = "%d" % dwordToSignedPyInt(value) else: assert(0) else: assert(0) self.__changeBlocked += 1 self.lcd.setDigitCount(len(value)) self.lcd.display(value) self.__changeBlocked -= 1 StateWindow.update(self) class StateWorkspace(QWorkspace): def __init__(self, parent=None): QWorkspace.__init__(self, parent) class CpuWidget(QWidget): runStateChanged = Signal(int) STATE_STOP = 0 STATE_RUN = 1 def __init__(self, mainWidget, parent=None): QWidget.__init__(self, parent) self.setLayout(QGridLayout(self)) self.mainWidget = mainWidget self.state = self.STATE_STOP self.nextUpdate = 0.0 group = QGroupBox("CPU status", self) group.setLayout(QGridLayout(group)) self.runButton = QRadioButton("RUN", group) group.layout().addWidget(self.runButton, 0, 0) self.stopButton = QRadioButton("STOP", group) group.layout().addWidget(self.stopButton, 1, 0) self.onlineViewCheckBox = QCheckBox("Online diag.", group) group.layout().addWidget(self.onlineViewCheckBox, 2, 0) self.layout().addWidget(group, 0, 0) group = QGroupBox("Add window", self) group.setLayout(QGridLayout(group)) self.newCpuStateButton = QPushButton("CPU", group) group.layout().addWidget(self.newCpuStateButton, 0, 0) self.newLCDButton = QPushButton("LCD", group) group.layout().addWidget(self.newLCDButton, 0, 1) self.newEButton = QPushButton("E", group) group.layout().addWidget(self.newEButton, 1, 0) self.newAButton = QPushButton("A", group) group.layout().addWidget(self.newAButton, 1, 1) self.newMButton = QPushButton("M", group) group.layout().addWidget(self.newMButton, 1, 2) self.layout().addWidget(group, 0, 1) self.stateWs = StateWorkspace(self) self.stateWs.setScrollBarsEnabled(True) self.layout().addWidget(self.stateWs, 1, 0, 1, 2) self.stopButton.setChecked(Qt.Checked) self.runButton.toggled.connect(self.__runStateToggled) self.stopButton.toggled.connect(self.__runStateToggled) self.onlineViewCheckBox.stateChanged.connect(self.__updateOnlineViewState) self.newCpuStateButton.released.connect(self.__newWin_CPU) self.newEButton.released.connect(self.__newWin_E) self.newAButton.released.connect(self.__newWin_A) self.newMButton.released.connect(self.__newWin_M) self.newLCDButton.released.connect(self.__newWin_LCD) self.__newWin_CPU() self.update() def __addWindow(self, win): win.cpuStateChanged.connect(self.update) self.stateWs.addWindow(win, Qt.Window) win.show() self.update() def __newWin_CPU(self): self.__addWindow(State_CPU(self.mainWidget.getSim(), self)) def __newWin_E(self): self.__addWindow(State_Mem(self.mainWidget.getSim(), AbstractDisplayWidget.ADDRSPACE_E, self)) def __newWin_A(self): self.__addWindow(State_Mem(self.mainWidget.getSim(), AbstractDisplayWidget.ADDRSPACE_A, self)) def __newWin_M(self): self.__addWindow(State_Mem(self.mainWidget.getSim(), AbstractDisplayWidget.ADDRSPACE_M, self)) def __newWin_LCD(self): self.__addWindow(State_LCD(self.mainWidget.getSim(), self)) def update(self): for win in self.stateWs.windowList(): win.update() def mayUpdate(self): now = time.time() if now < self.nextUpdate: return self.nextUpdate = now + 0.1 self.update() def __cycleExitCallback(self, cpu): if self.state == self.STATE_RUN: self.mainWidget.codeEdit.updateCpuStats_afterCycle(cpu) def __blockExitCallback(self, cpu): if self.state == self.STATE_RUN: self.mayUpdate() self.mainWidget.codeEdit.updateCpuStats_afterBlock(cpu) def __postInsnCallback(self, cpu): if self.state == self.STATE_RUN: self.mainWidget.codeEdit.updateCpuStats_afterInsn(cpu) def __directPeripheralCallback(self, cpu, operator): pass#TODO def __screenUpdateCallback(self, cpu): self.__postInsnCallback(cpu) self.__blockExitCallback(cpu) self.__cycleExitCallback(cpu) QApplication.processEvents(QEventLoop.AllEvents, 1) def __run(self): sim = self.mainWidget.getSim() self.state = self.STATE_RUN self.__updateOnlineViewState() self.mainWidget.codeEdit.resetCpuStats() self.runButton.setChecked(True) self.runButton.setEnabled(False) # Redraws the radio button self.runButton.setEnabled(True) ob1_awl = self.mainWidget.getCodeEditWidget().getCode() if not ob1_awl.strip(): QMessageBox.critical(self, "No AWL Code", "No AWL Code") self.stop() return try: parser = AwlParser() parser.parseData(ob1_awl) cpu = sim.getCPU() cpu.setBlockExitCallback(self.__blockExitCallback, cpu) cpu.setCycleExitCallback(self.__cycleExitCallback, cpu) cpu.setDirectPeripheralCallback( self.__directPeripheralCallback, cpu) cpu.setScreenUpdateCallback( self.__screenUpdateCallback, cpu) sim.load(parser.getParseTree()) except (AwlSimError, AwlParserError) as e: QMessageBox.critical(self, "Simulator exception", str(e)) self.stop() return self.runStateChanged.emit(self.state) while self.state == self.STATE_RUN: try: QApplication.processEvents(QEventLoop.AllEvents, 1) sim.runCycle() except AwlSimError as e: QMessageBox.critical(self, "Simulator exception", str(e)) self.stop() self.runStateChanged.emit(self.state) def stop(self): if self.state == self.STATE_STOP: return self.state = self.STATE_STOP self.stopButton.setChecked(True) self.runButton.setEnabled(True) def getState(self): return self.state def __runStateToggled(self): if self.runButton.isChecked(): if self.state != self.STATE_RUN: self.__run() if self.stopButton.isChecked(): if self.state != self.STATE_STOP: self.stop() def __updateOnlineViewState(self): en = self.onlineViewCheckBox.checkState() == Qt.Checked self.mainWidget.codeEdit.enableCpuStats(en) cpu = self.mainWidget.getSim().getCPU() if en: cpu.setPostInsnCallback(self.__postInsnCallback, cpu) else: cpu.setPostInsnCallback(None) class EditSubWidget(QWidget): needRepaint = Signal(QPaintEvent) wasScrolled = Signal(QWheelEvent) def __init__(self, editWidget): QWidget.__init__(self, editWidget) self.editWidget = editWidget def paintEvent(self, ev): self.needRepaint.emit(ev) def wheelEvent(self, ev): self.wasScrolled.emit(ev) def getPainter(self): p = QPainter(self) font = p.font() font.setFamily("Mono") font.setKerning(False) font.setFixedPitch(True) font.setStyleStrategy(QFont.PreferBitmap) p.setFont(font) return p class HeaderSubWidget(EditSubWidget): def __init__(self, editWidget): EditSubWidget.__init__(self, editWidget) def sizeHint(self): return QSize(0, self.editWidget.headerHeight()) class LineNumSubWidget(EditSubWidget): def __init__(self, editWidget): EditSubWidget.__init__(self, editWidget) def sizeHint(self): return QSize(self.editWidget.lineNumWidgetWidth(), 0) class CpuStatsSubWidget(EditSubWidget): def __init__(self, editWidget): EditSubWidget.__init__(self, editWidget) def sizeHint(self): return QSize(self.editWidget.cpuStatsWidgetWidth(), 0) def getBanner(self): return "STW ACCU 1 ACCU 2" class CpuStatsEntry(object): def __init__(self, stamp, statusWord, accu1, accu2): self.stamp = stamp self.obsolete = False self.statusWord = statusWord.getWord() self.accu1 = accu1.getDWord() self.accu2 = accu2.getDWord() @staticmethod def getTextWidth(): return 11 + 2 + 8 + 2 + 8 def __repr__(self): stw = [] for i in range(S7StatusWord.NR_BITS - 1, -1, -1): stw.append('1' if (self.statusWord & (1 << i)) else '0') if i % 4 == 0 and i: stw.append('_') return "%s %08X %08X" %\ ("".join(stw), self.accu1, self.accu2) class EditWidget(QPlainTextEdit): codeChanged = Signal() __aniChars = ( ' ', '.', 'o', '0', 'O', '0', 'o', '.' ) def __init__(self, mainWidget): QPlainTextEdit.__init__(self, mainWidget) self.mainWidget = mainWidget self.__updateFonts() self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setLineWrapMode(QPlainTextEdit.NoWrap) self.setTabStopWidth(self.tabStopWidth() // 2) self.headerWidget = HeaderSubWidget(self) self.lineNumWidget = LineNumSubWidget(self) self.cpuStatsWidget = CpuStatsSubWidget(self) self.__runStateCopy = CpuWidget.STATE_STOP self.__nextHdrUpdate = 0 self.__hdrAniStat = 0 self.enableCpuStats(False) self.resetCpuStats(True) self.__textChangeBlocked = 0 self.textChanged.connect(self.__textChanged) self.blockCountChanged.connect(self.__updateMargins) self.updateRequest.connect(self.__updateExtraWidgets) self.headerWidget.needRepaint.connect(self.__repaintHeaderWidget) self.lineNumWidget.needRepaint.connect(self.__repaintLineNumWidget) self.cpuStatsWidget.needRepaint.connect(self.__repaintCpuStatsWidget) self.headerWidget.wasScrolled.connect(self.__forwardWheelEvent) self.lineNumWidget.wasScrolled.connect(self.__forwardWheelEvent) self.cpuStatsWidget.wasScrolled.connect(self.__forwardWheelEvent) def runStateChanged(self, newState): self.__runStateCopy = newState if self.__cpuStatsEnabled: self.cpuStatsWidget.update() self.headerWidget.update() def __updateFonts(self): fmt = self.currentCharFormat() fmt.setFontFamily("Mono") fmt.setFontKerning(False) fmt.setFontFixedPitch(True) fmt.setFontStyleStrategy(QFont.PreferBitmap) self.setCurrentCharFormat(fmt) self.__charWidth = self.fontMetrics().width('X') self.__charHeight = self.fontMetrics().height() def enableCpuStats(self, enabled=True): self.__cpuStatsEnabled = enabled self.__updateMargins() self.__updateGeo() def resetCpuStats(self, force=False): if not force and not self.__lineCpuStats: return self.__lineCpuStats = { } self.__cpuStatsCount = 0 self.__cpuStatsUpdate = 1 self.__cpuStatsStamp = 0 self.__updateMargins() self.headerWidget.update() self.cpuStatsWidget.update() def updateCpuStats_afterInsn(self, cpu): insn = cpu.getCurrentInsn() if not self.__cpuStatsEnabled or not insn: return self.__lineCpuStats[insn.getLineNr() - 1] =\ CpuStatsEntry(self.__cpuStatsStamp, cpu.getStatusWord(), cpu.getAccu(1), cpu.getAccu(2)) self.__cpuStatsCount += 1 def updateCpuStats_afterBlock(self, cpu): if cpu.now >= self.__nextHdrUpdate: self.__nextHdrUpdate = cpu.now + 0.2 self.__hdrAniStat = (self.__hdrAniStat + 1) %\ len(self.__aniChars) self.headerWidget.update() if not self.__cpuStatsEnabled: return # Update the 'obsolete'-flag based on the timestamp for ent in self.__lineCpuStats.values(): ent.obsolete = (ent.stamp != self.__cpuStatsStamp) # Update the stats widget if self.__cpuStatsCount >= self.__cpuStatsUpdate: self.__cpuStatsCount = 0 self.__cpuStatsUpdate = cpu.getRandomInt(0, 255) + 1 self.cpuStatsWidget.update() def updateCpuStats_afterCycle(self, cpu): if self.__cpuStatsEnabled: self.__cpuStatsStamp += 1 def __forwardWheelEvent(self, ev): self.wheelEvent(ev) def lineNumWidgetWidth(self): digi, bcnt = 1, self.blockCount() while bcnt > 9: digi, bcnt = digi + 1, bcnt // 10 digi += 1 # colon return 5 + 5 + digi * self.__charWidth def cpuStatsWidgetWidth(self): if not self.__cpuStatsEnabled: return 0 return 5 + 5 + CpuStatsEntry.getTextWidth() * self.__charWidth def headerHeight(self): return 5 + 5 + self.__charHeight def __updateMargins(self): self.setViewportMargins(self.lineNumWidgetWidth(), self.headerHeight(), self.cpuStatsWidgetWidth(), 0) def sizeHint(self): sh = QPlainTextEdit.sizeHint(self) sh.setWidth(650 +\ self.lineNumWidgetWidth() +\ self.cpuStatsWidgetWidth()) return sh def __updateHeaderWidgetGeo(self): cont = self.contentsRect() rect = QRect(cont.left(), cont.top(), cont.width(), self.headerHeight()) self.headerWidget.setGeometry(rect) def __updateLineNumWidgetGeo(self): cont = self.contentsRect() rect = QRect(cont.left(), cont.top() + self.headerHeight(), self.lineNumWidgetWidth(), cont.height()) self.lineNumWidget.setGeometry(rect) def __updateCpuStatsWidgetGeo(self): if self.__cpuStatsEnabled: vp, cont = self.viewport(), self.contentsRect() rect = QRect(vp.width() + self.lineNumWidgetWidth(), cont.top() + self.headerHeight(), self.cpuStatsWidgetWidth(), cont.height()) self.cpuStatsWidget.setGeometry(rect) self.cpuStatsWidget.show() else: self.cpuStatsWidget.hide() def __updateGeo(self): self.__updateHeaderWidgetGeo() self.__updateLineNumWidgetGeo() self.__updateCpuStatsWidgetGeo() def __updateExtraWidgets(self, rect, dy): if dy: self.headerWidget.scroll(0, dy) self.lineNumWidget.scroll(0, dy) self.cpuStatsWidget.scroll(0, dy) return self.headerWidget.update(0, rect.y(), self.headerWidget.width(), rect.height()) self.lineNumWidget.update(0, rect.y(), self.lineNumWidget.width(), rect.height()) self.cpuStatsWidget.update(0, rect.y(), self.cpuStatsWidget.width(), rect.height()) if rect.contains(self.viewport().rect()): self.__updateMargins() def resizeEvent(self, ev): QPlainTextEdit.resizeEvent(self, ev) self.__updateGeo() def __repaintHeaderWidget(self, ev): p = self.headerWidget.getPainter() p.fillRect(ev.rect(), Qt.lightGray) if self.__runStateCopy == CpuWidget.STATE_RUN: runText = self.__aniChars[self.__hdrAniStat] else: runText = "-- CPU STOPPED --" p.drawText(5, 5, self.__charWidth * len(runText) + 1, self.headerWidget.height(), Qt.AlignLeft, runText) if self.__cpuStatsEnabled: # Map the starting point pt = self.cpuStatsWidget.mapToGlobal(QPoint(0, 0)) pt = self.headerWidget.mapFromGlobal(pt) p.drawText(pt.x() + 5, 5, self.headerWidget.width() - pt.x() - 5, self.headerWidget.height(), Qt.AlignLeft, self.cpuStatsWidget.getBanner()) def __repaintLineNumWidget(self, ev): p = self.lineNumWidget.getPainter() rect = ev.rect() p.fillRect(rect, Qt.lightGray) rect.setLeft(rect.left() + rect.width() - 3) rect.setWidth(3) p.fillRect(rect, Qt.white) p.setPen(Qt.black) block = self.firstVisibleBlock() bn = block.blockNumber() top = self.blockBoundingGeometry(block).translated(self.contentOffset()).top() bottom = top + self.blockBoundingRect(block).height() while block.isValid() and top <= ev.rect().bottom(): if block.isVisible() and bottom >= ev.rect().top(): p.drawText(-5, top, self.lineNumWidget.width(), self.__charHeight, Qt.AlignRight, str(bn + 1) + ':') block = block.next() top = bottom bottom = top + self.blockBoundingRect(block).height() bn += 1 def __repaintCpuStatsWidget(self, ev): p = self.cpuStatsWidget.getPainter() rect = ev.rect() p.fillRect(rect, Qt.lightGray) rect.setWidth(3) p.fillRect(rect, Qt.white) block = self.firstVisibleBlock() bn = block.blockNumber() top = self.blockBoundingGeometry(block).translated(self.contentOffset()).top() bottom = top + self.blockBoundingRect(block).height() while block.isValid() and top <= ev.rect().bottom(): statsEnt = self.__lineCpuStats.get(bn) if statsEnt and block.isVisible() and bottom >= ev.rect().top(): if statsEnt.obsolete: p.setPen(Qt.darkGray) else: p.setPen(Qt.black) p.drawText(5, top, self.cpuStatsWidget.width(), self.__charHeight, Qt.AlignLeft, str(statsEnt)) block = block.next() top = bottom bottom = top + self.blockBoundingRect(block).height() bn += 1 def __textChanged(self): self.__updateFonts() if self.__textChangeBlocked: return self.codeChanged.emit() self.resetCpuStats() def loadCode(self, code): self.__textChangeBlocked += 1 self.setPlainText(code) self.resetCpuStats() self.__textChangeBlocked -= 1 def getCode(self): return self.toPlainText() class CpuConfigDialog(QDialog): def __init__(self, parent, sim): QDialog.__init__(self, parent) self.sim = sim self.setWindowTitle("CPU configuration") self.__updateBlocked = 0 self.setLayout(QGridLayout(self)) label = QLabel("Number of accumulator registers", self) self.layout().addWidget(label, 0, 0) self.accuCombo = QComboBox(self) self.accuCombo.addItem("2 accus", 2) self.accuCombo.addItem("4 accus", 4) self.layout().addWidget(self.accuCombo, 0, 1) label = QLabel("Mnemonics", self) self.layout().addWidget(label, 1, 0) self.mnemonicsCombo = QComboBox(self) self.mnemonicsCombo.addItem("Automatic", S7CPUSpecs.MNEMONICS_AUTO) self.mnemonicsCombo.addItem("English", S7CPUSpecs.MNEMONICS_EN) self.mnemonicsCombo.addItem("German", S7CPUSpecs.MNEMONICS_DE) self.layout().addWidget(self.mnemonicsCombo, 1, 1) self.closeButton = QPushButton("Close", self) self.layout().addWidget(self.closeButton, 2, 1) self.accuCombo.currentIndexChanged.connect(self.__accuConfigChanged) self.mnemonicsCombo.currentIndexChanged.connect(self.__mnemonicsConfigChanged) self.closeButton.released.connect(self.accept) self.loadConfig() def loadConfig(self): specs = self.sim.getCPU().getSpecs() self.__updateBlocked += 1 index = self.accuCombo.findData(specs.getNrAccus()) assert(index >= 0) self.accuCombo.setCurrentIndex(index) index = self.mnemonicsCombo.findData(specs.getConfiguredMnemonics()) assert(index >= 0) self.mnemonicsCombo.setCurrentIndex(index) self.__updateBlocked -= 1 def __accuConfigChanged(self): if self.__updateBlocked: return specs = self.sim.getCPU().getSpecs() index = self.accuCombo.currentIndex() specs.setNrAccus(self.accuCombo.itemData(index)) def __mnemonicsConfigChanged(self): if self.__updateBlocked: return specs = self.sim.getCPU().getSpecs() index = self.mnemonicsCombo.currentIndex() specs.setConfiguredMnemonics(self.mnemonicsCombo.itemData(index)) class MainWidget(QWidget): dirtyChanged = Signal(bool) runStateChanged = Signal(int) def __init__(self, parent=None): QWidget.__init__(self, parent) self.setLayout(QGridLayout(self)) self.sim = AwlSim() if opt_extInsns: self.sim.getCPU().enableExtendedInsns(True) self.splitter = QSplitter(Qt.Horizontal) self.layout().addWidget(self.splitter, 0, 0) self.codeEdit = EditWidget(self) self.splitter.addWidget(self.codeEdit) self.cpuWidget = CpuWidget(self, self) self.splitter.addWidget(self.cpuWidget) self.filename = None self.dirty = False self.codeEdit.codeChanged.connect(self.__codeChanged) self.codeEdit.codeChanged.connect(self.cpuWidget.stop) self.cpuWidget.runStateChanged.connect(self.__runStateChanged) self.runStateChanged.connect(self.codeEdit.runStateChanged) def isDirty(self): return self.dirty def __runStateChanged(self, newState): self.runStateChanged.emit(newState) def getSim(self): return self.sim def getCodeEditWidget(self): return self.codeEdit def getCpuWidget(self): return self.cpuWidget def __codeChanged(self): self.dirty = True self.dirtyChanged.emit(self.dirty) def loadFile(self, filename): try: data = awlFileRead(filename) except AwlParserError as e: QMessageBox.critical(self, "File read failed", str(e)) return False self.codeEdit.loadCode(data) self.filename = filename return True def load(self): fn, fil = QFileDialog.getOpenFileName(self, "Open AWL source", "", "AWL source (*.awl);;" "All files (*)") if not fn: return self.loadFile(fn) def saveFile(self, filename): code = self.codeEdit.getCode() try: awlFileWrite(filename, code) except AwlParserError as e: QMessageBox.critical(self, "Failed to write file", str(e)) return False self.dirty = False self.dirtyChanged.emit(self.dirty) self.filename = filename return True def save(self, newFile=False): if newFile or not self.filename: fn, fil = QFileDialog.getSaveFileName(self, "AWL source save as", "", "AWL source (*.awl)", "*.awl") if not fn: return if not fn.endswith(".awl"): fn += ".awl" return self.saveFile(fn) else: return self.saveFile(self.filename) def cpuConfig(self): dlg = CpuConfigDialog(self, self.sim) dlg.exec_() class MainWindow(QMainWindow): def __init__(self): QMainWindow.__init__(self) self.setWindowTitle("S7 AWL simulator v%d.%d" %\ (VERSION_MAJOR, VERSION_MINOR)) self.setCentralWidget(MainWidget(self)) self.setMenuBar(QMenuBar(self)) menu = QMenu("&File", self) menu.addAction("&Open...", self.load) self.saveAct = menu.addAction("&Save", self.save) menu.addAction("&Save as...", self.saveAs) menu.addSeparator() menu.addAction("&Exit...", self.close) self.menuBar().addMenu(menu) menu = QMenu("&Simulator", self) menu.addAction("&CPU config...", self.cpuConfig) self.menuBar().addMenu(menu) menu = QMenu("Help", self) menu.addAction("&About...", self.about) self.menuBar().addMenu(menu) self.tb = QToolBar(self) self.tb.addAction("Open", self.load) self.tbSaveAct = self.tb.addAction("Save", self.save) self.addToolBar(self.tb) self.__dirtyChanged(False) self.centralWidget().dirtyChanged.connect(self.__dirtyChanged) self.centralWidget().runStateChanged.connect(self.__runStateChanged) if opt_awlSource: self.centralWidget().loadFile(opt_awlSource) def __runStateChanged(self, newState): self.menuBar().setEnabled(newState == CpuWidget.STATE_STOP) self.tb.setEnabled(newState == CpuWidget.STATE_STOP) def __dirtyChanged(self, isDirty): self.saveAct.setEnabled(isDirty) self.tbSaveAct.setEnabled(isDirty) def closeEvent(self, ev): cpuWidget = self.centralWidget().getCpuWidget() if cpuWidget.getState() != CpuWidget.STATE_STOP: res = QMessageBox.question(self, "CPU is in RUN state", "CPU is in RUN state.\n" "STOP CPU and quit application?", QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if res != QMessageBox.Yes: ev.ignore() return self.centralWidget().getCpuWidget().stop() if self.centralWidget().isDirty(): res = QMessageBox.question(self, "Unsaved AWL code", "The editor contains unsaved AWL code.\n" "AWL code will be lost by exiting without saving.", QMessageBox.Discard | QMessageBox.Save | QMessageBox.Cancel, QMessageBox.Cancel) if res == QMessageBox.Save: if not self.centralWidget().save(): ev.ignore() return elif res == QMessageBox.Cancel: ev.ignore() return ev.accept() QMainWindow.closeEvent(self, ev) def about(self): QMessageBox.information(self, "About S7 AWL simulator", "awlsim version %d.%d\n\n" "Copyright 2012-2013 Michael Büsch \n" "Licensed under the terms of the " "GNU GPL version 2." %\ (VERSION_MAJOR, VERSION_MINOR)) def load(self): self.centralWidget().load() def save(self): self.centralWidget().save() def saveAs(self): self.centralWidget().save(True) def cpuConfig(self): self.centralWidget().cpuConfig() def usage(): printInfo("awlsimgui version %d.%d" % (VERSION_MAJOR, VERSION_MINOR)) printInfo("") printInfo("%s [OPTIONS] [AWL-source]" % sys.argv[0]) printInfo("") printInfo("Options:") printInfo(" -x|--extended-insns Enable extended instructions") def main(): global opt_awlSource global opt_extInsns try: (opts, args) = getopt.getopt(sys.argv[1:], "hx", [ "help", "extended-insns", ]) except getopt.GetoptError as e: printError(str(e)) usage() return 1 for (o, v) in opts: if o in ("-h", "--help"): usage() return 0 if o in ("-x", "--extended-insns"): opt_extInsns = True if args: if len(args) == 1: opt_awlSource = args[0] else: usage() return 1 app = QApplication(sys.argv) mainwnd = MainWindow() mainwnd.show() return app.exec_() if __name__ == "__main__": sys.exit(main())