def test_equal_same_text(): drawer1 = ConfigDrawer('fake_canvas') drawer2 = ConfigDrawer('fake_canvas') drawer1.add_text(text='sample_text', pos_x=100, pos_y=200, draw=False) drawer2.add_text(text='sample_text', pos_x=100, pos_y=200, draw=False) assert drawer1 == drawer2
def test_equal_same_multiple_widgets(): drawer1 = ConfigDrawer('fake_canvas') drawer2 = ConfigDrawer('fake_canvas') drawer1.add_image_button(pos_x=100, pos_y=200, orig_on_release=True, images=['path1'], draw=False) drawer2.add_image_button(pos_x=100, pos_y=200, orig_on_release=True, images=['path1'], draw=False) drawer1.add_text(text='sample_text', pos_x=100, pos_y=200, draw=False) drawer2.add_text(text='sample_text', pos_x=100, pos_y=200, draw=False) assert drawer1 == drawer2
def test_save_config_no_unsaved_changes(monkeypatch): def mock_json_save(mock, mock2): None monkeypatch.setattr(imgloader, 'dump_json', mock_json_save) drawer = ConfigDrawer('fake_canvas') drawer.add_text(text='sample_text', pos_x=100, pos_y=200, draw=False) assert drawer.unsaved_changes drawer.save_config_to_file('fake_path') assert not drawer.unsaved_changes
def test_load_config_no_unsaved_changed(): config = { "background": "path", "Text": [ {'label': None, "text": "Test", "x": 100, "y": 100} ] } drawer = ConfigDrawer('fake_canvas') drawer.add_text(text='sample_text', pos_x=100, pos_y=200, draw=False) assert drawer.unsaved_changes drawer._load_config(config, config_path='path', draw=False) assert not drawer.unsaved_changes
def test_remove_widget(): drawer = ConfigDrawer('fake_canvas') widget = drawer.add_text(text='myText', pos_x=200, pos_y=300, draw=False) widget_id = id(widget) assert widget_id in drawer.widgets drawer.remove_widget(widget, draw=False) assert widget_id not in drawer.widgets
def test_save_load_config_identical(monkeypatch): def mock_json_save(mock, mock2): None monkeypatch.setattr(imgloader, 'dump_json', mock_json_save) # Create 1 Config from Blank $ Confirm empty, Add Text, Add Buttons drawer1 = ConfigDrawer('fake_canvas') drawer1.load_background('path', draw=False) # Add all widgets drawer1.add_text(label='myText', text='sample_text', pos_x=100, pos_y=200, draw=False) drawer1.add_image_button(label='button', pos_x=100, pos_y=200, orig_on_release=True, images=['path1'], draw=False) drawer1.add_input_box(label='box', pos_x=100, pos_y=200, draw=False) button2 = drawer1.add_image_button(pos_x=100, pos_y=200, orig_on_release=False, images=['path1', 'path2'], draw=False) button2.next_image() # Save the Config drawer1.save_config_to_file('fake_path') # Copy the saved config config_copy = copy.deepcopy(drawer1.saved_img_config) # Create Config 2 & Confirm Empty drawer2 = ConfigDrawer('fake_canvas') d2_config = drawer2.calc_config_dict() # Load Config drawer2._load_config(config_copy, config_path='fake_path', draw=False) # Check can find our Label widget = drawer2.get_widget_with_label('myText') assert widget is not None print(drawer1.calc_config_dict()) print(drawer2.calc_config_dict()) # Check both configs the same assert drawer1 == drawer2
def test_unequal_different_text(): drawer1 = ConfigDrawer('fake_canvas') drawer2 = ConfigDrawer('fake_canvas') drawer3 = ConfigDrawer('fake_canvas') drawer4 = ConfigDrawer('fake_canvas') drawer1.add_text(text='sample_text', pos_x=100, pos_y=200, draw=False) assert drawer1 != drawer2 drawer2.add_text(text='sample_text2', pos_x=100, pos_y=200, draw=False) assert drawer1 != drawer2 drawer3.add_text(text='sample_text', pos_x=300, pos_y=200, draw=False) assert drawer1 != drawer3 drawer4.add_text(text='sample_text', pos_x=100, pos_y=400, draw=False) assert drawer1 != drawer4
def test_add_widget(): drawer = ConfigDrawer('fake_canvas') widget = drawer.add_text(text='myText', pos_x=200, pos_y=300, draw=False) assert id(widget) in drawer.widgets
def test_add_widgets_reject_duplicate_labels(): drawer = ConfigDrawer('fake_canvas') widget = drawer.add_text(label='unique_label', text='myText', pos_x=200, pos_y=300, draw=False) with pytest.raises(ValueError): drawer.add_text(label='unique_label', text='myText', pos_x=200, pos_y=300, draw=False)
def test_add_text_widget_with_label(): drawer = ConfigDrawer('fake_canvas') widget = drawer.add_text(label='unique_label', text='myText', pos_x=200, pos_y=300, draw=False) assert widget.label == 'unique_label'
def test_add_text_widget_duplicate_text(): drawer = ConfigDrawer('fake_canvas') drawer.add_text(text='myText', pos_x=200, pos_y=300, draw=False) drawer.add_text(text='myText', pos_x=200, pos_y=300, draw=False)
class ImgEditor(): def __init__(self, root, working_dir): logger.debug(F'Working Dir: {working_dir}') self.root_window = root self.working_dir = working_dir self.canvas = None self.columnspan = 20 self.menu_widgets = [] # Init the Canvas self._init_canvas() # Init the Config Drawer self.img_loader = ConfigDrawer(self.canvas) # Draw the Menu Bar self._draw_menu() # draw the text options self._draw_navigation_options() def _init_canvas(self): self.root_window.title(F'Config: N/A') self.canvas = tk.Canvas(self.root_window) self.canvas.grid(row=0, columnspan=self.columnspan) def _draw_menu(self): logger.debug(F'Drawing Menu') menubar = tk.Menu(self.root_window) file_menu = tk.Menu(menubar, tearoff=0) # Load Background file_menu.add_command(label='Select Background', command=self._open_background_image) file_menu.add_separator() # Load and Save config file_menu.add_command(label='Load Config', command=self._load_config) file_menu.add_command(label='Save Config', command=self._save_config) file_menu.add_separator() # Exit file_menu.add_command(label='Exit', command=self.exit) menubar.add_cascade(label='File', menu=file_menu) menubar.add_command(label='+ Text', command=self.add_text) menubar.add_command(label='+ Image Button', command=self.add_image_button) menubar.add_command(label='+ Input Box', command=self.add_input_box) if not self.img_loader.background_path: menubar.entryconfig('+ Text', state="disabled") menubar.entryconfig('+ Image Button', state="disabled") menubar.entryconfig('+ Input Box', state="disabled") self.root_window.config(menu=menubar) # Set our job to refresh screen data self.root_window.after(1000, self._refresh_screen_data) def _draw_navigation_options(self): # pylint: disable=too-many-locals # Remove existing frames to redraw for menu_widget in self.menu_widgets: menu_widget.destroy() row = 0 for widget in self.img_loader.widgets.values(): row += 1 col = 0 frame = tk.Frame(self.root_window, width=self.root_window.winfo_width(), bg="SystemButtonFace", colormap="new") frame.grid(row=row, columnspan=self.columnspan, sticky=tk.NSEW) # Widget Description text_col_span = 4 main_text = tk.Label(frame, text=str(widget), anchor=tk.W) main_text.grid(row=row, column=col, columnspan=text_col_span, sticky=tk.W) frame.grid_columnconfigure(col, weight=2) col += text_col_span # Image Button Specific Menus if widget.widget_type == WidgetType.BUTTON: button = tk.Button(frame, borderwidth=1, text='+ Img', command=partial(self.add_image_to_button, widget)) button.grid(row=row, column=col, sticky=tk.NSEW) col += 1 button = tk.Button(frame, borderwidth=1, text='- Img', command=partial(self.remove_current_image, widget)) button.grid(row=row, column=col, sticky=tk.NSEW) col += 1 # Input box Specific if widget.widget_type == WidgetType.INPUT_BOX: def test_inputbox_callback(*, widget, text): logger.info( F'Callback called for Input Box "{widget}" with value "{text}"' ) widget.add_callback( input_confirm_callback=test_inputbox_callback) button = tk.Button(frame, borderwidth=1, text='Width', command=partial(self.set_input_box_width, widget)) button.grid(row=row, column=col, sticky=tk.NSEW) col += 1 # Raise the Widget button = tk.Button(frame, borderwidth=1, text='Raise', command=widget.lift) button.grid(row=row, column=col, sticky=tk.NSEW) col += 1 # Adding/Removing Labels if not widget.label: button = tk.Button(frame, borderwidth=1, text='+ Label', command=partial(self.add_widget_label, widget)) else: button = tk.Button(frame, borderwidth=1, text=F'- Label', command=partial(self.remove_widget_label, widget)) button.grid(row=row, column=col, sticky=tk.NSEW) col += 1 # Text Moving Navigation nav_intervals = [50, 10, 1] for interval in nav_intervals: # Large Nav Text label = tk.Label(frame, text=F'Move {interval}') label.grid(row=row, column=col, sticky=tk.NSEW) col += 1 # Large Nav Buttons for direction in NAV_DIRECTIONS: button = tk.Button(frame, borderwidth=1, text=direction[0], command=partial( self.move_widget_by, direction[1] * interval, direction[2] * interval, widget=widget, main_text_label=main_text)) button.grid(row=row, column=col, sticky=tk.NSEW) col += 1 # Remove Button remove_button = tk.Button(frame, borderwidth=1, text='Del', command=partial(self.remove_widget, widget)) remove_button.grid(row=row, column=col, sticky=tk.NSEW) row += 1 self.menu_widgets.append(frame) # Draw the separatpr sep = ttk.Separator(self.root_window, orient=tk.HORIZONTAL) sep.grid(column=0, row=row, columnspan=self.columnspan, sticky='ew') self.menu_widgets.append(sep) def _open_background_image(self): file_path = ask_image_filepath('Select the Background Image', self.working_dir) if file_path: logger.debug(F'Filepath Selected: "{file_path}""') rel_path = self._get_rel_path(file_path) logger.debug(F'Rel Filepath Selected: "{rel_path}"') self.img_loader.load_background(rel_path) self._draw_menu() # To enable Insert Box def _load_config(self): can_load = True if self.img_loader.unsaved_changes: if not messagebox.askyesno('Unsaved Changes', 'Load Config without saving?'): can_load = False if can_load: config_path = filedialog.askopenfilename( title='Select File to Save', initialdir=self.working_dir, filetypes=(('Save Config', '.json'), )) if config_path: self.img_loader.load_config_file(config_path) # Draw Editor Parts self._draw_menu() # To enable Insert Box self._draw_navigation_options() def _save_config(self): config_path = self.img_loader.config_path if not config_path: config_path = filedialog.asksaveasfilename( title='Select File to Save', initialdir=self.working_dir, filetypes=(('Save Config', '.json'), )) if config_path: if not config_path.lower().endswith('.json'): config_path += '.json' self.img_loader.save_config_to_file(config_path) def _get_rel_path(self, path): return os.path.relpath(path, self.working_dir) def exit(self): can_exit = True if self.img_loader.unsaved_changes: if not messagebox.askyesno('Unsaved Changes', 'Exit without Saving?'): can_exit = False if can_exit: logger.debug('Exit Application') self.root_window.quit() def move_widget_by(self, move_x, move_y, *, widget, main_text_label): # pylint: disable=no-self-use widget.move_by(move_x=move_x, move_y=move_y) main_text_label.config(text=str(widget)) def remove_widget(self, widget): self.img_loader.remove_widget(widget) self._draw_navigation_options() def add_widget_label(self, widget): answer = simpledialog.askstring("Input", "Enter Widget Label", parent=self.root_window) if answer: widget.label = answer # Draw Editor Parts self._draw_navigation_options() def remove_widget_label(self, widget): widget.label = None self._draw_navigation_options() # Text Related Options def add_text(self): answer = simpledialog.askstring("Input", "Enter the text to add", parent=self.root_window) if answer: self.img_loader.add_text(text=answer, pos_x=100, pos_y=100) # Draw Editor Parts self._draw_navigation_options() # Button Related Data def add_image_button(self): # Ask if a Button or Switch button_or_switch = messagebox.askyesno( "Question", "Is this a Button (Otherwise Switch)?") # Ask for the Button Image file_path_tuple = ask_multi_image_filepath('Select the Button Images', self.working_dir) if file_path_tuple: img_list = [ self._get_rel_path(file_path) for file_path in file_path_tuple ] button = self.img_loader.add_image_button( pos_x=100, pos_y=200, orig_on_release=button_or_switch, images=img_list) def test_release_callback(*, widget): logger.info(F'Callback called for Button "{widget}"') button.add_image_callback( button_release_func=test_release_callback) # Draw Editor Parts self._draw_navigation_options() else: messagebox.showerror('Error', F'At least 1 image needs to be selected') def add_image_to_button(self, widget): file_path_tuple = ask_multi_image_filepath('Select the Button Images', self.working_dir) if file_path_tuple: img_list = [ self._get_rel_path(file_path) for file_path in file_path_tuple ] widget.add_new_images(img_list) def remove_current_image(self, widget): try: deleted = widget.remove_current_image() if deleted: self._draw_navigation_options() except ValueError as error: messagebox.showerror('Warning', error) # Input Box Related Options def add_input_box(self): width = simpledialog.askinteger('Input', 'Input Box Width?', parent=self.root_window, minvalue=0) if width: self.img_loader.add_input_box(pos_x=100, pos_y=100, width=width) # Draw Editor Parts self._draw_navigation_options() def set_input_box_width(self, widget): width = simpledialog.askinteger('Input', 'New Input Box Width?', parent=self.root_window, minvalue=0) if width: widget.width = width def _refresh_screen_data(self): # https://riptutorial.com/tkinter/example/22870/-after-- dims = self.img_loader.dimensions resolution = F'{dims[0]} x {dims[1]}' self.root_window.title( F'Resolution: {resolution}, Config: "{self.img_loader.config_path}"' ) self.root_window.after(1000, self._refresh_screen_data)