# -*- coding: utf-8 -*- # # AWL simulator - Block type edit 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.common.xmlfactory import * from awlsim.gui.util import * import re class BlockTypeWidget_factory(XmlFactory): def parser_open(self, tag=None): self.inInstanceDBs = False self.inDB = False self.blockTypeWidget.clear(noChangeSignals=True) if tag: self.blockTypeWidget.setBlockTypeString( blockTypeString=tag.getAttr("type", "FC").upper().strip(), noChangeSignals=True) self.blockTypeWidget.setBlockName( blockName=tag.getAttr("name", "FC 1"), noChangeSignals=True) XmlFactory.parser_open(self, tag) def parser_close(self): XmlFactory.parser_close(self) def parser_beginTag(self, tag): if self.inInstanceDBs: if tag.name == "db": self.blockTypeWidget.addInstanceDB(tag.getAttr("name", "")) self.inDB = True return else: if tag.name == "instance_dbs": self.inInstanceDBs = True return XmlFactory.parser_beginTag(self, tag) def parser_endTag(self, tag): if self.inInstanceDBs: if self.inDB: if tag.name == "db": self.inDB = False return else: if tag.name == "instance_dbs": self.inInstanceDBs = False return else: if tag.name == "blockdecl": self.parser_finish() return XmlFactory.parser_endTag(self, tag) def composer_getTags(self): blockType, blockName, instanceDBs = self.blockTypeWidget.get() instanceDbTags = [ self.Tag(name="instance_dbs", tags=[ self.Tag(name="db", attrs={ "name" : str(dbName) }) for dbName in instanceDBs ]) ] return [ self.Tag(name="blockdecl", attrs={ "type" : str(blockType).upper().strip(), "name" : str(blockName), }, tags=instanceDbTags), ] class BlockValidator(QValidator): """Validator for a block string. """ __reBase = r'\s*(BLOCKTYPE)\s*(\d+)(.*)' def __init__(self, lineEdit=None, blockTypes=()): QValidator.__init__(self) self.lineEdit = lineEdit if lineEdit: self.defaultBgColor = lineEdit.palette().color(QPalette.Base) self.defaultFgColor = lineEdit.palette().color(QPalette.Text) self.blockTypes = blockTypes or () def doFixup(self, input): valid, blockStr = True, input regex = self.__reBase.replace(r'BLOCKTYPE', r'|'.join(r'(?:' + t + r')' for t in self.blockTypes)) regex = re.compile(regex, re.IGNORECASE | re.DOTALL) match = regex.match(blockStr) if match: if match.group(3).strip(): valid = False # We have dangling data else: try: blockNr = int(match.group(2)) except (ValueError, TypeError) as e: valid = False # Invalid index if blockNr < 0 or blockNr > 0xFFFF: valid = False # Invalid index blockStr = "%s %s" % (match.group(1).upper(), match.group(2)) else: blockStr = blockStr.strip() if blockStr: if len(blockStr) >= 3 and\ blockStr.startswith('"') and\ blockStr.endswith('"') and\ blockStr[1:-1].find('"') < 0: pass # The name is symbolic else: if not blockStr.startswith('"'): blockStr = blockStr.upper() valid = False # We have some crap return valid, blockStr def fixup(self, input): valid, fixedInput = self.doFixup(input) if self.lineEdit: self.lineEdit.setText(fixedInput) return fixedInput def validate(self, input, pos): valid, fixedInput = self.doFixup(input) if self.lineEdit: palette = self.lineEdit.palette() if valid: palette.setColor(QPalette.Base, self.defaultBgColor) palette.setColor(QPalette.Text, self.defaultFgColor) else: palette.setColor(QPalette.Base, getErrorColor()) palette.setColor(QPalette.Text, QColor("black")) self.lineEdit.setPalette(palette) state = self.Acceptable if input != fixedInput: state = self.Intermediate return state, input, pos class BlockListValidator(BlockValidator): """Validator for comma separated block list strings. """ def doFixup(self, input): valid, retList = True, [] for blockStr in input.split(","): v, fix = BlockValidator.doFixup(self, blockStr) if not v: valid = False if fix.strip(): retList.append(fix) return valid, ", ".join(retList) class BlockTypeWidget(QWidget): """AWL block type edit widget. """ factory = BlockTypeWidget_factory # Signal: Emitted, if the block type changed typeChanged = Signal() # Signal: Emitted, if the block name changed nameChanged = Signal() # Signal: Emitted, if the instance DBs changed dbChanged = Signal() def __init__(self, parent=None): QWidget.__init__(self, parent) self.setLayout(QGridLayout()) self.layout().setContentsMargins(QMargins()) self.__changeSignalsBlocked = Blocker() self.typeCombo = QComboBox(self) self.typeCombo.addItem("Block type: Function (FC)", "FC") self.typeCombo.addItem("Block type: Function block (FB)", "FB") self.typeCombo.addItem("Block type: Organization block (OB)", "OB") self.layout().addWidget(self.typeCombo, 0, 0) self.__prevTypeStr = "FC" self.blockNameEdit = QLineEdit(self) self.blockNameEdit.setToolTip( "Enter the block name here.\n\n" "This can be an absolute block name like\n" " FB 42 or FC 42 or OB 100\n" "or a symbolic block name like\n" " \"My function block\"\n" "(The symbolic name must be present in the symbol table.)") self.layout().addWidget(self.blockNameEdit, 0, 1) self.dbEditLabel = QLabel("DIs:", self) self.layout().addWidget(self.dbEditLabel, 0, 2) self.dbEdit = QLineEdit(self) self.dbEdit.setToolTip( "Enter the instance DBs to create here.\n\n" "If this field is left empty, no DB will be generated.\n" "One DB or a comma separated list of DBs to create can be specified.\n\n" "For example:\n" " DB 42\n" "or\n" " DB 42, DB 43, DB 44\n" "or a symbolic block name like\n" " \"First data block\", \"Second data block\"\n" "(The symbolic name must be present in the symbol table.)") self.dbEditLabel.setToolTip(self.dbEdit.toolTip()) self.dbEdit.setValidator(BlockListValidator(self.dbEdit, ("DB", "DI"))) self.layout().addWidget(self.dbEdit, 0, 3) self.layout().setColumnStretch(99, 1) self.clear(noChangeSignals=True) self.__handleTypeChange(self.typeCombo.currentIndex()) self.typeCombo.currentIndexChanged.connect(self.__handleTypeChange) self.blockNameEdit.textChanged.connect(self.__handleNameChange) self.dbEdit.textChanged.connect(self.__handleDBChange) def __handleTypeChange(self, comboIndex): typeStr = self.typeCombo.itemData(comboIndex) self.blockNameEdit.setValidator(BlockValidator(self.blockNameEdit, (typeStr,))) if typeStr == "FB": self.dbEditLabel.show() self.dbEdit.show() else: self.dbEditLabel.hide() self.dbEdit.hide() if not self.__changeSignalsBlocked: self.typeChanged.emit() self.__prevTypeStr = typeStr def revertTypeChange(self): """Revert type change. Can only be called from typeChanged signal. """ index = self.typeCombo.findData(self.__prevTypeStr, Qt.UserRole, Qt.MatchFixedString) if index >= 0: self.typeCombo.setCurrentIndex(index) def __handleNameChange(self, text): if not self.__changeSignalsBlocked: self.nameChanged.emit() def __handleDBChange(self, text): if not self.__changeSignalsBlocked: self.dbChanged.emit() def get(self): """Get (blockTypeString, blockName, instanceDBs) """ typeStr = self.typeCombo.itemData(self.typeCombo.currentIndex()) nameStr = self.blockNameEdit.text() instanceDBs = [] if typeStr == "FB": for db in self.dbEdit.text().split(","): instanceDBs.append(db.strip()) return (typeStr, nameStr, instanceDBs) def clear(self, noChangeSignals=False): """Clear content. """ with self.__changeSignalsBlocked if noChangeSignals else contextlib.nullcontext(): self.typeCombo.setCurrentIndex(0) self.blockNameEdit.setText("FC 1") self.dbEdit.clear() def setBlockTypeString(self, blockTypeString, noChangeSignals=False): """Set the block type. """ with self.__changeSignalsBlocked if noChangeSignals else contextlib.nullcontext(): index = self.typeCombo.findData(blockTypeString, Qt.UserRole, Qt.MatchFixedString) if index >= 0: self.typeCombo.setCurrentIndex(index) return True return False def setBlockName(self, blockName, noChangeSignals=False): """Set the block name. """ with self.__changeSignalsBlocked if noChangeSignals else contextlib.nullcontext(): self.blockNameEdit.setText(blockName) def addInstanceDB(self, dbName, noChangeSignals=False): """Add an instance DB. """ if not dbName.strip(): return False with self.__changeSignalsBlocked if noChangeSignals else contextlib.nullcontext(): typeStr = self.typeCombo.itemData(self.typeCombo.currentIndex()) if typeStr == "FB": if self.dbEdit.text().strip(): self.dbEdit.setText(self.dbEdit.text() + ", " + dbName) else: self.dbEdit.setText(dbName) return True return False