Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

1013 lines (856 sloc) 33.321 kb
# -*-Python-*-
# Copyright (c) 2002 Sean R. Lynch <seanl@chaosring.org>
#
# This file is part of PythonVerse.
#
# PythonVerse is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# PythonVerse is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with PythonVerse; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# vim:syntax=python
import sys, bisect, string, thread, asyncore, time
import OpenVerse, transutil
from wxPython.wx import *
from math import *
from types import *
def wrap_lines(dc, lines, width):
r = []
for text in lines:
r.extend(wrap(dc, text, width))
return r
def wrap(dc, text, width):
"""Wrap a line of text, returning a list of lines."""
lines = []
while text:
if dc.GetTextExtent(text)[0] <= width: return lines + [text]
try:
i = string.rindex(text, ' ')
while dc.GetTextExtent(text[:i])[0] > width:
i = string.rindex(text, ' ', 0, i)
except ValueError:
i = len(text)-1
while dc.GetTextExtent(text[:i])[0] > width and i: i = i - 1
if not i: raise ValueError, 'width %d too narrow' % width
lines.append(text[:i])
text = string.lstrip(text[i:])
return lines
def progress(fraction, size=(20, 50)):
w, h = size
bitmap = wxEmptyBitmap(w, h)
y = h * fraction
dc = wxMemoryDC()
dc.SelectObject(bitmap)
dc.SetPen(wxTRANSPARENT_PEN)
dc.SetBrush(wxBLACK_BRUSH)
dc.DrawRectangle(0, 0, w, h-y)
dc.SetBrush(wxGREEN_BRUSH)
dc.DrawRectangle(0, h-y+1, w, y)
return bitmap
def invertrect(left, top, width, height, rects, min_width=50, min_height=10):
irects = []
rects = rects + [(left-1, top, 0, height),
(left, top-1, width, 0),
(left+width, top, 0, height),
(left, top+height, width, 0)]
for ax, ay, aw, ah in rects:
# Pick a left side
l = ax+aw
if l < left: continue
# Pick a top
for bx, by, bw, bh in rects:
# Make sure the rect can actually border ours
if bx+bw <= l or by+bh > ay+ah: continue
# Pick a top
t = by+bh
# Pick a right side
for cx, cy, cw, ch in rects:
# Make sure this rect can border ours
if cx-1 <= l or cy+ch <= t or \
cy >= top+height or \
cx-1 <= bx: continue
r = cx-1
#w = r - l
#h = bottom - t
if r-l < min_width or top+height-t < min_height: continue
bot = top+height-1
# There can be only one rect with these three sides
#rect = wxRect(l, t, w, h)
# Now find the bottom
for dx, dy, dw, dh in rects:
# Check if this rect overlaps ours
if r >= dx and l < dx+dw and \
bot >= dy and t < dy+dh:
bot = dy-1
# Make sure the rect is still sane and still borders
# on the original border rects
if bot-t < min_height or \
bot < ay or bot < cy: break
else: irects.append((l, t, r-l+1, bot-t+1))
return irects
class Sprite(wxEvtHandler):
mouseme = 0
pollme = 0
def __init__(self, parent, x, y):
wxEvtHandler.__init__(self)
self.dead = 0
self.parent = parent
self.x = x
self.y = y
self.rect = None
self.parent.AddSprite(self)
def __repr__(self):
return '<%s(%s, %s)>' % (self.__class__, self.image, self.rect)
def move(self, x, y):
self.x = x
self.y = y
self.CalcRect()
def SetRect(self, x, y, w, h):
dc = wxClientDC(self.parent)
if self.rect is not None:
ox, oy, ow, oh = self.rect
self.rect = x, y, w, h
if ox+oh > x and ox <= x+w and oy+oh > y and oy <= y+h:
# The rectangles overlap
nx = min(x, ox)
ny = min(y, oy)
w = max(x+w, ox+ow) - nx
h = max(y+h, oy+oh) - ny
x = nx
y = ny
else: self.parent.DoDrawing(dc, ox, oy, ow, oh)
else: self.rect = x, y, w, h
self.parent.DoDrawing(dc, x, y, w, h)
class Mouseover(Sprite):
def __init__(self, parent, x, y, image1, image2):
self.image1 = image1
self.image2 = image2
self.on = 0
Sprite.__init__(self, parent, x, y)
self.CalcRect()
def mouseon(self):
self.on = 1
self.CalcRect()
def mouseoff(self):
self.on = 0
self.CalcRect()
def set_image1(self, image):
self.image1 = image
self.CalcRect()
def set_image2(self, image):
self.image2 = image
self.CalcRect()
def CalcRect(self):
if self.on: image = self.image2
else: image = self.image1
w = image.GetWidth()
h = image.GetHeight()
x = self.x-w/2
y = self.y-h/2
self.SetRect(x, y, w, h)
def draw(self, dc):
wxLogVerbose('draw')
if self.on: image = self.image2
else: image = self.image1
w = self.image2.GetWidth()
h = self.image2.GetHeight()
dc.DrawBitmap(image, self.x-w/2, self.y-h/2, 1)
class MoveTimer(wxTimer):
"""Call the update function for avatars"""
def __init__(self):
wxTimer.__init__(self)
self.sprites = []
def Notify(self):
t = time.time()
sprites = self.sprites
for i in range(len(sprites)-1, -1, -1):
sprite = sprites[i]
r = sprite.update(t)
if r: del sprites[i]
if not sprites: self.Stop()
def add(self, sprite):
if sprite in self.sprites: return
self.sprites.append(sprite)
if len(self.sprites) == 1:
wxLogVerbose('starting timer')
self.Start(40)
_timer = MoveTimer()
class Avatar(Sprite):
mouseme = 1
def __init__(self, parent, x, y, bitmap, nick, noffset, boffset):
Sprite.__init__(self, parent, x, y)
self.bitmap = bitmap
self.nick = nick
self.destpos = None
self.speed = None
self.balloon = None
self.label_on = 1
self.set(noffset, boffset)
def SaneNametagOffset(self):
w = self.bitmap.GetWidth()
h = self.bitmap.GetHeight()
nx = max(-w/2-10, min(self.nx, w/2+10))
ny = max(-h/2-10, min(self.ny, h/2+10))
return nx, ny
def SaneBalloonOffset(self):
w = self.bitmap.GetWidth()
h = self.bitmap.GetHeight()
bx = max(-w/2-10, min(self.bx, w/2+10))
by = max(-h/2-10, min(self.by, h/2+10))
return bx, by
def Balloon(self):
w = self.bitmap.GetWidth()
h = self.bitmap.GetHeight()
bx, by = self.SaneBalloonOffset()
if self.balloon is None or self.balloon.dead:
self.balloon = Balloon(self.parent, self, self.x+bx, self.y+by)
return self.balloon
def SetImage(self, bitmap):
self.bitmap = bitmap
self.CalcRect()
def draw(self, dc):
nx, ny = self.SaneNametagOffset()
dc.DrawBitmap(self.bitmap, self.x-self.bitmap.GetWidth()/2,
self.y-self.bitmap.GetHeight()/2, 1)
if self.label_on:
dc.SetFont(wxSMALL_FONT)
w, h = dc.GetTextExtent(self.nick)
dc.SetTextForeground(wxCYAN)
dc.SetLogicalFunction(wxXOR)
dc.DrawText(self.nick, self.x+nx-w/2, self.y+ny-h/2)
dc.SetTextForeground(wxBLACK)
dc.SetLogicalFunction(wxCOPY)
def CalcRect(self):
w = self.bitmap.GetWidth()
h = self.bitmap.GetHeight()
x = self.x-w/2
y = self.y-h/2
if self.label_on:
dc = wxClientDC(self.parent)
dc.SetFont(wxSMALL_FONT)
tw, th = dc.GetTextExtent(self.nick)
nx, ny = self.SaneNametagOffset()
tx, ty = self.x+nx-tw/2, self.y+ny-th/2
x, y, w, h = RectUnion((x, y, w, h),
(tx, ty, tw, th))
self.SetRect(x, y, w, h)
def set(self, noffset, boffset):
self.bx, self.by = boffset
self.nx, self.ny = noffset
self.CalcRect()
def AnimateMove(self, position, speed):
"""Changes the location of the avatar's center to the new position"""
wxLogVerbose('AnimateMove')
if position[0] >= 640 or position[1] > 480:
wxLogWarning('Attempt to move outside the screen')
return
self.speed = speed
pos = self.x, self.y
self.startpos = pos
self.starttime = time.time()
distance = dist(pos, position)
if distance == 0.0: return
self.dx = 300.0 * (position[0]-pos[0]) * self.speed / distance
self.dy = 300.0 * (position[1]-pos[1]) * self.speed / distance
self.stoptime = self.starttime + distance / 300.0 / speed
self.destpos = position
_timer.add(self)
def update(self, t):
"""Move the avatar if necessary"""
if t >= self.stoptime:
self.move(self.destpos[0], self.destpos[1])
if self.balloon is not None and not self.balloon.dead:
bx, by = self.SaneBalloonOffset()
x, y = self.destpos
self.balloon.move(x+bx, y+by)
return 1
else:
delta_t = t - self.starttime
x, y = self.startpos
x = x+int(round(self.dx*delta_t))
y = y+int(round(self.dy*delta_t))
self.move(x, y)
def arc(radius, center, start_angle, stop_angle, n):
x, y = center
step = (stop_angle - start_angle) / n
points = [0] * (n+1)
for i in range(n+1):
angle = start_angle + i*step
points[i] = (x + int(round(radius*sin(angle))),
y - int(round(radius*cos(angle))))
return tuple(points)
def closest(rect, x, y):
"""Find the closest point on a rect to a given point"""
rx, ry, rw, rh = rect
return min(rx+rw-1, max(x, rx)), min(ry+rh-1, max(y, ry))
def dist(point1, point2):
x1, y1 = point1
x2, y2 = point2
return sqrt((x1-x2)**2 + (y1-y2)**2)
def rectdist(rect, x, y):
return dist(closest(rect, x, y), (x, y))
def ClampRect(r1, r2):
x1, y1, w1, h1 = r1
x2, y2, w2, h2 = r2
x = max(x1, x2)
y = max(y1, y2)
if x+w1 > x2+w2: x = x2+w2-w1
if y+h1 > y2+h2: y = y2+h2-h1
return x, y, w1, h1
def RectUnion(r1, r2):
x1, y1, w1, h1 = r1
x2, y2, w2, h2 = r2
x = min(x1, x2)
y = min(y1, y2)
w = max(x1+w1, x2+w2)-x
h = max(y1+h1, y2+h2)-y
return x, y, w, h
class Balloon(Sprite):
"""A speech balloon"""
# Padding for balloon rectangles, also radius of corner arcs
pad = 3
def __init__(self, parent, avatar, x, y):
Sprite.__init__(self, parent, x, y)
self.avatar = avatar
self.text = []
#EVT_TIMER(self, -1, self.OnTimer)
def OnTimer(self):
if self.dead: return
del self.text[0]
if self.text: self.CalcRect()
else:
self.dead = 1
self.parent.RemoveSprite(self)
def move(self, x, y):
self.x = x
self.y = y
self.CalcRect()
def nearer(self, rect1, rect2):
"""Compare two rects based on their distance from our position"""
dist1 = rectdist(rect1, self.x, self.y)
dist2 = rectdist(rect2, self.x, self.y)
if dist1 == dist2: return cmp(rect2[2], rect1[2])
return cmp(dist1, dist2)
def CalcRect(self):
rects = self.parent.BalloonRects(self)
dc = wxClientDC(self.parent)
dc.SetFont(wxSMALL_FONT)
notdone = 1
while notdone:
# Loop until we can fit the balloon into *some* rect
for r in rects:
try: lines = wrap_lines(dc, self.text, r[2]-self.pad*2)
except ValueError: continue
height = 0
for l in lines: height = height + dc.GetTextExtent(l)[1]
if height < r[3]:
notdone = 0
break
else:
# Couldn't render the balloon, delete some lines
del self.text[0]
# Count the number of lines in each sequence of text
width = max(map(lambda l, dc=dc: dc.GetTextExtent(l)[0], lines)) +\
(self.pad * 2)
self.lines = lines
self.textrect = ClampRect((self.x-width/2, self.y-height/2, width,
height), r)
bigrect = RectUnion(self.textrect,
(self.x, self.y, 1, 1))
self.SetRect(bigrect[0], bigrect[1], bigrect[2], bigrect[3])
def draw(self, dc):
# The rectangles had better already be calculated.
pad = self.pad
left, top, w, h = self.textrect
right = left+w-1
bottom = top+h-1
dc.SetPen(wxBLACK_PEN)
dc.DrawRoundedRectangle(left, top, w, h, pad)
x = self.x
y = self.y
if x < left or x > right or y < top or y > bottom:
cx, cy = closest(self.textrect, x, y)
if x > right-pad*2:
# Drawing to the east
if y > bottom-pad*2:
# Draw the arrow to the southeast
x1 = right
y1 = bottom-pad
x2 = right-pad
y2 = bottom
elif y < top+pad*2:
# Draw the arrow to the northeast
x1 = right
x2 = right-pad
y2 = top+1
y1 = top+pad
elif x > right:
# Due east
x1 = right
y1 = cy-pad
x2 = right
y2 = cy+pad
elif x < left+pad*2:
# Drawing to the west
if y > bottom-pad*2:
# Southwest
x1 = left
y1 = bottom-pad
x2 = left+pad
y2 = bottom
elif y < top+pad*2:
# Northwest
x1 = left
y1 = top+pad
x2 = left+pad
y2 = top+1
elif x < left:
# Due west
x1 = left+1
y1 = cy+pad
x2 = left+1
y2 = cy-pad
else:
# Top or bottom
x1 = cx-pad
x2 = cx+pad
if y < top:
# Due north
y1 = top+1
y2 = top+1
elif y > bottom:
# Due south
y1 = bottom
y2 = bottom
points = [wxPoint(x1, y1), wxPoint(x, y), wxPoint(x2, y2)]
dc.SetPen(wxTRANSPARENT_PEN)
dc.DrawPolygon(points)
dc.SetPen(wxBLACK_PEN)
dc.DrawLines(points)
ty = top
tx = left + w/2
dc.SetFont(wxSMALL_FONT)
for line in self.lines:
lw, lh = dc.GetTextExtent(line)
dc.DrawText(line, tx-lw/2, ty)
ty = ty + lh
dc.SetPen(wxNullPen)
def add_text(self, text, timeout=10):
"""Add text to the balloon, scrolling it if necessary."""
self.text.append(text)
self.CalcRect()
_scheduler.ScheduleRel(10, self.OnTimer, ())
class ClientCanvas(wxPanel):
def __init__(self, parent):
wxPanel.__init__(self, parent, -1)
self.parent = parent
self.sprites = []
self.spriterects = {}
#self.background_dc = wxMemoryDC()
self.dc = wxMemoryDC()
self.SetBackground(wxEmptyBitmap(640, 480))
EVT_PAINT(self, self.OnPaint)
EVT_LEFT_DOWN(self, self.OnLeftButtonEvent)
def AddSprite(self, sprite):
self.sprites.append(sprite)
if sprite.rect is None: return
x, y, w, h = sprite.rect
self.DoDrawing(wxClientDC(self), x, y, w, h)
def RemoveSprite(self, sprite):
self.sprites.remove(sprite)
if sprite.rect is None: return
x, y, w, h = sprite.rect
self.DoDrawing(wxClientDC(self), x, y, w, h)
def BalloonRects(self, balloon):
w = self.background.GetWidth()
h = self.background.GetHeight()
x = balloon.x
y = balloon.y
rects = map(lambda s: s.rect,
filter(lambda s,n=balloon: s!=n,
self.sprites))
irects1 = invertrect(0, 0, w, h, rects)
irects1 = filter(lambda r,x=x,y=y: rectdist(r, x, y) <= 200, irects1)
irects1.sort(balloon.nearer)
rects = map(lambda s: s.rect,
filter(lambda s,n=balloon:
s!=n and s.__class__ is Balloon, self.sprites))
rects.append(balloon.avatar.rect)
irects2 = invertrect(0, 0, w, h, rects)
irects2.sort(balloon.nearer)
return irects1 + irects2
def Balloon(self, nick, text):
try: avatar = self.parent.avatars[nick]
except KeyError: self.debug('No avatar called %s' % nick)
else:
dc = wxClientDC(self)
w = self.background.GetWidth()
h = self.background.GetHeight()
balloon = avatar.Balloon()
if balloon not in self.sprites: self.sprites.append(balloon)
balloon.add_text(text)
def SetBackground(self, bitmap):
#self.background_dc.SelectObject(bitmap)
self.background = bitmap
self.width = bitmap.GetWidth()
self.height = bitmap.GetHeight()
self.SetClientSizeWH(self.width, self.height)
self.SetSizeHints(self.width, self.height, self.width, self.height)
self.bitmap = wxEmptyBitmap(self.width, self.height)
self.dc.SelectObject(self.bitmap)
#self.SetScrollbars(20, 20, self.width/20, self.height/20)
self.DoDrawing(wxClientDC(self), 0, 0, self.width, self.height)
def OnLeftButtonEvent(self, event):
x = event.GetX()
y = event.GetY()
self.parent.server.move((x, y))
self.parent.entry.SetFocus()
def OnPaint(self, event):
dc = wxPaintDC(self)
#self.PrepareDC(dc)
upd = wxRegionIterator(self.GetUpdateRegion())
while upd.HaveRects():
x = upd.GetX()
y = upd.GetY()
w = upd.GetW()
h = upd.GetH()
self.DoDrawing(dc, x, y, w, h)
upd.Next()
def DoDrawing(self, dc, x, y, w, h):
wxLogVerbose('%d %d %d %d' % (x, y, w, h))
dc.SetClippingRegion(x, y, w, h)
r = x+w-1
b = y+h-1
dc.BeginDrawing()
dc.DrawBitmap(self.background, 0, 0)
#dc.Blit(x, y, w, h, self.background_dc, x, y)
for sprite in self.sprites:
# Draw the sprite if its rect overlaps
if sprite.rect is None:
wxLogVerbose('Null rect for sprite')
continue
sx, sy, sw, sh = sprite.rect
if sx+sw > x and sx <= x+w and sy+sh > y and sy <= y+h:
sprite.draw(dc)
dc.EndDrawing()
dc.DestroyClippingRegion()
#dc.Blit(x, y, w, h, mdc, x, y)
class Client(wxPanel):
"""Callbacks for the server connection"""
def __init__(self, frame, parent, id, host, port, nick, avatar):
wxPanel.__init__(self, parent, id)
self.frame = frame
self.parent = parent
self.nick = nick
self.title = '%s:%s' % (host, port)
self.canvas = ClientCanvas(self)
hbox = wxBoxSizer(wxHORIZONTAL)
hbox.Add(self.canvas, 0, 0)
self.listbox = wxListBox(self, -1,
style=wxLB_EXTENDED|wxLB_NEEDED_SB|wxLB_SORT)
hbox.Add(self.listbox, 1, wxEXPAND)
self.sizer = wxBoxSizer(wxVERTICAL)
self.sizer.Add(hbox, 0, wxEXPAND)
self.textchat = wxTextCtrl(self, -1, style=wxTE_MULTILINE|\
wxTE_READONLY|wxTE_RICH)
self.sizer.Add(self.textchat, 1, wxEXPAND)
self.entry = wxTextCtrl(self, -1, style=wxTE_PROCESS_ENTER)
self.sizer.Add(self.entry, 0, wxEXPAND)
self.SetAutoLayout(true)
self.SetSizer(self.sizer)
self.sizer.SetSizeHints(self)
self.server = OpenVerse.ServerConnection(host, port, self, nick,
avatar)
self.avatars = {}
self.handler = transutil.InputHandler(
(('nick', self.cmd_nick, '(\S+)', (str,)),
('quote', self.cmd_quote, '(.*)', (str,)),
('avatar', self.cmd_avatar, '(\S+)', (str,)),
('whois', self.cmd_whois, '(\S+)', (str,))))
EVT_TEXT_ENTER(self, self.entry.GetId(), self.OnSendText)
# Event handlers
def OnSendText(self, event):
text = self.entry.GetValue()
self.entry.SetValue('')
self.entry.SetFocus()
if text and text[0] == '/':
text = text[1:]
if text and text[0] != '/':
try: self.handler.handle(text)
except transutil.HandlerError, info: self.debug(info)
return
self.server.chat(text)
# Utility functions
def debug(self, s):
"""Handle debug messages"""
wxLogVerbose(s)
# Command handlers
def cmd_nick(self, nick):
self.nick = nick
self.frame.rename(self, '%s - %s' % (self.title, nick))
self.server.set_nick(nick)
def cmd_quote(self, text):
self.server.quote(text)
def cmd_avatar(self, avatar):
self.server.set_avatar(avatar)
def cmd_msg(self, nicks, text):
nicks = string.split(nicks, ',')
self.server.privmsg(nicks, text)
def cmd_whois(self, nick):
self.server.whois(nick)
# Transport-called functions
def close(self):
self.frame.close(self)
def background_image(self, image):
"""Change the background"""
self.canvas.SetBackground(image)
def background_progress(self, length, filename, size): pass
#self.background_image(progress(float(length)/float(size)))
def set_title(self, title):
"""Change the room's name"""
self.title = title
self.frame.rename(self, '%s - %s' % (title, self.nick))
def raise_object(self, name):
"""Raise the named object to the top of the stacking order."""
try: avatar = self.avatars[name]
except: self.debug('No avatar called %s' % name)
else:
self.canvas.sprites.above(avatar)
def mouseover(self, name, pos, image1, image2):
"""Create a mouseover object"""
x, y = pos
mo = Mouseover(self.canvas, x, y, image1, image2)
self.avatars[name] = mo
def newimage(self, filename=None):
"""Load an image from a file object"""
self.debug('newimage %s' % repr(filename))
if filename is None: return wxNullBitmap
return wxImage(filename).ConvertToBitmap()
def new_avatar(self, nick, pos, image, noffset, boffset):
x, y = pos
avatar = Avatar(self.canvas, x, y, image, nick, noffset, boffset)
self.avatars[nick] = avatar
self.listbox.Append(nick)
self.canvas.Balloon(nick, '*%s* has entered the room.' % nick)
def del_avatar(self, nick):
self.textchat.AppendText('*%s* left the room.\n' % nick)
self.canvas.Balloon(nick, '*%s* has left the room.' % nick)
self.listbox.Delete(self.listbox.FindString(nick))
try: avatar = self.avatars[nick]
except KeyError: pass
else:
self.canvas.RemoveSprite(avatar)
del self.avatars[nick]
def avatar(self, nick, image, noffset, boffset):
"""Change the avatar for a nick"""
try: avatar = self.avatars[nick]
except KeyError: self.debug('No avatar called %s' % nick)
else:
avatar.SetImage(image)
avatar.set(noffset, boffset)
def mouseover_image1(self, name, image):
"""Set the unactivated image for a mouseover"""
mo = self.avatars[name]
mo.set_image1(image)
#self.canvas.DoDrawing(mo.rect.x, mo.rect.y, mo.rect.width,
# mo.rect.height)
def mouseover_image2(self, name, image):
"""Set the activated image for a mouseover"""
mo = self.avatars[name]
mo.set_image2(image)
#self.canvas.DoDrawing(mo.rect.x, mo.rect.y, mo.rect.width,
# mo.rect.height)
def avatar_image(self, nick, image):
"""Set the image for an avatar"""
try: avatar = self.avatars[nick]
except KeyError: self.debug('No avatar called %s' % nick)
else:
avatar.SetImage(image)
def avatar_progress(self, nick, length, filename, size):
"""Set an avatar's image to a progress bar"""
try: avatar = self.avatars[nick]
except KeyError: self.debug('No avatar called %s' % nick)
else: avatar.SetImage(progress(float(length)/float(size)))
def move_avatar(self, nick, x, y, speed):
wxLogVerbose('move_avatar')
try: avatar = self.avatars[nick]
except: self.debug('No avatar called %s' % nick)
else: avatar.AnimateMove((x, y), speed)
def privmsg(self, nick, s):
self.textchat.AppendText('[%s] %s\n' % (nick, s))
try: avatar = self.avatars[nick]
except KeyError: self.debug('No avatar called %s' % nick)
else: self.canvas.Balloon(nick, s)
def chat(self, nick, s):
self.textchat.AppendText('<%s> %s\n' % (nick, s))
self.canvas.Balloon(nick, s)
class Frame(wxFrame):
"""The main frame of the application"""
def __init__(self, parent, ID, title):
wxFrame.__init__(self, parent, ID, title,
wxDefaultPosition)
self.CreateStatusBar()
self.SetStatusText("This is the statusbar")
ID_ABOUT = wxNewId()
ID_EXIT = wxNewId()
ID_CONNECT = wxNewId()
menu = wxMenu()
menu.Append(ID_CONNECT, "&Connect", "Connect to a new room")
menu.AppendSeparator()
menu.Append(ID_EXIT, "E&xit", "Terminate the program")
menuBar = wxMenuBar()
menuBar.Append(menu, "&File")
menu = wxMenu()
menu.Append(ID_ABOUT, "&About",
"More information about this program")
menuBar.Append(menu, "&Help")
self.SetMenuBar(menuBar)
EVT_MENU(self, ID_ABOUT, self.OnAbout)
EVT_MENU(self, ID_EXIT, self.TimeToQuit)
EVT_MENU(self, ID_CONNECT, self.OnConnection)
self.client_nb = wxNotebook(self, -1)
self.client_nb.AddPage(LogWindow(self.client_nb), "Log")
self.sizer = wxNotebookSizer(self.client_nb)
self.SetAutoLayout(true)
self.SetSizer(self.sizer)
def GetClientPage(self, client):
"""Return the page number for a given client"""
ID = client.GetId()
for i in range(self.client_nb.GetPageCount()):
page = self.client_nb.GetPage(i)
if page.GetId() == ID:
return i
else: raise ValueError, 'Nonexistent page'
def close(self, client):
self.client_nb.DeletePage(self.GetClientPage(client))
def rename(self, client, name):
self.client_nb.SetPageText(self.GetClientPage(client), name)
def resize(self):
self.Fit()
self.sizer.SetSizeHints(self)
def OnAbout(self, event):
dlg = wxMessageDialog(self, "PythonVerse\n"
"Copyright 2001 Christine McIntyre\n"
"All rights reserved.\n",
"About PythonVerse", wxOK | wxICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()
def OnConnection(self, event):
window = ConnectionDialog(self, -1)
window.Show(true)
def TimeToQuit(self, event):
self.Close(true)
def connect(self, server, port, nick, avatar):
self.client_nb.AddPage(Client(self, self.client_nb, -1, server, port,
nick, avatar),
"%s:%d" % (server, port))
class LogWindow(wxPanel):
def __init__(self, parent, ID=-1):
wxPanel.__init__(self, parent, ID)
self.tc = wxTextCtrl(self, ID,
style=wxTE_MULTILINE|wxTE_READONLY|wxTE_RICH)
self.logger = wxLogTextCtrl(self.tc)
self.logger.SetVerbose(true)
wxLog_SetActiveTarget(self.logger)
self.sizer = wxBoxSizer(wxVERTICAL)
self.sizer.Add(self.tc, 1, wxEXPAND)
self.SetSizer(self.sizer)
self.SetAutoLayout(true)
class ConnectionDialog(wxDialog):
"""Dialog for connecting to a server"""
def __init__(self, parent, ID):
wxDialog.__init__(self, parent, ID, "Connect to server")
self.parent = parent
gs = wxFlexGridSizer(4,2,5,5)
gs.AddGrowableCol(1)
gs.Add(wxStaticText(self, -1, "Server"), 0, wxEXPAND)
self.editserver = wxTextCtrl(self, 255, "openverse.com")
gs.Add(self.editserver, 1, wxEXPAND)
#EVT_TEXT(self, 20, self.EvtText)
#EVT_CHAR(self.editname, self.EvtChar)
gs.Add(wxStaticText(self, -1, "Port"), 0, wxEXPAND)
self.editport = wxTextCtrl(self, 5, "6900")
gs.Add(self.editport, 1, wxEXPAND)
gs.Add(wxStaticText(self, -1, "Nick"), 0, wxEXPAND)
self.editnick = wxTextCtrl(self, 9, "Ryoko")
gs.Add(self.editnick, 1, wxEXPAND)
gs.Add(wxStaticText(self, -1, "Avatar"), 0, wxEXPAND)
self.editavatar = wxTextCtrl(self, 9, "ryoko")
gs.Add(self.editavatar, 1, wxEXPAND)
vbox = wxBoxSizer(wxVERTICAL)
vbox.Add(gs)
hbox = wxBoxSizer(wxHORIZONTAL)
button = wxButton(self, wxID_OK, "Connect")
EVT_BUTTON(self, wxID_OK, self.OnOK)
hbox.Add(button)
button = wxButton(self, wxID_CANCEL, "Cancel")
hbox.Add(button)
vbox.Add(hbox)
self.sizer = vbox
self.SetAutoLayout(true)
self.SetSizer(self.sizer)
self.sizer.Fit(self)
def OnOK(self, event):
server = self.editserver.GetValue()
port = self.editport.GetValue()
nick = self.editnick.GetValue()
avatar = self.editavatar.GetValue()
self.parent.connect(server, int(port), nick, avatar)
#wxDialog.OnOK(self, event)
return event.Skip()
class Application(wxApp):
"""The main application object"""
def OnInit(self):
"""Initialization function called by wxPython"""
# Create the main frame
frame = Frame(NULL, -1, 'PythonVerse')
frame.Show(true)
self.SetTopWindow(frame)
return true
class Scheduler(wxTimer):
def __init__(self):
wxTimer.__init__(self)
self.events = []
def Schedule(self, t, func, args):
event = t, func, args
bisect.insort(self.events, event)
# Restart the timer
self.Start(min(0, t-time.time())*1000, true)
return event
def ScheduleRel(self, delay, func, args):
now = time.time()
return self.Schedule(now+delay, func, args)
def Cancel(self, event):
self.events.remove(event)
def Notify(self):
now = time.time()
while self.events:
t, func, args = self.events[0]
if t > now: break
apply(func, args)
del self.events[0]
# If there are still events left over, then the last
# setting of t will be for the first event that's not yet
# scheduled. Make sure it's a single shot :)
if self.events: self.Start((t-now)*1000, true)
_scheduler = Scheduler()
class IOTimer(wxTimer):
"""Poll asyncore, would be better to use threads"""
def Notify(self):
asyncore.poll(0.0)
def main(argv):
app = Application(0)
wxInitAllImageHandlers()
t = IOTimer()
t.Start(500)
app.MainLoop()
if __name__ == '__main__': main(sys.argv)
Jump to Line
Something went wrong with that request. Please try again.