Esempio n. 1
0
def test_hover_leave(pixmap_differ: PixmapDiffer):
    actual: QPainter
    expected: QPainter
    with pixmap_differ.create_painters(240, 240,
                                       'spline_hover_leave') as (actual,
                                                                 expected):
        expected_scene = QGraphicsScene(0, 0, 240, 240)
        expected_scene.addPixmap(
            SplineDisplay.load_pixmap('board-1.png', QSize(240,
                                                           240))).setPos(1, 0)
        expected_scene.render(expected)

        display = SplineDisplay()
        height = 0
        row = 1
        column = 2
        piece_item = display.item_levels[height][row][column]

        display.resize(348, 264)
        display.grab()  # Force layout to recalculate.

        display.on_hover_enter(piece_item)
        display.on_hover_leave(piece_item)

        render_display(display, actual)
Esempio n. 2
0
def test_hover_enter_red(pixmap_differ: PixmapDiffer):
    actual: QPainter
    expected: QPainter
    with pixmap_differ.create_painters(
            240, 240, 'sandbox_hover_enter_red') as (actual, expected):
        expected_scene = QGraphicsScene(0, 0, 240, 240)
        expected_scene.addPixmap(
            SandboxDisplay.load_pixmap('board-1.png',
                                       QSize(240, 240))).setPos(1, 0)
        red_ball = SandboxDisplay.load_pixmap('ball-r-shadow-1.png',
                                              QSize(60, 60))

        new_piece = expected_scene.addPixmap(red_ball)
        new_piece.setPos(115, 114)
        new_piece.setOpacity(0.5)
        expected_scene.render(expected)

        display = SandboxDisplay()
        display.selected_move_type = MoveType.RED
        height = 0
        row = 1
        column = 2
        piece_item = display.item_levels[height][row][column]

        display.resize(348, 264)
        display.grab()  # Force layout to recalculate.

        display.on_hover_enter(piece_item)

        render_display(display, actual)
Esempio n. 3
0
def test_coordinates(pixmap_differ: PixmapDiffer):
    actual: QPainter
    expected: QPainter
    with pixmap_differ.create_painters(240, 200,
                                       'spline_coordinates') as (actual,
                                                                 expected):
        expected_scene = QGraphicsScene(0, 0, 240, 200)
        expected_scene.addPixmap(
            SplineDisplay.load_pixmap('board-1.png',
                                      QSize(160, 160))).setPos(36, 0)

        for i in range(7):
            add_text(expected_scene, str(7 - i), 15 + i % 2 * 10, i * 17 + 27,
                     10)
            add_text(expected_scene, chr(65 + i), i * 17 + 64,
                     178 - i % 2 * 10, 10)

        expected_scene.render(expected)

        display = SplineDisplay()
        display.resize(348, 224)

        display.show_coordinates = True

        render_display(display, actual)
Esempio n. 4
0
def test_update(pixmap_differ: PixmapDiffer):
    actual: QPainter
    expected: QPainter
    with pixmap_differ.create_painters(240, 240, 'spargo_update') as (
            actual,
            expected):
        expected_scene = QGraphicsScene(0, 0, 240, 240)
        expected_scene.addPixmap(
            SpargoDisplay.load_pixmap('board-1.png',
                                      QSize(240, 240))).setPos(1, 0)
        black_ball = SpargoDisplay.load_pixmap('ball-b-shadow-1.png',
                                               QSize(60, 60))

        expected_scene.addPixmap(black_ball).setPos(63, 166)
        expected_scene.render(expected)

        display = SpargoDisplay()

        display.resize(348, 264)
        display.update_board(SpargoState("""\
  A C E G
7 . . . . 7

5 . . . . 5

3 . . . . 3

1 . B . . 1
  A C E G
>W
"""))

        render_display(display, actual)
    assert display.ui.black_count.text() == '1'
    assert display.ui.white_count.text() == '0'
Esempio n. 5
0
def test_empty(pixmap_differ: PixmapDiffer):
    actual: QPainter
    expected: QPainter
    with pixmap_differ.create_painters(240, 240, 'spargo_empty') as (
            actual,
            expected):
        expected_scene = QGraphicsScene(0, 0, 240, 240)
        expected_scene.addPixmap(
            SpargoDisplay.load_pixmap('board-1.png',
                                      QSize(240, 240))).setPos(1, 0)

        expected_scene.render(expected)

        display = SpargoDisplay()
        display.resize(348, 264)

        render_display(display, actual)
    assert display.ui.move_text.text() == 'to move'
    assert display.ui.black_count.text() == '0'
    assert display.ui.red_count.text() == ''
    assert display.ui.white_count.text() == '0'
    black_icon = display.black_pixmap.toImage()
    assert display.ui.black_count_pixmap.pixmap().toImage() == black_icon
    assert display.ui.player_pixmap.pixmap().toImage() == black_icon
    white_icon = display.white_pixmap.toImage()
    assert display.ui.white_count_pixmap.pixmap().toImage() == white_icon
Esempio n. 6
0
def test_empty(pixmap_differ: PixmapDiffer):
    actual: QPainter
    expected: QPainter
    with pixmap_differ.create_painters(240, 240,
                                       'sandbox_empty') as (actual, expected):
        expected_scene = QGraphicsScene(0, 0, 240, 240)
        expected_scene.addPixmap(
            SandboxDisplay.load_pixmap('board-1.png',
                                       QSize(240, 240))).setPos(1, 0)

        expected_scene.render(expected)

        display = SandboxDisplay()

        display.resize(348, 264)

        render_display(display, actual)
    for widget in (display.ui.move_black, display.ui.move_white,
                   display.ui.move_red, display.ui.remove,
                   display.ui.black_count_pixmap,
                   display.ui.white_count_pixmap, display.ui.red_count_pixmap,
                   display.ui.black_count, display.ui.white_count,
                   display.ui.red_count):
        assert widget.isVisibleTo(display)
    assert display.ui.move_black.isChecked()
    assert display.ui.red_count.text() == '0'
def test_hover_enter_remove(pixmap_differ: PixmapDiffer):
    actual: QPainter
    expected: QPainter
    with pixmap_differ.create_painters(
            240, 240, 'spook_hover_enter_remove') as (actual, expected):
        expected_scene = QGraphicsScene(0, 0, 240, 240)
        expected_scene.addPixmap(
            SpookDisplay.load_pixmap('board-1.png', QSize(240,
                                                          240))).setPos(1, 0)
        white_ball = SpookDisplay.load_pixmap('ball-w-shadow-1.png',
                                              QSize(60, 60))
        red_ball = SpookDisplay.load_pixmap('ball-r-shadow-1.png',
                                            QSize(60, 60))
        black_ball = SpookDisplay.load_pixmap('ball-b-shadow-1.png',
                                              QSize(60, 60))

        black_piece = expected_scene.addPixmap(black_ball)
        black_piece.setPos(115, 114)
        black_piece.setOpacity(0.5)
        red_piece = expected_scene.addPixmap(red_ball)
        red_piece.setPos(63, 62)
        white_piece = expected_scene.addPixmap(white_ball)
        white_piece.setPos(115, 62)
        expected_scene.render(expected)

        display = SpookDisplay()
        display.update_board(
            SpookState('''\
  A C E G
7 . . . . 7

5 . R W . 5

3 . . B . 3

1 . . . . 1
  A C E G
>R(R,B)
'''))
        height = 0
        row = 1
        column = 2
        piece_item = display.item_levels[height][row][column]

        display.resize(348, 264)
        display.grab()  # Force layout to recalculate.

        display.on_hover_enter(piece_item)

        render_display(display, actual)
