diff -r 7eeb29bb12f2 urwid/main_loop.py --- a/urwid/main_loop.py Mon Apr 06 09:42:41 2009 -0400 +++ b/urwid/main_loop.py Sun May 17 17:17:39 2009 -0400 @@ -1,7 +1,8 @@ #!/usr/bin/python # # Urwid main loop code -# Copyright (C) 2004-2008 Ian Ward, Walter Mundt +# Copyright (C) 2004-2009 Ian Ward, Walter Mundt +# Copyright (C) 2009 Andrew Psaltis # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -27,6 +28,35 @@ from util import * from command_map import command_map +# These are overridden by whatever loop class is used. +def _handle_exceptions(exc): + pass + +def _quit_loop(): + pass + +def wrap_exceptions(func): + """ + Decorator function used to ensure that wrapped functions die properly + when an exception is thrown. + You may need to wrap some alarms/timeouts with this function to ensure + that your program doesn't break your terminal upon failure, if you use the + GLib or Twisted MainLoop classes. + func -- function to be wrapped + """ + def wrapper(*args, **kargs): + try: + return func(*args, **kargs) + except ExitMainLoop: + _quit_loop() + except Exception as exc: + _handle_exceptions(exc) + + wrapper.__name__ = func.__name__ + wrapper.__module__ = func.__module__ + wrapper.__dict__ = func.__dict__ + wrapper.__doc__ = func.__doc__ + return wrapper class ExitMainLoop(Exception): pass @@ -60,6 +90,7 @@ self.screen_size = None self._alarms = [] + self._timeouts = [] def set_alarm_in(self, sec, callback, user_data=None): """ @@ -411,3 +442,204 @@ else: return d + +# GLib main loop +try: + import gobject +except: + # Do nothing, the user should have pygobject if they want to use this. + pass +class GLibMainLoop(GenericMainLoop): + def __init__(self, widget, palette=[], screen=None, + handle_mouse=True): + """ + Initialize a screen object and palette to be used for + a generic main loop. + + widget -- topmost widget used for painting the screen, + stored as self.widget, may be modified + palette -- initial palette for screen + screen -- screen object or None to use raw_display.Screen, + stored as self.screen + handle_mouse -- True to process mouse events + """ + global _handle_exceptions,_quit_loop + self.widget = widget + self.handle_mouse = handle_mouse + + if not screen: + import raw_display + screen = raw_display.Screen() + + if palette: + screen.register_palette(palette) + + self.screen = screen + self.screen_size = None + + self.update_tag = None + + self._timeouts = [] + + _handle_exceptions = self.handle_exceptions + _quit_loop = self.quit_loop + + self._loop = gobject.MainLoop() + + def _set_screen_alarm(self,sec): + def ret_false(): + self._run() + return False + return gobject.timeout_add(sec*1000,ret_false) + + def set_alarm_in(self, sec, callback, user_data=None): + """ + Schedule an alarm in sec seconds that will call + callback(main_loop, user_data) as a timeout in the glib mainloop + in sec seconds. + + This returns the file descriptor that glib gives us. + + sec -- floating point seconds until alarm + callback -- callback(main_loop, user_data) callback function + user_data -- object to pass to callback + """ + def ret_false(callback): + callback(self,user_data) + return False + tag = gobject.timeout_add(sec*1000, ret_false) + self._timeouts.append(tag) + return tag + + def set_alarm_at(self, tm, callback, user_data=None): + """ + Schedule an alarm at sec seconds that will call + callback(main_loop, user_data) as a timeout in the glib mainloop. + + This returns the file descriptor that glib gives us. + + tm -- floating point local time of alarm + callback -- callback(main_loop, user_data) callback function + user_data -- object to pass to callback + """ + return self.set_alarm_in(sec-time.time(),callback,user_data) + + def set_timeout(self, tm, callback, user_data=None): + """ + Sechedule a call to callback(main_loop,user_data) to occur in int + seconds, repeating every tm seconds until it returns False. This + returns the file descriptor that glib gives us. + + tm -- floating point seconds until timeout is processed + callback -- callback(main_loop, user_data) callback function + user_data -- object to pass to callback + """ + tag = gobject.timeout_add(sec*1000, callback,user_data) + self._timeouts.append(tag) + return tag + + def remove_timeout(self,tag): + """ + Remove the timeout (or alarm or whatever) represented by the tag value. + + Calls gobject.source_remove(tag) and returns the tag if we know about + the tag in question. Else, return None. + + tag -- tag of the timeout/idle function/io watch to remove + """ + if tag in _timeouts: + return gobject.source_remove(tag) + else: + return None + + @wrap_exceptions + def run(self): + """ + Start a glib main loop handling input events and updating + the screen. The loop will continue until an ExitMainLoop + (or some other) exception is raised. + """ + #This function will call screen.run_wrapper() if screen.start() + #has not already been called. + #""" + if self.handle_mouse: + self.screen.set_mouse_tracking() + #gobject.idle_add(self._run) + + # Set up the UI, this starts setting up other timeouts/alarms for + # the input + try: + if not self.screen.started: + self.screen.run_wrapper(self._prerun) + else: + self._prerun() + except ExitMainLoop: + screen._loop.quit() + pass + + #@wrap_exceptions + def _prerun(self): + fds = self.screen.get_input_descriptors() + for fd in fds: + gobject.io_add_watch(fd, gobject.IO_IN,self._call_run) + + # Set up the cascading effect causing the rest of the screen updating + # events to occur. + self._run() + + self._loop.run() + + def _call_run(self,source,cb_condition): + self._run() + return True + + @wrap_exceptions + def _run(self): + input_data = self.screen.get_input_nonblocking() + max_wait = input_data[0] + keys = input_data[1] + + # Resolve any "alarms" in the waiting + if self.update_tag != None: + self.remove_timeout(self.update_tag) + if max_wait == None: + max_wait = 0.050 + + self.update_tag = self._set_screen_alarm(max_wait) + + if keys: + self.process_input(keys) + + if 'window resize' in keys: + self.screen_size = None + # If the "Quit" key is pressed, burn, baby, burn. + if not self.screen.started: + return False + self.draw_screen() + return True + + def handle_exceptions(self, exc): + """ + Handle exceptions that are caught by the wrap_exceptions decorator. + + This method is called to handle all exceptions except + urwid.ExitMainLoop, which is simply used to trigger an exit. + + Unless overridden, this just kills the loop and then raises + the exception. + + exc -- Exception to be processed. + """ + #if type(exc) == type(ExitMainLoop): + # quit_loop() + self.quit_loop() + raise + def quit_loop(self): + """ + Quit the mainloop. This is called by wrap_exceptions on ExitMainLoop + and is used in the default handle_exceptions to quit the loop before + raising the exception. + """ + self.screen.stop() + self._loop.quit() +