Example #1
0
    def show_cards(self, cards):
        """
        Create the card widgets.
        :param cards: the cards
        """
        # Hide the said tricks widgets.
        for w in self._said_tricks_widgets.values():
            w.clear_actions()
            w.add_action(actions.FadeOutAction(0.5))

        self._card_widgets = {}
        card_size = (130, 184)
        x_min = 290
        x_delta = 50
        y = 400
        cards.sort(cmp_colors_first)

        class HandleCardClicked(object):
            def __init__(self, view, card):
                self._view = view
                self._card = card

            def __call__(self, x, y):
                self._view._handle_say_card(self._card)

        for i, card in enumerate(cards):
            card_image_name = get_card_image_filename(card)
            im = self._rm.get_image(card_image_name)
            w = ImageWidget((x_min + i * x_delta, y), card_size, i, im)
            w.handle_clicked = HandleCardClicked(self, card)
            self._background_widget.add_widget(w)
            self._card_widgets[card] = w
Example #2
0
    def show_cards(self, cards):
        """
        Create the card widgets.
        :param cards: the cards
        """
        # Hide the said tricks widgets.
        for w in self._said_tricks_widgets.values():
            w.clear_actions()
            w.add_action(actions.FadeOutAction(0.5))

        self._card_widgets = {}
        card_size = (130, 184)
        x_min = 290
        x_delta = 50
        y = 400
        cards.sort(cmp_colors_first)
        class HandleCardClicked(object):
            def __init__(self, view, card):
                self._view = view
                self._card = card
            def __call__(self, x, y):
                self._view._handle_say_card(self._card)
        for i, card in enumerate(cards):
            card_image_name = get_card_image_filename(card)
            im = self._rm.get_image(card_image_name)
            w = ImageWidget((x_min+i*x_delta, y), card_size, i, im)
            w.handle_clicked = HandleCardClicked(self, card)
            self._background_widget.add_widget(w)
            self._card_widgets[card] = w
Example #3
0
    def __init__(self, parent):
        super().__init__(parent)

        self.image_widget = ImageWidget(parent)
        self.hist_widget = HistogramWidget(parent)
        self.coord_label = QLabel("", self)
        self.pixel_rgb_label = QLabel('', self)
        self.pixel_hsv_label = QLabel('', self)
        self.pixel_lab_label = QLabel('', self)

        self.hsv_checkbox = QCheckBox("Shift HSV")
        self.hsv_checkbox.toggled.connect(self.slider_update)

        h_slider_box, self.h_slider = self._get_slider_box(
            "H:", -180, 180, 60, self.slider_update
        )

        s_slider_box, self.s_slider = self._get_slider_box(
            "S:", -100, 100, 10, self.slider_update
        )

        v_slider_box, self.v_slider = self._get_slider_box(
            "V:", -100, 100, 10, self.slider_update
        )

        self._set_default()

        self.image_widget.selection_update.connect(self.selection_upd)

        hbox = QHBoxLayout()
        hbox.addWidget(self.image_widget, 20)

        vbox = QVBoxLayout()
        vbox.addWidget(self.hist_widget)
        vbox.addWidget(self.coord_label)
        vbox.addWidget(self.pixel_rgb_label)
        vbox.addWidget(self.pixel_hsv_label)
        vbox.addWidget(self.pixel_lab_label)

        vbox.addWidget(self.hsv_checkbox)
        vbox.addLayout(h_slider_box)
        vbox.addLayout(s_slider_box)
        vbox.addLayout(v_slider_box)

        self._filter_buttons(vbox)

        hbox.addLayout(vbox, 1)

        self.setLayout(hbox)
Example #4
0
    def choose_player(self):
        if self.painter.board is not None:
            title = gui.Label("Choose player")
            table = gui.Table()
            players = list(sorted(self.painter.board.players))
            radios = gui.Group(value=self.player_input.value)
            for player in players:
                table.td(gui.Radio(radios, value=str(player)), style=td_style)
            table.tr()
            for player in players:
                table.td(
                    ImageWidget(image=self.items[self.amount][player],
                                style=td_style,
                                width=request("map_editor.view_width"),
                                height=request("map_editor.view_height")))
            button = gui.Button("  Apply  ")
            table.tr()
            table.td(button, style=td_style)
            dialog = gui.Dialog(title, table)

            def on_click():
                dialog.value = int(radios.value)
                dialog.send(gui.CHANGE)
                dialog.close()

            button.connect(gui.CLICK, on_click)
            dialog.connect(gui.CHANGE, lambda: self.new_player(dialog.value))
            dialog.open()
Example #5
0
 def _player_played_card(self, player, card):
     """
     Show that the player played the card.
     :param player: the player
     :param card: the card
     """
     card_size = (130, 184)
     im_filename = get_card_image_filename(card)
     im = self._rm.get_image(im_filename)
     pos = self._player_positions[player]
     im_w = ImageWidget((pos[0]-card_size[0]/2, pos[1]-card_size[1]/2), card_size, 20+len(self._played_card_widgets), im)
     self._background_widget.add_widget(im_w)
     x = self.screen.get_width()/2 - 200 + len(self._played_card_widgets) * 60
     y = 150 + len(self._played_card_widgets) * 10
     im_w.add_action(actions.MoveToAction((x, y), 1))
     self._played_card_widgets.append(im_w)
Example #6
0
    def _create_widgets(self):
        """
        Create the widgets and return the one that contains them all.
        :return: the background widget
        """
        # Create the background widget.
        bg = self._rm.get_image(BACKGROUND_IMAGE, self.screen.get_size())
        bg_widget = ImageWidget((0, 0), self.screen.get_size(), -1, bg)

        # Create the container for the input widgets.
        x = (self.screen.get_width() - INPUT_WIDTH - INPUT_PADDING[1] - INPUT_PADDING[3]) / 2
        y = self.screen.get_height() / 2
        w = INPUT_WIDTH + INPUT_PADDING[1] + INPUT_PADDING[3]
        h = self.screen.get_height() - y
        input_container = Widget((x, y), (w, h), 0)
        bg_widget.add_widget(input_container)

        # Create the input widgets.
        username_input = TextInput(
            (0, 0),
            INPUT_WIDTH,
            0,
            self._font,
            padding=INPUT_PADDING,
            color=INPUT_FORE_COLOR,
            fill=INPUT_FILL_COLOR,
            default_text="username",
            default_font=self._default_font,
        )
        host_input = copy.copy(username_input)
        host_input.default_text = "host"
        host_input.position = (0, INPUT_OFFSET)
        port_input = copy.copy(username_input)
        port_input.default_text = "port"
        port_input.position = (0, 2 * INPUT_OFFSET)
        input_container.add_widget(username_input)
        input_container.add_widget(host_input)
        input_container.add_widget(port_input)
        self._text_inputs = {"username": username_input, "host": host_input, "port": port_input}

        # Create the button widget.
        btn = special_widgets.simple_button((0, 3 * INPUT_OFFSET), (w, 100), "Login", self._font)

        def btn_clicked(x, y):
            self._ev_manager.post(events.LoginRequestedEvent())

        btn.handle_clicked = btn_clicked
        input_container.add_widget(btn)

        # Create the connection failed warning.
        self._connection_failed_warning = special_widgets.warning_widget(
            None, (400, 100), "Connection failed", self._font, screen_size=self.screen.get_size()
        )
        bg_widget.add_widget(self._connection_failed_warning)
        self._username_taken_warning = special_widgets.warning_widget(
            None, (400, 100), "Username already taken", self._font, screen_size=self.screen.get_size()
        )
        bg_widget.add_widget(self._username_taken_warning)

        return bg_widget
