# -*-Python-*- # Copyright (c) 2002 Sean R. Lynch # # 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(wxScrollingWindow): 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)