library.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. # -*- coding: utf-8 -*-
  2. #
  3. # AWL simulator - GUI standard library window
  4. #
  5. # Copyright 2014-2018 Michael Buesch <m@bues.ch>
  6. #
  7. # This program is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation; either version 2 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License along
  18. # with this program; if not, write to the Free Software Foundation, Inc.,
  19. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  20. #
  21. from __future__ import division, absolute_import, print_function, unicode_literals
  22. #from awlsim.common.cython_support cimport * #@cy
  23. from awlsim.common.compat import *
  24. from awlsim.gui.util import *
  25. from awlsim.gui.icons import *
  26. from awlsim.core.systemblocks.system_sfc import *
  27. from awlsim.core.systemblocks.system_sfb import *
  28. from awlsim.core.systemblocks.tables import SFC_table, SFB_table
  29. from awlsim.library.libentry import *
  30. from awlsim.library.libselection import *
  31. class GenericActionWidget(QWidget):
  32. # Signal: Code paste request.
  33. paste = Signal(str)
  34. # Signal: Add a symbol to the symbol table
  35. # Arguments: symbolName, address, dataType, comment
  36. addSymbol = Signal(str, str, str, str)
  37. # Signal: Add library selection
  38. addLibrary = Signal(AwlLibEntrySelection)
  39. # Signal: Finish the library selection
  40. finish = Signal()
  41. def __init__(self, parent=None):
  42. QWidget.__init__(self, parent)
  43. self.setLayout(QGridLayout())
  44. self.layout().setContentsMargins(QMargins(5, 0, 5, 0))
  45. def _pasteCallGeneric(self, targetName, needDB, interfaceFields):
  46. fields = []
  47. for ftype in (BlockInterfaceField.FTYPE_IN,
  48. BlockInterfaceField.FTYPE_OUT,
  49. BlockInterfaceField.FTYPE_INOUT):
  50. with contextlib.suppress(KeyError):
  51. fields.extend(interfaceFields[ftype])
  52. ret = [ "CALL %s%s%s" %\
  53. (targetName,
  54. ", DB ..." if needDB else "",
  55. " (" if fields else "")
  56. ]
  57. for field in fields:
  58. ret.append("\t%s := ...\t, // %s" %\
  59. (field.name, str(field.dataType)))
  60. if fields:
  61. ret.append(")")
  62. ret.append("")
  63. self.paste.emit("\n".join(ret))
  64. def _blockToInterfaceText(self, blockIdent, blockSym,
  65. verboseText,
  66. interfaceFields):
  67. desc = [ "%s \"%s\"" % (blockIdent, blockSym) ]
  68. if verboseText:
  69. desc.append(verboseText)
  70. desc.append("")
  71. for ftype, fname in ((BlockInterfaceField.FTYPE_IN, "VAR_INPUT"),
  72. (BlockInterfaceField.FTYPE_OUT, "VAR_OUTPUT"),
  73. (BlockInterfaceField.FTYPE_INOUT, "VAR_IN_OUT")):
  74. try:
  75. fields = interfaceFields[ftype]
  76. except KeyError:
  77. continue
  78. if not fields:
  79. continue
  80. desc.append(" " + fname)
  81. for field in fields:
  82. field.fieldType = ftype
  83. desc.append(" %s" % field.varDeclString())
  84. return "\n".join(desc)
  85. def defaultPaste(self):
  86. pass
  87. class SysActionWidget(GenericActionWidget):
  88. def __init__(self, parent=None):
  89. GenericActionWidget.__init__(self, parent)
  90. self.systemBlockCls = None
  91. self.blockPrefix = None
  92. self.desc = QLabel(self)
  93. self.desc.setFont(getDefaultFixedFont())
  94. self.layout().addWidget(self.desc, 0, 0, 1, 2)
  95. self.layout().setRowStretch(1, 1)
  96. label = QLabel("Paste at cursor position:", self)
  97. self.layout().addWidget(label, 2, 0, 1, 2)
  98. self.pasteCallSymButton = QPushButton(self)
  99. self.layout().addWidget(self.pasteCallSymButton, 3, 0)
  100. self.pasteCallButton = QPushButton(self)
  101. self.layout().addWidget(self.pasteCallButton, 3, 1)
  102. self.pasteCallSymButton.released.connect(self.__pasteCallSym)
  103. self.pasteCallButton.released.connect(self.__pasteCall)
  104. def updateData(self, prefix, systemBlockCls):
  105. self.systemBlockCls = systemBlockCls
  106. self.blockPrefix = prefix
  107. blockNumber, symbolName, blockDesc = systemBlockCls.name
  108. desc = self._blockToInterfaceText("%s %d" % (prefix, blockNumber),
  109. symbolName, blockDesc,
  110. systemBlockCls.interfaceFields)
  111. self.desc.setText(desc)
  112. self.pasteCallButton.setText("CALL %s %d" %\
  113. (prefix, blockNumber))
  114. self.pasteCallSymButton.setText("CALL \"%s\"" % symbolName)
  115. def __pasteCall(self):
  116. blockNumber, symbolName, blockDesc = self.systemBlockCls.name
  117. self._pasteCallGeneric("%s %d" % (self.blockPrefix, blockNumber),
  118. self.systemBlockCls._isFB,
  119. self.systemBlockCls.interfaceFields)
  120. self.finish.emit()
  121. def __pasteCallSym(self):
  122. blockNumber, symbolName, blockDesc = self.systemBlockCls.name
  123. self._pasteCallGeneric('"%s"' % symbolName,
  124. self.systemBlockCls._isFB,
  125. self.systemBlockCls.interfaceFields)
  126. self.addSymbol.emit(symbolName,
  127. "%s %s" % (self.blockPrefix, blockNumber),
  128. "%s %s" % (self.blockPrefix, blockNumber),
  129. blockDesc)
  130. self.finish.emit()
  131. def defaultPaste(self):
  132. self.__pasteCallSym()
  133. class LibActionWidget(GenericActionWidget):
  134. def __init__(self, parent=None):
  135. GenericActionWidget.__init__(self, parent)
  136. self.libEntryCls = None
  137. self.desc = QLabel(self)
  138. self.desc.setFont(getDefaultFixedFont())
  139. self.layout().addWidget(self.desc, 0, 0, 1, 2)
  140. self.layout().setRowStretch(1, 1)
  141. label = QLabel("Paste at cursor position:", self)
  142. self.layout().addWidget(label, 2, 0, 1, 2)
  143. self.pasteCallSymButton = QPushButton(self)
  144. self.layout().addWidget(self.pasteCallSymButton, 3, 0)
  145. self.pasteCallButton = QPushButton(self)
  146. self.layout().addWidget(self.pasteCallButton, 3, 1)
  147. self.pasteCodeSymButton = QPushButton(self)
  148. self.layout().addWidget(self.pasteCodeSymButton, 4, 0)
  149. self.pasteCodeButton = QPushButton(self)
  150. self.layout().addWidget(self.pasteCodeButton, 4, 1)
  151. self.pasteCallSymButton.released.connect(self.__pasteCallSym)
  152. self.pasteCallButton.released.connect(self.__pasteCall)
  153. self.pasteCodeSymButton.released.connect(self.__pasteCodeSym)
  154. self.pasteCodeButton.released.connect(self.__pasteCode)
  155. def updateData(self, libEntryCls):
  156. self.libEntryCls = libEntryCls
  157. prefix = "FC" if libEntryCls._isFC else "FB"
  158. typeStr = "FUNCTION" if libEntryCls._isFC else "FUNCTION_BLOCK"
  159. self.blockName = "%s %d" % (prefix, libEntryCls.staticIndex)
  160. desc = self._blockToInterfaceText(self.blockName,
  161. libEntryCls.symbolName,
  162. libEntryCls.description,
  163. libEntryCls.interfaceFields)
  164. self.desc.setText(desc)
  165. self.pasteCodeButton.setText("%s %s" %\
  166. (typeStr, self.blockName))
  167. self.pasteCodeSymButton.setText("%s \"%s\"" %\
  168. (typeStr, libEntryCls.symbolName))
  169. self.pasteCallButton.setText("CALL %s" % self.blockName)
  170. self.pasteCallSymButton.setText("CALL \"%s\"" %\
  171. libEntryCls.symbolName)
  172. def __pasteCodeWarning(self):
  173. res = QMessageBox.warning(self,
  174. "Paste library code body?",
  175. "Warning: It is not recommended to paste library "
  176. "code into the project sources. You should instead "
  177. "just import the library (via library selection table) "
  178. "and CALL the imported function.\n\n"
  179. "See the 'CALL \"%s\"' or 'CALL %s' buttons.\n\n"
  180. "Do you want to paste the code nevertheless?" % (
  181. self.libEntryCls.symbolName, self.blockName),
  182. QMessageBox.Yes | QMessageBox.No,
  183. QMessageBox.No)
  184. return res == QMessageBox.Yes
  185. def __pasteCode(self):
  186. if not self.__pasteCodeWarning():
  187. return
  188. self.paste.emit(self.libEntryCls().getCode(False))
  189. self.finish.emit()
  190. def __pasteCodeSym(self):
  191. if not self.__pasteCodeWarning():
  192. return
  193. self.paste.emit(self.libEntryCls().getCode(True))
  194. self.addSymbol.emit(self.libEntryCls.symbolName,
  195. self.blockName,
  196. self.blockName,
  197. self.libEntryCls.description)
  198. self.finish.emit()
  199. def __pasteCall(self):
  200. self._pasteCallGeneric(self.blockName,
  201. self.libEntryCls._isFB,
  202. self.libEntryCls.interfaceFields)
  203. self.addLibrary.emit(self.libEntryCls().makeSelection())
  204. self.finish.emit()
  205. def __pasteCallSym(self):
  206. self._pasteCallGeneric('"%s"' % self.libEntryCls.symbolName,
  207. self.libEntryCls._isFB,
  208. self.libEntryCls.interfaceFields)
  209. self.addSymbol.emit(self.libEntryCls.symbolName,
  210. self.blockName,
  211. self.blockName,
  212. self.libEntryCls.description)
  213. self.addLibrary.emit(self.libEntryCls().makeSelection())
  214. self.finish.emit()
  215. def defaultPaste(self):
  216. self.__pasteCallSym()
  217. class LibraryDialog(QDialog):
  218. ITEM_SFC = QListWidgetItem.UserType + 0
  219. ITEM_SFB = QListWidgetItem.UserType + 1
  220. ITEM_LIB_BASE = QListWidgetItem.UserType + 100
  221. BLOCK_OFFSET = QListWidgetItem.UserType + 0xFFFF
  222. def __init__(self, project, parent=None):
  223. QDialog.__init__(self, parent)
  224. self.setLayout(QGridLayout())
  225. self.setWindowTitle("AWL/STL - Standard library")
  226. self.setWindowIcon(getIcon("stdlib"))
  227. self.project = project
  228. self.pasteText = None
  229. self.pasteSymbol = None
  230. self.pasteLibSel = None
  231. self.currentActionWidget = None
  232. self.__nr2lib = {}
  233. self.__nr2entry = {}
  234. self.libList = QListWidget(self)
  235. QListWidgetItem("System functions (SFC)", self.libList, self.ITEM_SFC)
  236. QListWidgetItem("System function blocks (SFB)", self.libList, self.ITEM_SFB)
  237. for i, libName in enumerate(("IEC",)):
  238. try:
  239. lib = AwlLib.getByName(libName)
  240. except AwlSimError as e:
  241. MessageBox.handleAwlSimError(self, "Library error", e)
  242. continue
  243. self.__nr2lib[self.ITEM_LIB_BASE + i] = lib
  244. QListWidgetItem(lib.description, self.libList,
  245. self.ITEM_LIB_BASE + i)
  246. self.layout().addWidget(self.libList, 0, 0, 3, 1)
  247. self.libElemList = QListWidget(self)
  248. self.libElemList.setFont(getDefaultFixedFont())
  249. self.layout().addWidget(self.libElemList, 0, 1, 3, 1)
  250. self.iconLabel = QLabel(self)
  251. self.iconLabel.setAlignment(Qt.AlignHCenter | Qt.AlignTop)
  252. self.iconLabel.setPixmap(getIcon("stdlib").pixmap(QSize(64, 64)))
  253. self.layout().addWidget(self.iconLabel, 0, 2)
  254. self.sysAction = SysActionWidget(self)
  255. self.sysAction.hide()
  256. self.layout().addWidget(self.sysAction, 1, 2)
  257. self.libAction = LibActionWidget(self)
  258. self.libAction.hide()
  259. self.layout().addWidget(self.libAction, 2, 2)
  260. self.libList.currentItemChanged.connect(self.__libItemChanged)
  261. self.libElemList.currentItemChanged.connect(self.__libElemItemChanged)
  262. self.libElemList.itemDoubleClicked.connect(self.__libElemDoubleClicked)
  263. for actionWidget in (self.sysAction, self.libAction):
  264. actionWidget.paste.connect(self.__actionPaste)
  265. actionWidget.addSymbol.connect(self.__actionAddSym)
  266. actionWidget.addLibrary.connect(self.__actionAddLib)
  267. actionWidget.finish.connect(self.accept)
  268. self.libList.setCurrentRow(0)
  269. self.libList.setMinimumWidth(190)
  270. self.libElemList.setMinimumWidth(350)
  271. self.iconLabel.setMinimumWidth(400)
  272. self.sysAction.setMinimumWidth(self.iconLabel.minimumWidth())
  273. self.libAction.setMinimumWidth(self.iconLabel.minimumWidth())
  274. self.resize(self.size().width(), 360)
  275. def __addLibElemList(self, entries):
  276. # Figure out the padding.
  277. maxA = maxB = ""
  278. for number, textA, textB, textC in entries:
  279. if len(textA) > len(maxA):
  280. maxA = textA
  281. if len(textB) > len(maxB):
  282. maxB = textB
  283. # Pad the entries and add them to the widget.
  284. for number, textA, textB, textC in entries:
  285. textA += " " * (len(maxA) - len(textA))
  286. textB += " " * (len(maxB) - len(textB))
  287. QListWidgetItem("%s %s%s" % (textA, textB, textC),
  288. self.libElemList,
  289. number)
  290. def __addSystemBlockTable(self, prefix, table):
  291. entries = []
  292. for blockCls in sorted(dictValues(table), key=lambda c: c.name[0]):
  293. if blockCls.broken:
  294. continue
  295. number, name, desc = blockCls.name
  296. conf = self.project.getCpuConf()
  297. if number < 0 and not conf.extInsnsEn:
  298. continue
  299. absName = "%s %d" % (prefix, number)
  300. symName = '"%s"' % name
  301. if desc:
  302. desc = " (%s)" % desc
  303. else:
  304. desc = ""
  305. entries.append((number + self.BLOCK_OFFSET,
  306. absName, symName, desc))
  307. self.__addLibElemList(entries)
  308. def __addLibraryTable(self, lib):
  309. entries = []
  310. for i, libCls in enumerate(sorted(lib.entries(),
  311. key=lambda c: c.staticIndex)):
  312. if libCls.broken:
  313. continue
  314. absName = "%s %d" % ("FC" if libCls._isFC else "FB",\
  315. libCls.staticIndex)
  316. symName = '"%s"' % libCls.symbolName
  317. if libCls.description:
  318. desc = " (%s)" % libCls.description
  319. else:
  320. desc = ""
  321. entries.append((i + self.BLOCK_OFFSET,
  322. absName, symName, desc))
  323. self.__nr2entry[i + self.BLOCK_OFFSET] = libCls
  324. self.__addLibElemList(entries)
  325. def __hideAllActionWidgets(self):
  326. self.currentActionWidget = None
  327. self.sysAction.hide()
  328. self.libAction.hide()
  329. def __libItemChanged(self, item, prevItem):
  330. self.__hideAllActionWidgets()
  331. self.libElemList.clear()
  332. if item.type() == self.ITEM_SFC:
  333. self.__addSystemBlockTable("SFC", SFC_table)
  334. elif item.type() == self.ITEM_SFB:
  335. self.__addSystemBlockTable("SFB", SFB_table)
  336. elif item.type() >= self.ITEM_LIB_BASE:
  337. lib = self.__nr2lib[item.type()]
  338. self.__addLibraryTable(lib)
  339. else:
  340. assert(0)
  341. def __libElemItemChanged(self, item, prevItem):
  342. if not item:
  343. self.__hideAllActionWidgets()
  344. return
  345. libType = self.libList.currentItem().type()
  346. if libType in (self.ITEM_SFC, self.ITEM_SFB):
  347. blockNum = item.type() - self.BLOCK_OFFSET
  348. self.sysAction.show()
  349. self.currentActionWidget = self.sysAction
  350. if libType == self.ITEM_SFC:
  351. self.sysAction.updateData("SFC", SFC_table[blockNum])
  352. else:
  353. self.sysAction.updateData("SFB", SFB_table[blockNum])
  354. elif libType >= self.ITEM_LIB_BASE:
  355. entryCls = self.__nr2entry[item.type()]
  356. self.libAction.show()
  357. self.currentActionWidget = self.libAction
  358. self.libAction.updateData(entryCls)
  359. else:
  360. assert(0)
  361. def __libElemDoubleClicked(self, item):
  362. if self.currentActionWidget:
  363. self.currentActionWidget.defaultPaste()
  364. def __actionPaste(self, text):
  365. assert(self.pasteText is None)
  366. self.pasteText = text
  367. def __actionAddSym(self, symbolName, address, dataType, comment):
  368. assert(self.pasteSymbol is None)
  369. self.pasteSymbol = (symbolName, address, dataType, comment)
  370. def __actionAddLib(self, libSelection):
  371. assert(self.pasteLibSel is None)
  372. self.pasteLibSel = libSelection