[Urwid] scrolling

James Mills prologic at shortcircuit.net.au
Wed Jun 28 10:28:13 EDT 2006


On Wed, Jun 28, 2006 at 12:55:28PM +0200, Rebecca Breu wrote:
> Hi James!

Hi :)

> Instead of a Pile widget, I would use a ListBox, they are ideal for scrolling.
> You can then set the focus to the newest line, like this:
> 
> 
>     #Many lines:
>     wlist = [urwid.Text(str(i)) for i in range(1, 40)]
> 
>     #widget = urwid.Filler(urwid.Pile(wlist));
>     widget = urwid.ListBox(wlist)
>     widget.set_focus(len(wlist))

Thank you for this, I've applied it and it seems to work
well.

I noticed though that the granuality of the get_input
in urwid (or maybe even curses) isn't small enough, so
I've implemented my event manager, irc client and user
interface as threads.

See attached update source.

cheers
James

-- 
--
-"Problems are Solved by Method"
-
- James Mills <prologic at shortcircuit.net.au>
- HomePage: http://shortcircuit.net.au/~prologic/
- Phone: +61732166379
- Mobile: +61404270962
- Skype: therealprologic
- MSN: prologic at shortcircuit.net.au
- ICQ: 98888663
- IRC: irc://shortcircuit.net.au#se

Please avoid sending me Word or PowerPoint attachments.
See http://www.gnu.org/philosophy/no-word-attachments.html
-------------- next part --------------
#!/usr/bin/env python

import re
import os
import sys
import urwid
import curses
import socket
from time import sleep
from threading import Thread
from inspect import getargspec
from urwid.curses_display import Screen

from pymills.irc import *
from pymills.event import *
from pymills.misc import backMerge
from pymills.sockets import TCPClient

MAIN_TITLE = "PyMills IRC Client"

HELP_STRINGS = {
"main": "For help, type: /help"
}

class Manager(EventManager, Thread):

	def __init__(self):
		EventManager.__init__(self)
		Thread.__init__(self)
	
	def start(self):
		self.running = True
		Thread.start(self)

	def stop(self):
		self.running = False

	def run(self):
		while self.running:
			self.flush()
			sleep(0.0001)

class Client(TCPClient, IRC, Thread):

	def __init__(self, event):
		TCPClient.__init__(self)
		IRC.__init__(self)
		Thread.__init__(self)

	def start(self):
		self.running = True
		Thread.start(self)

	def stop(self):
		self.running = False

	def run(self):
		while self.running:
			if self.connected:
				self.process()
			else:
				sleep(1)
	
	def ircRAW(self, data):
		self.write(data + "\r\n")
	
	@listener("read")
	def onREAD(self, line):
		TCPClient.onREAD(self, line)
		IRC.onREAD(self, line)
	
