# -*- coding: utf-8 -*- """ Copyright (c) 2025 - 2026 KenanZhu. All rights reserved. This software is provided "as is", without any warranty of any kind. You may use, modify, and distribute this file under the terms of the MIT License. See the LICENSE file for details. """ import os import sys from PySide6.QtCore import ( Qt, Signal, Slot, QTime, QDate, QDir, QFileInfo ) from PySide6.QtWidgets import ( QDialog, QWidget, QLineEdit, QMessageBox, QFileDialog, QTreeWidgetItem, QMenu, QInputDialog ) from PySide6.QtGui import ( QCloseEvent, QAction ) from utils.ConfigReader import ConfigReader from utils.ConfigWriter import ConfigWriter from gui.resources.ui.Ui_ALConfigWidget import Ui_ALConfigWidget from gui.ALSeatMapSelectDialog import ALSeatMapSelectDialog from gui.ALSeatMapTable import ALSeatMapTable from gui.ALUserTreeWidget import ALUserTreeWidget, ALUserTreeItemType class ALConfigWidget(QWidget, Ui_ALConfigWidget): configWidgetIsClosed = Signal(dict) def __init__( self, parent = None, config_paths = { "run": "", "user": "" } ): super().__init__(parent) self.__config_paths = config_paths self.__config_data = {"run": {}, "user": {}} self.setupUi(self) self.modifyUi() self.connectSignals() self.initlizeFloorRoomMap() self.initlizeDefaultConfigPaths() if not self.initlizeConfigs(): self.close() def modifyUi( self ): self.setWindowFlags(Qt.WindowType.Window) # replace the treewidget with ALUserTreeWidget self.UserTreeWidget.setParent(None) self.UserTreeWidget.deleteLater() self.UserTreeWidget = ALUserTreeWidget() self.UserListLayout.insertWidget(0, self.UserTreeWidget) self.UserTreeWidget.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.UserTreeWidget.customContextMenuRequested.connect(self.onUserTreeWidgetContextMenu) self.initlizeFloorRoomMap() self.initilizeUserInfoWidget() def connectSignals( self ): self.ShowPasswordCheckBox.clicked.connect(self.onShowPasswordCheckBoxChecked) self.FloorComboBox.currentIndexChanged.connect(self.onFloorComboBoxCurrentIndexChanged) self.SelectSeatsButton.clicked.connect(self.onSelectSeatsButtonClicked) self.UserTreeWidget.currentItemChanged.connect(self.onUserTreeWidgetCurrentItemChanged) self.UserTreeWidget.itemChanged.connect(self.onUserTreeWidgetItemChanged) self.AddUserButton.clicked.connect(self.onAddUserButtonClicked) self.DelUserButton.clicked.connect(self.onDelUserButtonClicked) self.BrowseBrowserDriverButton.clicked.connect(self.onBrowseBrowserDriverButtonClicked) self.BrowseCurrentRunConfigButton.clicked.connect(self.onBrowseCurrentRunConfigButtonClicked) self.BrowseCurrentUserConfigButton.clicked.connect(self.onBrowseCurrentUserConfigButtonClicked) self.BrowseExportRunConfigButton.clicked.connect(self.onBrowseExportRunConfigButtonClicked) self.BrowseExportUserConfigButton.clicked.connect(self.onBrowseExportUserConfigButtonClicked) self.ExportConfigButton.clicked.connect(self.onExportConfigButtonClicked) self.NewConfigButton.clicked.connect(self.onNewConfigButtonClicked) self.LoadConfigButton.clicked.connect(self.onLoadConfigButtonClicked) self.ConfirmButton.clicked.connect(self.onConfirmButtonClicked) self.CancelButton.clicked.connect(self.onCancelButtonClicked) def showEvent( self, event ): result = super().showEvent(event) screen_rect = self.screen().geometry() target_pos = self.parent().geometry().center() target_pos.setX(target_pos.x() - self.width()//2) target_pos.setY(target_pos.y() - self.height()//2) if target_pos.x() < 0: target_pos.setX(0) if target_pos.x() + self.width() > screen_rect.width(): target_pos.setX(screen_rect.width() - self.width()) if target_pos.y() < 0: target_pos.setY(0) if target_pos.y() + self.height() > screen_rect.height(): target_pos.setY(screen_rect.height() - self.height()) self.move(target_pos) return result def closeEvent( self, event: QCloseEvent ): self.configWidgetIsClosed.emit(self.__config_paths) super().closeEvent(event) def initlizeFloorRoomMap( self ): self.__floor_map = { "2": "二层", "3": "三层", "4": "四层", "5": "五层" } self.__room_map = { "1": "二层内环", "2": "二层西区", "3": "三层内环", "4": "三层外环", "5": "四层内环", "6": "四层外环", "7": "四层期刊", "8": "五层考研" } self.__floor_rmap = { v: k for k, v in self.__floor_map.items() } self.__room_rmap = { v: k for k, v in self.__room_map.items() } self.__floor_room_map = { "二层": ["二层内环", "二层西区"], "三层": ["三层内环", "三层外环"], "四层": ["四层内环", "四层外环", "四层期刊"], "五层": ["五层考研"] } def initlizeDefaultConfigPaths( self ): executable_path = sys.executable executable_dir = QFileInfo(executable_path).absoluteDir() self.__default_config_paths = { "user": QDir.toNativeSeparators(executable_dir.absoluteFilePath("user.json")), "run": QDir.toNativeSeparators(executable_dir.absoluteFilePath("run.json")) } def initlizeConfigToWidget( self, which: str, config_data: dict ): if which == "run": self.setRunConfigToWidget(config_data) self.CurrentRunConfigEdit.setText(self.__config_paths["run"]) elif which == "user": self.initilizeUserInfoWidget() self.setUsersToTreeWidget(config_data) self.CurrentUserConfigEdit.setText(self.__config_paths["user"]) def initlizeConfig( self, which: str ) -> bool: msg = "" # no use for now is_success = True if which == "run": run_config_path = self.__config_paths[which] if not os.path.exists(run_config_path): self.__config_data[which] = self.defaultRunConfig() self.__config_paths[which] = self.__default_config_paths[which] if self.saveRunConfig(self.__config_paths[which], self.__config_data[which]): msg += f"运行配置文件已初始化, 文件路径: \n{self.__config_paths[which]}\n" else: is_success = False else: self.__config_data[which] = self.loadRunConfig(run_config_path) if self.__config_data[which] is None: is_success = False elif which == "user": user_config_path = self.__config_paths[which] if not os.path.exists(user_config_path): self.__config_data[which] = self.defaultUserConfig() self.__config_paths[which] = self.__default_config_paths[which] if self.saveUserConfig(self.__config_paths[which], self.__config_data[which]): msg += f"用户配置文件已初始化, 文件路径: \n{self.__config_paths[which]}\n" else: is_success = False else: self.__config_data[which] = self.loadUserConfig(user_config_path) if self.__config_data[which] is None: is_success = False return is_success def initlizeConfigs( self ) -> bool: is_success = True for which in ["run", "user"]: if not self.__config_paths[which]: self.__config_paths[which] = self.__default_config_paths[which] if not self.initlizeConfig(which): is_success = False break self.initlizeConfigToWidget(which, self.__config_data[which]) return is_success def defaultRunConfig( self ) -> dict: return { "library": { "host_url": "http://10.1.20.7", "login_url": "/login" }, "login": { "auto_captcha": True, "max_attempt": 3 }, "web_driver": { "driver_type": "edge", "driver_path": "", "headless": False }, "mode": { "run_mode": 1 } } def defaultUserConfig( self ) -> dict: return { "groups": [ ] } def collectRunConfigFromWidget( self ) -> dict: run_config = self.defaultRunConfig() # library config is never changed run_config["login"]["auto_captcha"] = self.AutoCaptchaCheckBox.isChecked() run_config["login"]["max_attempt"] = self.LoginAttemptSpinBox.value() run_config["web_driver"]["driver_type"] = self.BrowserTypeComboBox.currentText() run_config["web_driver"]["driver_path"] = self.BrowseBrowserDriverEdit.text() run_config["web_driver"]["headless"] = self.HeadlessCheckBox.isChecked() run_mode = 0 if self.AutoReserveCheckBox.isChecked(): run_mode |= 0x01 if self.AutoCheckinCheckBox.isChecked(): run_mode |= 0x02 if self.AutoRenewalCheckBox.isChecked(): run_mode |= 0x04 run_config["mode"]["run_mode"] = run_mode return run_config def setRunConfigToWidget( self, run_config: dict ): try: self.HostUrlEdit.setText(run_config["library"]["host_url"]) self.LoginUrlEdit.setText(run_config["library"]["login_url"]) self.AutoCaptchaCheckBox.setChecked(run_config["login"]["auto_captcha"]) self.LoginAttemptSpinBox.setValue(run_config["login"]["max_attempt"]) self.BrowserTypeComboBox.setCurrentText(run_config["web_driver"]["driver_type"]) if run_config["web_driver"]["driver_path"]: driver_path = os.path.abspath(run_config["web_driver"]["driver_path"]) else: driver_path = "" self.BrowseBrowserDriverEdit.setText(QDir.toNativeSeparators(driver_path)) self.HeadlessCheckBox.setChecked(run_config["web_driver"]["headless"]) run_mode = run_config["mode"]["run_mode"] self.AutoReserveCheckBox.setChecked(run_mode&0x01) self.AutoCheckinCheckBox.setChecked(run_mode&0x02) self.AutoRenewalCheckBox.setChecked(run_mode&0x04) except KeyError as e: QMessageBox.warning( self, "警告 - AutoLibrary", f"运行配置文件: {self.__config_paths['run']}\n" f"读取时键 '{e}' 发生错误,文件可能被意外修改或已经损坏\n" ) except Exception as e: QMessageBox.warning( self, "警告 - AutoLibrary", f"运行配置文件: {self.__config_paths['run']}\n" f"读取时键 '{e}' 发生未知错误,文件可能被意外修改或已经损坏\n" ) def initilizeUserInfoWidget( self ): self.UsernameEdit.setText("") self.PasswordEdit.setText("") self.PasswordEdit.setEchoMode(QLineEdit.EchoMode.Password) self.ShowPasswordCheckBox.setChecked(False) self.FloorComboBox.setCurrentIndex(0) self.onFloorComboBoxCurrentIndexChanged() self.DateEdit.setDate(QDate.currentDate()) self.DateEdit.setMinimumDate(QDate.currentDate()) self.BeginTimeEdit.setTime(QTime.currentTime()) self.PreferEarlyBeginTimeCheckBox.setChecked(False) self.MaxBeginTimeDiffSpinBox.setValue(30) self.EndTimeEdit.setTime(QTime.currentTime().addSecs(120*60)) self.PreferLateEndTimeCheckBox.setChecked(False) self.MaxEndTimeDiffSpinBox.setValue(30) self.ExpectDurationSpinBox.setValue(self.BeginTimeEdit.time().secsTo(self.EndTimeEdit.time())/3600) self.SatisfyDurationCheckBox.setChecked(False) self.ExpectRenewDurationSpinBox.setValue(1.0) self.MaxRenewTimeDiffSpinBox.setValue(30) self.PreferLateRenewTimeCheckBox.setChecked(False) def collectUserFromWidget( self ) -> dict: user = { "username": self.UsernameEdit.text(), "password": self.PasswordEdit.text(), "enabled": True, "reserve_info": { "begin_time":{}, "end_time": {}, "renew_time": {} } } user["reserve_info"]["date"] = self.DateEdit.dateTime().toString("yyyy-MM-dd") user["reserve_info"]["place"] = self.PlaceComboBox.currentText() user["reserve_info"]["floor"] = self.__floor_rmap[self.FloorComboBox.currentText()] user["reserve_info"]["room"] = self.__room_rmap[self.RoomComboBox.currentText()] user["reserve_info"]["seat_id"] = self.SeatIDEdit.text() user["reserve_info"]["begin_time"]["time"] = self.BeginTimeEdit.time().toString("HH:mm") user["reserve_info"]["begin_time"]["max_diff"] = self.MaxBeginTimeDiffSpinBox.value() user["reserve_info"]["begin_time"]["prefer_early"] = self.PreferEarlyBeginTimeCheckBox.isChecked() user["reserve_info"]["end_time"]["time"] = self.EndTimeEdit.time().toString("HH:mm") user["reserve_info"]["end_time"]["max_diff"] = self.MaxEndTimeDiffSpinBox.value() user["reserve_info"]["end_time"]["prefer_early"] = not self.PreferLateEndTimeCheckBox.isChecked() user["reserve_info"]["expect_duration"] = self.ExpectDurationSpinBox.value() user["reserve_info"]["satisfy_duration"] = self.SatisfyDurationCheckBox.isChecked() user["reserve_info"]["renew_time"]["expect_duration"] = self.ExpectRenewDurationSpinBox.value() user["reserve_info"]["renew_time"]["max_diff"] = self.MaxRenewTimeDiffSpinBox.value() user["reserve_info"]["renew_time"]["prefer_early"] = not self.PreferLateRenewTimeCheckBox.isChecked() return user def collectUsersFromTreeWidget( self ) -> dict: user_config = self.defaultUserConfig() for i in range(self.UserTreeWidget.topLevelItemCount()): group_item = self.UserTreeWidget.topLevelItem(i) group_config = { "name": group_item.text(0), "enabled": group_item.checkState(1) == Qt.CheckState.Checked, "users": [] } for j in range(group_item.childCount()): user_item = group_item.child(j) user = user_item.data(0, Qt.UserRole) if not user: continue user["enabled"] = user_item.checkState(1) == Qt.CheckState.Checked group_config["users"].append(user) user_config["groups"].append(group_config) return user_config def setUserToWidget( self, user: dict ) -> None: try: self.UsernameEdit.setText(user["username"]) self.PasswordEdit.setText(user["password"]) self.DateEdit.setDate(QDate.fromString(user["reserve_info"]["date"], "yyyy-MM-dd")) self.PlaceComboBox.setCurrentText(user["reserve_info"]["place"]) self.FloorComboBox.setCurrentText(self.__floor_map[user["reserve_info"]["floor"]]) self.RoomComboBox.setCurrentText(self.__room_map[user["reserve_info"]["room"]]) self.SeatIDEdit.setText(user["reserve_info"]["seat_id"]) self.BeginTimeEdit.setTime(QTime.fromString(user["reserve_info"]["begin_time"]["time"], "H:mm")) self.MaxBeginTimeDiffSpinBox.setValue(user["reserve_info"]["begin_time"]["max_diff"]) self.PreferEarlyBeginTimeCheckBox.setChecked(user["reserve_info"]["begin_time"]["prefer_early"]) self.EndTimeEdit.setTime(QTime.fromString(user["reserve_info"]["end_time"]["time"], "H:mm")) self.MaxEndTimeDiffSpinBox.setValue(user["reserve_info"]["end_time"]["max_diff"]) self.PreferLateEndTimeCheckBox.setChecked(not user["reserve_info"]["end_time"]["prefer_early"]) self.ExpectDurationSpinBox.setValue(user["reserve_info"]["expect_duration"]) self.SatisfyDurationCheckBox.setChecked(user["reserve_info"]["satisfy_duration"]) self.ExpectRenewDurationSpinBox.setValue(user["reserve_info"]["renew_time"]["expect_duration"]) self.MaxRenewTimeDiffSpinBox.setValue(user["reserve_info"]["renew_time"]["max_diff"]) self.PreferLateRenewTimeCheckBox.setChecked(not user["reserve_info"]["renew_time"]["prefer_early"]) except KeyError as e: QMessageBox.warning( self, "警告 - AutoLibrary", f"用户配置文件: {self.__config_paths['user']}\n"\ f"读取时键 '{e}' 发生错误,文件可能被意外修改或已经损坏\n" ) except Exception as e: QMessageBox.warning( self, "警告 - AutoLibrary", f"用户配置文件: {self.__config_paths['user']}\n"\ f"读取时发生未知错误 '{e}',文件可能被意外修改或已经损坏\n" ) def setUsersToTreeWidget( self, users: dict ): self.UserTreeWidget.clear() self.UserTreeWidget.itemChanged.disconnect(self.onUserTreeWidgetItemChanged) try: if "groups" in users: for group_config in users["groups"]: group_item = QTreeWidgetItem(self.UserTreeWidget, ALUserTreeItemType.GROUP.value) group_item.setText(0, group_config["name"]) group_item.setFlags(group_item.flags() | Qt.ItemIsEditable) group_item.setCheckState(1, Qt.Checked if group_config.get("enabled", True) else Qt.Unchecked) for user_config in group_config["users"]: user_item = QTreeWidgetItem(group_item, ALUserTreeItemType.USER.value) user_item.setText(0, user_config["username"]) user_item.setText(1, "" if user_config.get("enabled", True) else "跳过") user_item.setData(0, Qt.UserRole, user_config) user_item.setCheckState(1, Qt.Checked if user_config.get("enabled", True) else Qt.Unchecked) user_item.setDisabled(not group_config.get("enabled", True)) group_item.setExpanded(True) except KeyError as e: QMessageBox.warning( self, "警告 - AutoLibrary", f"用户配置文件: {self.__config_paths['user']}\n"\ f"读取时键 '{e}' 发生错误,文件可能被意外修改或已经损坏\n" ) except Exception as e: QMessageBox.warning( self, "警告 - AutoLibrary", f"用户配置文件: {self.__config_paths['user']}\n"\ f"读取时发生未知错误 '{e}',文件可能被意外修改或已经损坏\n" ) finally: self.UserTreeWidget.itemChanged.connect(self.onUserTreeWidgetItemChanged) def loadRunConfig( self, run_config_path: str ) -> dict: try: if not run_config_path or not os.path.exists(run_config_path): raise Exception("文件路径不存在") run_config = ConfigReader(run_config_path).getConfigs() if run_config and "library" in run_config\ and "web_driver" in run_config\ and "login" in run_config: return run_config return None except Exception as e: QMessageBox.warning( self, "警告 - AutoLibrary", f"运行配置文件读取发生错误 ! : \n{e}" ) return None def saveRunConfig( self, run_config_path: str, run_config_data: dict ) -> bool: try: if not run_config_path: raise Exception("文件路径为空") if not run_config_data or not isinstance(run_config_data, dict): raise Exception("运行配置数据为空或类型错误") ConfigWriter(run_config_path, run_config_data) return True except Exception as e: QMessageBox.warning( self, "警告 - AutoLibrary", f"配置文件写入发生错误 ! : \n{e}" ) return False def loadUserConfig( self, user_config_path: str ) -> dict: try: if not user_config_path or not os.path.exists(user_config_path): raise Exception("文件路径不存在") user_config = ConfigReader(user_config_path).getConfigs() if user_config and "groups" in user_config: return user_config # compatibility with old version config format if user_config and "users" in user_config: user_config = { "groups": [ { "name": f"兼容分组-{QFileInfo(user_config_path).fileName()}", "enabled": True, "users": user_config["users"] } ] } return user_config return None except Exception as e: QMessageBox.warning( self, "警告 - AutoLibrary", f"用户配置文件读取发生错误 ! : \n{e}" ) return None def saveUserConfig( self, user_config_path: str, user_config_data: dict ) -> bool: try: if not user_config_path: raise Exception("文件路径为空") if not user_config_data or not isinstance(user_config_data, dict): raise Exception("用户配置数据为空或类型错误") ConfigWriter(user_config_path, user_config_data) return True except Exception as e: QMessageBox.warning( self, "警告 - AutoLibrary", f"用户配置文件写入发生错误 ! : \n{e}" ) return False def saveConfigs( self, run_config_path: str, user_config_path: str ) -> bool: if user_config_path: self.__config_data["user"] = self.collectUsersFromTreeWidget() if not self.saveUserConfig( user_config_path, self.__config_data["user"] ): return False if run_config_path: self.__config_data["run"] = self.collectRunConfigFromWidget() if not self.saveRunConfig( run_config_path, self.__config_data["run"] ): return False return True def loadConfig( self, config_path: str ) -> bool: if not config_path: config_path = QFileDialog.getOpenFileName( self, "从现有配置文件中加载 - AutoLibrary", f"{QDir.toNativeSeparators(QDir.currentPath())}", "JSON 文件 (*.json);;所有文件 (*)" )[0] if not config_path: return False try: run_config = self.loadRunConfig(config_path) user_config = self.loadUserConfig(config_path) if run_config is not None: self.__config_data["run"].update(run_config) self.setRunConfigToWidget(self.__config_data["run"]) return True if user_config is not None: self.__config_data["user"].update(user_config) self.setUsersToTreeWidget(self.__config_data["user"]) return True except: return False def addGroup( self, group_name: str = "" ) -> QTreeWidgetItem: self.UserTreeWidget.itemChanged.disconnect(self.onUserTreeWidgetItemChanged) group_item = QTreeWidgetItem(self.UserTreeWidget, ALUserTreeItemType.GROUP.value) if not group_name: group_name = f"新分组-{self.UserTreeWidget.topLevelItemCount()}" group_item.setText(0, group_name) group_item.setFlags(group_item.flags() | Qt.ItemIsEditable) group_item.setCheckState(1, Qt.Checked) self.UserTreeWidget.setCurrentItem(group_item) self.UserTreeWidget.itemChanged.connect(self.onUserTreeWidgetItemChanged) return group_item def delGroup( self, group_item: QTreeWidgetItem = None ): if group_item is None: return if group_item.type() != ALUserTreeItemType.GROUP.value: return index = self.UserTreeWidget.indexOfTopLevelItem(group_item) self.UserTreeWidget.takeTopLevelItem(index) def addUser( self, group_item: QTreeWidgetItem = None ) -> QTreeWidgetItem: if group_item is None: current_item = self.UserTreeWidget.currentItem() if current_item is None: group_item = self.addGroup() if group_item.type() == ALUserTreeItemType.USER.value: group_item = group_item.parent() if group_item.checkState(1) == Qt.CheckState.Unchecked: return None new_user = { "username": f"新用户-{group_item.childCount()}", "password": "000000", "enabled": True, "reserve_info": { "date": f"{QDate.currentDate().toString("yyyy-MM-dd")}", "place": "\u56fe\u4e66\u9986", "floor": "2", "room": "1", "seat_id": "", "begin_time": { "time": f"{QTime.currentTime().toString("hh:mm")}", "max_diff": 30, "prefer_early": False }, "end_time": { "time": f"{QTime.currentTime().addSecs(2*3600).toString("hh:mm")}", "max_diff": 30, "prefer_early": True }, "expect_duration": 2.0, "satisfy_duration": False, "renew_time": { "expect_duration": 1.0, "max_diff": 30, "prefer_early": True } } } self.UserTreeWidget.itemChanged.disconnect(self.onUserTreeWidgetItemChanged) user_item = QTreeWidgetItem(group_item, ALUserTreeItemType.USER.value) user_item.setText(0, new_user["username"]) user_item.setText(1, "") user_item.setData(0, Qt.UserRole, new_user) user_item.setCheckState(1, Qt.CheckState.Checked) group_item.setExpanded(True) self.UserTreeWidget.setCurrentItem(user_item) self.setUserToWidget(new_user) self.UserTreeWidget.itemChanged.connect(self.onUserTreeWidgetItemChanged) return user_item def delUser( self, user_item: QTreeWidgetItem = None ): if user_item is None: return if user_item.type() != ALUserTreeItemType.USER.value: return parent_item = user_item.parent() index = parent_item.indexOfChild(user_item) parent_item.takeChild(index) if parent_item.childCount() == 0: self.UserTreeWidget.setCurrentItem(None) def renameItem( self, item: QTreeWidgetItem, ): if item is None: return old_name = item.text(0) if item.parent() is None: item_type = "分组" else: item_type = "用户" new_name, ok = QInputDialog.getText( self, f"重命名{item_type}项 : '{old_name}'", f"请输入新的{item_type}名:", text=old_name ) new_name = new_name.strip() if not ok or not new_name: return item.setText(0, new_name) if item.type() == ALUserTreeItemType.GROUP.value: item.setText(0, new_name) else: user = item.data(0, Qt.UserRole) user["username"] = new_name item.setText(0, new_name) item.setData(0, Qt.UserRole, user) self.setUserToWidget(user) @Slot() def onShowPasswordCheckBoxChecked( self, checked: bool ): if checked: self.PasswordEdit.setEchoMode(QLineEdit.Normal) else: self.PasswordEdit.setEchoMode(QLineEdit.Password) @Slot() def onFloorComboBoxCurrentIndexChanged( self ): floor = self.FloorComboBox.currentText() self.RoomComboBox.clear() self.RoomComboBox.addItems(self.__floor_room_map[floor]) self.RoomComboBox.setCurrentIndex(0) @Slot() def onSelectSeatsButtonClicked( self ): floor = self.FloorComboBox.currentText() room = self.RoomComboBox.currentText() floor_idx = self.__floor_rmap[floor] room_idx = self.__room_rmap[room] dialog = ALSeatMapSelectDialog( self, floor, room, ALSeatMapTable[floor_idx][room_idx] ) dialog.selectSeats(self.SeatIDEdit.text().split(",")) if dialog.exec() == QDialog.DialogCode.Accepted: selected_seats = dialog.getSelectedSeats() if len(selected_seats) == 0: self.SeatIDEdit.clear() return self.SeatIDEdit.setText(",".join(dialog.getSelectedSeats())) @Slot() def onUserTreeWidgetCurrentItemChanged( self, current: QTreeWidgetItem, previous: QTreeWidgetItem ): # dont care about the 'self.__config_data["user"]', we already # cant effectively update the data of each user, due to the # possiblity of frequency edit. we just let the QListWidget # help us. if previous and previous.type() == ALUserTreeItemType.USER.value: user = self.collectUserFromWidget() if user: self.UsernameEdit.textEdited.disconnect() user["enabled"] = previous.checkState(1) == Qt.Checked previous.setText(0, user["username"]) previous.setText(1, "" if user.get("enabled", True) else "跳过") previous.setData(0, Qt.UserRole, user) if current is None: self.initilizeUserInfoWidget() return if current.type() == ALUserTreeItemType.USER.value: user = current.data(0, Qt.UserRole) if user: self.setUserToWidget(user) self.UsernameEdit.textEdited.connect(lambda text: current.setText(0, text)) else: self.initilizeUserInfoWidget() @Slot() def onUserTreeWidgetItemChanged( self, item: QTreeWidgetItem, column: int ): if item is None: return if column != 1: return if item.type() == ALUserTreeItemType.GROUP.value: is_checked = item.checkState(1) == Qt.CheckState.Checked for i in range(item.childCount()): child = item.child(i) if self.UserTreeWidget.currentItem() == child: self.UserTreeWidget.setCurrentItem(item) child.setDisabled(not is_checked) else: is_checked = item.checkState(1) == Qt.CheckState.Checked item.setText(1, "" if is_checked else "跳过") def showTreeMenu( self, menu: QMenu ): add_group_action = QAction("添加分组", menu) add_group_action.triggered.connect(self.addGroup) menu.addAction(add_group_action) def showGroupMenu( self, menu: QMenu, group_item: QTreeWidgetItem = None ): add_user_action = QAction("添加用户", menu) rename_group_action = QAction("重命名分组", menu) del_group_action = QAction("删除分组", menu) add_user_action.triggered.connect(lambda: self.addUser(group_item)) rename_group_action.triggered.connect(lambda: self.renameItem(group_item)) del_group_action.triggered.connect(lambda: self.delGroup(group_item)) menu.addAction(add_user_action) menu.addSeparator() menu.addAction(rename_group_action) menu.addAction(del_group_action) if group_item.checkState(1) == Qt.CheckState.Unchecked: add_user_action.setEnabled(False) def showUserMenu( self, menu: QMenu, user_item: QTreeWidgetItem = None ): rename_user_action = QAction("重命名用户", menu) del_user_action = QAction("删除用户", menu) rename_user_action.triggered.connect(lambda: self.renameItem(user_item)) del_user_action.triggered.connect(lambda: self.delUser(user_item)) menu.addAction(rename_user_action) menu.addAction(del_user_action) @Slot() def onUserTreeWidgetContextMenu( self, pos ): current_item = self.UserTreeWidget.itemAt(pos) menu = QMenu(self.UserTreeWidget) if current_item is None: self.showTreeMenu(menu) elif current_item.type() == ALUserTreeItemType.GROUP.value: self.showGroupMenu(menu, current_item) else: self.showUserMenu(menu, current_item) menu.exec_(self.UserTreeWidget.mapToGlobal(pos)) @Slot() def onAddUserButtonClicked( self ): current_item = self.UserTreeWidget.currentItem() self.addUser(current_item) @Slot() def onDelUserButtonClicked( self ): current_item = self.UserTreeWidget.currentItem() self.delUser(current_item) @Slot() def onBrowseBrowserDriverButtonClicked( self ): browser_driver_path = QFileDialog.getOpenFileName( self, "选择浏览器驱动 - AutoLibrary", self.BrowseBrowserDriverEdit.text(), "可执行文件 (*.exe);;所有文件 (*)" )[0] if browser_driver_path: self.BrowseBrowserDriverEdit.setText(QDir.toNativeSeparators(browser_driver_path)) @Slot() def onBrowseCurrentRunConfigButtonClicked( self ): run_config_path = QFileDialog.getOpenFileName( self, "选择其它的运行配置 - AutoLibrary", self.CurrentRunConfigEdit.text(), "JSON 文件 (*.json);;所有文件 (*)" )[0] if run_config_path: run_config_path = QDir.toNativeSeparators(run_config_path) if self.loadConfig(run_config_path): self.__config_paths["run"] = run_config_path self.CurrentRunConfigEdit.setText(run_config_path) @Slot() def onBrowseCurrentUserConfigButtonClicked( self ): user_config_path = QFileDialog.getOpenFileName( self, "选择其它的用户配置 - AutoLibrary", self.CurrentUserConfigEdit.text(), "JSON 文件 (*.json);;所有文件 (*)" )[0] if user_config_path: user_config_path = QDir.toNativeSeparators(user_config_path) if self.loadConfig(user_config_path): self.__config_paths["user"] = user_config_path self.CurrentUserConfigEdit.setText(user_config_path) @Slot() def onBrowseExportRunConfigButtonClicked( self ): run_config_path = QFileDialog.getSaveFileName( self, "导出运行配置 - AutoLibrary", self.CurrentRunConfigEdit.text(), "JSON 文件 (*.json);;所有文件 (*)" )[0] if run_config_path: self.ExportRunConfigEdit.setText(QDir.toNativeSeparators(run_config_path)) @Slot() def onBrowseExportUserConfigButtonClicked( self ): user_config_path = QFileDialog.getSaveFileName( self, "导出用户配置 - AutoLibrary", self.CurrentUserConfigEdit.text(), "JSON 文件 (*.json);;所有文件 (*)" )[0] if user_config_path: self.ExportUserConfigEdit.setText(QDir.toNativeSeparators(user_config_path)) @Slot() def onExportConfigButtonClicked( self ): msg = "" run_config_path = self.ExportRunConfigEdit.text() user_config_path = self.ExportUserConfigEdit.text() if run_config_path: if self.saveConfigs( run_config_path, "" ): msg += f"运行配置文件已导出到: \n'{run_config_path}'\n" else: msg += f"运行配置文件导出失败: \n'{run_config_path}'\n" if user_config_path: if self.saveConfigs( "", user_config_path ): msg += f"用户配置文件已导出到: \n'{user_config_path}'\n" else: msg += f"用户配置文件导出失败: \n'{user_config_path}'\n" if msg: QMessageBox.information( self, "提示 - AutoLibrary", msg ) @Slot() def onLoadConfigButtonClicked( self ): self.loadConfig("") @Slot() def onNewConfigButtonClicked( self ): file_path = self.CurrentRunConfigEdit.text() folder_dir = QFileDialog.getExistingDirectory( self, "选择新建配置的文件夹 - AutoLibrary", QDir.toNativeSeparators(QFileInfo(os.path.abspath(file_path)).absoluteDir().path()) ) if not folder_dir: return run_config_path = QDir.toNativeSeparators(os.path.join(folder_dir, "run.json")) user_config_path = QDir.toNativeSeparators(os.path.join(folder_dir, "user.json")) run_exists = os.path.isfile(run_config_path) user_exists = os.path.isfile(user_config_path) if run_exists or user_exists: exist_files = [] if run_exists: exist_files.append(run_config_path) if user_exists: exist_files.append(user_config_path) reply = QMessageBox.information( self, "提示 - AutoLibrary", f"文件夹中已存在以下文件, 是否覆盖 ?\n{chr(10).join(exist_files)}", QMessageBox.Yes | QMessageBox.No, QMessageBox.No ) if reply == QMessageBox.No: return self.__config_data["run"] = self.defaultRunConfig() self.__config_data["user"] = self.defaultUserConfig() self.__config_paths = { "run": run_config_path, "user": user_config_path } self.initlizeConfigToWidget("run", self.__config_data["run"]) self.initlizeConfigToWidget("user", self.__config_data["user"]) @Slot() def onConfirmButtonClicked( self ): current_item = self.UserTreeWidget.currentItem() if current_item and current_item.type() == ALUserTreeItemType.USER.value: self.UserTreeWidget.setCurrentItem(None) if self.saveConfigs( self.__config_paths["run"], self.__config_paths["user"] ): QMessageBox.information( self, "提示 - AutoLibrary", "配置文件保存成功 !\n" f"运行配置文件路径: \n{self.__config_paths['run']}\n"\ f"用户配置文件路径: \n{self.__config_paths['user']}" ) else: QMessageBox.warning( self, "警告 - AutoLibrary", "配置文件保存失败, 请检查文件路径权限" ) self.close() @Slot() def onCancelButtonClicked( self ): self.close()