From 6b07e6ced446585d675594a8faaabbe037717613 Mon Sep 17 00:00:00 2001 From: yanganjie Date: Wed, 27 Aug 2025 14:11:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=A4=9A=E4=BD=99=E7=9A=84?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cood_forms_interface.py | 1235 --------------------------------------- 1 file changed, 1235 deletions(-) delete mode 100644 cood_forms_interface.py diff --git a/cood_forms_interface.py b/cood_forms_interface.py deleted file mode 100644 index 3caf21e..0000000 --- a/cood_forms_interface.py +++ /dev/null @@ -1,1235 +0,0 @@ -# coding:utf-8 -import sys -import os -from PySide6.QtCore import ( - Qt, - QSize, - QPoint, - QEvent, - Slot, - Signal, - QThread, - QMutex, - QWaitCondition, -) - -from PySide6.QtGui import QIcon, QColor, QAction - -from PySide6.QtWidgets import ( - QApplication, - QWidget, - QVBoxLayout, - QHBoxLayout, - QStackedWidget, - QMessageBox, - QLabel, - QFrame, - QTreeWidgetItem, - QTreeWidgetItemIterator, - QTableWidget, - QTableWidgetItem, - QListWidget, - QListWidgetItem, - QInputDialog, - QMenu, - QSizePolicy, - QDialog, -) - -from qfluentwidgets import ( - TableWidget, - HorizontalFlipView, - TabBar, - TabCloseButtonDisplayMode, - setTheme, - isDarkTheme, - Theme, - PushButton, - FluentIcon as FIF, - Dialog, -) - -from .gallery_interface import GalleryInterface -from ..common.translator import Translator -from ..common.style_sheet import StyleSheet - -import sqlite3 - - -class CoodFormsInterface(GalleryInterface): - """Cood Forms interface""" - - def __init__(self, parent=None): - t = Translator() - super().__init__(title=t.view, subtitle="CoordinateFormsWidget", parent=parent) - self.setObjectName("coodFormsInterface") - - # CoordinateFormsWidget - self.formsWidget = CoordinateFormsWidget(self) - card = self.addExampleCard( - title=self.tr(""), - widget=self.formsWidget, - sourcePath=".", - stretch=1, - ) - card.topLayout.setContentsMargins(6, 0, 6, 16) - - # 动态设置高度 - self.__connectMainWindowSignal() - - def __connectMainWindowSignal(self): - main_window = self.window() - if main_window: - self.onWindowHeightChanged(main_window.height() - 160) # 设置窗口初始高度 - main_window.heightChanged.connect(self.onWindowHeightChanged) - - def onWindowHeightChanged(self, new_height): - self.formsWidget.setFixedHeight(new_height - 160) - - -class FormDatabase: - def __init__(self, db_path): - self.db_path = db_path - self.conn = None # 数据库连接对象 - self.cursor = None # 游标对象 - - def init_database(self): - """初始化SQLite数据库, 不存在则创建form_info表和form_data表""" - try: - # 数据库文件路径(db目录下的forms.db) - os.makedirs( - os.path.dirname(self.db_path), exist_ok=True - ) # 确保数据库目录存在 - cursor = self.__connect() - - # 创建表单信息表 form_info - cursor.execute( - """ - CREATE TABLE IF NOT EXISTS form_info ( - form_id INTEGER PRIMARY KEY AUTOINCREMENT, - form_name TEXT NOT NULL UNIQUE, -- 表单名称唯一 - create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ) - """ - ) - - # 创建表单数据表form_data(存储每行数据) - cursor.execute( - """ - CREATE TABLE IF NOT EXISTS form_data ( - data_id INTEGER PRIMARY KEY AUTOINCREMENT, - form_id INTEGER NOT NULL, -- 关联表单信息表的 form_id - row_index INTEGER NOT NULL, -- 行号(从0开始) - x TEXT, - y TEXT, - z TEXT, - rx TEXT, - ry TEXT, - rz TEXT, - FOREIGN KEY (form_id) REFERENCES form_info(form_id) ON DELETE CASCADE, - UNIQUE(form_id, row_index) -- 确保一行数据只存一次 - ) - """ - ) - - # 自动更新表单修改时间 - # 更新表单修改时间的触发器 - cursor.execute( - """ - CREATE TRIGGER IF NOT EXISTS update_form_time - AFTER UPDATE ON form_data - FOR EACH ROW - BEGIN - UPDATE form_info SET update_time = CURRENT_TIMESTAMP WHERE form_id = NEW.form_id; - END - """ - ) - - # 提交,初始化数据库成功 - self.commit_and_close() - - except Exception as exc: - self.rollback_and_close() - raise exc - - def __connect(self): - """连接数据库并获取游标""" - if not self.conn: - self.conn = sqlite3.connect(self.db_path) - self.cursor = self.conn.cursor() - return self.cursor - - def commit_and_close(self): - """提交事务并关闭连接""" - if self.conn: - self.conn.commit() # 提交事务 - self.conn.close() # 关闭连接 - self.conn = None - self.cursor = None - - def rollback_and_close(self): - """回滚事务并关闭连接""" - if self.conn: - self.conn.rollback() # 回滚事务 - self.conn.close() # 关闭连接 - self.conn = None - self.cursor = None - - def check_form_exists(self, form_name): - """检查表单是否已经存在 - 成功返回form_id - 失败返回None - """ - try: - cursor = self.__connect() - cursor.execute( - "SELECT form_id FROM form_info WHERE form_name = ?", (form_name,) - ) - result = cursor.fetchone() - return result[0] if result else None - except Exception as exc: - self.rollback_and_close() - raise exc - - def delete_form_data(self, form_id): - """删除指定表单的所有数据""" - try: - cursor = self.__connect() - cursor.execute("DELETE FROM form_data WHERE form_id = ?", (form_id,)) - except Exception as exc: - self.rollback_and_close() - raise exc - - def add_new_form(self, form_name): - """新增表单 (返回新表单的form_id)""" - try: - cursor = self.__connect() - cursor.execute("INSERT INTO form_info (form_name) VALUES (?)", (form_name,)) - return cursor.lastrowid # 返回新插入的ID - except Exception as exc: - self.rollback_and_close() - raise exc - - def insert_form_data(self, form_id, data_rows): - """批量插入form_data表""" - try: - cursor = self.__connect() - for row in data_rows: - # row格式: [row_idx, x, y, z, rx, ry, rz] - cursor.execute( - """INSERT INTO form_data - (form_id, row_index, x, y, z, rx, ry, rz) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", - (form_id, row[0], row[1], row[2], row[3], row[4], row[5], row[6]), - ) - except Exception as exc: - self.rollback_and_close() - raise exc - - def get_all_form_id_name(self): - """获取所有form_id 和 对应的 form_name""" - try: - cursor = self.__connect() - cursor.execute( - "SELECT form_id, form_name FROM form_info ORDER BY create_time" - ) - # [(form_id1, form_name1), (form_id2, form_name2), ...] - form_id_name = cursor.fetchall() - return form_id_name - except Exception as exc: - self.rollback_and_close() - raise exc - - def get_row_idx_coordinate_list(self, form_id): - """根据form_id查询对应的表单数据 data_rows (包含行索引row_idx, x, y, z, rx, ry, rz)""" - try: - cursor = self.__connect() - cursor.execute( - "SELECT row_index, x, y, z, rx, ry, rz FROM form_data " - "WHERE form_id = ?", - (form_id,), - ) - # [(row_idx, x, y, z, rx, ry, rz), ...] - data_rows = cursor.fetchall() - return data_rows - except Exception as exc: - self.rollback_and_close() - raise exc - - def get_sorted_coordinate_list(self, form_name): - """ - form_name 等同于线名 - form_name 存在, 返回 [(x1, y1, z1, rx1, ry1, rz1), ......], 按照row_idx排序 - 不存在, 返回 None - """ - pass - - # 包含 坐标 和 对应的状态(运动类型?点位类型?) - def get_coordinate_state_list(self, form_name): - pass - - -# 数据库保存线程 -class DatabaseSaveThread(QThread): - # 信号1:需要用户确认是否覆盖数据库中的数据 - need_confirm_signal = Signal() - # 信号2:数据库操作结果 - result_signal = Signal(str) - - def __init__(self, db_path, form_name, data_rows): - super().__init__() - self.db_path = db_path - self.form_name = form_name - self.data_rows = data_rows # 主线程传递过来的UI数据 - self.user_choice = None # 存储用户选择(True=覆盖,False=取消) - self.mutex = QMutex() - self.condition = QWaitCondition() - - def run(self): - try: - db = FormDatabase(self.db_path) - - # 初始化数据库,至少执行一次 - db.init_database() - - # 检查form_name名的表单是否已经存在 - exist_form_id = db.check_form_exists(self.form_name) - - if exist_form_id is not None: - # 存在表单,发送信号给主线程, 向用户请求确认 - self.need_confirm_signal.emit() - - # 等待UI线程传入用户选择 - self.wait_for_user_choice() - - if not self.user_choice: - # 用户选择不覆盖,结束操作 - self.result_signal.emit(f"已取消,表单「{self.form_name}」未修改") - return - - # 用户选择覆盖:删除旧数据 - db.delete_form_data(exist_form_id) - # 插入新数据(复用exist_form_id) - db.insert_form_data(exist_form_id, self.data_rows) - self.result_signal.emit(f"表单「{self.form_name}」已覆盖并保存") - - else: - # 表单不存在:直接新增 - new_form_id = db.add_new_form(self.form_name) - db.insert_form_data(new_form_id, self.data_rows) - self.result_signal.emit(f"表单「{self.form_name}」已保存") - - # 操作完成,提交并关闭连接 - db.commit_and_close() - # db.rollback_and_close() - - except Exception as e: - # 数据库操作异常 - self.result_signal.emit(f"保存失败:{str(e)}") - - def wait_for_user_choice(self): - """阻塞线程,等待用户选择(条件变量)""" - self.mutex.lock() - # 等待,直到set_user_choice调用wakeAll() - self.condition.wait(self.mutex) - self.mutex.unlock() - - def set_user_choice(self, choice): - """UI线程调用: 设置用户选择并唤醒数据库保存操作线程""" - self.mutex.lock() - self.user_choice = choice - self.condition.wakeAll() # 唤醒等待的数据库保存操作线程 - self.mutex.unlock() - - -# 数据库读取操作线程 -class DatabaseReadThread(QThread): - # 信号1: 从form_info 读取所有的 id-name 发送 - # [(form_id1, form_name1),...] - form_id_name_signal = Signal(list) - - # 信号2: 从 from_data 读取所有的 选择的表单的数据发送 - # {form_name1: data_rows1, ...} - form_data_signal = Signal(dict) - - # 信号3: 状态信息 - state_signal = Signal(str) - - def __init__(self, db_path): - super().__init__() - self.db_path = db_path - self.selected_form_id_name = None # 选择的 表单id-name - - # 初始化信号量 - self.mutex = QMutex() - self.condition = QWaitCondition() - - def run(self): - try: - db = FormDatabase(self.db_path) - - # 初始化数据库 - db.init_database() - - # 从form_info表 查询所有的 form_id 和 form_name - all_form_id_name = db.get_all_form_id_name() - self.form_id_name_signal.emit(all_form_id_name) - - # 等待用户选择 想要查看的 form - self.wait_for_selection() - - if not self.selected_form_id_name: - self.state_signal.emit("未选择任何表单") - return - - form_date_dict = dict() - for form_id, form_name in self.selected_form_id_name: - data_rows = db.get_row_idx_coordinate_list(form_id) - form_date_dict[form_name] = data_rows - - self.form_data_signal.emit(form_date_dict) - db.commit_and_close() - - except Exception as exc: - # 数据库异常 - self.state_signal.emit(f"读取失败:{str(exc)}") - - def wait_for_selection(self): - self.mutex.lock() - self.condition.wait(self.mutex) - self.mutex.unlock() - - def set_selected_forms(self, selected_form_id_name): - """UI线程调用, 设置选择的 表单的 id-name""" - self.mutex.lock() - self.selected_form_id_name = selected_form_id_name - self.condition.wakeAll() - self.mutex.unlock() - - -class CoordinateTableWidget(QWidget): - # 选中行更新信号 - update_table_signal = Signal() - - # 移动到选中行的 坐标的信号 - move_to_coodinate_signal = Signal(list) - - def __init__( - self, parent=None, form_name="default", hideVHeader=False, initRowCount=True - ): - super().__init__(parent=parent) - # 保存当前表单名,根据form_name设置行名 - self.form_name = form_name - self.hideVHeader = hideVHeader # 是否隐藏行名 - self.initRowCount = initRowCount # 是否需要初始的行数 - self.setObjectName("CoordinateTableWidget") - - # 主布局 - self.mainLayout = QVBoxLayout(self) - self.mainLayout.setContentsMargins(30, 30, 30, 30) - self.mainLayout.setSpacing(20) - - # 创建表格 - self.createTable() - - # 创建按钮区域 - self.createButtons() - - # 状态标签 - self.status_label = QLabel("未选中任何行") - self.mainLayout.addWidget(self.status_label) - - # 应用主题样式 - self.applyThemeStyle() - # StyleSheet.VIEW_INTERFACE.apply(self) - - # 初始化数据(行名会关联表单名) - self.initTableData() - - # 更新表单名(供外部调用,修改后同步更新行名) - def update_form_name(self, new_name): - self.form_name = new_name - self.updateRowHeaders() # 同步更新所有行名 - - def createTable(self): - self.table = TableWidget(self) - self.table.setBorderVisible(True) - self.table.setBorderRadius(8) - self.table.setWordWrap(False) - - # 表单的初始列数和行数 - if self.initRowCount: - self.table.setRowCount(5) # 表的行数 - - self.table.setColumnCount(6) # 表的列数 - - self.table.setHorizontalHeaderLabels(["x", "y", "z", "rx", "ry", "rz"]) - self.table.horizontalHeader().setStyleSheet( - "QHeaderView::section {font-size: 19px; font-weight: bold;}" - ) - self.table.verticalHeader().setStyleSheet( - "QHeaderView::section {font-size: 14px; font-weight: bold;}" - ) - - if self.hideVHeader: - self.table.verticalHeader().hide() # 隐藏行标题(行名) - - for i in range(6): - self.table.horizontalHeader().setSectionResizeMode( - i, self.table.horizontalHeader().ResizeMode.Stretch - ) - - self.table.setSelectionBehavior(self.table.SelectionBehavior.SelectRows) - self.table.setSelectionMode(self.table.SelectionMode.ExtendedSelection) - self.table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) - self.table.customContextMenuRequested.connect(self.showContextMenu) - self.table.itemSelectionChanged.connect(self.onCellSelected) - self.mainLayout.addWidget(self.table, stretch=1) - - def createButtons(self): - btnLayout = QHBoxLayout() - btnLayout.setSpacing(15) - - self.addBtn = PushButton("添加行") - self.addBtn.clicked.connect(self.addNewRow) - btnLayout.addWidget(self.addBtn) - - self.deleteBtn = PushButton("删除行") - self.deleteBtn.clicked.connect(self.deleteSelectedRow) - btnLayout.addWidget(self.deleteBtn) - - self.setBtn = PushButton("设置点位") - self.setBtn.clicked.connect(self.setSelectedRow) - btnLayout.addWidget(self.setBtn) - - self.resetBtn = PushButton("重置点位") - self.resetBtn.clicked.connect(self.resetSelectedRow) - btnLayout.addWidget(self.resetBtn) - - # 移动按钮 - self.readBtn = PushButton("移动") - self.readBtn.setIcon(FIF.RIGHT_ARROW.icon()) # FOLDER - self.readBtn.clicked.connect(self.moveToSelectedCoodinate) - btnLayout.addWidget(self.readBtn) - - # 保存按钮 - self.saveBtn = PushButton("保存数据") - self.saveBtn.setIcon(FIF.SAVE.icon()) # 使用保存图标 - self.saveBtn.clicked.connect(self.saveToDatabase) # 关联保存函数 - btnLayout.addWidget(self.saveBtn) - - self.mainLayout.addLayout(btnLayout) - - # 数据保存到数据库,获取数据时调用 - def get_ui_data(self): - """从表格UI中获取数据 [[行索引row_idx, x, y, z, rx, ry, rz], ......]""" - row_count = self.table.rowCount() - # column_count = self.table.columnCount() - data_rows = [] - for row_idx in range(row_count): - row_data = [row_idx] # 先保存行索引 - is_valid_row = True # 标记当前行是否完全有效 - - # 这里只有前6列是 x, y, rx, ry, rz - # 目前 从ui获取的 需要保存到数据库的 只有 x, y, z, rx, ry, rz - # 故这里为 0到5 - for col_idx in range(6): - item = self.table.item(row_idx, col_idx) - - # 数据合法性和完整性判定(检查): 保证获取到的是有效数据 - # 对于非法数据 和 不完整数据,不保存在数据库 - # 1、判断填写的 x, y, z, rx, ry, rz中是否有为空的坐标 - if not item or not item.text().strip(): - is_valid_row = False - break # 跳过这一行数据,保存下一行数据 - - # 2、检查填写的坐标是否都为数字 - coord_str = item.text().strip() - try: - coord_num = float(coord_str) # 尝试转换为数字 - except ValueError: - is_valid_row = False - break # 跳过这一行数据,保存下一行数据 - - # 保存单个坐标,浮点类型 - row_data.append(coord_num) - - # 放入这一行的数据 行索引row_idx, x, y, z, rx, ry, rz - if is_valid_row: # 有效,才保存到数据库 - data_rows.append(row_data) - - return data_rows - - def showConfirmDialog(self): - """显示覆盖确认对话框""" - msg_box = QMessageBox(self) - msg_box.setWindowTitle("覆盖确认") - msg_box.setText(f"表单「{self.form_name}」已存在,是否覆盖?") - msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) - msg_box.setDefaultButton(QMessageBox.No) - msg_box.setStyleSheet("QMessageBox QLabel { color: black !important; }") - reply = msg_box.exec() - - # 将用户选择(Yes=True,No=False)传给线程,唤醒数据库线程继续执行 - if ( - hasattr(self, "db_thread") - and self.db_thread is not None - and not self.db_thread.isFinished() - ): - self.db_thread.set_user_choice(reply == QMessageBox.Yes) - - def applyThemeStyle(self): - if isDarkTheme(): - self.setStyleSheet( - """ - #CoordinateTableWidget {background-color: rgb(30, 30, 30);} - #CoordinateTableWidget QLabel {font-size: 16px; color: rgb(240, 240, 240);} - #CoordinateTableWidget QTableWidget {color: rgb(240, 240, 240); gridline-color: rgb(60, 60, 60);} - #CoordinateTableWidget QHeaderView::section { - background-color: rgb(40, 40, 40); - color: rgb(200, 200, 200); - border: 1px solid rgb(60, 60, 60); - padding: 5px; - } - """ - ) - else: - self.setStyleSheet( - """ - #CoordinateTableWidget {background-color: rgb(245, 245, 245);} - #CoordinateTableWidget QLabel {font-size: 16px; color: rgb(60, 60, 60);} - #CoordinateTableWidget QTableWidget {color: rgb(60, 60, 60); gridline-color: rgb(200, 200, 200);} - #CoordinateTableWidget QHeaderView::section { - background-color: rgb(230, 230, 230); - color: rgb(60, 60, 60); - border: 1px solid rgb(200, 200, 200); - padding: 5px; - } - """ - ) - - # 初始化表 - def initTableData(self): - for row in range(self.table.rowCount()): - # 行名格式:表单名_行号 - header_item = QTableWidgetItem(f"{self.form_name}_{row+1}") - header_item.setTextAlignment(Qt.AlignCenter) - self.setThemeTextColor(header_item) - self.table.setVerticalHeaderItem(row, header_item) - - for col in range(self.table.columnCount()): - item = QTableWidgetItem("") - item.setTextAlignment(Qt.AlignCenter) - self.setThemeTextColor(item) - self.table.setItem(row, col, item) - - def setThemeTextColor(self, item): - item.setForeground(Qt.white if isDarkTheme() else Qt.black) - - def showNoSelectWarning(self, msg): - msg_box = QMessageBox(self) - msg_box.setIcon(QMessageBox.Warning) - msg_box.setWindowTitle("提示") - msg_box.setText(msg) - msg_box.setStandardButtons(QMessageBox.Ok) - msg_box.setStyleSheet("QLabel{color: black;}") - msg_box.exec() - - def update_table_data(self, positionList=None): - selectedRows = self.getSelectedRows() - for row in selectedRows: - for col in range(self.table.columnCount()): - item = QTableWidgetItem( - str(positionList[col]) - if (positionList and 0 <= col < len(positionList)) - else "-9999" - ) - item.setTextAlignment(Qt.AlignCenter) - self.setThemeTextColor(item) - self.table.setItem(row, col, item) - - if positionList: - self.status_label.setText(f"已将选中行设置为{positionList}") - else: - self.status_label.setText( - "已将选中行设置为[-9999, -9999, -9999, -9999, -9999, -9999]" - ) - - """ - ======================================槽函数================================= - """ - - # 添加新行时,行名同样关联当前表单名 - def addNewRow(self): - # 检查有没有选中行 - # 如果有选中行,在选中行的最下方新增一行 - # 没有选中行,直接在最下方新增一行 - selected_rows = self.getSelectedRows() - insert_pos = max(selected_rows) + 1 if selected_rows else self.table.rowCount() - - # 新增一行 - self.table.insertRow(insert_pos) - - # 更新行名 - self.updateRowHeaders() - - # 初始化单元格 - for col in range(self.table.columnCount()): - item = QTableWidgetItem("") - item.setTextAlignment(Qt.AlignCenter) - self.setThemeTextColor(item) - self.table.setItem(insert_pos, col, item) - - self.status_label.setText(f"已添加第 {insert_pos+1} 行") - - # 删除行 - def deleteSelectedRow(self): - selectedRows = self.getSelectedRows() - if not selectedRows: - self.showNoSelectWarning("请先选中再删除!") - return - - msg_box = QMessageBox(self) - msg_box.setWindowTitle("提示") - rowsStr = ", ".join(map(str, [row + 1 for row in selectedRows])) - msg_box.setText(f"确定要删除选中的第 {rowsStr} 行吗?") - msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) - msg_box.setStyleSheet("QLabel{color: black;}") - if msg_box.exec() != QMessageBox.Yes: - return - - for row in sorted(selectedRows, reverse=True): - self.table.removeRow(row) - self.updateRowHeaders() # 删除后重新编号行名 - self.status_label.setText("已删除选中行") - - # 设置选中行 - def setSelectedRow(self): - selectedRows = self.getSelectedRows() - if not selectedRows: - self.showNoSelectWarning("请先选中再设置点位!") - return - - # 选中行默认设置 - self.update_table_data() - - # 发送选中行设置(更新)信号 - self.update_table_signal.emit() - - # 重置点位 - def resetSelectedRow(self): - selectedRows = self.getSelectedRows() - if not selectedRows: - self.showNoSelectWarning("请先选中再重置!") - return - - for row in selectedRows: - for col in range(self.table.columnCount()): - item = QTableWidgetItem("") - item.setTextAlignment(Qt.AlignCenter) - self.setThemeTextColor(item) - self.table.setItem(row, col, item) - - self.status_label.setText("已重置选中行") - - # 移动 - def moveToSelectedCoodinate(self): - selectedRows = self.getSelectedRows() - if not selectedRows: - self.showNoSelectWarning("请先选中再移动!") - return - - # 得到选中行的 点位(坐标) 列表,然后发送 - sortedRowIdList = sorted(selectedRows) # 将选中行的行索引进行排序 - coordinate_rows = list() - for row_idx in sortedRowIdList: - row_coordinate = list() # 保存这一行的坐标 - for col_idx in range(6): # 0-5 ,这些列为坐标 x, y, z, rx, ry, rz - item = self.table.item(row_idx, col_idx) - - # 坐标合法性检查 - # 1、判断填写的 x, y, z, rx, ry, rz中是否为空 - if not item or not item.text().strip(): - self.status_label.setText( - f"移动失败:第{row_idx+1}行有坐标为空,请检查!!!" - ) - return - - # 2、检查填写的坐标是否都为数字 - coord_str = item.text().strip() - try: - coord_num = float(coord_str) # 尝试转换为数字 - except ValueError: - self.status_label.setText( - f"移动失败:第{row_idx+1}行第{col_idx+1}列坐标不是有效数字,请检查!!!" - ) - return - # 放入单个坐标(浮点类型),如 x - row_coordinate.append(coord_num) - - # 放入这一行的坐标 - # 最终结果:[[x1, y1, z1, rx1, ry1, rz1], ......] - # x1等都为浮点类型 - coordinate_rows.append(row_coordinate) - # 发送移动到这些坐标的信号 - self.move_to_coodinate_signal.emit(coordinate_rows) - - # 保存数据 - def saveToDatabase(self): - # 检查是否已有正在运行的数据库线程 - if hasattr(self, "db_thread") and self.db_thread and self.db_thread.isRunning(): - self.status_label.setText("有未完成的数据库保存操作, 请稍后再试......") - return - - # 获取UI数据 - data_rows = self.get_ui_data() - if not data_rows: - self.status_label.setText("没有数据可以保存, 请检查数据有效性!!!") - return - - # 创建数据库操作线程并启动 - self.db_thread = DatabaseSaveThread( - db_path="db/forms.db", form_name=self.form_name, data_rows=data_rows - ) - - # 连接信号:线程需要确认时,UI线程弹对话框 - self.db_thread.need_confirm_signal.connect(self.showConfirmDialog) - # 连接信号:数据库操作线程返回最终结果,UI线程更新 状态标签 - self.db_thread.result_signal.connect(self.status_label.setText) - - # 启动线程 - self.db_thread.start() - - def showContextMenu(self, position): - """显示右键菜单(复制行数据)""" - if not self.getSelectedRows(): - return - - menu = QMenu() - copyAction = QAction("复制此行数据", self) - copyAction.triggered.connect(self.copyRowData) - menu.addAction(copyAction) - menu.exec(self.table.mapToGlobal(position)) - - def copyRowData(self): - """复制选中行数据到剪贴板""" - selectedRows = self.getSelectedRows() - if not selectedRows: - return - - # 目前只支持复制一行数据, - # 选中多行,默认复制选中的多行中行数最小的那行 - row = selectedRows[0] - rowData = [] - for col in range(self.table.columnCount()): - item = self.table.item(row, col) - rowData.append(item.text() if item else "") - - dataStr = ", ".join(rowData) - clipboard = QApplication.clipboard() - clipboard.setText(dataStr) - self.status_label.setText(f"已复制第 {row + 1} 行的数据: {dataStr}") - - def getSelectedRows(self): - return list(set(item.row() for item in self.table.selectedItems())) - - def onCellSelected(self): - selectedRows = self.getSelectedRows() - if selectedRows: - self.status_label.setText( - f"选中行:{', '.join(str(row + 1) for row in selectedRows)}" - ) - else: - self.status_label.setText("未选中任何行") - - # 更新行名 - def updateRowHeaders(self): - for row in range(self.table.rowCount()): - # 行名随表单名动态变化:表单名_行号 - header_item = QTableWidgetItem(f"{self.form_name}_{row+1}") - header_item.setTextAlignment(Qt.AlignCenter) - self.setThemeTextColor(header_item) - self.table.setVerticalHeaderItem(row, header_item) - - -class CoordinateFormsWidget(QWidget): - # 表单中 更新信号 (用于更新表单中的选中行数据) - form_update_signal = Signal(CoordinateTableWidget) - - # 表单中 移动到坐标的信号 (用于机器臂的移动) - form_move_signal = Signal(list) - - def __init__(self, parent=None): - super().__init__(parent) - self.setWindowTitle("Coordinate Forms") - self.setObjectName("CoordinateFormsWidget") - self.resize(1100, 750) - self.formTotalCnt = 0 # 创建过的总表单数 - - # 主布局 - self.mainLayout = QVBoxLayout(self) - self.mainLayout.setContentsMargins(0, 0, 0, 0) - self.mainLayout.setSpacing(0) - - # 标签栏(支持双击修改名称) - self.tabBar = TabBar(self) - self.tabBar.setMovable(True) - self.tabBar.setTabMaximumWidth(200) - self.tabBar.setCloseButtonDisplayMode(TabCloseButtonDisplayMode.ON_HOVER) - - # 绑定槽函数 - self.tabBar.tabAddRequested.connect(self.addNewForm) - self.tabBar.tabCloseRequested.connect(self.closeForm) - self.tabBar.currentChanged.connect(self.switchForm) - - # 给tabBar安装事件过滤器,用于监听双击事件 - self.tabBar.installEventFilter(self) - self.mainLayout.addWidget(self.tabBar) - - # 表单内容区 - self.formStack = QStackedWidget(self) - self.formStack.setObjectName("CoordinateFormStack") - self.mainLayout.addWidget(self.formStack) - - # 读取按钮 - self.readBtn = PushButton("读取数据") - self.readBtn.setIcon(FIF.LIBRARY.icon()) - self.readBtn.clicked.connect(self.readFromDatabase) - self.mainLayout.addWidget(self.readBtn) - - # 初始化第一个表单 - self.initFirstForm() - - # 初始化样式,应用主题样式 - self.applyThemeStyle() - - def applyThemeStyle(self): - """根据当前主题应用样式""" - if isDarkTheme(): - # 深色主题样式 - self.setStyleSheet( - """ - #CoordinateFormStack { - background-color: rgb(30, 30, 30); /* 堆叠窗口背景设为深色 */ - } - #CoordinateFormsWidget { - background-color: rgb(30, 30, 30); /* 主窗口背景设为深色 */ - } - """ - ) - else: - # 浅色主题样式 - self.setStyleSheet( - """ - #CoordinateFormStack { - background-color: rgb(245, 245, 245); /* 堆叠窗口背景设为浅色 */ - } - #CoordinateFormsWidget { - background-color: rgb(245, 245, 245); /* 主窗口背景设为浅色 */ - } - """ - ) - - # 事件过滤器:监听TabBar的鼠标双击事件 - def eventFilter(self, obj, event): - # 只处理TabBar的双击事件 - if obj == self.tabBar and event.type() == QEvent.MouseButtonDblClick: - if event.button() == Qt.LeftButton: # 左键双击 - # 获取双击位置(相对于TabBar) - pos = event.position().toPoint() - # 判断双击位置是否在标签区域内 - tab_region = self.tabBar.tabRegion() - if tab_region.contains(pos): - # 计算双击的是哪个标签 - index = self.getTabIndexByPos(pos) - if index != -1: - self.renameForm(index) # 调用修改表单名的方法 - return True # 事件已处理 - return super().eventFilter(obj, event) - - # 根据点击位置计算对应的标签索引 - def getTabIndexByPos(self, pos): - """通过坐标判断双击的是哪个标签""" - x = 0 - for i in range(self.tabBar.count()): - # 获取第i个标签的宽度 - tab_rect = self.tabBar.tabRect(i) - if x <= pos.x() < x + tab_rect.width(): - return i # 返回标签索引 - x += tab_rect.width() - return -1 # 未命中任何标签 - - # 初始化第一个表单 - def initFirstForm(self): - self.formTotalCnt += 1 # 1 - firstFormName = f"default" - formKey = f"form_{self.formTotalCnt}" # 唯一标识, form_1 - - # 创建表单实例时传入表单名 - newForm = CoordinateTableWidget(form_name=firstFormName, parent=self) - newForm.update_table_signal.connect(self.handleFormUpdate) - newForm.move_to_coodinate_signal.connect(self.form_move_signal) - - self.tabBar.addTab(routeKey=formKey, text=firstFormName, icon=FIF.TILES.icon()) - self.formStack.addWidget(newForm) - - # 添加新表单 - def addNewForm(self): - """添加新表单(带自定义名称输入)""" - self.formTotalCnt += 1 - formCount = self.tabBar.count() - defaultName = f"default{formCount}" - - # 弹出输入框让用户输入表单名 - # formName, ok = QInputDialog.getText( - # self, "创建新表单名称", "请输入新表单的名称:", text=defaultName - # ) - - formName, ok = QInputDialog.getText( - self, "请输入新创建的表单名", "", text=defaultName - ) - - # 用户取消,不添加新表单 - if not ok: - return - - # 用户输入为空,使用默认名称 - if not formName.strip(): - formName = defaultName - - formKey = f"form_{self.formTotalCnt}" # 唯一标识 - - # 创建表单实例时传入表单名 - newForm = CoordinateTableWidget(form_name=formName, parent=self) - newForm.update_table_signal.connect(self.handleFormUpdate) - newForm.move_to_coodinate_signal.connect(self.form_move_signal) - - self.tabBar.addTab(routeKey=formKey, text=formName, icon=FIF.TILES.icon()) - self.formStack.addWidget(newForm) - - # 切换到新创建的表单, 并同步内容 - self.tabBar.setCurrentIndex(self.tabBar.count() - 1) - self.tabBar.currentChanged.emit(self.tabBar.currentIndex()) - - # 关闭表单 - def closeForm(self, index): - # if self.tabBar.count() <= 1: # 必须保留一个表单 - # msg_box = QMessageBox(self) - # msg_box.setIcon(QMessageBox.Warning) - # msg_box.setWindowTitle("提示") - # msg_box.setText("至少保留一个表单") - # msg_box.setStandardButtons(QMessageBox.Ok) - # msg_box.setStyleSheet("QMessageBox QLabel { color: black !important; }") - # msg_box.exec() - # return - - self.tabBar.removeTab(index) - formToRemove = self.formStack.widget(index) - self.formStack.removeWidget(formToRemove) - formToRemove.deleteLater() - - def switchForm(self, index): - if 0 <= index < self.formStack.count(): - self.formStack.setCurrentIndex(index) - - # 更新表单名 - def renameForm(self, index): - """双击标签修改表单名,并同步更新行名""" - if index < 0 or index >= self.tabBar.count(): - return - - # 获取当前表单名作为默认值 - currentName = self.tabBar.tabText(index) - - # 弹出输入框修改名称 - # newName, ok = QInputDialog.getText( - # self, "修改表单名称", "请输入新的表单名称:", text=currentName - # ) - newName, ok = QInputDialog.getText( - self, "请输入修改后的表单名", "", text=currentName - ) - if not ok or not newName.strip(): - return # 取消或空输入则不修改 - - # 更新标签栏显示的名称 - self.tabBar.setTabText(index, newName) - - # 获取对应的表单实例,更新表单并同步更新行名 - formWidget = self.formStack.widget(index) - if isinstance(formWidget, CoordinateTableWidget): - formWidget.update_form_name(newName) # 同步更新行名 - - # 处理表单选中行更新 - def handleFormUpdate(self): - sender_form = self.sender() - if isinstance(sender_form, CoordinateTableWidget): - self.form_update_signal.emit(sender_form) - - def readFromDatabase(self): - """启动读取线程,处理表单选择和数据生成""" - # 检查是否已有运行中的读取线程 - if ( - hasattr(self, "read_thread") - and self.read_thread - and self.read_thread.isRunning() - ): - # self.status_label.setText("有未完成的数据库读取操作,请稍后再试......") - return - - # 创建读取线程 - self.read_thread = DatabaseReadThread(db_path="db/forms.db") - - # bind - # 连接信号:接收所有表单名,显示选择页面 - self.read_thread.form_id_name_signal.connect(self.showFormSelection) - # 连接信号:接收表单数据,生成新表单 - self.read_thread.form_data_signal.connect(self.generateNewForms) - # 连接信号:状态提示 (数据库错误等) - self.read_thread.state_signal.connect(self.showDatabaseReadWarning) - - # 启动线程 - self.read_thread.start() - - def showDatabaseReadWarning(self, msg): - msg_box = QMessageBox(self) - msg_box.setIcon(QMessageBox.Warning) - msg_box.setWindowTitle("提示") - msg_box.setText(msg) - msg_box.setStandardButtons(QMessageBox.Ok) - msg_box.setStyleSheet("QLabel{color: black;}") - msg_box.exec() - - def showFormSelection(self, all_form_id_name): - """显示多选表单选择页面 (UI线程处理)""" - if not all_form_id_name: - # self.status_label.setText("数据库为空, 没有任何表单") - self.read_thread.set_selected_forms(list()) - return - - # 提取表单名(用于列表显示) - form_names = [name for (_, name) in all_form_id_name] - - # 创建多选列表对话框 - dialog = QDialog(self) - - # 设置对话框宽高 - dialog.resize(400, 300) # 可根据需要调整数值 - - dialog.setWindowTitle("选择表单") - layout = QVBoxLayout(dialog) - list_widget = QListWidget() - list_widget.setSelectionMode(QListWidget.ExtendedSelection) # 支持多选 - - # 设置列表样式 - list_widget.setStyleSheet( - """ - QListWidget { - font-size: 14px; /* 字体大小 */ - padding: 5px; /* 内边距 */ - } - QListWidget::item { - height: 30px; /* 行高 */ - } - """ - ) - - # 表单名添加到列表 - list_widget.addItems(form_names) - layout.addWidget(list_widget) - - # 确认/取消按钮 - btn_layout = QHBoxLayout() - confirm_btn = PushButton("确认") - confirm_btn.setStyleSheet("color: black;") - cancel_btn = PushButton("取消") - cancel_btn.setStyleSheet("color: black;") - btn_layout.addWidget(confirm_btn) - btn_layout.addWidget(cancel_btn) - layout.addLayout(btn_layout) - - # 确认按钮逻辑:获取选中的表单ID和名称 - def onDialogConfirm(): - selected_items = list_widget.selectedItems() - if not selected_items: # 没有选择,则退出读取 - # self.read_thread.set_selected_forms(list()) - # QMessageBox.warning(dialog, "提示", "请至少选择一个表单") - dialog.reject() # 按取消处理,在onDialogClosed中处理 - return - - # 映射选中的名称到 (form_id, form_name) - selected = [ - all_form_id_name[i] - for i in range(len(all_form_id_name)) - if list_widget.item(i).isSelected() - ] - self.read_thread.set_selected_forms(selected) # 通知线程用户选择 - dialog.accept() - - # 对话框关闭时的统一处理 - def onDialogClosed(result): - # 如果不是用户确认(result != QDialog.Accepted),则通知线程取消 - if result != QDialog.Accepted: - if ( - hasattr(self, "read_thread") - and self.read_thread - and not self.read_thread.isFinished() - ): - self.read_thread.set_selected_forms(list()) - - # bind - confirm_btn.clicked.connect(onDialogConfirm) - cancel_btn.clicked.connect(dialog.reject) - dialog.finished.connect(onDialogClosed) - - # 显示表单选择框 - dialog.exec() - - # 由读取的数据生成新表单 - def generateNewForms(self, form_data): - """根据读取到的数据生成新表单(UI线程处理)""" - # form_data格式:{form_name: [(row_idx, x, y, z, rx, ry, rz), ...], ...} - for form_name, data_rows in form_data.items(): - # 创建新表单 - self.formTotalCnt += 1 # 创建的总的表单数加一 - new_form = CoordinateTableWidget( - form_name=f"{form_name}", parent=self, initRowCount=False - ) - new_form.update_table_signal.connect(self.handleFormUpdate) - new_form.move_to_coodinate_signal.connect(self.form_move_signal) - - # 填充数据到新表单 - for row_idx, x, y, z, rx, ry, rz in data_rows: - # 如果数单行数超过表格行数,自动添加行 - while row_idx >= new_form.table.rowCount(): - new_form.addNewRow() - - # 填充单元格 - new_form.table.setItem(row_idx, 0, QTableWidgetItem(x)) - new_form.table.setItem(row_idx, 1, QTableWidgetItem(y)) - new_form.table.setItem(row_idx, 2, QTableWidgetItem(z)) - new_form.table.setItem(row_idx, 3, QTableWidgetItem(rx)) - new_form.table.setItem(row_idx, 4, QTableWidgetItem(ry)) - new_form.table.setItem(row_idx, 5, QTableWidgetItem(rz)) - # 将新表单添加到UI - form_key = f"form_{self.formTotalCnt}" # 唯一标识 - self.tabBar.addTab( - routeKey=form_key, text=f"{form_name}", icon=FIF.TILES.icon() - ) - self.formStack.addWidget(new_form) - - -# 更新表单中选中行数据 x,y,z,rx,ry,rz -# def test(form_obj: CoordinateTableWidget): -# form_obj.update_table_data([666, 666, 666, 666, 666, 666]) - -# 移动 - - -# if __name__ == "__main__": -# app = QApplication(sys.argv) -# setTheme(Theme.DARK) -# window = CoordinateFormsWidget() -# window.form_update_signal.connect(test) -# window.show() -# sys.exit(app.exec())