class ChessClaimView(QMainWindow): """ The main window of the application. Attributes: rowCount(int): The number of the row the TreeView Table has. iconsSize(int): The recommended size of the icons. mac_notification: Notification for macOS win_notification: Notification for windows OS """ def __init__(self): super().__init__() self.resize(720, 275) self.iconsSize = 16 self.setWindowTitle('Chess Claim Tool') self.center() self.rowCount = 0 if (platform.system() == "Darwin"): from MacNotification import Notification self.mac_notification = Notification() elif (platform.system() == "Windows"): from win10toast import ToastNotifier self.win_notification = ToastNotifier() def center(self): """ Centers the window on the screen """ screen = QDesktopWidget().screenGeometry() size = self.geometry() self.move((screen.width()-size.width())/2, (screen.height()-size.height())/2) def set_GUI(self): """ Initialize GUI components. """ # Create the Menu self.livePgnOption = QAction('Live PGN',self) self.livePgnOption.setCheckable(True) aboutAction = QAction('About',self) menubar = self.menuBar() optionsMenu = menubar.addMenu('&Options') optionsMenu.addAction(self.livePgnOption) aboutMenu = menubar.addMenu('&Help') aboutMenu.addAction(aboutAction) aboutAction.triggered.connect(self.slots.on_about_clicked) # Create the Claims Table (TreeView) self.claimsTable = QTreeView() self.claimsTable.setFocusPolicy(Qt.NoFocus) self.claimsTable.setEditTriggers(QAbstractItemView.NoEditTriggers) self.claimsTable.header().setDefaultAlignment(Qt.AlignCenter) self.claimsTable.setSortingEnabled(True) self.claimsTable.doubleClicked.connect(self.open_game) # Create the Claims Model self.claimsTableModel = QStandardItemModel() labels = ["#","Timestamp","Type","Board","Players","Move"] self.claimsTableModel.setHorizontalHeaderLabels(labels) self.claimsTable.setModel(self.claimsTableModel) # Create the Scan & Stop Button Box self.buttonBox = ButtonBox(self) # Create the Sources Button sourcesButton = QPushButton("Add Sources") sourcesButton.setObjectName("sources") sourcesButton.clicked.connect(self.slots.on_sourcesButton_clicked) # Create the Status Bar self.pixmapCheck = QPixmap(resource_path("check_icon.png")) self.pixmapError = QPixmap(resource_path("error_icon.png")) self.sourceLabel = QLabel() self.sourceLabel.setObjectName("source-label") self.sourceImage = QLabel() self.sourceImage.setObjectName("source-image") self.downloadLabel = QLabel() self.downloadLabel.setObjectName("download-label") self.downloadImage = QLabel() self.downloadImage.setObjectName("download-image") self.scanningLabel = QLabel() self.scanningLabel.setObjectName("scanning") self.spinnerLabel = QLabel() self.spinnerLabel.setVisible(False) self.spinnerLabel.setObjectName("spinner") self.spinner = QMovie(resource_path("spinner.gif")) self.spinner.setScaledSize(QSize(self.iconsSize, self.iconsSize)) self.spinnerLabel.setMovie(self.spinner) self.spinner.start() self.statusBar = QStatusBar() self.statusBar.setSizeGripEnabled(False) self.statusBar.addWidget(self.sourceLabel) self.statusBar.addWidget(self.sourceImage) self.statusBar.addWidget(self.downloadLabel) self.statusBar.addWidget(self.downloadImage) self.statusBar.addWidget(self.scanningLabel) self.statusBar.addWidget(self.spinnerLabel) self.statusBar.addPermanentWidget(sourcesButton) self.statusBar.setContentsMargins(10,5,9,5) # Container Layout for the Central Widget containerLayout = QVBoxLayout() containerLayout.setSpacing(0) containerLayout.addWidget(self.claimsTable) containerLayout.addWidget(self.buttonBox) # Central Widget containerWidget = QWidget() containerWidget.setLayout(containerLayout) self.setCentralWidget(containerWidget) self.setStatusBar(self.statusBar) def open_game(self): """ TODO: Double click should open a window to replay the game.""" pass def resize_claimsTable(self): """ Resize the table (if needed) after the insertion of a new element""" for index in range(0,6): self.claimsTable.resizeColumnToContents(index) def set_slots(self, slots): """ Connect the Slots """ self.slots = slots def add_to_table(self,type,bo_number,players,move): """ Add new row to the claimsTable Args: type: The type of the draw (3 Fold Repetition, 5 Fold Repetition, 50 Moves Rule, 75 Moves Rule). bo_number: The number of the boards, if this information is available. players: The name of the players. move: With which move the draw is valid. """ # Before insertion, remove rows as descripted in the remove_rows function self.remove_rows(type,players) timestamp = str(datetime.now().strftime('%H:%M:%S')) row = [] items = [str(self.rowCount+1),timestamp,type,bo_number,players,move] """ Convert each item(str) to QStandardItem, make the necessary stylistic additions and append it to row.""" for index in range(len(items)): standardItem = QStandardItem(items[index]) standardItem.setTextAlignment(Qt.AlignCenter) if(index == 2): font = standardItem.font() font.setBold(True) standardItem.setFont(font) if (items[index] == "5 Fold Repetition" or items[index] == "75 Moves Rule"): standardItem.setData(QColor(255,0,0), Qt.ForegroundRole) row.append(standardItem) self.claimsTableModel.appendRow(row) self.rowCount = self.rowCount+1 # After the insertion resize the table self.resize_claimsTable() # Always the last row(the bottom of the table) should be visible. self.claimsTable.scrollToBottom() #Send Notification self.notify(type,players,move) def notify(self,type,players,move): """ Send notification depending on the OS. Args: type: The type of the draw (3 Fold Repetition, 5 Fold Repetition, 50 Moves Rule, 75 Moves Rule). players: The names of the players. move: With which move the draw is valid. """ if (platform.system() == "Darwin"): self.mac_notification.clearNotifications() self.mac_notification.notify(type,players,move) elif(platform.system() == "Windows"): self.win_notification.show_toast(type, players+"\n"+move, icon_path=resource_path("logo.ico"), duration=5, threaded=True) def remove_from_table(self,index): """ Remove element from the claimsTable. Args: index: The index of the row we want to remove. First row has index=0. """ self.claimsTableModel.removeRow(index) def remove_rows(self,type,players): """ Removes a existing row from the Claims Table when same players made the same type of draw with a new move - or they made 5 Fold Repetition over the 3 Fold or 75 Moves Rule over 50 moves Rule. Args: type: The type of the draw (3 Fold Repetition, 5 Fold Repetition, 50 Moves Rule, 75 Moves Rule). players: The names of the players. """ for index in range(self.rowCount): try: modelType = self.claimsTableModel.item(index,2).text() modelPlayers = self.claimsTableModel.item(index,4).text() except AttributeError: modelType = "" modelPlayers = "" if (modelType == type and modelPlayers == players): self.remove_from_table(index) self.rowCount = self.rowCount - 1 break elif (type == "5 Fold Repetition" and modelType == "3 Fold Repetition" and modelPlayers == players) : self.remove_from_table(index) self.rowCount = self.rowCount - 1 break elif (type == "75 Moves Rule" and modelType == "50 Moves Rule" and modelPlayers == players): self.remove_from_table(index) self.rowCount = self.rowCount - 1 break def clear_table(self): """ Clear all the elements off the Claims Table and resets the rowCount. """ for index in range(self.rowCount): self.claimsTableModel.removeRow(0) self.rowCount = 0 def set_sources_status(self,status,validSources=None): """ Adds the sourcess in the statusBar. Args: status(str): The status of the validity of the sources. "ok": At least one source is valid. "error": None of the sources are valid. validSources(list): The list of valid sources, if there is any. This list is used here to display the ToolTip. """ self.sourceLabel.setText("Sources:") # Set the ToolTip if there are sources. try: text = "" for index in range(len(validSources)): if (index == len(validSources) - 1): number = str(index+1) text = text+number+") "+validSources[index].get_value() else: number = str(index+1) text = text+number+") "+validSources[index].get_value()+"\n" self.sourceLabel.setToolTip(text) except TypeError: pass if (status == "ok"): self.sourceImage.setPixmap(self.pixmapCheck.scaled(self.iconsSize,self.iconsSize,transformMode=Qt.SmoothTransformation)) else: self.sourceImage.setPixmap(self.pixmapError.scaled(self.iconsSize,self.iconsSize,transformMode=Qt.SmoothTransformation)) def set_download_status(self,status): """ Adds download status in the statusBar. Args: status(str): The status of the download(s). "ok": The download of the sources is successful. "error": The download of the sources failed. "stop": The download process stopped. """ timestamp = str(datetime.now().strftime('%H:%M:%S')) self.downloadLabel.setText(timestamp+" Download:") if (status == "ok"): self.downloadImage.setPixmap(self.pixmapCheck.scaled(self.iconsSize,self.iconsSize,transformMode=Qt.SmoothTransformation)) elif (status == "error"): self.downloadImage.setPixmap(self.pixmapError.scaled(self.iconsSize,self.iconsSize,transformMode=Qt.SmoothTransformation)) elif (status == "stop"): self.downloadImage.clear() self.downloadLabel.clear() def set_scan_status(self,status): """ Adds the scan status in the statusBar. Args: status(str): The status of the scan process. "active": The scan process is active. "error": The scan process waits for a new file. "stop": The scan process stopped. """ if (status == "wait"): self.scanningLabel.setText("Scan: Waiting") self.spinnerLabel.setVisible(False) elif (status == "active"): self.scanningLabel.setText("Scanning...") self.spinnerLabel.setVisible(True) elif (status == "stop"): self.scanningLabel.clear() self.spinnerLabel.setVisible(False) def change_scanButton_text(self,status): """ Changes the text of the scanButton depending on the status of the application. Args: status(str): The status of the scan process. "active": The scan process is active. "wait": The scan process is being terminated "stop": The scan process stopped. """ if (status == "active"): self.buttonBox.scanButton.setText("Scanning PGN...") elif (status == "stop"): self.buttonBox.scanButton.setText("Start Scan") elif(status == "wait"): self.buttonBox.scanButton.setText("Please Wait") def enable_buttons(self): self.buttonBox.scanButton.setEnabled(True) self.buttonBox.stopButton.setEnabled(True) def disable_buttons(self): self.buttonBox.scanButton.setEnabled(False) self.buttonBox.stopButton.setEnabled(False) def enable_statusBar(self): """ Show download and scan status messages - if they were previously hidden (by disable_statusBar) - from the statusBar.""" self.downloadLabel.setVisible(True) self.scanningLabel.setVisible(True) self.downloadImage.setVisible(True) def disable_statusBar(self): """ Hide download and scan status messages from the statusBar. """ self.downloadLabel.setVisible(False) self.downloadImage.setVisible(False) self.scanningLabel.setVisible(False) self.spinnerLabel.setVisible(False) def closeEvent(self,event): """ Reimplement the close button If the program is actively scanning a pgn a warning dialog shall be raised in order to make sure that the user didn't clicked the close Button accidentally. Args: event: The exit QEvent. """ try: if (self.slots.scanWorker.isRunning): exitDialog = QMessageBox() exitDialog.setWindowTitle("Warning") exitDialog.setText("Scanning in Progress") exitDialog.setInformativeText("Do you want to quit?") exitDialog.setIcon(exitDialog.Warning) exitDialog.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel) exitDialog.setDefaultButton(QMessageBox.Cancel) replay = exitDialog.exec() if replay == QMessageBox.Yes: event.accept() else: event.ignore() except: event.accept() def load_warning(self): """ Displays a Warning Dialog. trigger: User clicked the "Start Scanning" Button without any valid pgn source. """ warningDialog = QMessageBox() warningDialog.setIcon(warningDialog.Warning) warningDialog.setWindowTitle("Warning") warningDialog.setText("PGN File(s) Not Found") warningDialog.setInformativeText("Please enter at least one valid PGN source.") warningDialog.exec() def load_about_dialog(self): """ Displays the About Dialog.""" self.aboutDialog = AboutDialog() self.aboutDialog.set_GUI() self.aboutDialog.show()
class App(QMainWindow): def __init__(self): super().__init__() self._options = {} self._count = 0 self._size = 0 self._netType = '_wifi' self._message_box_count = 0 self._lastTotal = 0 self._export = None self._items = [] self._model=[] self._last_adb_err_time = 0 _helpAction = QPushButton('帮助') _helpAction.clicked.connect(self.onHelpClick) _helpAction.setFlat(True) _helpAction.setAutoFillBackground(True) _palette = QPalette() _palette.setColor(QPalette.ButtonText, QColor('#1E90FF')) _palette.setColor(QPalette.Button, QColor('#DCDCDC')) _helpAction.setPalette(_palette) _aboutAction = QPushButton('关于') _aboutAction.clicked.connect(self.onAboutClick) _aboutAction.setFlat(True) _aboutAction.setAutoFillBackground(True) _palette = QPalette() _palette.setColor(QPalette.ButtonText, QColor('#1E90FF')) _palette.setColor(QPalette.Button, QColor('#DCDCDC')) _aboutAction.setPalette(_palette) self.toolbar = self.addToolBar('help') self.toolbar.addWidget(_helpAction) self.toolbar.addSeparator() self.toolbar.addWidget(_aboutAction) _widget = QWidget() self.layout = QVBoxLayout(_widget) self.setCentralWidget(_widget) self.resize(600, 200) self.setWindowTitle('性能监控') self.setOptionLayout() def setOptionLayout(self): self._titleLayout = QVBoxLayout() self.newCheckBtn(CPU, NETWORK) self.newCheckBtn(FPS, BATTERY) self.newCheckBtn(MEMORY, TEMPERATURE) self.newCheckBtn(TOTAL_MEMORY, TOTAL_CPU) self.newInput() _hLayout = QHBoxLayout() _checkbox = QCheckBox('导出Excel文件', self) _checkbox.stateChanged.connect(self.onExportChanged) _hLayout.addWidget(_checkbox) self._titleLayout.addLayout(_hLayout) btn = QPushButton('开始') btn.clicked.connect(self.onStart) self._titleLayout.addWidget(btn) self.layout.addLayout(self._titleLayout) def check(self): print('Checking...') if not remnant: QMessageBox.warning(self, '警告:软件到期!', '请关注微信公众号 "测试一般不一般" ,\n进行软件更新,谢谢~') self.close() sys.exit(1) print('OK...') def onHelpClick(self): QMessageBox.information(self, '帮助', '请关注微信公众号: "测试一般不一般" ,\n查看性能监控工具使用测试说明~') def onAboutClick(self): QMessageBox.information(self, '关于', '版本号 : {}\n日期 : {}'.format(identify.VERSION, identify.DATE)) def onNetworkChanged(self, state): if state == Qt.Checked: self._netType = '_local' else: self._netType = '_wifi' def onExportChanged(self, state): self._export = state def newInput(self): layout = QHBoxLayout() self._App__pkg_edit = QLineEdit() self._App__pkg_edit.setPlaceholderText('包名') self._App__pkg_edit.setText('com.tct.launcher')#com.tct.live self._App__serial_edit = QLineEdit() self._App__serial_edit.setPlaceholderText('设备号(单设备,可不输)') layout.addWidget(self._App__pkg_edit) layout.addWidget(self._App__serial_edit) self._titleLayout.addLayout(layout) def newCheckBtn(self, name1, name2): layout = QHBoxLayout() btn = QPushButton(name1) btn.setCheckable(True) btn.clicked[bool].connect(self.onNetworkCheck) layout.addWidget(btn) btn2 = QPushButton(name2) btn2.setCheckable(True) btn2.clicked[bool].connect(self.onNetworkCheck) layout.addWidget(btn2) self._titleLayout.addLayout(layout) def onStart(self): items = [k if k != 'network' else k + self._netType for k, v in self._options.items() if v] _it = 'network' + self._netType self._networkIndex = utils.listFind(items, _it) + 1 self._fpsIndex = utils.listFind(items, 'fps') + 1 if self._networkIndex > 0: items.append(_it + '_speed') items.append('network_all_speed') items.insert(0, 'timestamp') items.append('curActivity') self._curIndex = len(items) - 1 if len(items) <= 1: QMessageBox.warning(self, '警告!', '一项指标都没选!') return pkg = self._App__pkg_edit.text() if not pkg: QMessageBox.warning(self, '错误!', '包名没有传入!') return serial = self._App__serial_edit.text() data = (items, pkg, serial) clearLayout(self._titleLayout) self._size = len(items) _windowSize = 130 * self._size _windowSize = _windowSize if _windowSize > 500 else 500 self.resize(_windowSize, 500) self.setWorkerLayout(items) self.startThreads(data) def onReset(self): self.onClearModels() self.abortWorkers() adb.cpuHasRun = False clearLayout(self.layout) self.resize(600, 200) self.setOptionLayout() def onNetworkCheck(self, pressed): source = self.sender() name = NAMES[source.text()] self._options[name] = pressed def setWorkerLayout(self, items): self._titleLine = [F_NAMES[it] for it in items] _hLayout = QHBoxLayout() if self._export: self.exportBtn = QPushButton('导出') self.exportBtn.setEnabled(False) self.exportBtn.clicked.connect(self.onExport) _hLayout.addWidget(self.exportBtn) self.clearBtn = QPushButton('清空') self.clearBtn.clicked.connect(self.onClearModels) _hLayout.addWidget(self.clearBtn) self.exitBtn = QPushButton('停止') self.exitBtn.clicked.connect(self.abortWorkers) _hLayout.addWidget(self.exitBtn) self.resetBtn = QPushButton('重置') self.resetBtn.clicked.connect(self.onReset) self.resetBtn.setEnabled(False) _hLayout.addWidget(self.resetBtn) _timeLayout = QHBoxLayout() self._start_time = time.time() self._startLabel = QLabel('开始 :' + time.strftime('%H:%M:%S', time.localtime())) self._totalLabel = QLabel('总耗时 :') self.startTotalLabel = QLabel('初始流量 :') self.endTotalLabel = QLabel('总流量 :') _timeLayout.addWidget(self._startLabel) _timeLayout.addWidget(self._totalLabel) _timeLayout.addWidget(self.startTotalLabel) _timeLayout.addWidget(self.endTotalLabel) groupBox = QGroupBox('性能') self._treeView = QTreeView() self._treeView.setRootIsDecorated(True) self._treeView.setAutoScroll(True) self._treeView.setAlternatingRowColors(True) tree_layout = QHBoxLayout() tree_layout.addWidget(self._treeView) groupBox.setLayout(tree_layout) self._model = QStandardItemModel(0, self._size, self) for index in range(self._size): self._model.setHeaderData(index, Qt.Horizontal, F_NAMES[items[index]]) self._treeView.setModel(self._model) self.layout.addLayout(_hLayout) self.layout.addLayout(_timeLayout) self.layout.addWidget(groupBox) if self._networkIndex > 0: self.startTotalLabel.setText('初始流量 :0k') def onClearModels(self): if self._count == 0: return self._model.removeRows(0,self._count) self._count = 0 def onExport(self): try: data = Dataset(*self._items, **{'headers': self._titleLine}) fileName = time.strftime('%m-%d-%H_%M_%S', time.localtime()) + '-performance.xls' with open(fileName, 'wb') as (f): f.write(data.export('xls')) QMessageBox.information(self, '导出成功!', 'Excel文件名为' + fileName) except Exception as err: try: QMessageBox.warning(self, '导出异常!', str(err)) finally: err = None del err def addModel(self, items): # if self._count > 1000: # self.onClearModels() # else: if self._networkIndex > 0: self._lastTotal = items[self._networkIndex] items[self._networkIndex] = utils.number_format(items[self._networkIndex]) self._model.insertRow(self._count) for index in range(self._size): self._model.setData(self._model.index(self._count, index), items[index]) if self._fpsIndex > 0: if float(items[self._fpsIndex] > 16.66): self._model.item(self._count, self._fpsIndex).setForeground(QBrush(QColor(255, 0, 0))) if not items[self._curIndex].startswith(';'): self._model.item(self._count, self._curIndex).setForeground(QBrush(QColor(255, 0, 0))) items[self._curIndex] += '(out)' else: items[self._curIndex] = items[self._curIndex].replace(';', '') self._model.setData(self._model.index(self._count, self._curIndex), items[self._curIndex]) self._items.append(items) self._count += 1 self._treeView.scrollToBottom() def startThreads(self, data): worker = Worker(1) worker.setData(data) thread = QThread() worker.moveToThread(thread) worker.sigStep.connect(self.onWorkerStep) worker.sigInit.connect(self.onInitCompleted) worker.sigAdbErr.connect(self.onAdbErr) thread.started.connect(worker.work) thread.start() self.thread = thread self.worker = worker def onInitCompleted(self): print('数据初始化结束!!!') @pyqtSlot(list) def onWorkerStep(self, items: list): self.addModel(items) def abortWorkers(self): if self._export: self.exportBtn.setEnabled(True) self.clearBtn.setEnabled(False) self.exitBtn.setEnabled(False) self.resetBtn.setEnabled(True) adb.cpuHasRun = False total = time.time() - self._start_time self._totalLabel.setText('总耗时 : %s' % utils.time2hms(int(total))) if self._networkIndex > 0: self.endTotalLabel.setText('总流量 : {}'.format(utils.kbFormat(self._lastTotal))) self.worker.abort() self.thread.quit() def onAdbErr(self, err): if self._message_box_count > 0: return self._message_box_count += 1 reply = QMessageBox.question(self, 'adb连接错误!', err, QMessageBox.Yes) if reply == QMessageBox.Yes: self._message_box_count -= 1