Esempio n. 8
0
    def __init__(self, start_state: ShibumiGameState):
        super().__init__(start_state)
        self.start_state = start_state

        ui = self.ui = Ui_ShibumiDisplay()
        ui.setupUi(self)
        scene = QGraphicsScene()
        ui.game_display.setScene(scene)
        self.background_pixmap = self.assemble_board()
        self.background_item = scene.addPixmap(self.background_pixmap)
        self.white_scaled = self.white_pixmap = self.load_pixmap(
            'ball-w-shadow-1.png')
        self.black_scaled = self.black_pixmap = self.load_pixmap(
            'ball-b-shadow-1.png')
        self.red_scaled = self.red_pixmap = self.load_pixmap(
            'ball-r-shadow-1.png')
        self.remove_pixmap = self.load_pixmap('ball-x-shadow-1.png')
        self.item_levels = []
        self.hovered_piece: typing.Optional[GraphicsShibumiPieceItem] = None
        for height in range(self.start_state.size):
            item_level = []
            for row in range(self.start_state.size - height):
                item_row = []
                for column in range(self.start_state.size - height):
                    item = GraphicsShibumiPieceItem(height, row, column, self)
                    scene.addItem(item)
                    item_row.append(item)
                item_level.append(item_row)
            self.item_levels.append(item_level)
        self.row_labels = []
        self.column_labels = []
        for i in range(self.start_state.size * 2 - 1):
            self.row_labels.append(scene.addSimpleText(str(i + 1)))
            self.column_labels.append(scene.addSimpleText(chr(i + 65)))
        self.ui.move_black.clicked.connect(
            lambda: self.on_move_type_selected(MoveType.BLACK))
        self.ui.move_white.clicked.connect(
            lambda: self.on_move_type_selected(MoveType.WHITE))
        self.ui.move_red.clicked.connect(
            lambda: self.on_move_type_selected(MoveType.RED))
        self.ui.remove.clicked.connect(
            lambda: self.on_move_type_selected(MoveType.REMOVE))
        self._selected_move_type = MoveType.BLACK
        self._visible_counts: typing.FrozenSet[PlayerCode] = frozenset()
        self._visible_move_types: typing.FrozenSet[MoveType] = frozenset()
        self.ui.black_count_pixmap.setText('')
        self.ui.white_count_pixmap.setText('')
        self.ui.red_count_pixmap.setText('')
        self.ui.move_black.setText('')
        self.ui.move_white.setText('')
        self.ui.move_red.setText('')
        self.ui.remove.setText('')
        self.ui.move_black.setIcon(self.black_pixmap)
        self.ui.move_white.setIcon(self.white_pixmap)
        self.ui.move_red.setIcon(self.red_pixmap)
        self.ui.remove.setIcon(self.remove_pixmap)
        self.ui.pass_button.setVisible(False)
        self.show_counts = False
        self.show_move_types = False
        self.debug_message = ''
Esempio n. 9
0
def test_resize_narrow(pixmap_differ: PixmapDiffer):
    actual: QPainter
    expected: QPainter
    with pixmap_differ.create_painters(120, 240,
                                       'spline_resize_narrow') as (actual,
                                                                   expected):
        expected_scene = QGraphicsScene(0, 0, 120, 240)
        expected_scene.addPixmap(
            SplineDisplay.load_pixmap('board-1.png',
                                      QSize(120, 123))).setPos(0, 59)

        expected_scene.render(expected)

        display = SplineDisplay()

        display.resize(204, 264)

        render_display(display, actual)
Esempio n. 10
0
def test_hover_enter_leave(pixmap_differ: PixmapDiffer):
    actual: QPainter
    expected: QPainter
    with pixmap_differ.create_painters(
            240, 240, 'sandbox_hover_enter_leave') as (actual, expected):
        expected_scene = QGraphicsScene(0, 0, 240, 240)
        expected_scene.addPixmap(
            SandboxDisplay.load_pixmap('board-1.png',
                                       QSize(240, 240))).setPos(1, 0)
        white_ball = SandboxDisplay.load_pixmap('ball-w-shadow-1.png',
                                                QSize(60, 60))

        new_piece = expected_scene.addPixmap(white_ball)
        new_piece.setPos(115, 114)
        expected_scene.render(expected)

        display = SandboxDisplay()
        display.update_board(
            SandboxState('''\
  A C E G
7 . . . . 7

5 . . . . 5

3 . . W . 3

1 . . . . 1
  A C E G
'''))
        display.selected_move_type = MoveType.REMOVE
        height = 0
        row = 1
        column = 2
        piece_item = display.item_levels[height][row][column]

        display.resize(348, 264)
        display.grab()  # Force layout to recalculate.

        display.on_hover_enter(piece_item)
        display.on_hover_leave(piece_item)

        render_display(display, actual)
Esempio n. 11
0
def test_empty(pixmap_differ: PixmapDiffer):
    actual: QPainter
    expected: QPainter
    with pixmap_differ.create_painters(240, 240,
                                       'spline_empty') as (actual, expected):
        expected_scene = QGraphicsScene(0, 0, 240, 240)
        expected_scene.addPixmap(
            SplineDisplay.load_pixmap('board-1.png', QSize(240,
                                                           240))).setPos(1, 0)

        expected_scene.render(expected)

        display = SplineDisplay()

        display.resize(348, 264)

        render_display(display, actual)
    black_icon = SplineDisplay.load_pixmap('ball-b-shadow-1.png').toImage()
    assert display.ui.player_pixmap.pixmap().toImage() == black_icon
    assert display.ui.move_text.text() == 'to move'
Esempio n. 12
0
def test_hover_leave_existing(pixmap_differ: PixmapDiffer):
    actual: QPainter
    expected: QPainter
    with pixmap_differ.create_painters(
            240, 240, 'spline_hover_leave_existing') as (actual, expected):
        expected_scene = QGraphicsScene(0, 0, 240, 240)
        expected_scene.addPixmap(
            SplineDisplay.load_pixmap('board-1.png', QSize(240,
                                                           240))).setPos(1, 0)
        black_ball = SplineDisplay.load_pixmap('ball-b-shadow-1.png',
                                               QSize(60, 60))

        expected_scene.addPixmap(black_ball).setPos(115, 114)
        expected_scene.render(expected)

        display = SplineDisplay()
        display.update_board(
            SplineState("""\
  A C E G
7 . . . . 7

5 . . . . 5

3 . . B . 3

1 . . . . 1
  A C E G
"""))
        height = 0
        row = 1
        column = 2
        piece_item = display.item_levels[height][row][column]

        display.resize(348, 264)
        display.grab()  # Force layout to recalculate.

        display.on_hover_leave(piece_item)

        render_display(display, actual)
Esempio n. 13
0
def test_second_level(pixmap_differ: PixmapDiffer):
    actual: QPainter
    expected: QPainter
    with pixmap_differ.create_painters(240, 240,
                                       'spline_second_level') as (actual,
                                                                  expected):
        expected_scene = QGraphicsScene(0, 0, 240, 240)
        expected_scene.addPixmap(
            SplineDisplay.load_pixmap('board-1.png', QSize(240,
                                                           240))).setPos(1, 0)
        white_ball = SplineDisplay.load_pixmap('ball-w-shadow-1.png',
                                               QSize(60, 60))
        black_ball = SplineDisplay.load_pixmap('ball-b-shadow-1.png',
                                               QSize(60, 60))

        expected_scene.addPixmap(black_ball).setPos(11, 62)
        expected_scene.addPixmap(white_ball).setPos(63, 62)
        expected_scene.addPixmap(white_ball).setPos(11, 10)
        expected_scene.addPixmap(black_ball).setPos(63, 10)
        expected_scene.addPixmap(black_ball).setPos(37, 36)
        expected_scene.render(expected)

        display = SplineDisplay()
        display.update_board(
            SplineState("""\
  A C E G
7 W B . . 7

5 B W . . 5

3 . . . . 3

1 . . . . 1
  A C E G
   B D F
 6 B . . 6

 4 . . . 4

 2 . . . 2
   B D F
"""))

        display.resize(348, 264)

        render_display(display, actual)
