def test():
    import sys

    from PySide6.QtWidgets import QApplication

    app = QApplication(sys.argv)
    win = QMainWindow()
    MainWindowUI().setup_ui(win)
    win.show()
    sys.exit(app.exec_())
Exemple #2
0
def test():
    import sys

    from tester.AutoLab.utils.qthelpers import create_qt_app
    from tester.config.manager import get_settings

    app = create_qt_app()
    win = QMainWindow()
    settings = get_settings()
    MainWindowUI().setup_ui(win, settings)
    win.show()
    sys.exit(app.exec_())
Exemple #3
0
def QVTKRenderWidgetConeExample():
    """A simple example that uses the QVTKRenderWindowInteractor class."""

    from vtkmodules.vtkFiltersSources import vtkConeSource
    from vtkmodules.vtkRenderingCore import vtkActor, vtkPolyDataMapper, vtkRenderer
    # load implementations for rendering and interaction factory classes
    import vtkmodules.vtkRenderingOpenGL2
    import vtkmodules.vtkInteractionStyle

    # every QT app needs an app
    app = QApplication(['QVTKRenderWindowInteractor'])

    window = QMainWindow()

    # create the widget
    widget = QVTKRenderWindowInteractor(window)
    window.setCentralWidget(widget)
    # if you don't want the 'q' key to exit comment this.
    widget.AddObserver("ExitEvent", lambda o, e, a=app: a.quit())

    ren = vtkRenderer()
    widget.GetRenderWindow().AddRenderer(ren)

    cone = vtkConeSource()
    cone.SetResolution(8)

    coneMapper = vtkPolyDataMapper()
    coneMapper.SetInputConnection(cone.GetOutputPort())

    coneActor = vtkActor()
    coneActor.SetMapper(coneMapper)

    ren.AddActor(coneActor)

    # show the widget
    window.show()

    widget.Initialize()
    widget.Start()

    # start event processing
    # Source: https://doc.qt.io/qtforpython/porting_from2.html
    # 'exec_' is deprecated and will be removed in the future.
    # Use 'exec' instead.
    try:
        app.exec()
    except AttributeError:
        app.exec_()
Exemple #4
0
        alignment = self.ui.legendComboBox.itemData(idx)

        if not alignment:
            for chart_view in self.charts:
                chart_view.chart().legend().hide()
        else:
            for chart_view in self.charts:
                alignment_name = Qt.AlignTop
                if alignment == 32:
                    alignment_name = Qt.AlignTop
                elif alignment == 64:
                    alignment_name = Qt.AlignBottom
                elif alignment == 1:
                    alignment_name = Qt.AlignLeft
                elif alignment == 2:
                    alignment_name = Qt.AlignRight
                chart_view.chart().legend().setAlignment(alignment_name)
                chart_view.chart().legend().show()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = QMainWindow()
    widget = ThemeWidget(None)
    window.setCentralWidget(widget)
    available_geometry = app.desktop().availableGeometry(window)
    size = available_geometry.height() * 0.75
    window.setFixedSize(size, size * 0.8)
    window.show()
    sys.exit(app.exec_())
Exemple #5
0
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_())
Exemple #6
0
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
Exemple #7
0
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
Exemple #8
0
    def getInputDir(self):
        dir = QFileDialog.getExistingDirectory(None, "Select folder",
                                               self.userDesktop)

        if dir:
            path = dir.replace("/", "\\")
            self.InputLineEdit.setText(path)

    def getOuputDir(self):
        dir = QFileDialog.getExistingDirectory(None,
                                               "Select save folder location",
                                               self.userDesktop)

        if dir:
            path = dir.replace("/", "\\")
            self.OutputLineEdit.setText(path)


if __name__ == "__main__":
    os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"  # For High DPI Displays
    app = QApplication(sys.argv)  # Create the Qt Application
    app.setAttribute(
        QtCore.Qt.AA_EnableHighDpiScaling)  # For High DPI Displays
    MainWindow = QMainWindow()

    # Create an instance
    ui = TheApp(MainWindow)

    # Show the window and start the app
    MainWindow.show()
    app.exec()