Example #7
0
 def _player_played_card(self, player, card):
     """
     Show that the player played the card.
     :param player: the player
     :param card: the card
     """
     card_size = (130, 184)
     im_filename = get_card_image_filename(card)
     im = self._rm.get_image(im_filename)
     pos = self._player_positions[player]
     im_w = ImageWidget(
         (pos[0] - card_size[0] / 2, pos[1] - card_size[1] / 2), card_size,
         20 + len(self._played_card_widgets), im)
     self._background_widget.add_widget(im_w)
     x = self.screen.get_width() / 2 - 200 + len(
         self._played_card_widgets) * 60
     y = 150 + len(self._played_card_widgets) * 10
     im_w.add_action(actions.MoveToAction((x, y), 1))
     self._played_card_widgets.append(im_w)
Example #8
0
    def __init__(self, model):
        """Inits the class."""
        Subject.__init__(self)
        QMainWindow.__init__(self)

        # Set model
        self.model = model

        # Create interface elements
        self.menu_bar = MenuBar(self)
        self.tool_bar = ToolBar(self)
        self.exif_area = ExifWidget(self)
        self.image_area = ImageWidget(self)
        self.status_bar = StatusBar()
        about_text = 'IEViewer 1.0' \
                     '<br><br>' \
                     'Copyright © 2021 by' \
                     '<br>' \
                     'Paula Mihalcea' \
                     '<br>' \
                     '<a href="mailto:[email protected]">[email protected]</a>' \
                     '<br><br>' \
                     'This program uses PyQt5, a comprehensive set of Python bindings for Qt v5. Qt is a set of cross-platform C++ libraries that implement high-level APIs for accessing many aspects of modern desktop and mobile systems.\n' \
                     '<br><br>' \
                     'PyQt5 is copyright © Riverbank Computing Limited. Its homepage is <a href="https://www.riverbankcomputing.com/software/pyqt/">https://www.riverbankcomputing.com/software/pyqt/</a>.' \
                     '<br><br>' \
                     'No genasi were harmed in the making of this application. <a href="https://www.dndbeyond.com/races/genasi#WaterGenasi">#GenasiLivesMatter#NereisThalian</a>'
        self.about = AboutWidget('About IEViewer',
                                 about_text,
                                 image_path='icons/about_img.png')

        # Disable GUI elements that are unavailable when no image is opened
        self.menu_bar.disable_widgets()
        self.exif_area.hide()

        # Set layout
        self.setCentralWidget(Layout(self).central_widget)

        # Set window properties
        self.set_window_properties()

        # Install additional event filters
        self.image_area.installEventFilter(self)
Example #9
0
    def _create_widgets(self):
        """
        Create the widgets and return the one that contains them all.
        :return: the background widget
        """
        # Create the background widget.
        bg = self._rm.get_image(BACKGROUND_IMAGE, self.screen.get_size())
        bg_widget = ImageWidget((0, 0), self.screen.get_size(), -1, bg)

        # Create the container for the input widgets.
        x = (self.screen.get_width() - INPUT_WIDTH - INPUT_PADDING[1] - INPUT_PADDING[3]) / 2
        y = self.screen.get_height() / 2
        w = INPUT_WIDTH + INPUT_PADDING[1] + INPUT_PADDING[3]
        h = self.screen.get_height() - y
        input_container = Widget((x, y), (w, h), 0)
        bg_widget.add_widget(input_container)

        # Create the input widgets.
        username_input = TextInput((0, 0), INPUT_WIDTH, 0, self._font, padding=INPUT_PADDING, color=INPUT_FORE_COLOR,
                                   fill=INPUT_FILL_COLOR, default_text="username",
                                   default_font=self._default_font)
        host_input = copy.copy(username_input)
        host_input.default_text = "host"
        host_input.position = (0, INPUT_OFFSET)
        port_input = copy.copy(username_input)
        port_input.default_text = "port"
        port_input.position = (0, 2*INPUT_OFFSET)
        input_container.add_widget(username_input)
        input_container.add_widget(host_input)
        input_container.add_widget(port_input)
        self._text_inputs = {"username": username_input, "host": host_input, "port": port_input}

        # Create the button widget.
        btn = special_widgets.simple_button((0, 3*INPUT_OFFSET), (w, 100), "Login", self._font)

        def btn_clicked(x, y):
            self._ev_manager.post(events.LoginRequestedEvent())
        btn.handle_clicked = btn_clicked
        input_container.add_widget(btn)

        # Create the connection failed warning.
        self._connection_failed_warning = special_widgets.warning_widget(None, (400, 100), "Connection failed",
                                                                         self._font, screen_size=self.screen.get_size())
        bg_widget.add_widget(self._connection_failed_warning)
        self._username_taken_warning = special_widgets.warning_widget(None, (400, 100), "Username already taken",
                                                                      self._font, screen_size=self.screen.get_size())
        bg_widget.add_widget(self._username_taken_warning)

        return bg_widget
Example #10
0
def warning_widget(position, size, text, font, screen_size=None, text_color=(255, 255, 255, 255), z_index=99,
                   fill=(0, 0, 0, 160), close_on_click=True):
    """
    Create an ImageWidget with the given text. If screen_size is given, position is ignored and the widget is centered.
    :param position: the position
    :param size: the widget size
    :param text: the text
    :param font: the font
    :param screen_size: the screen size
    :param text_color: the text color
    :param z_index: the z index
    :param fill: the fill color
    :return: the widget
    """
    # Create the widget.
    if screen_size is None:
        x, y = position
    else:
        x = (screen_size[0] - size[0]) / 2
        y = (screen_size[1] - size[1]) / 2
    font_obj = font.render(text, True, text_color)
    offset_x = (size[0] - font_obj.get_width()) / 2
    offset_y = (size[1] - font_obj.get_height()) / 2
    warning_bg = pygame.Surface(size, flags=pygame.SRCALPHA)
    warning_bg.fill(fill)
    warning_bg.blit(font_obj, (offset_x, offset_y))
    warning = ImageWidget((x, y), size, z_index, warning_bg, visible=False)

    # Attach the click function.
    if close_on_click:
        def warning_clicked(xx, yy):
            warning.hide()
            warning.clear_actions()
        warning.handle_clicked = warning_clicked

    return warning