class MainWindow(Screen, Component, Thread):

	def __init__(self, event, client):
		Screen.__init__(self)
		Component.__init__(self)
		Thread.__init__(self)

		self.client = client
		self.channel = None

		self.client.setNick(
				os.getenv("USER", "PyMills"))
		self.client.setIdent(
				os.getenv("USER", "PyMills"))

		self.cmdRegex = re.compile(
				"\/(?P<command>[a-z]+) ?"
				"(?P<args>.*)(?iu)")

		self.register_palette([
				("title", "white", "dark blue", "standout"),
				("line", "light gray", "black"),
				("help", "white", "dark blue")])

		self.lines = []
		self.body = urwid.ListBox(self.lines)
		self.body.set_focus(len(self.lines))

		self.title = urwid.Text(MAIN_TITLE)
		self.header = urwid.AttrWrap(self.title, "title")

		self.help = urwid.AttrWrap(
				urwid.Text(
					HELP_STRINGS["main"]), "help")
		self.input = urwid.Edit(caption="%s> " % self.channel)
		self.footer = urwid.Pile([self.help, self.input])

		self.top = urwid.Frame(self.body, self.header,
				self.footer)

	def start(self):
		self.running = True
	
		self.s = curses.initscr()
		self.has_color = curses.has_colors()
		if self.has_color:
			curses.start_color()
			if curses.COLORS < 8:
				# not colourful enough
				self.has_color = False
		if self.has_color:
			try:
				curses.use_default_colors()
				self.has_default_colors=True
			except:
				self.has_default_colors=False
		self._setup_colour_pairs()
		curses.noecho()
		curses.meta(1)
		curses.halfdelay(10) # don't wait longer than 1s for keypress
		self.s.keypad(0)
		self.s.scrollok(1)

		Thread.start(self)
	
	def stop(self):
		self.running = False

	def unknownCommand(self, command):
		self.lines.append(
				urwid.Text(
					"Unknown command: %s" % command))
	def syntaxError(self, command, args, expected):
		self.lines.append(
				urwid.Text(
					"Syntax error (%s): %s Expected: %s" % (
						command, args, expected)))

	def process(self, s):

		match = self.cmdRegex.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(*backMerge(tokens, factor))
								else:
									print "1"
									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.channel is not None:
				self.lines.append(urwid.Text(
					"<%s> %s" % (self.client.getNick(), s)))
				self.client.ircPRIVMSG(self.channel, s)
			else:
				self.lines.append(urwid.Text(
					"No channel joined. Try /join #<channel>"))

	@filter("numeric")
	def onNUMERIC(self, source, target, numeric, arg, message):

		if numeric == ERR_NICKNAMEINUSE:
			self.client.ircNICK(
					self.client.getNick() + "_")
		else:
			if arg is not None:
				self.lines.append(urwid.Text(
					"%s :%s" % (arg, message)))
			else:
				self.lines.append(urwid.Text(message))

		return True, None
	
	@filter("notice")
	def onNOTICE(self, source, target, message):
		if type(source) == str:
			nick = source
		else:
			nick, ident, host = sourceSplit(source)

		self.lines.append(urwid.Text(
				"-%s- %s" % (nick, message)))
		return True, None

	@filter("message")
	def onMESSAGE(self, source, target, message):
		if type(source) == str:
			nick = source
		else:
			nick, ident, host = sourceSplit(source)
		self.lines.append(urwid.Text(
				"<%s> %s" % (nick, message)))
		return True, None
	
	def cmdEXIT(self, message=""):
		if self.client.connected:
			self.cmdQUIT(message)
		self.running = False

	def cmdSERVER(self, host, port=6667):
		self.client.open(host, port)
		self.client.ircUSER(
				self.client.getIdent(),
				socket.gethostname(),
				host,
				"PyMills Example IRC Client")
		self.client.ircNICK(self.client.getNick())

	def cmdJOIN(self, channel):
		if self.channel is not None:
			self.cmdPART(self.channel, "Joining %s" % channel)

		self.client.ircJOIN(channel)
		self.channel = channel
	
	def cmdPART(self, channel=None, message="Leaving"):
		if channel is None:
			channel = self.channel
		if channel is not None:
			self.client.ircPART(channel, message)
			self.channel = None
		
	def cmdQUOTE(self, message):
		self.client.ircRAW(message)

	def cmdQUIT(self, message="Bye"):
		self.client.ircQUIT(message)

	def run(self):

		try:
			size = self.get_cols_rows()

			while self.running:

				self.update_screen(size)

				keys = self.get_input()

				for k in keys:

					if k == "window resize":
						size = self.get_cols_rows()
						continue
					elif k == "enter":
						self.process(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)

		finally:
			curses.echo()
			self._curs_set(1)
			try:
				curses.endwin()
			except:
				pass # don't block original error with curses error

	def update_screen(self, size):
		self.body.set_focus(len(self.lines))
		canvas = self.top.render(size, focus=True)
		self.draw_screen(size, canvas)
	
def main():
	manager = Manager()
	client = Client(manager)
	window = MainWindow(manager, client)

	manager.start()
	client.start()
	window.start()

	while window.running:
		sleep(1)

	client.stop()
	manager.stop()
	window.stop()

if __name__ == "__main__":
	main()


More information about the Urwid mailing list