# -*- coding: utf-8 -*- # # AWL simulator - GUI main window # # Copyright 2012-2014 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 * import sys import os from awlsim.gui.util import * from awlsim.gui.editwidget import * from awlsim.gui.projectwidget import * from awlsim.gui.cpuconfig import * from awlsim.gui.coreconfig import * from awlsim.gui.icons import * class MainWidget(QWidget): dirtyChanged = Signal(bool) runStateChanged = Signal(int) def __init__(self, parent=None): QWidget.__init__(self, parent) self.setLayout(QGridLayout(self)) self.simClient = GuiAwlSimClient() self.coreConfigDialog = CoreConfigDialog(self, self.simClient) self.splitter = QSplitter(Qt.Horizontal) self.layout().addWidget(self.splitter, 0, 0) self.projectWidget = ProjectWidget(self) self.splitter.addWidget(self.projectWidget) self.cpuWidget = CpuWidget(self, self) self.splitter.addWidget(self.cpuWidget) self.filename = None self.dirty = False self.projectWidget.codeChanged.connect(self.__somethingChanged) self.projectWidget.symTabChanged.connect(self.__somethingChanged) self.projectWidget.visibleLinesChanged.connect(self.cpuWidget.updateVisibleLineRange) self.cpuWidget.runStateChanged.connect(self.__runStateChanged) self.cpuWidget.onlineDiagChanged.connect(self.projectWidget.handleOnlineDiagChange) self.cpuWidget.haveInsnDump.connect(self.projectWidget.handleInsnDump) self.runStateChanged.connect(self.projectWidget.updateRunState) def isDirty(self): return self.dirty def setDirty(self, dirty, force=False): if dirty != self.dirty or force: self.dirty = dirty self.dirtyChanged.emit(self.dirty) def getFilename(self): return self.filename def __runStateChanged(self, newState): self.runStateChanged.emit(newState) def getSimClient(self): return self.simClient def getCpuWidget(self): return self.cpuWidget def getProject(self): return self.projectWidget.getProject() def __somethingChanged(self): self.cpuWidget.stop() self.setDirty(True) def newFile(self, filename=None): if isWinStandalone: executableName = "awlsim-gui.exe" else: executableName = sys.executable executable = findExecutable(executableName) if not executable: QMessageBox.critical(self, "Failed to find '%s'" % executableName, "Could not spawn a new instance.\n" "Failed to find '%s'" % executableName) return if isWinStandalone: argv = [ executable, ] else: argv = [ executable, "-m", "awlsim.gui.mainwindow", ] if filename: argv.append(filename) PopenWrapper(argv, env = os.environ) def loadFile(self, filename): if self.dirty: res = QMessageBox.question(self, "Unsaved project", "The current project is modified and contains unsaved changes.\n " "Do you want to:\n" "- Save the project, close it and open the new project\n" "- Open the new project in a new instance or\n" "- Discard the changes and open the new project\n" "- Cancel the operation", QMessageBox.Save | QMessageBox.Discard |\ QMessageBox.Open | QMessageBox.Cancel, QMessageBox.Open) if res == QMessageBox.Save: if not self.save(): return elif res == QMessageBox.Discard: pass elif res == QMessageBox.Open: self.newFile(filename) return elif res == QMessageBox.Cancel: return else: assert(0) try: res = self.projectWidget.loadProjectFile(filename) if not res: return False except AwlSimError as e: QMessageBox.critical(self, "Failed to load project file", str(e)) return False self.filename = filename self.setDirty(dirty = not bool(self.getProject().getProjectFile()), force = True) return True def load(self): fn, fil = QFileDialog.getOpenFileName(self, "Open project", "", "Awlsim project or AWL/STL source (*.awlpro *.awl);;" "Awlsim project (*.awlpro);;" "AWL source (*.awl);;" "All files (*)") if not fn: return self.loadFile(fn) def saveFile(self, filename): try: res = self.projectWidget.saveProjectFile(filename) if res == 0: # Failure return False elif res < 0: # Force save-as return self.save(newFile=True) except AwlSimError as e: QMessageBox.critical(self, "Failed to write project file", str(e)) return False self.filename = filename self.setDirty(dirty = False, force = True) return True def save(self, newFile=False): if newFile or not self.filename: fn, fil = QFileDialog.getSaveFileName(self, "Awlsim project save as", "", "Awlsim project (*.awlpro)", "*.awlpro") if not fn: return if not fn.endswith(".awlpro"): fn += ".awlpro" return self.saveFile(fn) else: return self.saveFile(self.filename) def cpuConfig(self): project = self.getProject() dlg = CpuConfigDialog(self, self.simClient) dlg.loadFromProject(project) if dlg.exec_() == dlg.Accepted: dlg.saveToProject(project) self.__somethingChanged() def coreConfig(self): self.coreConfigDialog.exec_() def insertOB(self): self.projectWidget.insertOB() def insertFC(self): self.projectWidget.insertFC() def insertFB(self): self.projectWidget.insertFB() def insertInstanceDB(self): self.projectWidget.insertInstanceDB() def insertGlobalDB(self): self.projectWidget.insertGlobalDB() def insertUDT(self): self.projectWidget.insertUDT() def insertFCcall(self): self.projectWidget.insertFCcall() def insertFBcall(self): self.projectWidget.insertFBcall() class MainWindow(QMainWindow): @classmethod def start(cls, qApplication = None, initialAwlSource = None): if not qApplication: qApplication = QApplication(sys.argv) mainwnd = cls(initialAwlSource) mainwnd.show() return mainwnd def __init__(self, awlSource=None, parent=None): QMainWindow.__init__(self, parent) self.setWindowIcon(getIcon("cpu")) self.setCentralWidget(MainWidget(self)) self.setMenuBar(QMenuBar(self)) menu = QMenu("&File", self) menu.addAction(getIcon("new"), "&New project", self.new) menu.addAction(getIcon("open"), "&Open project...", self.load) self.saveAct = menu.addAction(getIcon("save"), "&Save project", self.save) menu.addAction(getIcon("save"), "&Save project as...", self.saveAs) menu.addSeparator() menu.addAction("&Exit...", self.close) self.menuBar().addMenu(menu) menu = QMenu("&Templates", self) menu.addAction("Insert &OB...", self.insertOB) menu.addAction("Insert F&C...", self.insertFC) menu.addAction("Insert F&B...", self.insertFB) menu.addAction("Insert &instance-DB...", self.insertInstanceDB) menu.addAction("Insert &DB...", self.insertGlobalDB) menu.addAction("Insert &UDT...", self.insertUDT) menu.addSeparator() menu.addAction("Insert FC C&ALL...", self.insertFCcall) menu.addAction("Insert FB CA&LL...", self.insertFBcall) self.menuBar().addMenu(menu) menu = QMenu("&PLC", self) menu.addAction("&Server connection...", self.coreConfig) menu.addAction("&CPU config...", self.cpuConfig) self.menuBar().addMenu(menu) menu = QMenu("Help", self) menu.addAction(getIcon("cpu"), "&About...", self.about) self.menuBar().addMenu(menu) self.tb = QToolBar(self) self.tb.addAction(getIcon("new"), "New project", self.new) self.tb.addAction(getIcon("open"), "Open project", self.load) self.tbSaveAct = self.tb.addAction(getIcon("save"), "Save project", self.save) self.addToolBar(self.tb) self.__dirtyChanged(False) self.centralWidget().dirtyChanged.connect(self.__dirtyChanged) self.centralWidget().runStateChanged.connect(self.__runStateChanged) if awlSource: self.centralWidget().loadFile(awlSource) def insertOB(self): self.centralWidget().insertOB() def insertFC(self): self.centralWidget().insertFC() def insertFB(self): self.centralWidget().insertFB() def insertInstanceDB(self): self.centralWidget().insertInstanceDB() def insertGlobalDB(self): self.centralWidget().insertGlobalDB() def insertUDT(self): self.centralWidget().insertUDT() def insertFCcall(self): self.centralWidget().insertFCcall() def insertFBcall(self): self.centralWidget().insertFBcall() def runEventLoop(self): return QApplication.exec_() def getSimClient(self): return self.centralWidget().getSimClient() def cpuRun(self): self.centralWidget().getCpuWidget().run() def cpuStop(self): self.centralWidget().getCpuWidget().stop() 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) filename = self.centralWidget().getFilename() if filename: postfix = " -- " + os.path.basename(filename) if isDirty: postfix += "*" else: postfix = "" self.setWindowTitle("AWL/STL Soft-PLC v%d.%d%s" %\ (VERSION_MAJOR, VERSION_MINOR, postfix)) 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/STL code", "The editor contains unsaved AWL/STL code.\n" "AWL/STL 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.about(self, "About AWL/STL Soft-PLC", "awlsim version %d.%d\n\n" "Copyright 2012-2014 Michael Büsch \n" "Licensed under the terms of the " "GNU GPL version 2 or (at your option) " "any later version." %\ (VERSION_MAJOR, VERSION_MINOR)) def new(self): self.centralWidget().newFile() 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 coreConfig(self): self.centralWidget().coreConfig() # If invoked as script, run a new instance. if __name__ == "__main__": fn = sys.argv[1] if (len(sys.argv) >= 2) else None sys.exit(MainWindow.start(initialAwlSource = fn).runEventLoop())