Example #11
0
    def _create_widgets(self):
        """
        Create the widgets and return the background widget.
        :return: the background widget
        """
        # Create the background widget.
        bg = self._rm.get_image(BACKGROUND_IMAGE, self.screen.get_size())
        bg_widget = ImageWidget((0, 0), self.screen.get_size(), -1, bg)

        # Create the waiting text.
        wait_box = special_widgets.warning_widget(
            None, (400, 100),
            "Waiting for other players",
            self._font,
            screen_size=self.screen.get_size(),
            close_on_click=False)
        wait_box.visible = True
        bg_widget.add_widget(wait_box)
        self._warnings["wait_box"] = wait_box

        # Create the "invalid num tricks" warning.
        invalid_num_warning = special_widgets.warning_widget(
            None, (400, 100),
            "Invalid number of tricks",
            self._font,
            screen_size=self.screen.get_size())
        bg_widget.add_widget(invalid_num_warning)
        self._warnings["invalid_num_tricks"] = invalid_num_warning

        # Create the chat widget.
        chat_box = special_widgets.warning_widget(
            (10, self.screen.get_height() - 260), (260, 200),
            "chat",
            self._font,
            close_on_click=False)
        chat_box.visible = True
        bg_widget.add_widget(chat_box)

        # Create the "Your move" box.
        your_move_w = Text(
            (self.screen.get_width() - 140, self.screen.get_height() - 110),
            (120, 40),
            0,
            "Your move",
            self._font,
            fill=(0, 0, 0, 160))
        your_move_w.opacity = 0
        bg_widget.add_widget(your_move_w)
        self._user_move_widget = your_move_w

        # Create the trump widgets.
        trump_pos = (180, 180)
        trump_size = (125, 125)
        for color in ["W", "H", "D", "S", "C"]:
            im_filename = get_color_image_filename(color)
            im = self._rm.get_image(im_filename, trump_size)
            im_w = ImageWidget(trump_pos, trump_size, 0, im)
            im_w.opacity = 0
            bg_widget.add_widget(im_w)
            self._trump_widgets[color] = im_w

        # Create the "choose trump" widgets.
        class ChooseHandler(object):
            def __init__(self, view, trump):
                self._view = view
                self._trump = trump

            def __call__(self, x, y):
                self._view._handle_choose_trump(self._trump)

        choose_size = (90, 90)
        choose_trump_bg = pygame.Surface((400, 170), flags=pygame.SRCALPHA)
        choose_trump_bg.fill((0, 0, 0, 160))
        font_obj = self._font.render("Choose the trump:", True,
                                     (255, 255, 255, 255))
        choose_trump_bg.blit(
            font_obj,
            ((choose_trump_bg.get_width() - font_obj.get_width()) / 2, 20))
        choose_trump_container = ImageWidget(
            (self.screen.get_width() / 2 - 200, 200),
            choose_trump_bg.get_size(),
            99,
            choose_trump_bg,
            visible=False)
        for i, color in enumerate(["D", "S", "H", "C"]):
            im_filename = get_color_image_filename(color)
            im = self._rm.get_image(im_filename, choose_size)
            im_w = ImageWidget((i * (choose_size[0] + 10), 70), choose_size, 0,
                               im)
            choose_trump_container.add_widget(im_w)
            im_w.handle_clicked = ChooseHandler(self, color)
        bg_widget.add_widget(choose_trump_container)
        self._choose_trump_widget = choose_trump_container

        return bg_widget