def test_coordinates(pixmap_differ: PixmapDiffer):
    actual: QPainter
    expected: QPainter
    with pixmap_differ.create_painters(360, 240,
                                       'margo_coordinates') as (actual,
                                                                expected):
        expected_scene = QGraphicsScene(0, 0, 360, 240)
        display = MargoDisplay(size=2)
        full_board = display.assemble_board()
        scaled_board = display.scale_pixmap(full_board, 185, 192)
        expected_scene.addPixmap(scaled_board).setPos(82, 0)
        add_text(expected_scene, '3', 57, 57, 12)
        add_text(expected_scene, '2', 68, 93, 12)
        add_text(expected_scene, '1', 57, 129, 12)
        add_text(expected_scene, 'A', 141, 214, 12)
        add_text(expected_scene, 'B', 177, 203, 12)
        add_text(expected_scene, 'C', 213, 214, 12)
        expected_scene.render(expected)

        display.resize(492, 264)

        display.show_coordinates = True

        render_display(display, actual)
Esempio n. 15
0
def test_double_update(pixmap_differ: PixmapDiffer):
    actual: QPainter
    expected: QPainter
    with pixmap_differ.create_painters(240, 240,
                                       'spline_double_update') as (actual,
                                                                   expected):
        expected_scene = QGraphicsScene(0, 0, 240, 240)
        expected_scene.addPixmap(
            SplineDisplay.load_pixmap('board-1.png', QSize(240,
                                                           240))).setPos(1, 0)
        white_ball = SplineDisplay.load_pixmap('ball-w-shadow-1.png',
                                               QSize(60, 60))
        black_ball = SplineDisplay.load_pixmap('ball-b-shadow-1.png',
                                               QSize(60, 60))

        expected_scene.addPixmap(black_ball).setPos(11, 10)
        expected_scene.addPixmap(white_ball).setPos(63, 10)
        expected_scene.addPixmap(black_ball).setPos(115, 62)
        expected_scene.render(expected)

        display = SplineDisplay()
        trigger_resize(display, 300, 240)
        display.update_board(
            SplineState("""\
  A C E G
7 W B . . 7

5 . . . W 5

3 . . . . 3

1 . . . . 1
  A C E G
"""))
        display.update_board(
            SplineState("""\
  A C E G
7 B W . . 7

5 . . B . 5

3 . . . . 3

1 . . . . 1
  A C E G
"""))

        display.resize(348, 264)

        render_display(display, actual)
Esempio n. 16
0
def test_red(pixmap_differ: PixmapDiffer):
    actual: QPainter
    expected: QPainter
    with pixmap_differ.create_painters(240, 240,
                                       'sandbox_red') as (actual, expected):
        expected_scene = QGraphicsScene(0, 0, 240, 240)
        expected_scene.addPixmap(
            SandboxDisplay.load_pixmap('board-1.png',
                                       QSize(240, 240))).setPos(1, 0)
        white_ball = SandboxDisplay.load_pixmap('ball-w-shadow-1.png',
                                                QSize(60, 60))
        black_ball = SandboxDisplay.load_pixmap('ball-b-shadow-1.png',
                                                QSize(60, 60))
        red_ball = SandboxDisplay.load_pixmap('ball-r-shadow-1.png',
                                              QSize(60, 60))

        expected_scene.addPixmap(white_ball).setPos(11, 10)
        expected_scene.addPixmap(red_ball).setPos(63, 10)
        expected_scene.addPixmap(black_ball).setPos(167, 62)
        expected_scene.render(expected)

        display = SandboxDisplay()
        display.update_board(
            SandboxState("""\
  A C E G
7 W R . . 7

5 . . . B 5

3 . . . . 3

1 . . . . 1
  A C E G
"""))

        display.resize(348, 264)

        render_display(display, actual)
    assert display.ui.player_pixmap.pixmap().isNull()
Esempio n. 17
0
def test_first_level(pixmap_differ: PixmapDiffer):
    actual: QPainter
    expected: QPainter
    with pixmap_differ.create_painters(240, 240,
                                       'spline_first_level') as (actual,
                                                                 expected):
        expected_scene = QGraphicsScene(0, 0, 240, 240)
        expected_scene.addPixmap(
            SplineDisplay.load_pixmap('board-1.png', QSize(240,
                                                           240))).setPos(1, 0)
        white_ball = SplineDisplay.load_pixmap('ball-w-shadow-1.png',
                                               QSize(60, 60))
        black_ball = SplineDisplay.load_pixmap('ball-b-shadow-1.png',
                                               QSize(60, 60))

        expected_scene.addPixmap(white_ball).setPos(11, 10)
        expected_scene.addPixmap(black_ball).setPos(63, 10)
        expected_scene.addPixmap(black_ball).setPos(167, 62)
        expected_scene.render(expected)

        display = SplineDisplay()
        display.update_board(
            SplineState("""\
  A C E G
7 W B . . 7

5 . . . B 5

3 . . . . 3

1 . . . . 1
  A C E G
"""))

        display.resize(348, 264)

        render_display(display, actual)
    white_icon = SplineDisplay.load_pixmap('ball-w-shadow-1.png').toImage()
    assert display.ui.player_pixmap.pixmap().toImage() == white_icon
def test_board_size_2(pixmap_differ: PixmapDiffer):
    actual: QPainter
    expected: QPainter
    with pixmap_differ.create_painters(240, 240,
                                       'margo_board_size_2') as (actual,
                                                                 expected):
        expected_scene = QGraphicsScene(0, 0, 240, 240)
        full_board = MargoDisplay.load_pixmap('board-1.png')
        width = full_board.width()
        height = full_board.height()
        top_height = round(height * 0.28)
        left_width = round(width * 0.28)
        right_width = round(width * 0.279)
        bottom_height = round(height * 0.29)
        assembled_board = QPixmap(left_width + right_width,
                                  top_height + bottom_height)
        assembled_board.fill(Qt.transparent)
        assembled_painter = QPainter(assembled_board)
        # top left
        assembled_painter.drawPixmap(0, 0, left_width, top_height, full_board,
                                     0, 0, left_width, top_height)
        # top right
        assembled_painter.drawPixmap(left_width, 0, right_width, top_height,
                                     full_board, width - right_width, 0,
                                     right_width, top_height)
        # bottom left
        assembled_painter.drawPixmap(0, top_height, left_width, bottom_height,
                                     full_board, 0, height - bottom_height,
                                     left_width, bottom_height)
        # bottom right
        assembled_painter.drawPixmap(left_width, top_height, right_width,
                                     bottom_height, full_board,
                                     width - right_width,
                                     height - bottom_height, right_width,
                                     bottom_height)
        assembled_painter.end()
        scaled_board = assembled_board.scaled(232, 240, Qt.KeepAspectRatio,
                                              Qt.SmoothTransformation)
        board_item = expected_scene.addPixmap(scaled_board)
        board_item.setPos(4, 0)
        white_ball = MargoDisplay.load_pixmap('ball-w-shadow-1.png',
                                              QSize(103, 103))
        black_ball = MargoDisplay.load_pixmap('ball-b-shadow-1.png',
                                              QSize(103, 103))

        expected_scene.addPixmap(white_ball).setPos(23, 108)
        expected_scene.addPixmap(black_ball).setPos(113, 18)
        expected_scene.render(expected)

        display = MargoDisplay(size=2)

        trigger_resize(display, 292, 240)
        display.resize(348, 264)
        board_text = """\
  A C
3 . B 3

1 W . 1
  A C
>B
"""
        display.update_board(SpargoState(board_text, size=2))

        render_display(display, actual)
