class TableWidget(QWidget): def __init__(self, table, headers, bold=True, mono=True, tooltips=None, align=False, search=True, parent=None): super(TableWidget, self).__init__(parent) self.table_widget = QTableWidget(len(table), len(table[0])) for i, row in enumerate(table): for j, item in enumerate(row): if item is not None: self.table_widget.setItem(i, j, QTableWidgetItem(str(item))) if tooltips is not None: self.table_widget.setToolTip(tooltips[i][j]) modify_font(self.table_widget.item(i, j), bold=bold and j == 0, mono=mono) if align: self.table_widget.item(i, j).setTextAlignment( Qt.AlignRight) self.table_headers = headers self.table_widget.setHorizontalHeaderLabels(self.table_headers) self.table_widget.setSelectionMode(QAbstractItemView.SingleSelection) self.table_widget.setEditTriggers(QAbstractItemView.NoEditTriggers) self.table_widget.resizeColumnsToContents() self.table_widget.setAlternatingRowColors(True) self.table_widget.itemDoubleClicked.connect(self.copy) search_layout = QHBoxLayout() search_layout.addWidget(QLabel(self.tr('Search:'))) self.search_edit = QLineEdit() self.search_edit.textChanged.connect(self.start) self.search_edit.returnPressed.connect(self.next) search_layout.addWidget(self.search_edit) clear_button = QToolButton() clear_button.setIcon(QIcon('icons/clear.svg')) clear_button.setShortcut(QKeySequence.DeleteCompleteLine) clear_button.setToolTip(self.tr('Clear pattern')) clear_button.clicked.connect(self.search_edit.clear) search_layout.addWidget(clear_button) self.case_button = QToolButton() self.case_button.setText(self.tr('Aa')) self.case_button.setCheckable(True) self.case_button.toggled.connect(self.start) self.case_button.setToolTip(self.tr('Case sensitive')) search_layout.addWidget(self.case_button) self.word_button = QToolButton() self.word_button.setText(self.tr('W')) self.word_button.setCheckable(True) self.word_button.toggled.connect(self.start) self.word_button.setToolTip(self.tr('Whole words')) search_layout.addWidget(self.word_button) self.regex_button = QToolButton() self.regex_button.setText(self.tr('.*')) self.regex_button.setCheckable(True) self.regex_button.toggled.connect(self.start) self.regex_button.setToolTip(self.tr('Regular expression')) search_layout.addWidget(self.regex_button) prev_button = QToolButton() prev_button.setIcon(QIcon('icons/up.svg')) prev_button.setShortcut(QKeySequence.FindPrevious) prev_button.clicked.connect(self.previous) prev_button.setToolTip(self.tr('Previous occurence')) search_layout.addWidget(prev_button) next_button = QToolButton() next_button.setIcon(QIcon('icons/down.svg')) next_button.setShortcut(QKeySequence.FindNext) next_button.clicked.connect(self.next) next_button.setToolTip(self.tr('Next occurence')) search_layout.addWidget(next_button) self.matches_label = QLabel() search_layout.addWidget(self.matches_label) search_layout.addStretch() export_button = QToolButton() export_button.setText(self.tr('Export...')) export_button.clicked.connect(self.export) search_layout.addWidget(export_button) main_layout = QVBoxLayout() main_layout.addWidget(self.table_widget) if search: main_layout.addLayout(search_layout) self.setLayout(main_layout) def start(self): self.search(self.search_edit.text(), -1, -1, 1) def next(self): row = self.table_widget.currentRow() col = self.table_widget.currentColumn() + 1 if col == self.table_widget.columnCount(): row += 1 col = 0 self.search(self.search_edit.text(), row, col, +1) def previous(self): row = self.table_widget.currentRow() col = self.table_widget.currentColumn() - 1 if col == -1: row -= 1 col = self.table_widget.columnCount() - 1 self.search(self.search_edit.text(), row, col, -1) def search(self, pattern, row, col, direction): nocase = not self.case_button.isChecked() word = self.word_button.isChecked() regex = self.regex_button.isChecked() matches = 0 index = 0 if direction > 0: row_range = range(self.table_widget.rowCount() - 1, -1, -1) col_range = range(self.table_widget.columnCount() - 1, -1, -1) else: row_range = range(self.table_widget.rowCount()) col_range = range(self.table_widget.columnCount()) for i in row_range: for j in col_range: item = self.table_widget.item(i, j) if item is not None: text = item.text() if regex: match = QRegularExpression(pattern).match( text).hasMatch() else: if nocase: text = text.lower() pattern = pattern.lower() if word: match = text == pattern else: match = pattern in text if match and pattern: self.table_widget.item(i, j).setBackground(Qt.yellow) if (direction > 0 and (i > row or i == row and j > col)) or \ (direction < 0 and (i < row or i == row and j < col)): self.table_widget.setCurrentCell(i, j) index = matches matches += 1 else: self.table_widget.item(i, j).setBackground(Qt.transparent) if pattern: self.matches_label.setVisible(True) if matches > 0: match = matches - index if direction > 0 else index + 1 self.matches_label.setText( self.tr('match #{}/{}'.format(match, matches))) self.matches_label.setStyleSheet('color: #000000') modify_font(self.matches_label, bold=True) else: self.matches_label.setText(self.tr('not found!')) self.matches_label.setStyleSheet('color: #FF0000') modify_font(self.matches_label, italic=True) else: self.matches_label.setText('') def export(self): settings = QSettings() filename = QFileDialog.getSaveFileName(self, self.tr('Export metadata'), settings.value('save_folder'), self.tr('CSV files (*.csv)'))[0] if not filename: return if not filename.endswith('.csv'): filename += '.csv' settings.setValue('save_folder', QFileInfo(filename).absolutePath()) rows = self.table_widget.rowCount() cols = self.table_widget.columnCount() table = [[None for _ in range(cols)] for __ in range(rows)] for i in range(rows): for j in range(cols): item = self.table_widget.item(i, j) if item is not None: table[i][j] = item.text() with open(filename, 'w') as file: writer = csv.writer(file) writer.writerow(self.table_headers) writer.writerows(table) self.info_message.emit(self.tr('Table contents exported to disk')) def copy(self, item): QApplication.clipboard().setText(item.text()) QToolTip.showText(QCursor.pos(), self.tr('Cell contents copied to clipboard'), self, QRect(), 3000)
class GameControlPanel(QWidget): def __init__(self, first: str, second: str, parent=None): super(GameControlPanel, self).__init__(parent) # buttons on the top self.flipButton = QPushButton("*") self.toStartFenButton = QPushButton("<<") self.previousMoveButton = QPushButton("<") self.nextMoveButton = QPushButton(">") self.toCurrentFenButton = QPushButton(">>") self.flipButton.setFixedWidth(45) self.toStartFenButton.setFixedWidth(45) self.previousMoveButton.setFixedWidth(45) self.nextMoveButton.setFixedWidth(45) self.toStartFenButton.setFixedWidth(45) self.toStartFenButton.setDisabled(True) self.previousMoveButton.setDisabled(True) self.nextMoveButton.setDisabled(True) self.toCurrentFenButton.setDisabled(True) self.toolButtonsLayout = QHBoxLayout() self.toolButtonsLayout.setContentsMargins(0, 0, 0, 0) self.toolButtonsLayout.addWidget(self.flipButton) self.toolButtonsLayout.addWidget(self.toStartFenButton) self.toolButtonsLayout.addWidget(self.previousMoveButton) self.toolButtonsLayout.addWidget(self.nextMoveButton) self.toolButtonsLayout.addWidget(self.toCurrentFenButton) self.toolButtonsLayout.addStretch() self.toolButtonsLayout.setSpacing(14) # the column that contains the first empty cell. self.nextColumn = 0 self.firstMaterial = QLabel() self.firstName = QLabel(first) self.secondName = QLabel(second) self.secondMaterial = QLabel() self.moveTable = QTableWidget() self.moveTable.setEditTriggers(QTableWidget.NoEditTriggers) self.moveTable.setFocusPolicy(Qt.NoFocus) self.moveTable.setSelectionMode(QTableWidget.SingleSelection) self.moveTable.currentCellChanged.connect(self.onCurrentChanged) self.moveTable.setFixedWidth(320) self.moveTable.setColumnCount(2) self.moveTable.horizontalHeader().hide() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addStretch() layout.addWidget(self.firstMaterial) layout.addLayout(self.toolButtonsLayout) layout.addWidget(self.firstName) layout.addWidget(self.moveTable) layout.addWidget(self.secondName) layout.addWidget(self.secondMaterial) layout.addStretch() layout.setSpacing(0) self.setLayout(layout) def isLive(self) -> bool: if not self.moveTable.rowCount(): return True return (self.moveTable.currentRow() == self.moveTable.rowCount() - 1 and self.moveTable.currentColumn() != self.nextColumn) def reset(self): self.moveTable.setRowCount(0) self.nextColumn = 0 self.toStartFenButton.setDisabled(True) self.previousMoveButton.setDisabled(True) self.nextMoveButton.setDisabled(True) self.toCurrentFenButton.setDisabled(True) @Slot(int, int) def onCurrentChanged(self, row, column): self.toStartFenButton.setDisabled(self.moveTable.currentItem() is None) self.previousMoveButton.setDisabled(self.moveTable.currentItem() is None) def toPreviousCell(self): current = self.moveTable.currentItem() if current is not None: row = current.row() column = current.column() if row == column == 0: self.moveTable.setCurrentCell(-1, -1) else: prevCoord = row * 2 + column - 1 if prevCoord % 2: row -= 1 self.moveTable.setCurrentCell(row, not column) def toNextCell(self): current = self.moveTable.currentItem() if self.moveTable.rowCount(): if current is None: self.moveTable.setCurrentCell(0, 0) else: row = current.row() column = current.column() nextCoord = row * 2 + column + 1 if nextCoord % 2 == 0: row += 1 self.moveTable.setCurrentCell(row, not column) def addMove(self, move: str) -> QTableWidgetItem: if self.isLive(): if not self.nextColumn: self.moveTable.setRowCount(self.moveTable.rowCount() + 1) item = QTableWidgetItem(move) self.moveTable.setItem(self.moveTable.rowCount()-1, self.nextColumn, item) self.nextColumn = not self.nextColumn return item def popMove(self): if self.moveTable.rowCount(): if self.isLive(): self.toPreviousCell() self.nextColumn = not self.nextColumn self.moveTable.takeItem(self.moveTable.rowCount() - 1, self.nextColumn) if not self.nextColumn: self.moveTable.setRowCount(self.moveTable.rowCount()-1) def swapNames(self): tempFirstMaterial = self.firstMaterial.text() tempFirstName = self.firstName.text() self.firstMaterial.setText(self.secondMaterial.text()) self.secondMaterial.setText(tempFirstMaterial) self.firstName.setText(self.secondName.text()) self.secondName.setText(tempFirstName)