Example #12
0
    def __init__(self, board=None, callback=None, **params):
        pygame.display.set_mode(
            (request("map_editor.width"), request("map_editor.height")))
        gui.Desktop.__init__(self, theme=self.theme, **params)
        self.callback = callback or (lambda *_, **__: None)
        self.connect(gui.QUIT, self.quit)
        self.board = board
        self.filename = None
        container = gui.Container(width=request("map_editor.width"),
                                  height=request("map_editor.height"))

        spacer = request("map_editor.space_size")

        self.new_dialog = NewMapDialog()
        self.new_dialog.connect(gui.CHANGE, self.action_new)
        self.open_dialog = FileDialog("Choose map",
                                      "Choose",
                                      path=folder('map'),
                                      preview=Preview(display_players=False),
                                      exts=['map', 'preset', 'state'])
        self.open_dialog.connect(gui.CHANGE, self.new_map)
        self.save_dialog = FileDialog("Enter filename to save with",
                                      "Choose",
                                      path=folder('map'),
                                      exts=['map'],
                                      save=True)
        self.save_dialog.connect(gui.CHANGE, self.action_saveas)

        # self.help_dialog = HelpDialog()
        # QUESTION: may be put it into json: {"<menu>": "<method name>", ...}
        self.menus = menus = gui.Menus([
            ('File/New', self.new_dialog.open, None),
            ('File/Open', self.open_dialog.open, None),
            ('File/Save', self.action_save, None),
            ('File/Save As', self.save_as, None),
            ('File/Exit', self.quit, None), ('Add/Extend', self.extend, None),
            ('Add/Add top row', self.add_top_row, None),
            ('Add/Add bottom row', self.add_bottom_row, None),
            ('Add/Add left column', self.add_left_column, None),
            ('Add/Add right column', self.add_right_column, None),
            ('Add/Add cells', self.add_cells, None),
            ('Remove/Reduce', self.reduce, None),
            ('Remove/Remove top row', self.remove_top_row, None),
            ('Remove/Remove bottom row', self.remove_bottom_row, None),
            ('Remove/Remove left column', self.remove_left_column, None),
            ('Remove/Remove right column', self.remove_right_column, None),
            ('Remove/Remove the same checkers', self.remove_same_checkers,
             None),
            ('Remove/Remove checkers with the same owner',
             self.remove_same_player_checkers, None),
            ('Remove/Remove checkers with the same level',
             self.remove_same_level_checkers, None),
            ('Remove/Remove all checkers', self.remove_checkers, None),
            ('Remove/Remove cells', self.remove_cells, None),
            ('Edit/Permute cells', self.permute_cells, None),
            ('Edit/Permute players', self.permute_players, None),
            ('Edit/Permute checkers', self.permute_checkers, None)
            # ('Help/Help', self.help_dialog.open)
        ])
        container.add(self.menus, 0, 0)
        self.menus.rect.w, self.menus.rect.h = menus.resize()
        self.filename_input = gui.Input("")
        container.add(self.filename_input, self.menus.rect.w + spacer, 0)

        # # new ::
        # self.mode = mode = gui.Group(name="brushes", value='player')
        # cell_tool = gui.Tool(self.mode, "Cell", 'cell')
        # empty_tool = gui.Tool(self.mode, "Empty", 'none')
        # player_tool = gui.Tool(self.mode, "Player", 'player')
        # # :: new
        self.mode = mode = gui.Toolbox([('Cell', 'cell'), ('Empty', 'none'),
                                        ('Player', 'player')],
                                       cols=1,
                                       value='player')  # NOTE: DEPERECATED
        self.mode.connect(gui.CHANGE, self.set_brush)
        self.player = request("map_editor.player")
        self.amount = request("map_editor.amount")
        container.add(mode, 0, self.menus.rect.bottom + spacer)
        # # new ::
        # container.add(cell_tool, 0, self.menus.rect.bottom + spacer)
        # container.add(empty_tool, 0, cell_tool.rect.bottom + spacer)
        # container.add(cell_tool, 0, empty_tool.rect.bottom + spacer)
        # # :: new
        mode.rect.x, mode.rect.y = mode.style.x, mode.style.y
        mode.rect.w, mode.rect.h = mode.resize()

        self.player_table = gui.Table()
        self.player_table.td(gui.Label("Holes"))
        self.player_table.tr()
        self.selector = gui.Select(value=request("map_editor.amount"))
        for i in sorted(self.items.keys()):
            self.selector.add(str(i), i)
        self.selector.connect(gui.CHANGE, self.new_amount)
        self.player_table.td(self.selector, style=td_style)
        self.player_table.tr()
        self.player_table.tr()
        self.player_button = gui.Button('Player')
        self.player_table.td(self.player_button, style=td_style)
        self.player_button.connect(gui.CLICK, self.choose_player)
        self.player_table.tr()
        self.player_input = gui.Input(str(self.player), size=5)
        add_event_handler(self.player_input, enter_pressed,
                          lambda e: self.new_player())
        self.player_table.td(self.player_input, style=td_style)
        self.player_table.tr()
        self.checker = ImageWidget(image=self.items[self.amount][self.player],
                                   width=request("map_editor.view_width"),
                                   height=request("map_editor.view_height"))
        self.player_table.td(self.checker, style=td_style)
        self.player_table.tr()
        # self.color = gui.Color("#000000", width=mode.rect.w, height=mode.rect.w)
        # self.color.connect(gui.CLICK, self.choose_player)
        # self.player_table.td(self.color, style=td_style)
        container.add(self.player_table, 0, mode.rect.bottom + spacer)

        self.painter = Painter(
            width=container.rect.w - mode.rect.w - spacer * 2,
            height=container.rect.h - self.menus.rect.h - spacer * 2,
            style={'border': 1})
        container.add(self.painter, mode.rect.w + spacer,
                      self.menus.rect.h + spacer)
        if board:
            self.painter.set_map(board)
        self.painter.rect.w, self.painter.rect.h = self.painter.resize()

        self.widget = container