def test_board_size_3(pixmap_differ: PixmapDiffer):
    actual: QPainter
    expected: QPainter
    with pixmap_differ.create_painters(240, 240,
                                       'margo_board_size_3') as (actual,
                                                                 expected):
        expected_scene = QGraphicsScene(0, 0, 240, 240)
        full_board = MargoDisplay.load_pixmap('board-1.png')
        width = full_board.width()
        height = full_board.height()
        top_height = round(height * 0.28)
        mid_height = round(height * 0.21)
        bottom_height = round(height * 0.29)
        left_width = round(width * 0.28)
        mid_width = round(width * 0.22)
        right_width = round(width * 0.279)
        assembled_board = QPixmap(left_width + mid_width + right_width,
                                  top_height + mid_height + bottom_height)
        assembled_board.fill(Qt.transparent)
        assembled_painter = QPainter(assembled_board)
        # top left
        assembled_painter.drawPixmap(0, 0, left_width, top_height, full_board,
                                     0, 0, left_width, top_height)
        # top middle
        assembled_painter.drawPixmap(left_width, 0, mid_width, top_height,
                                     full_board, left_width, 0, mid_width,
                                     top_height)
        # top right
        assembled_painter.drawPixmap(left_width + mid_width, 0, right_width,
                                     top_height, full_board,
                                     width - right_width, 0, right_width,
                                     top_height)
        # left middle
        assembled_painter.drawPixmap(0, top_height, left_width, mid_height,
                                     full_board, 0, top_height, left_width,
                                     mid_height)
        # middle middle
        assembled_painter.drawPixmap(left_width, top_height, mid_width,
                                     mid_height, full_board, left_width,
                                     top_height, mid_width, mid_height)
        # right middle
        assembled_painter.drawPixmap(left_width + mid_width, top_height,
                                     right_width, mid_height, full_board,
                                     width - right_width, top_height,
                                     right_width, mid_height)
        # bottom left
        assembled_painter.drawPixmap(0, top_height + mid_height, left_width,
                                     bottom_height, full_board, 0,
                                     height - bottom_height, left_width,
                                     bottom_height)
        # bottom middle
        assembled_painter.drawPixmap(left_width, top_height + mid_height,
                                     mid_width, bottom_height, full_board,
                                     left_width, height - bottom_height,
                                     mid_width, bottom_height)
        # bottom right
        assembled_painter.drawPixmap(left_width + mid_width,
                                     top_height + mid_height, right_width,
                                     bottom_height, full_board,
                                     width - right_width,
                                     height - bottom_height, right_width,
                                     bottom_height)
        assembled_painter.end()
        scaled_board = assembled_board.scaled(240, 240, Qt.KeepAspectRatio,
                                              Qt.SmoothTransformation)
        board_item = expected_scene.addPixmap(scaled_board)
        board_item.setPos(2, 0)
        white_ball = MargoDisplay.load_pixmap('ball-w-shadow-1.png',
                                              QSize(76, 76))
        black_ball = MargoDisplay.load_pixmap('ball-b-shadow-1.png',
                                              QSize(76, 76))

        expected_scene.addPixmap(white_ball).setPos(15, 145)
        expected_scene.addPixmap(black_ball).setPos(81, 79)
        expected_scene.render(expected)

        display = MargoDisplay(size=3)
        display.resize(348, 264)

        board_text = """\
  A C E
5 . . . 5

3 . B . 3

1 W . . 1
  A C E
>B
"""
        display.update_board(SpargoState(board_text, size=3))

        render_display(display, actual)
