# -*- coding: utf-8 -*- # # AWL simulator - GUI project widget # # Copyright 2014-2016 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.compat import * from awlsim.common.templates import * from awlsim.common.codevalidator import * from awlsim.gui.util import * from awlsim.gui.sourcetabs import * from awlsim.gui.library import * from awlsim.gui.libtablewidget import * from awlsim.gui.icons import * class TemplateDialog(QDialog): def __init__(self, blockName, verboseBlockName=None, extra=None, parent=None): QDialog.__init__(self, parent) self.setLayout(QGridLayout()) if not verboseBlockName: verboseBlockName = blockName self.setWindowTitle("Awlsim - Insert %s template" %\ verboseBlockName) hbox = QHBoxLayout() label = QLabel(self) label.setPixmap(getIcon("textsource").pixmap(QSize(48, 48))) hbox.addWidget(label) label = QLabel("Insert %s template" % verboseBlockName, self) font = label.font() font.setPointSize(max(12, font.pointSize())) label.setFont(font) label.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) hbox.addWidget(label) hbox.addStretch() self.layout().addLayout(hbox, 0, 0, 1, 2) label = QLabel("%s number:" % verboseBlockName, self) self.layout().addWidget(label, 1, 0) self.blockNr = QSpinBox(self) self.blockNr.setMinimum(1) self.blockNr.setMaximum(0xFFFF) self.blockNr.setValue(1) self.blockNr.setPrefix(blockName + " ") self.layout().addWidget(self.blockNr, 1, 1) if extra: label = QLabel("%s number:" % extra, self) self.layout().addWidget(label, 2, 0) self.extraNr = QSpinBox(self) self.extraNr.setMinimum(1) self.extraNr.setMaximum(0xFFFF) self.extraNr.setValue(1) self.extraNr.setPrefix(extra + " ") self.layout().addWidget(self.extraNr, 2, 1) self.verbose = QCheckBox("Generate &verbose code", self) self.verbose.setCheckState(Qt.Checked) self.layout().addWidget(self.verbose, 3, 0, 1, 2) self.layout().setRowStretch(4, 1) self.okButton = QPushButton("&Paste code", self) self.layout().addWidget(self.okButton, 5, 0, 1, 2) self.okButton.released.connect(self.accept) def getBlockNumber(self): return self.blockNr.value() def getExtraNumber(self): return self.extraNr.value() def getVerbose(self): return self.verbose.checkState() == Qt.Checked class ProjectWidget(QTabWidget): # Signal: Some source changed codeChanged = Signal() # Signal: Some FUP diagram changed fupChanged = Signal() # Signal: Some KOP diagram changed kopChanged = Signal() # Signal: Some symbol table changed symTabChanged = Signal() # Signal: The visible AWL line range changed # Parameters are: source, visibleFromLine, visibleToLine visibleLinesChanged = Signal(AwlSource, int, int) # Signal: The library selection changed libTableChanged = Signal() # Signal: Source text focus changed textFocusChanged = Signal(bool) # Signal: The selected project resource changed # Parameter is one of RES_... selResourceChanged = Signal(int) # Signal: UndoAvailable state changed undoAvailableChanged = Signal(bool) # Signal: RedoAvailable state changed redoAvailableChanged = Signal(bool) # Signal: CopyAvailable state changed copyAvailableChanged = Signal(bool) # Project resource identifier EnumGen.start RES_FUP = EnumGen.item # FBD/FUP diagrams RES_KOP = EnumGen.item # LAD/KOP diagrams RES_SOURCES = EnumGen.item # Sources RES_SYMTABS = EnumGen.item # Symbol tables RES_LIBSELS = EnumGen.item # Library selections EnumGen.end def __init__(self, parent=None): QTabWidget.__init__(self, parent) self.__suppressValidation = False self.fupTabs = FupTabWidget(self) self.kopTabs = None #TODO self.awlTabs = AwlSourceTabWidget(self) self.symTabs = SymSourceTabWidget(self) self.libTable = LibTableView(None, self) i = 0 if self.fupTabs: self.addTab(self.fupTabs, "FUP / FBD") self.setTabToolTip(i, "Create Function Block Diagrams here") self.setTabIcon(i, getIcon("fup")) i += 1 if self.kopTabs: self.addTab(self.kopTabs, "KOP / LAD") self.setTabToolTip(i, "Create Ladder logic here") self.setTabIcon(i, getIcon("kop")) i += 1 if self.awlTabs: self.addTab(self.awlTabs, "AWL / STL") self.setTabToolTip(i, "Enter your AWL/STL program here") self.setTabIcon(i, getIcon("textsource")) i += 1 if self.symTabs: self.addTab(self.symTabs, "Symbol tables") self.setTabToolTip(i, "Enter your symbol table here") self.setTabIcon(i, getIcon("tag")) i += 1 if self.libTable: self.addTab(self.libTable, "Library selections") self.setTabToolTip(i, "Select standard libraries to include") self.setTabIcon(i, getIcon("stdlib")) i += 1 self.reset() self.currentChanged.connect(self.__handleTabChange) if self.fupTabs: self.fupTabs.sourceChanged.connect(self.fupChanged) if self.kopTabs: self.kopTabs.sourceChanged.connect(self.kopChanged) if self.awlTabs: self.awlTabs.sourceChanged.connect(self.codeChanged) self.awlTabs.visibleLinesChanged.connect(self.visibleLinesChanged) self.awlTabs.focusChanged.connect(self.textFocusChanged) self.awlTabs.undoAvailableChanged.connect(self.undoAvailableChanged) self.awlTabs.redoAvailableChanged.connect(self.redoAvailableChanged) self.awlTabs.copyAvailableChanged.connect(self.copyAvailableChanged) self.awlTabs.resizeFont.connect(self.__doSourceCodeFontResize) self.awlTabs.validateDocument.connect(self.__doDocumentValidation) if self.symTabs: self.symTabs.sourceChanged.connect(self.symTabChanged) if self.libTable: self.libTable.model().contentChanged.connect(self.libTableChanged) # Send an initial tab-change notification signal. QTimer.singleShot(0, lambda: self.__handleTabChange(self.currentIndex())) # Run a background source validation. def __doDocumentValidation(self, editWidget): if not editWidget or self.__suppressValidation: return validator = AwlValidator.get() if validator: validator.validate(project=self.__project, symTabSources=self.getSymTabSources(), libSelections=self.getLibSelections(), awlSources=self.getAwlSources(), fupSources=self.getFupSources(), kopSources=self.getKopSources()) QTimer.singleShot(100, self.__checkValidationResult) # Poll the validation result def __checkValidationResult(self): validator = AwlValidator.get() if validator: running, exception = validator.getState() self.__handleValidationResult(exception) if running: QTimer.singleShot(100, self.__checkValidationResult) # Handle a validator exception def __handleValidationResult(self, exception): if self.__suppressValidation: return self.awlTabs.handleValidationResult(exception) self.symTabs.handleValidationResult(exception) self.libTable.handleValidationResult(exception) # Resize project editor font. def __doSourceCodeFontResize(self, bigger): font = getDefaultFixedFont() fontStr = self.__project.getGuiSettings().getEditorFont() if fontStr: font.fromString(fontStr) font.setStyleHint(QFont.Courier) if bigger: font.setPointSize(font.pointSize() + 1) if font.pointSize() > 72: return else: font.setPointSize(font.pointSize() - 1) if font.pointSize() < 6: return self.__project.getGuiSettings().setEditorFont(font.toString()) self.setSettings(self.__project.getGuiSettings()) self.codeChanged.emit() self.symTabChanged.emit() def __setSelectedResource(self, res): self.__selectedResource = res self.selResourceChanged.emit(res) def getSelectedResource(self): return self.__selectedResource def __handleTabChange(self, newTabIndex): widget = self.widget(newTabIndex) if not widget: return if widget is self.fupTabs: self.__setSelectedResource(self.RES_FUP) self.undoAvailableChanged.emit(False) self.redoAvailableChanged.emit(False) self.copyAvailableChanged.emit(False) elif widget is self.kopTabs: self.__setSelectedResource(self.RES_KOP) self.undoAvailableChanged.emit(False) self.redoAvailableChanged.emit(False) self.copyAvailableChanged.emit(False) elif widget is self.awlTabs: self.__setSelectedResource(self.RES_SOURCES) self.undoAvailableChanged.emit(self.awlTabs.undoIsAvailable()) self.redoAvailableChanged.emit(self.awlTabs.redoIsAvailable()) self.copyAvailableChanged.emit(self.awlTabs.copyIsAvailable()) self.__doDocumentValidation(widget.currentWidget()) elif widget is self.symTabs: self.__setSelectedResource(self.RES_SYMTABS) self.undoAvailableChanged.emit(self.symTabs.undoIsAvailable()) self.redoAvailableChanged.emit(self.symTabs.redoIsAvailable()) self.copyAvailableChanged.emit(self.symTabs.copyIsAvailable()) elif widget is self.libTable: self.__setSelectedResource(self.RES_LIBSELS) self.undoAvailableChanged.emit(False) self.redoAvailableChanged.emit(False) self.copyAvailableChanged.emit(False) else: assert(0) def handleOnlineDiagChange(self, enabled): self.awlTabs.handleOnlineDiagChange(enabled) def handleInsnDump(self, insnDumpMsg): self.awlTabs.handleInsnDump(insnDumpMsg) def handleIdentsMsg(self, identsMsg): self.awlTabs.handleIdentsMsg(identsMsg) self.symTabs.handleIdentsMsg(identsMsg) def updateRunState(self, runState): self.awlTabs.updateRunState(runState) self.symTabs.updateRunState(runState) def getProject(self): """Returns the project description object (class Project). Do _not_ use awlSources and symTabs from this project!""" return self.__project def getAwlSources(self): "Returns a list of AwlSource()s" if not self.awlTabs: return [] return self.awlTabs.getSources() def getCurrentAwlSource(self): "Returns the currently selected AwlSource()." if not self.awlTabs: return [] return self.awlTabs.getCurrentSource() def getFupSources(self): "Returns a list of FupSource()s" if not self.fupTabs: return [] return self.fupTabs.getSources() def getCurrentFupSource(self): "Returns the currently selected FupSource()." if not self.fupTabs: return [] return self.fupTabs.getCurrentSource() def getKopSources(self): "Returns a list of KopSource()s" if not self.kopTabs: return [] return self.kopTabs.getSources() def getCurrentKopSource(self): "Returns the currently selected KopSource()." if not self.kopTabs: return [] return self.kopTabs.getCurrentSource() def getSymTabSources(self): "Returns a list of SymTabSource()s" return self.symTabs.getSources() def getCurrentSymTabSource(self): "Returns the currently selected SymTabSource()." return self.symTabs.getCurrentSource() def getLibSelections(self): "Returns a list of AwlLibEntrySelection()s" return self.libTable.model().getLibSelections() def reset(self): self.__project = Project(None) # Empty project self.__isAdHocProject = False self.__warnedFileBacked = False self.__selectedResource = self.RES_SOURCES if self.fupTabs: self.fupTabs.reset() if self.kopTabs: self.kopTabs.reset() if self.awlTabs: self.awlTabs.reset() if self.symTabs: self.symTabs.reset() self.setCurrentIndex(self.indexOf(self.awlTabs)) def setSettings(self, guiSettings): self.awlTabs.setSettings(guiSettings) self.symTabs.setSettings(guiSettings) def __loadProject(self, project): self.__suppressValidation = True try: self.__project = project self.setSettings(project.getGuiSettings()) if self.awlTabs: self.awlTabs.setSources(self.__project.getAwlSources()) if self.fupTabs: self.fupTabs.setSources(self.__project.getFupSources()) if self.kopTabs: self.kopTabs.setSources(self.__project.getKopSources()) if self.symTabs: self.symTabs.setSources(self.__project.getSymTabSources()) self.libTable.model().setLibSelections(self.__project.getLibSelections()) self.__warnedFileBacked = False self.__isAdHocProject = False finally: self.__suppressValidation = False def __loadPlainAwlSource(self, filename): project = Project(None) # Create an ad-hoc project source = AwlSource.fromFile(name=filename, filepath=filename, compatReEncode=True) project.setAwlSources([ source, ]) self.__loadProject(project) self.__isAdHocProject = True QMessageBox.information(self, "Opened plain AWL/STL file", "The plain AWL/STL source file \n'%s'\n has sucessfully " "been opened.\n\n" "If you click on 'save', you will be asked to create " "a project file for this source." % filename) def loadProjectFile(self, filename): if Project.fileIsProject(filename): self.__loadProject(Project.fromFile(filename)) else: # This might be a plain AWL-file. # Try to load it. self.__loadPlainAwlSource(filename) def saveProjectFile(self, filename): if self.__isAdHocProject: srcs = self.__project.getAwlSources() assert(len(srcs) == 1) if filename == srcs[0].filepath: # This is an ad-hoc project, that was created from # a plain AWL file. Do not overwrite the AWL file. # Ask the user to create an .awlpro file. res = QMessageBox.question(self, "Create Awlsim project file?", "The current project was created ad-hoc from a " "plain AWL/STL file.\n" "Can not save without creating a project file.\n\n" "Do you want to create a project file?", QMessageBox.Yes, QMessageBox.No) if res != QMessageBox.Yes: return 0 # The user has to choose a new project file name. # Signal this to our caller. return -1 awlSrcs = self.getAwlSources() fupSrcs = self.getFupSources() kopSrcs = self.getKopSources() symTabSrcs = self.getSymTabSources() libSelections = self.getLibSelections() if not all(awlSrcs) or not all(symTabSrcs): # Failed to generate some sources return 0 if (any(src.isFileBacked() for src in awlSrcs) or\ any(src.isFileBacked() for src in symTabSrcs)) and\ not self.__warnedFileBacked: QMessageBox.information(self, "Project contains external sources", "The project contains external sources.\n" "It is strongly recommended to integrate " "external sources into the project.\n" "Click on 'integrate source into project' " "in the source menu.") self.__warnedFileBacked = True self.__project.setAwlSources(awlSrcs) self.__project.setFupSources(fupSrcs) self.__project.setKopSources(kopSrcs) self.__project.setSymTabSources(symTabSrcs) self.__project.setLibSelections(libSelections) self.__project.setProjectFile(filename) self.__project.toFile() if self.__isAdHocProject: # We got converted to a real project. Update the tabs. self.awlTabs.setSources(self.__project.getAwlSources()) self.symTabs.setSources(self.__project.getSymTabSources()) self.__isAdHocProject = False return 1 def __pasteAwlText(self, text): if self.currentWidget() == self.awlTabs: self.awlTabs.pasteText(text) return True QMessageBox.information(self, "Please select AWL/STL source", "Can not paste template.\n\n" "Please move the text cursor to the place " "in the AWL/STL code where you want to paste " "the template to.") return False def __addSymbolToTabWidget(self, tabWidget, symbol): tabWidget.model().beginResetModel() tabWidget.getSymTab().add(symbol) tabWidget.model().revert() tabWidget.model().endResetModel() def __pasteSymbol(self, symbolName, address, dataType, comment): # Check if we already have this symbol. for tabWidget in self.symTabs.allTabWidgets(): symTable = tabWidget.getSymTab() if symbolName in symTable: # We already have it. return True # We don't have this symbol, yet. Parse it. try: p = SymTabParser(self.__project.getCpuConf().getConfiguredMnemonics()) symbol = p.parseSym(symbolName, address, dataType, comment, 0) except AwlSimError as e: MessageBox.handleAwlSimError(self, "Library symbol error", e) return False assert(self.symTabs.count() >= 1) if self.symTabs.count() == 1: # We only have one table. Add the symbol. self.__addSymbolToTabWidget(self.symTabs.widget(0), symbol) else: # Ask which table to add the symbol to. tabWidgets = tuple(self.symTabs.allTabWidgets()) entries = [] for i, tabWidget in enumerate(tabWidgets): entries.append("%d: %s" %\ (i + 1, tabWidget.getSource().name)) entry, ok = QInputDialog.getItem(self, "Select symbol table", "Please select the symbol table "\ "where to add the symbol to:" "\n%s \"%s\"" %\ (address, symbolName), entries, 0, False) if not ok or not entry: return False selIndex = int(entry.split(":")[0]) - 1 tabWidget = tabWidgets[selIndex] self.__addSymbolToTabWidget(tabWidget, symbol) return True def __pasteLibSel(self, libSelection): self.libTable.addEntry(libSelection) return True def insertOB(self): dlg = TemplateDialog("OB", parent=self) if dlg.exec_() == QDialog.Accepted: self.__pasteAwlText(Templates.getOB(dlg.getBlockNumber(), dlg.getVerbose())) dlg.deleteLater() def insertFC(self): dlg = TemplateDialog("FC", parent=self) if dlg.exec_() == QDialog.Accepted: self.__pasteAwlText(Templates.getFC(dlg.getBlockNumber(), dlg.getVerbose())) dlg.deleteLater() def insertFB(self): dlg = TemplateDialog("FB", parent=self) if dlg.exec_() == QDialog.Accepted: self.__pasteAwlText(Templates.getFB(dlg.getBlockNumber(), dlg.getVerbose())) dlg.deleteLater() def insertInstanceDB(self): dlg = TemplateDialog("DB", "Instance-DB", extra="FB", parent=self) if dlg.exec_() == QDialog.Accepted: self.__pasteAwlText(Templates.getInstanceDB(dlg.getBlockNumber(), dlg.getExtraNumber(), dlg.getVerbose())) dlg.deleteLater() def insertGlobalDB(self): dlg = TemplateDialog("DB", parent=self) if dlg.exec_() == QDialog.Accepted: self.__pasteAwlText(Templates.getGlobalDB(dlg.getBlockNumber(), dlg.getVerbose())) dlg.deleteLater() def insertUDT(self): dlg = TemplateDialog("UDT", parent=self) if dlg.exec_() == QDialog.Accepted: self.__pasteAwlText(Templates.getUDT(dlg.getBlockNumber(), dlg.getVerbose())) dlg.deleteLater() def insertFCcall(self): dlg = TemplateDialog("FC", "FC call", parent=self) if dlg.exec_() == QDialog.Accepted: self.__pasteAwlText(Templates.getFCcall(dlg.getBlockNumber(), dlg.getVerbose())) dlg.deleteLater() def insertFBcall(self): dlg = TemplateDialog("FB", "FB call", extra="DB", parent=self) if dlg.exec_() == QDialog.Accepted: self.__pasteAwlText(Templates.getFBcall(dlg.getBlockNumber(), dlg.getExtraNumber(), dlg.getVerbose())) dlg.deleteLater() def openLibrary(self): dlg = LibraryDialog(self.__project, self) if dlg.exec_() == QDialog.Accepted: if dlg.pasteText: # Paste the code. if not self.__pasteAwlText(dlg.pasteText): dlg.deleteLater() return if dlg.pasteSymbol: # Add a symbol to a symbol table. symbolName, address, dataType, comment = dlg.pasteSymbol if not self.__pasteSymbol(symbolName, address, dataType, comment): dlg.deleteLater() return if dlg.pasteLibSel: # Add a library selection to the library table. if not self.__pasteLibSel(dlg.pasteLibSel): dlg.deleteLater() return dlg.deleteLater() def undo(self): widget = self.currentWidget() if widget: widget.undo() def redo(self): widget = self.currentWidget() if widget: widget.redo() def clipboardCut(self): widget = self.currentWidget() if widget: widget.clipboardCut() def clipboardCopy(self): widget = self.currentWidget() if widget: widget.clipboardCopy() def clipboardPaste(self): widget = self.currentWidget() if widget: widget.clipboardPaste() def findText(self): widget = self.currentWidget() if widget: widget.findText() def findReplaceText(self): widget = self.currentWidget() if widget: widget.findReplaceText()