Example #13
0
class MapEditor(gui.Desktop):

    items = core.load_items()
    items = core.transformed_items(items,
                                   cell_size=request("map_editor.cell_size"))
    theme = gui.Theme(preference.theme())

    def __init__(self, board=None, callback=None, **params):
        pygame.display.set_mode(
            (request("map_editor.width"), request("map_editor.height")))
        gui.Desktop.__init__(self, theme=self.theme, **params)
        self.callback = callback or (lambda *_, **__: None)
        self.connect(gui.QUIT, self.quit)
        self.board = board
        self.filename = None
        container = gui.Container(width=request("map_editor.width"),
                                  height=request("map_editor.height"))

        spacer = request("map_editor.space_size")

        self.new_dialog = NewMapDialog()
        self.new_dialog.connect(gui.CHANGE, self.action_new)
        self.open_dialog = FileDialog("Choose map",
                                      "Choose",
                                      path=folder('map'),
                                      preview=Preview(display_players=False),
                                      exts=['map', 'preset', 'state'])
        self.open_dialog.connect(gui.CHANGE, self.new_map)
        self.save_dialog = FileDialog("Enter filename to save with",
                                      "Choose",
                                      path=folder('map'),
                                      exts=['map'],
                                      save=True)
        self.save_dialog.connect(gui.CHANGE, self.action_saveas)

        # self.help_dialog = HelpDialog()
        # QUESTION: may be put it into json: {"<menu>": "<method name>", ...}
        self.menus = menus = gui.Menus([
            ('File/New', self.new_dialog.open, None),
            ('File/Open', self.open_dialog.open, None),
            ('File/Save', self.action_save, None),
            ('File/Save As', self.save_as, None),
            ('File/Exit', self.quit, None), ('Add/Extend', self.extend, None),
            ('Add/Add top row', self.add_top_row, None),
            ('Add/Add bottom row', self.add_bottom_row, None),
            ('Add/Add left column', self.add_left_column, None),
            ('Add/Add right column', self.add_right_column, None),
            ('Add/Add cells', self.add_cells, None),
            ('Remove/Reduce', self.reduce, None),
            ('Remove/Remove top row', self.remove_top_row, None),
            ('Remove/Remove bottom row', self.remove_bottom_row, None),
            ('Remove/Remove left column', self.remove_left_column, None),
            ('Remove/Remove right column', self.remove_right_column, None),
            ('Remove/Remove the same checkers', self.remove_same_checkers,
             None),
            ('Remove/Remove checkers with the same owner',
             self.remove_same_player_checkers, None),
            ('Remove/Remove checkers with the same level',
             self.remove_same_level_checkers, None),
            ('Remove/Remove all checkers', self.remove_checkers, None),
            ('Remove/Remove cells', self.remove_cells, None),
            ('Edit/Permute cells', self.permute_cells, None),
            ('Edit/Permute players', self.permute_players, None),
            ('Edit/Permute checkers', self.permute_checkers, None)
            # ('Help/Help', self.help_dialog.open)
        ])
        container.add(self.menus, 0, 0)
        self.menus.rect.w, self.menus.rect.h = menus.resize()
        self.filename_input = gui.Input("")
        container.add(self.filename_input, self.menus.rect.w + spacer, 0)

        # # new ::
        # self.mode = mode = gui.Group(name="brushes", value='player')
        # cell_tool = gui.Tool(self.mode, "Cell", 'cell')
        # empty_tool = gui.Tool(self.mode, "Empty", 'none')
        # player_tool = gui.Tool(self.mode, "Player", 'player')
        # # :: new
        self.mode = mode = gui.Toolbox([('Cell', 'cell'), ('Empty', 'none'),
                                        ('Player', 'player')],
                                       cols=1,
                                       value='player')  # NOTE: DEPERECATED
        self.mode.connect(gui.CHANGE, self.set_brush)
        self.player = request("map_editor.player")
        self.amount = request("map_editor.amount")
        container.add(mode, 0, self.menus.rect.bottom + spacer)
        # # new ::
        # container.add(cell_tool, 0, self.menus.rect.bottom + spacer)
        # container.add(empty_tool, 0, cell_tool.rect.bottom + spacer)
        # container.add(cell_tool, 0, empty_tool.rect.bottom + spacer)
        # # :: new
        mode.rect.x, mode.rect.y = mode.style.x, mode.style.y
        mode.rect.w, mode.rect.h = mode.resize()

        self.player_table = gui.Table()
        self.player_table.td(gui.Label("Holes"))
        self.player_table.tr()
        self.selector = gui.Select(value=request("map_editor.amount"))
        for i in sorted(self.items.keys()):
            self.selector.add(str(i), i)
        self.selector.connect(gui.CHANGE, self.new_amount)
        self.player_table.td(self.selector, style=td_style)
        self.player_table.tr()
        self.player_table.tr()
        self.player_button = gui.Button('Player')
        self.player_table.td(self.player_button, style=td_style)
        self.player_button.connect(gui.CLICK, self.choose_player)
        self.player_table.tr()
        self.player_input = gui.Input(str(self.player), size=5)
        add_event_handler(self.player_input, enter_pressed,
                          lambda e: self.new_player())
        self.player_table.td(self.player_input, style=td_style)
        self.player_table.tr()
        self.checker = ImageWidget(image=self.items[self.amount][self.player],
                                   width=request("map_editor.view_width"),
                                   height=request("map_editor.view_height"))
        self.player_table.td(self.checker, style=td_style)
        self.player_table.tr()
        # self.color = gui.Color("#000000", width=mode.rect.w, height=mode.rect.w)
        # self.color.connect(gui.CLICK, self.choose_player)
        # self.player_table.td(self.color, style=td_style)
        container.add(self.player_table, 0, mode.rect.bottom + spacer)

        self.painter = Painter(
            width=container.rect.w - mode.rect.w - spacer * 2,
            height=container.rect.h - self.menus.rect.h - spacer * 2,
            style={'border': 1})
        container.add(self.painter, mode.rect.w + spacer,
                      self.menus.rect.h + spacer)
        if board:
            self.painter.set_map(board)
        self.painter.rect.w, self.painter.rect.h = self.painter.resize()

        self.widget = container

    def extend(self, *pargs):
        if self.painter.board is not None:
            self.painter.set_map(self.painter.board.extend())

    def reduce(self, *pargs):
        if self.painter.board is not None:
            self.painter.set_map(self.painter.board.reduce())

    def add_top_row(self, *pargs):
        if self.painter.board is not None:
            self.painter.set_map(self.painter.board.add_top_row())

    def add_bottom_row(self, *pargs):
        if self.painter.board is not None:
            self.painter.set_map(self.painter.board.add_bottom_row())

    def add_left_column(self, *pargs):
        if self.painter.board is not None:
            self.painter.set_map(self.painter.board.add_left_column())

    def add_right_column(self, *pargs):
        if self.painter.board is not None:
            self.painter.set_map(self.painter.board.add_right_column())

    def remove_top_row(self, *pargs):
        if self.painter.board is not None:
            self.painter.set_map(self.painter.board.remove_top_row())

    def remove_bottom_row(self, *pargs):
        if self.painter.board is not None:
            self.painter.set_map(self.painter.board.remove_bottom_row())

    def remove_left_column(self, *pargs):
        if self.painter.board is not None:
            self.painter.set_map(self.painter.board.remove_left_column())

    def remove_right_column(self, *pargs):
        if self.painter.board is not None:
            self.painter.set_map(self.painter.board.remove_right_column())

    def remove_same_checkers(self, *pargs):
        if self.painter.board is not None:
            self.painter.set_map(
                self.painter.board.empty_cells(
                    condition=lambda cell: cell == (self.player, self.amount)))

    def remove_same_player_checkers(self, *pargs):
        if self.painter.board is not None:
            self.painter.set_map(
                self.painter.board.empty_cells(
                    condition=lambda cell: cell[0] == self.player))

    def remove_same_level_checkers(self, *pargs):
        if self.painter.board is not None:
            self.painter.set_map(
                self.painter.board.empty_cells(
                    condition=lambda cell: cell[1] == self.amount))

    def remove_checkers(self, *pargs):
        if self.painter.board is not None:
            self.painter.set_map(self.painter.board.empty_cells())

    def remove_cells(self, *pargs):
        if self.painter.board is not None:
            self.painter.set_map(self.painter.board.remove_all_cells())

    def add_cells(self, *pargs):
        if self.painter.board is not None:
            self.painter.set_map(self.painter.board.fill_cells())

    def permute_players(self, *pargs):
        if self.painter.board is not None:
            self.painter.set_map(self.painter.board.permute_players())

    def permute_checkers(self, *pargs):
        if self.painter.board is not None:
            self.painter.set_map(self.painter.board.permute_checkers())

    def permute_cells(self, *pargs):
        if self.painter.board is not None:
            self.painter.set_map(self.painter.board.permute_cells())

    def choose_player(self):
        if self.painter.board is not None:
            title = gui.Label("Choose player")
            table = gui.Table()
            players = list(sorted(self.painter.board.players))
            radios = gui.Group(value=self.player_input.value)
            for player in players:
                table.td(gui.Radio(radios, value=str(player)), style=td_style)
            table.tr()
            for player in players:
                table.td(
                    ImageWidget(image=self.items[self.amount][player],
                                style=td_style,
                                width=request("map_editor.view_width"),
                                height=request("map_editor.view_height")))
            button = gui.Button("  Apply  ")
            table.tr()
            table.td(button, style=td_style)
            dialog = gui.Dialog(title, table)

            def on_click():
                dialog.value = int(radios.value)
                dialog.send(gui.CHANGE)
                dialog.close()

            button.connect(gui.CLICK, on_click)
            dialog.connect(gui.CHANGE, lambda: self.new_player(dialog.value))
            dialog.open()

    def parse_player(self):
        s = self.player_input.value
        if s.isdigit():
            return int(s)
        else:
            print("WARNING: Wrong `player`: '{}', `player` must be an integer".
                  format(s))

    def new_player(self, player=None):
        if player is None: player = self.parse_player()
        if player in range(len(self.items[1])):
            self.player = player
            self.player_input.value = str(self.player)
            self.checker.new_image(self.items[self.amount][self.player])
            if self.painter.board is not None:
                self.set_brush()
        else:
            print(
                "WARNING: Wrong `player`: '{}', applicable players: {}".format(
                    player, list(range(len(self.items[1])))))

    def new_amount(self):
        if self.selector.value in self.items.keys():
            self.amount = self.selector.value
            self.checker.new_image(self.items[self.amount][self.player])
            self.set_brush()
        else:
            print("WARNING: Wrong amount: {}, applicable amounts: {}".format(
                self.selector.value, list(self.items.keys())))

    def set_brush(self):
        self.painter.set_brush(brush=self.mode.value,
                               player=self.player,
                               amount=self.amount)

    def action_new(self):
        self.new_dialog.close()
        self.filename = self.new_dialog.filename
        self.filename_input.value = op.split(self.filename)[-1]
        board = self.new_dialog.value
        self.painter.set_map(board)
        self.set_brush()

    def action_save(self, *pargs):
        if self.painter.board is not None:
            core.save_map(self.painter.board, self.filename, full=True)
        else:
            print("WARNING: Nothing done, nothing to save")

    def quit(self, *_, **__):
        gui.Desktop.quit(self)
        # BUG with closing: doesn't resize
        sys_exit()
        # self.callback(board=self.board, filename=self.filename)

    def save_as(self, *pargs):
        if self.painter.board is not None:
            self.save_dialog.new_filename(self.filename_input.value)
            self.save_dialog.open()
        else:
            print("WARNING: Nothing done, nothing to save")

    def action_saveas(self):
        self.save_dialog.close()
        self.filename = self.save_dialog.value
        self.filename_input.value = op.split(self.filename)[-1]
        core.save_map(self.painter.board, self.filename, full=True)

    def new_map(self):
        self.open_dialog.close()
        self.filename = self.open_dialog.value
        loader = self.open_dialog.format.map_loader
        self.painter.set_map(board=loader(self.filename))
        self.set_brush()
        name, ext = op.splitext(self.filename)
        self.filename = request("map_editor.autosave_pattern").format(
            name=name, ext=request("formats.map.extension"))
        self.filename_input.value = op.split(self.filename)[-1]
