def __call__(self, job, raw=False): rex = self._rex expected = Expected() if not raw else Expected([('[Pp]assword:?', 'password'), ('(?P<line>.*?)\r\n', 'line'), ('(?P<reline>.*?)\r', 'reline'),]) try: job.render except AttributeError: job = Job(job) r = spawn(rex + ' ' + job.render(raw=raw)) try: for ev in expected(r): ev['job'] = job if job._close: return r.close(True) elif ev['name'] == 'password': count = -1 while count < 5 and not job._close: count += 1 password = job._password if password: break yield ev else: return r.close(True) r.sendline(password) else: yield ev except pexpect.EOF: pass
def __call__(self, job, raw=False): rex = self._rex expected = Expected() if not raw else Expected([ ('[Pp]assword:?', 'password'), ('(?P<line>.*?)\r\n', 'line'), ('(?P<reline>.*?)\r', 'reline'), ]) try: job.render except AttributeError: job = Job(job) r = spawn(rex + ' ' + job.render(raw=raw)) try: for ev in expected(r): ev['job'] = job if job._close: return r.close(True) elif ev['name'] == 'password': count = -1 while count < 5 and not job._close: count += 1 password = job._password if password: break yield ev else: return r.close(True) r.sendline(password) else: yield ev except pexpect.EOF: pass
def render_wrapper(button, func, *args): """ Helper render wrapper """ if not self.sane_values(): # Something is wrong! return # Disable the button in question. button.config(state = "disabled") # Start the progress bar. # TODO: Figure out how to use a 'determinate' mode for the bar. self.bar_start() # Create a lock. lock = Lock() # Create a cleanup helper function that waits for the job to finish # and then cleans up. def check_ended(): if lock.acquire(False): button.config(state = "normal") self.bar_stop() if self.render_job != None: self.render_job.join() else: self.after(100, check_ended) # This is wrapped to ensure that the cleanup function is always # run. try: # Generate self's panels. panels = self.create_panels() # Generate the render_frame function and frame count. render_frame, frames = gen_render_frame(panels, \ (None, self.options.get('Text size')), \ self.options.get('Title'), self.options.get('Timewarp'), \ self.options.get('Edge render') == "True", \ self.options.get('Significant figures')) # Create a job wrapper to hold the lock. def wrap_render(*args, **kargs): with lock: func(*args, **kargs) # Create and start the job. self.render_job = Job(wrap_render, render_frame, frames, \ *[self.options.get(arg) for arg in args]) self.render_job.start() finally: # Call the cleanup function, which will reschedule itself as # required. check_ended()
class Main(ttk.Frame): """ The main window """ def __init__(self, master, *args, **kargs): """ Init self """ # Initialise self. ttk.Frame.__init__(self, master, *args, **kargs) self.pack(expand = True, fill = 'both') # Add the general exception handler. master.report_callback_exception = lambda *args: \ self.pretty_error("\n".join(traceback.format_exception(*args))) # Models. self.models = ThreadedDict(lambda name: Model(*name)) # Values. # Don't bother with early caching for this; rendering takes quite a bit # longer anyway... self.values = ThreadedDict(lambda name: Values(self.models[name[0]], \ name[1], transforms = name[2])) # Create the widgets... self.create_buttons() # Create the options... self.create_options() # Create the lists... self.create_lists() # Add the render job variable. self.render_job = None def pretty_error(self, message): """ Show a pretty error message """ print("ERROR: {}".format(message)) tkMessageBox.showerror('Error', message) def create_buttons(self): """ Create the button widgets """ # Create the holder frame. lower = ttk.Frame(self) lower.pack(side='bottom', fill='x') # Create the helper function... def render_wrapper(button, func, *args): """ Helper render wrapper """ if not self.sane_values(): # Something is wrong! return # Disable the button in question. button.config(state = "disabled") # Start the progress bar. # TODO: Figure out how to use a 'determinate' mode for the bar. self.bar_start() # Create a lock. lock = Lock() # Create a cleanup helper function that waits for the job to finish # and then cleans up. def check_ended(): if lock.acquire(False): button.config(state = "normal") self.bar_stop() if self.render_job != None: self.render_job.join() else: self.after(100, check_ended) # This is wrapped to ensure that the cleanup function is always # run. try: # Generate self's panels. panels = self.create_panels() # Generate the render_frame function and frame count. render_frame, frames = gen_render_frame(panels, \ (None, self.options.get('Text size')), \ self.options.get('Title'), self.options.get('Timewarp'), \ self.options.get('Edge render') == "True", \ self.options.get('Significant figures')) # Create a job wrapper to hold the lock. def wrap_render(*args, **kargs): with lock: func(*args, **kargs) # Create and start the job. self.render_job = Job(wrap_render, render_frame, frames, \ *[self.options.get(arg) for arg in args]) self.render_job.start() finally: # Call the cleanup function, which will reschedule itself as # required. check_ended() # Create the buttons. # Preview button. # TODO: Currently, this hangs the UI (something to do with the # interaction between pygame and tkinter?), so we set it to # disabled by default. preview_button = ttk.Button(lower, text = 'Preview', \ state = 'disabled', command = lambda: \ render_wrapper(preview_button, preview, 'FPS', 'Dimensions', \ 'Title')) preview_button.pack(side = 'left') # Render button. render_button = ttk.Button(lower, text = 'Render', \ command = lambda: render_wrapper(render_button, render, 'FPS', \ 'Dimensions', 'Movie filename')) render_button.pack(side = 'right') # Create the progress bar (shares the same frame). self.create_progressbar(lower) def sane_values(self): """ Sanitize the current values. Prints an error and returns false if the values are not sane. """ def wrap_get(name, message = "{name} is invalid!"): """ Get the value, and if an error occured, print the message """ try: self.options.get(name) except Exception as e: self.pretty_error(message.format(name = name)) raise ValueError("Value {} is invalid!".format(name)) try: wrap_get('Dimensions') wrap_get('Text size') wrap_get('FPS') wrap_get('Significant figures') except ValueError: return False if len(self.panel_list.items) == 0: self.pretty_error("No panels defined!") raise ValueError("There must be something to render!") for i, item in enumerate(self.panel_list.items): if item['Field'].get() == "": self.pretty_error("Panel {} has no field set!".format(i + 1)) return False try: self.models[(item['GIS files'].get(), \ item['CSV directory'].get())] except ValueError as e: self.pretty_error(e) return False format_strings = ['name', 'field', 'csv', 'gis', 'transform' ] try: item["Description string"].get().format( \ **{format_string: '' for format_string in format_strings}) except KeyError as e: raise e args = ["{" + arg + "}" for arg in format_strings] self.pretty_error("""Invalid format string: {} Valid fields are {}.""".format(e, ", ".join(args[:-1]) + ", and " + args[-1])) return False # Check that the user *really* wants to overwrite the existing movie. movie = self.options.get('Movie filename') if os.path.exists(movie): return tkMessageBox.askokcancel("Confirm movie filename", \ "Are you sure that you want to overwrite {}?".format(movie)) return True def create_panels(self): """ Generate the panels from self's current config. This is called from within the render_wrapper function. """ # Create and save the panels. panels = [] domains = {} # id: ([items], colour) for index, config in enumerate(self.panel_list): gis = config['GIS files'].get() csv = config['CSV directory'].get() field = config['Field'].get() graph = config["Graph statistics"].get() per_field = config["Per-field"].get() map_domain_id = config["Same scales (map)"].get() if map_domain_id == "": map_domain_id = len(domains) graph_domain_id = config["Same scales (graph)"].get() if graph_domain_id == "": graph_domain_id = len(domains) + 1 name = config["Name"].get() # Find the value transformations and names. value_trans, value_tran_names = \ get_transform_tuple(config['Transforms'].get(), \ self.models[(gis, csv)]) value = self.values[((gis, csv), field, value_trans)] graph_trans, graph_tran_names = \ get_transform_tuple(config['Graph transforms'].get(), \ self.models[(gis, csv)]) panel = {'values': value} if graph != 'None': graphs = [] # Generate a list of statistics. stats = [] for stat in graph.split("+"): stats.append(stat.strip().lower()) stat_name = " (" + ", ".join(stats) + ") (" + \ " + ".join(graph_tran_names) + ")" if per_field == 'False': # Just one graph. graph_value = self.values[((gis, csv), field, \ graph_trans)] graphs.append(Graphable(graph_value, field + stat_name, \ statistics = stats)) graph_label = 'Key' else: # Multiple, per-field graphs. # Figure out the available fields. fields = value.model.get_patch_fields().items() # Generate a graph for each field. for field_no, patch_set in fields: graph_value = self.values[((gis, csv), field, \ tuple(list(graph_trans) + \ [lambda v: patch_filter(v, patch_set)]))] graphs.append(Graphable(graph_value, str(field_no), \ statistics = stats)) # Set the graph label. graph_label = "Fields" + stat_name # Add the graph to the panel. graph = Graph(graphs, label = graph_label) panel['graphs'] = graph # Add the graph to the domain list. if graph_domain_id in domains: domains[graph_domain_id][0].append(graph) else: domains[graph_domain_id] = ([graph], False) # Add the description. panel['desc'] = config["Description string"].get().format(\ name = name, field = field, csv = csv, gis = gis, \ transform = " + ".join(value_tran_names)) # Add the map to the domains. domains[map_domain_id] = (domains.get(map_domain_id, \ ([], True))[0] + [value], True) panels.append(panel) # Initialise the domains. i = 0 for items, coloured in domains.values(): if coloured: Domain(items, MAP_COLOUR_LIST[i]) i += 1 else: Domain(items) return panels def create_progressbar(self, frame): """ Create self's progress bar """ # Do not pack; that will happen as required. bar = ttk.Progressbar(frame, mode = 'indeterminate') # Create the control variable. self.bar_running = 0 # Create the two helper functions. def bar_start(): """ Start the progress bar """ if self.bar_running == 0: bar.start() bar.pack() self.bar_running += 1 def bar_stop(): """ Stop the progress bar """ self.bar_running -= 1 if self.bar_running <= 0: bar.stop() bar.pack_forget() # Add the functions to self. self.bar_start = bar_start self.bar_stop = bar_stop def create_options(self): """ Create self's options """ self.options = Options(self) self.options.pack(expand = True, fill = 'both') # Add the 'raw' (string) options. self.options.add_entry("Title", tk.StringVar()) def check_int(i, min, max): if min <= i <= max: return i else: raise ValueError("{} not within [{}, {}]!".format(i, min, max)) self.options.add_entry("FPS", tk.IntVar(value = 4), \ lambda x: check_int(x, MIN_FPS, MAX_FPS)) def check_size(size): x, y = size.split('x') return int(x), int(y) self.options.add_entry("Dimensions", \ tk.StringVar(value = "1280x1024"), check_size) self.options.add_entry("Text size", tk.IntVar(value = 25), \ lambda x: check_int(x, MIN_TEXT_HEIGHT, MAX_TEXT_HEIGHT)) self.options.add_entry("Significant figures", tk.IntVar(value = 2), \ lambda x: check_int(x, 1, 8)) # Add the listbox options. self.options.add_combobox("Timewarp", \ tk.StringVar(value = 'basic'), times.keys()) self.options.add_combobox("Edge render", \ tk.StringVar(value = "True"), ["True", "False"]) # Add the file option. movie_filename = tk.StringVar(value = "movies/movie.mp4") self.options.add_file("Movie filename", movie_filename, \ lambda: tkFileDialog.asksaveasfilename( \ title = 'Choose the movie filename', \ filetypes = [('MP4', '.mp4')], defaultextension = '.mp4', \ initialfile = 'movie')) def transform_options(self, master, values): """ Helper for panel_options that creates the options for some transformations. """ master.add_combobox('Name', values['Name'], transformations.keys()) def panel_options(self, master, values): """ Helper for create_list that creates the options for a specific item in the list. """ def cache_model(): """ Cache the given model (async) """ try: self.models.cache((values['GIS files'].get(), \ values['CSV directory'].get())) except KeyError: pass def add_file(name, default, *args): values[name] = tk.StringVar(value = default) values[name].trace("w", lambda *args: cache_model()) master.add_file(name, values[name], *args) def add_entry(name, default, **kargs): values[name] = tk.StringVar(value = default) master.add_entry(name, values[name], **kargs) def add_combo(name, options, default, **kargs): values[name] = tk.StringVar(value = default) master.add_combobox(name, values[name], options, **kargs) def add_text(name, default): values[name] = FuncVar(value = default) master.add_text(name, values[name]) def add_itemlist(name, func, default): values[name] = ListVar() master.add_itemlist(name, values[name], func, default, height = 5) def post_field(box): """ Callback function for updating the list of fields """ try: fields = self.models[(values['GIS files'].get(), \ values['CSV directory'].get())].fields() except ValueError as e: self.pretty_error(e) fields = [] box['values'] = sorted(list(fields)) # Add the rename option. master.add_entry("Name", values["Name"]) # Add a description string option. add_text("Description string", """{name}: Field of interest: {field} CSV: {csv} GIS: {gis} Transform: {transform}""") # Add the Value options. add_file("GIS files", "gis/SmallPatches.shp", \ lambda: tkFileDialog.askopenfilename( \ filetypes = [('ESRI shapefile', '.shp')], \ title = 'Choose a GIS file')) add_file("CSV directory", "csv/small", \ lambda: tkFileDialog.askdirectory( \ title = 'Choose the CSV directory')) cache_model() add_combo("Field", [], "", postcommand = post_field) add_entry("Same scales (map)", "") add_itemlist("Transforms", self.transform_options, \ list(transformations.keys())[0]) # Add the graph options. add_combo("Graph statistics", ["Mean", "Min", "Max", "Min + Max", \ "Min + Mean + Max", "Sum", "None"], "None") add_combo("Per-field", ['True', 'False'], 'False') add_entry("Same scales (graph)", "") add_itemlist("Graph transforms", self.transform_options, \ list(transformations.keys())[0]) def create_lists(self): """ Create the lists """ self.panel_list = ItemList(self, "Panels", self.panel_options, [], \ 'new') self.panel_list.pack(expand = True, fill = 'both')