From 4a68878046ef7af8831dea408258f6b147795f42 Mon Sep 17 00:00:00 2001 From: ES Alexander Date: Tue, 31 Mar 2020 15:20:22 +1100 Subject: [PATCH] -whitespace, nicer syntax --- Lessons/L9/GUI_Example/Controller.py | 20 +++---- Lessons/L9/GUI_Example/Model.py | 17 +++--- Lessons/L9/GUI_Example/View.py | 90 +++++++++++++++------------- 3 files changed, 67 insertions(+), 60 deletions(-) diff --git a/Lessons/L9/GUI_Example/Controller.py b/Lessons/L9/GUI_Example/Controller.py index 0891c95..361d34b 100644 --- a/Lessons/L9/GUI_Example/Controller.py +++ b/Lessons/L9/GUI_Example/Controller.py @@ -18,7 +18,7 @@ def __init__(self, master, graph_img=None): automatic line detection, saving of state (to reopen where you left off), or full automation with automatic axis detection and line detection combined. - + Constructor: Controller(tk.Tk, *str) ''' @@ -36,15 +36,15 @@ def _add_bind_functions(self): self._add_bind_functions() -> None ''' - self._view.add_binding_func('save_data', self._model.save_data) - self._view.add_binding_func('clear_data',self._model.clear_data) - self._view.add_binding_func('new_point', self._model.add_stored_point) - self._view.add_binding_func('del_point', - self._model.remove_stored_point) - self._view.add_binding_func('clear_stored_pts', - self._model.clear_stored_points) - self._view.add_binding_func('set_def_pts', - self._model.set_defining_points) + self._view.add_binding_funcs( + save_data = self._model.save_data, + clear_data = self._model.clear_data, + clear_stored_pts = self._model.clear_stored_points, + set_def_pts = self._model.set_defining_points, + new_point = self._model.add_stored_point, + del_point = self._model.remove_stored_point, + ) + if __name__ == '__main__': root = tk.Tk() diff --git a/Lessons/L9/GUI_Example/Model.py b/Lessons/L9/GUI_Example/Model.py index 44dd466..58d5f28 100644 --- a/Lessons/L9/GUI_Example/Model.py +++ b/Lessons/L9/GUI_Example/Model.py @@ -11,9 +11,9 @@ def __init__(self): Users specify defining points mapping the pixel-plane to the graph- plane. The Model stores the mapping, and converts additional pixel- point values to graph-points. - + Constructor: Model() - + ''' # initialise memory and points self.clear_data() @@ -74,14 +74,14 @@ def remove_stored_point(self, pixel_point, max_dist=10): # try to remove the exact point point_removed = self._stored_points.pop(pixel_point, None) - + if point_removed is None: # pixel_point is not a key in stored points, find nearest closest_point, dist = Model.get_closest(pixel_point, list(self._stored_points.keys())) if dist < max_dist: point_removed = self._stored_points.pop(closest_point) - + return point_removed def _update_stored_points(self): @@ -193,8 +193,7 @@ def _verify_points_map(points_map): # check graph values are integers/floats for graph_point in points_map.values(): Model._verify_point(graph_point) - - + @staticmethod def _verify_point(point, value_type=None): ''' Checks if a point is valid. @@ -221,7 +220,7 @@ def _verify_point(point, value_type=None): "point values should be {}.".format( value_type))) return # no issues, continue where left off - + raise Exception(("Invalid point length: {}".format(num_vals), "- points should have 2 values")) @@ -262,7 +261,7 @@ def get_closest(check_point, search_points): cp = np.matrix(check_point) sps = np.matrix(search_points) diff = sps - cp - + # get a list of the distance between check_point and each search point dists = [np.linalg.norm(i) for i in diff] # 'zip' joins N iterables for the first m elements, where m is the @@ -272,5 +271,5 @@ def get_closest(check_point, search_points): # the values of dists). Also available in 'max' function. min_dist, closest_point = min(zip(dists,search_points), key=lambda dist_sp: dist_sp[0]) - + return (closest_point, min_dist) diff --git a/Lessons/L9/GUI_Example/View.py b/Lessons/L9/GUI_Example/View.py index cf7ccc3..59baf0c 100644 --- a/Lessons/L9/GUI_Example/View.py +++ b/Lessons/L9/GUI_Example/View.py @@ -18,9 +18,11 @@ class View(object): # the set of valid external bindings used in the class external_bindings = ('save_data', 'clear_data', 'clear_stored_pts', 'set_def_pts', 'new_point', 'del_point') + INVALID_BINDING_MSG = "Invalid binding '{}'\nValid bindings are found " \ + "using View.external_bindings." # the set of valid internal bindings used in the class _internal_bindings = ('define', 'add', 'delete') - + def __init__(self, master, graph_img=None): ''' A class for managing the display of a graph-analysis GUI. @@ -42,15 +44,15 @@ def _init_master(self, master): ''' self._master = master # set the overarching master of the view self._master.title("Data From Graph") # set a title for the view - + # set the window to be 2/3 of the full size of the screen (int division) sw = self._master.winfo_screenwidth() sh = self._master.winfo_screenheight() self._master.geometry("{}x{}".format(sw*2//3,sh*2//3)) - + # turn off resizing in the x and y directions self._master.resizable(0, 0) - + # make GUI as tall as window self._master.rowconfigure(0, weight=1) @@ -101,7 +103,7 @@ def _setup_display(self): | | ------- | | | errors | +---------------+---------+ - + self._setup_display() -> None ''' @@ -168,7 +170,7 @@ def _setup_def_pt_controls(self, master): heading = tk.Label(master, text='Set Defining Points') grid_frame = tk.Frame(master) - # pack the heading and frame into the control frame grid + # put the heading and frame into the control frame grid heading.grid() grid_frame.grid() @@ -207,10 +209,10 @@ def _def_pt_submit(self, index): self._errors.set('Please set a defining point location on the ' + 'graph before submitting') return # invalid starting point - do not continue - + # update controls self._def_pt_controls[index].submit() - + # check if all 3 points defined (ready to submit to external Model) currently_defined = self._graph_canvas.find_withtag('define') if len(currently_defined) == 3: @@ -260,10 +262,10 @@ def _reset_def_points(self): # reset controls for point in range(3): self._def_pt_controls[point].reset() - + # set to defining first def pt self._def_pt_select(0) - + def _setup_control_buttons(self, master): ''' Initialises the stored-points control buttons. @@ -296,7 +298,7 @@ def _setup_error_window(self, master): self._errors.set('') tk.Label(master, textvariable=self._errors, fg='red', justify=tk.LEFT, wraplength=200).grid(pady=10) - + def _setup_menubar(self): ''' Initialises the menubar with the desired menus. @@ -328,7 +330,7 @@ def _setup_filemenu(self): filemenu.add_separator() # add a separation line (denote sections) filemenu.add_command(label="Save Data", command=self._save_data) filemenu.add_command(label="Save Graph", command=self._save_graph) - + # add to the menubar self._menubar.add_cascade(label="File", menu=filemenu) @@ -373,7 +375,7 @@ def _set_img(self, graph_img=None): title = "Select graph image", filetypes = (("JPEG image","*.jpg"), ("PNG image","*.png"), ("All Files","*.*"))) - + img = Image.open(graph_img) # extract image as PIL Image # scale image to take up the full canvas (stretching is fine) size = (self._graph_canvas.winfo_width(), @@ -381,7 +383,7 @@ def _set_img(self, graph_img=None): resized = img.resize(size, resample=Image.BILINEAR) # convert to tkinter PhotoImage, store externally to make visible self._img = PhotoImage(resized) - + # add the image to the canvas (modify existing if possible) canvas_image = self._graph_canvas.find_withtag('graph_image') if canvas_image: @@ -389,7 +391,7 @@ def _set_img(self, graph_img=None): else: self._graph_canvas.create_image(0, 0, image=self._img, anchor=NW, tags='graph_image') - + # new graph specified, so re-initialise data and view self._clear_data() # clear all data self._reset_def_points() # reset defining points controls @@ -406,14 +408,14 @@ def _save_data(self, filename=None): filename = tk.filedialog.asksaveasfilename(title = "Save Data As", filetypes = (("Comma Separated Value","*.csv"), ("All Files","*.*"))) - + self._call_bind_func('save_data', filename) def _clear_data(self): ''' Clears all data from the graph display and model. self._clear_data() -> None - + ''' self._graph_canvas.delete('point') # delete all view points self._call_bind_func('clear_data') # clear external point data @@ -439,7 +441,7 @@ def _clear_defining_points(self): ''' # delete all points with tag 'define' - self._graph_canvas.delete('define') + self._graph_canvas.delete('define') def _add_stored_point(self, pixel_point): ''' Add a new stored point to the external data and the graph. @@ -451,7 +453,7 @@ def _add_stored_point(self, pixel_point): ''' # check if point can be added (try adding to external data) point_added = self._call_bind_func('new_point', pixel_point) - + if point_added: self._draw_point(pixel_point, {'outline':'','fill':'blue', 'tags':('stored','point')}) @@ -476,7 +478,7 @@ def _draw_point(self, pixel_point, properties): 'pixel_point' should be (x,y) coordinates of the point 'properties' should be a dictionary of desired properties of the point - self._draw_point(tuple(int,int), dict) -> + self._draw_point(tuple(int,int), dict) -> None ''' ppx, ppy = pixel_point # extract coordinates @@ -493,10 +495,10 @@ def _delete_point(self, pixel_point, max_dist=5): if not self._graph_canvas.find_withtag('stored'): # no stored points defined return - + # remove the point from the external data storage point_removed = self._call_bind_func('del_point', pixel_point, max_dist) - + if point_removed: # if a point was removed, it must satisfy the max_dist condition, # so remove the closest point (to pixel_point) from the view @@ -542,7 +544,7 @@ def _graph_click(self, event): if self._mode == 'add': pixel_point = (event.x, event.y) self._delete_point(pixel_point) - + def _call_bind_func(self, binding, *args, **kwargs): ''' Call a binding function with the given arguments. @@ -562,7 +564,7 @@ def _display_instructions(self): ''' Display usage instructions on startup. self._display_instructions() -> None - + ''' instructions = 'Choose 3 defining points which specify the plane ' +\ 'your graph is on (click to select point location, then specify ' +\ @@ -571,22 +573,28 @@ def _display_instructions(self): 'pressing File/Save Data or File/Save Graph to save.' self._errors.set(instructions) - def add_binding_func(self, binding, callback): - ''' Adds the specified callback to the given binding, if valid. + def add_binding_funcs(self, **bindings_map): + ''' Adds the bindings and callbacks in 'bindings_map'. - A valid binding must be in the tuple returned by View.external_bindings. + If any bindings are invalid (ie they are not found in + View.external_bindings), raises Exception. - Raises Exception on invalid binding. + self.add_binding_funcs(**binding=func) -> None + + ''' + # update but with validity check first in a dictionary comprehension + self._bindings.update({binding: callback if binding in self._bindings \ + else self.invalid_binding(binding) \ + for binding, callback in bindings_map.items()}) - self.add_binding_func(str, func) -> None + @classmethod + def invalid_binding(cls, binding): + ''' Raises Exception specifying that 'binding' is invalid. + + cls.invalid_binding(str) ''' - if binding in View.external_bindings: - self._bindings[binding] = callback - else: - raise Exception('Invalid binding option {!r} -'.format(binding), - 'Valid bindings are found using ', - 'View.external_bindings') + raise Exception(cls.INVALID_BINDING_MSG.format(binding)) @staticmethod def snapshot(widget, filename, scale=None): @@ -605,7 +613,7 @@ def snapshot(widget, filename, scale=None): scale = 2 # computer is a mac else: scale = 1 # assume everything is fine - + # determine minimum bounds of widget x_min = widget.winfo_rootx() * scale y_min = widget.winfo_rooty() * scale @@ -619,7 +627,7 @@ def snapshot(widget, filename, scale=None): save_image.save(filename) except Exception: save_image.save(filename + '.png') - + class DefPointControl(object): ''' A class for a defining point control in a View. ''' @@ -635,11 +643,11 @@ def __init__(self, master, r): # initialise external functinos to do nothing until set externally self._view_submit = lambda: None self._view_select = lambda: None - + # set up some variables to easily extract Entry values later - xr = tk.StringVar(); yr = tk.StringVar() + xr = tk.StringVar(); yr = tk.StringVar() xr.set('x{}'.format(r)); yr.set('y{}'.format(r)) - + # create "Def Pt r (_,_)" self._button = tk.Button(master, text='Def Pt {}'.format(r), width=9, command=lambda: self._view_select(self._id)) @@ -702,7 +710,7 @@ def submit(self): command=lambda: self._view_select(self._id)) for entry in self._entries: entry.config(state='disabled') - + def reset(self): ''' The internal reset function for this defining point.