Example #14
0
class ProgramWidget(QWidget):
    def __init__(self, parent):
        super().__init__(parent)

        self.image_widget = ImageWidget(parent)
        self.hist_widget = HistogramWidget(parent)
        self.coord_label = QLabel("", self)
        self.pixel_rgb_label = QLabel('', self)
        self.pixel_hsv_label = QLabel('', self)
        self.pixel_lab_label = QLabel('', self)

        self.hsv_checkbox = QCheckBox("Shift HSV")
        self.hsv_checkbox.toggled.connect(self.slider_update)

        h_slider_box, self.h_slider = self._get_slider_box(
            "H:", -180, 180, 60, self.slider_update
        )

        s_slider_box, self.s_slider = self._get_slider_box(
            "S:", -100, 100, 10, self.slider_update
        )

        v_slider_box, self.v_slider = self._get_slider_box(
            "V:", -100, 100, 10, self.slider_update
        )

        self._set_default()

        self.image_widget.selection_update.connect(self.selection_upd)

        hbox = QHBoxLayout()
        hbox.addWidget(self.image_widget, 20)

        vbox = QVBoxLayout()
        vbox.addWidget(self.hist_widget)
        vbox.addWidget(self.coord_label)
        vbox.addWidget(self.pixel_rgb_label)
        vbox.addWidget(self.pixel_hsv_label)
        vbox.addWidget(self.pixel_lab_label)

        vbox.addWidget(self.hsv_checkbox)
        vbox.addLayout(h_slider_box)
        vbox.addLayout(s_slider_box)
        vbox.addLayout(v_slider_box)

        self._filter_buttons(vbox)

        hbox.addLayout(vbox, 1)

        self.setLayout(hbox)

    def _filter_buttons(self, vbox):
        self.filter_rbtn = QButtonGroup()
        hbox = QHBoxLayout()

        radio1 = QRadioButton("Отключить", self)
        self.filter_rbtn.addButton(radio1, 0)
        hbox.addWidget(radio1)
        radio1.setChecked(True)

        radio2 = QRadioButton("Гаусса", self)
        self.filter_rbtn.addButton(radio2, 1)
        hbox.addWidget(radio2)

        radio3 = QRadioButton("Собеля", self)
        self.filter_rbtn.addButton(radio3, 2)
        hbox.addWidget(radio3)

        radio4 = QRadioButton("Габора", self)
        self.filter_rbtn.addButton(radio4, 3)
        hbox.addWidget(radio4)

        vbox.addLayout(hbox)

        slider_box, self.sigma_slider = self._get_slider_box("σ", 0, 10, 0.5, self._filter_change)

        vbox.addLayout(slider_box)
        self.filter_rbtn.buttonClicked.connect(self._filter_change)

    def _filter_change(self):
        filter_id = self.filter_rbtn.checkedId()
        self._update_sigma_slider(filter_id)
        param1 = self.sigma_slider.value() / 10
        self.image_widget.set_filter(filter_id, param1)

    def _update_sigma_slider(self, filter_id):
        if filter_id == 1:
            self.sigma_slider.setEnabled(True)
            self.sigma_slider.setText("σ")
            self.sigma_slider.setMinimum(0)
            self.sigma_slider.setMaximum(100)
            self.sigma_slider.setTickInterval(1)
        elif filter_id == 3:
            self.sigma_slider.setEnabled(True)
            self.sigma_slider.setText("θ")
            # self.sigma_slider.setValue(1)
            self.sigma_slider.setMinimum(0)
            self.sigma_slider.setMaximum(3600)
            self.sigma_slider.setTickInterval(50)
        else:
            self.sigma_slider.setText("-")
            self.sigma_slider.setDisabled(True)

    def _get_slider_box(self, label_name, _min, _max, _interval, callback, layout=Qt.Horizontal) -> Tuple[QBoxLayout, QSlider]:
        slider = QSlider(layout, self)
        slider.setMinimum(_min)
        slider.setMaximum(_max)
        slider.setTickPosition(QSlider.TicksBelow)
        slider.setTickInterval(_interval)

        slider.sliderReleased.connect(callback)

        box = QHBoxLayout()
        label = QLabel(label_name, self)
        box.addWidget(label)
        box.addWidget(slider)
        slider._label = label

        def setText(text):
            slider._label.setText(text)

        slider.setText = setText

        return box, slider

    def _set_default(self):
        self.coord_label.setText("Select pixel")
        self.pixel_rgb_label.setText("Select pixel")
        self.pixel_hsv_label.setText("Select pixel")
        self.pixel_lab_label.setText("Select pixel")

        self.h_slider.setValue(0)

    def slider_update(self):
        if self.hsv_checkbox.isChecked():
            self.image_widget.shift_hsv = self.h_slider.value(), self.s_slider.value(), self.v_slider.value()
        else:
            self.image_widget.shift_hsv = [0, 0, 0]

    def selection_upd(self):
        img = self.image_widget.selected(self.hist_widget.speed)
        print(img.width(), img.height())

        coord = self.image_widget.selection_img
        self.coord_label.setText("{}, {} x {}, {}".format(
            coord.left(), coord.top(),
            coord.right(), coord.bottom()
        ))

        if 1 == img.width() == img.height():
            pixel = QColor(img.pixel(0, 0))

            self.pixel_rgb_label.setText(
                "R:{}, G:{}, B:{}".format(pixel.red(), pixel.green(), pixel.blue())
            )

            self.pixel_hsv_label.setText(
                "H:{}, S:{}, V:{}".format(pixel.hue(), pixel.saturation(), pixel.value())
            )

            self.pixel_lab_label.setText(
                "L:{:.1f}, A:{:.1f}, B:{:.1f}".format(*pixel.lab())
            )
        else:
            self.pixel_rgb_label.setText("Select one pixel")
            self.pixel_hsv_label.setText("Select one pixel")
            self.pixel_lab_label.setText("Select one pixel")

        self.hist_widget.calc_image(img)

    def set_image(self, image: QImage):
        self.image_widget.set_image(image)
