from ast import Mod from math import comb import typing import sys from numpy import var import qtawesome from PyQt5 import QtGui,QtCore,QtWidgets from PyQt5.QtCore import QAbstractTableModel, QModelIndex, Qt, QVariant, QSize from PyQt5.QtWidgets import QItemDelegate, QHBoxLayout, QWidget, QMessageBox, QComboBox, QStyleOptionViewItem from UI.VarManages.BaseButtonDelegate import BaseButtonDelegate from sympy import N # 移除 Celery 相关导入,现在使用 ProtocolManage 和 ModbusManager from ..TrendManage.ActualTrendWidget import ActualTrend from model.ProjectModel.VarManage import * from utils import Globals import re sys.path.append('protocol/Celery/MBTCPMaster') class QPushButton(QtWidgets.QPushButton): def __init__(self, *__args: any,clicked = None): super(QPushButton,self).__init__(*__args) self.setCursor(Qt.PointingHandCursor) self.setIconSize(QSize(25, 25)) class VarTableModel(QAbstractTableModel): ''' 变量表模型类''' def __init__(self, header, data: list, modbusType = None, table = None): ''' header : 表头变量 data : 表格内容 table : 缺省参数 ''' QAbstractTableModel.__init__(self, parent=None) self.header = header self.datas = data self.checkList = ['Unchecked'] * len(self.datas) self.supportedDragActions() self.table = table self.editableList = [] # 表格中可编辑项 self.modbusType = modbusType def initTable(self, initVar = None): self.datas = [] self.editableList = [] if self.table: self.table.parent.initIcon() varDatas = ModbusVarManage.getAllVar(self.modbusType) if not varDatas: # self.layoutChanged.emit() self.table.proxy.invalidate() return for x in varDatas: x.insert(1, '') x.insert(2, '') self.datas.append(x) self.checkList = ['Unchecked'] * len(self.datas) # self.layoutChanged.emit() self.table.proxy.invalidate() self.refreshComboBox() def append_data(self, x): self.datas.append(x) self.checkList = ['Unchecked'] * len(self.datas) self.table.proxy.invalidate() # self.layoutChanged.emit() def insert_data(self, x, index): self.datas.insert(index, x) self.checkList = ['Unchecked'] * len(self.datas) self.table.proxy.invalidate() def remove_row(self, row): self.datas.pop(row) self.checkList = ['UnChecked'] * len(self.datas) self.table.proxy.invalidate() def rowCount(self, parent: QModelIndex = ...) -> int: if len(self.datas) > 0: return len(self.datas) return 0 def columnCount(self, parent: QModelIndex = ...) -> int: return len(self.header) def data(self, QModelIndex, role=None): # print(Qt.__dict__.items()) if role == Qt.TextAlignmentRole: return Qt.AlignCenter if not QModelIndex.isValid(): print("行或者列有问题") return QVariant() if role == Qt.BackgroundRole: # 只有强制变量显示绿色背景,其他都是白色 if self.datas[QModelIndex.row()][3] in Globals.getValue('forceVars'): return QtGui.QColor('#00FF7F') if role == Qt.TextColorRole: return QtGui.QColor('#1A1A1A') if role == Qt.CheckStateRole: if QModelIndex.column() == 0: return Qt.Checked if self.checkList[QModelIndex.row()] == 'Checked' else Qt.Unchecked else: return None if role == Qt.ToolTipRole: if QModelIndex.column() == 0: return self.checkList[QModelIndex.row()] if role == Qt.DisplayRole or role == Qt.EditRole: if QModelIndex.row() in self.editableList: return self.datas[QModelIndex.row()][QModelIndex.column()] if role != Qt.DisplayRole: return QVariant() # 获取变量值并插入表格 if QModelIndex.column() == 2: varName = self.datas[QModelIndex.row()][3] if varName != '': try: protocolManage = Globals.getValue('protocolManage') if protocolManage: result = protocolManage.readVariableValue(varName) if result is not None: self.datas[QModelIndex.row()][QModelIndex.column()] = result except Exception as e: pass return QVariant(self.datas[QModelIndex.row()][QModelIndex.column()]) def headerData(self, section: int, orientation: Qt.Orientation, role: int = ...) -> typing.Any: # 表头 if role != Qt.DisplayRole: return None if orientation == Qt.Horizontal: return self.header[section] def setData(self, index, value, role): row = index.row() col = index.column() if role == Qt.CheckStateRole and col == 0: self.checkList[row] = 'Checked' if value == Qt.Checked else 'Unchecked' return True if role == Qt.EditRole: self.datas[row][col] = value return True return True def flags(self, index): if index.column() == 0: return Qt.ItemIsEnabled | Qt.ItemIsUserCheckable if index.row() in self.editableList and index.column() or index.column() == 1: return Qt.ItemIsEnabled | Qt.ItemIsUserCheckable | Qt.ItemIsEditable return Qt.ItemIsEnabled def headerClick(self, isOn): self.beginResetModel() if isOn: self.checkList = [] self.checkList = ['Checked'] * len(self.datas) else: self.checkList = [] self.checkList = ['UnChecked'] * len(self.datas) self.endResetModel() def dragMoveEvent(self, event): event.setDropAction(QtCore.Qt.MoveAction) event.accept() def moveRow(self, sourceParent: QModelIndex, sourceRow: int, destinationParent: QModelIndex, destinationChild: int) -> bool: if self.datas[destinationChild] == self.datas[sourceRow]: return self.datas[sourceRow], self.datas[destinationChild] = self.datas[destinationChild], self.datas[sourceRow] self.table.proxy.invalidate() def refreshComboBox(self): #功能类型的index是5,通讯类型index是10 for i in range(len(self.datas)): cbRow = str('cb' + str(i) + str(5)) index = self.index(i, int(5)) delegate = self.table.itemDelegate(index) # 不需要调用paint方法,因为委托会在需要时自动创建控件 # 直接尝试获取已存在的comboBox try: comboBox = getattr(delegate, cbRow, None) if comboBox is None: # 如果comboBox不存在,跳过这一行 continue # 检查comboBox对象是否仍然有效,避免RuntimeError if not comboBox or comboBox.isHidden(): continue # print(comboBox, i, num, cbRow) except Exception as e: continue if self.datas[i][5] in [0, 1]: comboBox.setCurrentIndex(self.datas[i][5]) elif self.datas[i][5] in [3, 4]: comboBox.setCurrentIndex(self.datas[i][5] - 1) else: comboBox.setCurrentIndex(-1) class ModbusButtonDelegate(BaseButtonDelegate): """该类用于向单元格中添加按钮 任务表格""" trendWidgetDict = {} def __init__(self, parent=None): super(ModbusButtonDelegate, self).__init__(parent) def trendWidget(self, varName): if varName not in self.trendWidgetDict: trendWidget = ActualTrend(varName = varName) self.trendWidgetDict[varName] = trendWidget return self.trendWidgetDict[varName] def paint(self, painter, option, index): # 首先绘制背景颜色,与其他委托保持一致 if index.data(Qt.BackgroundRole): height = option.rect.height() top = option.rect.top() paint_rect = option.rect paint_rect.setHeight(max(46, height - 4)) paint_rect.moveTop(top + 2) painter.fillRect(paint_rect, index.data(Qt.BackgroundRole)) if not self.parent().indexWidget(index): button1 = QPushButton() button1.setIcon(qtawesome.icon('fa.play', color='#1fbb6f')) button1.setToolTip("启动强制") button2 = QPushButton() button2.setIcon(qtawesome.icon('fa.pencil', color='#4c8cf2')) button2.setToolTip("编辑变量") button3 = QPushButton() button3.setIcon(qtawesome.icon('fa.trash', color='#ff6d6d')) button3.setToolTip("删除变量") button4 = QPushButton() button4.setIcon(qtawesome.icon('fa.line-chart', color='#393c4e')) button4.setToolTip("趋势图") comboBox = QComboBox(self.parent()) comboBox.addItem('int', 0) comboBox.addItem('ABCD', 1) comboBox.addItem('CDAB', 2) comboBox.addItem('BADC', 3) comboBox.addItem('DCBA', 4) comboBox.setCurrentText(str(self.parent().model.datas[index.row()][index.column()])) comboBox.setToolTip("字节序") comboBox.currentIndexChanged.connect(self.indexChange) button1.clicked.connect(self.start_action) button2.clicked.connect(self.edit_action) button3.clicked.connect(self.delete_action) button4.clicked.connect(self.trend_action) button2.oldName = False button2.isEdit = True button3.editButton = button2 button1.index = [index.row(), index.column()] button2.index = [index.row(), index.column()] button3.index = [index.row(), index.column()] button4.index = [index.row(), index.column()] comboBox.index = [index.row(), index.column()] data = self.parent().model.datas[index.row()] for x in data[:-3]: if x != '': break else: button2.isEdit = False button2.setIcon(qtawesome.icon('fa.save', color='#1fbb6f')) button2.setToolTip("保存变量") self.parent().model.editableList.append(button2.index[0]) hboxLayout = QHBoxLayout() hboxLayout.addWidget(comboBox) hboxLayout.addWidget(button1) hboxLayout.addWidget(button2) hboxLayout.addWidget(button3) hboxLayout.addWidget(button4) hboxLayout.setContentsMargins(2, 2, 2, 2) hboxLayout.setAlignment(Qt.AlignCenter) hboxLayout.setSpacing(3) widget = QWidget() widget.setLayout(hboxLayout) widget.setStyleSheet("QComboBox { background-color: transparent; }") # 所有下拉框使用统一样式 widget.setObjectName('ModbusButtonWidget') comboBox.setObjectName('ModbusOrderBox') self.parent().setIndexWidget(index, widget) def indexChange(self): sender = self.sender() modbusType = self.parent().modbusType index = sender.currentIndex() text = sender.itemText(index) name = self.parent().model.datas[sender.index[0]][3] ModbusVarManage.editOrder(name, text, modbusType) self.parent().model.datas[sender.index[0]][sender.index[1]] = text def start_action(self): sender = self.sender() model = self.parent().model # 使用基类的验证方法获取行索引 view_row, source_row = self._validate_button_indices(sender) if view_row is None: return # 获取强制值(从视图)和其他数据(从源模型) value = str(self._get_view_data(view_row, 1)) # 强制值列 varMes = model.datas[source_row] name, des, varType, slaveID, address, min, max, order = str(varMes[3]), str(varMes[4]), str(varMes[5]), str(varMes[6]), str(varMes[7]), str(varMes[8]), str(varMes[9]), str(varMes[10]) pattern = re.compile(r'[^0-9\.-]+') if not value or re.findall(pattern, str(value)): reply = QMessageBox.question(self.parent(), '警告', "请输入强制值或数字", QMessageBox.Yes) return if min and max: if float(value) < float(min) or float(value) > float(max): reply = QMessageBox.question(self.parent(), '警告', "超出量程范围", QMessageBox.Yes) return elif min and not max: if float(value) < float(min): reply = QMessageBox.question(self.parent(), '警告', "超出量程范围", QMessageBox.Yes) return elif max and not min: if float(value) > float(max): reply = QMessageBox.question(self.parent(), '警告', "超出量程范围", QMessageBox.Yes) return # 使用新的 ProtocolManage 进行变量写入 protocolManage = Globals.getValue('protocolManage') if protocolManage: try: result = protocolManage.writeVariableValue(name, value) if result: forceVars = Globals.getValue('forceVars') if forceVars is None: forceVars = set() forceVars.add(name) Globals.setValue('forceVars', forceVars) else: QMessageBox.warning(self.parent(), '错误', f'变量 {name} 写入失败', QMessageBox.Yes) except Exception as e: QMessageBox.warning(self.parent(), '错误', f'写入变量时发生错误: {str(e)}', QMessageBox.Yes) else: QMessageBox.warning(self.parent(), '错误', '协议管理器未初始化', QMessageBox.Yes) def edit_action(self): sender = self.sender() model = self.parent().model modbusType = self.parent().modbusType pattern = re.compile(r'[^0-9\.-]+') # 使用基类的验证方法获取行索引 view_row, source_row = self._validate_button_indices(sender) if view_row is None: return fucationCbRow = str('cb' + str(source_row) + str(5)) fucationIndex = self.parent().model.index(source_row, 5) fucationDelegate = self.parent().itemDelegate(fucationIndex) fucationCheckbox = getattr(fucationDelegate, fucationCbRow) if sender.isEdit: sender.setIcon(qtawesome.icon('fa.save', color='#1fbb6f')) sender.isEdit = False sender.oldName = model.datas[source_row][3] model.editableList.append(source_row) fucationCheckbox.setEnabled(True) self.parent().viewport().update() else: varMes = model.datas[source_row] name, des, varType, slaveID, address, min, max, varModel, order = str(varMes[3]), str(varMes[4]), str(varMes[5]), str(varMes[6]), str(varMes[7]), str(varMes[8]), str(varMes[9]), str(varMes[-2]), str(varMes[-1]) if varType == '': varType = 0 if not name or not slaveID or not address: reply = QMessageBox.question(self.parent(), '警告', "有字段为空或输入错误", QMessageBox.Yes) return # 验证slaveID、address、min、max是否为有效数字 # 检查slaveID是否为有效数字 if slaveID and re.findall(pattern, slaveID): reply = QMessageBox.question(self.parent(), '警告', "从站地址必须为数字", QMessageBox.Yes) return # 检查address是否为有效数字 if address and re.findall(pattern, address): reply = QMessageBox.question(self.parent(), '警告', "寄存器地址必须为数字", QMessageBox.Yes) return # 检查min是否为有效数字(如果存在) if min and re.findall(pattern, min): reply = QMessageBox.question(self.parent(), '警告', "工程量下限必须为数字", QMessageBox.Yes) return # 检查max是否为有效数字(如果存在) if max and re.findall(pattern, max): reply = QMessageBox.question(self.parent(), '警告', "工程量上限必须为数字", QMessageBox.Yes) return if sender.oldName and ModbusVarManage.getByName(sender.oldName, modbusType): if sender.oldName == name: # 名称未改变,直接执行修改变量操作 ModbusVarManage.editVar(name=sender.oldName, Nname=name, des=des, varType=varType, slaveID=slaveID, address=address, min=min, max=max, order=order, modbusType=modbusType, varModel=varModel) else: # 名称改变,检查新名称是否已存在 if GlobalVarManager.isVarNameExist(name): QMessageBox.information(self.parent(), '提示', '已有同名变量') return else: # 执行修改变量操作 ModbusVarManage.editVar(name=sender.oldName, Nname=name, des=des, varType=varType, slaveID=slaveID, address=address, min=min, max=max, order=order, modbusType=modbusType, varModel=varModel) else: if GlobalVarManager.isVarNameExist(name): QMessageBox.information(self.parent(), '提示', '已有同名变量') return else: ModbusVarManage.createVar(varName = name, varType = varType, des = des, address = address, slaveID = slaveID, min = min, max = max, order = order, modbusType = modbusType, varModel= varModel) sender.setIcon(qtawesome.icon('fa.pencil', color='#4c8cf2')) varMes = ModbusVarManage.getByName(name, modbusType) varMes.append('本地值') varMes.append('int') varMes.insert(1, '') varMes.insert(2, '') model.insert_data(varMes, source_row) model.remove_row(source_row + 1) sender.isEdit = True fucationCheckbox.setEnabled(False) # varModelCheckbox.setEnabled(False) model.editableList.remove(source_row) self.parent().viewport().update() def delete_action(self): sender = self.sender() model = self.parent().model # 使用基类的验证方法获取行索引 view_row, source_row = self._validate_button_indices(sender) if view_row is None: return name = str(model.datas[source_row][3]) modbusType = self.parent().modbusType if sender.editButton.isEdit: ModbusVarManage.deleteVar(name = name, modbusType = modbusType) model.remove_row(source_row) def trend_action(self): model = self.parent().model if model.table.parent._isPopenOpen: sender = self.sender() # 使用基类的验证方法获取行索引 view_row, source_row = self._validate_button_indices(sender) if view_row is None: return name = str(model.datas[source_row][3]) trend = self.trendWidget(varName = name) trend.show() class ModbusTypeBox(QItemDelegate): def __init__(self, parent=None): super(ModbusTypeBox, self).__init__(parent) def paint(self, painter, option, index): # 首先绘制背景颜色,与其他委托保持一致 if index.data(Qt.BackgroundRole): height = option.rect.height() top = option.rect.top() paint_rect = option.rect paint_rect.setHeight(max(46, height - 4)) paint_rect.moveTop(top + 2) painter.fillRect(paint_rect, index.data(Qt.BackgroundRole)) if (index.column() == 5) and index.row() not in self.parent().model.editableList: data = self.parent().model.datas[index.row()] comBox = str('cb' + str(index.row()) + str(index.column())) setattr(self, comBox, QComboBox()) comboBox = getattr(self, comBox) items = ['Coil Status', 'Input Status', 'Input Register', 'Holding Register'] comboBox.addItems(items) if self.parent().model.datas[index.row()][index.column()] in [0, 1]: comboBox.setCurrentIndex(self.parent().model.datas[index.row()][index.column()]) elif self.parent().model.datas[index.row()][index.column()] in [3, 4]: comboBox.setCurrentIndex(self.parent().model.datas[index.row()][index.column()] - 1) else: comboBox.setCurrentIndex(0) comboBox.currentIndexChanged.connect(self.indexChange) comboBox.setObjectName('ModbusTypeBox') comboBox.setEditable(True) comboBox.lineEdit().setAlignment(Qt.AlignCenter) hboxLayout = QHBoxLayout() hboxLayout.addWidget(comboBox) hboxLayout.setContentsMargins(2, 2, 2, 2) comboBox.index = [index.row(), index.column()] # 所有下拉框使用统一样式 comboBox.setObjectName('ModbusTypeBox') if str(data[index.column()]): comboBox.setEnabled(False) comboBox.setProperty('disabled', True) else: comboBox.setEnabled(True) comboBox.setProperty('disabled', False) widget = QWidget() widget.setStyleSheet("QComboBox { background-color: transparent; }") widget.setLayout(hboxLayout) self.parent().setIndexWidget(index, widget) self.parent().openPersistentEditor(index) def indexChange(self): sender = self.sender() index = sender.currentIndex() if index in [2, 3]: index += 1 self.parent().model.datas[sender.index[0]][sender.index[1]] = index class ModbusVarModelBox(QItemDelegate): def __init__(self, parent=None, comBoxColumn = 10): super(ModbusVarModelBox, self).__init__(parent) self.comBoxColumn = comBoxColumn def paint(self, painter, option, index): # 首先绘制背景颜色,与其他委托保持一致 if index.data(Qt.BackgroundRole): height = option.rect.height() top = option.rect.top() paint_rect = option.rect paint_rect.setHeight(max(46, height - 4)) paint_rect.moveTop(top + 2) painter.fillRect(paint_rect, index.data(Qt.BackgroundRole)) # 本地值、模拟值、远程值 对应 0, 1, 2 if (index.column() == self.comBoxColumn) and not self.parent().indexWidget(index): data = self.parent().model.datas[index.row()] comBox = str('cb' + str(index.row()) + str(index.column())) setattr(self, comBox, QComboBox()) comboBox = getattr(self, comBox) comboBox = QComboBox(self.parent()) items = ['本地值', '模拟值'] comboBox.addItems(items) comboBox.setCurrentText(str(self.parent().model.datas[index.row()][index.column()])) comboBox.currentIndexChanged.connect(self.indexChange) comboBox.setObjectName('ModbusVarModelBox') comboBox.setEditable(True) comboBox.lineEdit().setAlignment(Qt.AlignCenter) hboxLayout = QHBoxLayout() hboxLayout.addWidget(comboBox) hboxLayout.setContentsMargins(2, 2, 2, 2) comboBox.index = [index.row(), index.column()] widget = QWidget() widget.setStyleSheet("QComboBox { background-color: transparent; }") widget.setLayout(hboxLayout) self.parent().setIndexWidget(index, widget) self.parent().openPersistentEditor(index) def indexChange(self): sender = self.sender() modbusType = self.parent().modbusType index = sender.currentIndex() text = sender.itemText(index) name = self.parent().model.datas[sender.index[0]][3] ModbusVarManage.editVarModel(name, text, modbusType) self.parent().model.datas[sender.index[0]][sender.index[1]] = text class HartVarModelBox(ModbusVarModelBox): def __init__(self, parent=None, comBoxColumn = 8): super(HartVarModelBox, self).__init__(parent) self.comBoxColumn = comBoxColumn def indexChange(self): sender = self.sender() index = sender.currentIndex() text = sender.itemText(index) name = self.parent().model.datas[sender.index[0]][1] HartVarManage.editVarModel(name, text) self.parent().model.datas[sender.index[0]][sender.index[1]] = text class TcRtdVarModelBox(ModbusVarModelBox): def __init__(self, parent=None, comBoxColumn = 11): super(TcRtdVarModelBox, self).__init__(parent) self.comBoxColumn = comBoxColumn def indexChange(self): sender = self.sender() index = sender.currentIndex() text = sender.itemText(index) name = self.parent().model.datas[sender.index[0]][3] TcRtdManage.editVarModel(name, text) self.parent().model.datas[sender.index[0]][sender.index[1]] = text class AnalogVarModelBox(ModbusVarModelBox): def __init__(self, parent=None, comBoxColumn = 10): super(AnalogVarModelBox, self).__init__(parent) self.comBoxColumn = comBoxColumn def indexChange(self): sender = self.sender() index = sender.currentIndex() text = sender.itemText(index) name = self.parent().model.datas[sender.index[0]][3] AnalogManage.editVarModel(name, text, ) self.parent().model.datas[sender.index[0]][sender.index[1]] = text class HartSimulateVarModelBox(ModbusVarModelBox): def __init__(self, parent=None, comBoxColumn = 9): super(HartSimulateVarModelBox, self).__init__(parent) self.comBoxColumn = comBoxColumn def indexChange(self): sender = self.sender() index = sender.currentIndex() text = sender.itemText(index) name = self.parent().model.datas[sender.index[0]][1] HartSimulateVarManage.editVarModel(name, text) self.parent().model.datas[sender.index[0]][sender.index[1]] = text