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
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
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 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 _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)
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
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)
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 _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
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
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
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
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]
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)
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
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()