Created
December 26, 2011 13:30
-
-
Save ksamuel/1521153 to your computer and use it in GitHub Desktop.
Urwid chat UI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# coding: UTF-8 | |
# code extracted from nigiri | |
import os | |
import datetime | |
import sys | |
import traceback | |
import re | |
import logging | |
import locale | |
import commands | |
import urwid | |
from urwid import MetaSignals | |
class ExtendedListBox(urwid.ListBox): | |
""" | |
Listbow widget with embeded autoscroll | |
""" | |
__metaclass__ = urwid.MetaSignals | |
signals = ["set_auto_scroll"] | |
def set_auto_scroll(self, switch): | |
if type(switch) != bool: | |
return | |
self._auto_scroll = switch | |
urwid.emit_signal(self, "set_auto_scroll", switch) | |
auto_scroll = property(lambda s: s._auto_scroll, set_auto_scroll) | |
def __init__(self, body): | |
urwid.ListBox.__init__(self, body) | |
self.auto_scroll = True | |
def switch_body(self, body): | |
if self.body: | |
urwid.disconnect_signal(body, "modified", self._invalidate) | |
self.body = body | |
self._invalidate() | |
urwid.connect_signal(body, "modified", self._invalidate) | |
def keypress(self, size, key): | |
urwid.ListBox.keypress(self, size, key) | |
if key in ("page up", "page down"): | |
logging.debug("focus = %d, len = %d" % (self.get_focus()[1], len(self.body))) | |
if self.get_focus()[1] == len(self.body)-1: | |
self.auto_scroll = True | |
else: | |
self.auto_scroll = False | |
logging.debug("auto_scroll = %s" % (self.auto_scroll)) | |
def scroll_to_bottom(self): | |
logging.debug("current_focus = %s, len(self.body) = %d" % (self.get_focus()[1], len(self.body))) | |
if self.auto_scroll: | |
# at bottom -> scroll down | |
self.set_focus(len(self.body)) | |
""" | |
-------context------- | |
| --inner context---- | | |
|| HEADER || | |
|| || | |
|| BODY || | |
|| || | |
|| DIVIDER || | |
| ------------------- | | |
| FOOTER | | |
--------------------- | |
inner context = context.body | |
context.body.body = BODY | |
context.body.header = HEADER | |
context.body.footer = DIVIDER | |
context.footer = FOOTER | |
HEADER = Notice line (urwid.Text) | |
BODY = Extended ListBox | |
DIVIDER = Divider with information (urwid.Text) | |
FOOTER = Input line (Ext. Edit) | |
""" | |
class MainWindow(object): | |
__metaclass__ = MetaSignals | |
signals = ["quit","keypress"] | |
_palette = [ | |
('divider','black','dark cyan', 'standout'), | |
('text','light gray', 'default'), | |
('bold_text', 'light gray', 'default', 'bold'), | |
("body", "text"), | |
("footer", "text"), | |
("header", "text"), | |
] | |
for type, bg in ( | |
("div_fg_", "dark cyan"), | |
("", "default")): | |
for name, color in ( | |
("red","dark red"), | |
("blue", "dark blue"), | |
("green", "dark green"), | |
("yellow", "yellow"), | |
("magenta", "dark magenta"), | |
("gray", "light gray"), | |
("white", "white"), | |
("black", "black")): | |
_palette.append( (type + name, color, bg) ) | |
def __init__(self, sender="1234567890"): | |
self.shall_quit = False | |
self.sender = sender | |
def main(self): | |
""" | |
Entry point to start UI | |
""" | |
self.ui = urwid.raw_display.Screen() | |
self.ui.register_palette(self._palette) | |
self.build_interface() | |
self.ui.run_wrapper(self.run) | |
def run(self): | |
""" | |
Setup input handler, invalidate handler to | |
automatically redraw the interface if needed. | |
Start mainloop. | |
""" | |
# I don't know what the callbacks are for yet, | |
# it's a code taken from the nigiri project | |
def input_cb(key): | |
if self.shall_quit: | |
raise urwid.ExitMainLoop | |
self.keypress(self.size, key) | |
self.size = self.ui.get_cols_rows() | |
self.main_loop = urwid.MainLoop( | |
self.context, | |
screen=self.ui, | |
handle_mouse=False, | |
unhandled_input=input_cb, | |
) | |
def call_redraw(*x): | |
self.draw_interface() | |
invalidate.locked = False | |
return True | |
inv = urwid.canvas.CanvasCache.invalidate | |
def invalidate (cls, *a, **k): | |
inv(*a, **k) | |
if not invalidate.locked: | |
invalidate.locked = True | |
self.main_loop.set_alarm_in(0, call_redraw) | |
invalidate.locked = False | |
urwid.canvas.CanvasCache.invalidate = classmethod(invalidate) | |
try: | |
self.main_loop.run() | |
except KeyboardInterrupt: | |
self.quit() | |
def quit(self, exit=True): | |
""" | |
Stops the ui, exits the application (if exit=True) | |
""" | |
urwid.emit_signal(self, "quit") | |
self.shall_quit = True | |
if exit: | |
sys.exit(0) | |
def build_interface(self): | |
""" | |
Call the widget methods to build the UI | |
""" | |
self.header = urwid.Text("Chat") | |
self.footer = urwid.Edit("> ") | |
self.divider = urwid.Text("Initializing.") | |
self.generic_output_walker = urwid.SimpleListWalker([]) | |
self.body = ExtendedListBox( | |
self.generic_output_walker) | |
self.header = urwid.AttrWrap(self.header, "divider") | |
self.footer = urwid.AttrWrap(self.footer, "footer") | |
self.divider = urwid.AttrWrap(self.divider, "divider") | |
self.body = urwid.AttrWrap(self.body, "body") | |
self.footer.set_wrap_mode("space") | |
main_frame = urwid.Frame(self.body, | |
header=self.header, | |
footer=self.divider) | |
self.context = urwid.Frame(main_frame, footer=self.footer) | |
self.divider.set_text(("divider", | |
("Send message:"))) | |
self.context.set_focus("footer") | |
def draw_interface(self): | |
self.main_loop.draw_screen() | |
def keypress(self, size, key): | |
""" | |
Handle user inputs | |
""" | |
urwid.emit_signal(self, "keypress", size, key) | |
# scroll the top panel | |
if key in ("page up","page down"): | |
self.body.keypress (size, key) | |
# resize the main windows | |
elif key == "window resize": | |
self.size = self.ui.get_cols_rows() | |
elif key in ("ctrl d", 'ctrl c'): | |
self.quit() | |
elif key == "enter": | |
# Parse data or (if parse failed) | |
# send it to the current world | |
text = self.footer.get_edit_text() | |
self.footer.set_edit_text(" "*len(text)) | |
self.footer.set_edit_text("") | |
if text in ('quit', 'q'): | |
self.quit() | |
if text.strip(): | |
self.print_sent_message(text) | |
self.print_received_message('Answer') | |
else: | |
self.context.keypress (size, key) | |
def print_sent_message(self, text): | |
""" | |
Print a received message | |
""" | |
self.print_text('[%s] You:' % self.get_time()) | |
self.print_text(text) | |
def print_received_message(self, text): | |
""" | |
Print a sent message | |
""" | |
header = urwid.Text('[%s] System:' % self.get_time()) | |
header.set_align_mode('right') | |
self.print_text(header) | |
text = urwid.Text(text) | |
text.set_align_mode('right') | |
self.print_text(text) | |
def print_text(self, text): | |
""" | |
Print the given text in the _current_ window | |
and scroll to the bottom. | |
You can pass a Text object or a string | |
""" | |
walker = self.generic_output_walker | |
if not isinstance(text, urwid.Text): | |
text = urwid.Text(text) | |
walker.append(text) | |
self.body.scroll_to_bottom() | |
def get_time(self): | |
""" | |
Return formated current datetime | |
""" | |
return datetime.datetime.now().strftime('%H:%M:%S') | |
def except_hook(extype, exobj, extb, manual=False): | |
if not manual: | |
try: | |
main_window.quit(exit=False) | |
except NameError: | |
pass | |
message = _("An error occured:\n%(divider)s\n%(traceback)s\n"\ | |
"%(exception)s\n%(divider)s" % { | |
"divider": 20*"-", | |
"traceback": "".join(traceback.format_tb(extb)), | |
"exception": extype.__name__+": "+str(exobj) | |
}) | |
logging.error(message) | |
print >> sys.stderr, message | |
def setup_logging(): | |
""" set the path of the logfile to tekka.logfile config | |
value and create it (including path) if needed. | |
After that, add a logging handler for exceptions | |
which reports exceptions catched by the logger | |
to the tekka_excepthook. (DBus uses this) | |
""" | |
try: | |
class ExceptionHandler(logging.Handler): | |
""" handler for exceptions caught with logging.error. | |
dump those exceptions to the exception handler. | |
""" | |
def emit(self, record): | |
if record.exc_info: | |
except_hook(*record.exc_info) | |
logfile = '/tmp/chat.log' | |
logdir = os.path.dirname(logfile) | |
if not os.path.exists(logdir): | |
os.makedirs(logdir) | |
logging.basicConfig(filename=logfile, level=logging.DEBUG, | |
filemode="w") | |
logging.getLogger("").addHandler(ExceptionHandler()) | |
except BaseException, e: | |
print >> sys.stderr, "Logging init error: %s" % (e) | |
if __name__ == "__main__": | |
setup_logging() | |
main_window = MainWindow() | |
sys.excepthook = except_hook | |
main_window.main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Also, you need to change to
except BaseException as e
on 369 and removeimport commands
at the top.