#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # AWL simulator - GUI # Copyright 2012 Michael Buesch # # Licensed under the terms of the GNU General Public License version 2. # import sys import os import time try: from PySide.QtCore import * from PySide.QtGui import * except ImportError as e: print("PLEASE INSTALL PySide (http://www.pyside.org/)") input("Press enter to continue.") sys.exit(1) from awlsim import * from awlinsntrans import * 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, 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, 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, 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 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 diagnostics", 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 details", group) group.layout().addWidget(self.newCpuStateButton, 0, 0, 1, 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.__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 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 __blockExitCallback(self, cpu): self.mayUpdate() self.mainWidget.codeEdit.updateCpuStats_afterBlock(cpu) def __postInsnCallback(self, cpu): self.mainWidget.codeEdit.updateCpuStats_afterInsn(cpu) 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) sim.load(parser.getParseTree()) sim.getCPU().setBlockExitCallback(self.__blockExitCallback, sim.getCPU()) except AwlSimError 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): 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 7 + 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.__charWidth = self.fontMetrics().width('0') self.__charHeight = self.fontMetrics().height() 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) 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.__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.status, 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) self.__cpuStatsStamp += 1 # Update the stats widget if self.__cpuStatsCount >= 500: self.__cpuStatsCount = 0 self.cpuStatsWidget.update() 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: p.drawText(5, 5, self.__charWidth + 1, self.headerWidget.height(), Qt.AlignLeft, self.__aniChars[self.__hdrAniStat]) 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.resetCpuStats() self.codeChanged.emit() def loadCode(self, code): self.__textChangeBlocked += 1 self.setPlainText(code) self.resetCpuStats() self.__textChangeBlocked -= 1 def getCode(self): return self.toPlainText() 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() 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.cpuWidget.runStateChanged.connect(self.__runStateChanged) self.runStateChanged.connect(self.codeEdit.runStateChanged) def isDirty(self): return self.dirty def __runStateChanged(self, newState): self.codeEdit.setReadOnly(newState == CpuWidget.STATE_RUN) 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) 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("Help", self) menu.addAction("&Instruction support...", self.aboutInsns) menu.addSeparator() 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 len(sys.argv) > 1: self.centralWidget().loadFile(sys.argv[1]) 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 aboutInsns(self): suppTypes, unsuppTypes = [], [] for insnType in AwlInsnTranslator.type2class.keys(): if insnType == AwlInsn.TYPE_INVALID or\ insnType >= AwlInsn.TYPE_EXTENDED: continue cls = AwlInsnTranslator.type2class[insnType] if cls == AwlInsn_NotImplemented: unsuppTypes.append(insnType) else: suppTypes.append(insnType) text = [ "Supported instructions (%d):\n" % len(suppTypes) ] suppInsns = [ AwlInsn.type2name[t] for t in suppTypes ] suppInsns.sort() unsuppInsns = [ AwlInsn.type2name[t] for t in unsuppTypes ] unsuppInsns.sort() text.append(" , ".join(suppInsns) + "\n") text.append("\nUnsupported instructions (%d):\n" % len(unsuppTypes)) text.append(" , ".join(unsuppInsns) + "\n") QMessageBox.information(self, "About S7 AWL simulator instructions", "".join(text)) def about(self): QMessageBox.information(self, "About S7 AWL simulator", "awlsim version %d.%d\n\n" "Copyright 2012 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 main(): app = QApplication(sys.argv) mainwnd = MainWindow() mainwnd.show() return app.exec_() if __name__ == "__main__": sys.exit(main())