1 from PyQt5.QtWidgets
import QFileDialog, QDialog, QMainWindow, QTreeWidgetItem, QCheckBox, QMessageBox, QDialogButtonBox, QComboBox, QHeaderView
2 from PyQt5.QtCore
import Qt
3 from PyQt5.QtGui
import QBrush, QColor
10 from typing
import Optional, List
13 def _getFileDialog(self):
14 dlg = QFileDialog(self)
15 dlg.setFileMode(QFileDialog.ExistingFile)
16 dlg.setViewMode( QFileDialog.Detail )
17 dlg.setNameFilters( [self.tr(
'Certificate file (*.der)'), self.tr(
'All Files (*)')] )
18 dlg.setDefaultSuffix(
'der')
23 dlg.setWindowTitle(
'Set application certificate file')
26 self.
settings.certificate =
str(dlg.selectedFiles()[0])
31 dlg.setWindowTitle(
'Set appliction private key file')
37 def _openDirectory(self) -> Optional[str]:
38 dlg = QFileDialog(self)
39 dlg.setFileMode(QFileDialog.Directory)
40 dlg.setOption(QFileDialog.ShowDirsOnly)
42 return str(dlg.selectedFiles()[0])
65 def __init__(self, data:EncryptionSettings, parent=
None):
66 super(EncryptionDialog, self).
__init__(parent)
71 self.
userCert.setText(data.certificate)
88 def __init__(self, data:QComboBox, histories: List[HistorySetting], edit:bool, parent=
None):
90 @param data: The setting to be added.
91 @param histories: The list of all settings. Is used to ensure that names are unique!
93 super(HistorySettingsDialog,self).
__init__(parent)
95 self.
data = data.currentData()
115 self.
buttonBox.button( QDialogButtonBox.Ok ).setEnabled(
False );
117 self.
buttonBox.button( QDialogButtonBox.Ok ).setEnabled(
True );
127 def _createMapGenerator(self, fileName: str|
None):
133 except RuntimeError
as error:
134 QMessageBox.critical(self,
"Map file generator",
135 "Could not parse xml file. Is that a file generated by the XML generator of your application?\n Error: {}".
format(error) )
139 QMessageBox.critical(self,
"Map file generator",
140 "Could not parse xml file. Is that a file generated by the XML generator of your application?" )
146 Read all information from the given XML file.
147 Data is stored in the MapGenerator object.
149 @param fromMenu: If true no user interaction box is shown, because we know the user wants to open a file.
153 buttonReply = QMessageBox.question(self,
'PyQt5 message',
"You did not specify an input xml file (created with the XMLGenerator of your application).\nIf not you will only be able to set general OPC UA server properties without mapping features.\n Do you want to specify it now?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
154 if buttonReply == QMessageBox.Yes
or fromMenu:
155 name = QFileDialog.getOpenFileName(self, caption=
'Set input file', filter=
"XML files (*.xml)", options=QFileDialog.DontResolveSymlinks)
160 logging.warning(
"No xml input file given.")
162 def _fillConfig(self):
164 Fill config information to the GUI fields.
192 Fill the tree by passing the root node read from the XML file.
193 Filling is done recursively.
205 header.setSectionResizeMode(0, QHeaderView.ResizeToContents)
206 header.setSectionResizeMode(1, QHeaderView.ResizeToContents)
207 header.setSectionResizeMode(2, QHeaderView.ResizeToContents)
208 header.setSectionResizeMode(3, QHeaderView.ResizeToContents)
209 header.setSectionResizeMode(4, QHeaderView.Interactive)
211 def _getCheckBox(self, item:XMLDirectory | XMLVar, text:str, node:QTreeWidgetItem):
215 @param item: The XML items that corresponds to the checkbox.
216 @param text: The check box label.
217 @param node: The widget item where the checkbox will be added. It is assumed it is a QTreeWidgetItem with multiple columns.
219 checkbox = QCheckBox(parent=self.
treeWidget, text=text)
220 if item.exclude ==
True:
221 checkbox.setChecked(
True)
222 if isinstance(item, XMLDirectory) ==
True:
223 checkbox.stateChanged.connect(
lambda state: self.
_mapDirectory(state,item, node))
225 checkbox.stateChanged.connect(
lambda state: self.
_mapItem(state,item, node))
228 def _getComboBox(self, item:XMLDirectory | XMLVar, node:QTreeWidgetItem):
232 @param item: The XML items that corresponds to the checkbox.
233 @param node: The widget item where the checkbox will be added. It is assumed it is a QTreeWidgetItem with multiple columns.
236 combobox.addItem(
'No history',
None)
237 combobox.currentIndexChanged.connect(
lambda histIndex: self.
_setHistoryItem(histIndex, item, combobox, node))
238 combobox.setSizeAdjustPolicy(QComboBox.AdjustToContents)
241 def _updateItem(self, item:QTreeWidgetItem, index:int):
243 data = item.data(0, Qt.UserRole)
246 if item.text(index) == data.name:
248 item.setForeground(index,QBrush(QColor(
"#000000")))
250 data.newName = item.text(index)
251 item.setText(index, item.text(index) +
" (Orig.: " + data.name +
")")
252 item.setForeground(index,QBrush(QColor(
"#FF0000")))
255 if isinstance(data,XMLVar):
256 if item.text(index) == data.unit:
258 item.setForeground(index,QBrush(QColor(
"#000000")))
260 data.newUnit = item.text(index)
261 item.setText(index, item.text(index) +
" (Orig.: " +
str(data.unit
or '') +
")")
262 item.setForeground(index,QBrush(QColor(
"#FF0000")))
264 item.setText(index,
'')
265 msg =
"Adding unit to a directory does not make sense!"
266 QMessageBox.warning(self,
"Map file generator", msg )
269 if item.text(index) ==
"":
270 data.newDescription =
None
271 item.setForeground(index,QBrush(QColor(
"#000000")))
273 data.newDescription = item.text(index)
274 item.setText(index, item.text(index) +
" (Added descr.)")
275 item.setForeground(index,QBrush(QColor(
"#FF0000")))
278 if item.text(index) ==
"":
279 data.newDestination =
None
280 item.setForeground(index,QBrush(QColor(
"#000000")))
282 data.newDestination = item.text(index)
283 item.setText(5,data.newDestination)
284 item.setForeground(5,QBrush(QColor(
"#FF0000")))
287 def _setupRow(self, item:QTreeWidgetItem, data: XMLDirectory|XMLVar):
288 item.setData(0, Qt.UserRole, data)
291 if isinstance(data, XMLDirectory):
292 item.setFlags(Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled | Qt.ItemIsSelectable | Qt.ItemIsDropEnabled)
294 item.setFlags(Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled | Qt.ItemIsSelectable)
296 def _setState(self, state:int, item: XMLDirectory|XMLVar):
298 Set data in XMLDirectory or XMLVar.
299 This is used to enable mapping.
301 if state == Qt.Checked:
303 logging.debug(
"Excluding item {}.".
format(item.name))
306 logging.debug(
"Including item {}.".
format(item.name))
308 def _createVariableNode(self, parent:QTreeWidgetItem, v:XMLVar) -> QTreeWidgetItem:
310 Create variable entry in the tree.
311 Reads description and unit.
312 Add CheckBox to enable/disable mapping of that variable.
315 if v.newName !=
None:
316 name =
"{} (Orig.: {})".
format(v.newName, v.name)
317 description = v.description
318 if v.newDescription !=
None:
319 description =
"{} (Orig.: {})".
format(v.newDescription, v.description)
321 if v.newUnit !=
None:
322 unit =
"{} (Orig.: {})".
format(v.newUnit, v.unit)
323 node = QTreeWidgetItem(parent, [name]+[
""]*2 + [unit] + [description]+[v.newDestination])
325 if v.direction ==
'control_system_to_application':
326 node.setBackground(0,QBrush(QColor(
"#fee8c8")))
328 node.setBackground(0,QBrush(QColor(
"#e5f5e0")))
330 if v.newName !=
None:
331 node.setForeground(0,QBrush(QColor(
"#FF0000")))
332 if v.newUnit !=
None:
333 node.setForeground(3,QBrush(QColor(
"#FF0000")))
334 if v.newDescription !=
None:
335 node.setForeground(4,QBrush(QColor(
"#FF0000")))
336 if v.newDestination !=
None:
337 node.setForeground(5,QBrush(QColor(
"#FF0000")))
341 def _createDirectoryNode(self, parent:QTreeWidgetItem, directory:XMLDirectory, isRootNode:bool =
False) -> QTreeWidgetItem:
343 Create a directory entry in the Tree.
344 This will recursively add sub directories and variables in the directories.
346 name = directory.name
347 if directory.newName !=
None:
348 name =
"{} (Orig.: {})".
format(directory.newName, directory.name)
350 if directory.newDescription !=
None:
351 description =
"{} (Added descr.)".
format(directory.newDescription)
352 mainNode = QTreeWidgetItem(parent, [name] + [
""]*3 + [description] + [directory.newDestination
or ''])
353 if directory.newName !=
None:
354 mainNode.setForeground(0,QBrush(QColor(
"#FF0000")))
355 if directory.newDescription !=
None:
356 mainNode.setForeground(4,QBrush(QColor(
"#FF0000")))
357 if directory.newDestination !=
None:
358 mainNode.setForeground(5,QBrush(QColor(
"#FF0000")))
361 if isRootNode ==
True:
362 mainNode.setExpanded(
True)
363 for d
in directory.dirs:
365 for v
in directory.vars:
369 def _mapItem(self, state:int, var:XMLVar, node:QTreeWidgetItem):
371 Called when a variable is to be mapped.
372 The corresponding check box is edited.
375 logging.debug(
"Childs: {}".
format(node.childCount()))
377 def _mapDirectory(self, state:int, directory:XMLDirectory, node:QTreeWidgetItem):
379 Called when a directory is to be mapped.
380 The corresponding check box is edited.
381 This will change the map status of all variables in the directory.
383 logging.debug(
"Mapping directory.")
384 for chId
in range(node.childCount()):
385 ch = node.child(chId)
386 self.
treeWidget.itemWidget(ch, 1).setCheckState(state)
389 def _setHistoryItem(self, histIndex:int, var:XMLVar|XMLDirectory, combo:QComboBox, node:QTreeWidgetItem):
391 Called when historizing setting is choosen.
392 @param histIndex: The index of the history setting.
394 if combo.itemText(histIndex) !=
'No history':
395 var.historizing = combo.itemText(histIndex)
397 var.historizing =
None
399 if isinstance(var, XMLDirectory):
400 for chId
in range(node.childCount()):
401 ch = node.child(chId)
402 self.
treeWidget.itemWidget(ch, 2).setCurrentText(combo.itemText(histIndex))
404 self.
treeWidget.itemWidget(ch, 2).setEnabled(
False)
406 self.
treeWidget.itemWidget(ch, 2).setEnabled(
True)
409 msg =
"Are you sure you want to close the editor?"
410 reply = QMessageBox.question(self,
'ChimeraTK Logical Name Mapping editor', msg, QMessageBox.Yes, QMessageBox.No)
411 if reply != QMessageBox.Yes:
418 Save file. If file not yet set open dialog with saveFileAs()
425 except RuntimeError
as error:
426 msg =
"The following error was reported: " +
str(error)
427 QMessageBox.critical(self,
"Map file generator", msg )
431 dlg = QFileDialog(self, directory=self.
MapGenerator.applicationName +
"_mapping.xml")
432 dlg.setAcceptMode(QFileDialog.AcceptSave)
433 dlg.setWindowTitle(
'Save XML mapping file')
434 dlg.setViewMode( QFileDialog.Detail )
435 dlg.setNameFilters( [self.tr(
'XML Mapping Files (*.xml)'), self.tr(
'All Files (*)')] )
436 dlg.setDefaultSuffix(
'xml')
438 dlg.setDirectory( os.path.dirname(self.
MapGenerator.outputFile) )
449 if state == Qt.Checked:
471 if isinstance(item.data(0,Qt.UserRole), XMLVar):
472 msg =
"Variables or directories can only be moved to other directories."
473 QMessageBox.critical(self,
"Map file generator", msg )
478 currentData = current.data(0,Qt.UserRole)
479 currentData.newDestination = item.data(0,Qt.UserRole).path
480 current.setText(5,currentData.newDestination)
481 current.setForeground(5,QBrush(QColor(
"#FF0000")))
504 histName =
'historySetting'
505 if self.
histories.findText(histName) != -1:
507 histName =
'historySetting_{}'.
format(i)
508 while(self.
histories.findText(histName) != -1):
509 histName =
'historySetting_{}'.
format(i)
513 self.
histories.addItem(setting.name, setting)
514 self.
histories.setCurrentText(setting.name)
521 @param setting: The Setting to add.
522 @param updateMapGenerator: If False the setting is not added to the mapGenerator list.
523 This option is used when an existing map file was read and here
524 only the combo box should be updated.
526 logging.info(
"Adding historizing setting with name: {}".
format(setting.name))
528 if updateMapGenerator:
535 header.resizeSection(2,200)
540 self.
treeWidget.itemWidget(item, 2).addItem(setting.name, setting)
541 for chId
in range(item.childCount()):
550 After reading an existing map file the tree needs to be updated.
551 The information which history to use is already in the Item data.
553 data = item.data(0, Qt.UserRole)
554 if isinstance(data, XMLVar):
555 if data.historizing !=
None:
556 self.
treeWidget.itemWidget(item, 2).setCurrentText(data.historizing)
557 elif isinstance(data, XMLDirectory):
558 if data.historizing !=
None:
560 self.
treeWidget.itemWidget(item, 2).setCurrentText(data.historizing)
563 for chId
in range(item.childCount()):
567 data = node.data(0, Qt.UserRole)
568 if isinstance(data, XMLVar)
and data.direction ==
'control_system_to_application':
572 self.
treeWidget.itemWidget(node, 2).setCurrentText(
'No history')
575 for chId
in range(node.childCount()):
578 def _blockAndSetTextBox(self, value: str, control):
580 Set a text box without updating the configuration,
581 which would override parameters of the MapGenerator.
583 control.blockSignals(
True)
585 control.setText(value)
586 control.blockSignals(
False)
588 def _bockAndSetValue(self, value: int, control):
590 Set value of spinbox.
592 control.blockSignals(
True)
594 control.setValue(value)
595 control.blockSignals(
False)
597 def _blockAndSetCheckbox(self, value: bool, control):
599 Set a check box without updating the configuration,
600 which would override parameters of the MapGenerator.
602 control.blockSignals(
True)
604 control.setChecked(
True)
606 control.setChecked(
False)
607 control.blockSignals(
False)
611 msg =
"You have to open a xml file first."
612 QMessageBox.critical(self,
"Map file generator", msg )
614 name = QFileDialog.getOpenFileName(self, caption=
'Load existing mapping file', filter=
"XML files (*.xml)", options=QFileDialog.DontResolveSymlinks)
618 if missed[0] != 0
or missed[1] != 0
or missed[2] != 0:
619 QMessageBox.warning(self,
"Map file generator",
620 "{} directories, {} PV and {} exclude paths could not be found by their source name in the original variable tree.\n Probably"
621 " the mapping file does not correspong to the application XML file or the application was changed in the mean time.".
format(missed[0], missed[1], missed[2]))
629 if self.
histories.findText(setting.name) == -1:
630 self.
histories.addItem(setting.name,setting)
633 logging.warning(
"Setting with name {} already exist. Not loading parameters from mapping file.".
format(setting.name))
637 except RuntimeError
as error:
638 QMessageBox.critical(self,
"Map file generator",
639 "Failed to load map file: {}\n Error: {}".
format(name[0], error) )
642 super(MapGeneratorForm, self).
__init__(parent)
654 self.
histories.setSizeAdjustPolicy(QComboBox.AdjustToContents)