Example #15
0
    def _create_widgets(self):
        """
        Create the widgets and return the background widget.
        :return: the background widget
        """
        # Create the background widget.
        bg = self._rm.get_image(BACKGROUND_IMAGE, self.screen.get_size())
        bg_widget = ImageWidget((0, 0), self.screen.get_size(), -1, bg)

        # Create the waiting text.
        wait_box = special_widgets.warning_widget(None, (400, 100), "Waiting for other players", self._font,
                                                  screen_size=self.screen.get_size(), close_on_click=False)
        wait_box.visible = True
        bg_widget.add_widget(wait_box)
        self._warnings["wait_box"] = wait_box

        # Create the "invalid num tricks" warning.
        invalid_num_warning = special_widgets.warning_widget(None, (400, 100), "Invalid number of tricks", self._font,
                                                             screen_size=self.screen.get_size())
        bg_widget.add_widget(invalid_num_warning)
        self._warnings["invalid_num_tricks"] = invalid_num_warning

        # Create the chat widget.
        chat_box = special_widgets.warning_widget((10, self.screen.get_height()-260), (260, 200), "chat", self._font,
                                                  close_on_click=False)
        chat_box.visible = True
        bg_widget.add_widget(chat_box)

        # Create the "Your move" box.
        your_move_w = Text((self.screen.get_width()-140, self.screen.get_height()-110), (120, 40), 0, "Your move",
                           self._font, fill=(0, 0, 0, 160))
        your_move_w.opacity = 0
        bg_widget.add_widget(your_move_w)
        self._user_move_widget = your_move_w

        # Create the trump widgets.
        trump_pos = (180, 180)
        trump_size = (125, 125)
        for color in ["W", "H", "D", "S", "C"]:
            im_filename = get_color_image_filename(color)
            im = self._rm.get_image(im_filename, trump_size)
            im_w = ImageWidget(trump_pos, trump_size, 0, im)
            im_w.opacity = 0
            bg_widget.add_widget(im_w)
            self._trump_widgets[color] = im_w

        # Create the "choose trump" widgets.
        class ChooseHandler(object):
            def __init__(self, view, trump):
                self._view = view
                self._trump = trump
            def __call__(self, x, y):
                self._view._handle_choose_trump(self._trump)
        choose_size = (90, 90)
        choose_trump_bg = pygame.Surface((400, 170), flags=pygame.SRCALPHA)
        choose_trump_bg.fill((0, 0, 0, 160))
        font_obj = self._font.render("Choose the trump:", True, (255, 255, 255, 255))
        choose_trump_bg.blit(font_obj, ((choose_trump_bg.get_width()-font_obj.get_width())/2, 20))
        choose_trump_container = ImageWidget((self.screen.get_width()/2 - 200, 200), choose_trump_bg.get_size(), 99,
                                             choose_trump_bg, visible=False)
        for i, color in enumerate(["D", "S", "H", "C"]):
            im_filename = get_color_image_filename(color)
            im = self._rm.get_image(im_filename, choose_size)
            im_w = ImageWidget((i*(choose_size[0]+10), 70), choose_size, 0, im)
            choose_trump_container.add_widget(im_w)
            im_w.handle_clicked = ChooseHandler(self, color)
        bg_widget.add_widget(choose_trump_container)
        self._choose_trump_widget = choose_trump_container

        return bg_widget
