def setup(self, main_window: QMainWindow) -> None: """ Initialize the UI. :param main_window: An instance of the `QMainWindow` class. :type main_window: :class:`QMainWindow` """ main_window.setObjectName("main_window") main_window.setWindowTitle("TeaseAI") main_window.resize(1137, 751) main_window.setSizePolicy(*EXP_EXP) main_window.setTabShape(QTabWidget.Rounded) self.menubar = QMenuBar(main_window) self.menubar.setObjectName("menubar") self.menubar.setGeometry(0, 0, 1137, 23) self.file_menu = QMenu("File", self.menubar) self.file_menu.setObjectName("file_men") self.server_menu = QMenu("Server", self.menubar) self.server_menu.setObjectName("server_men") self.options_menu = QMenu("Options", self.menubar) self.options_menu.setObjectName("options_men") self.media_menu = QMenu("Media", self.menubar) self.media_menu.setObjectName("media_men") main_window.setMenuBar(self.menubar) self.exit = QAction("Exit", main_window) self.exit.setObjectName("exit") self.start_server = QAction("Start Server", main_window) self.start_server.setObjectName("start_server") self.connect_server = QAction("Connect to Server", main_window) self.connect_server.setObjectName("connect_server") self.kill_server = QAction("Kill Server", main_window) self.kill_server.setObjectName("kill_server") self.options = QAction("Options", main_window) self.options.setObjectName("options") self.start_webcam = QAction("Start Webcam", main_window) self.start_webcam.setObjectName("start_webcam") self.start_webcam.setCheckable(False) self.centralwidget = QWidget(main_window) self.centralwidget.setObjectName("centralwidget") self.centralwidget.setContentsMargins(QMargins(0, 0, 0, 0)) self.centralwidget.setSizePolicy(*EXP_EXP) self.grid_layout = QGridLayout(self.centralwidget) self.media = QFrame(self.centralwidget) self.media.setObjectName("media") self.media.setSizePolicy(*EXP_EXP) self.media.setMinimumSize(200, 200) self.media.setStyleSheet("background: #000;") self.grid_layout.addWidget(self.media, 0, 0, 5, 1) self.users_label = QLabel(" Online users:", self.centralwidget) self.users_label.setObjectName("users_label") self.users_label.setMinimumSize(300, 15) self.users_label.setMaximumSize(300, 15) self.grid_layout.addWidget(self.users_label, 0, 1, 1, 2) self.online = QPlainTextEdit("", self.centralwidget) self.online.setObjectName("online") self.online.setSizePolicy(*FIX_FIX) self.online.setMinimumSize(300, 50) self.online.setMaximumSize(300, 50) self.online.setStyleSheet("margin-left: 3px;" + SUNKEN) self.online.setLineWidth(2) self.online.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.online.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.online.setSizeAdjustPolicy(QAbstractScrollArea.AdjustIgnored) self.online.setReadOnly(True) self.grid_layout.addWidget(self.online, 1, 1, 1, 2) self.chat = QPlainTextEdit("", self.centralwidget) self.chat.setObjectName("chat") self.chat.setSizePolicy(*FIX_EXP) self.chat.setMinimumSize(300, 0) self.chat.setMaximumSize(300, INFINITE) self.chat.setStyleSheet("margin-bottom: 3px; margin-top: 8px;" + SUNKEN) self.chat.setLineWidth(2) self.chat.setReadOnly(True) self.chat.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.grid_layout.addWidget(self.chat, 2, 1, 1, 2) self.input = QLineEdit(self.centralwidget) self.input.setObjectName("input") self.input.setSizePolicy(*FIX_FIX) self.input.setMinimumSize(224, 30) self.input.setMaximumSize(224, 30) self.input.setStyleSheet(SUNKEN) self.input.setEchoMode(QLineEdit.Normal) self.input.setClearButtonEnabled(True) self.grid_layout.addWidget(self.input, 3, 1, 1, 1) self.submit = QPushButton("Submit", self.centralwidget) self.submit.setObjectName("submit") self.submit.setSizePolicy(*FIX_FIX) self.submit.setMinimumSize(70, 30) self.submit.setMaximumSize(70, 30) self.grid_layout.addWidget(self.submit, 3, 2, 1, 1) self.tabs = QTabWidget(self.centralwidget) self.tabs.setObjectName("tabs") self.tabs.setSizePolicy(*FIX_FIX) self.tabs.setMinimumSize(300, 150) self.tabs.setMaximumSize(300, 150) self.tab = QWidget() self.tab.setObjectName("tab") self.tabs.addTab(self.tab, "Actions") self.tab2 = QWidget() self.tab2.setObjectName("tab2") self.tabs.addTab(self.tab2, "My Media") self.tab3 = QWidget() self.tab3.setObjectName("tab3") self.tab3.setSizePolicy(*FIX_FIX) self.grid_layout2 = QGridLayout(self.tab3) self.grid_layout2.setHorizontalSpacing(0) self.grid_layout2.setVerticalSpacing(3) self.grid_layout2.setContentsMargins(3, -1, 3, -1) self.server_folder = QLineEdit(self.tab3) self.server_folder.setObjectName("server_folder") self.grid_layout2.addWidget(self.server_folder, 0, 0, 1, 3) self.srv_browse = QPushButton("BROWSE", self.tab3) self.srv_browse.setObjectName("srv_browse") self.srv_browse.setStyleSheet("background: transparent;\n" " color: #4d4940;\n" " font-size: 8pt;\n" " font-weight: 450;\n" " padding: 6px;\n") self.grid_layout2.addWidget(self.srv_browse, 0, 3, 1, 1) self.back_button = QPushButton("", self.tab3) self.back_button.setObjectName("back_button") self.back_button.setSizePolicy(*FIX_FIX) self.back_button.setMaximumSize(SEVENTY_FIVE) self.back_button.setCursor(QCursor(Qt.PointingHandCursor)) self.back_button.setStyleSheet("border: 0;\n" "background: transparent;") icon = QIcon() icon.addFile(":/newPrefix/back_button.png", SIXTY_FOUR, QIcon.Normal, QIcon.Off) self.back_button.setIcon(icon) self.back_button.setIconSize(SIXTY_FOUR) self.grid_layout2.addWidget(self.back_button, 1, 0, 1, 1) self.play_button = QPushButton("", self.tab3) self.play_button.setObjectName("play_button") self.play_button.setSizePolicy(*FIX_FIX) self.play_button.setMaximumSize(SEVENTY_FIVE) self.play_button.setCursor(QCursor(Qt.PointingHandCursor)) self.play_button.setStyleSheet("border: 0;\n" "background: transparent;") icon1 = QIcon() icon1.addFile(":/newPrefix/play_button.png", SIXTY_FOUR, QIcon.Normal, QIcon.Off) self.play_button.setIcon(icon1) self.play_button.setIconSize(SIXTY_FOUR) self.grid_layout2.addWidget(self.play_button, 1, 1, 1, 1) self.stop_button = QPushButton("", self.tab3) self.stop_button.setObjectName("stop_button") self.stop_button.setSizePolicy(*FIX_FIX) self.stop_button.setMaximumSize(SEVENTY_FIVE) self.stop_button.setCursor(QCursor(Qt.PointingHandCursor)) self.stop_button.setStyleSheet("border: 0;\n" "background: transparent;") icon2 = QIcon() icon2.addFile(":/newPrefix/stop_button.png", SIXTY_FOUR, QIcon.Normal, QIcon.Off) self.stop_button.setIcon(icon2) self.stop_button.setIconSize(SIXTY_FOUR) self.grid_layout2.addWidget(self.stop_button, 1, 2, 1, 1) self.fast_forward = QPushButton("", self.tab3) self.fast_forward.setObjectName("fast_forward") self.fast_forward.setSizePolicy(*FIX_FIX) self.fast_forward.setMaximumSize(SEVENTY_FIVE) self.fast_forward.setCursor(QCursor(Qt.PointingHandCursor)) self.fast_forward.setStyleSheet("border: 0;\n" "background: transparent;") icon3 = QIcon() icon3.addFile(":/newPrefix/fast_forward.png", SIXTY_FOUR, QIcon.Normal, QIcon.Off) self.fast_forward.setIcon(icon3) self.fast_forward.setIconSize(SIXTY_FOUR) self.grid_layout2.addWidget(self.fast_forward, 1, 3, 1, 1) self.tabs.addTab(self.tab3, "Server Media") self.grid_layout.addWidget(self.tabs, 4, 1, 1, 2) main_window.setCentralWidget(self.centralwidget) self.statusbar = QStatusBar(main_window) self.statusbar.setObjectName("statusbar") self.statusbar.setEnabled(True) self.statusbar.setStyleSheet("margin-bottom: 5px;") self.statusbar.setSizePolicy(*EXP_FIX) self.statusbar.setMinimumSize(INFINITE, 30) self.statusbar.setMaximumSize(INFINITE, 30) self.statusbar.setSizeGripEnabled(False) main_window.setStatusBar(self.statusbar) self.menubar.addAction(self.file_menu.menuAction()) self.menubar.addAction(self.server_menu.menuAction()) self.menubar.addAction(self.options_menu.menuAction()) self.menubar.addAction(self.media_menu.menuAction()) self.file_menu.addAction(self.exit) self.server_menu.addAction(self.start_server) self.server_menu.addAction(self.connect_server) self.server_menu.addAction(self.kill_server) self.options_menu.addAction(self.options) self.media_menu.addAction(self.start_webcam) self.exit.triggered.connect(main_window.close) self.tabs.setCurrentIndex(0) QMetaObject.connectSlotsByName(main_window) self.exit.setStatusTip("Exit the program.") self.start_server.setStatusTip("Initialize a local server instance.") self.connect_server.setStatusTip("Connect to a remote server.") self.kill_server.setStatusTip("Shut down a running local server.") self.options.setStatusTip("Open the options menu.") self.start_webcam.setStatusTip("Start webcam feed.") self.tooltip = QLabel("", self.statusbar) tooltip_policy = QSizePolicy(*EXP_FIX) tooltip_policy.setHorizontalStretch(100) self.tooltip.setSizePolicy(tooltip_policy) self.tooltip.setMinimumSize(INFINITE, 26) self.tooltip.setMaximumSize(INFINITE, 26) self.server_status = QLabel("Server status:", self.statusbar) self.server_status.setSizePolicy(*FIX_FIX) self.server_status.setMinimumSize(300, 26) self.server_status.setMaximumSize(300, 26) self.client_status = QLabel("Client status:", self.statusbar) self.client_status.setSizePolicy(*FIX_FIX) self.client_status.setMinimumSize(302, 26) self.client_status.setMaximumSize(302, 26) self.statusbar.addPermanentWidget(self.tooltip) self.statusbar.addPermanentWidget(self.server_status) self.statusbar.addPermanentWidget(self.client_status) self.tooltip.setStyleSheet(SUNKEN + "margin-left: 4px;\ margin-right: 0px;") self.client_status.setStyleSheet(SUNKEN + "margin-right: 7px;") self.server_status.setStyleSheet(SUNKEN + "margin-right: 2px;\ margin-left: 2px;") self.statusbar.messageChanged.connect(main_window.status_tip)
class PredictionWindow(QWidget): """Subwindow dedicated to random forest prediction functions.""" # Private Instance Attributes: # - _predict_btn: Button that signals for a prediction to be made. # - _predict_btn: Button that signals for the selected model to be deleted. # - _model: The RandomForest model currently loaded. # - _model_info: QPlainTextEdit widget displaying information about the selected model. # - _target_date: Calendar widget for the user to select a target prediction date. # -_plot_window: Window for displaying historical information with a prediction. _predict_btn: QDialogButtonBox _delete_btn: QDialogButtonBox _model: QComboBox _model_info: QPlainTextEdit _target_date: QCalendarWidget _plot_window: QMainWindow def __init__(self) -> None: super().__init__() main_layout = QGridLayout() button_box = self._create_button_box() main_layout.addWidget(self._create_options_group_box(), 0, 0) main_layout.addWidget(button_box, 1, 0) main_layout.setSizeConstraint(QLayout.SetMinimumSize) self.setLayout(main_layout) self._refresh_model_info() self.setWindowTitle('Predict') self._plot_window = QMainWindow() def show(self) -> None: """Override of QWidget's show() function. Refreshes window and then shows the window. """ self._refresh_lists() return super().show() @property def _selected_model(self) -> Union[RandomForest, None]: """Gets the currently selected model.""" model_name = self._model.currentText() if model_name != '': try: return load_model(model_name) except pickle.UnpicklingError: self._error_event(f'{model_name} is an invalid model.') return None else: return None def _create_button_box(self) -> QDialogButtonBox: """Creates the lower control buttons at the bottom of the window.""" button_box = QDialogButtonBox() self._predict_btn = button_box.addButton('Predict', QDialogButtonBox.ActionRole) self._delete_btn = button_box.addButton('Delete Model', QDialogButtonBox.ActionRole) refresh_btn = button_box.addButton('Refresh &Options', QDialogButtonBox.ActionRole) self._predict_btn.clicked.connect(self._predict) refresh_btn.clicked.connect(self._refresh_lists) self._delete_btn.clicked.connect(self._delete) return button_box def _create_options_group_box(self) -> QGroupBox: """Returns the group of prediction options.""" options_group_box = QGroupBox("Options") options_layout = QGridLayout() left_options = QGridLayout() right_options = QGridLayout() date_label = QLabel("Target Date:") self._target_date = QCalendarWidget() left_options.addWidget(date_label, 0, 0) left_options.addWidget(self._target_date, 1, 0, 1, 3) left_options.setColumnStretch(0, 1) self._model = QComboBox() self._model_info = QPlainTextEdit() self._model_info.setReadOnly(True) self._model.currentTextChanged.connect(self._refresh_model_info) self._refresh_lists() models_label = QLabel("Models:") info_label = QLabel("Model Information:") right_options.addWidget(models_label, 0, 0) right_options.addWidget(self._model, 1, 0) right_options.addWidget(info_label, 2, 0) right_options.addWidget(self._model_info, 3, 0) options_layout.addLayout(left_options, 0, 0) options_layout.addLayout(right_options, 0, 1) options_group_box.setLayout(options_layout) return options_group_box def _delete(self) -> None: """Deletes the currently selected dataset.""" self.setEnabled(False) name = self._model.currentText() warning = f'Are you sure you want to delete {name}?' response = QMessageBox.warning(self, self.tr("Delete Model"), warning, QMessageBox.Yes, QMessageBox.No) if response == QMessageBox.Yes: data_ingest.delete_data(name, file_type='model') self._refresh_lists() self.setEnabled(True) def _refresh_lists(self) -> None: """Refreshes avaliable datasets for training.""" self._model.clear() data_list = data_ingest.get_avaliable_data(search_type='model') self._model.addItems(data_list) def _refresh_model_info(self) -> None: """Refreshes avaliable features for the selected target.""" self._predict_btn.setEnabled(False) self._target_date.setEnabled(False) self._model_info.clear() model_name = self._model.currentText() model = self._selected_model if model is None: self._delete_btn.setEnabled(False) return None self._delete_btn.setEnabled(True) self._display_model_info(model) req_features = model.window.req_features avaliable_sym = data_ingest.get_avaliable_sym() if len(req_features - avaliable_sym) != 0: self._error_event( f'Missing required data for {model_name}: {req_features - avaliable_sym}' ) return None dfs = load_corresponding_dataframes(model) grouped_dataframe = data_ingest.create_grouped_dataframe(dfs) date_offset = pd.DateOffset(days=model.window.target_shift) self._target_date.setMaximumDate(grouped_dataframe.index.max() + date_offset) self._target_date.setMinimumDate(grouped_dataframe.index.min() + date_offset) self._target_date.setEnabled(True) self._predict_btn.setEnabled(True) return None def _display_model_info(self, model: RandomForest) -> None: """Updates model info box to display current model's information.""" self._model_info.appendPlainText( f'Target Feature Name: \n{model.window.target_lbl}') self._model_info.appendPlainText('Window Information:') self._model_info.appendPlainText( f'\t- Window Size: {model.window.window_size}') self._model_info.appendPlainText( f'\t- Target Shift: {model.window.target_shift}') self._model_info.appendPlainText( f'\t- Required Features: {model.window.req_features}') self._model_info.appendPlainText('Forest Information:') self._model_info.appendPlainText( f'\t- Number of Trees: {model.forest.n_trees}') self._model_info.appendPlainText( f'\t- Tree Max Depth: {model.forest.max_depth}') self._model_info.appendPlainText(f'\t- Seed: {model.forest.seed}') def _predict(self) -> None: """Creates a model prediction using the selected target date and model.""" self.setEnabled(False) self._predict_btn.setEnabled(False) model = self._selected_model if model is None: self.setEnabled(True) return None target_date = self._target_date.selectedDate().toPython() try: dfs = load_corresponding_dataframes(model) prediction_input = data_ingest.create_input( model.window.window_size, model.window.target_shift, target_date, dfs) except ce.MissingData: self._error_event( 'Missing required data. Could be that loaded datasets have holes.' ) self.setEnabled(True) return None prediction = model.predict(prediction_input) historical_dfs = load_corresponding_dataframes(model, 'target') if len(historical_dfs) == 0: self._prediction_historical_error(prediction) else: self._plot_prediction(historical_dfs, model, prediction, target_date) self._predict_btn.setEnabled(True) self.setEnabled(True) return None def _plot_prediction(self, historical_dfs: list[pd.DataFrame], model: RandomForest, prediction: ndarray, target_date: datetime.date) -> None: """Opens a window with a plot of the historical target data as well as the prediction the model made.""" hdf = historical_dfs[0] for frame in historical_dfs[1:]: hdf = hdf.combine_first(frame) window_end = target_date - \ pd.DateOffset(days=model.window.target_shift) window_start = window_end - pd.DateOffset(days=30 - 1) hdf = pd.Series( hdf.loc[window_end:window_start][model.window.target_lbl]) hdf_data = hdf.to_list() hdf_dates = hdf.index hdf_dates = [ts.to_pydatetime().timestamp() for ts in hdf_dates] b_axis = pg.DateAxisItem(orientation='bottom') b_axis.setLabel('Date') plot = PlotWidget(axisItems={'bottom': b_axis}) target_time = datetime.combine(target_date, datetime.min.time()) plot.addLegend() plot.plot(x=hdf_dates, y=hdf_data, name=f'Historical {model.window.target_lbl}') plot.plot(x=[target_time.timestamp()], y=prediction, pen=None, symbol='o', name=f'Predicted Value: {prediction[0]}') model_name = self._model.currentText() self._plot_window.setWindowTitle(f'{model_name} Prediction') self._plot_window.setCentralWidget(plot) self._plot_window.show() def _prediction_historical_error(self, prediction: list) -> None: """Displays a message for when historical target is unavalable such that a graph can't be made.""" QMessageBox.information( self, self.tr("Information"), f'Prediction was: {prediction}. \n ' 'Unable to display graph due to missing historical data.', QtWidgets.QMessageBox.Ok) def _error_event( self, error: str, choice: bool = False, btn: QMessageBox = QMessageBox.Abort ) -> Union[QMessageBox.Ignore, QMessageBox.Abort, None]: """Displays an error message with the given error.""" if choice: response = QMessageBox.critical(self, self.tr("Error"), error, btn, QMessageBox.Ignore) return response else: QMessageBox.critical(self, self.tr("Error"), error, QMessageBox.Ok) return None
import sys from PySide6.QtCore import QFile from PySide6.QtWidgets import QMainWindow, QApplication from PySide6.QtUiTools import QUiLoader from . import LinearStageControlGUI def load_ui(ui_file, parent=None): loader = QUiLoader() file = QFile(ui_file) file.open(QFile.ReadOnly) myWidget = loader.load(file, None) myWidget.show() file.close() myWidget.show() return myWidget if __name__ == "__main__": # init application app = QApplication(sys.argv) widget = QMainWindow() widget.setWindowTitle("Linear Stage Control") widget.resize(309, 202) widget.ui = LinearStageControlGUI(widget) widget.show() # execute qt main loop sys.exit(app.exec_())
class MainUI(QApplication): def __init__(self, saveDefaultPath: List[str] = None, loadFile: List[str] = None, time: float = None, enableIa: bool = None): super(MainUI, self).__init__(argv) if loadFile: self.game = Game.loadJson(loadFile[0], self.trigger, allowedTime=time) else: self.game = Game(self.trigger, allowedTime=time) self.enableIa = enableIa self.saveDefaultPath = None if not saveDefaultPath or saveDefaultPath.__class__ != list else saveDefaultPath[ 0] self.ia = MinMax(game=self.game) def initialize(self): screen = QGuiApplication.primaryScreen().size() size = screen.height() * 0.7 self.window = QMainWindow() self.window.setAutoFillBackground(True) self.window.setWindowFlag( PySide6.QtCore.Qt.WindowType.WindowMaximizeButtonHint, False) self.window.setWindowTitle('Chess') self.window.resize(size, size) self.window.move((screen.width() - self.window.width()) // 2, (screen.height() - self.window.height()) // 2) self.window.show() self.margin = 10 self.button_size = (size - 2 * self.margin) // 8 def construct(self): self.container = QWidget() self.content = QWidget(parent=self.container) # self.content = QWidget() self.window.setCentralWidget(self.container) self.constructMenu() self.constructButtons() self.constructChoices() self.createClock() self.updateView() self.window.resizeEvent = self.onResize @staticmethod def originalTileColor(i: int, j: int) -> ColorPalette: return ColorPalette.TILE_ONE if i % 2 == j % 2 else ColorPalette.TILE_TWO def onGridClicked(self, i, j): if self.selected != None and (self.selected, (i, j)) in self.previousSuggestions: self.game.move(self.selected, (i, j)) if self.enableIa and not self.game.player: move = self.ia.generate() self.game.move(frm=move[0], to=move[1]) self.selected = None self.previousSuggestions = None if self.game.map[i, j] != Piece.EMPTY: self.selected = (i, j) self.previousSuggestions = self.game.getAvailableMoves(i, j) self.updateView() def onUpgrade(self, pieceIndex): self.game.choice(self.getChoices()[pieceIndex]) def updateView(self, event: QResizeEvent = None): self.content.setEnabled(self.game.winner() == Result.RUNNING) if self.game.upgrade: self.choicesView.show() self.content.setDisabled(True) else: self.choicesView.hide() self.content.setDisabled(False) if not event or event.oldSize().width() - event.size().width(): self.button_size = (self.window.width() - 2 * self.margin) // 8 elif not event or event.oldSize().height() - event.size().height(): self.button_size = (self.window.height() - 2 * self.margin - self.window.menuBar().height() - self.clockContainer.height()) // 8 boardSize = 2 * self.margin + 8 * self.button_size self.container.resize(boardSize, boardSize + self.clockContainer.height()) self.window.resize( self.container.width(), self.container.height() + self.window.menuBar().height()) self.choicesView.resize( len(self.choicesButtons) * (self.margin + self.button_size) + self.margin, self.margin * 2 + self.button_size) self.choicesView.move( (boardSize - self.choicesView.width()) // 2, (self.content.height() - self.choicesView.height()) // 2) self.content.resize(boardSize, boardSize) self.clockContainer.resize(boardSize, self.clockContainer.height()) self.content.move(0, self.clockContainer.height()) self.clockB.move(boardSize - self.clockB.width() - self.margin, self.margin) for i, j in self.buttons: self.buttons[i, j].resize(self.button_size, self.button_size) self.buttons[i, j].move(self.button_size * j + self.margin, self.button_size * i + self.margin) self.buttons[i, j].setIcon(self.game.map[i, j].getIcon()) self.setBackgourdColor(self.buttons[i, j], self.originalTileColor(i, j)) self.buttons[i, j].setIconSize( QSize(self.buttons[i, j].width() * 2 / 3, self.buttons[i, j].height() * 2 / 3)) winner = self.game.winner() if winner != Result.RUNNING: self.showDialog( str(winner) + "\nPlease start a new game.", "Info", QMessageBox.Ok, lambda _: None) return for i in range(len(self.choicesButtons)): self.choicesButtons[i].setIcon(self.getChoices()[i].getIcon()) self.choicesButtons[i].setIconSize( QSize(self.button_size * 2 / 3, self.button_size * 2 / 3)) self.choicesButtons[i].resize(self.button_size, self.button_size) btnW = self.button_size + self.margin self.choicesButtons[i].move( self.margin // 2 + (self.choicesView.width() - btnW * len(self.choicesButtons)) // 2 + btnW * i, (self.choicesView.height() - self.button_size) // 2) if self.selected: self.setBackgourdColor( self.buttons[self.selected[0], self.selected[1]], ColorPalette.ACTIVE_COLOR) if (self.previousSuggestions): for (_, (mi, mj)) in self.previousSuggestions: self.setBackgourdColor( self.buttons[mi, mj], ColorPalette.ATTACK if self.game.map[mi, mj] != Piece.EMPTY else ColorPalette.SUGGESTED_MOVE) if self.game.check(): self.setBackgourdColor( self.buttons[self.game.getPiece( Piece.P1_KING if self.game.player else Piece.P2_KING)], ColorPalette.CHECK) self.undo.setEnabled(len(self.game.undoes) > 0) self.redo.setEnabled(len(self.game.redos) > 0) @staticmethod def setBackgourdColor(widget: QWidget, color: ColorPalette): if 'background-color' in widget.styleSheet(): bk_index_start = widget.styleSheet().index('background-color') bk_index_end = widget.styleSheet().index(';', bk_index_start) + 1 widget.setStyleSheet(widget.styleSheet()[:bk_index_start] + widget.styleSheet()[bk_index_end:] + 'background-color: ' + color.value + ';') else: widget.setStyleSheet(widget.styleSheet() + 'background-color: ' + color.value + ';') def onResize(self, event: QResizeEvent): self.updateView(event) def getChoices(self) -> List[Piece]: return [ Piece(choice.value % Piece.P2_PAWN.value + (0 if self.game.player else Piece.P2_PAWN.value)) for choice in [Piece.P1_ROOK, Piece.P1_KNIGHT, Piece.P1_BISHOP, Piece.P1_QUEEN] ] def trigger(self): self.updateView() def constructChoices(self): self.game.trigger = self.trigger self.choicesView: QWidget = QWidget(parent=self.container) # self.choicesBackground: QWidget = QWidget(parent=self.choicesView) self.choicesButtons: list[QPushButton] = [] self.choicesView.setStyleSheet("border: solid;border-width: 2;") self.setBackgourdColor(widget=self.choicesView, color=ColorPalette.CHOICES_BACKGROUND) for i in range(len(self.getChoices())): self.choicesButtons.append(QPushButton(self.choicesView)) self.choicesButtons[i].clicked.connect( functools.partial(self.onUpgrade, i)) def constructButtons(self): self.buttons: List[QPushButton] = {} self.selected = None self.previousSuggestions = None for i in range(8): for j in range(8): self.buttons[i, j] = QPushButton(self.content) self.buttons[i, j].clicked.connect( functools.partial(self.onGridClicked, i, j)) self.buttons[i, j].setStyleSheet( f"background-color: {self.originalTileColor(i, j).value};") def constructMenu(self): menuBar = QMenuBar() self.window.setMenuBar(menuBar) file = menuBar.addMenu('File') edit = menuBar.addMenu('Edit') file.addAction('New Game').triggered.connect( functools.partial(self.newGame, )) self.undo = edit.addAction('Undo') self.undo.triggered.connect(lambda _: self.game.undo()) self.redo = edit.addAction('Redo') self.redo.triggered.connect(lambda _: self.game.redo()) sv = file.addMenu('Save') sv.addAction('as text').triggered.connect( functools.partial(self.save, self.game.saveAsText)) sv.addAction('as PGN').triggered.connect( functools.partial(self.save, self.game.saveAsPGN)) sv.addAction('as JSON').triggered.connect( functools.partial(self.save, self.game.saveAsJSON)) sva = file.addMenu('Save As') sva.addAction('as text').triggered.connect( functools.partial(self.saveAs, self.game.saveAsText)) sva.addAction('as PGN').triggered.connect( functools.partial(self.saveAs, self.game.saveAsPGN)) sva.addAction('as JSON').triggered.connect( functools.partial(self.saveAs, self.game.saveAsJSON)) file.addAction('Load Game').triggered.connect( functools.partial(self.load, self.game.loadJson)) file.addAction('Abandon').triggered.connect( functools.partial(self.showDialog, "Do you want really to abandon?", "Warning", QMessageBox.No | QMessageBox.Yes, self.abandon)) file.addAction('Draw').triggered.connect( functools.partial(self.showDialog, ("White" if self.game.player else "Black") + " is proposing draw", "Info", QMessageBox.No | QMessageBox.Yes, self.proposeDraw)) file.addAction('Quit').triggered.connect(lambda _: exit(0)) def save(self, f): if self.saveDefaultPath: f(self.saveDefaultPath) else: self.saveAs(f) def saveAs(self, f): saveAs = QFileDialog() saveAs.setDefaultSuffix('.json') self.timer.stop() fileName, _ = saveAs.getSaveFileName(parent=self.window, filter="JSON (*.json)", caption="Save as json file.") self.timer.start(1) if fileName: self.saveDefaultPath = fileName self.save(f) def load(self, f): self.timer.stop() fileName, _ = QFileDialog.getOpenFileName(parent=self.window, filter="JSON (*.json)") self.timer.start() if fileName: self.saveDefaultPath = fileName self.game: Game = f(fileName, trigger=self.trigger, maxTime=self.game.allowedTime) self.updateView() def newGame(self): self.game: Game = Game(self.trigger, allowedTime=self.game.allowedTime) self.construct() def createClock(self): self.clockContainer = QWidget(self.container) self.clockW: QLabel = QLabel(parent=self.clockContainer, text=self.format( self.game.getRemainingTime(True))) self.clockB: QLabel = QLabel(parent=self.clockContainer, text=self.format( self.game.getRemainingTime(False))) self.clockW.move(self.margin, self.margin) self.timer: QTimer = QTimer(self.clockContainer) self.timer.timeout.connect(self.updateTime) self.timer.start(1000) def showDialog(self, text: str, title: str, buttons, callback): dialog = QMessageBox(self.window) dialog.setWindowTitle(title) dialog.setText(text) dialog.setStandardButtons(buttons) dialog.buttonClicked.connect(callback) dialog.exec_() def abandon(self, value: QPushButton): if value.text().lower().count('yes'): self.game.abandon = self.game.player self.updateView() def proposeDraw(self, value: QPushButton): if value.text().lower().count('yes'): self.game.draw = True self.updateView() def updateTime(self): wr = self.game.getRemainingTime(True) br = self.game.getRemainingTime(False) if self.game.winner() == Result.RUNNING: c = wr if self.game.player else br self.timer.start(c - int(c) if c > 0 else 1) else: self.timer.stop() self.updateView() self.clockW.setText(self.format(wr)) self.clockB.setText(self.format(br)) def format(self, time: float): if time <= 0: return "tmout" st = "" if time >= 3600: st += f"{time//3600}:" time = time % 3600 st += '0' if time // 60 < 10 else '' st += f"{time//60}:" time = time % 60 st += '0' if time < 10 else '' st += f"{int(time)}" return st