Пример #1
0
 def __init__(self):
     self.conf = PVConf(self)
     self.controller = None
     self.tk_do_q = Queue()  # a queue of things we have to do (function closures)
     self.tk_do_ret = {}     # a dictionary of Event objects to indicate something is done,
                             # accompanied by return values and possible exceptions
     self.alive = Event()     # clear until run() has initialized all widgets and after tk.mainloop() returned
     self.idle = Event()     # set unless somebody calls wait_idle()
     self.idle.set()
Пример #2
0
class PVWindow:
    def __init__(self):
        self.conf = PVConf(self)
        self.controller = None
        self.tk_do_q = Queue()  # a queue of things we have to do (function closures)
        self.tk_do_ret = {}     # a dictionary of Event objects to indicate something is done,
                                # accompanied by return values and possible exceptions
        self.alive = Event()     # clear until run() has initialized all widgets and after tk.mainloop() returned
        self.idle = Event()     # set unless somebody calls wait_idle()
        self.idle.set()

    def main(self):
        # the root widget
        self.tk = Tk()

        self.tk.title(app_name)
        self.tk.minsize(800, 600)
        self.tk.protocol("WM_DELETE_WINDOW", lambda: self.do(PVAction.quit_app))

        # top-level widget
        self.frame0 = Frame(self.tk)

        # menubar
        self.menu_bar = Menu(self.tk)

        ## file menu
        self.file_menu = Menu(self.menu_bar, tearoff=0)
        self.file_menu.add_command(label="Open...",     command=lambda: self.do(PVAction.open_exp))
        self.file_menu.add_command(label="Import...",   command=lambda: self.do(PVAction.import_exp))
        self.file_menu.add_command(label="Quit",        command=lambda: self.do(PVAction.quit_app))

        ## view menu
        self.view_menu = Menu(self.menu_bar, tearoff=0)
        self.view_menu.add_command(label="Reset Scale", command=lambda: self.do(PVAction.reset_scale), state=DISABLED)

        ### this function en- or disables the "Reset Scale" menu entry
        def vm_listener(conf):
            if len(conf.open_experiments) > 0 and conf.viewmode == ViewMode.graph:
                self.view_menu.entryconfig(0, state=NORMAL)
            else:
                self.view_menu.entryconfig(0, state=DISABLED)

        ### register our viewmode_listener
        self.conf.add_ox_listener(self.tk_cb(vm_listener))
        self.conf.add_viewmode_listener(self.tk_cb(vm_listener))

        ## help menu
        self.help_menu = Menu(self.menu_bar, tearoff=0)
        self.help_menu.add_command(label="Contents",    command=lambda: self.do(PVAction.show_help))
        self.help_menu.add_command(label="About",       command=lambda: self.do(PVAction.show_about))
    
        ## add the menues to the menubar
        self.menu_bar.add_cascade(label="File", menu=self.file_menu)
        self.menu_bar.add_cascade(label="View", menu=self.view_menu)
        self.menu_bar.add_cascade(label="Help", menu=self.help_menu)

        # main widget with vertical "splitter" bar
        ## frame0 currently contains only this widget but might hold a tool- and/or menubar as well in the future
        self.main_region = PanedWindow(self.frame0, sashwidth=4)

        ## TAB is on the left
        self.tab_region = TabRegion(self.main_region, self)

        ## DATA is on the right
        self.data_region = DataRegion(self.main_region, self)

        ## 1. pack() then -> 2. add()  -- Reihenfolge beachten!
        self.tab_region.pack(fill=BOTH, expand=1)
        self.data_region.pack(fill=BOTH, expand=1)
        
        ## add()
        self.main_region.add(self.tab_region)
        self.main_region.add(self.data_region)

        # pack() top-level widget
        self.frame0.pack(fill=BOTH, expand=1)

        # pack() main widget
        self.tk.config(menu=self.menu_bar)
        self.main_region.pack(fill=BOTH, expand=1)

        # set the default viewmode to make the window look more interesting
        #  TODO: keep an eye on this - it used not to work after changing tk_thread to be the main thread and
        #  PVWindow still extending the Thread class, .... i think :-) 
        self.conf.set_viewmode(self.conf.viewmode)

        # bind out virtual private event to the thread context switch helper event handler
        self.tk.bind("<<PVEvent>>", self.tk_do_handler)

        # register the fact that everything is set up now
        self.alive.set()

        # call Tk.mainloop()
        self.tk.mainloop()

        self.alive.clear()

    def stop(self):
        if self.alive.is_set():             # FIXME: queue the request if self exists but is not yet alive
            self.tk_do(self.tk.quit)        # do a context switch if necessary

    def wait_idle(self):
        "wait until the Tk.mainloop() thread is idle i.e. no more events are pending"
        self.alive.wait()                 # possibly wait for run() to instantiate Tk and call its mainloop() first
        self.after_idle(self.idle.set)   # arrange for the "idle" Event to be set once tk.mainloop() is idle
        self.idle.wait()                 # wait for it
        self.idle.clear()                # the ui is usually considered busy

    def after_idle(self, action):
        "schedule a function to be called by the tk.mainloop() thread as soon as it is idle - the calling thread returns immediately"
        self.tk_do(self.tk.after_idle, action)              # do a context switch if necessary

    def tk_do(self, task, *args):
        "call task(*args) from the PVWindow/Tk.mainloop() thread"
        if current_thread() == tk_thread:                   # already in the right thread
            return task(*args)                              # just call the function - otherwise we have to switch
        method = partial(task, *args)                       # create a closure to call the function with the specified arguments
        self.tk_do_q.put(method)                            # and queue that to be called in the <<PVEvent>> handler, tk_do_handler()
        self.tk_do_ret[method] = Event()                    # plus, use the reference as a key to the tk_do_ret dictionary and insert an Event()
        self.tk.event_generate("<<PVEvent>>", when='tail')  # queue a virtual Event for the Tk.mainloop()
        self.tk_do_ret[method].wait()                       # now wait for the handler to be called and event to be set
        e = self.tk_do_ret[method].exception                # safe the exceptions produced by the closure (if any)
        ret = self.tk_do_ret[method].value                  # and the return value
        del self.tk_do_ret[method]                          # and delete the event
        if e: raise type(e), e, e.tb                        # if an exception occured, raise here it the calling thread - otherwise go ahead
        return ret                                          # return the return value

    def tk_do_handler(self, event):
        "call the current self.tk_task"
        method = self.tk_do_q.get_nowait()                  # remove one task from the queue
        self.tk_do_ret[method].value = None
        self.tk_do_ret[method].exception = None
        try:
            self.tk_do_ret[method].value = method()         # execute the method and safe the return value
        except Exception, e:
            import sys
            e.tb = sys.exc_info()[2]
            self.tk_do_ret[method].exception = e
        finally: