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 Client(Component): channel = "client" def init(self, host, port=6667, opts=None): self.host = host self.port = port self.opts = opts self.hostname = gethostname() self.nick = opts.nick self.ircchannel = opts.channel # Add TCPClient and IRC to the system. TCPClient(channel=self.channel).register(self) IRC(channel=self.channel).register(self) self.create_interface() def create_interface(self): self.screen = Screen() self.screen.start() self.screen.register_palette([ ("title", "white", "dark blue", "standout"), ("line", "light gray", "black"), ("help", "white", "dark blue")] ) self.body = ListBox(SimpleListWalker([])) self.lines = self.body.body self.title = Text(MAIN_TITLE) self.header = AttrWrap(self.title, "title") self.help = AttrWrap( Text(HELP_STRINGS["main"]), "help" ) self.input = Edit(caption="%s> " % self.ircchannel) self.footer = Pile([self.help, self.input]) self.top = Frame(self.body, self.header, self.footer) def ready(self, component): """Ready Event This event is triggered by the underlying ``TCPClient`` Component when it is ready to start making a new connection. """ self.fire(connect(self.host, self.port)) def connected(self, host, port): """connected Event This event is triggered by the underlying ``TCPClient`` Component when a successfully connection has been made. """ nick = self.nick hostname = self.hostname name = "%s on %s using circuits/%s" % (nick, hostname, systemVersion) self.fire(NICK(nick)) self.fire(USER(nick, hostname, host, name)) def numeric(self, source, numeric, *args): """Numeric Event This event is triggered by the ``IRC`` Protocol Component when we have received an IRC Numberic Event from server we are connected to. """ if numeric == ERR_NICKNAMEINUSE: self.fire(NICK("{0:s}_".format(args[0]))) elif numeric in (RPL_ENDOFMOTD, ERR_NOMOTD): self.fire(JOIN(self.ircchannel)) @handler("stopped", channel="*") def _on_stopped(self, component): self.screen.stop() @handler("generate_events") def _on_generate_events(self, event): event.reduce_time_left(0) size = self.screen.get_cols_rows() if not select( self.screen.get_input_descriptors(), [], [], 0.1)[0] == []: timeout, keys, raw = self.screen.get_input_nonblocking() for k in keys: if k == "window resize": size = self.screen.get_cols_rows() continue elif k == "enter": self.processCommand(self.input.get_edit_text()) self.input.set_edit_text("") continue self.top.keypress(size, k) self.input.set_edit_text(self.input.get_edit_text() + k) self.update_screen(size) def unknownCommand(self, command): self.lines.append(Text("Unknown command: %s" % command)) def syntaxError(self, command, args, expected): self.lines.append( Text("Syntax error ({0:s}): {1:s} Expected: {2:s}".format( command, args, expected) ) ) def processCommand(self, s): # noqa match = CMD_REGEX.match(s) if match is not None: command = match.groupdict()["command"] if not match.groupdict()["args"] == "": tokens = match.groupdict()["args"].split(" ") else: tokens = [] fn = "cmd" + command.upper() if hasattr(self, fn): f = getattr(self, fn) if callable(f): args, vargs, kwargs, default = getargspec(f) args.remove("self") if len(args) == len(tokens): if len(args) == 0: f() else: f(*tokens) else: if len(tokens) > len(args): if vargs is None: if len(args) > 0: factor = len(tokens) - len(args) + 1 f(*back_merge(tokens, factor)) else: self.syntaxError( command, " ".join(tokens), " ".join( x for x in args + [vargs] if x is not None ) ) else: f(*tokens) elif default is not None and \ len(args) == ( len(tokens) + len(default)): f(*(tokens + list(default))) else: self.syntaxError( command, " ".join(tokens), " ".join( x for x in args + [vargs] if x is not None ) ) else: if self.ircchannel is not None: self.lines.append(Text("<%s> %s" % (self.nick, s))) self.fire(PRIVMSG(self.ircchannel, s)) else: self.lines.append(Text( "No channel joined. Try /join #<channel>")) def cmdEXIT(self, message=""): self.fire(QUIT(message)) raise SystemExit(0) def cmdSERVER(self, host, port=6667): self.fire(connect(host, port)) def cmdSSLSERVER(self, host, port=6697): self.fire(connect(host, port, secure=True)) def cmdJOIN(self, channel): if self.ircchannel is not None: self.cmdPART(self.ircchannel, "Joining %s" % channel) self.fire(JOIN(channel)) self.ircchannel = channel def cmdPART(self, channel=None, message="Leaving"): if channel is None: channel = self.ircchannel if channel is not None: self.fire(PART(channel, message)) self.ircchannel = None def cmdQUOTE(self, message): self.fire(request(Message(message))) def cmdQUIT(self, message="Bye"): self.fire(QUIT(message)) def update_screen(self, size): canvas = self.top.render(size, focus=True) self.screen.draw_screen(size, canvas) @handler("notice", "privmsg") def _on_notice_or_privmsg(self, event, source, target, message): nick, ident, host = source if event.name == "notice": self.lines.append(Text("-%s- %s" % (nick, message))) else: self.lines.append(Text("<%s> %s" % (nick, message)))
class GUI(object): def __init__(self): self.screen = Screen() self.screen.set_input_timeouts(max_wait=0) self.steps = GridFlow([], 20, 2, 1, 'left') self.progress = SimpleFocusListWalker([]) self.log = SimpleFocusListWalker([]) self.widget = AttrMap( LineBox(Pile([ ('fixed', 6, AttrMap(Filler(self.steps), 'default')), ('fixed', 1, Filler(Divider('\u2500'))), ('fixed', 3, ListBox(self.progress)), AttrMap(LineBox(ListBox(self.log), title='Message log'), 'default') ]), title='Indico 1.2 -> 2.0 migration'), 'global_frame') self.screen.register_palette( [('green', 'light green', ''), ('white', 'white', ''), ('red', 'dark red', ''), ('yellow', 'yellow', ''), ('progress_empty', 'black', 'light gray'), ('progress_progress', 'light cyan', 'light gray'), ('progress_done', 'black', 'light cyan'), ('box', 'white', 'dark gray'), ('step_done', 'light green', ''), ('step_working', 'dark gray', ''), ('global_frame', 'light cyan', ''), ('fill', 'light cyan', 'dark cyan'), ('done', 'white', 'dark green'), ('eta', 'yellow', 'dark gray')] + generate_urwid_palette(PALETTE)) def print_log(self, icon, message, prefix='', event_id=''): self.log.append( Text([ color_segments(icon), ' ', color_segments(prefix), ' ' if prefix else '', color_segments( '%[cyan][%[cyan!]{}%[cyan]]%[reset]'.format(event_id)) if event_id else '', ' ' if event_id else '', color_segments(message) ])) self.log.set_focus(len(self.log) - 1) self.redraw() def start(self): # don't let Python warnings ruin the GUI warnings.filterwarnings('ignore') self.screen.start() self.redraw() def stop(self): self.screen.stop() warnings.filterwarnings('default') def create_progress_bar(self, description): if self.progress: del self.progress[:] return StepProgressBar(self, description) def set_success(self): if self.progress: del self.progress[:] self.progress.append( AttrMap(Text('Migration finished!', align='center'), 'done')) self.progress.append( AttrMap(Text('Please press any key...', align='center'), 'done')) self.redraw() self.wait_for_input() def wait_for_input(self): self.screen._getch(None) def set_step_banner(self, msg): if self.progress: del self.progress[:] self.progress.append(BoxAdapter(AttrMap(SolidFill('#'), 'fill'), 3)) def redraw(self): screen_size = self.screen.get_cols_rows() canvas = self.widget.render(screen_size, focus=True) self.screen.get_input() self.screen.draw_screen(screen_size, canvas)
class MultiGraphDisplay: def __init__(self, cols, urwid_ui): if urwid_ui == "smoothed": smoothed = True self.palette = self.smoothed_palette else: smoothed = False self.palette = self.blocky_palette self.displays = [] l = [] for c in cols: a = [] for tap in c: if tap.ftype == 'file_exp': d = GraphDisplayProgress(tap, smoothed) else: d = GraphDisplay(tap, smoothed) a.append(d) self.displays.append(d) l.append(a) graphs = urwid.Columns( [BoxPile(a) for a in l], 1 ) graphs = urwid.AttrWrap( graphs, 'background' ) title = urwid.Text("Speedometer "+__version__) title = urwid.AttrWrap( urwid.Filler( title ), 'title' ) self.top = urwid.Overlay( title, graphs, ('fixed left', 0), 16, ('fixed top', 0), 1 ) self.urwid_ui = urwid_ui blocky_palette = [ ('background', 'dark gray', 'black'), ('reading', 'light gray', 'black'), ('1MB/s', 'dark cyan','light gray','standout'), ('32KB/s', 'light gray', 'dark cyan','standout'), ('1KB/s', 'dark blue','dark cyan','standout'), ('32B/s', 'light cyan','dark blue','standout'), ('bar:num', 'light gray', 'black' ), ('ca:background', 'light gray','black'), ('ca:c', 'yellow','black','standout'), ('ca:a', 'dark gray','black','standout'), ('ca:c:num','yellow','black','standout'), ('ca:a:num','dark gray','black','standout'), ('title', 'white', 'black','underline'), ('pr:n', 'white', 'dark blue'), ('pr:c', 'white', 'dark green','standout'),] smoothed_palette = [ ('background', 'dark gray', 'black'), ('reading', 'light gray', 'black'), ('bar:top', 'dark cyan', 'black' ), ('bar', 'black', 'dark cyan','standout'), ('bar:num', 'dark cyan', 'black' ), ('ca:background', 'light gray','black'), ('ca:c:top','dark blue','black'), ('ca:c', 'black','dark blue','standout'), ('ca:c:num','light blue','black'), ('ca:a:top','light gray','black'), ('ca:a', 'black','light gray','standout'), ('ca:a:num','light gray', 'black'), ('title', 'white', 'black','underline'), ('pr:n', 'white', 'dark blue'), ('pr:c', 'white', 'dark green','standout'), ('pr:cn', 'dark green', 'dark blue'), ] def main(self): self.ui = Screen() self.ui.set_input_timeouts( max_wait=INTERVAL_DELAY ) self.ui.register_palette(self.palette) self.ui.run_wrapper( self.run ) def run(self): try: self.update_readings() except EndOfData: return time.sleep(INITIAL_DELAY) resizing = False size = self.ui.get_cols_rows() while True: if not resizing: try: self.update_readings() except EndOfData: self.end_of_data() return resizing = False self.draw_screen(size) if isinstance(time,SimulatedTime): time.sleep( INTERVAL_DELAY ) continue keys = self.ui.get_input() for k in keys: if k == "window resize": size = self.ui.get_cols_rows() resizing = True else: return def update_readings(self): for d in self.displays: d.update_readings() def end_of_data(self): # pause for taking screenshot of simulated data if isinstance(time, SimulatedTime): while not self.ui.get_input(): pass def draw_screen(self, size): canvas = self.top.render( size, focus=True ) self.ui.draw_screen( size, canvas )
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()