Example #16
0
class View(Subject, QMainWindow):
    """Main window class, and the View of the MVC pattern.

    Attributes:
        model: The model of the MVC pattern.
        menu_bar: A menu bar derived from QMenuBar.
        tool_bar: A tool bar derived from QToolBar.
        exif_area: The widget containing any available EXIF data.
        image_area: The widget containing the displayed image.
        status_bar: A status bar derived from QStatusBar.
        about: The classic "About" informative widget (actually a new, separate window).
    """
    def __init__(self, model):
        """Inits the class."""
        Subject.__init__(self)
        QMainWindow.__init__(self)

        # Set model
        self.model = model

        # Create interface elements
        self.menu_bar = MenuBar(self)
        self.tool_bar = ToolBar(self)
        self.exif_area = ExifWidget(self)
        self.image_area = ImageWidget(self)
        self.status_bar = StatusBar()
        about_text = 'IEViewer 1.0' \
                     '<br><br>' \
                     'Copyright © 2021 by' \
                     '<br>' \
                     'Paula Mihalcea' \
                     '<br>' \
                     '<a href="mailto:[email protected]">[email protected]</a>' \
                     '<br><br>' \
                     'This program uses PyQt5, a comprehensive set of Python bindings for Qt v5. Qt is a set of cross-platform C++ libraries that implement high-level APIs for accessing many aspects of modern desktop and mobile systems.\n' \
                     '<br><br>' \
                     'PyQt5 is copyright © Riverbank Computing Limited. Its homepage is <a href="https://www.riverbankcomputing.com/software/pyqt/">https://www.riverbankcomputing.com/software/pyqt/</a>.' \
                     '<br><br>' \
                     'No genasi were harmed in the making of this application. <a href="https://www.dndbeyond.com/races/genasi#WaterGenasi">#GenasiLivesMatter#NereisThalian</a>'
        self.about = AboutWidget('About IEViewer',
                                 about_text,
                                 image_path='icons/about_img.png')

        # Disable GUI elements that are unavailable when no image is opened
        self.menu_bar.disable_widgets()
        self.exif_area.hide()

        # Set layout
        self.setCentralWidget(Layout(self).central_widget)

        # Set window properties
        self.set_window_properties()

        # Install additional event filters
        self.image_area.installEventFilter(self)

    def set_window_properties(self):
        """Sets some main window properties."""
        self.statusBar()
        self.setStatusTip('Ready.')
        self.setWindowTitle(
            'IEViewer')  # Window title (the one in the title bar)
        self.resize(
            512,
            256)  # These default dimensions should be fine for most displays

    def get_open_file_dialog(self, caption, filter):
        """Opens an "Open File" dialog and returns a file path. If no file has been selected and the user has pressed "Cancel" or closed the dialog, then it returns None."""
        file_path, _ = QFileDialog.getOpenFileName(caption=caption,
                                                   directory='',
                                                   filter=filter)
        if file_path == '':
            return None
        else:
            return file_path

    def get_save_file_dialog(self, caption, filter):
        """Opens a "Save As" dialog and returns a file path and format. If no name has been entered and the user has pressed "Cancel" or closed the dialog, then it returns None."""
        file_path, format = QFileDialog.getSaveFileName(
            caption=caption, directory='', filter=filter
        )  # By omitting the directory argument (empty string, ''), the dialog should remember the last directory (depends on operating system)
        if file_path == '':
            return None, None
        else:
            format = format.split(' ')
            return file_path, format[0]

    def show_message_box(self, title, text, icon=QMessageBox.Information):
        """Opens a simple message box window, with some text and a customizable icon."""
        info_box = QMessageBox(self)

        if icon is not None:
            info_box.setIcon(icon)
        info_box.setWindowTitle(title)
        info_box.setText(text)

        info_box.show()

    def event(self, e):
        """Defines the default status bar message (when nothing else is displayed)."""
        if e.type() == QEvent.StatusTip:
            if e.tip() == '':
                e = QStatusTipEvent('Ready.')
        return super().event(e)

    def open(self):
        """Only needed to notify observers (i.e. the controller) that they should load an image."""
        self.set_state('open')

    def load_image(self):
        """Displays the image contained in the model."""
        # Get original image dimensions
        width = self.model.image.width()
        height = self.model.image.height()

        # Recalculate image dimensions so as to have a maximum dimension (height or width) of 512 pixels
        if width >= height and width > 512:
            w = 512
            h = int(512 * height / width)
        elif height >= width and height > 512:
            w = int(512 * width / height)
            h = 512
        else:
            w = width
            h = height

        # Generate and set a pixmap from the image
        self.image_area.set_image(self.model.image, w, h)

        # Resize the window and pixmap (generated from the image),
        # so as to display correctly the window title, menu bar and image (actually, its pixmap)
        #
        # The diff parameter is another piece of PyQt5 magic.
        # Without it, an image larger than 512 pixels might be displayed smaller
        # than the maximum 512 that it has been resized to
        # (despite having been resized to 512 pixels).

        diff = 130  # This value is good for both Windows 10 and Ubuntu 20.04
        if w < 280:  # Again, this should suffice for both OSs
            self.resize(280, h + diff)
        else:
            self.resize(w, h + diff)
        self.image_area.pixmap.scaled(w, h)

        # Add EXIF data from the model
        self.exif_area.load_exif()

        # Update window title
        self.setWindowTitle('IEViewer - ' + self.model.filename)

        # Enable menus
        self.menu_bar.enable_widgets()

    def save(self):
        """Saves the modified image. Uses the stored original image, not the modified version currently displayed in the window."""
        self.model.set_image(
            self.model.image.transformed(
                QTransform().rotate(self.image_area.rot),
                Qt.SmoothTransformation))
        self.set_state('save')

    def saveas(self):
        """Saves the modified image with another name. Uses the stored original image, not the modified version currently displayed in the window."""
        self.model.set_image(
            self.model.image.transformed(
                QTransform().rotate(self.image_area.rot),
                Qt.SmoothTransformation))
        self.set_state('saveas')

    def eventFilter(self, widget, event):
        """Adds new behavior for certain events."""

        # Resize event
        # Or what should happen to the image widget when the user resizes the main window
        if event.type(
        ) == QEvent.Resize and widget is self.image_area and self.image_area.pixmap is not None:
            self.image_area.setPixmap(
                QPixmap.fromImage(self.model.modified_image).scaled(
                    self.image_area.width(),
                    self.image_area.height(),
                    aspectRatioMode=Qt.KeepAspectRatio,
                    transformMode=Qt.SmoothTransformation))
            # Update new dimensions for later use
            self.image_area.w = self.image_area.width()
            self.image_area.h = self.image_area.height()
            return True

        return QMainWindow.eventFilter(self, widget, event)

    def close(self):
        """Closes an image."""
        self.set_state('close')

        # Clear image area widget and adjust layout
        self.image_area.clear_image()
        self.exif_area = ExifWidget(self)
        self.setCentralWidget(Layout(self).central_widget)

        # Disable unavailable menus
        self.menu_bar.disable_widgets()

        # Hide EXIF area (if visible) and show blank image widget
        self.exif_area.hide()
        self.image_area.show()

        # Update window title
        self.setWindowTitle('IEViewer')

    def exit(self):
        """Only needed to notify observers (i.e. the controller) that they should exit the application."""
        self.set_state('exit')

    def rotate(self, degree):
        """Base function for rotation.

        Subsequent rotation functions are only needed as hooks for menu actions; actual rotations happen here.
        Here, the View directly changes the Model. Arguably, this is a violation of the MVC pattern. It happens that, for some reason, returning a reference to the rotated image to the Controller in order to have the Controller itself change the Model does not work, probably because it only returns a reference and not a copy of the rotated image. PyQt5 does not allow deep copies of its objects, so the following seems to be the only way to make it work.
        """
        self.model.set_modified_image(
            self.model.image.transformed(
                QTransform().rotate(self.image_area.rot + degree),
                Qt.SmoothTransformation))  # Rotate image
        self.image_area.set_image(self.model.modified_image, self.image_area.w,
                                  self.image_area.h)  # Display rotated image
        self.image_area.rot += degree  # Update rotation history

    def rotate180(self):
        """Rotate the displayed image by 180 degrees."""
        self.rotate(180)

    def rotate90c(self):
        """Rotate the displayed image by 90 degrees clock wise."""
        self.rotate(90)

    def rotate90cc(self):
        """Rotate the displayed image by 90 degrees clock wise."""
        self.rotate(-90)

    def reset_image(self):
        """Reset the displayed image to its original orientation."""
        self.rotate(-self.image_area.rot)

    def show_exif(self):
        """Display EXIF data (and at the same time hide the image)."""
        if self.exif_area.isHidden():
            self.image_area.hide()
            self.exif_area.show()
        else:
            self.image_area.show()
            self.exif_area.hide()

    def about(self):
        """Display info about the program."""
        self.about.show()