Esempio n. 20
0
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.art_scene = QGraphicsScene(self)
        self.art_scene.addText('Open an image file.')
        self.ui.art_view.setScene(self.art_scene)
        self.symbols_scene = QGraphicsScene(self)
        self.ui.symbols_view.setScene(self.symbols_scene)
        self.ui.action_exit.triggered.connect(self.close)
        self.ui.action_open_art.triggered.connect(self.open_image)
        self.ui.action_open_words.triggered.connect(self.open_words)
        self.ui.action_save.triggered.connect(self.save_pdf)
        self.ui.action_save_png.triggered.connect(self.save_png)
        self.ui.action_shuffle.triggered.connect(self.shuffle)
        self.ui.action_sort.triggered.connect(self.sort)
        self.ui.rows.valueChanged.connect(self.on_options_changed)
        self.ui.columns.valueChanged.connect(self.on_options_changed)
        self.ui.word_clues_radio.toggled.connect(self.on_options_changed)
        self.ui.symbol_clues_radio.toggled.connect(self.on_options_changed)

        self.word_layout = QGridLayout(self.ui.word_content)
        self.ui.word_scroll.setWidgetResizable(True)
        self.word_labels: typing.Dict[str, QLabel] = {}
        self.word_shuffler = WordShuffler([])

        self.clues = None

        self.pixmap = self.scaled_pixmap = self.mini_pixmap = None
        self.sliced_pixmap_item: typing.Optional[QGraphicsPixmapItem] = None
        self.sliced_image: typing.Optional[QImage] = None
        self.selection_grid: typing.Optional[SelectionGrid] = None
        self.cells = []
        self.art_shuffler: typing.Optional[ArtShuffler] = None
        self.symbols_source_pixmap_item: typing.Optional[
            QGraphicsPixmapItem] = None
        self.symbols_pixmap_item: typing.Optional[QGraphicsPixmapItem] = None
        self.symbols_image: typing.Optional[QImage] = None
        self.symbols_shuffler: typing.Optional[ArtShuffler] = None
        self.selected_row: typing.Optional[int] = None
        self.selected_column: typing.Optional[int] = None
        self.settings = QSettings()
        self.image_path: typing.Optional[str] = self.settings.value(
            'image_path')
        self.words_path: typing.Optional[str] = self.settings.value(
            'words_path')

        self.dirty_letters = set()
        self.timer = QTimer()
        self.timer.setInterval(500)
        self.timer.setSingleShot(True)
        # noinspection PyUnresolvedReferences
        self.timer.timeout.connect(self.on_dirty)

        self.row_count = self.column_count = 0
        self.clue_type = ClueType.words
        self.ui.rows.setValue(self.settings.value('row_count', 6, int))
        self.ui.columns.setValue(self.settings.value('column_count', 4, int))
        clue_type_name = self.settings.value('clue_type', ClueType.words.name)
        try:
            clue_type = ClueType[clue_type_name]
        except KeyError:
            clue_type = ClueType.words
        if clue_type == ClueType.words:
            self.ui.word_clues_radio.setChecked(True)
        else:
            self.ui.symbol_clues_radio.setChecked(True)
        self.row_clues: typing.List[QPixmap] = []
        self.column_clues: typing.List[QPixmap] = []
        self.on_options_changed()

    def on_dirty(self):
        for letter in self.dirty_letters:
            self.word_labels[letter].setText(
                self.word_shuffler.make_display(letter))
            self.settings.setValue(f'word_{letter}',
                                   self.word_shuffler[letter])
        if self.dirty_letters:
            self.clues = self.word_shuffler.make_clues()
            self.art_shuffler.clues = dict(self.clues)
            self.dirty_letters.clear()
            self.on_selection_moved()

        if self.pixmap is not None:
            x, y, width, height = self.get_selected_fraction()
            self.settings.setValue('x', x)
            self.settings.setValue('y', y)
            self.settings.setValue('width', width)
            self.settings.setValue('height', height)

        new_rows = self.ui.rows.value()
        new_columns = self.ui.columns.value()
        if self.ui.word_clues_radio.isChecked():
            new_clue_type = ClueType.words
        else:
            new_clue_type = ClueType.symbols
        if (new_rows, new_columns,
                new_clue_type) == (self.row_count, self.column_count,
                                   self.clue_type):
            return
        self.settings.setValue('row_count', new_rows)
        self.settings.setValue('column_count', new_columns)
        self.settings.setValue('clue_type', new_clue_type.name)
        self.row_count, self.column_count = new_rows, new_columns
        self.clue_type = new_clue_type

        word_count = (self.row_count * self.column_count)
        while self.word_layout.count():
            layout_item = self.word_layout.takeAt(0)
            layout_item.widget().deleteLater()
        self.word_labels.clear()

        self.row_clues.clear()
        self.column_clues.clear()
        if self.image_path is not None:
            self.load_image(self.image_path)

        if self.words_path is not None:
            self.load_words(self.words_path)

        letters = [chr(65 + i) for i in range(word_count)]
        if self.word_shuffler.needs_blank:
            letters.insert(0, '')
        word_fields = {}
        for i, letter in enumerate(letters):
            word_field = QLineEdit()
            self.word_layout.addWidget(word_field, i, 0)
            # noinspection PyUnresolvedReferences
            word_field.textEdited.connect(partial(self.on_word_edited, letter))

            word_label = QLabel()
            self.word_layout.addWidget(word_label, i, 1)
            self.word_labels[letter] = word_label
            word_fields[letter] = word_field
        for i, letter in enumerate(letters):
            word = self.settings.value(f'word_{letter}', '')
            self.word_shuffler[letter] = word
            self.dirty_letters.add(letter)
            word_fields[letter].setText(word)

    def on_options_changed(self, *_):
        self.timer.start()

    def shuffle(self):
        self.clues = self.word_shuffler.make_clues()
        if self.art_shuffler is not None:
            self.art_shuffler.shuffle()
            self.on_selection_moved()

    def sort(self):
        if self.art_shuffler is not None:
            self.art_shuffler.sort()
            self.on_selection_moved()

    def open_words(self):
        word_filter = 'Text files (*.txt)'
        if self.words_path is None:
            words_folder = None
        else:
            words_folder = str(Path(self.words_path).parent)
        file_name, _ = QFileDialog.getOpenFileName(self,
                                                   "Open a words file.",
                                                   dir=words_folder,
                                                   filter=word_filter)
        if not file_name:
            return
        self.settings.setValue('words_path', file_name)
        self.load_words(file_name)

    def load_words(self, words_path):
        with open(words_path) as f:
            choice = 0
            if choice == 0:
                self.word_shuffler = WordShuffler(f)
            else:
                self.word_shuffler = WordStripper(f)

    def open_image(self):
        formats = QImageReader.supportedImageFormats()
        patterns = (f'*.{fmt.data().decode()}' for fmt in formats)
        image_filter = f'Images ({" ".join(patterns)})'
        if self.image_path is None:
            image_folder = None
        else:
            image_folder = str(Path(self.image_path).parent)
        file_name, _ = QFileDialog.getOpenFileName(self,
                                                   "Open an image file.",
                                                   dir=image_folder,
                                                   filter=image_filter)
        if not file_name:
            return
        self.settings.setValue('image_path', file_name)
        self.load_image(file_name)

    def load_image(self, image_path):
        self.pixmap = QPixmap(image_path)
        if self.pixmap.isNull():
            self.pixmap = None
        self.image_path = image_path
        self.scale_image()

    def scale_image(self):
        if self.pixmap is None:
            return

        if self.selection_grid is None:
            x = self.settings.value('x', 0.0, float)
            y = self.settings.value('y', 0.0, float)
            width = self.settings.value('width', 1.0, float)
            height = self.settings.value('height', 1.0, float)
        else:
            x, y, width, height = self.get_selected_fraction()
        self.art_scene.clear()
        self.cells.clear()
        view_size = self.ui.art_view.maximumViewportSize()
        if view_size.width() == 0:
            return
        self.art_scene.setSceneRect(0, 0, view_size.width(),
                                    view_size.height())
        display_size = QSize(view_size.width() * 0.99 / 2,
                             view_size.height() * 0.99)
        self.scaled_pixmap = self.pixmap.scaled(
            display_size, aspectMode=Qt.AspectRatioMode.KeepAspectRatio)
        self.art_scene.addPixmap(self.scaled_pixmap)
        scaled_size = self.scaled_pixmap.size()
        self.selection_grid = SelectionGrid(scaled_size.width() * x,
                                            scaled_size.height() * y,
                                            scaled_size.width() * width,
                                            scaled_size.height() * height,
                                            row_count=self.row_count,
                                            column_count=self.column_count)
        self.selection_grid.on_moved = self.on_selection_moved
        self.art_scene.addItem(self.selection_grid)
        self.sliced_image = QImage(display_size,
                                   QImage.Format.Format_ARGB32_Premultiplied)
        self.check_clues()
        self.art_shuffler = ArtShuffler(self.selection_grid.row_count,
                                        self.selection_grid.column_count,
                                        self.sliced_image,
                                        QRect(0, 0, display_size.width(),
                                              display_size.height()),
                                        clues=self.clues,
                                        row_clues=self.row_clues,
                                        column_clues=self.column_clues)
        self.sliced_pixmap_item = self.art_scene.addPixmap(
            QPixmap.fromImage(self.sliced_image))
        self.sliced_pixmap_item.setPos(display_size.width(), 0)

        self.symbols_scene.clear()
        self.symbols_source_pixmap_item = self.symbols_scene.addPixmap(
            self.scaled_pixmap)
        self.symbols_image = QImage(display_size,
                                    QImage.Format.Format_ARGB32_Premultiplied)
        if self.symbols_shuffler is not None:
            selected_row = self.symbols_shuffler.selected_row
            selected_column = self.symbols_shuffler.selected_column
        else:
            selected_row = 0
            selected_column = None
        self.symbols_shuffler = ArtShuffler(self.selection_grid.row_count,
                                            self.selection_grid.column_count,
                                            self.symbols_image,
                                            QRect(0, 0, display_size.width(),
                                                  display_size.height()),
                                            row_clues=self.row_clues,
                                            column_clues=self.column_clues)
        self.symbols_shuffler.selected_row = selected_row
        self.symbols_shuffler.selected_column = selected_column
        self.symbols_pixmap_item = ClickablePixmapItem(
            QPixmap.fromImage(self.symbols_image))
        self.symbols_pixmap_item.on_click = self.on_symbols_clicked
        self.symbols_scene.addItem(self.symbols_pixmap_item)

        self.symbols_pixmap_item.setPos(display_size.width(), 0)

        self.on_selection_moved()

    def on_symbols_clicked(self, event: QGraphicsSceneMouseEvent):
        self.symbols_scene.clearSelection()
        self.symbols_shuffler.select_clue(event.pos().toPoint())
        self.on_selection_moved()

    def on_word_edited(self, letter, word):
        self.word_shuffler[letter] = word
        self.dirty_letters.add(letter)
        self.timer.start()

    def get_selected_fraction(self):
        selection_rect = self.selection_grid.rect()
        selection_pos = self.selection_grid.pos()
        size = self.scaled_pixmap.size()
        x = (selection_pos.x() + selection_rect.x()) / size.width()
        width = selection_rect.width() / size.width()
        y = (selection_pos.y() + selection_rect.y()) / size.height()
        height = selection_rect.height() / size.height()
        return x, y, width, height

    def on_selection_moved(self):
        selected_pixmap = self.get_selected_pixmap()
        self.art_shuffler.draw(selected_pixmap)
        self.sliced_pixmap_item.setPixmap(QPixmap.fromImage(self.sliced_image))

        selected_pixmap = self.get_selected_pixmap()
        cell_width = (selected_pixmap.width() /
                      self.selection_grid.column_count)
        cell_height = (selected_pixmap.height() /
                       self.selection_grid.row_count)
        self.row_clues.clear()
        self.column_clues.clear()
        for i in range(self.selection_grid.row_count):
            clue_image = selected_pixmap.copy(0, i * cell_height, cell_width,
                                              cell_height)
            self.row_clues.append(clue_image)
        for j in range(self.selection_grid.column_count):
            clue_image = selected_pixmap.copy(j * cell_width, 0, cell_width,
                                              cell_height)
            self.column_clues.append(clue_image)
        self.symbols_shuffler.row_clues = self.row_clues
        self.symbols_shuffler.column_clues = self.column_clues

        self.symbols_shuffler.draw_grid(selected_pixmap)
        self.symbols_pixmap_item.setPixmap(
            QPixmap.fromImage(self.symbols_image))
        self.timer.start()

    def get_selected_pixmap(self) -> QPixmap:
        x, y, width, height = self.get_selected_fraction()
        original_size = self.pixmap.size()
        selected_pixmap = self.pixmap.copy(x * original_size.width(),
                                           y * original_size.height(),
                                           width * original_size.width(),
                                           height * original_size.height())
        return selected_pixmap

    def resizeEvent(self, event: QResizeEvent):
        super().resizeEvent(event)
        self.scale_image()

    def save_pdf(self):
        pdf_folder = self.settings.value('pdf_folder')
        file_name, _ = QFileDialog.getSaveFileName(self,
                                                   "Save a PDF file.",
                                                   dir=pdf_folder,
                                                   filter='Documents (*.pdf)')
        if not file_name:
            return
        self.settings.setValue('pdf_folder', os.path.dirname(file_name))
        writer = QPdfWriter(file_name)
        writer.setPageSize(QPageSize(QPageSize.Letter))
        writer.setTitle('Sliced Art Puzzle')
        writer.setCreator('Don Kirkby')
        self.paint_puzzle(writer)

    def save_png(self):
        pdf_folder = self.settings.value('pdf_folder')
        file_name, _ = QFileDialog.getSaveFileName(self,
                                                   "Save an image file.",
                                                   dir=pdf_folder,
                                                   filter='Images (*.png)')
        if not file_name:
            return
        writer = QPixmap(1000, 2000)
        self.paint_puzzle(writer)
        writer.save(file_name)
        self.settings.setValue('pdf_folder', os.path.dirname(file_name))

    def paint_puzzle(self, writer: QPaintDevice):
        self.check_clues()
        painter = QPainter(writer)
        try:
            print_shuffler = ArtShuffler(self.art_shuffler.rows,
                                         self.art_shuffler.cols,
                                         writer,
                                         QRect(0, 0, writer.width(),
                                               round(writer.height() / 2)),
                                         clues=self.clues,
                                         row_clues=self.row_clues,
                                         column_clues=self.column_clues)
            print_shuffler.cells = self.art_shuffler.cells[:]
            print_shuffler.is_shuffled = self.art_shuffler.is_shuffled
            selected_pixmap = self.get_selected_pixmap()
            print_shuffler.draw(selected_pixmap, painter)

            print_shuffler.rect.moveTop(writer.height() / 2)
            print_shuffler.draw_grid(selected_pixmap, painter)
        finally:
            painter.end()

    def check_clues(self):
        if self.clue_type == ClueType.words:
            if self.clues is None:
                self.clues = self.word_shuffler.make_clues()
            self.row_clues.clear()
            self.column_clues.clear()
        else:
            self.clues = None