class DebugExecutorWindow(MemoryOperationExecutor, Ui_DebugBackendWindow):
    _connected: bool = False
    _timer: QtCore.QTimer
    pickups: List[PickupEntry]

    def __init__(self):
        super().__init__()
        self.logger.setLevel(logging.DEBUG)
        self.window = QMainWindow()
        self.setupUi(self.window)
        common_qt_lib.set_default_window_icon(self.window)

        self.pickups = []

        self.collect_location_combo.setVisible(False)
        self.setup_collect_location_combo_button = QtWidgets.QPushButton(
            self.window)
        self.setup_collect_location_combo_button.setText(
            "Load list of locations")
        self.setup_collect_location_combo_button.clicked.connect(
            self._setup_locations_combo)
        self.gridLayout.addWidget(self.setup_collect_location_combo_button, 0,
                                  0, 1, 2)

        self.collect_location_button.clicked.connect(self._emit_collection)
        self.collect_location_button.setEnabled(False)
        self.collect_randomly_check.stateChanged.connect(
            self._on_collect_randomly_toggle)
        self._timer = QtCore.QTimer(self.window)
        self._timer.timeout.connect(self._collect_randomly)
        self._timer.setInterval(10000)

        self._used_version = echoes_dol_versions.ALL_VERSIONS[0]
        self._connector = EchoesRemoteConnector(self._used_version)
        self.game = default_database.game_description_for(
            RandovaniaGame.METROID_PRIME_ECHOES)

        self._game_memory = bytearray(24 * (2**20))
        self._game_memory_initialized = False

    def _on_collect_randomly_toggle(self, value: int):
        if bool(value):
            self._timer.start()
        else:
            self._timer.stop()

    def _collect_randomly(self):
        row = random.randint(0, self.collect_location_combo.count())
        self._collect_location(self.collect_location_combo.itemData(row))

    async def _ensure_initialized_game_memory(self):
        if self._game_memory_initialized:
            return

        world = self.game.world_list.worlds[0]

        await self.perform_memory_operations([
            # Build String
            MemoryOperation(self._used_version.build_string_address,
                            write_bytes=self._used_version.build_string),

            # current CWorld
            MemoryOperation(self._used_version.game_state_pointer,
                            offset=4,
                            write_bytes=world.extra['asset_id'].to_bytes(
                                4, "big")),

            # CPlayer VTable
            MemoryOperation(
                self._used_version.cstate_manager_global + 0x14fc,
                offset=0,
                write_bytes=self._used_version.cplayer_vtable.to_bytes(
                    4, "big")),

            # CPlayerState
            MemoryOperation(self._used_version.cstate_manager_global + 0x150c,
                            write_bytes=0xA00000.to_bytes(4, "big")),
        ])

        self._game_memory_initialized = True

    def _read_memory(self, address: int, count: int):
        address &= ~0x80000000
        return self._game_memory[address:address + count]

    def _read_memory_format(self, format_string: str, address: int):
        return struct.unpack_from(format_string, self._game_memory,
                                  address & ~0x80000000)

    def _write_memory(self, address: int, data: bytes):
        address &= ~0x80000000
        self._game_memory[address:address + len(data)] = data
        self.logger.info(f"Wrote {data.hex()} to {hex(address)}")

    async def _update_inventory_label(self):
        inventory = await self._connector.get_inventory(self)

        s = "<br />".join(f"{name} x {quantity.amount}/{quantity.capacity}"
                          for name, quantity in inventory.items())
        self.inventory_label.setText(s)

    def show(self):
        self.window.show()

    def _get_magic_address(self):
        multiworld_magic_item = self.game.resource_database.multiworld_magic_item
        return _echoes_powerup_address(multiworld_magic_item.index)

    def _read_magic(self):
        return self._read_memory_format(">II", self._get_magic_address())

    def _write_magic(self, magic_amount, magic_capacity):
        self._write_memory(self._get_magic_address(),
                           struct.pack(">II", magic_amount, magic_capacity))

    def _emit_collection(self):
        self._collect_location(self.collect_location_combo.currentData())

    def _collect_location(self, location: int):
        new_magic_value = location + 1
        magic_amount, magic_capacity = self._read_magic()
        if magic_amount == 0:
            magic_amount += new_magic_value
            magic_capacity += new_magic_value
            self._write_magic(magic_amount, magic_capacity)

    @asyncSlot()
    @handle_network_errors
    async def _setup_locations_combo(self):
        network_client = common_qt_lib.get_network_client()
        game_session = network_client.current_game_session
        user = network_client.current_user

        game = self.game
        index_to_name = {
            node.pickup_index.index: game.world_list.area_name(area)
            for world, area, node in game.world_list.all_worlds_areas_nodes
            if isinstance(node, PickupNode)
        }

        if game_session is None:
            names = index_to_name
        else:
            patcher_data = await network_client.session_admin_player(
                user.id, SessionAdminUserAction.CREATE_PATCHER_FILE,
                BaseCosmeticPatches().as_json)
            names = {
                pickup["pickup_index"]:
                "{}: {}".format(index_to_name[pickup["pickup_index"]],
                                pickup["hud_text"][0].split(", ", 1)[0])
                for pickup in patcher_data["pickups"]
            }

        self.collect_location_combo.clear()
        for index, name in sorted(names.items(), key=lambda t: t[1]):
            self.collect_location_combo.addItem(name, index)

        self.collect_location_button.setEnabled(True)
        self.collect_location_combo.setVisible(True)
        self.setup_collect_location_combo_button.deleteLater()

    def clear(self):
        self.messages_list.clear()
        self.pickups.clear()

    def _memory_operation(self, op: MemoryOperation) -> Optional[bytes]:
        op.validate_byte_sizes()

        address = op.address
        if op.offset is not None:
            address = self._read_memory_format(">I", address)[0]
            address += op.offset

        result = None
        if op.read_byte_count is not None:
            result = self._read_memory(address, op.read_byte_count)
        if op.write_bytes is not None:
            self._write_memory(address, op.write_bytes)
        return result

    def _add_power_up(self, registers: Dict[int, int]):
        item_id = registers[4]
        delta = registers[5]
        item = next(item for item in self.game.resource_database.item
                    if item.extra.get("item_id") == item_id)
        address = _echoes_powerup_address(item_id)

        amount, capacity = self._read_memory_format(">II", address)
        capacity = max(0, min(capacity + delta, item.max_capacity))
        amount = min(amount, capacity)
        self._write_memory(address, struct.pack(">II", amount, capacity))

    def _incr_pickup(self, registers: Dict[int, int]):
        item_id = registers[4]
        delta = registers[5]
        address = _echoes_powerup_address(item_id)

        amount, capacity = self._read_memory_format(">II", address)
        amount = max(0, min(amount + delta, capacity))
        self._write_memory(address, struct.pack(">I", amount))

    def _decr_pickup(self, registers: Dict[int, int]):
        item_id = registers[4]
        delta = registers[5]
        address = _echoes_powerup_address(item_id)

        amount, capacity = self._read_memory_format(">II", address)
        amount = max(0, min(amount - delta, capacity))
        self._write_memory(address, struct.pack(">I", amount))

    def _display_hud_memo(self, registers):
        string_start = self._used_version.string_display.message_receiver_string_ref
        message_bytes = self._read_memory(
            string_start,
            self._used_version.string_display.max_message_size + 2)
        message = message_bytes[:message_bytes.find(b"\x00\x00")].decode(
            "utf-16_be")
        self.messages_list.addItem(message)

    def _handle_remote_execution(self):
        has_message_address = self._used_version.cstate_manager_global + 0x2
        if self._read_memory(has_message_address, 1) == b"\x00":
            return

        self.logger.debug("has code to execute, start parsing ppc")
        registers: Dict[int, int] = {i: 0 for i in range(32)}
        registers[3] = self._used_version.sda2_base

        functions = {
            self._used_version.powerup_functions.add_power_up:
            self._add_power_up,
            self._used_version.powerup_functions.incr_pickup:
            self._incr_pickup,
            self._used_version.powerup_functions.decr_pickup:
            self._decr_pickup,
            self._used_version.string_display.wstring_constructor:
            lambda reg: None,
            self._used_version.string_display.display_hud_memo:
            self._display_hud_memo,
        }

        patch_address, _ = all_prime_dol_patches.create_remote_execution_body(
            self._used_version.string_display, [])
        current_address = patch_address
        while current_address - patch_address < 0x2000:
            instruction = self._read_memory(current_address, 4)
            inst_value = int.from_bytes(instruction, "big")
            header = inst_value >> 26

            def get_signed_16():
                return struct.unpack("h", struct.pack("H",
                                                      inst_value & 65535))[0]

            if header == 19:
                # subset of branch without address. Consider these blr
                end_address = current_address
                self.logger.debug("blr")
                break

            if header == 14:
                # addi
                output_reg = (inst_value >> 21) & 31
                input_reg = (inst_value >> 16) & 31
                value = get_signed_16()
                if input_reg == 0:
                    self.logger.debug(f"addi r{output_reg}, {value}")
                    registers[output_reg] = value
                else:
                    self.logger.debug(
                        f"addi r{output_reg}, r{input_reg}, {value}")
                    registers[output_reg] = registers[input_reg] + value

            elif header == 15:
                # addis
                output_reg = (inst_value >> 21) & 31
                input_reg = (inst_value >> 16) & 31
                value = inst_value & 65535
                if input_reg == 0:
                    self.logger.debug(f"lis r{output_reg}, {value}")
                    registers[output_reg] = value << 16
                else:
                    self.logger.debug(
                        f"addis r{output_reg}, r{input_reg}, {value}")
                    registers[output_reg] = (registers[input_reg] +
                                             value) << 16

            elif header == 18:
                # b and bl (relative branch)
                jump_offset = (inst_value >> 2) & ((2 << 24) - 1)
                link = bool(inst_value & 1)
                address = current_address + (jump_offset * 4)

                if address in functions:
                    function_name = functions[address].__name__
                    functions[address](registers)
                else:
                    function_name = "unknown"

                self.logger.debug(
                    f"b{'l' if link else ''} 0x{address:0x} [{function_name}]")

            elif header == 24:
                # ori
                output_reg = (inst_value >> 21) & 31
                input_reg = (inst_value >> 16) & 31
                value = inst_value & 65535

                self.logger.debug(f"ori r{output_reg}, r{input_reg}, {value}")
                registers[output_reg] = registers[input_reg] | value

            elif header == 36 or header == 38:
                # stw (36) and stb (38)
                input_reg = (inst_value >> 21) & 31
                output_reg = (inst_value >> 16) & 31
                value = get_signed_16()

                if header == 36:
                    inst = 'stw'
                    size = 4
                else:
                    inst = 'stb'
                    size = 1

                self.logger.debug(
                    f"{inst} r{input_reg}, 0x{value:0x} (r{output_reg})")
                # ignore r1, as it's writing to stack
                if output_reg != 1:
                    self._write_memory(
                        registers[output_reg] + value,
                        registers[input_reg].to_bytes(size, "big"))

            elif header == 32:
                # lwz
                output_reg = (inst_value >> 21) & 31
                input_reg = (inst_value >> 16) & 31
                value = get_signed_16()

                self.logger.debug(
                    f"lwz r{input_reg}, 0x{value:0x} (r{output_reg})")
                ea = value
                if input_reg != 0:
                    ea += registers[input_reg]

                registers[output_reg] = int.from_bytes(
                    self._read_memory(ea, 4), "big")
            else:
                self.logger.debug(
                    f"unsupported instruction: {instruction.hex()}; {header}")

            current_address += 4

        self._write_memory(has_message_address, b"\x00")

    # MemoryOperationExecutor

    @property
    def lock_identifier(self) -> Optional[str]:
        return None

    @property
    def backend_choice(self):
        return DebugGameBackendChoice()

    async def connect(self) -> bool:
        await self._ensure_initialized_game_memory()
        self._handle_remote_execution()
        await self._update_inventory_label()
        self._connected = True
        return True

    async def disconnect(self):
        self._connected = False

    def is_connected(self) -> bool:
        return self._connected

    async def perform_memory_operations(
            self, ops: List[MemoryOperation]) -> Dict[MemoryOperation, bytes]:
        result = {}
        for op in ops:
            op_result = self._memory_operation(op)
            if op_result is not None:
                result[op] = op_result
        return result