Esempio n. 1
0
  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
Esempio n. 2
0
    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')