# -*- coding: utf-8 -*- # # AWL simulator - FUP widget # # Copyright 2016-2018 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, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # from __future__ import division, absolute_import, print_function, unicode_literals #from awlsim.common.cython_support cimport * #@cy from awlsim.common.compat import * from awlsim.gui.fup.fupdrawwidget import * from awlsim.gui.fup.fupelemcontainerwidget import * from awlsim.gui.fup.undo import * from awlsim.gui.interfedit.interfwidget import * from awlsim.gui.editwidget import * from awlsim.gui.optimizerconfig import * from awlsim.gui.util import * from awlsim.common.blocker import * from awlsim.fupcompiler import * from awlsim.awloptimizer.awloptimizer import * FUP_DEBUG = 0 class FupFactory(XmlFactory): FUP_VERSION = 0 def parser_open(self, tag=None): self.inFup = False self.inGrids = False XmlFactory.parser_open(self, tag) def parser_beginTag(self, tag): blockTypeEdit = self.fupWidget.interf.blockTypeEdit interfModel = self.fupWidget.interf.interfView.model() drawWidget = self.fupWidget.edit.draw grid = drawWidget.grid if self.inFup: if self.inGrids: if tag.name == "grid": self.parser_switchTo(grid.factory(grid=grid)) return else: if tag.name == "blockdecl": self.parser_switchTo(blockTypeEdit.factory( blockTypeWidget=blockTypeEdit)) return if tag.name == "interface": self.parser_switchTo(interfModel.factory( model=interfModel)) return if tag.name == "grids": self.inGrids = True return else: if tag.name == "FUP": version = tag.getAttrInt("version") zoom = tag.getAttrFloat("zoom", 1.0) if version != self.FUP_VERSION: raise self.Error("Unsupported FUP version. " "Got %d, but expected %d." % ( version, self.FUP_VERSION)) drawWidget.beginLoad() drawWidget.zoom = zoom self.inFup = True return XmlFactory.parser_beginTag(self, tag) def parser_endTag(self, tag): drawWidget = self.fupWidget.edit.draw if self.inFup: if self.inGrids: if tag.name == "grids": self.inGrids = False return else: if tag.name == "FUP": self.inFup = False drawWidget.finalizeLoad() self.parser_finish() return XmlFactory.parser_endTag(self, tag) def composer_getTags(self): childTags = [] drawWidget = self.fupWidget.edit.draw grid = drawWidget.grid blockTypeEdit = self.fupWidget.interf.blockTypeEdit childTags.extend(blockTypeEdit.factory( blockTypeWidget=blockTypeEdit).composer_getTags()) interfModel = self.fupWidget.interf.interfView.model() childTags.extend(interfModel.factory( model=interfModel).composer_getTags()) childTags.append(self.Tag(name="grids", tags=grid.factory(grid=grid).composer_getTags())) tags = [ self.Tag(name="FUP", comment="Awlsim FUP/FBD source generated by awlsim-%s" % VERSION_STRING, attrs={ "version" : str(self.FUP_VERSION), "zoom" : "%.1f" % float(drawWidget.zoom), }, tags=childTags), ] return tags def compose(self, *args, **kwargs): kwargs["stripWs"] = True return super(self.__class__, self).compose(*args, **kwargs) class FupEditWidgetMenu(QMenu): configOpt = Signal() showAwlOpt = Signal() showAwl = Signal() showStlOpt = Signal() showStl = Signal() genCall = Signal() def __init__(self, parent=None): QMenu.__init__(self, parent) self.addAction("Configure &optimizer...", lambda: self.configOpt.emit()) self.addAction("Show AWL code (DE, optimized)...", lambda: self.showAwlOpt.emit()) self.addAction("Show AWL code (DE)...", lambda: self.showAwl.emit()) self.addAction("Show STL code (EN, optimized)...", lambda: self.showStlOpt.emit()) self.addAction("Show STL code (EN)...", lambda: self.showStl.emit()) self.addAction("Generate CALL template...", lambda: self.genCall.emit()) class FupEditWidget(QWidget): def __init__(self, parent, interfWidget): QWidget.__init__(self, parent) self.setLayout(QGridLayout()) self.layout().setContentsMargins(QMargins()) self.__splitter = QSplitter(self) self.__leftWidget = QWidget(self) self.__leftWidget.setLayout(QVBoxLayout()) self.__leftWidget.layout().setContentsMargins(QMargins()) self.container = FupElemContainerWidget(self) self.__leftWidget.layout().addWidget(self.container) self.menu = FupEditWidgetMenu(self) self.menuButton = QPushButton("FUP/FBD compiler...", self) self.menuButton.setMenu(self.menu) self.__leftWidget.layout().addWidget(self.menuButton) self.__splitter.addWidget(self.__leftWidget) self.draw = FupDrawWidget(self, interfWidget) self.__drawScroll = QScrollArea(self) self.__drawScroll.setWidget(self.draw) self.__splitter.addWidget(self.__drawScroll) self.layout().addWidget(self.__splitter, 0, 0) self.draw.selectTipChanged.connect(self.__handleMouseTipChange) self.draw.moveTipChanged.connect(self.__handleMouseTipChange) def __handleMouseTipChange(self, x, y): # Scroll the draw area so that the # selection or element move is visible. self.__drawScroll.ensureVisible(x, y) class FupWidget(QWidget): """Main FUP/FBD widget.""" diagramChanged = Signal() undoAvailableChanged = Signal(bool) redoAvailableChanged = Signal(bool) clipboardCopyAvailableChanged = Signal(bool) clipboardCutAvailableChanged = Signal(bool) clipboardPasteAvailableChanged = Signal(bool) def __init__(self, parent, getSymTabSourcesFunc): QWidget.__init__(self, parent) self.setLayout(QGridLayout()) self.__undoStack = FupUndoStack(self) self.__undoStackUpdateTimer = QTimer(self) self.__undoStackUpdateTimer.setSingleShot(True) self.undoStackBlocked = Blocker() self.__getSymTabSourcesFunc = getSymTabSourcesFunc self.__source = FupSource(name = "Diagram 1") self.__needSourceUpdate = True self.splitter = QSplitter(Qt.Vertical) self.interf = AwlInterfWidget(self) self.splitter.addWidget(self.interf) self.edit = FupEditWidget(self, self.interf) self.splitter.addWidget(self.edit) self.layout().addWidget(self.splitter, 0, 0) self.interf.contentChanged.connect(self.diagramChanged) self.edit.draw.diagramChanged.connect(self.diagramChanged) self.edit.draw.selectionChanged.connect(self.__handleSelectionChange) self.edit.menu.configOpt.connect(self.__configureOptimizer) self.edit.menu.showAwlOpt.connect(self.__compileAndShowAwlOpt) self.edit.menu.showAwl.connect(self.__compileAndShowAwl) self.edit.menu.showStlOpt.connect(self.__compileAndShowStlOpt) self.edit.menu.showStl.connect(self.__compileAndShowStl) self.edit.menu.genCall.connect(self.__generateCallTemplate) self.diagramChanged.connect(self.__handleDiagramChange) self.__undoStackUpdateTimer.timeout.connect(self.__updateUndoStack) self.__undoStack.canUndoChanged.connect(self.undoAvailableChanged) self.__undoStack.canRedoChanged.connect(self.redoAvailableChanged) @property def allGrids(self): """Get an iterator over all grids. Currently there's only one grid. """ for grid in (self.edit.draw.grid,): yield grid def regenAllUUIDs(self): """Re-generate all UUIDs that belong to this FUP diagram. """ self.interf.regenAllUUIDs() for grid in self.allGrids: grid.regenAllUUIDs() def __handleDiagramChange(self): """Handle a change in the FUP diagram or interface. """ self.__needSourceUpdate = True if self.undoStackBlocked: self.__undoStackUpdateTimer.stop() else: # Schedule an undo stack update. self.__undoStackUpdateTimer.start(0) def __updateUndoStack(self): """Push an entry onto the undo stack. """ self.__undoStack.appendSourceChange(self.getSource()) def __updateSource(self): # Generate XML try: xmlBytes = FupFactory(fupWidget=self).compose() except FupFactory.Error as e: raise AwlSimError("Failed to create FUP source: " "%s" % str(e)) if FUP_DEBUG: print("Composed FUP XML:") print(xmlBytes.decode(FupFactory.XML_ENCODING)) self.__source.sourceBytes = xmlBytes self.__needSourceUpdate = False def getSource(self): if self.__needSourceUpdate: try: self.__updateSource() except AwlSimError as e: printWarning(str(e)) return self.__source def setSource(self, source, initUndoStack=True): self.__source = source.dup() self.__source.sourceBytes = b"" # Parse XML if FUP_DEBUG: print("Parsing FUP XML:") print(source.sourceBytes.decode(FupFactory.XML_ENCODING)) try: factory = FupFactory(fupWidget=self) factory.parse(source.sourceBytes) except FupFactory.Error as e: raise AwlSimError("Failed to parse FUP source: " "%s" % str(e)) self.__needSourceUpdate = True if initUndoStack: self.__undoStack.initializeStack(self.getSource()) def __configureOptimizer(self): grid = self.edit.draw.grid dlg = OptimizerConfigDialog( settingsContainer=grid.optSettingsCont.dup(), parent=self) if dlg.exec_() == QDialog.Accepted: grid.optSettingsCont = dlg.getSettingsContainer() self.diagramChanged.emit() def __compileAndShow(self, mnemonics, showCall, optimize=False): fupSource = self.getSource() try: symTabSources = self.__getSymTabSourcesFunc() optSettCont = None if not optimize: optSettCont = AwlOptimizerSettingsContainer(globalEnable=False) compiler = FupCompiler() blockAwlSource = compiler.compile(fupSource=fupSource, symTabSources=symTabSources, mnemonics=mnemonics, optimizerSettingsContainer=optSettCont) if showCall: callAwlSource = compiler.generateCallTemplate() except AwlSimError as e: MessageBox.handleAwlSimError(self, "FUP compiler error", e) return dlg = EditDialog(self, readOnly=True, withHeader=False, withCpuStats=False) if showCall: title = "FUP/FBD CALL template - %s" awlSource = callAwlSource else: title = "Compiled FUP/FBD diagram - %s" awlSource = blockAwlSource dlg.setWindowTitle(title % self.__source.name) dlg.edit.setSource(awlSource) dlg.show() def __compileAndShowAwlOpt(self): self.__compileAndShow(S7CPUConfig.MNEMONICS_DE, showCall=False, optimize=True) def __compileAndShowAwl(self): self.__compileAndShow(S7CPUConfig.MNEMONICS_DE, showCall=False, optimize=False) def __compileAndShowStlOpt(self): self.__compileAndShow(S7CPUConfig.MNEMONICS_EN, showCall=False, optimize=True) def __compileAndShowStl(self): self.__compileAndShow(S7CPUConfig.MNEMONICS_EN, showCall=False, optimize=False) def __generateCallTemplate(self): self.__compileAndShow(S7CPUConfig.MNEMONICS_AUTO, showCall=True) def undoIsAvailable(self): return self.__undoStack.canUndo() def undo(self): self.__undoStack.undo() return True def redoIsAvailable(self): return self.__undoStack.canRedo() def redo(self): self.__undoStack.redo() return True def clipboardCopyIsAvailable(self): grid = self.edit.draw.grid return bool(grid) and bool(grid.selectedElems) def clipboardCopy(self): return self.edit.draw.clipboardCopy() def clipboardCutIsAvailable(self): return self.clipboardCopyIsAvailable() def clipboardCut(self): return self.edit.draw.clipboardCut() def clipboardPasteIsAvailable(self): grid = self.edit.draw.grid return bool(grid) and bool(grid.selectedCells) def clipboardPaste(self, text=None): if text: return False else: return self.edit.draw.clipboardPaste() def __handleSelectionChange(self, cellsAreSelected, elemsAreSelected, grid): self.clipboardCopyAvailableChanged.emit(bool(elemsAreSelected)) self.clipboardCutAvailableChanged.emit(bool(elemsAreSelected)) self.clipboardPasteAvailableChanged.emit(bool(cellsAreSelected))