Esempio n. 21
0
class MainWindow(QMainWindow):
    """Main application window"""
    def __init__(self) -> None:
        QMainWindow.__init__(self)
        self.setSizePolicy(
            QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum))
        self.setMaximumSize(QSize(1920, 1080))
        self.setStyleSheet("padding: 0px; margin: 0px;")
        self.setIconSize(QSize(32, 32))
        self.setWindowTitle("BossyBot 2000 - Image Tagger")
        self.setWindowIcon(self.load_icon(icon))

        self.menubar = QMenuBar(self)
        self.menubar.setSizePolicy(EXP_MAX)
        self.menubar.setMaximumSize(QSize(INFINITE, 30))
        self.menu_file = QMenu('File', self.menubar)
        self.menu_options = QMenu('Options', self.menubar)
        self.menu_help = QMenu('Help', self.menubar)
        self.menubar.addAction(self.menu_file.menuAction())
        self.menubar.addAction(self.menu_options.menuAction())
        self.menubar.addAction(self.menu_help.menuAction())
        self.open = QAction('Open', self)
        self.menu_file.addAction(self.open)
        self.open.triggered.connect(self.open_file)
        self.exit_button = QAction('Exit', self)
        self.exit_button.triggered.connect(lambda: sys.exit(0),
                                           Qt.QueuedConnection)
        self.menu_file.addAction(self.exit_button)
        self.setMenuBar(self.menubar)

        self.previous_button = QAction(self.load_icon(previous), '<<', self)
        self.next_button = QAction(self.load_icon(next_icon), '>>', self)
        self.rotate_left_button = QAction(self.load_icon(left), '', self)
        self.rotate_right_button = QAction(self.load_icon(right), '', self)
        self.play_button = QAction(self.load_icon(play), '', self)
        self.play_button.setCheckable(True)
        self.delete_button = QAction(self.load_icon(delete), '', self)
        self.reload_button = QAction(self.load_icon(reload), '', self)
        self.mirror_button = QAction('Mirror', self)
        self.actual_size_button = QAction('Actual Size', self)
        self.browser_button = QAction('Browser', self)
        self.browser_button.setCheckable(True)
        self.browser_button.setChecked(True)
        self.crop_button = QAction('Crop', self)
        self.crop_button.setCheckable(True)

        self.toolbuttons = {
            self.rotate_left_button: {
                'shortcut':
                ',',
                'connect':
                lambda: self.pixmap.setRotation(self.pixmap.rotation() - 90)
            },
            self.rotate_right_button: {
                'shortcut':
                '.',
                'connect':
                lambda: self.pixmap.setRotation(self.pixmap.rotation() + 90)
            },
            self.delete_button: {
                'shortcut': 'Del',
                'connect': self.delete
            },
            self.previous_button: {
                'shortcut': 'Left',
                'connect': self.previous
            },
            self.play_button: {
                'shortcut': 'Space',
                'connect': self.play
            },
            self.next_button: {
                'shortcut': 'Right',
                'connect': self.next
            },
            self.reload_button: {
                'shortcut': 'F5',
                'connect': self.reload
            }
        }

        self.toolbar = QToolBar(self)
        self.toolbar.setSizePolicy(EXP_MAX)
        self.toolbar.setMaximumSize(QSize(INFINITE, 27))
        for _ in (self.browser_button, self.crop_button, self.mirror_button,
                  self.actual_size_button):
            self.toolbar.addAction(_)
        self.addToolBar(Qt.TopToolBarArea, self.toolbar)

        for button in self.toolbuttons:
            button.setShortcut(self.toolbuttons[button]['shortcut'])
            button.triggered.connect(self.toolbuttons[button]['connect'])
            self.toolbar.addAction(button)

        self.centralwidget = QWidget(self)
        self.centralwidget.setSizePolicy(EXP_EXP)
        self.setCentralWidget(self.centralwidget)
        self.grid = QGridLayout(self.centralwidget)

        self.media = QGraphicsScene(self)
        self.media.setItemIndexMethod(QGraphicsScene.NoIndex)
        self.media.setBackgroundBrush(QBrush(Qt.black))
        self.view = MyView(self.media, self)
        self.view.setSizePolicy(EXP_EXP)
        self.media.setSceneRect(0, 0, self.view.width(), self.view.height())
        self.grid.addWidget(self.view, 0, 0, 1, 1)

        self.frame = QFrame(self.centralwidget)
        self.frame.setSizePolicy(
            QSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding))
        self.frame.setMinimumSize(QSize(325, 500))
        self.frame.setStyleSheet(
            "QFrame { border: 4px inset #222; border-radius: 10; }")

        self.layout_widget = QWidget(self.frame)
        self.layout_widget.setGeometry(QRect(0, 400, 321, 91))
        self.layout_widget.setContentsMargins(15, 15, 15, 15)

        self.grid2 = QGridLayout(self.layout_widget)
        self.grid2.setContentsMargins(0, 0, 0, 0)

        self.save_button = QPushButton('Yes (Save)', self.layout_widget)
        self.save_button.setSizePolicy(FIX_FIX)
        self.save_button.setMaximumSize(QSize(120, 26))
        self.save_button.setVisible(False)
        self.grid2.addWidget(self.save_button, 1, 0, 1, 1)

        self.no_save_button = QPushButton('No (Reload)', self.layout_widget)
        self.no_save_button.setSizePolicy(FIX_FIX)
        self.no_save_button.setMaximumSize(QSize(120, 26))
        self.no_save_button.setVisible(False)
        self.grid2.addWidget(self.no_save_button, 1, 1, 1, 1)

        self.label = QLabel("Current image modified, save it?",
                            self.layout_widget)
        self.label.setSizePolicy(FIX_FIX)
        self.label.setMaximumSize(QSize(325, 60))
        self.label.setVisible(False)
        self.label.setAlignment(Qt.AlignCenter)
        self.grid2.addWidget(self.label, 0, 0, 1, 2)

        self.layout_widget = QWidget(self.frame)
        self.layout_widget.setGeometry(QRect(0, 0, 321, 213))

        self.ass = QRadioButton('Ass', self.layout_widget)
        self.ass_exposed = QRadioButton('Ass (exposed)', self.layout_widget)
        self.ass_reset = QRadioButton(self.frame)
        self.ass_group = QButtonGroup(self)

        self.breasts = QRadioButton('Breasts', self.layout_widget)
        self.breasts_exposed = QRadioButton('Breasts (exposed)',
                                            self.layout_widget)
        self.breasts_reset = QRadioButton(self.frame)
        self.breasts_group = QButtonGroup(self)

        self.pussy = QRadioButton('Pussy', self.layout_widget)
        self.pussy_exposed = QRadioButton('Pussy (exposed)',
                                          self.layout_widget)
        self.pussy_reset = QRadioButton(self.frame)
        self.pussy_group = QButtonGroup(self)

        self.fully_clothed = QRadioButton('Fully Clothed', self.layout_widget)
        self.fully_nude = QRadioButton('Fully Nude', self.layout_widget)
        self.nudity_reset = QRadioButton(self.frame)
        self.nudity = QButtonGroup(self)

        self.smiling = QRadioButton('Smiling', self.layout_widget)
        self.glaring = QRadioButton('Glaring', self.layout_widget)
        self.expression_reset = QRadioButton(self.frame)
        self.expression = QButtonGroup(self)

        self.grid3 = QGridLayout(self.layout_widget)
        self.grid3.setVerticalSpacing(15)
        self.grid3.setContentsMargins(0, 15, 0, 0)

        self.radios = {
            self.ass: {
                'this': 'ass',
                'that': 'ass_exposed',
                'group': self.ass_group,
                'reset': self.ass_reset,
                'grid': (0, 0, 1, 1)
            },
            self.ass_exposed: {
                'this': 'ass_exposed',
                'that': 'ass',
                'group': self.ass_group,
                'reset': self.ass_reset,
                'grid': (0, 1, 1, 1)
            },
            self.breasts: {
                'this': 'breasts',
                'that': 'breasts_exposed',
                'group': self.breasts_group,
                'reset': self.breasts_reset,
                'grid': (1, 0, 1, 1)
            },
            self.breasts_exposed: {
                'this': 'breasts_exposed',
                'that': 'breasts',
                'group': self.breasts_group,
                'reset': self.breasts_reset,
                'grid': (1, 1, 1, 1)
            },
            self.pussy: {
                'this': 'pussy',
                'that': 'pussy_exposed',
                'group': self.pussy_group,
                'reset': self.pussy_reset,
                'grid': (2, 0, 1, 1)
            },
            self.pussy_exposed: {
                'this': 'pussy_exposed',
                'that': 'pussy',
                'group': self.pussy_group,
                'reset': self.pussy_reset,
                'grid': (2, 1, 1, 1)
            },
            self.fully_clothed: {
                'this': 'fully_clothed',
                'that': 'fully_nude',
                'group': self.nudity,
                'reset': self.nudity_reset,
                'grid': (3, 0, 1, 1)
            },
            self.fully_nude: {
                'this': 'fully_nude',
                'that': 'fully_clothed',
                'group': self.nudity,
                'reset': self.nudity_reset,
                'grid': (3, 1, 1, 1)
            },
            self.smiling: {
                'this': 'smiling',
                'that': 'glaring',
                'group': self.expression,
                'reset': self.expression_reset,
                'grid': (4, 0, 1, 1)
            },
            self.glaring: {
                'this': 'glaring',
                'that': 'smiling',
                'group': self.expression,
                'reset': self.expression_reset,
                'grid': (4, 1, 1, 1)
            },
        }

        for radio in self.radios:
            radio.setSizePolicy(FIX_FIX)
            radio.setMaximumSize(QSize(150, 22))
            self.radios[radio]['reset'].setGeometry(QRect(0, 0, 0, 0))
            self.grid3.addWidget(radio, *self.radios[radio]['grid'])
            if self.radios[radio]['group'] != self.nudity:
                radio.toggled.connect(
                    lambda x=_, y=radio: self.annotate(self.radios[y]['this']))
            self.radios[radio]['group'].addButton(radio)
            self.radios[radio]['group'].addButton(self.radios[radio]['reset'])

        self.save_tags_button = QPushButton('Save Tags', self.layout_widget)
        self.save_tags_button.setSizePolicy(FIX_FIX)
        self.save_tags_button.setMaximumSize(QSize(120, 26))
        self.grid3.addWidget(self.save_tags_button, 5, 1, 1, 1)

        self.grid.addWidget(self.frame, 0, 1, 1, 1)

        self.browse_bar = QLabel(self.centralwidget)
        self.browse_bar.setSizePolicy(EXP_FIX)
        self.browse_bar.setMinimumSize(QSize(0, 100))
        self.browse_bar.setMaximumSize(QSize(INFINITE, 100))
        self.browse_bar.setStyleSheet("background: #000;")
        self.browse_bar.setAlignment(Qt.AlignCenter)
        self.h_box2 = QHBoxLayout(self.browse_bar)
        self.h_box2.setContentsMargins(4, 0, 0, 0)

        self.grid.addWidget(self.browse_bar, 1, 0, 1, 2)

        hiders = [
            self.no_save_button.clicked, self.save_button.clicked,
            self.reload_button.triggered
        ]
        for hider in hiders:
            hider.connect(self.save_button.hide)
            hider.connect(self.no_save_button.hide)
            hider.connect(self.label.hide)
        showers = [
            self.mirror_button.triggered, self.rotate_right_button.triggered,
            self.rotate_left_button.triggered
        ]
        for shower in showers:
            shower.connect(self.save_button.show)
            shower.connect(self.no_save_button.show)
            shower.connect(self.label.show)

        self.no_save_button.clicked.connect(self.reload)
        self.browser_button.toggled.connect(self.browse_bar.setVisible)

        self.play_button.toggled.connect(lambda: self.frame.setVisible(
            (True, False)[self.frame.isVisible()]))
        self.reload_button.triggered.connect(self.reload)
        self.mirror_button.triggered.connect(lambda: self.pixmap.setScale(-1))
        self.save_button.clicked.connect(self.save_image)
        self.play_button.toggled.connect(
            lambda: self.browser_button.setChecked(
                (True, False)[self.browse_bar.isVisible()]))
        self.crop_button.toggled.connect(self.view.reset)
        self.actual_size_button.triggered.connect(self.actual_size)
        self.browser_button.triggered.connect(self.browser)
        self.save_tags_button.clicked.connect(self.save_tags)
        self.view.got_rect.connect(self.set_rect)

        self.crop_rect = QRect(QPoint(0, 0), QSize(0, 0))
        self.dir_now = os.getcwd()
        self.files = []
        self.index = 0
        self.refresh_files()
        self.pixmap_is_scaled = False
        self.pixmap = QGraphicsPixmapItem()
        self.active_tag = ''
        self.reset_browser = False
        self.txt = PngInfo()

    def set_rect(self, rect: tuple[QPointF, QPointF]):
        """Converts the crop rectangle to a QRect after a crop action"""
        self.crop_rect = QRect(rect[0].toPoint(), rect[1].toPoint())

    def keyPressEvent(self, event: QKeyEvent):  # pylint: disable=invalid-name;
        """Keyboard event handler."""
        if event.key() == Qt.Key_Escape and self.play_button.isChecked():
            self.play_button.toggle()
            self.browser_button.setChecked((True, False)[self.reset_browser])
        elif (event.key() in [16777220, 16777221]
              and self.view.g_rect.rect().width() > 0):
            self.view.got_rect.emit((self.view.g_rect.rect().topLeft(),
                                     self.view.g_rect.rect().bottomRight()))
            if self.view.g_rect.pen().color() == Qt.red:
                new_pix = self.pixmap.pixmap().copy(self.crop_rect)
                if self.pixmap_is_scaled:
                    new_pix = new_pix.transformed(
                        self.view.transform().inverted()[0],
                        Qt.SmoothTransformation)
                self.update_pixmap(new_pix)
            elif self.view.g_rect.pen().color() == Qt.magenta:
                self.annotate_rect()
                self.view.annotation = False
            for _ in (self.label, self.save_button, self.no_save_button):
                _.show()
            self.view.reset()

    def play(self):
        """Starts a slideshow."""
        if self.play_button.isChecked():
            if self.browser_button.isChecked():
                self.reset_browser = True
            else:
                self.reset_browser = False
            QTimer.singleShot(3000, self.play)
            self.next()

    def _yield_radio(self):
        """Saves code connecting signals from all the radio buttons."""
        yield from self.radios.keys().__str__()

    def load_icon(self, icon_file):
        """Loads an icon from Base64 encoded strings in icons.py."""
        pix = QPixmap()
        pix.loadFromData(icon_file)
        return QIcon(pix)

    def open_file(self, file: str) -> None:
        """
        Open an image file and display it.

        :param file: The filename of the image to open
        """
        if not os.path.isfile(file):
            file = QFileDialog(self, self.dir_now,
                               self.dir_now).getOpenFileName()[0]
            self.dir_now = os.path.dirname(file)
            self.refresh_files()
        for i, index_file in enumerate(self.files):
            if file.split('/')[-1] == index_file:
                self.index = i
        self.view.setTransform(QTransform())
        self.update_pixmap(QPixmap(file))
        self.browser()
        self.load_tags()

    def refresh_files(self) -> list[str]:
        """Updates the file list when the directory is changed.
        Returns a list of image files available in the current directory."""
        files = os.listdir(self.dir_now)
        self.files = [
            file for file in sorted(files, key=lambda x: x.lower())
            if file.endswith((".png", ".jpg", ".gif", ".bmp", ".jpeg"))
        ]

    def next(self) -> None:
        """Opens the next image in the file list."""
        self.index = (self.index + 1) % len(self.files)
        self.reload()

    def previous(self) -> None:
        """Opens the previous image in the file list."""
        self.index = (self.index + (len(self.files) - 1)) % len(self.files)
        self.reload()

    def save_image(self) -> None:
        """
        Save the modified image file.  If the current pixmap has been
        scaled, we need to load a non-scaled pixmap from the original file and
        re-apply the transformations that have been performed to prevent it
        from being saved as the scaled-down image.
        """
        if self.pixmap_is_scaled:
            rotation = self.pixmap.rotation()
            mirror = self.pixmap.scale() < 0
            pix = QPixmap(self.files[self.index])
            pix = pix.transformed(QTransform().rotate(rotation))
            if mirror:
                pix = pix.transformed(QTransform().scale(-1, 1))
            pix.save(self.files[self.index], quality=-1)
        else:
            self.pixmap.pixmap().save(self.files[self.index], quality=-1)
        self.save_tags()

    def delete(self) -> None:
        """Deletes the current image from the file system."""
        with suppress(OSError):
            os.remove(f"{self.dir_now}/{self.files.pop(self.index)}")
        self.refresh_files()

    def reload(self) -> None:
        """Reloads the current pixmap; used to update the screen when the
        current file is changed."""
        self.open_file(f"{self.dir_now}/{self.files[self.index]}")

    def annotate(self, tag):
        """Starts an annotate action"""
        self.txt = PngInfo()
        self.view.annotation = True
        self.active_tag = tag
        self.view.reset()

    def wheelEvent(self, event: QWheelEvent) -> None:  # pylint: disable=invalid-name
        """With Ctrl depressed, zoom the current image, otherwise fire the
        next/previous functions."""
        modifiers = QApplication.keyboardModifiers()
        if event.angleDelta().y() == 120 and modifiers == Qt.ControlModifier:
            self.view.scale(0.75, 0.75)
        elif event.angleDelta().y() == 120:
            self.previous()
        elif event.angleDelta().y(
        ) == -120 and modifiers == Qt.ControlModifier:
            self.view.scale(1.25, 1.25)
        elif event.angleDelta().y() == -120:
            self.next()

    def actual_size(self) -> None:
        """Display the current image at its actual size, rather than scaled to
        fit the viewport."""
        self.update_pixmap(QPixmap(self.files[self.index]), False)
        self.view.setDragMode(QGraphicsView.ScrollHandDrag)

    def mousePressEvent(self, event: QMouseEvent) -> None:  # pylint: disable=invalid-name
        """Event handler for mouse button presses."""
        if event.button() == Qt.MouseButton.ForwardButton:
            self.next()
        elif event.button() == Qt.MouseButton.BackButton:
            self.previous()

    def update_pixmap(self, new: QPixmap, scaled: bool = True) -> None:
        """
        Updates the currently displayed image.

        :param new: The new `QPixmap` to be displayed.
        :param scaled: If False, don't scale the image to fit the viewport.
        """
        self.pixmap_is_scaled = scaled
        self.media.clear()
        self.pixmap = self.media.addPixmap(new)
        self.pixmap.setTransformOriginPoint(
            self.pixmap.boundingRect().width() / 2,
            self.pixmap.boundingRect().height() / 2)
        if scaled and (new.size().width() > self.view.width()
                       or new.size().height() > self.view.height()):
            self.view.fitInView(self.pixmap, Qt.KeepAspectRatio)
        self.media.setSceneRect(self.pixmap.boundingRect())

    def annotate_rect(self):
        """Creates image coordinate annotation data."""
        self.txt.add_itxt(
            f'{str(self.active_tag)}-rect',
            f'{str(self.crop_rect.x())}, {str(self.crop_rect.y())}, {str(self.crop_rect.width())}, {str(self.crop_rect.height())}'
        )

    def browser(self):
        """Slot function to initialize image thumbnails for the
        'browse mode.'"""
        while self.h_box2.itemAt(0):
            self.h_box2.takeAt(0).widget().deleteLater()
        index = (self.index + (len(self.files) - 2)) % len(self.files)
        for i, file in enumerate(self.files):
            file = self.dir_now + '/' + self.files[index]
            label = ClickableLabel(self, file)
            self.h_box2.addWidget(label)
            pix = QPixmap(file)
            if (pix.size().width() > self.browse_bar.width() / 5
                    or pix.size().height() > 100):
                pix = pix.scaled(self.browse_bar.width() / 5, 100,
                                 Qt.KeepAspectRatio)
            label.setPixmap(pix)
            index = (index + 1) % len(self.files)
            if i == 4:
                break

    def save_tags(self):
        """Save tags for currently loaded image into its iTxt data."""
        file = self.files[self.index]
        img = Image.open(file)
        img.load()
        for key, value, in img.text.items():
            self.txt.add_itxt(key, value)
        for key in self.radios:
            if key.isChecked():
                self.txt.add_itxt(self.radios[key]['this'], 'True')
                self.txt.add_itxt(self.radios[key]['that'], 'False')
        img.save(file, pnginfo=self.txt)

    def load_tags(self):
        """Load tags from iTxt data."""
        for radio in self.radios:
            if radio.isChecked():
                self.radios[radio]['reset'].setChecked(True)
        filename = self.files[self.index]
        fqp = filename
        img = Image.open(fqp)
        img.load()
        with suppress(AttributeError):
            for key, value in img.text.items():
                if value == 'True':
                    for radio in self.radios:
                        if key == self.radios[radio]['this']:
                            radio.setChecked(True)
                            self.view.annotation = False
                            self.active_tag = ''
                            self.view.reset()
            for key, value in img.text.items():
                if key.endswith('-rect'):
                    btn = [
                        radio for radio in self.radios
                        if self.radios[radio]['this'] == key.split('-')[0]
                    ]
                    print(key, value)
                    if btn[0].isChecked():
                        coords = [int(coord) for coord in value.split(', ')]
                        rect = QGraphicsRectItem(*coords)
                        rect.setPen(QPen(Qt.magenta, 1, Qt.SolidLine))
                        rect.setBrush(QBrush(Qt.magenta, Qt.Dense4Pattern))
                        self.view.scene().addItem(rect)
                        text = self.view.scene().addText(
                            key.split('-')[0],
                            QFont('monospace', 20, 400, False))
                        text.font().setPointSize(text.font().pointSize() * 2)
                        text.update()
                        text.setX(rect.rect().x() + 10)
                        text.setY(rect.rect().y() + 10)
                        print(f'set {key}')