class Display(object): lifetime = 5 # seconds def __init__(self, palette=None): # A screen is useful to have right away self.screen = Screen() if palette is None: palette = gui.GUI.palette self.screen.register_palette(palette) def setWidget(self, widget, useFiller=False): def possiblyQuit(key): if key in ('q', 'Q'): reactor.stop() # The widget under test is outline, possibly padded with filler outlined = u.LineBox(widget) if useFiller: height = 'flow' if hasattr(widget, 'rows') else 3 w = u.Filler(outlined, valign='top', height=height) else: w = outlined main = u.WidgetWrap(w) # The loops eventLoop = u.TwistedEventLoop(reactor, manage_reactor=False) self.loop = u.MainLoop( main, screen=self.screen, unhandled_input=possiblyQuit, event_loop=eventLoop) self.loop.start() def width(self): return self.screen.get_cols_rows()[0] - 2 def update(self): self.loop.draw_screen() def stop(self): self.screen.unhook_event_loop(self.loop) self.loop.stop()
class GUI(object): """ I am the main curses interface. """ title = "Logalyzer" palette = [ # Name # 'fg color,setting', 'background color', 'mono setting' ('heading', 'dark cyan', 'default', 'default'), ('heading_current', 'light cyan,underline', 'default', 'underline'), ('message', 'white', 'default', 'default'), ('error_label', 'light red,underline', 'default', 'underline'), ('error', 'brown', 'default', 'default'), ('warning_label', 'yellow,underline', 'default', 'underline'), ('warning', 'brown', 'default', 'default'), ] def __init__(self, stopperFunction): """Constructor""" self.running = False self.stopperFunction = stopperFunction self.id_counter = 0 # A screen is useful right away self.screen = Screen() self.screen.register_palette(self.palette) self.screen.set_mouse_tracking(True) def start(self, fileNames): """ Constructs my widgets and starts my event loop and main loop. """ def possiblyQuit(key): if key in ('q', 'Q'): if not hasattr(self, '_stopping'): self._stopping = None self.warning("Shutting down, please wait...") if reactor.running: # I trust the stopper function to call my stop # method at the appropriate time reactor.callFromThread(reactor.stop) # The top-level widgets self.m = Messages() self.f = Files(fileNames, self._dims()[0]) p = u.Pile([u.Divider("=", 1, 1), self.f, u.Divider(" ")]) main = u.WidgetWrap( u.LineBox( u.Padding( u.Frame(self.m, footer=p), left=1, right=1), title=self.title)) eventLoop = u.TwistedEventLoop(reactor, manage_reactor=False) self.formerDims = self._dims() self.loop = u.MainLoop( main, screen=self.screen, unhandled_input=possiblyQuit, event_loop=eventLoop) reactor.addSystemEventTrigger('after', 'shutdown', self.stop) #sys.stdout = StdSubstitute('STDOUT', self) sys.stderr = observer = StdSubstitute('STDERR', self) twisted.python.log.addObserver(observer) self.running = True self.loop.start() def _dims(self): # Deduct 4 from each dimension due to outline and padding return [x-4 for x in self.screen.get_cols_rows()] def update(self): """ Updates my display, possibly with an updated screen width. """ if not self.running: return width, height = self._dims() # Update for new width if width != self.formerDims[0]: self.f.updateWidth(width) self.loop.draw_screen() def stop(self): """ Tears down the GUI display. This will be called by L{main.Recorder.shutdown} after all other shutdown steps are done, as part of the Twisted reactor shutdown. """ if self.running and not hasattr(self, '_shutdownFlag'): self._shutdownFlag = None self.running = False self.screen.unhook_event_loop(self.loop) self.loop.stop() def msgHeading(self, textProto, *args): """ Sends a new heading to my scrolling message window. You can supply a single string, or a string prototype followed by one or more formatting arguments. Returns a unique integer ID for this heading. Use that when supplying lines of message body under this heading. """ self.id_counter += 1 ID = self.id_counter self.m.heading(textProto.format(*args), ID) self.update() return ID def msgBody(self, ID, textProto, *args): """ Adds a new line of message body under heading ID. You can supply a single string after the integer ID, or a string prototype followed by one or more formatting arguments. """ text = textProto.format(*args) self.m.msg(text, ID) self.update() def msgOrphan(self, textProto, *args): """ Adds a new line of message body under a (possibly blank) orphan heading ID. You can supply a single string, or a string prototype followed by one or more formatting arguments. """ text = textProto.format(*args) self.m.msg(text) self.update() def warning(self, textProto, *args): """ Adds a distinctive warning message to the message window. """ self.m.distinctMsg('warning', textProto.format(*args)) self.update() def error(self, textProto, *args): """ Adds a distinctive error message to the message window. """ self.m.distinctMsg('error', textProto.format(*args)) self.update() def msgProgress(self, ID, N): self.m.progress(ID, N) self.update() def fileStatus(self, fileName, *args): """ Updates the status entry for the specified fileName. With no further arguments, the progress indicator for the file is given a spin. With a string or string prototype followed by formatting arguments, the progress indicator is reset and the brief status text following the filename is updated. """ if args: self.f.setStatus(fileName, *args) else: self.f.indicator(fileName) self.update()