summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlastair Tse <liquidx@gentoo.org>2003-03-16 16:59:04 +0000
committerAlastair Tse <liquidx@gentoo.org>2003-03-16 16:59:04 +0000
commit24228640448df7bf9a8c32f2800754756a5ab4fa (patch)
treed512b16a22fae76d5e24ba39371ab2133eba4332 /net-p2p/pysoulseek
parent~x86 -> x86 (diff)
downloadgentoo-2-24228640448df7bf9a8c32f2800754756a5ab4fa.tar.gz
gentoo-2-24228640448df7bf9a8c32f2800754756a5ab4fa.tar.bz2
gentoo-2-24228640448df7bf9a8c32f2800754756a5ab4fa.zip
patch added and deps touched
Diffstat (limited to 'net-p2p/pysoulseek')
-rw-r--r--net-p2p/pysoulseek/ChangeLog7
-rw-r--r--net-p2p/pysoulseek/files/digest-pysoulseek-1.0.0-r11
-rw-r--r--net-p2p/pysoulseek/files/pysoulseek-1.0.0-hyriand-11.patch2566
-rw-r--r--net-p2p/pysoulseek/pysoulseek-1.0.0-r1.ebuild35
-rw-r--r--net-p2p/pysoulseek/pysoulseek-1.0.0.ebuild4
5 files changed, 2610 insertions, 3 deletions
diff --git a/net-p2p/pysoulseek/ChangeLog b/net-p2p/pysoulseek/ChangeLog
index 0f690968c373..5883d752a33b 100644
--- a/net-p2p/pysoulseek/ChangeLog
+++ b/net-p2p/pysoulseek/ChangeLog
@@ -1,7 +1,12 @@
# ChangeLog for net-p2p/pysoulseek
# Copyright 2002-2003 Gentoo Technologies, Inc.; Distributed under the GPL v2
-# $Header: /var/cvsroot/gentoo-x86/net-p2p/pysoulseek/ChangeLog,v 1.8 2003/03/14 23:15:51 tantive Exp $
+# $Header: /var/cvsroot/gentoo-x86/net-p2p/pysoulseek/ChangeLog,v 1.9 2003/03/16 16:59:04 liquidx Exp $
+*pysoulseek-1.0.0-r1 (16 Mar 2003)
+
+ 16 Mar 2003; Alastair Tse <liquidx@gentoo.org> pysoulseek-1.0.0-r1.ebuild,
+ pysoulseek-1.0.0.ebuild, files/pysoulseek-1.0.0-hyriand-11.patch:
+ added hyriand patch and changed wxPython dependency
*pysoulseek-1.0.0 (15 Mar 2003)
diff --git a/net-p2p/pysoulseek/files/digest-pysoulseek-1.0.0-r1 b/net-p2p/pysoulseek/files/digest-pysoulseek-1.0.0-r1
new file mode 100644
index 000000000000..989f97390abe
--- /dev/null
+++ b/net-p2p/pysoulseek/files/digest-pysoulseek-1.0.0-r1
@@ -0,0 +1 @@
+MD5 9c60085d1121bfeee5fc7b591d07aa68 pyslsk-1.0.0.tar.gz 89270
diff --git a/net-p2p/pysoulseek/files/pysoulseek-1.0.0-hyriand-11.patch b/net-p2p/pysoulseek/files/pysoulseek-1.0.0-hyriand-11.patch
new file mode 100644
index 000000000000..eb6cceaa9c9a
--- /dev/null
+++ b/net-p2p/pysoulseek/files/pysoulseek-1.0.0-hyriand-11.patch
@@ -0,0 +1,2566 @@
+diff -rNu3 pyslsk-1.0.0/encode_bitmaps.py slsk-tmp/encode_bitmaps.py
+--- pyslsk-1.0.0/encode_bitmaps.py 2002-09-29 15:42:51.000000000 +0200
++++ slsk-tmp/encode_bitmaps.py 2003-03-12 21:31:31.000000000 +0100
+@@ -12,7 +12,7 @@
+ "-a -n Offline img/offline.gif pysoulseek/wxgui/images.py",
+ "-a -n Down img/sm_down.png pysoulseek/wxgui/images.py",
+ "-a -n Up img/sm_up.png pysoulseek/wxgui/images.py",
+-
++ "-a -n Yellow img/active2.gif pysoulseek/wxgui/images.py",
+ ]
+
+
+diff -rNu3 pyslsk-1.0.0/pysoulseek/config.py slsk-tmp/pysoulseek/config.py
+--- pyslsk-1.0.0/pysoulseek/config.py 2003-03-12 18:17:38.000000000 +0100
++++ slsk-tmp/pysoulseek/config.py 2003-03-12 21:33:16.000000000 +0100
+@@ -32,20 +32,29 @@
+ "transfers":{"downloaddir":None,"sharedownloaddir":1,"shared":None, \
+ "uploadbandwidth":100,"uselimit":0,"uploadlimit":100,"limitby":1,
+ "usecustomban":0,"customban":"don't bother to retry",
+- "downloads":[],"sharedfiles":{}, \
++ "downloads":[],"sharedfiles":{},"preferfriends":0, \
++ "useupslots":0,"uploadslots":2, \
+ "sharedfilesstreams":{},"sharedindex":{}},"userinfo":{"descr":"''", \
+- "pic":""},"logging":{"logsdir":os.path.expanduser("~"),"privatechat":0,"chatrooms":0},"searches":{"maxresults":50}}
++ "pic":""},"logging":{"logsdir":os.path.expanduser("~"),"privatechat":0,"chatrooms":0},"searches":{"maxresults":50},
++ "ui":{"chatme":"FOREST GREEN", "chatremote":"","chatlocal":"BLUE", \
++ "chathilite":"", "search":"","searchq":"GREY", "decimalsep":","}}
+ try:
+ f = open(filename+".shares")
+ self.sharesdb = cPickle.load(f)
+ f.close()
+ except:
+ self.sharesdb = None
+-
++ try:
++ f = open(filename+".alias")
++ self.aliases = cPickle.load(f)
++ f.close()
++ except:
++ self.aliases = {}
++
+ def needConfig(self):
+ for i in self.sections.keys():
+ for j in self.sections[i].keys():
+- if self.sections[i][j] is None or self.sections[i][j] == '' and i != "userinfo":
++ if self.sections[i][j] is None or self.sections[i][j] == '' and i not in ("userinfo","ui"):
+ return 1
+ return 0
+
+@@ -57,7 +66,7 @@
+ print "Bogus config section:",i
+ elif j not in self.sections[i].keys():
+ print "Bogus config option",j,"section",i
+- elif j in ['server','shared','uploadbandwidth','uselimit',"usecustomban",'uploadlimit','limitby','downloads','sharedfiles','sharedownloaddir','userlist','banlist','autojoin','sharedfilesstreams','privatechat','chatrooms','maxresults']:
++ elif j in ['server','shared','uploadbandwidth','uselimit',"usecustomban",'uploadlimit','limitby','downloads','sharedfiles','sharedownloaddir','userlist','banlist','autojoin','sharedfilesstreams','privatechat','chatrooms','maxresults','preferfriends','useupslots','uploadslots']:
+ try:
+ self.sections[i][j] = eval(val)
+ except:
+@@ -95,9 +104,40 @@
+ except:
+ pass
+
++ def writeAliases(self):
++ f = open(self.filename+".alias","w")
++ cPickle.dump(self.aliases, f, 1)
++ f.close()
++
+ def writeShares(self):
+ sharesdb = [self.sections["transfers"]["sharedfiles"],self.sections["transfers"]["sharedfilesstreams"],self.sections["transfers"]["sharedindex"]]
+ f = open(self.filename + ".shares","w")
+- cPickle.dump(sharesdb, f)
++ cPickle.dump(sharesdb, f, 1)
+ f.close()
+
++ def AddAlias(self, rest):
++ if rest:
++ args = rest.split(" ", 1)
++ if len(args) == 2:
++ if args[0] in ("alias", "unalias"):
++ return "I will not alias that!\n"
++ self.aliases[args[0]] = args[1]
++ self.writeAliases()
++ if self.aliases.has_key(args[0]):
++ return "Alias %s: %s\n" % (args[0], self.aliases[args[0]])
++ else:
++ return "No such alias (%s)\n" % rest
++ else:
++ m = "\nAliases:\n"
++ for i in self.aliases.keys():
++ m = m + "%s: %s\n" % (i, self.aliases[i])
++ return m+"\n"
++
++ def Unalias(self, rest):
++ if rest and self.aliases.has_key(rest):
++ x = self.aliases[rest]
++ del self.aliases[rest]
++ self.writeAliases()
++ return "Removed alias %s: %s\n" % (rest, x)
++ else:
++ return "No such alias (%s)\n" % rest
+Files pyslsk-1.0.0/pysoulseek/config.pyc and slsk-tmp/pysoulseek/config.pyc differ
+Files pyslsk-1.0.0/pysoulseek/__init__.pyc and slsk-tmp/pysoulseek/__init__.pyc differ
+Files pyslsk-1.0.0/pysoulseek/mp3.pyc and slsk-tmp/pysoulseek/mp3.pyc differ
+diff -rNu3 pyslsk-1.0.0/pysoulseek/pysoulseek.py slsk-tmp/pysoulseek/pysoulseek.py
+--- pyslsk-1.0.0/pysoulseek/pysoulseek.py 2003-03-12 19:58:00.000000000 +0100
++++ slsk-tmp/pysoulseek/pysoulseek.py 2003-03-14 22:02:58.000000000 +0100
+@@ -5,6 +5,7 @@
+ """
+
+ import time
++import socket
+ import slskproto
+ import slskmessages
+ from slskmessages import newId
+@@ -101,6 +102,7 @@
+ slskmessages.PeerTransfer:self.PeerTransfer,
+ slskmessages.SharedFileList:self.SharedFileList,
+ slskmessages.GetSharedFileList:self.GetSharedFileList,
++ slskmessages.FileSearchRequest:self.FileSearchRequest,
+ slskmessages.FileSearchResult:self.FileSearchResult,
+ slskmessages.ConnectToPeer:self.ConnectToPeer,
+ slskmessages.GetUserStatus:self.GetUserStatus,
+@@ -576,9 +578,15 @@
+ pic = None
+ descr = eval(self.config.sections["userinfo"]["descr"])
+ if self.transfers is not None:
+- totalupl = self.transfers.getTotalUploadsAllowed()
+ queuesize = self.transfers.getUploadQueueSizes()[0]
+- slotsavail = not self.transfers.bandwidthLimitReached()
++ if self.config.sections["transfers"]["useupslots"]:
++ totalupl = self.config.sections["transfers"]["uploadslots"]
++ slotsavail = totalupl - self.transfers.activeUploads()
++ if slotsavail < 0:
++ slotsavail = 0
++ else:
++ totalupl = self.transfers.getTotalUploadsAllowed()
++ slotsavail = not self.transfers.bandwidthLimitReached()
+ self.queue.put(slskmessages.UserInfoReply(msg.conn.conn,descr,pic,totalupl, queuesize,slotsavail))
+
+ self.logMessage("%s %s" %(msg.__class__, vars(msg)),1)
+@@ -760,6 +768,12 @@
+ def FileSearch(self, msg):
+ self.logMessage("%s %s" %(msg.__class__, vars(msg)))
+
++ def FileSearchRequest(self, msg):
++ for i in self.peerconns:
++ if i.conn == msg.conn.conn:
++ msg.user = i.username
++ self.SearchRequest(msg)
++
+ def SearchRequest(self, msg):
+ maxresults = self.config.sections["searches"]["maxresults"]
+ index = self.config.sections["transfers"]["sharedindex"]
+@@ -791,7 +805,13 @@
+ results.remove(i)
+
+ queuesizes = self.transfers.getUploadQueueSizes()
+- slotsavail = not self.transfers.bandwidthLimitReached()
++ if self.config.sections["transfers"]["useupslots"]:
++ totalupl = self.config.sections["transfers"]["uploadslots"]
++ slotsavail = totalupl - self.transfers.activeUploads()
++ if slotsavail < 0:
++ slotsavail = 0
++ else:
++ slotsavail = not self.transfers.bandwidthLimitReached()
+ if len(resultsogg) > 0:
+ message = slskmessages.FileSearchResult(None, msg.user, msg.searchid,resultsogg,slotsavail, self.speed, queuesizes[1])
+ self.ProcessRequestToPeer(msg.user, message)
+Files pyslsk-1.0.0/pysoulseek/pysoulseek.pyc and slsk-tmp/pysoulseek/pysoulseek.pyc differ
+diff -rNu3 pyslsk-1.0.0/pysoulseek/slskmessages.py slsk-tmp/pysoulseek/slskmessages.py
+--- pyslsk-1.0.0/pysoulseek/slskmessages.py 2003-03-12 19:30:16.000000000 +0100
++++ slsk-tmp/pysoulseek/slskmessages.py 2003-03-14 23:51:46.000000000 +0100
+@@ -738,6 +738,21 @@
+ def makeNetworkMessage(self):
+ return ""
+
++class FileSearchRequest(PeerMessage):
++ """ We send this to the peer when we search for something."""
++ """ Peer sends this to tell us he is searching for something."""
++ def __init__(self, conn, requestid = None, text = None):
++ self.conn = conn
++ self.requestid = requestid
++ self.text = text
++
++ def makeNetworkMessage(self):
++ return self.packObject(self.requestid)+self.packObject(self.text)
++
++ def parseNetworkMessage(self,message):
++ len, self.searchid = self.getObject(message,types.IntType)
++ len, self.searchterm = self.getObject(message,types.StringType, len)
++
+ class FileSearchResult(PeerMessage):
+ """ Peer sends this when it has a file search match."""
+ def __init__(self,conn, user = None, token = None, list = None, freeulslots = None, ulspeed = None, inqueue = None):
+Files pyslsk-1.0.0/pysoulseek/slskmessages.pyc and slsk-tmp/pysoulseek/slskmessages.pyc differ
+diff -rNu3 pyslsk-1.0.0/pysoulseek/slskproto.py slsk-tmp/pysoulseek/slskproto.py
+--- pyslsk-1.0.0/pysoulseek/slskproto.py 2003-03-12 19:29:47.000000000 +0100
++++ slsk-tmp/pysoulseek/slskproto.py 2003-03-14 21:18:16.000000000 +0100
+@@ -79,7 +79,7 @@
+ Msg83:83,Msg84:84,Msg85:85,Msg86:86,Msg87:87,Msg88:88,
+ Msg89:89,Msg90:90,
+ AddToPrivileged:91,CheckPrivileges:92,CantConnectToPeer:1001}
+- peercodes = {GetSharedFileList:4, SharedFileList:5, FileSearchResult:9,
++ peercodes = {GetSharedFileList:4, SharedFileList:5, FileSearchRequest:8, FileSearchResult:9,
+ UserInfoRequest:15,UserInfoReply:16, FolderContentsRequest:36,
+ FolderContentsResponse:37, TransferRequest:40,
+ TransferResponse:41,PlaceholdUpload:42,QueueUpload:43,
+Files pyslsk-1.0.0/pysoulseek/slskproto.pyc and slsk-tmp/pysoulseek/slskproto.pyc differ
+diff -rNu3 pyslsk-1.0.0/pysoulseek/transfers.py slsk-tmp/pysoulseek/transfers.py
+--- pyslsk-1.0.0/pysoulseek/transfers.py 2003-03-11 16:00:34.000000000 +0100
++++ slsk-tmp/pysoulseek/transfers.py 2003-03-12 21:40:48.000000000 +0100
+@@ -332,15 +332,28 @@
+ return 1
+ return 0
+
++ def activeUploads(self):
++ uploads = 0
++ for i in self.uploads:
++ if i.conn is not None and i.speed is not None:
++ uploads = uploads + 1
++ return uploads
++
+ def bandwidthLimitReached(self):
+ maxbandwidth = self.eventprocessor.config.sections["transfers"]["uploadbandwidth"]
+- bandwidth = 0
++ maxslots = self.eventprocessor.config.sections["transfers"]["uploadslots"]
++ useupslots = self.eventprocessor.config.sections["transfers"]["useupslots"]
++ bandwidth = uploads = 0
+ curtime = time.time()
+ for i in self.uploads:
+ if i.conn is not None and i.speed is not None:
+ bandwidth = bandwidth + i.speed
++ uploads = uploads + 1
+ # self.eventprocessor.logMessage("%i %i " %(bandwidth, maxbandwidth),1)
+- if bandwidth > maxbandwidth:
++ if useupslots:
++ if uploads >= maxslots:
++ return 1
++ elif bandwidth > maxbandwidth:
+ return 1
+ return 0
+
+@@ -600,9 +613,16 @@
+ transfercandidate = None
+ list = [i for i in self.uploads if not self.userTransfers(i.user) and i.status == "Queued"]
+ listogg = [i for i in list if i.filename[-4:].lower() == ".ogg"]
++ if self.eventprocessor.config.sections["transfers"]["preferfriends"]:
++ userlist = [i[0] for i in self.eventprocessor.frame.userlist]
++ listfriends = [i for i in list if i.user in userlist]
++ else:
++ listfriends = []
+ listprivileged = [i for i in list if i.user in self.privilegedusers]
+ if len(listogg) > 0:
+ list = listogg
++ if len(listfriends) > 0:
++ list = listfriends
+ if len(listprivileged) > 0:
+ list = listprivileged
+ if len(list) == 0:
+@@ -620,16 +640,22 @@
+ if i.conn is msg.conn.conn:
+ user = i.username
+
++ userlist = [i[0] for i in self.eventprocessor.frame.userlist]
+ list = {user:time.time()}
+ listogg = {user:time.time()}
++ listfriend = {user:time.time()}
+ listpriv = {user:time.time()}
+ countogg = 0
++ countfriend = 0
+ countpriv = 0
+ for i in self.uploads:
+ if i.status == "Queued":
+ if i.user in self.privilegedusers:
+ listpriv[i.user] = i.timequeued
+ countpriv += 1
++ elif i.user in userlist:
++ listfriend[i.user] = i.timequeued
++ countfriend += 1
+ elif i.filename[-4:].lower() == ".ogg":
+ listogg[i.user] = i.timequeued
+ countogg += 1
+@@ -639,11 +665,14 @@
+ place = 0
+ if user in self.privilegedusers:
+ list = listpriv
++ elif user in userlist:
++ list = listfriend
++ place = place + countpriv
+ elif msg.file[-4:].lower() == ".ogg":
+ list = listogg
+- place = place + countpriv
++ place = place + countpriv + countfriend
+ else:
+- place = place + countpriv + countogg
++ place = place + countpriv + countfriend + countogg
+ for i in list.keys():
+ if list[i] < list[user]:
+ place = place + 1
+Files pyslsk-1.0.0/pysoulseek/transfers.pyc and slsk-tmp/pysoulseek/transfers.pyc differ
+diff -rNu3 pyslsk-1.0.0/pysoulseek/utils.py slsk-tmp/pysoulseek/utils.py
+--- pyslsk-1.0.0/pysoulseek/utils.py 2003-03-02 17:43:23.000000000 +0100
++++ slsk-tmp/pysoulseek/utils.py 2003-03-13 00:26:31.000000000 +0100
+@@ -5,8 +5,8 @@
+ """
+
+ import string
+-import os.path
+ import os,stat
++import os.path
+ import mp3
+
+ def getServerList(url):
+@@ -28,19 +28,28 @@
+ except:
+ return []
+
+-def getFilesList(dirs):
++def getFilesList(dirs, oldshares):
+ """ Get a list of files in dirs and subdirs with their filelength and
+ (if mp3) bitrate and track length in seconds """
+ list = {}
+ for i in dirs:
+- dircontents = getDirContents(i)
++ dircontents = getDirContents(i, oldshares)
+ for j in dircontents.keys():
+ list[j] = dircontents[j]
+ return list
+
+-def getDirContents(dir):
++def getDirContents(dir, oldshares):
+ """ Same as getFilesList, but only for one dir """
+ dir = dir.replace("//","/")
++
++ olddir = {}
++ if oldshares.has_key(dir):
++ for i in oldshares[dir]:
++ if len(i) == 5:
++ olddir[i[0]] = i
++ else:
++ break
++
+ list = {dir:[]}
+ try:
+ contents = os.listdir(dir)
+@@ -50,23 +59,30 @@
+ for f in contents:
+ pathname = os.path.join(dir, f)
+ try:
+- mode = os.stat(pathname)[stat.ST_MODE]
++ pathstat = os.stat(pathname)
++ mode = pathstat[stat.ST_MODE]
++ mtime = pathstat[stat.ST_MTIME]
+ except OSError, errtuple:
+ print errtuple
+ continue
+ else:
+ if stat.S_ISDIR(mode):
+ # It's a directory, recurse into it
+- dircontents = getDirContents(pathname)
++ dircontents = getDirContents(pathname, oldshares)
+ for j in dircontents.keys():
+ list[j] = dircontents[j]
+ elif stat.S_ISREG(mode):
+ # It's a file, check if it is mp3
+- list[dir].append(getFileInfo(f,pathname))
++ if olddir.has_key(f) and olddir[f][4] == mtime:
++ list[dir].append(olddir[f])
++ else:
++ list[dir].append(getFileInfo(f,pathname))
+ return list
+
+ def getFileInfo(name, pathname):
+- size = os.stat(pathname)[stat.ST_SIZE]
++ filestat = os.stat(pathname)
++ size = filestat[stat.ST_SIZE]
++ mtime = filestat[stat.ST_MTIME]
+ if name[-4:] == ".mp3" or name[-4:] == ".MP3":
+ mp3info=mp3.detect_mp3(pathname)
+ if mp3info:
+@@ -74,20 +90,20 @@
+ bitrateinfo = (mp3info["vbrrate"],1)
+ else:
+ bitrateinfo = (mp3info["bitrate"],0)
+- fileinfo = (name,size,bitrateinfo,mp3info["time"])
++ fileinfo = (name,size,bitrateinfo,mp3info["time"],mtime)
+ else:
+- fileinfo = (name,size,None,None)
++ fileinfo = (name,size,None,None,mtime)
+ elif name[-4:] == ".ogg" or name[-4:] == ".OGG":
+ try:
+ import ogg.vorbis
+ vf = ogg.vorbis.VorbisFile(pathname)
+ time = int(vf.time_total(0))
+ bitrate = vf.bitrate(0)/1000
+- fileinfo = (name,size, (bitrate,0), time)
++ fileinfo = (name,size, (bitrate,0), time, mtime)
+ except:
+- fileinfo = (name,size,None,None)
++ fileinfo = (name,size,None,None,mtime)
+ else:
+- fileinfo = (name,size,None,None)
++ fileinfo = (name,size,None,None,mtime)
+ return fileinfo
+
+
+@@ -141,3 +157,96 @@
+ d[x] = x
+ return d.values()
+
++def Humanize(number,fashion):
++ if fashion == "" or fashion == "<none>":
++ return str(number)
++ elif fashion == "<space>":
++ fashion = " "
++ number = str(number)
++ ret = ""
++ while number[-3:]:
++ part, number = number[-3:], number[:-3]
++ ret = "%s%s%s" % (part, fashion, ret)
++ return ret[:-1]
++
++def expand_alias(aliases, cmd):
++ def getpart(line):
++ if line[0] != "(":
++ return ""
++ ix = 1
++ ret = ""
++ level = 0
++ while ix < len(line):
++ if line[ix] == "(":
++ level = level + 1
++ if line[ix] == ")":
++ if level == 0:
++ return ret
++ else:
++ level = level - 1
++ ret = ret + line[ix]
++ ix = ix + 1
++ return ""
++
++ if not cmd:
++ return None
++ if cmd[0] != "/":
++ return None
++ cmd = cmd[1:].split(" ")
++ if not aliases.has_key(cmd[0]):
++ return None
++ alias = aliases[cmd[0]]
++ ret = ""
++ i = 0
++ while i < len(alias):
++ if alias[i:i+2] == "$(":
++ arg=getpart(alias[i+1:])
++ if not arg:
++ ret = ret + "$"
++ i = i + 1
++ continue
++ i = i + len(arg) + 3
++ args = arg.split("=",1)
++ if len(args) > 1:
++ default = args[1]
++ else:
++ default = ""
++ args = args[0].split(":")
++ if len(args) == 1:
++ first = last = int(args[0])
++ else:
++ if args[0]:
++ first = int(args[0])
++ else:
++ first = 1
++ if args[1]:
++ last = int(args[1])
++ else:
++ last = len(cmd)
++ v = string.join(cmd[first:last+1])
++ if not v: v = default
++ ret = ret + v
++ elif alias[i:i+2] == "|(":
++ arg = getpart(alias[i+1:])
++ if not arg:
++ ret = ret + "|"
++ i = i + 1
++ continue
++ i = i + len(arg) + 3
++ for j in range(len(cmd)-1, -1, -1):
++ arg = arg.replace("$%i" % j, cmd[j])
++ arg = arg.replace("$@", string.join(cmd[1:], " "))
++ stdin, stdout = os.popen2(arg)
++ v = stdout.read().split("\n")
++ r = ""
++ for l in v:
++ l = l.strip()
++ if l:
++ r = r + l + "\n"
++ ret = ret + r.strip()
++ stdin.close()
++ stdout.close()
++ else:
++ ret = ret + alias[i]
++ i = i + 1
++ return ret
+Files pyslsk-1.0.0/pysoulseek/utils.pyc and slsk-tmp/pysoulseek/utils.pyc differ
+diff -rNu3 pyslsk-1.0.0/pysoulseek/wxgui/about.py slsk-tmp/pysoulseek/wxgui/about.py
+--- pyslsk-1.0.0/pysoulseek/wxgui/about.py 2003-03-11 12:14:37.000000000 +0100
++++ slsk-tmp/pysoulseek/wxgui/about.py 2003-03-15 00:48:29.000000000 +0100
+@@ -7,7 +7,7 @@
+ from wxPython.wx import *
+ import images
+
+-version = "1.0.0"
++version = "1.0.0-hyriand-11"
+
+ class About(wxDialog):
+ def __init__(self, parent, id, title):
+@@ -59,6 +59,9 @@
+ "/whois /w user", "Request user info for user 'user'",
+ "/ip user", "Show IP for user 'user'",
+ "", "",
++ "/alias /al [command [definition]]", "Add a new alias",
++ "/unalias /un command", "Remove an alias",
++ "", "",
+ "/ban user", "Add user 'user' to your ban list",
+ "/unban user", "Remove user 'user' from your ban list",
+ "", "",
+@@ -66,6 +69,7 @@
+ "/pm user", "Open private to user 'user'",
+ "", "",
+ "/search /s query", "Start a new search for 'query'",
++ "/usearch /us user query", "Search a user's shares for 'query'",
+ "/away /a", "Toggles your away status",
+ "/quit /q", "Quit PySoulSeek",
+ ]
+@@ -78,10 +82,14 @@
+ "/whois /w [user]", "Request user info for user 'user'",
+ "/ip [user]", "Show IP for user 'user'",
+ "", "",
++ "/alias /al [command [definition]]", "Add a new alias",
++ "/unalias /un command", "Remove an alias",
++ "", "",
+ "/ban [user]", "Add user 'user' to your ban list",
+ "/unban [user]", "Remove user 'user' from your ban list",
+ "", "",
+ "/search /s query", "Start a new search for 'query'",
++ "/usearch /us query", "Search a user's shares for 'query'",
+ "/away /a", "Toggles your away status",
+ "/quit /q", "Quit PySoulSeek",
+ ]
+@@ -99,3 +107,49 @@
+ self.SetSizer(mainsizer)
+ self.SetAutoLayout(True)
+ mainsizer.Fit(self)
++
++class AboutFilters(wxDialog):
++ def __init__(self, parent, id, title):
++
++ wxDialog.__init__(self,parent,id,title)
++
++ sizer = wxStaticBoxSizer(wxStaticBox(self,-1,""),wxHORIZONTAL)
++
++ mainsizer = wxBoxSizer(wxVERTICAL)
++
++ ok = wxButton(self, wxID_OK, "OK")
++ ok.SetDefault()
++
++ sizer.Add(wxStaticText(self,-1,"""Search filtering
++
++You can use this to refine which results are displayed. The full results
++from the server are always available if you clear all the search terms.
++
++You can filter by:
++
++Included text: Files are shown if they contain this text. Case is insensitive,
++but word order is important. "Spears Brittany" will not show any "Brittany Spears"
++
++Excluded text: As above, but files will not be displayed if the text matches
++
++Size: Shows results based on size. use > and < to find files larger or smaller.
++Files exactly the same as this term will always match. Use = to specify an exact
++match. Use k or m to specify kilo or megabytes. >10M will find files larger than
++10 megabytes. <4000k will find files smaller than 4000k.
++
++Bitrate: Find files based on bitrate. Use < and > to find lower or higher. >192
++finds 192 and higher, <192 finds 192 or lower. =192 only finds 192. for VBR, the
++average bitrate is used.
++
++Free slot: Show only those results from users which have at least one upload slot
++free.
++
++To set the filter, press Enter. This will apply to any existing results, and any
++more that are returned. To filter in a different way, just set the relevant terms.
++You do not need to do another search to apply a different filter.""",style=wxALIGN_CENTRE), flag = wxALL, border = 5)
++
++ mainsizer.Add(sizer, flag=wxALL,border = 5)
++ mainsizer.Add(ok, flag = wxALL|wxALIGN_CENTER,border = 10)
++ self.SetSizer(mainsizer)
++ self.SetAutoLayout(True)
++ mainsizer.Fit(self)
+Files pyslsk-1.0.0/pysoulseek/wxgui/about.pyc and slsk-tmp/pysoulseek/wxgui/about.pyc differ
+Files pyslsk-1.0.0/pysoulseek/wxgui/buttonsplitter.pyc and slsk-tmp/pysoulseek/wxgui/buttonsplitter.pyc differ
+diff -rNu3 pyslsk-1.0.0/pysoulseek/wxgui/chat.py slsk-tmp/pysoulseek/wxgui/chat.py
+--- pyslsk-1.0.0/pysoulseek/wxgui/chat.py 2003-03-12 20:28:29.000000000 +0100
++++ slsk-tmp/pysoulseek/wxgui/chat.py 2003-03-14 22:46:55.000000000 +0100
+@@ -15,7 +15,8 @@
+ import string
+ from sortablelist import sortableListCtrl
+ from wxPython.wx import *
+-import locale
++import random
++from pysoulseek.utils import Humanize,expand_alias
+
+
+ def GetCompletion(part, list):
+@@ -247,7 +248,10 @@
+ def SayChatRoom(self,msg,text):
+ self.CreateRoomWindow(msg.room)
+ self.joinedrooms[msg.room].SayChatRoom(msg,text)
+- self.frame.OnPageUpdated(self.parent)
++ if text.find(self.frame.np.config.sections["server"]["login"])>=0:
++ self.frame.OnPageUpdated(self.parent, True)
++ else:
++ self.frame.OnPageUpdated(self.parent)
+
+ def UserLeftRoom(self,msg):
+ self.CreateRoomWindow(msg.room)
+@@ -374,15 +378,15 @@
+ sendmessageID=wxNewId()
+ self.menu.Append(sendmessageID, 'Send Message')
+ EVT_MENU(self,sendmessageID, self.OnSendMessage)
+- showipID=wxNewId()
+- self.menu.Append(showipID, 'Show IP address')
+- EVT_MENU(self,showipID, self.OnShowIp)
+ getinfoID=wxNewId()
+ self.menu.Append(getinfoID, 'Get User Info')
+ EVT_MENU(self,getinfoID, self.OnGetInfo)
+ browseID=wxNewId()
+ self.menu.Append(browseID, 'Browse Files')
+ EVT_MENU(self,browseID, self.OnBrowse)
++ showipID=wxNewId()
++ self.menu.Append(showipID, 'Show IP address')
++ EVT_MENU(self,showipID, self.OnShowIp)
+ addtolistID=wxNewId()
+ self.menu.Append(addtolistID, 'Add to User List')
+ EVT_MENU(self,addtolistID, self.OnAddToList)
+@@ -431,12 +435,12 @@
+ return username
+ elif col == 1:
+ if not sort:
+- return locale.format("%s",self.parent.users[username].avgspeed,1)
++ return Humanize(self.parent.users[username].avgspeed,self.parent.frame.np.config.sections["ui"]["decimalsep"])
+ else:
+ return self.parent.users[username].avgspeed
+ elif col == 2:
+ if not sort:
+- return locale.format("%s",self.parent.users[username].files,1)
++ return Humanize(self.parent.users[username].files,self.parent.frame.np.config.sections["ui"]["decimalsep"])
+ else:
+ return self.parent.users[username].files
+ else:
+@@ -533,13 +537,20 @@
+ text = self.frame.np.encode(self.mychatphrase.GetLineText(0))
+ if len(text) == 0:
+ return
++ result = expand_alias(self.frame.np.config.aliases, text)
++ if result is not None:
++ text = result
+ s = text.split(" ", 1)
+ cmd = s[0]
+ if len(s) > 1:
+ rest = s[1]
+ else:
+ rest = ""
+- if cmd in ("/join", "/j"):
++ if cmd in ("/alias", "/al"):
++ self.chat.AppendText(self.frame.np.config.AddAlias(rest))
++ elif cmd in ("/unalias", "/un"):
++ self.chat.AppendText(self.frame.np.config.Unalias(rest))
++ elif cmd in ("/join", "/j"):
+ if rest:
+ self.queue.put(slskmessages.JoinRoom(rest))
+ elif cmd in ("/leave", "/part", "/l", "/p"):
+@@ -581,12 +592,17 @@
+ elif cmd in ("/search", "/s"):
+ if rest:
+ self.frame.np.search.DoSearch(rest)
++ elif cmd in ("/usearch", "/us"):
++ if rest:
++ l = rest.split(" ", 1)
++ if len(l) == 2:
++ self.frame.np.search.DoSearch(l[1], l[0])
+ elif cmd == "/slap":
+ import random
+ if rest:
+- msg = "/me slaps %s with a %s" % (rest, random.choice("a large trout", "a dictionary", "a rubber duck", "a copy of Windows XP", "a glove", "an empty bottle", "a lawsuit", "a ddos", "google", "a herring"))
++ msg = "/me slaps %s with a %s" % (rest, random.choice(("a large trout", "a dictionary", "a rubber duck", "a copy of Windows XP", "a glove", "an empty bottle", "a lawsuit", "a ddos", "google", "a herring")))
+ self.queue.put(slskmessages.SayChatroom(self.room, msg))
+- elif cmd[0] == "/" and cmd != "/me":
++ elif len(cmd) > 0 and cmd[0] == "/" and cmd != "/me":
+ wxLogMessage("Command %s is not recognized" %(text))
+ else:
+ self.queue.put(slskmessages.SayChatroom(self.room, text))
+@@ -655,15 +671,18 @@
+
+ if text[:4] == "/me ":
+ str = "%s * %s %s\n" %(time.strftime("%X"),msg.user,text[4:])
+- color = "FOREST GREEN"
++ color = self.frame.np.config.sections["ui"]["chatme"]
+ else:
+ str = "%s [%s] %s\n" %(time.strftime("%X"),msg.user,text)
+ if msg.user == self.frame.np.config.sections["server"]["login"]:
+- color = wxBLUE
++ color = self.frame.np.config.sections["ui"]["chatlocal"]
++ elif text.upper().find(self.frame.np.config.sections["server"]["login"].upper()) >= 0:
++ color = self.frame.np.config.sections["ui"]["chathilite"]
++ self.parent.OnPageUpdated(self, True)
+ else:
+- color = None
++ color = self.frame.np.config.sections["ui"]["chatremote"]
+
+- if color is not None:
++ if color is not None and color != "":
+ self.chat.SetDefaultStyle(wxTextAttr(color))
+ self.chat.AppendUserText(self.frame.np.decode(str,wxUSE_UNICODE),msg.user,color)
+ self.chat.SetDefaultStyle(wxTextAttr())
+@@ -723,9 +742,14 @@
+ timestamp = self.np.encode(time.strftime("%c",time.localtime()))
+ if text[:4] == "/me ":
+ str = "%s * %s %s\n" %(timestamp,msg.user,text[4:])
+- color = "FOREST GREEN"
++ color = self.np.config.sections["ui"]["chatme"]
+ else:
+ str = "%s [%s] %s\n" %(timestamp,msg.user,text)
++ if text.upper().find(self.np.config.sections["server"]["login"].upper()) >= 0:
++ color = self.np.config.sections["ui"]["chathilite"]
++ else:
++ color = self.np.config.sections["ui"]["chatremote"]
++ if color == "":
+ color = None
+ self.users[msg.user].AddText(self.np.decode(str,wxUSE_UNICODE),color)
+ self.np.frame.OnPageUpdated(self)
+@@ -819,10 +843,12 @@
+ timestamp = self.parent.np.encode(time.strftime("%c",time.localtime()))
+ if text[:4] == "/me ":
+ str = "%s * %s %s\n" %(timestamp,username,text[4:])
+- color = "FOREST GREEN"
++ color = self.parent.np.config.sections["ui"]["chatme"]
+ else:
+ str = "%s %s\n" %(timestamp,text)
+- color = wxBLUE
++ color = self.parent.np.config.sections["ui"]["chatlocal"]
++ if color == "":
++ color = None
+
+ if len(text) > 0:
+ self.AddText(self.parent.np.decode(str, wxUSE_UNICODE), color)
+@@ -833,6 +859,9 @@
+ def OnEnter(self, event):
+ """ Sends our chat phrase and updates the window."""
+ text = self.parent.np.encode(self.mychatphrase.GetLineText(0))
++ result = expand_alias(self.parent.np.config.aliases, text)
++ if result is not None:
++ text = result
+ s = text.split(" ", 1)
+ cmd = s[0]
+ if len(s) > 1:
+@@ -844,7 +873,11 @@
+ truerest = ""
+ else:
+ truerest = rest
+- if cmd in ("/away", "/a"):
++ if cmd in ("/alias", "/al"):
++ self.chat.AppendText(self.parent.np.config.AddAlias(truerest))
++ elif cmd in ("/unalias", "/un"):
++ self.chat.AppendText(self.parent.np.config.Unalias(truerest))
++ elif cmd in ("/away", "/a"):
+ self.parent.np.frame.OnAway(event)
+ elif cmd in ("/quit", "/q"):
+ self.parent.np.frame.Close()
+@@ -867,7 +900,10 @@
+ elif cmd in ("/search", "/s"):
+ if truerest:
+ self.parent.np.search.DoSearch(truerest)
+- elif cmd[0] == "/" and cmd != "/me":
++ elif cmd in ("/usearch", "/us"):
++ if truerest:
++ self.parent.np.search.DoSearch(truerest, self.user)
++ elif len(cmd) > 0 and cmd[0] == "/" and cmd != "/me":
+ wxLogMessage("Command %s is not recognized" %(text))
+ else:
+ self.SendMessage(text)
+Files pyslsk-1.0.0/pysoulseek/wxgui/chat.pyc and slsk-tmp/pysoulseek/wxgui/chat.pyc differ
+diff -rNu3 pyslsk-1.0.0/pysoulseek/wxgui/configwindow.py slsk-tmp/pysoulseek/wxgui/configwindow.py
+--- pyslsk-1.0.0/pysoulseek/wxgui/configwindow.py 2003-03-12 03:11:16.000000000 +0100
++++ slsk-tmp/pysoulseek/wxgui/configwindow.py 2003-03-12 21:44:20.000000000 +0100
+@@ -10,6 +10,7 @@
+ import os,stat
+ from pysoulseek import mp3
+ from pysoulseek import utils
++import time
+
+ from wxPython.wx import *
+
+@@ -116,6 +117,46 @@
+ self._list.sort()
+ self.listbox.Set(self._list)
+
++class ColourPicker:
++ def __init__(self, parent, id, title):
++ self.button = wxButton(parent, -1, title)
++ self.textctrl = wxTextCtrl(parent, -1, size=wxSize(125,25), style=wxTE_READONLY|wxTE_RICH|wxTE_MULTILINE)
++ self.button2 = wxButton(parent, -1, "Default")
++ self.parent = parent
++ self._value = ""
++ EVT_BUTTON(parent, self.button.GetId(), self.OnClick)
++ EVT_BUTTON(parent, self.button2.GetId(), self.OnClear)
++
++ def OnClick(self, event):
++ colour = wxColourData()
++ colour.SetColour(self.textctrl.GetValue())
++ dlg = wxColourDialog(self.parent, colour)
++ if dlg.ShowModal() == wxID_OK:
++ colour = dlg.GetColourData().GetColour()
++ if colour:
++ colourname = wxTheColourDatabase.FindName(colour)
++ if colourname:
++ self.SetValue(colourname)
++ else:
++ self.SetValue("#%2x%2x%2x" % (colour.Red(),colour.Green(),colour.Blue()))
++ else:
++ self.SetValue("")
++ dlg.Destroy()
++
++ def OnClear(self,event):
++ self.SetValue("")
++
++ def GetValue(self):
++ return self._value
++
++ def SetValue(self, value):
++ self._value = value
++ self.textctrl.Clear()
++ style = self.textctrl.GetDefaultStyle()
++ self.textctrl.SetDefaultStyle(wxTextAttr(value))
++ self.textctrl.AppendText(value.capitalize())
++ self.textctrl.SetDefaultStyle(style)
++
+ class ConfigWindow(wxDialog):
+ """
+ This class defines a settings window that the main application should
+@@ -135,10 +176,12 @@
+ serverpanel = wxPanel(nb, -1)
+ transferspanel = wxPanel(nb, -1)
+ userinfopanel = wxPanel(nb, -1)
++ uipanel = wxPanel(nb, -1)
+ miscpanel = wxPanel(nb, -1)
+ nb.AddPage(serverpanel,"Server")
+ nb.AddPage(transferspanel,"Transfers")
+ nb.AddPage(userinfopanel,"Personal info")
++ nb.AddPage(uipanel,"Interface")
+ nb.AddPage(miscpanel,"Miscellaneous")
+
+ self.serverctrl = wxTextCtrl(serverpanel,-1,size=wxSize(250, 25))
+@@ -165,7 +208,10 @@
+
+ self.downloaddirctrl = wxTextCtrl(transferspanel,-1,size=wxSize(250, 25))
+ self.uploaddirsctrl = wxListBox(transferspanel, -1, size=wxSize(250,100))
++ self.upload_use_width = wxRadioButton(transferspanel,-1,"upload speed exceeds ",style=wxRB_GROUP)
++ self.upload_use_slots = wxRadioButton(transferspanel,-1,"number of uploads exceeds ")
+ self.uploadbandwidth = wxTextCtrl(transferspanel,-1,size=wxSize(30, 25))
++ self.upslots = wxTextCtrl(transferspanel,-1,size=wxSize(30,25))
+ self.downloaddirchoose = wxButton(transferspanel, -1, "Choose...")
+ self.sharedownloadctrl = wxCheckBox(transferspanel, -1, "Share download directory")
+ self.uploaddiradd = wxButton(transferspanel, -1, "Add...")
+@@ -175,6 +221,7 @@
+ self.uploadlimit = wxTextCtrl(transferspanel, -1, size=wxSize(30,25))
+ self.limittransfer = wxRadioButton(transferspanel, -1, "per transfer", style=wxRB_GROUP)
+ self.limittotal = wxRadioButton(transferspanel, -1, "total for all transfers")
++ self.preferfriends = wxCheckBox(transferspanel, -1, "Let users in my list download first")
+
+ EVT_BUTTON(self,self.downloaddirchoose.GetId(),self.OnDownloadChoose)
+ EVT_BUTTON(self,self.uploaddiradd.GetId(),self.OnUploadAdd)
+@@ -182,6 +229,8 @@
+ EVT_BUTTON(self,self.uploaddirrescan.GetId(),self.OnUploadRescan)
+ EVT_CHECKBOX(self,self.sharedownloadctrl.GetId(),self.OnShareDownload)
+ EVT_CHECKBOX(self,self.useuploadlimit.GetId(),self.OnUseUploadLimit)
++ EVT_RADIOBUTTON(self,self.upload_use_width.GetId(),self.OnUploadChoose)
++ EVT_RADIOBUTTON(self,self.upload_use_slots.GetId(),self.OnUploadChoose)
+
+ downloadsizer = wxBoxSizer(wxHORIZONTAL)
+ downloadsizer.Add(self.downloaddirctrl)
+@@ -197,9 +246,14 @@
+ uploadsizer.Add(uploadbuttonssizer)
+
+ bandwidthsizer = wxBoxSizer(wxHORIZONTAL)
++ bandwidthsizer.Add(self.upload_use_width)
+ bandwidthsizer.Add(self.uploadbandwidth, flag=wxLEFT, border=10)
+ bandwidthsizer.Add(wxStaticText(transferspanel, -1, " KBytes/sec"),flag=wxALIGN_CENTER)
+
++ upslotssizer = wxBoxSizer(wxHORIZONTAL)
++ upslotssizer.Add(self.upload_use_slots)
++ upslotssizer.Add(self.upslots)
++
+ limitsizer = wxFlexGridSizer(cols=3, rows=2)
+ limitsizer.Add(self.uploadlimit, flag=wxLEFT, border = 10)
+ limitsizer.Add(wxStaticText(transferspanel, -1, " KBytes/sec "), flag=wxALIGN_CENTER)
+@@ -214,10 +268,12 @@
+ transferssizer.Add(self.sharedownloadctrl,flag=wxLEFT|wxTOP, border = 10)
+ transferssizer.Add(wxStaticText(transferspanel, -1, "Shared directories:"),flag=wxTOP|wxLEFT, border = 10)
+ transferssizer.Add(uploadsizer, flag=wxLEFT|wxRIGHT, border = 10)
+- transferssizer.Add(wxStaticText(transferspanel, -1, "Locally queue uploads if total upload speed exceeds:"),flag=wxTOP|wxLEFT, border = 10)
+- transferssizer.Add(bandwidthsizer,flag=wxLEFT|wxBOTTOM, border = 10)
++ transferssizer.Add(wxStaticText(transferspanel, -1, "Locally queue uploads if:"),flag=wxTOP|wxLEFT, border = 10)
++ transferssizer.Add(bandwidthsizer,flag=wxLEFT, border = 10)
++ transferssizer.Add(upslotssizer,flag=wxBOTTOM|wxLEFT, border = 10)
+ transferssizer.Add(self.useuploadlimit, flag=wxLEFT, border = 10)
+ transferssizer.Add(limitsizer, flag=wxLEFT|wxBOTTOM, border = 10)
++ transferssizer.Add(self.preferfriends, flag=wxLEFT|wxBOTTOM, border = 10)
+
+ self.descr = wxTextCtrl(userinfopanel,-1,size=wxSize(250,100),style = wxTE_MULTILINE|wxTE_RICH)
+ self.pic = wxTextCtrl(userinfopanel,-1,size=wxSize(250, 25))
+@@ -234,6 +290,33 @@
+ userinfosizer.Add(wxStaticText(userinfopanel, -1, "Picture:"),flag=wxTOP|wxLEFT, border = 10)
+ userinfosizer.Add(picsizer,flag=wxLEFT|wxBOTTOM, border = 10)
+
++ self.colourchatremote = ColourPicker(uipanel, -1, "Remote text:")
++ self.colourchatlocal = ColourPicker(uipanel, -1, "Local text:")
++ self.colourchatme = ColourPicker(uipanel, -1, "/me text:")
++ self.colourhighlight = ColourPicker(uipanel, -1, "Highlight colour:")
++ self.coloursearchnoqueue = ColourPicker(uipanel, -1, "Without queue:")
++ self.coloursearchqueue = ColourPicker(uipanel, -1, "With queue:")
++ self.decimalsep = wxComboBox(uipanel, -1, style=wxCB_DROPDOWN|wxCB_READONLY, choices = ["<none>", ",", ".", "<space>"])
++
++ uisizer=wxStaticBoxSizer(wxStaticBox(uipanel,-1,"Colours:"),wxVERTICAL)
++ uisizer.Add(wxStaticText(uipanel,-1,"Chat colours:"), flag=wxLEFT|wxTOP, border = 10)
++ chatcoloursgrid = wxFlexGridSizer(cols=3, vgap=2, hgap=5)
++ for i in self.colourchatremote, self.colourchatlocal, self.colourchatme, self.colourhighlight:
++ chatcoloursgrid.Add(i.button, flag=wxEXPAND)
++ chatcoloursgrid.Add(i.textctrl)
++ chatcoloursgrid.Add(i.button2)
++ uisizer.Add(chatcoloursgrid, flag=wxEXPAND|wxLEFT, border=15)
++ uisizer.Add(wxStaticText(uipanel,-1,"Search result colours:"),flag=wxLEFT|wxTOP, border=10)
++ searchcoloursgrid = wxFlexGridSizer(cols=3,vgap=2,hgap=5)
++ for i in self.coloursearchnoqueue, self.coloursearchqueue:
++ searchcoloursgrid.Add(i.button, flag=wxEXPAND)
++ searchcoloursgrid.Add(i.textctrl)
++ searchcoloursgrid.Add(i.button2)
++ uisizer.Add(searchcoloursgrid, flag=wxEXPAND|wxLEFT, border=15)
++ decimalsepsizer = wxBoxSizer(wxHORIZONTAL)
++ decimalsepsizer.Add(wxStaticText(uipanel, -1, "Decimal separator:"), flag=wxALIGN_CENTER|wxRIGHT, border = 5)
++ decimalsepsizer.Add(self.decimalsep)
++ uisizer.Add(decimalsepsizer, flag=wxTOP|wxLEFT, border=10)
+
+ self.logsdirctrl = wxTextCtrl(miscpanel,-1,size=wxSize(250, 25))
+ self.logsdirchoose = wxButton(miscpanel, -1, "Choose...")
+@@ -271,6 +354,8 @@
+ transferspanel.SetAutoLayout(True)
+ userinfopanel.SetSizer(userinfosizer)
+ userinfopanel.SetAutoLayout(True)
++ uipanel.SetSizer(uisizer)
++ uipanel.SetAutoLayout(true)
+ miscpanel.SetSizer(miscsizer)
+ miscpanel.SetAutoLayout(True)
+
+@@ -345,7 +430,7 @@
+ shared.append(self.encode(self.uploaddirsctrl.GetString(i)))
+ if self.sharedownloadctrl.GetValue():
+ shared.append(self.encode(self.downloaddirctrl.GetValue()))
+- self.sharedfiles = utils.getFilesList(shared)
++ self.sharedfiles = utils.getFilesList(shared, self.sharedfiles)
+ self.sharedfilesstreams = utils.getFilesStreams(self.sharedfiles)
+ self.sharedindex = utils.getFilesIndex(shared,self.sharedfiles)
+
+@@ -365,12 +450,21 @@
+ if dir is not None:
+ self.logsdirctrl.SetValue(dir)
+
++ def OnUploadChoose(self,event):
++ if self.upload_use_width.GetValue():
++ self.upslots.Enable(false)
++ self.uploadbandwidth.Enable(true)
++ else:
++ self.upslots.Enable(true)
++ self.uploadbandwidth.Enable(false)
++
+ def SetSettings(self, config):
+ server = config.sections["server"]
+ transfers = config.sections["transfers"]
+ userinfo = config.sections["userinfo"]
+ logging = config.sections["logging"]
+ searches = config.sections["searches"]
++ ui = config.sections["ui"]
+ if server["server"] is not None:
+ self.serverctrl.SetValue(string.join([str(i) for i in server["server"]],":"))
+ if server["login"] is not None:
+@@ -408,6 +502,16 @@
+ self.usecustomban.SetValue(transfers["usecustomban"])
+ if transfers["customban"] is not None:
+ self.customban.SetValue(transfers["customban"])
++ if transfers["preferfriends"] is not None:
++ self.preferfriends.SetValue(transfers["preferfriends"])
++ if transfers["uploadslots"] is not None:
++ self.upslots.SetValue(str(transfers["uploadslots"]))
++ if transfers["useupslots"] is not None:
++ if transfers["useupslots"]:
++ self.upload_use_slots.SetValue(true);
++ else:
++ self.upload_use_width.SetValue(true);
++ self.OnUploadChoose(None)
+ if userinfo["descr"] is not None:
+ self.descr.SetValue(eval(userinfo["descr"]))
+ if userinfo["pic"] is not None:
+@@ -420,6 +524,20 @@
+ self.loggingchatctrl.SetValue(logging["chatrooms"])
+ if searches["maxresults"] is not None:
+ self.maxresults.SetValue(str(searches["maxresults"]))
++ if ui["chatremote"] is not None:
++ self.colourchatremote.SetValue(ui["chatremote"])
++ if ui["chatlocal"] is not None:
++ self.colourchatlocal.SetValue(ui["chatlocal"])
++ if ui["chatme"] is not None:
++ self.colourchatme.SetValue(ui["chatme"])
++ if ui["chatme"] is not None:
++ self.colourhighlight.SetValue(ui["chathilite"])
++ if ui["search"] is not None:
++ self.coloursearchnoqueue.SetValue(ui["search"])
++ if ui["searchq"] is not None:
++ self.coloursearchqueue.SetValue(ui["searchq"])
++ if ui["decimalsep"] is not None:
++ self.decimalsep.SetValue(ui["decimalsep"])
+
+ def encode(self, str):
+ import locale,types
+@@ -455,6 +573,11 @@
+ except:
+ uploadlimit = None
+ try:
++ uploadslots = int(self.encode(self.upslots.GetValue()))
++ except:
++ uploadslots = None
++ useupslots = self.encode(self.upload_use_slots.GetValue())
++ try:
+ maxresults = int(self.encode(self.maxresults.GetValue()))
+ except:
+ maxresults = None
+@@ -475,11 +598,21 @@
+ "usecustomban":self.encode(self.usecustomban.GetValue()), \
+ "customban":self.encode(self.customban.GetValue()), \
+ "uploadlimit":uploadlimit, \
++ "uploadslots":uploadslots, \
++ "useupslots":useupslots, \
+ "limitby":self.encode(self.limittotal.GetValue()), \
++ "preferfriends":self.encode(self.preferfriends.GetValue()), \
+ "sharedownloaddir":self.encode(self.sharedownloadctrl.GetValue())},"userinfo": \
+ {"descr":self.encode(self.descr.GetValue()).__repr__(), \
+ "pic":self.encode(self.pic.GetValue())},"logging":{ \
+ "logsdir":self.encode(self.logsdirctrl.GetValue()), \
+ "privatechat":self.encode(self.loggingprivatectrl.GetValue()), \
+ "chatrooms":self.encode(self.loggingchatctrl.GetValue())},
+- "searches":{"maxresults":maxresults}}
++ "searches":{"maxresults":maxresults}, \
++ "ui":{"chatremote":self.encode(self.colourchatremote.GetValue()), \
++ "chatlocal":self.encode(self.colourchatlocal.GetValue()), \
++ "chatme":self.encode(self.colourchatme.GetValue()), \
++ "chathilite":self.encode(self.colourhighlight.GetValue()), \
++ "search":self.encode(self.coloursearchnoqueue.GetValue()), \
++ "searchq":self.encode(self.coloursearchqueue.GetValue()),
++ "decimalsep":self.encode(self.decimalsep.GetValue())}}
+Files pyslsk-1.0.0/pysoulseek/wxgui/configwindow.pyc and slsk-tmp/pysoulseek/wxgui/configwindow.pyc differ
+diff -rNu3 pyslsk-1.0.0/pysoulseek/wxgui/frame.py slsk-tmp/pysoulseek/wxgui/frame.py
+--- pyslsk-1.0.0/pysoulseek/wxgui/frame.py 2003-03-10 17:42:25.000000000 +0100
++++ slsk-tmp/pysoulseek/wxgui/frame.py 2003-03-15 00:42:13.000000000 +0100
+@@ -21,6 +21,7 @@
+ import buttonsplitter
+ from wxPython.wx import *
+ from configwindow import *
++from types import StringType
+
+ class NetworkEvent(wxPyEvent):
+ """
+@@ -123,6 +124,9 @@
+ aboutPrivateID = wxNewId()
+ helpmenu.Append(aboutPrivateID, '&Private chat commands', 'About private chat commands')
+ EVT_MENU(self,aboutPrivateID, self.OnAboutPrivateCommands)
++ aboutFiltersID = wxNewId()
++ helpmenu.Append(aboutFiltersID, '&Search filters', 'About search filtering')
++ EVT_MENU(self,aboutFiltersID, self.OnAboutFilters)
+ helpmenu.AppendSeparator()
+ aboutID = wxNewId()
+ helpmenu.Append(aboutID, '&About', 'About PySoulSeek')
+@@ -295,6 +299,9 @@
+ def OnAboutPrivateCommands(self, event):
+ about.AboutCommands(self, -1, "About Chat Commands", True).ShowModal()
+
++ def OnAboutFilters(self, event):
++ about.AboutFilters(self, -1, "About Search Filters").ShowModal()
++
+ def ConnectError(self,msg):
+ self.mainmenu.Enable(self.connectID,1)
+ self.mainmenu.Enable(self.disconnectID,0)
+@@ -373,8 +380,8 @@
+ break
+
+
+- def OnPageUpdated(self, pageobj):
+- self.nb.OnPageUpdated(pageobj)
++ def OnPageUpdated(self, pageobj, hilite=False):
++ self.nb.OnPageUpdated(pageobj, hilite)
+
+ def callback(self,msgs):
+ """ Callback function called by networking thread."""
+Files pyslsk-1.0.0/pysoulseek/wxgui/frame.pyc and slsk-tmp/pysoulseek/wxgui/frame.pyc differ
+diff -rNu3 pyslsk-1.0.0/pysoulseek/wxgui/images.py slsk-tmp/pysoulseek/wxgui/images.py
+--- pyslsk-1.0.0/pysoulseek/wxgui/images.py 2003-02-20 14:08:48.000000000 +0100
++++ slsk-tmp/pysoulseek/wxgui/images.py 2003-03-12 21:31:36.000000000 +0100
+@@ -1,5 +1,5 @@
+ #----------------------------------------------------------------------
+-# This file was generated by ./encode_bitmaps.py
++# This file was generated by encode_bitmaps.py
+ #
+ from wxPython.wx import wxImageFromStream, wxBitmapFromImage
+ from wxPython.wx import wxEmptyIcon
+@@ -8,150 +8,289 @@
+
+ def getBirdData():
+ return zlib.decompress(
+-"x\xda\x01'\x0e\xd8\xf1\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00@\x00\
++'x\xda\x01~\x1c\x81\xe3\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00@\x00\
+ \x00\x00@\x08\x06\x00\x00\x00\xaaiq\xde\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\
+-\x08d\x88\x00\x00\r\xdeIDATx\x9c\xe5[+p#\xc7\x16=I5\xb8@\xa0\x81\xc1\x00\x83\
+-\x01\x06\x03\x02\x06\x18\x0c0\x10\x08\x10X \xb0@\xe0\x01\x83\x05\x06\x0f\x18\
+-\x1a\x04,\x080\x0c\\\x10`\xb0@`\x81\xc1\x02\x83\x05\x06\x0f\x08,\x100\x18\
+-\xb0`\x80\xc1\x00\x83\x01\x06\x17LU\x1e\xe8\xdf\xed\x9e\x96,o\x92\xaa\xd4{\
+-\xb7J\xf6h>\xdd\xf7{\xeeG\xd2\x0f\x00\xfe\xc0\xff1)\xf3\x97\xcc\xbb\x91\xc5%\
+-\nw\x8c\xc9\xff\x1c\x8d\xd1\x8a\xf19\xb7\xc7\xc8\xf9u\xe4}\x87q\x1c\x9e;d\
+-\x1d%\xafqt\xee\x07X\x0f\xd0E\x89\xa2(\x02\xb3d\x14@\x8a\xec&\xe6?9eM6\xd9q\
+-\xfe@rO{\x13x\xa5s\xf6~O\xc9u\x1e\xa3Ub\xa5\x8c\x0c\x8c\x8c\xfe\xb1G\xff\xd8\
+-\x01`\xe7\x01\x80.\n\x94?\xd5\xa0\x99\x06\x91Q\x80\x11\xde\xfe'2Lz\x85\x08e\
+-\x08\xe1I\x99\xedI\xb2\xa1\x00\xb2\x02\xf1\x18\xde\xbb\xfb\"\x01$\xd3R\xb81\
+-\xb6\\\xb8\x8f#!\xd9\xdd'\xae\xf1h\xef\x19\x19426\x9b{\xab\x80 \x8a\xf9;\xd3\
+-\x00i`F\xc1\x0b\xac\x12\x00\x02+\xe9\x11\x00\x0b\xef\xf0\x16t\xd7<C\xd6\x99x\
+-0\xe7(\xac\x85\x91\xc1\xd6\xb3\"\xa1\xad\x12\xa3\xd0L<\x8c\xe1\xc2\x89\x12O!\
+-q\x873\x03\x81\x15\x83F2\xca\x10k)\xb7\x01)\xf2\x96\x07\xe9`u+<\x12\xe1\x91X\
+-\xdfo\x9f`\x04)\x86&\xa0i\x1a\x00\xc0\xc0\xc0\xb6\xed\xc0\xcf\xec\xbd\x89\
+-\xd9pB L\x1c>\x13\xe7\xecB\x12\xc2\xdaJ\n\x9e\xf1\xad\x11V\t\x88\xae\xfd\x18\
+-8%\xef\xfeq\x08X\xe6\xc8\x1d\x93g\xdc\x85\x00\xa9\xf0\n\xd7\xc9\xdb\xe3\xf6\
+-\xe3\x1a\xf3\x93\x12\xf3\xaa\xc4\xf6\xeb\xc6Z\x8bA\x8a\xa3\xe7\xbdU\xdd\x9e\
+-\xfe8x\xa1;o\xde\xb9\xb0\x8c_\x04\xf2\xf7\x00\xf0\xbc\x12(\xbe7\xfc3\xc2\x92\
+-\"0i\x1f\xf7D\xc1\xca\x91\xf5\x11\x94A\xd9\xcc \x04\x03P\x1ekl,\x005U\tm\xc1\
+-\x96a<\xa2\xfd\xd6c\x18\x06\xb3_\x04Z\xc1\xa3\xd8e\x90\xc8\xb2\x0c\xef7\xf69\
+-V\xd63F\x02\xd4\x14@)\xf1\x8c\xc0\xbaty\xd2\x89\xe6\x85%@\xb1\xc0\xf2\xba\
+-\x8d:\xc3<\x1b\x05\xd8\xff\xee\xdc\xa2\xae\xa2\xf5~\xfbt\x07\x1em\xe8IW\x96)\
+-\xd3\xed#A\xd1\xc7\xbftw\x16k8\xe5 \x80\xa6\x9a\xae\xeb\xb3\x80\xf3\x80\x00|\
+-\xb0\xc8/\xdc\xc6\x81\xa5\xf26\x8e\xb5k7\xd6D\xa8OJh\x82\t\xf0\xa1\xf3\x0cEq\
+-\x0b\xa0>)\x00]\x80\x01l\x1ez\xf03\x92\x98N\x05\x0b\xca\xf4<\t\x00$\xc56\xd3\
+-\xb8\xf3\xc1\x13\xc8\x03m\xacG\x7fD32.4#\xeb~$\x16\xf6b\xda8\x8bD\x0fG#c}s\
+-\x83\xcb\xf5\x07`\x1c\xa2\xbb\xe2\x82\xc4\x081\xafk\xeb\t\xb7 \xe8\x90*\xa3B\
+-\x8c<^D\x1e\x01!,\x84\x12\xd2\xf3\xa3\xc4\x8a\xd8\x83\xe3\x10P\x1ad1 \xb8|\
+-\x82\xfeB\t\xb1p\xae\xd2#_p8!\xa3\xeb)\xf1\x00\x10\xa1:.@G\x05\x86\x11h\xbf\
+-\r\x18\x9e\xad\xf5F\x18\x0b\x8eq\xa8\xc1*ij\xf1\xcc\xf9([\xc4\xdb\xff(\xdf\
+-\x90s\x7f\x84\x02(\xa0\xbfS\x12\x19\xe3L\x10\x1a\xf6\x19\x80\x99\xd1,\x16X.\
+-\x97R\xd2\x0cX\xbaK\x06\x1b\xe6'%\xfao\x9dO\xcb^\xe0\x04\xb9\xa5\xc7\xf9\xd8\
+-\x06\x10c\x14E\xd9\"d\x16\x82\xacf\x13\x0f\x08\x18@\xb9\rS\x14\xce\x08\xc4 ,\
+-\x96K\xe0y\xc0\xf6\xcbm`h\xdc\xa1\x80\xa4\x08b\x1e\x00\xa5\x03OQ\xde\xce\xac\
+-\xe1=\x83\x83\x85\x1d8\xcaL0)\x98\x0c\x19\x0fp\xe5\xa9(\x7f\xa5'\xb8W\\\x17\
+-\xc0\xe7\xe4P\x1a\x9b\xf8\xd5\xc7\x15\x8a\xaaA\x0f\x8d\xe6\xcd\x12\xcb\xb7+\
+-\xf8}\x12\xea\xba\x1e\xddc\xef\xafir.k2\nE\x1e \x95\x1f\x0cE>\xf7KC!\x18L\
+-\x1a\xd3\xc9%o\x81/\x12\x8c{\xfb\"\"JwV\xc8D\x80\xb4\x02$\n\x1b,\xde\x9e\x03\
+-O\x1d\xfao\xdb\xa9\xe4r]\xd1\xf8\x94\xc7\x1a\xa5.\xc0#\xb0m\x07SJ\x8f\xa6\
+-\x94\x8db8M\x8fv\x1d\x12\xeb\xf1(\xd6w<\x8e\xec\xcb\xf5XD\x17\xebp\n`+`\x0e\
+-\xf0v\x08#\x9b\x12\x00\xc5q\t*4\xba\xae\xdd\t\x82\xa5\xeb@-\xcd\xab\nP\x84\
+-\x9b\xfbm\x08\x01\x05\x80)\x08g\x8b\xdd\xc0\x8fQ8\x8fl\r\xc9V\x19\x1c\x9a/\
+-\x98\x8cl*B/\xb4ok\x82\x86\x88\xa05\xa1<2\x1bv\xbd\x01\xb5a4\xd6\xe5Q<\xa1Dn\
+-M\x15\xe3\x18w\xa1sh\xcf\xef3\x89\xac7l\xc8\xb9\x0e2\xad\x07\xec~\x9a\xcc\
+-\xfb\xaem1\x9f7(\x8e4\xd86\\Z\x13\xee\xef7\xe0A\xaa\x8eE\x16P\xc6\x8d4\x18\
+-\x9b\xcfk,NJ\xcc\xcb\x12\xf7\xb77`\x1e\x82\x8b{4\x85\x8f\xd1\xe8\x85p\x1e\
+-\xf6\x98w\x01\xe0^r\xfd\x82\xb0\xb7\xc5\x1e\xaf|\xb1\x97)\x87\x19\x18\x07l\
+-\xbf\xde\xe1|\xd1`Q\x97X\x9eVX5\x15\xf8i\xf0kHR2\xb6\xc9\x95\xb3\x0e-G\xc6\
+-\xfc\xb4BY\x15\xe8z\xd3e\xf1\xe8\xf0\x81\xe2\xde=\xac\xe8\x89l\xe5\xd7\xf5\
+-\xbdS\xc5\xc1\xc2\x97\x85\x06\xcf4\x18@\xf7\xc8\x18\\\x85hs\xbc\x8c\xeb\xf6a\
+-\x83\xe5\x9b\x85\xe1\xfby\x00\x9f\xd6\xd8~^G\xde\x14\xf71\x12\x04}\x1a\t\xd5\
+-\x92T\xc0\xf9\x9b\x05@\x1a\x17\xbf^\xa3>\x9bC+\x8a\xfa\xec8\x88\x822\xfd\xb9\
+-\xd1l\xd8,\x16\xd0\x04\xdc}ZO\xe5U\xc0\xc5\xe5\x15\xba\xae\xc3\xdd\xada|^\
+-\x95\x00i\xdcl\xb6F\xe9Q\x9c\xc3\xf7\x1a\xe0\x01\xed\xd7\r\x96\xef/\xed\xa6C\
+-\xc2\x8dP\x16\x91\xed\xbd\xa3\xad\x11\x152\x13\x94\xb5\x8b\xd4'%\xea\xb2\xf0\
+-\xe8\xda\x0f\xc00\xc2\xf7\xe7\x94 \xaeT\xccru\x0e~\xdc\xa2ks\xd9\xc0\xec\xdd\
+-4s@m\x84\xfa\x000\xa3\x7f\xec@G\xa5\xe7\x9b\x00t\xdf\xb6\x987\r\x8a\x19\xc0\
+-\xcf\xc0\xbc\xa9\xb0\xb9e\xb8\xb0\x89h\x04@\x84a\x18\xb28\x14`F\x85\xbdC\xe7\
+-d\xdf=3.\x96\x0b\x91W\t\x97\xd7\xbf\xa1>\x9b{\xe5y\xc0\x12!\xe0\xa6:\xba(\
+-\xa0\x8b9\xda\xb6\xc54\x14\xac'\xcd\x08\x98i\xac.\xafP\x14\x05\xf8i@\xd7\xf7\
+-\xe8\xbeu\xa8H\x9b>\x01\x00\x14c\xf3\xe5\x16\x1f.\xcf\xcd\xb3n\xdc\x95)r\x00\
+-\xe0\xf2\xfd56\x0f-\xee\xbe\xdc\xa3\xac\xea\xb8\x96@\n\t\xf6]\xf7\xd8\x85\
+-\xb4\xb5c>\xd7\xd4\x15\xcaB\xc7!`\x95\xc6\xb0M \xc7\x1e\x97\xf6\xe2\x92\xaa\
+-\xb2ByR\x81\x99\xfd`s\xb3\xd9\xa29\x9b\x9bA\rl^\xe7\x01\x8b\xa6\xc6\xf6\xf6&\
+-\xe1)]\xd1\x18\xaa\x9e/\x80\xa3\xca\xb4\xf8\xe3TI*s\x04f\x84\x98]\x8b\x98\
+-\x15\x13\xd7\xd5Y\xb3\xa3\xb45\x1b_\x7f\\\xa38\xa9\x8c\xc8D\xc03\xfb\xeaq\
+-\xc2\xec\xc8h\xaa\xc20m\x1e\xc0\xfa\xcb=\x86\xbe\xc7\xb2.3\x02\xbe\x00\xa6\
+-\x8ap\xf9\xcb{l\xb6\x1d\xee7[TU\x83\x81]\xaa\xdf\xeb\x01\x06\x03\x96oW\xc0\
+-\xd0\xa3{\xd8\xe0\x0e\x19\xd0rL\xec\xc9\xed\xd5q\x01\xad\xb5I[6\x0e\xcb\xb2|\
+-\xa1\x1e\x08k\x96Ea<\xec\xa5\xb1\xf8\x84Lrl~^\x82\x8a\x0e\xacB\xed\x02 \x93\
+-\x06\xc5\x91c\xb6(K\xd0h+\xb8W\x93\x11b\xd9$\x1e\xe2\x059D 3:{}\xed@\xb8\xfa\
+-\xf5\x1a\xf7_[\xdc\xfdg\x8b\xaa\xae\xc1\x8c\xcc\x80$<1\xdd\x82\x08\x033\xb4\
+-\"\xb4]\xbf\xb3\x84\xddI.L\x9c\xab\xbf\xda\x82\x083\x80W=jc\xfel\x01\x1c\xd7\
+-\x00i3j\xf3YM\x80\xbd\xa0P\x1e\xb8,02\xb4&\x03F`4\xcb%\xb4\"\xdc\xado^)\xc4w\
+-\x08\x0e\x08\xd0}\xc53\x8a\x0c\xdao[\xdc\x7fmQ\xd5\x8dI\xd1\x92\x0f9k\xcc\
+-\x8e\xc4\x1c\x11\xf9\x0f0\x96\xe7\xe7\xa0a\xc0\xed\xc7\x9bx\x81\x7f\x12Y\xd0\
+-m\xe6\x0bPY\x03\xb3\xc2N\x861\x1d\x86\xe6\x1e\xcf\x9e\xb5\x8d\x87\xc9\xdf\
+-\x05x\xa6\xbf\xdf\x13\xfeV\n1\x7f\xbfiQ\x9d6`X\xd0\x13\xa5r4c\xdc\x89\x01\
+-\xb6\x04f\x15\xe6\xb743\x8b\xad..\xc0\x8f=\xee?\xad_\x8f\t\x7f\x1b\x19A\xaa\
+-\xb3\x05\xa8j\x00\xa5\xad\xe5\xa3B\x1c\xbb>\x1fp\x14c\x80\xe8\xf4\xfc\x12\n(\
+-\xca\x028*\xc0\x9fo1\x7f\xbb\x04!\xa9\x0fr\xab\xbe&\x86\x0f%E\xb8z\x7f\x8d\
+-\xfe\xc9\x0cJ\xba\xbe\xc7\xfd\xd7\x16\xf5\xd9\xdc\x16\\\xd3\xb6\xdbf\x7f\xec\
+-BT%\xfe\":\x16\x13\x96a\x04\xf4\x0cX^\x9c\x03}\x8f\xfb\xcfw\xa2\xa4\xb4\xc3\
+-\x072\xc0\t\xe0\xaf\x17^\x8c\xe7\xeb\x9f\x17\xe8\x9f\xcc\xd4\xb8\x02\x01G\
+-\xb6\x80\x8a&K\xf0\x9e\xeaC 2J:\x12\x13\xef\x08\xb6\xb9\xb1\xef\x9d\x17\xf0\
+-\xe8&<%\xfa\xf5\x1a\xe5i\x83\xc2\x16:}\xd7\xa1}h_\xd5\xf0\x1e.\xbcy\xd5\xcd\
+-\x1c=\x03\xdd\xd3\x80\xe2\xb8\n\xa5\xb1r\x8a\x7fI\t\xa6\x8d&\xb1&\xc6D\x01N\
+-\xf8\xc997c\xb3\x0f\x95\xa7\r\xf4Q\x01b\xf6\x1f7\xb7\x0f\xadh\x93\xffzZ\xbd\
+-\xbb\xc0\xe6\xa1E\xff\xc4\xa0c\xf8\xaa\x95\xbd\xf7\x05\x85\xd1\x18x\xcd\x91,\
+-\x86\x8d\xb8i\xaeL\x1f\x90}\x82\x02\x96\xe7+\xd3\xfe\x8e\x00\x8d\x8c\xeb_\
+-\xde\x1b\x8f8\xd2\xd1P\xb2\xef:\xb4\xdbm\xd8\xe9\x90\xd0\xb0{\xd5\xcd\x1cZk0\
+-\x08\xddc\x0f\xd2\x05\x96\xab\xc6\x94\xb6V\x04_[\xe6Z\xf1\xc8\xdd3m\xb2=\x9f\
+-M\x83\x949N\x1bX\x17&<\x02\xe5i\r:\xd2\xa6\x8br\xb3\x01\x0e\x9e\xf1=\xf5\xfc\
+-\xf9\xc5%6\x0f[0\x03\xc5O5\xba\xa7\x01u)\xa69\xa3\x80\xb6\xd4\xf5\x1d\t\x1ew\
+-\x01\xe1D\x01\xb9n}\x17\xb12\xee\xb7x\xbb\x8c&6Z\x99\xee\xef\xfa\xea\nU]\x83\
+-F\xc6v\xbbg4.\xa8>\x9b\xa3\x7f\x06x\xa6\xb1zw\tV\xc0\xc0\x0c\"\r\x1e\x11\x1a\
+-\x1b\x07\xd2;\x94\x10\xd9\xddaZ~ \x12\xd4@\xfek%Se\xa4\xc1\xe1\x15\xa3\xc2\
+-\xe8\xdcu}\x03\x1b\xc5T\xa7\x8d\xf9\xa0\xe3y0\nx)\x0c\x14au~\x81\xf6\xb1G\
+-\xcf\x8c\xc2\x81\xf0L\x9b\xa6F\xf0*\xdd]\xe2\xd4\xfe0\x9b\x86\xb8\x8a\x16K\
+-\x84\xcf?2=/'\xc6\x0c\x80m%\xb9\xf8\xd7\n\x1a+|\xb8\xbe\xb6\xad\xe6n\x7f\xaa\
+-\xe7sS\xbf\xcf4\x96\xef\x96\xe6X\nD\x16\xdcr\xad\xed\x98x\xae\x18\xf4\xfa\
+-\xb9\xa4o\xd0d%Hy\x0c\xf8\xd3d\x99\x1c\x9e\x07_#\xec\xfd\x0e\x9f\"\xac\xde]`\
+-\xfb\xad\xc7\xa0\xc8W\xa3\xfe\xba[3;\xaf\xcc\xacwp\x1d2\x01A\xf6c\xab?\x93\
+-\xd0\xc8\x02\xe1\xfa\xf7\x1bp\xdf\x83\x9fz\xf8\x19\x7f\xc2\\\xdd\x04\xcb\xaf\
+-\xfe\xbd\xb0(/\xc07\xb9\x7f\x92\xaa\x13\xc5\xbc\x96\xef\xbd\x1e\xb0{\x82\x17\
+-(\x9b\\\x9e\x06\x93\t\x86\x01\xb7\xbf\x7f\xf0\xd9!\x15\xc6Y\xbe\xed{\x0c\n`\
+-\xdb\x89\xfa\xcf\x1e\x1c\x87I8\xe42\xd3\x84\xdf\x14$\xf7f\x01_\xff\x1f\"r\
+-\xb2\xa9c\x8e\xc3\xa6\xb7\xeb5\xfa\xb6\x03\x86!\xc4}R\xa1U\xa7\xb5\xb1\xb6\
+-\xd6X\xbd]b\x80\xb0\xac[o\x14\x16Os|.\xf5\x89\xf2=\xca\x16\x8e2\xb3\x86\x00\
+-\x17B[\xafR\x832\x16\xd7.\r2\x83\xfb\x1ew770\x1fR \x02\x1dw|qy\x85\xedc\x07\
+-\xcc(vi\x85\xe97\xcfv\x08?\x01>W\xb1\xe6\xc2f7\xfb\xdfAv#\x97[\xd7\x9fn\xd1?\
+-\xb4\xd0v\xb5\xa171/W\xafO\x1b\xe8\xa2\x04\x03\xe8{\x03v\xe7\x17\x17`\x02\
+-\x06\xbb\x8e\xfc04\x12\xc0u\xa7\x19\x8b\xbfT\xf6\x02n\xcd\x03\x07\"\xbbr\xbf\
+-|\xdc\x95\xbb<\x18\x0b\x1b\x8b\x7f\x083\xc0\x0cC\xab\x8b\x0b\xdf\xc62\x00:\
+-\xd2\xa6\xc8qB\t\xf7\xf5\xfbe*\xbb\x14\xf4r\xe1\x91\xb7\xf8>\x0c\xc8\x94\xaa\
+-i\x18D\xe1a7\xb9\xfdt\x8b\xf6a\x0b\xe2\xcc\x88<J_\x84\xb2\xaa0\xafj0\x85\n\
+-\x8d\x91X\xdd\t\x9f\x9aE\n\xba\x0b\x0f\x10\xce\xcb\xe7\xe5t\xc8\x9f\xcb\xb09\
+-\x152C\x12\x99\t@\xfb\xad\xc5\xdd\xc7\xb5\x00\xba\x04\xf0\xdc\x0e\xca~JD\x88\
+-s<v\xb8\xbc\x14bG\x8d?\x1dy\x89\xf3/\xd5\x01S\x10\xdcM9\xc7\x91\rF\xf9S\x8d\
+-\xe2\xa8\x80\xc9\xf3\xa6\xff\xee\x1f\xcd|\x00p\xb5=\xa3\x1f\x06\x94\x07\xae\
+-\x1f1)\xddzg\xc7\x97P\x922Y\xc1\x18I~}\xcf\xee\xba\x13\x03\xd2\x8e0b\xd2.\
+-\xbe|\xb3@{R\x82\xc4\x1d\xcc\x8cv\xb3\xf1\nX\xaeV\x18\x14\xc2\x8f1\x12\xdaY\
+-\xb4d\x04\xdc\x95\xff_\xa4\x89\xa2B\xc8\xca\x04\xb5\x97&E\x06\x80\xfa\xacA}\
+-\xd6\x84s\xd6RW]\x87\xd2~\x03\x14G\x1a\xcb\xc5\xc2\x0fT\xf2]\xf9\x8e\xfd\x9c\
+-{'1\x9ev\x80i\x85\x19U\x91R\xf8\x11\xe1\xc7\x13\xb1({\x84\xddG\x12\xb1\x05\
+-\xc3\xf37\x0b\xd4?U\x80\"\x14e\t\xd0\xfe\xcc\xb2\xd7\xb2\xa9\xdb[A^[\xf3\xbb\
+-\xcf\x08(y\xe6/k\x86$@\xce\xe7s\xb7\xed\xd4\x8a\xaf\xa5\x9c\x909\xe1sY\xc2\
+-\xf1\xe0\xd2\xf3\xc8\xe1'5\x96\xa2\xaf\xcaJ\"\x1c\xee\t\x93\xfb\x94pQ \xfe\
+-\xf1\xc2\x9e\xe7\xb2\xf7\xecS\x9c\xab\x1f2\xcf\xf2\x18\x9f\x0f\x8a\xf0\xef\
+-\x00\xa4\xdf\x15VS\xc1_\x9a\x0b\xece.\xa1?\xa3\x04\xaf\x88\\\x89\x0c!\x92\
+-\x152\xfa\xa1\xc5\xe4\xb5\x97\xcd<\xfd=\xb3\xde\xc3\xf79tL\xe7\xefq\xd6v/7\
+-\xc1\xe68\x0cv\x86\x80$J^\xc0wx\xc4\x01\xf4\xa2`\xa9\x17\xe4\xcc'2M\x98\x1a\
+-\xdb\xba\x93\xed\x97\xb1\xc5\xf7\x16\xe3%\xf64$\x92\xf6)\xe1\xc5>b\xc7\xbd\
+-\xb9\nt\xf2\xdc\x0e$\x87\xbd\x06\xd8Y\xc2\xe8\xfe[\x81y0]*\xdb\xf6<\x17\x02\
+-\x04\x98\x8bD\xfb\x81\xe7\x05\xca\x16MJV\x8f,\xee\xa5Xa\xae\xc9r\xe7<\xaa\
+-\x87\xbb\\\x1e\x8f]\xdc\n5\x12\x88\xad\xc5\xd9\xca3Z\x05<\xbb\xff\xbd\xf5\
+-\x02\x00\x8a\xa0\xdc\xa6\xf7_\xee\xf3S\x9b\x94\xe4\x0f\x14\xf7*\xca~\xc15\
+-\xfa^o\xac\x00g\xf3W\xe1\x8bS\x90\x1fn8\x8bR\xb8\xe6\x940\"\x08\xcb\x83\xc7\
+-\x00\xf7\xb3Y\x80\xeco\x87\xd3\xae\xea\x1fM\x07\xa2O\xfa\xb1x\xa4|\xf2\x9e\
+-\xf5\x03)\xfcq\xd8L\xfd\x7f\x93\xfe\x0b\x0bS\xa9=J\x9e\xec\xb4\x00\x00\x00\
+-\x00IEND\xaeB`\x82\x88\x05\xbc\xdc" )
++\x08d\x88\x00\x00\x1c5IDATx\x9c\x8d\x9b{\xb0eWq\xde\x7f\xddk\xed}\xce\xbds\
++\x19\x8d\x06i\xf4FHHc!L$!!\x10\x8c\x80\xf0\xb0\x11\xc2\x10 @\x01\x81\xaa\x84\
++\x10\xe1\x98r\xe2\x84\xbc\\\xce\xcb\t\x15\xdb\xa9\xa4*\xe5J\x95S\x15\x13cl\
++\x10\x81\x80A`W\xe2` \x01\x02\xc2\x06$\x01\x12`$\xac\x91\xd0\xa0\xe70\x8f{\
++\xcf9{\xad\xee\xfc\xd1k\x9fs\xeeHJ\xe5TM\xdd9\xaf}\xd6\xea\xd5\xfd\xf5\xd7_\
++\xf7\x16\xd1\xde\xdd\x1d0\x00\xd4\xe3/(\x00.\xbb\x9f#\t\xb7\xf15@|\xd7\xfb"\
++\x82\xbb\xa3\xaa`\x8e\x8a\xe3\xee\x98+(\xa8\x03\x18Fm\xdf\xef\xda\xef\xc6Ss_\
++]Z\x04#\x9e\'Q\xdc\x1d\x97\xdd?km\xbd\x82\xb4\xbf\x86\x88\xb4\xb5\x13\xdfq_\
++\xaeO\xb5]\xd7\n"\x82 \x9d\xa7\x94p\x15\xac\xb6E\xb9\xc4?i\xdfp\x07I\xab\xbf\
++\xb2\\!\x8c\xc6X\xael|\x13p\xc0k\xbc\xa6)>+\xb4\xeb\xb4\x1dH\x18\x8a\xe5\xbe\
++%\xdesY3\xee\xdac\xd7\xf5}\xb5>\x80Zv\xbf?\x1e\x9e\x08X\\O\xbc\x02NR\xa8u \
++\xabBu\xe3\xcc3\xce\xe5\x8c\xb3\xcf\x01\xc9x\xca\xf4\xfd\x144\x93\xbb\x1eI\
++\x8aHBU\xc9\xa9\x8fk\x89 \x92\xc0\x15\x10T\xb5\xbd\xb6Z\x80\xcb\xe8W\x8f\x7f\
++4\xffZ\xee\xb1\xf9\x17\x86`V\x11\x0b\xdb\xb8\xd7\xb6W_z\x965\xa3\xc7k\xab\
++\xf7q\xc7\xcc\xf0Z\x96\x06\x18\xea0^\x18\xa9\x95l\x0b\x1e\xb8\xef/8r\xdf\x0f\
++I\x92\xc9\xa3\x85\xf6\x1f8\x87K/\xbf\x92\x8d\xbd\xfb\xd0~\x83~\xbaE\xea{\xba\
++n\x82\xe6\x9e\xdcMH)\x93s\xc656\x9c\xb4C\xb5\x07I$QP\tW];DS\x01)$\x0f\xd7\
++\xac\x06\xae\x89d\xb1h\x19W7\x9adt\xd9j\xb8\xd7xN\xc5\xab\xc5\xeb\x1a!\xe6\
++\xd503\xccJ\x84\x98\x19\xe2F\xad5\xbeg\xf1\xfdZ\x07j\xadX\xa9P\n]\xd9\xe6\
++\xd6/}\x8e\x1f\xdd\xf7C\xc0\xc9n\x05IS\x10ec\xebt\x98\xec%\xedy\n\xf4\x1b0\
++\x99bi\x82\xe4\t\x9ezjN\x90\x12\x92\x144\xe3\x9aI)\xe3\x92"\xc6\x93\xe2\xa2\
++\x98DLW*\xd5\x0b\xd3\x0e\x18NP\xcd\xb0\xe9\x16\x95\x0c\x08\xc3PH\xa2\xcbS\
++\x1d7\x02\xe0\xc9\x81\xf1T\x0b\x96l\x97\x81Lc\xe3b\xed\xd4\xad\xc4\xff\xd50\
++\xaf\xcd@\x85*s\\\x8c*\x15\x95\x05&\x02\xfd&\xe6\x82J\n\x0fp34O\xc9\x1b[\xb0\
++\xb9\x85L\xb7\xd0~\x83\xd4OIy\x82\xa6\t\x9a;\x92v\x90@R\x86\xd4\x81$LS\x03\
++\xab\x08\x81"\xab3\x85J/\xca\xe6D8\xf4\x82+\x10\x81\x87v\xe0\x9b\xdf\xf9s\
++\xe6\xdb;\xa4\xae\x03M,\x06\xc3TI)QJ\x18B\xbd\xb9\xbf{\x18X\xac\x81W3\x94t\
++\x888\xb5\x94p7\xedp\xa0\x96\x05f\x05\xb0\xf0>\x03\x93\x8a{\xc12\xb8/"\xb6$\
++\xe1^G\xe8\x86n\xd2\xb3\xb1\xf5\x14\xba\xc9&\xfdt\x0f\xb9\x9f\xd2\xf7\x13r\
++\xee\xe9R\xa6\xeb:r\xa7\xe4\xdc\x93s&\xa5D\xce\x19UE5\x93\x92\xa0\n\xa9SR\
++\xa7\xe4\xacL\x922\xa1\xf2\xe9\x0f\x7f\x80C\xcf8\x8f\x17\x1e\xbc\x90;\xff\
++\xf4\xff\x90\xebIzf\xf4y iE\xb4\x92\xb3bT4\x0b\xa9S$\xcb\xf2\xfa\xf1\x9b}\
++\xfb\x9d\xf8\xdd>e\x12\x89\xac\x89\x94\xc2x\xaaJ\xa7\x89>erJt)\x91\x05\xb2\
++\x80v\xf1]m!\x1c\x00id\x91\x84\xa3h\x9e\xd0\xf5\x1b\xe8d\x0b\xe97\xc8\xa9\
++\xa7\xeb\xa7\xa8d4gr\xea\x91\x9c\x10I\x88*\x92;\x10\xc5]@\x12"\x0ebaQ\x89x\
++\xec)lf\xb8\xe8\xbc3\xf8b\x9dA\xa9\\\xfdS\x17\xb0\xef\xecs\x01\xa1"<\xba\x03\
++w\xfe\xf9a\x1e=~\x92\xac\x1dq\xe8\x8e\x88\xb6\x13\x1a\xcf\xc8\xc0\x1d%^7\x94\
++$\x15\x17\xc7]\x1a\x068\x96\x13R\x03Y\xdd\x1cK\x19\x11%\x9b\xe0n(\xdd.0\xce\
++\x8e\x02J\xdfM\xe9\xbbMJ\xb7A\x9an!\x9a\xd1.,\x8f\n\xa2\x1d\xa4LJ]\x80\\R\
++\x0c\x05\xcfa\x14q\x94\x82\xc8@\xaf\xe0eF/\x03b\x03]\xaaa\xf1a\x87\x9f\xb9\
++\xea\xd2\xa5\x0b\x927\xf8\xcd\x8f\xff!\x8b\xda\xd3\xf5\x13\x165\x914\x91$S\
++\xeb\x80\xa18c\x1e\x97\x95A\\P\x0c\x17AE0\x8f\x9c\x8e\x19B\xc6\x04\xa8\x86\
++\x8a\x90\x89\x10(^Q\xed\xf0\xe2\xa4\xd4R\xba;\xd9q\x10%\xe5)\xddd\x82N\xf7 \
++\xdd\x14I\x1d\xb9\x1f\xe3>\xdc\x07\tw\x17U*N\x92\x84\x93\x97\xa9OD\xe8\xc5\
++\xd8;Q~\xfa\x92g\xb2\x7f\x8f`\xb3\x19<z/P\xc9\xaa\xb8-\xe2dD\x01\xe3\x8ag\
++\x9c\x87\x9ev\x16\'U\xf8\xfa\x1d\xf7\xb1s\xa2Rk\x05M\x08\t\xc7\x10\xaf\x88@\
++\xb21\xbd8BA\xdc\xc3\x085\xbc\xce\xb4\x91\x1b\x03<(\x94\xb8#.\x88\n\xeau\x95\
++\xbaM\x10\x87\x8cy0\xb4\x9c\xe866\xa1\x9f\x9266\xc1\x15\xcd=\x9a\x12\x82\x92\
++r\x86\xc6\x05L\x94,\x12\x0bt\x07M\xb8(\x99\x84\x96m>\xfa\xbb\xff\x85\xf7|\
++\xf8?A\xddn\x0b\xb6`\x98*\xd8\xda&\xa8s^t\xc5\xe50\xd9\xe2?~\xe4\x93t\xb2IM\
++\x99\x99)\x9e\xa6\x98\x0bNA\x97\'\xee\x88\x1b\x8e\xb7\xac\xd9\x00\x11k\xfc)\
++\xa1\x1e\xd9\xc3\x1d\xbcy(h\\\xc3+)\xf5\xbb\xb8\xca\x92\x07\xc4\x89o\xc2t\
++\x13\xf24\xf2|\xee\x10\x11R\xea\xd0\x94p\x8d\r\xab\xa4]\xa4\xc75\xe1\x92\x11\
++[0I\x13l1\x83:\x90l\x1e\xf1j\x86\xa4\x0e\xf3\x95;+\x8e`\xd8\xe2$\x8e\xf1\xcc\
++\xf3\xcf`\xe3\xc0\x05\x1c]\xc0]w?\xca#\xc7\x16\xb8$\x12\x82S\x83\xe2z\x05\
++\x14\xb1\x16\xe3.@E4\x07F\x188\x8e\xaac\x8da\x84\x07\rA\xcd=\xf8\xcb.\x0c\
++\x08\xca\xeeHJ\xf4\xd3=X\xb7\x81\xa7\x9e\xd4\x90\x9e\xd4\xe2U\x83\r&m1\x06K#\
++8B\x15E\xb4C<\xb33T\x0e\xfd\xcc+9\xb0\x01\x9f\xfc\xf8Gb\xf1\x18"\xe0"\r\xccF\
++\xde>P\x17\xf0\xd2\xe7<\x1b\xfa\xbd\xfc\xd6\xa7?K\xf1M&\x93)en\xa8\x06\x0e\
++\x18 &\xe0\x15\xd7\xd8\xbc\x8b4\x10nP\xa9}\x10!@)\xabt\x9c\xba\x16r\x05\xed{\
++R?\x01\x14G\xc9>\xf2h\xed\xd1\xae\'M6\xb04%w\xe1\xee.\xa0\x12q\x1e\x1e hj\
++\xc8\x9c\xb4\xf1{A\xd1@az^\xf1s\xaf\xa7?\xf9\x10\xb7~\xf6\xd3T2]\x97\xa8\xa5\
++\x84A\xdb\xb2\x02\xe5\xc3\x88J\x05\n\xd5f\xd8p\x02\xc9\x19qe\xd2ux\x01!\x83\
++\x18\x9e\xea\x12\xdc\xdc#\x03U\x1a\xd5\xf7\x8a:\x88\x18hnL\xb44o\x08L\x90\
++\xa4\x8c5X\xd4$c\x08\xb8\xa0\xa9\xa3\xeb7\xa9\xdd\x06E{\xc8\x82\x8a\xe2\x1a\
++\x9c_P4\xe9\x92\x8e\xaaj\x10X\x8d\xf3U\x0fD\x9e-\x943/\xbc\x8c\r\x9e\xc1\xc3\
++\x7f\xfc9^x\xc3\x1b9\xb0\xa5|\xf2\xa3\x1ff\x18\xe6A\x95\x11\x8a\x83#\xdc\x7f\
++\xef\x8f\x10I\x9c\x7f\xfe\x05\x90*[\xd3\xcc\xbc\xcc\x10\x87N\x14\xeb:\xaa\
++\xb5\x90\x17Z|;0\xba}Pci\xfc;a-%*\xae\t+\xdej\xba\x84x\x0e\x10\x97\xd4<`\x89\
++\x01\tI\t\xed{\xb4\xcbt\xb9\xa3O\xb5\x15V\x82\x01)g\x0c\x1f\x13S\xb8\xb2@m\
++\xa0\xa3"\xb8@\x9eLq&\x14*\xafz\xd3;\xa9\x8f\xdc\xcd\x03\xdf\xfd:fN\x972\xd5\
++\xdbU\xdc\x11\x12\xaa\x99Zk\x18\xb2\xcc\xb8\xf8\x9c3\xb8t\xdf\xd9\x1c3\xe1\
++\xb6;\x1fb6\x08\x1a\xdb\x02\xab \xb5\x85]\x84\x81\xea\x8a\x17`58\n\x16\x86\
++\xaa@\x1aq\xa1\x15\xcc\xd5\x97!\x1c<\xc0\xc3\x17\xba\xc9F\xd0C1\xba\xce\x98\
++\xb0\x00\xa0JO\xee2\x95\x8a\xb8\x93d,r\xa04\xde\x1eu\xb5\x839\xf1?an\xc2\x19\
++\xe7_\xc8\xd69\xa7s\xf8\x07w\xa2\xfd\x04\x1f\xb6\x11I\x98[\x03#\xe7\xdcs\xcf\
++\xa6!\x1a"\x89C\x97\x1d\x84\xbc\xc9\x07\xfe\xd77I\xa9#\x990 P\x05\xd5\x0c\
++\x94(\x8e<\xe1\x1eP\xa7I\xc3\xfb\xca\tzUT\xa0\xda\x02W\xa3\x96J\xea\x14\x17\
++\xa7\xeeT\xfa\x14\x94=N7\x11\xc1"\x16\x1b\xef\x954\xc9\xec;m\xca\x85\x07\xf6\
++\xd0\'\xb8\xe7\xc8\x9c\x93\x8b\xc2\xf1\xc1\x99L\xa6X-8\xc2\xc0\x10\xcc\xd0\
++\xad\xc5^\xc4\xb7\x12Y\xce\x10\x9c\x84iO\xea7\xc2\x15\xd7\x04\x8d\xd8se\xbd\
++\x10\xc6A\xeb\x02\xcb\x13(\x0b\xba\xdc\xb33\x14T\x13\xf4\x1dX\xa5:\x88dp%\
++\x89cu\x1e8\xe4\xce\x9eiB\xca\x8c{\xbe\xf7m^\xf1\x92\x17p\xe0\x8c}\xcc\x87\
++\xca\x02a\xcf\xde\xd3\xf8\xd2\xff\xfe\n\xf3\xc7\x16ki\xb0\xa2!\xc5T\x92V\xfa\
++\\\xd9\x90\x1d\xbe\xf0\x99\x8f\xf2\xd2K\x9e\xce\xa1\xa7\x9f\xcf\xe7?\xf5\xfb\
++\xd4\x93\x0f\xb2\xd9y\xe4\xe4,\x90\x84\x9c\x95\xac\xce4+\x93,tbt\xc9\xc9\x1a\
++5ANNNB\x02\xe6e\x0e\xda\xd2\x92\xfb\xda\xdf CQ\xf6J\xab\xf2F{,H\x0c\xe4T\xa9\
++\xb6\x13\xe9,e\xa4\xeb\xf1\x94\x91\x14uB\x12\x98d\xa7\xf7\x19]9A_~\xc2\x9d_\
++\xfb\x1co{\xc5\x0bx\xc5\xb3/\xe2\xd5\xd7\\\xc6\xeb\x9fw\x05;\x8f\x1d\r\xedA3\
++H^\xe2`\xa6\x01\x82\xa83I\x80T\xb6z\x83\xc51\xf0\x05/\xbe\xea \x97^\xf64\xbe\
++\xff\xe3\x13\xecx\r@RA%G\xbaq\x0f\x12\xd22\x03\xde\xe4)\x07\xb5P\x82\xee{\
++\xe0\xc1V>\'\xbc\xa9NK!M\xc3\x95c\x1d\x8d 1p\xf19\xfb){\xf6\xb3\x93\x84\x1f\
++\x1c\xde\xe1\'\xc7\x8cRJ\x93\xda\n*\x86H\xa1\xeb\x8c\xef\xdd\xf1\r^\xfb\xaa\
++\x97\xb3\x99\xa1l?\xca\xb1\xe7>\x9b\xaf\xde\xf2!\xa8\x0br\xd7Q\x86\x05}j^\
++\xa6i\x95\x8aE\xc8R3\xaeJ\'\x13:M\xb8:S\x11(\x0b\xa83\xde\xf1\xda\x1b\xa0\
++\xdf\xe2=\xff\xea\xdf\xf3\x9cC/a:\x992\x90\xa8\x02\x05eh\x8e?n\xc8\xd3\xa8\
++\xef)CM\xa8w\xccS\xc7\xa1W\xbe\x9a\xfd\xbdq\xcb\xc7n\x0e\x99\x0cm\x08%\x90\
++\x13\xbf\xf0w\xdf\xcb\xbd\xf7\xfe\x90[>\xfe\x11(\xdb\\\x7f\xd9\xd3`\xb2\x8f\
++\x0f}\xe5v\x86\x85\xd0\xe7\tY2\xb5l\x93\xb5\xe0\xc3\x9cir\x98\x1f\xe7\xbb\
++\x7f\xf6\x05~\xee\x9f\xff"P`q\x12l\x0e>\xb4\xfa\x80\x90\xc3\xac2\x99LX\xec\
++\xcc"\\\xc5I\xee\x8d\x07\x88D\xba\xcb\t\x15\x0f|\xf0`_J\xc1|\xc6s\x0e\x9e\
++\xcf\x95\x17\x9d\x83\'\xd8\xa9p\xe4\xd8\xc0\xf1\xc1\x98W0\ti\xca\x9b j#Kt\
++\xa7v\x997\xbc\xf5\x1d\x9c<|\'\x0f\xdc\xf5\x8dP\x8f\xdc0\x04\x11G4\xe1\xaa\\\
++\xfb\xc2\xeb\xf1\xc9\x14\xb4oZ`\x82a\xce\x8f\xef?\xcc\xf4\xa9\x17\xb23/\xa8C\
++\xaf\x85{\xbe\xf7-^z\xdd5\xec\xdf\x9c0lOx\xf8\xb9\xcf\xe2\x8b\x7f\xf0A\xb2-\
++\x18\xeaN\xabL=\xd2mu\x98n\xf2\xe8\xb1\xe3\x14\xf3F\xdeB\xae\xaaQ\xc9\x0c\
++\xa1\xccjEz\xa5za\xde\\\x05\x04\xac\xe0;\xdb\xfc\x8d\xd7\xbd*\xaa\xb8\x94!o\
++\xf0K\xbf\xf6\x1f\xb8\xf2\xd0\x8b\xd8\xc8\x93\xd0\x08U\xa8.T\x82\x88\x98+U\
++\x9d\xe2\xca\xbes\xce\xe7\xe9\x07\xf6s\xcf]\xdf\nq\x83\x01\xc5\x82g`@&o\xec\
++\xc57\xf7\xf1\xd6_\xfcG\x9c\x7f\xee9\x1c{\xe41~x\xe4!\xee\xbe\xfbn\x9e1\xddG\
++\xaf[\x889\x89\x19\xb7\xfe\xc9\xa7\xf8\xcd\xbf\xf7\x0e\xa8\x0b\xb0\x01\xca\
++\x9c\xbeK\xd42oi\xba\xd1d\xc9\xfc\xd2\xbfx\x1f_\xb9\xfd\xfb\xfc\xd1\xff\xfc<\
++\x17\x1f|\x16IWY\xc0L\xc8\xd2@\x88$h\xaf\x88w\xfc\xf0\xf0a\xe8&\xf8|F\xad5\
++\xd8\xd5\xd0h\xa9%lXp\xe8\xca\xcb\xb8\xe0\xec\xfdx\n\xb6X\x89\x1a~aNu\xe1\
++\xf8\xdc96+l\xcf\r\xab%\xa44\xed\x97\xa8?\xaa\x80\xda\xf2\xfb\xc1g\\\xcc\xc5\
++\x07\x0frbg{\xa9\n}\xf9\xab\xb7r\xdd\xf5\x87\xd8\xd8:\r\xa5\xc7\x86\x81a\x06\
++\xafx\xfe\x15|\xf5\x0f~\x07\xca,6\xec\x05/\x89$Nq\xc3=\x81v\x90\xa6\\\xf3\
++\xa2\x9f\x85\xfd\x97 \x1b\xfb\xa8%0K[\xda\x05!+\x89\xaa\xda\xea\xfb\xa8\xa0\
++\xb6\x17\x95\xeb~\xf6F\xce\xd8\x14n\xb9\xf9\xf7q\xab\x88h\x94\xa9\xb5\x82(o8\
++tMx\xc4(K\xb7\xaa\x90\xd4C\xbf\xc1\xbf\xfb\xe0G8\xe7\xe2\x9fb\xaa\xcad\xa2\
++\xb0=c\xdawk\x12y\xa5\x985R\xe5<\xf7\xe0\x05\xb1\xe8\xbe\x03\xcd|\xe2\x8f\
++\xbf\xc0\xd1\x07\x8fp\xe3U\x07#\xcd-\xd3C\x8dS\xa7\x82\x1b\x16b\x1cV\x0b*\
++\x8aj\x8fi\xcf{\xff\xd9\xaf\xf2\xe5\xdb\xbe\xc7\xe7\xbfr\x07\xcf\xb8\xfc9\
++\x9c\xd8\x19\x10w\xb2\x97U\x1e\xc6\xc9\xea\xc1\xe6D\x04\xcd\t\xb4\xe75\x7f\
++\xf5\x8d\xd8c\x0fp\xf8\xdb\x7f\x06\xfe\xa1\x06\xd9\xb5\xd5p\x04#k\xa4Z\xc4\
++\x97\xa9M4#^1\x81\xcb\xcf;\xc0\xbe\xfd[\xe8tB\xad\x05&\xce\xc1\x8b.\x00+AU\
++\x1bl:4P\xf40\xeeP!u<\xed\xec38\xff\xc0>\x18v\xc0\xd6\x17=>\xc6\x86\xc8\xb8\
++\x95Dq\xc5\xb5\x03\x9d\xf0\xfc\x97\xbd\x9a\xee\xec\xbb)\xdd\x16\x8b*T2\xca\
++\xb0\xab\x14\x06\xc8u\\\x88\x80\xf4\x99Y\x85\xb3\x9f~1\x93s\x0fp\xff=\xdfo\
++\x95V\xe3\xdb\xb4Z\x1c]6 \\|\xacFp\x8b4\xa5u\xc6\r/xn\x9c(5@\xad\x14\x04o\
++\x9d\'_.}\x14\xc6\x85(\xaa\x12\x15s\xb8\xfa\xb2\x8b\x90\x94\xda\xe6\x9f\xac\
++\xbb\xd0\x96\xd1\xb2\x8eK\xc7/\xff\xeb\x7f\xcb\x17\xfe\xf4[\xfc\x8f/~\x9dg^y\
++-\'v\x06\x06\'$\xbc\xea\xc1%\x96F\x10\xb2\x8f\x05\x86:\xa2J\xee&\x9c\x98\xef\
++\x90\xba\t\xdf;\xfc#\xd0\x1e\xb3X\xb8*`\xe1v\xd2\xc8\x84\xefZ\x8cPm@\xcc\x10\
++5\xd4\x07j\x1d\xc0*\xba\xd4\x0f\xea\xf2;\xc6\xa8\xfd5\x0frPq\xea\xb0 \xa5\
++\x0e+\xc3\xffs\xf3\xe3\xc9\x19)\x80\xbc\xdb\xe2\x8aC\xaf \x9f\xf7l|z\x1a\'\
++\x87\x84e\x8d\x8d\xe3\x88\xc9\xe3\x9a7\xd9\x9b\x19sV\xa4\x83R\xe6\xec\xd9\
++\xbb\xc9b>\xe3\x98\x19\xd7\xbd\xf6u\x1c\xe8\x13\xb7|\xe4\xc3\xe0\x05Qo\x82NK\
++)>\xba \xcbM\xb8\x170\xa3JS_\xdd\xb1V\xfa\xc6\xf7\xc2\xfa\xac\xbd6\xfaD)5*\
++\xd52\x9cb\xde\'y\xa4\x1e\xa4\xe3\x1f\xfe\xeao\xf0\xa5o\xdc\xc5\xe7\xbe\xf6\
++\x1d.\xbb\xea:N.\xa0 q\x1d\xb5\xd6\xa2\x0b\xba>\xf6\x1e\x04o\xd5 \x84\xa2\
++\xab\x90\xa7\x99\x1d\xafL6z\xde\xf8\xd7\xdf\x81>\xfa\x08\xb7\xfc\xde\x07\xb0\
++\xd4\xa3\xaeX\x99ASs \xedZ\xcbR[h\xe7\xbbd\x85k\x16\x17\x91\x08\x1b@e\xd5\
++\xe6\x8aox\x93\xbf\xa2\x056\x82\xe4\x93?2\xa6}\xa0\xfdK^\xc9\xf4\xc2+\x91=gR\
++t\x13K\x86\xd7\x1amN-\x01\xfaM\xba\xdbu\x05!\xe28\xe1\x01h\xe2\xf4\xd3L-\x85\
++\xd3\xcf9\x8b\x8d\xb3\xceb\xd8:\x9d\x17\xff\x95\xd7\xb1/+\x9f\xbc\xf9\x83(\
++\xad\x15E]\xb9\xefZG\xb6\tr\xcd(c\x8c7yJ\x04\x19y\xff\xae\xcd\x8d\xddh\xc1\
++\xdc\xb0S=\x7f\x85v\xab\xcfk\xe6\x1f\xbf\xef\xd7\xf9\xfc\xadw\xf0\'_\xbd\x9d\
++\x9f\xbe\xe6zvj\xc7\xbc\xc6BD$\x14\xad\x1a\r]m\x9a\xa6\xaa6\xb2e\xeb\x1e\x10\
++\xa5\xaev\xca\xdc\x0bY\x9d\xb4\xb5A\x99\x1boy\xf7M\xcc\xee\xbf\x9f\xff\xfe\
++\xb1\x9bA;\xcc\x86\xd5\x82\xc6\xb557\xa6\x91\x8c\xb0\x83\xaf\x14\xe3\x06T\
++\xa33\xe8\x08\xa9\xbeB\x04\x9aN\xe0\xcd\\"\xa0\xdeZ\xe4\xaeMN\x17F\x06\x81\
++\xf6\\q\xe8\xe5\x9cv\xf0\xf9\xd4\xbc\x97\x85\xf4\xd4<\xc1k4=\x92I\xab]<\x0c\
++\xaa\xf2\xf8,\x10\x174\xfa\x9c\xc8\x1dH\xe7\xa1\x04\xd7Juc\xc8\xc6\xfe\xa7\
++\x9fGw\xd6\x99\xcc?\xfdI\xfe\xf2\x9b\xde\xcc\xa6W>\xf3_oF\xcdB.l\xe5pu\x8f\
++\xbe\xbf\n^\xdb&}w\xf13b\xdax\xc0\xd2\xf4\x85\xf8\x90\xed\n\x177GEI\x08&\x19\
++\xd7\x9e\x7f\xf2/\xdf\xc7\x03\x0f\x1f\xa5\x98\xf3\x83\x1f=\xc4g\xbf\xf6\x1d\
++\xae>\xf42N\xee\x00i\x03\x8a\xa3\x9a1\x1bB\xfd\xf6&\xda\xaa\xa3>R/m\x0b\xd0U\
++- \xea\xa8\x1a\xa4\x06\\*Ts\xf2\xa4\xe7\xc4|\xc1\xd6F\xcf\x9bn\xfa[,\x8e\x1c\
++\xe1K\x7f\xf4\x99@\xdd\x14r\xe5`\x03{\xfa\x0e_,\xb0&Q\x05\xc0\xad\xf5\xee\
++\x9f\xe4\xb1\xae\x0f,G\x03X\x85\x10\x12}\xcbj\n\xa9\xe7\xda\x97\xdf\xc0\x91G\
++Op\xf4\xd86\x97\xe8\x84\xfe\xf4\xb3\x18t\x8at0T\tbf\x86H\xc2\xbd,\x1b\xaf\
++\xaaM\x0f\x1c\xb3\x80*bBn]\x84(LD\xc8\x02\x9e\x13\xc9\x01\x0b\xcd]7:f\xd59\
++\xfd\xc2\xf3\xd9:\xef\x1cn\xbe\xf9f.y\xde\x0b8w\xdf>&\x9d\xf0\xe3\xc3\xf7r\
++\xd7mw\x90$\xe32,\xf7,\xed\xa4\xd7M\xf08s\x9c\x12\xeb\xd2\xdcBF\xe3\xa4DI\
++\x89k\x0e\xbd\x84\x87\xe6\xc6\x0f\x1e\xfe\tg^p)\xe9\\\x98\x17\xc5\xd3\x84\
++\xc5P\x10\x9d\x84\xa7X\xeb\x07@\xeb"\xb5F\xab\x06\xb0\xa6\x94B\x9b\x88$\xb4\
++\xc2\x00q\xd0\x14\x9e0>\x17\x15\x14\x18\x86\x01\xed{\x8a\xc2L\x13\x97>\xefy<\
++\xf5\x8c3\xe9\x86\x81a~\x12\x9fL\xb8\xfd\xf6o\xa3\xee\xb8\xe4\xa6\xe3\x8f\
++\xa7\xf9\xff\xf7\xd0uPA\x97\xba~\xf5\x04$\xde\xf2\xae\x9f\xe7\xcb\xb7\xdd\
++\xc9}\x8f\x9ed\xcf\xd3:j\xce\xd4\xa4\xd4\x02\xfd\xa4g>\xb3\xc88J\x13H\x05\
++\x13%\x99c\xb2\x1b\xa8\xc7\xde\x80\xbb\xb7\xceP\x1bY\xc9\x9aPZ\xcb(\x8c\x84\
++\xb8\xd3\xe5L\x01\xaa\xc0Nrn|\xfb[\x98\x9a\x93k\xa5/\x85_\xff\x95_\xe1\xa2\
++\xab\x9f\xcb\x05g\x9d\x85&Gj\xa1\x138r\xf8>\xee\xf8\xc6\xd7[\x89-m\xa0\xe1Tx\
++o\xd5Y\x9b\xf5I]\xcf\xe0p\xed\x0b_\xcc\xe6\xde}\x98&\xee\xbe\xef\x08\xfdi\
++\x07x\xfd\xdb\xaecG\xa6,\xb4\xa3z\xe8\xfaH\xb0gM\x81\x19\xe2\x82&\xa8M0\x15\
++\x0b\xc3\xacf\x85\x9a!Z\xaf#\x8f\n\xeb\xfaI\xa4V/\x8bG\xb7\xd7%ZSh\xa0\xf4\
++\x1c\xa7\x08\xf4\x08\'\x87\xcaEW_\xcd\xc6\xe9O\xc5\xe7\xf3\xa8\x87\xdc\xe0\
++\xe4\x1c\xcf\x13n\xbf\xfdv\xbc.H)=\xc1\xe6W\x8f\x18h\x12\xcc\x01\xedx\xfb\
++\xbb\xdf\xc3Wo\xfb\x16\xdb\x8b\x81\x03\xcf\xba\x86\xbb\x1fz\x94\xa7\\\xf4,\
++\xaa\xf4K\x10\x13$0S\xc0DZ\x8d\xe5\xab\xd4\xbb\xd4%\xd6\xfe\x7f\xca#\x8b\x04\
++\x90\xa9\xb70\xa0\xa5,\tD\xae\x8d\xb9\xe9X\n\tm\xea\xcb\x18TH\x1bS^\xfe\xc6\
++\xd71\xd1\x1c1o\x85\xcd\xdc#\xc7v\xf8\x8d_\xfee.\xbf\xf2*\xfa\xb2\xe0\xf6\
++\xdb\xbe\xf1\xe4\x9bo\xa5)\x9a\xb8\xea\xd0\x8bxd{`\xbe\xb1\x977\xdd\xf4\x1e\
++\x16\x9286\x9b\xa3\xd3\xa70/F\xf5\xc0\x07\x15\x02\xe2\x8d\x18\xd9\x11p\x13L\
++\xc2\xe5G\x86i\xeaM\x16\xd7\'6@3U;\xfd\xc8\x9f"\xdazwP\xdcP\x8f\xd9\x9f4N\
++\x7f\x88 I\x969\xbb\x92q\x11\x8a\x19\xdau\xcc\x87\xc2\x9e\x8d)\x07\xaf\xbe\
++\x86\xad\xe9\x04\x8e?\xc67o\xbf\x8dIN\xcc\xe7\xf3\'\xb6B+\xa5\xdf\xfc\xce\
++\x9b\xb8\xeb\xde\x1f\xf3\xd0\xa2rN\xea\x98\x9b\xc3\xde}\xcc\xe6\x95\x9a\xa2\
++\xb3\xeb\xce\x8a\x8cIp\x0b\x1b\xe7r\xdc\x11\x8f\xd0\xf0\xa5w\x87\xf8\xbaR\
++\xa0Y\xf2\x98\x1c}\x8d\xe0\xf7"\x12\xdd\x18\x844r\xf6S\xe4\xee\x98\xd4\x92\
++\xa5\xf4\xadX\x80\xa7+\x9a\x95\xc1\r\xeb\x82\x8d\xbd\xeamoe\x1f\xceo\xfd\xda\
++\xbf\x81\xaeg\xb1X\xc4\xc4\x99[45\x08\xb74\xc9\\\xf3\xa2\x97pt\x00\xb6\xf6\
++\xf3\xba\xbf\xf9\x1a\x8eUgH)\xa4\x00w\xe8\x84d\x89Z\xc6\xd3\x8dT\xad\x12\xc8\
++/\x1e\x1d\xe1$\x8ai\x13~k\x94\xc9\xa1\xdbj\x0c{,;C\xb1\xdf\x15\x13\xc4hR\x1a\
++M/o-\x8eq\xfb,\xbf4r|\x8d~Mk\x8c\xc8\xb2T\x8eI\xad\xcc\xf1\xe3\xc7\xa9e`\xb2\
++\xb9\x11*\xb1\x08fka$\x8ak\x86\xae\xe7-\xef\xba\x89\xaf\xff\xe0^\x8e\xe7\xcc\
++,%\x8a\x84\x02\xe1:\xe2\x11\xadb\xf5\xc6\x97dy\x8cmI\r\xdcV\\*\xd6\x1a\xde\
++\x80FOb\xbd+\x14Y`\xfd!\x86\x88\x93E\xc9(N]/\xb8\x9f\xe0\xd1~\x80\xd6-r\x107\
++6SbZ\xe0\xfd\xef\xff\x1d\xb6\x1f\xb8\x97\xc5\xc3\x0f\x91ZfX\xe0K\x9e\xe0\xa2\
++\\\xf5\x82\xeby\xac\x1a\xf3\xa7\xec\xe5\x8d?\x7f\x13\x8b~\xc2\xb69Z\xc3\xc8\
++\xb5\xb2\xec\x02GA\xd3\xdc\xba=/6\xb2oi\xa1\x11\xcb5V\xaf\x8f\x07\xf8\xe4\
++\x18\xb0v\xba\xd2N*Z\x9e++\x9f\xb2\xedh\xa9\x8f\x05P[\x84Z\xbc9{\xe41\xf2 \
++\xd8\xd1\xc7\xf8\xc4\x7f\xfemX\x9cd\xa2\xa9Ik\xa3`\x99@\x13o~\xd7\xbb\xb9\
++\xeb\xc8\x11\x8e\xe7\xc4b\xa3\xe3\xa4\xc7\x10\xad\x9a\xc7\xa8\xadjd\x86\xe5\
++\xa9z `\xd42$\x19\xb9T\x03k\x951\xab/\xbb\xcf"\xf6\x84\x9b\x07\xc8\xde\x8a\
++\x97\x94\x12\xb9\xf5\xd9\xc7x\x1f\xa1\xe3\xf1\x0f\x8b\xb1\x13@\x86B\x9736\
++\x1f\x98\xa6L\xa9\x85\xffv\xf3\x87y\xf8\xce\xbb\xb1\xa3\x8f\xc1|\x07qcXNy:\
++\xdao\xf0\xac\xe7\\\xcdv\xee\xb0\xbd{y\xdb\x1b^\xcf1\x8c\x99\xd2(*$$\xe4?o\
++\x14A\x9a\xec\xa8\xb4\x06gx\x82\xc6\x082\xc5b\xe3fkz\x83y\xc8\x96\xaeT\x11\
++\xac\x85^Pu\x03[V\x83\x91\xdfGP\x1aS\x9e2Vuk\xa7\xafq\x1c"\x82\x1bt)s\xec\
++\xa1\x879m\xb2\x89\xd4\x19\xba\xd8f\xf6\xc0\x11>\xf5\xbb\xef\x87\xd9\x0e\x8a\
++\x93rfpp\xcd\xb8\x04\x89y\xe7\xdf\xf9\xfb|\xeb\xbe\xc3\xd8\xd6&\xb3,\x14FI\
++\xdd\x97,\xb4\xd9:\x0e\x7f\xac\x93\x9a\xab\xc5\xdc\xcf\xc8\x9f<>\xdf\xb0\xa1\
++\xb6~\xa7(\x88+\xa2O\xce?\xf2\xb8;\xa5\x95\xafM\xfb\x93\xe6v\x82\xb3\xcb{\
++\x1cJ\x1bv\xc8\x08\x94\xca\x1f~\xec\xe3\xfc\xe8;\xdfe\x8f\nI\x9d\xc7~|\x84T\
++\x07\x90\xc8\x18\x8er\xe5\xf3\xae\xe5\xf4\xb3\xce\xc7L8\xfc\xe0\xc3lwS\xfe\
++\xda\xdf\xfe\x05vzaG\x0c\xb5\xcaD\x13\xd5\x9b>\xa9\xb4R\xb6\xfdlkS8\xc1\xf5\
++\x97\xd3\xe54\xec\x91\xb5\xfai\xd9\xbao\xc1a\xad\xc0[\xf7\xe7V\x85\xee\xaa\
++\x05\xc4\x89\\/\x91^\xd2\xda\xe9\xaf\xa3g\x9f2b\xce\xce\xd1\xa3tfl\x1f9\xc2-\
++\xef\xffm(\x15|hU\xdd\xd0b8Qp\xde\xfe\xae\x9b\xb8\xef\xd1\xa3\xcc\x8bp\x89(\
++\xf9\xa9\xfb\x98w\xc2\x8e\x97U\'\xc9\nI3.\xca\x80\xe3c<\x8b\x07\x98\x8f\xb2\
++\x00\xb2\x1c0\x1f\xc54\xb7\xf0\x02\x19?OT\x80\xb5\xee\xae\x03v=\xdc\xc9\x82\
++\xe05N\xbcW\t\x94f\x1cb\x92%\xde\xe0\xe3\xc4\xb9!\xe6LL\xf8\xc4\xc7>\xce=\
++\xb7}\x13\x9d\xed@\x19H8\xb5\x0ehn\xee)P\xcd\xa0\xcb\\t\xf9\xe5\xbc\xf0\x99\
++\xcfb\xd1w\xcc\x80\xaa\xb0h\x1d_F\xcf\x96\xd6\xe9\x16\xd0\x1c\x9b\xaf\xa5\
++\x11>!\xdc:\xb5\xde\xcfJD\x88\x10Im\x91\x8d\xab\x90b\x84\xc6j\x9b?\xf6(\x7f\
++\xa3bl?\xc2\xc8\x03<\x88D\xb4+\xa5q\xa6\x95\xe09\xfe\xd4h\x94,\xc24\x0b\xf7|\
++\xf7\xfb|\xf2\xf7>\x04\xf3\x19\xa8\x86\x02\xac1\x83\x9b$a\xe2T\x8d^\xc3\xd1\
++\xf9\xc0\x99]b;9\xc3R>k\xfa\xe0\x9a(\xd0\n\xb9\xe5\xc9J\r\xaa+\x1e\xb3\xbf\
++\xea\xe1\rc\x86\x8e9\xe5\xf8\xbf\xa9C\x95\x15\xb5>\xe5\xb4\xdd\xbdYs\x05jy\
++\xac\xa8\xc2\xcf\x035\xc7\xe4\x97ZEX\x1b \xd2@Q\xd5\xa9\x05\x90\xc4\xa5W\\\
++\xc3\xd9O\xdd\x1f\xfcA\x05\xb5\x81\x07\xef;\xcc\x9d\xb7\xdd\x81\x88p\xcd\xf5\
++/\xe6\xc8\xf6\x0eG\x8e\x1e\xe5\xe9\xed\x98G\x82\x1a\xe9TZse\x8d\xd48\xcb\x18\
++\x17\xf5\xf6\xb9\x06\x06\xd5\x96\x85\xcdn\x11v\xf5X/\xf0\x92\x80I\xb4\xdf\
++\xac\x0e\xd1\xeb\xf4\xdaTb\xdf]\x0b\x88\x84\x8bTdU\x0e\xef\xf2\x01\x8f"\xa4\
++\x18f\xc2\r\xafy5\x97]ri\xbbG F\xd6\xebl\x9b;n\xfd\x1aw\xdc\xf1\x1d\xdc\xe15\
++oy\x1b\'\x93\xb2\xff\xac\xb3\x190\x90(\x9ab4\xda\x1bMM\x80\x85d%\xa3p3J\xe8\
++\x91\xc7\x97\xcbl\x84g\x94\xe7C\xc8\x89\xb0\x18\xff>\xce >\x02|H\xe22\x02\
++\x88\x8c\xcdQ\xf3V\xec\x84T\x99DIkn)b\x8d\x19\xc6\xf1h\x8a\x05^{\xe8\xf9<\
++\xf7\x85\xd7\x85i\x14\xc4\x8c\xec\xc6?}\xef?\xe0\xa2+\xaf\xc2R"\xed?\x8d\xd7\
++\xdcx#C\x82\x92\x84ad0\xc4)\xa7\x96\xfe\xc2\xed\xc7\x1a\x81`|\xad\x85-\xa2\
++\xa85| 2\x8b-\x19 K\x11\xc7\xc7Z\x05]r\x027\xc7\xdd\xe2\xdaVb\x06\xd9[\xb7\
++\xc9G\x1e \xb6\xb6\xd9\x91\r\xb2D`edc+\x9d\xde%\x04\x92*\xab\xa9L\x11e>\x14^\
++t\xe3\r\\\xfe\x97\xae\x00\xcd\x1c\xb8\xf8Bl\x1a@\xeb\xad\x91),/\xceXE@S\x85$\
++>\xb5\xcb\x9d=\x8e1\xd2\x9b\x8f_%\xc6\x8dW\xbc\xe4I\xc8^\xdc\xe4\xd5\xee0)e\
++\xec3F8\xaf\xd2\xe0\x9a|\x9dD\x02\x14\xdb\x8f\xbb4\xd7\x1f\x97*-\x01I\x84\
++\x85\xa0K\xe6H\xce\xbc\xf8e/G\xcc\x19\x1cj\x16\xe6eh\xf3<\xb2d\x97\xc1\xe4VU\
++(\xad\xb3\xdf&\x90\xa3\x10\x1a\x11}$^\xac6\xedk\x9b\x96&\xea\xcarl.<\xc5\xa9\
++T\xabq[M-X\x19\xa8e\x881\x1d\xf36,=Z\xc9\xeb\x92\xfd!!$\x06\x00\xb2\xfb\xe6\
++\xad\xd6\r\xf65s+\x11B4#\x999\x839\xde)\xe6N\xce\xf13c\xfd\xce2\xf6i\xcc\xd3\
++W\x8cs\xac\xec\x88\x9e\x86K\xa42\x1f\xd9\x9e\xcb\xd2\x0b\x97\x9b\x97\xb5\x03\
++\x1c\xcb\x84\xa8\xa3\xe3`<\xee\x10s+X\x1d\xa2\xc8\xd3`\xb2y\xac*Rk\x8cd\x95f\
++\xddq?-\xcd\xb0ba\xe3{\xeb\xee;\x9e\x964\xce-9\x0c\x19e\xb3\x84\xb0"i\x8d\
++\x8b5\x15:\xa2\x13\xc3I\xe2D\xa3<N\xd8\x83\xf3\x858\xab\x8eU\xa1\x8e\xe3\x08\
++\x12\xf2\xd7\x08\xd0q\xffP\x1b%\xb0\x10>\x8bW\xbcT\xea\x10\x1b\xa7\xcc\x19\
++\x86\x9dH\xd7\xad/\xb5\x14c\xd7]\xbfm\x89\xd5\xcd\nk4\xd3W\x7f\xc7\x1e\xce\
++\xe8\x0b\xcb\xcd\x8f\xe9n\xcc\xf7\x10\xd4j\xd7\x88\\s\xe56.\xb7\xfa\x9d\xd5\
++\xe6\x97\xa5v\x9b?\xaam&!nkla\xb1\x16\xf7\xeb\xe9o\xa8\xde\x8cax\x1d(\x8b9e\
++\x98Q\x163|\xbc\x95\x8eQ\x11Z\x1e\x8a.w\xb8,\x86e\xb5\xc1\xd1\xc7Z\x97\xa9\t\
++h\xab\x11\xf8\xe5eF\xff\x1e\x8d\xb2\x16\xc7\xe6\xb6\x94\x18\x82\x8c%\nF\x950\
++|u\x1a\xa8\xb6\x82\\h\xe4\xca\xa1K\xd4\x12\xa8\xe3\xd2\xea\x03\x1b\xcd\x16k1\
++\x81R\x9d\xeaQK\x98Y\xb8\x7f]P\xe63\x16\xb3\x1d\x86a\x08\x00\x1e\xef\x1a\xc3\
++\x1b\x88\xb9\xd1\x99\xb6j\xebI\xf8\xf3\xba\xbd$\xa6K\x02\x13|<\xd4e\xbb{\xbc\
++\xf9t|?\xee5\x88\xf4\x94\x9b\x91\x8b\xc7\xc8\xcd\x18\x1c\xd5ey\xcbk\xf5\x15\
++\x1f07Ju\xb2\x83\x99b8\xd5\xac\xdd\x8f\xe1\xa8\x85\xf1\xa4\x121R\x812\xa7.N\
++\xe0\xf3\xe3\xd8\xec\x18e\xe78ev\x1c/\x8b\xe5A\xe5q\xb6wC3\xbe=\xa7\xdf\x9c\
++\x90\xad\r\x1c\xef\xea\xfc\xae\xdc1\x1e\xdahh\x93\xc5\x08\xcem\xb40\xc01wJ\
++\x1bNN.m\x80i\x9c\xe2r\\\x95\xea\x89J\t\xfe\xafJ)\x85\xeakq-\x12\x03\xd6\x1e\
++\xac\xb1V\xa7\x16\xa7,*VB\xcd\xae\x831\x0c\x8e\x9bR\x160\x9f\x17\x86yaX,\xd0\
++\xc5ql\xe7(\xf3\x13\x0f\xe3;GY\x1c\x7f\x842;\t^\xa3\xa2\xeds\xc7`\xce\xe7?\
++\xfbYH\xca\xcePZ\\5\xf6\xb5\x0c\x91\xc7\x1b x\xbb\xef\xbaS\xb4\x8cz\x9dX\xb8\
++~Z\x89N\xeb7F\x8e\xe1T\x90]=\xfb%>\xf8\x98\xf3\xdbtw\xeb\x12{\x8d\xf2\xd6]"\
++\xd5\x0e\x05\xdas\xb7D\xad\x86\x0f\xc2b\x11\xc07\xcc\xb7\xa9\xc3\x82:\xdf\
++\xa6\x0e;\xd4\x9d\xe3<x\xff_,e,\xe9U\xdc4a\x1e\x9a\xfa2\xf0\xc7\x82a\xc9\x82\
++\xdb"G\x92>>\x1f\xef@\x18a\xe0\xd4\xb0\x89\xf4\xb0k\xd3\xbb\x1e\xba{\xc8b\
++\xf5\xbd\xb5\xdf^\x97\xb4\x97\xa8\xb7>\xe8\xa0k\xebiypy\xf3u\xfbg#\xff\x0f\
++\xc9/\x89PJ!\xe7\x9c\x1bQ\xc9\x98\xd5\xd5b\xc7\xf8\xe3\x94\r\x8f\xac\xd1G\
++\xa1q7\xf7\xf6S\xf6w\xca\xdbkvh}\x05\xa9O\xf8\xb9\xe5\xe8\xcd\xa9\xd7_\xe2\
++\xc2*\xcb\x84:\xb5*\x90B\xa5f\t\x8c\xc8H\xb3\x9c,\x89ZK\xe8\xde"\xfc_\xce\
++\xbc\x05\xfcSMC%\x00\x00\x00\x00IEND\xaeB`\x82\xb3\x8d;\x80' )
+
+ def getBirdBitmap():
+ return wxBitmapFromImage(getBirdImage())
+@@ -169,15 +308,15 @@
+ def getAwayData():
+ return zlib.decompress(
+ 'x\xda\xeb\x0c\xf0s\xe7\xe5\x92\xe2b``\xe0\xf5\xf4p\t\x02\xd2\x02 \xcc\xc1\
+-\x06$\xe5?\xffO\x04R,\xc5N\x9e!\x1c@P\xc3\x91\xd2\x01\xe4\xcf\xf1tq\x0c\xa9\
+-\x98\x93," \x90\xb0\x80\xc5 Q\xf8\xcf\x87\x87G\xff\xf3\xcb\xc7\xfdqOOf\xee\
+-\xd9\x11\xd3\xb0\xc6\xd3\xc1\xa1i\xa2\x8a\xf8\x04\xa6\x02\xa9\x86\x1cm;}\x81\
+-\xa7L\x9b\xf6|x\xcc\x9f\x17\xcd)\xa5\xa0\xf0\xe1\x91=;w\x93\xff$q\x11\x91\r\
+-\xda\xc7R9\xb8w5\xb4\xf0,[$__p\xb3\xff(C\xceI\xa6\xb4\x03\xfd\xfa"\x12\x1f\
+-\x1eq~\x9bq\x80\xa3Y\xd4[E\x82\x95E\x81\xcd\xf3\x84\x83\x81\xde\xd9\x88\xd8\
+-\x867]\xb2\xd1\x05\xd9G^6q\n\xc7\xfd\x99.\xd0\xb8\xa3eB\x1a\x1b\x9b\xcd\xcc\
+-\xce\x08\x06\x86\xd3\x9b\xac\xff\xe90\x1b\x96\x03\xdd\xc9\xe0\xe9\xea\xe7\
+-\xb2\xce)\xa1\t\x00\xd0\xb5LC' )
++\x06$\xe5?\xffO\x04R,\xc5N\x9e!\x1c@P\xc3\x91\xd2\x01\xe4\xcf\xf0tq\x0c\xa9\
++\x98\xb34\xf8 o\x83\x01\xc7\x92c\x8c/X\xca\x8c\xc3\x8bX\xdd\x0bZ\xfa\x1e\xb1\
++\\\xe0\xfc\x90\xe8#\xc5\xbdv\xeb\xf7E,}\xbb\x1dd\x95\x0eq\xa8\xe9\x9e\xf49\
++\xe4\xf3\xa9\xcf\xd4\xc2\xdb\xf1^\xcb\x8b\xd8\xe9Mr3X~^\x99\xd5\xea\xcfU\x17\
++)\xb0q\xb9\x19\xa7\x80g\x8c\xd9\xe6\xd3N\x1cU\x1b]\x8d\xf6\x0b\x9f\x0e\x12\
++\xf3<\xea\xe3\xb8`\xeeQ\xe7\t\x97\x18\x0eV\xbe\x9a\x1d\xa0_\x18\xeb9\x83q\
++\xc6\xfb\xf2\x9eoR\xdc\xac;\xbb\x1eo\x90\x96\x17\x89\xbea&\xf4\x9e\xf1\xdb\
++\xde\xbb<\x01?}\xe2\x0b\x19C\xb6;D\x9f0\x8e\xb7\x00\xba\x91\xc1\xd3\xd5\xcfe\
++\x9dSB\x13\x00`\xd1Pt' )
+
+ def getAwayBitmap():
+ return wxBitmapFromImage(getAwayImage())
+@@ -189,21 +328,19 @@
+ #----------------------------------------------------------------------
+ def getOnlineData():
+ return zlib.decompress(
+-'x\xda\x01a\x01\x9e\xfe\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\
++'x\xda\x01@\x01\xbf\xfe\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\
+ \x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\
+-\x08\x08\x08|\x08d\x88\x00\x00\x01\x18IDATx\x9c\x8d\x92\xadr\x021\x14\x85O\
+-\x98\x15\x11\x88##\xf7\x11\x90\x08\x04\xb2\xa2\x0fPQ\x81@ \xfa\x10<\x02\xa2\
+-\x02Q\x81\xacDT\xf4\x01**\x11\x15\x95\x08DdD\xc5\x8a\x9dI\xc5M\xda\r{\x1783\
+-\x99\xfc\xdc\xdco\xee=\x89!\tM\xa4\x8f!8\xa3\x06;2\x1a\x80\xf4\xd1\x8e\xff\
+-\xf7\xfe4\x0c\x1ai\x87!8c+\x80\x94qI*@\xaa\x00l\x05|\x7f93\x99\xf88\x08\x98\
+-\xce\xf4\xa0\x1d\xcb\x98\xce\xa4\x1d\rR\xd7>\x8eH\xe0\xee\xbe\x1f\xfc\xfc\
+-\x906l%\xeb\xc3\xa1\xf4\xa1\xae%\xc7\x90\xc4\xc3\xa3l\x9a\x1f\t\xee\xf7r9\
+-\x83\xdf\xdf\x9c\x99\xcf}lZ\xb9\x93\xe7\xe3\xd1\x19C\x12\xab\xa7\x12\xd0\xb4\
+-e5!\x94\x89M+\xde\x00\xc9\xc4\xed\xf3\xf5\xf7\xee*\'\xff\x012\x84L\xe6\xa5\
+-\xde\xb3\xba\xebs/\xd4\x8f\x04\x00\x8b\x85\xf4\x9c[\xcb\xbe\x9c\xab\xd2\x0e\
+-\x01\xa9\x04\xc9\x93\xd7\x81\xe4\xa2\x85\x1e \xa1w\xbb\xcb\xfe\xa8\x80\xf5Z^\
+-\xe5Z\xb2\n\xd8\xbe\xc8\xcf\xdbln{\x99\x1e`\xb5\x94\x1fx\xab~\x01\xf4=f\x9f\
+-\x11\xb7S\x87\x00\x00\x00\x00IEND\xaeB`\x82T\xf1\x96\x10' )
++\x08\x08\x08|\x08d\x88\x00\x00\x00\xf7IDATx\x9c\x8d\x92a\x11\xc20\x0c\x85\
++\x13\x0c\xec\xd5\xc1$\xe0`8@\x02\xa0\x00\x0bH\x00\x05 \x01$\xe0\x00\x1c\x80\
++\x82\x16\x05\xe1\xc7#\xb0\x8dl\xf0\xeezk\x97\xe6\xbb\xe45\n@"\x01\xd9JI\x1a\
++\x06[\xd2\x08\x00d\xab\xaa\xcf\xf9~\x1f\x06M\xa2\x9f\xa5$U\x15\x01\xb8\xc6\
++\x14\x02X\x85\x88\xaa\xc8\xf5\x9at:\xcd6\x08h\x9a8XU\\M\xc3v"H]g\x9b\x00"\
++\xf3\xf9w\xf0|f\x1b\xaa\xdc_.]\x1f\xea\x9a9\n@\x16\x0b\x1e\x1e\x0f\x06\x8fG^\
++v\xf0\xe9\x94t6\xcbf\xc6;\xfe\xbd\xdd\x92*\x00Y\xaf\xbb\x00\xeb\xd5SJ7\xd1\
++\x8c\xde\x88\xbcL\xdc\xed~\xbfw[\x9e\xfc\x068\x04\xa0q\xde\xbb\xab\xbd\xef{\
++\x11\x0e\x92\x88\xc8r\xc9\x9e\xbd5\xf7\xa5\xaf\xc19hW2\x94<\n\xf0\xb2\x0f\
++\x87q\x7fB\xc0f\xc3W\xf9\x95\x1c\x02\xf6{N\xdev\xfb\xdf\xcb|\x01V+N\xe0\xbfz\
++\x02c\xcceV\x80\xefu4\x00\x00\x00\x00IEND\xaeB`\x82\xfa\xa9\x7f\xb3' )
+
+ def getOnlineBitmap():
+ return wxBitmapFromImage(getOnlineImage())
+@@ -215,13 +352,13 @@
+ #----------------------------------------------------------------------
+ def getOfflineData():
+ return zlib.decompress(
+-"x\xda\xeb\x0c\xf0s\xe7\xe5\x92\xe2b``\xe0\xf5\xf4p\t\x02\xd2\x02 \xcc\xc1\
++'x\xda\xeb\x0c\xf0s\xe7\xe5\x92\xe2b``\xe0\xf5\xf4p\t\x02\xd2\x02 \xcc\xc1\
+ \x06$\xe5?\xffO\x04R,\xc5N\x9e!\x1c@P\xc3\x91\xd2\x01\xe4\xa7{\xba8\x86T\xcc\
+-I\x16\x11\x10HX\xc0b\x90(|\xe6\xc7\x87\x87\xf5\xc7\xda\xf9\xff_\xbcy\x83AYO\
+-\\!{\x96\xa2\xc2\x01\x8fI\xdc\x1e\x8c\x16\xbc\x0e\xdbD\xe7\x8bw\x141F\xc6\
++I\x16\x11\x10HX\xc0b\x90(|\xe6\xff\x87\x87\xf5\xff\xda\xf9\xff_\xbcy\x83AYO\
++\\!{\x96\x86\xc2\x01\x8fI\xdc\x1e\x8c\x16\xbc\x0e\xdbD\xe7\x8bw\x141F\xc6\
+ \x1dH\xd7vd\xbe`:\xf9\tkLWa\xe7e=\xb1\x15y\x0c\xebf\x1c0`\x98\xedTs\xf1\x84S\
+-\xdf\xda\x8aX\x06\xee\xdc$\x93d\xd3\x86\x88^\x03\x06\x86\x86\rza\x99\xf9'~\
+-\x01\xadd\xf0t\xf5sY\xe7\x94\xd0\x04\x00\xc0q7\xbe" )
++\xdf\xda\x8aX\x06\xee\xdc$\x93d\xd3\x86\x88^\x03\x06\x06\xd6Gz\xd9\xdb\xd7\
++\xfd\n\x06Z\xc9\xe0\xe9\xea\xe7\xb2\xce)\xa1\t\x00\xda`7\xe8' )
+
+ def getOfflineBitmap():
+ return wxBitmapFromImage(getOfflineImage())
+@@ -264,3 +401,28 @@
+ stream = cStringIO.StringIO(getUpData())
+ return wxImageFromStream(stream)
+
++#----------------------------------------------------------------------
++def getYellowData():
++ return zlib.decompress(
++'x\xda\x01Z\x01\xa5\xfe\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\
++\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\
++\x08\x08\x08|\x08d\x88\x00\x00\x01\x11IDATx\x9c\x95\x92\xbfN\x02A\x10\xc6\
++\xbf!\xac\t\x85q\x07\x1a\x1aJ\x13{\x1bihllh\xb0\xd3{\x07\x9e\x81G\xe0\x1d\
++\xb4\xa4"!6\xd4^cCe\xc2\x0b\xd0xcabrW\x8c\x05\xdcqd\xe7\x8es\x9a\xcd\xce\x97\
++\xef\xb7\xf3g\xc9{\x0f+d\xd1Q~\xfc%S,\x05Y\x00Yt\x14\x17W\xc5\x9d\xc7\xbbJP\
++\xcbJ\x16/;\x8f\x9f\xec\xb2\xb6\x02\x13\x90\x9b\x01`0\xd9\x92\xbc\xddh%@\xd6\
++C[t\x0c8\x86\xac\x87\n\xc7\xb0 \xb2\xec+\xe9\xe6I\x01\x80G\xab\xa0\xcf\x1c\
++\xce\xf7\xef\xa1\xb6\xec+\x00\xb4y\xb4"\x89#\x958R\xa4\tN`\x8e\x03\x182\xd9\
++\'\xb2o\xf0xG\xe4\xbd\x87|L\x0f\xe2\x97\xddh\x9a\x94\x8c\xfb\x93\x1f>\t8\x0c\
++\x91o\xe7g\xf7]\x8e\xdc\\\x00\n\x88\xeb\x01\xae\xd7\xd8\x0cT|$\x00\x908:N=M\
++\xcc!\x9fT\x10D\xa9\x92*s= 7\xdf\xbd\xd4\xce\xc7\x04\xc8f\xa6M\xcc&@\xb6\xaf\
++\x8av\xb7\xf1f\x02\x00_?\xffk\xa5\x7f\xd3\x0fZ\x88\xbc\xbf\xaa\xee\x00\x00\
++\x00\x00IEND\xaeB`\x82H\xf3\x8b\xc6' )
++
++def getYellowBitmap():
++ return wxBitmapFromImage(getYellowImage())
++
++def getYellowImage():
++ stream = cStringIO.StringIO(getYellowData())
++ return wxImageFromStream(stream)
++
+Files pyslsk-1.0.0/pysoulseek/wxgui/images.pyc and slsk-tmp/pysoulseek/wxgui/images.pyc differ
+Files pyslsk-1.0.0/pysoulseek/wxgui/__init__.pyc and slsk-tmp/pysoulseek/wxgui/__init__.pyc differ
+diff -rNu3 pyslsk-1.0.0/pysoulseek/wxgui/notebook.py slsk-tmp/pysoulseek/wxgui/notebook.py
+--- pyslsk-1.0.0/pysoulseek/wxgui/notebook.py 2003-03-02 17:44:25.000000000 +0100
++++ slsk-tmp/pysoulseek/wxgui/notebook.py 2003-03-12 21:31:36.000000000 +0100
+@@ -15,31 +15,40 @@
+
+ self.imglist = wxImageList(18,18)
+ self.offline = self.imglist.Add(images.getOnlineBitmap())
++ self.yellow = self.imglist.Add(images.getYellowBitmap())
+ self.AssignImageList(self.imglist)
+ self.pages = []
++ self.hilites = {}
+ self.page = 0
+
+ def OnPageChanged(self, event):
++ self.hilites[self.pages[self.page]] = 0
+ self.page = event.GetSelection()
++ self.hilites[self.pages[self.page]] = 0
+ self.SetPageImage(self.page,-1)
+
+ def AddPage(self, page, title):
+ wxNotebook.AddPage(self, page, title)
++ self.hilites[page] = 0
+ self.pages.append(page)
+
+ def DeleteAllPages(self):
+ wxNotebook.DeleteAllPages(self)
+ self.pages = []
++ self.hilites = {}
+ self.page = 0
+
+ def DeletePage(self, index):
+ wxNotebook.DeletePage(self, index)
++ del self.hilites[self.pages[index]]
+ del self.pages[index]
+ self.page = self.GetSelection()
+ if index <= self.GetSelection():
+ self.page = self.page - 1
+
+- def OnPageUpdated(self, pageobj):
++ def OnPageUpdated(self, pageobj, hilite=False):
++ if hilite:
++ self.hilites[pageobj] = 1
+ if self.pages[self.page] != pageobj:
+- self.SetPageImage(self.pages.index(pageobj),0)
++ self.SetPageImage(self.pages.index(pageobj),self.hilites[pageobj])
+
+Files pyslsk-1.0.0/pysoulseek/wxgui/notebook.pyc and slsk-tmp/pysoulseek/wxgui/notebook.pyc differ
+diff -rNu3 pyslsk-1.0.0/pysoulseek/wxgui/search.py slsk-tmp/pysoulseek/wxgui/search.py
+--- pyslsk-1.0.0/pysoulseek/wxgui/search.py 2003-03-12 18:59:56.000000000 +0100
++++ slsk-tmp/pysoulseek/wxgui/search.py 2003-03-14 22:04:23.000000000 +0100
+@@ -16,7 +16,7 @@
+ import notebook
+ from sortablelist import sortableListCtrl
+ from wxPython.wx import *
+-import locale
++from pysoulseek.utils import Humanize
+
+ class SearchWindow(wxPanel):
+ """ A search window with notebook that contains search results.
+@@ -54,12 +54,16 @@
+ self.DoSearch(self.frame.np.encode(self.search.GetLineText(0)))
+ self.search.Clear()
+
+- def DoSearch(self, text):
++ def DoSearch(self, text, user = None):
+ requestid = wxNewId()
+ tab, list = self.MakeSearchTab()
+ self.searches[requestid] = (list,text)
+- self.resultsnb.AddPage(tab,text)
+- self.queue.put(slskmessages.FileSearch(requestid,text))
++ if user is None:
++ self.resultsnb.AddPage(tab,text)
++ self.queue.put(slskmessages.FileSearch(requestid,text))
++ else:
++ self.resultsnb.AddPage(tab,"[%s] %s" % (user, text))
++ self.processrequest(user, slskmessages.FileSearchRequest(None,requestid,text))
+
+ def ShowResult(self, msg, username):
+ """ Show search result."""
+@@ -69,17 +73,42 @@
+ tab, list = self.MakeSearchTab()
+ self.searches[msg.token] = (list, text)
+ self.resultsnb.AddPage(tab,text)
+- self.searches[msg.token][0].AddResult(msg, username)
+- self.onupdate(self)
+- self.resultsnb.OnPageUpdated(self.searches[msg.token][0].parent)
++ results = self.searches[msg.token][0].AddResult(msg, username)
++ if results:
++ self.onupdate(self)
++ self.resultsnb.OnPageUpdated(self.searches[msg.token][0].parent)
+
+ def MakeSearchTab(self):
+ """ Create a result window, which is a notebook tab. """
+ panel = wxPanel(self.resultsnb, -1)
++ filterinstr = wxStaticText(panel, -1, label = " Filter In: ")
++ filterin = wxTextCtrl(panel, -1, value = "", style = wxTE_PROCESS_ENTER)
++ filteroutstr = wxStaticText(panel, -1, label = " Filter Out: ")
++ filterout = wxTextCtrl(panel, -1, value = "", style = wxTE_PROCESS_ENTER)
++ filtersizestr = wxStaticText(panel, -1, label = " Size: ")
++ filtersize = wxTextCtrl(panel, -1, value = "", style = wxTE_PROCESS_ENTER)
++ filterbrstr = wxStaticText(panel, -1, label = " Bitrate: ")
++ filterbr = wxTextCtrl(panel, -1, value = "", size = wxSize(45,20), style = wxTE_PROCESS_ENTER)
++ filterfree = wxCheckBox(panel, -1, label = "free slot")
+ close = wxButton(panel, -1, "Close")
+ closeignore = wxButton(panel, -1, "Close and ignore")
++ panel.filterin = filterin
++ panel.filterout = filterout
++ panel.filtersize = filtersize
++ panel.filterbr = filterbr
++ panel.filterfree = filterfree
+ list = SearchList(panel, -1, self.processrequest, self.privatechat, self.info, self.browse, self.transfers,self.frame)
+ sizerh = wxBoxSizer(wxHORIZONTAL)
++ sizerh.Add(filterinstr, flag = wxALIGN_CENTER)
++ sizerh.Add(filterin)
++ sizerh.Add(filteroutstr, flag = wxALIGN_CENTER)
++ sizerh.Add(filterout)
++ sizerh.Add(filtersizestr, flag = wxALIGN_CENTER)
++ sizerh.Add(filtersize)
++ sizerh.Add(filterbrstr, flag = wxALIGN_CENTER)
++ sizerh.Add(filterbr)
++ sizerh.Add(filterfree, flag = wxALIGN_CENTER|wxLEFT, border=5)
++
+ sizerh.Add(60,10,1,wxEXPAND)
+ sizerh.Add(closeignore)
+ sizerh.Add(close)
+@@ -90,6 +119,12 @@
+ panel.SetAutoLayout(True)
+ EVT_BUTTON(self, close.GetId(), self.OnClose)
+ EVT_BUTTON(self, closeignore.GetId(), self.OnCloseIgnore)
++ EVT_TEXT_ENTER(self, filterin.GetId(), list.OnRefilter)
++ EVT_TEXT_ENTER(self, filterout.GetId(), list.OnRefilter)
++ EVT_TEXT_ENTER(self, filtersize.GetId(), list.OnRefilter)
++ EVT_TEXT_ENTER(self, filterbr.GetId(), list.OnRefilter)
++ EVT_CHECKBOX(self, filterfree.GetId(), list.OnRefilter)
++
+ return panel,list
+
+ def Close(self, ignore):
+@@ -141,6 +176,9 @@
+ self.frame = frame
+
+ self.results = []
++ self.results_visible = []
++
++ self.UpdateColours()
+
+ self.menu = wxMenu()
+ downloadID=wxNewId()
+@@ -172,12 +210,16 @@
+ EVT_RIGHT_UP(self,self.OnRightUp)
+
+
++ def UpdateColours(self):
++ self.normal.SetTextColour(self.frame.np.config.sections["ui"]["search"])
++ self.grey.SetTextColour(self.frame.np.config.sections["ui"]["searchq"])
++
+ def OnRightUp(self,event):
+ """ Pops up a menu on a right-click in users list."""
+ pt = event.GetPosition()
+ item, flags = self.HitTest(pt)
+ self.id = item
+- self.selecteduser = self.results[self.id][1]
++ self.selecteduser = self.results_visible[self.id][1]
+ if item >= 0:
+ self.SetItemState(item,wxLIST_STATE_FOCUSED,wxLIST_STATE_FOCUSED)
+ self.PopupMenu(self.menu, pt)
+@@ -201,6 +243,11 @@
+ def OnShowIP(self, event):
+ self.frame.np.queue.put(slskmessages.GetPeerAddress(self.selecteduser))
+
++ def OnBanUser(self, event):
++ self.frame.BanUser(self.selecteduser)
++
++ def OnShowIP(self, event):
++ self.frame.np.queue.put(slskmessages.GetPeerAddress(self.selecteduser))
+
+ def OnDownload(self, event):
+ item = -1
+@@ -208,20 +255,78 @@
+ item = self.GetNextItem(item,wxLIST_NEXT_ALL,wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED)
+ if item == -1:
+ break
+- self.transfers.getFile(self.results[item][1],self.results[item][6]+self.results[item][0])
++ self.transfers.getFile(self.results_visible[item][1],self.results_visible[item][6]+self.results_visible[item][0])
+
+ def OnDownloadFolder(self,event):
+- self.processrequest(self.selecteduser, slskmessages.FolderContentsRequest(None,self.results[self.id][6]))
++ self.processrequest(self.selecteduser, slskmessages.FolderContentsRequest(None,self.results_visible[self.id][6]))
+
++ def checkDigit(self, filter, value, factorize = True):
++ op = ">="
++ if filter[:1] in (">", "<", "="):
++ op, filter = filter[:1]+"=", filter[1:]
++
++ if not filter:
++ return True
++
++ factor = 1
++ if factorize:
++ if filter.lower()[-1] == "g":
++ factor = 1024*1024*1024
++ filter = filter[:-1]
++ elif filter.lower()[-1] == "m":
++ factor = 1024*1024
++ filter = filter[:-1]
++ elif filter.lower()[-1] == "k":
++ factor = 1024
++ filter = filter[:-1]
++
++ if not filter:
++ return True
++
++ if not filter.isdigit():
++ return True
++
++ filter = long(filter) * factor
++
++ if eval(str(value)+op+str(filter)):
++ return True
++
++ return False
++
++ def checkFilter(self, name, size, br, free):
++ filterin = self.parent.filterin.GetLineText(0)
++ filterout = self.parent.filterout.GetLineText(0)
++ filtersize = self.parent.filtersize.GetLineText(0).replace(" ", "")
++ filterbr = self.parent.filterbr.GetLineText(0).replace(" ", "")
++ shown = True
++
++ if self.parent.filterfree.GetValue() and not free:
++ shown = False
++
++ if name.replace("_", " ").lower().find(filterin) == -1:
++ shown = False
++
++ if shown and filterout and name.replace("_"," ").lower().find(filterout) >= 0:
++ shown = False
++
++ if shown and filtersize and not self.checkDigit(filtersize, size):
++ shown = False
++
++ if shown and filterbr and not self.checkDigit(filterbr, br, False):
++ shown = False
++
++ return shown
++
+ def AddResult(self, msg, username):
+ """ Add a result to the list."""
+- import string
++ results = 0
+ for i in msg.list:
+- s = string.split(i[1],'\\')
++ s = i[1].split("\\")
+ name = s[-1]
+ dir = i[1][:-len(name)]
+ user = username
+ size = i[2]
++ br = 0
+
+ if i[3] == 'mp3' and len(i[4]) == 3:
+ attrs = i[4]
+@@ -236,22 +341,29 @@
+ attributes = ""
+ else:
+ attributes = str(i[4])
+- self.results.append([name,user,size,msg.ulspeed,msg.inqueue,attributes,dir,msg.freeulslots])
+- self.SetItemCount(len(self.results))
++
++ self.results.append([name,user,size,msg.ulspeed,msg.inqueue,attributes,dir,msg.freeulslots,br])
++
++ if self.checkFilter(name, size, br, msg.freeulslots):
++ self.results_visible.append([name,user,size,msg.ulspeed,msg.inqueue,attributes,dir,msg.freeulslots,br])
++ results += 1
++
++ self.SetItemCount(len(self.results_visible))
++ return results
+
+ def OnGetItemText(self, item, col):
+ import types
+- text = self.results[item][col]
++ text = self.results_visible[item][col]
+ if type(text) == types.StringType:
+ if len(text) > 0:
+ return self.frame.np.decode(text,wxUSE_UNICODE)
+ else:
+ return ''
+ else:
+- return locale.format("%s",text,1)
++ return Humanize(text,self.frame.np.config.sections["ui"]["decimalsep"])
+
+ def OnGetItemAttr(self, item):
+- if self.results[item][7] == 0:
++ if self.results_visible[item][7] == 0:
+ return self.grey
+ else:
+ return self.normal
+@@ -259,9 +371,17 @@
+ def OnGetItemImage(self, item):
+ return -1
+
++ def refilter(self):
++ self.results_visible = [i for i in self.results if self.checkFilter(i[0], i[2], i[8], i[7])]
++ self.SetItemCount(len(self.results_visible))
++
++ def OnRefilter(self, event):
++ self.refilter()
++
+ def SortList(self, col, order):
++ self.refilter()
+ if order == 0:
+- self.results.sort(lambda x,y: self.cmp(x[col],y[col]))
++ self.results_visible.sort(lambda x,y: self.cmp(x[col],y[col]))
+ else:
+- self.results.sort(lambda y,x: self.cmp(x[col],y[col]))
++ self.results_visible.sort(lambda y,x: self.cmp(x[col],y[col]))
+
+diff -rNu3 pyslsk-1.0.0/pysoulseek/wxgui/search.py~ slsk-tmp/pysoulseek/wxgui/search.py~
+--- pyslsk-1.0.0/pysoulseek/wxgui/search.py~ 1970-01-01 01:00:00.000000000 +0100
++++ slsk-tmp/pysoulseek/wxgui/search.py~ 2003-03-12 21:46:18.000000000 +0100
+@@ -0,0 +1,279 @@
++# Copyright (c) 2001-2003 Alexander Kanavin. All rights reserved.
++
++"""
++This module contains GUI classes for displaying search results.
++"""
++
++import time
++from pysoulseek import slskproto
++from pysoulseek import slskmessages
++import Queue
++import threading
++import images
++import about
++import userinfobrowse
++import search
++import notebook
++from sortablelist import sortableListCtrl
++from wxPython.wx import *
++from pysoulseek.utils import Humanize
++
++class SearchWindow(wxPanel):
++ """ A search window with notebook that contains search results.
++ searches contains pointers to windows that display them."""
++ def __init__(self, parent, id, queue, processrequest, privatechat, info, browse, transfers, onupdate,frame):
++ wxPanel.__init__(self, parent, id)
++ self.queue = queue
++ self.processrequest = processrequest
++ self.privatechat = privatechat
++ self.info = info
++ self.browse = browse
++ self.transfers = transfers
++ self.onupdate = onupdate
++ self.frame = frame
++ self.search = wxTextCtrl(self,-1, style = wxTE_PROCESS_ENTER)
++ self.searchbutton = wxButton(self, -1, "Search")
++ self.resultsnb = notebook.IconNotebook(self, -1, style = wxCLIP_CHILDREN)
++ sizerh = wxBoxSizer(wxHORIZONTAL)
++ sizerh.Add(self.search,1,wxEXPAND)
++ sizerh.Add(self.searchbutton,0,wxEXPAND)
++ sizerv = wxBoxSizer(wxVERTICAL)
++ sizerv.Add(sizerh,0,wxEXPAND)
++ sizerv.Add(self.resultsnb,1,wxEXPAND)
++
++ self.SetSizer(sizerv)
++ self.SetAutoLayout(True)
++
++ EVT_BUTTON(self, self.searchbutton.GetId(), self.OnSearch)
++ EVT_TEXT_ENTER(self,self.search.GetId(), self.OnSearch)
++
++ self.searches = {}
++
++ def OnSearch(self, event):
++ """ Process search request"""
++ self.DoSearch(self.frame.np.encode(self.search.GetLineText(0)))
++ self.search.Clear()
++
++ def DoSearch(self, text):
++ requestid = wxNewId()
++ tab, list = self.MakeSearchTab()
++ self.searches[requestid] = (list,text)
++ self.resultsnb.AddPage(tab,text)
++ self.queue.put(slskmessages.FileSearch(requestid,text))
++
++ def ShowResult(self, msg, username):
++ """ Show search result."""
++ if self.searches.has_key(msg.token):
++ if self.searches[msg.token][0] is None:
++ text = self.searches[msg.token][1]
++ tab, list = self.MakeSearchTab()
++ self.searches[msg.token] = (list, text)
++ self.resultsnb.AddPage(tab,text)
++ self.searches[msg.token][0].AddResult(msg, username)
++ self.onupdate(self)
++ self.resultsnb.OnPageUpdated(self.searches[msg.token][0].parent)
++
++ def MakeSearchTab(self):
++ """ Create a result window, which is a notebook tab. """
++ panel = wxPanel(self.resultsnb, -1)
++ close = wxButton(panel, -1, "Close")
++ closeignore = wxButton(panel, -1, "Close and ignore")
++ list = SearchList(panel, -1, self.processrequest, self.privatechat, self.info, self.browse, self.transfers,self.frame)
++ sizerh = wxBoxSizer(wxHORIZONTAL)
++ sizerh.Add(60,10,1,wxEXPAND)
++ sizerh.Add(closeignore)
++ sizerh.Add(close)
++ sizerv = wxBoxSizer(wxVERTICAL)
++ sizerv.Add(sizerh,0,wxEXPAND)
++ sizerv.Add(list,1,wxEXPAND)
++ panel.SetSizer(sizerv)
++ panel.SetAutoLayout(True)
++ EVT_BUTTON(self, close.GetId(), self.OnClose)
++ EVT_BUTTON(self, closeignore.GetId(), self.OnCloseIgnore)
++ return panel,list
++
++ def Close(self, ignore):
++ """ Close the search results window."""
++ selectednum = self.resultsnb.GetSelection()
++ selected = self.resultsnb.GetPage(selectednum)
++ for i in self.searches.keys():
++ if self.searches[i][0] is not None and self.searches[i][0].GetParent() == selected:
++ self.searches[i] = (None,self.searches[i][1])
++ if ignore:
++ del self.searches[i]
++
++ parent = self.resultsnb
++ self.resultsnb.DeletePage(selectednum)
++ if parent.GetPageCount() > 0:
++ parent.SetSelection(0)
++
++ def OnClose(self, event):
++ self.Close(0)
++
++ def OnCloseIgnore(self, event):
++ self.Close(1)
++
++class SearchList(sortableListCtrl):
++ """ List of search results."""
++ def __init__(self, parent, id, processrequest, privatechat, info, browse, transfers,frame,style = wxLC_REPORT|wxLC_VIRTUAL|wxLC_VRULES|wxSUNKEN_BORDER):
++ sortableListCtrl.__init__(self,parent,id,style = style)
++ self.InsertColumn(0,"Filename", width=250)
++ self.InsertColumn(1,"User", width = 100)
++ self.InsertColumn(2,"Size",width=100,format=wxLIST_FORMAT_RIGHT)
++ self.InsertColumn(3,"Speed",width=50,format=wxLIST_FORMAT_RIGHT)
++ self.InsertColumn(4,"In queue",width=50,format=wxLIST_FORMAT_RIGHT)
++ self.InsertColumn(5,"Attributes",width=150)
++ self.InsertColumn(6,"Directory",width=500)
++ self.SetItemCount(0)
++
++ self.normal = wxListItemAttr()
++ self.grey = wxListItemAttr()
++ self.grey.SetTextColour("grey")
++ self.red = wxListItemAttr()
++ self.red.SetTextColour("red")
++
++ self.parent = parent
++ self.processrequest = processrequest
++ self.privatechat = privatechat
++ self.info = info
++ self.browse = browse
++ self.transfers = transfers
++ self.frame = frame
++
++ self.results = []
++
++ self.UpdateColours()
++
++ self.menu = wxMenu()
++ downloadID=wxNewId()
++ self.menu.Append(downloadID, 'Download File(s)')
++ EVT_MENU(self,downloadID, self.OnDownload)
++ downloadfolderID=wxNewId()
++ self.menu.Append(downloadfolderID, 'Download Containing Folder')
++ EVT_MENU(self,downloadfolderID, self.OnDownloadFolder)
++ self.menu.AppendSeparator()
++ sendmessageID=wxNewId()
++ self.menu.Append(sendmessageID, 'Send Message')
++ EVT_MENU(self,sendmessageID, self.OnSendMessage)
++ getinfoID=wxNewId()
++ self.menu.Append(getinfoID, 'Get User Info')
++ EVT_MENU(self,getinfoID, self.OnGetInfo)
++ browseID=wxNewId()
++ self.menu.Append(browseID, 'Browse Files')
++ EVT_MENU(self,browseID, self.OnBrowse)
++ showIpID=wxNewId()
++ self.menu.Append(showIpID, "Show IP")
++ EVT_MENU(self,showIpID, self.OnShowIP)
++ addtolistID=wxNewId()
++ self.menu.Append(addtolistID, 'Add to User List')
++ EVT_MENU(self,addtolistID, self.OnAddToList)
++ banuserID=wxNewId()
++ self.menu.Append(banuserID, 'Ban this User')
++ EVT_MENU(self,banuserID, self.OnBanUser)
++
++ EVT_RIGHT_UP(self,self.OnRightUp)
++
++
++ def UpdateColours(self):
++ self.normal.SetTextColour(self.frame.np.config.sections["ui"]["search"])
++ self.grey.SetTextColour(self.frame.np.config.sections["ui"]["searchq"])
++
++ def OnRightUp(self,event):
++ """ Pops up a menu on a right-click in users list."""
++ pt = event.GetPosition()
++ item, flags = self.HitTest(pt)
++ self.id = item
++ self.selecteduser = self.results[self.id][1]
++ if item >= 0:
++ self.SetItemState(item,wxLIST_STATE_FOCUSED,wxLIST_STATE_FOCUSED)
++ self.PopupMenu(self.menu, pt)
++
++ """ Handlers for the menu items"""
++ def OnSendMessage(self, event):
++ self.privatechat.SendMessage(self.selecteduser)
++
++ def OnGetInfo(self, event):
++ self.processrequest(self.selecteduser, slskmessages.UserInfoRequest(None), self.info)
++
++ def OnBrowse(self, event):
++ self.processrequest(self.selecteduser, slskmessages.GetSharedFileList(None), self.browse)
++
++ def OnAddToList(self, event):
++ self.frame.AddToList(self.selecteduser)
++
++ def OnBanUser(self, event):
++ self.frame.BanUser(self.selecteduser)
++
++ def OnShowIP(self, event):
++ self.frame.np.queue.put(slskmessages.GetPeerAddress(self.selecteduser))
++
++
++ def OnBanUser(self, event):
++ self.frame.BanUser(self.selecteduser)
++
++ def OnShowIP(self, event):
++ self.frame.np.queue.put(slskmessages.GetPeerAddress(self.selecteduser))
++
++ def OnDownload(self, event):
++ item = -1
++ while 1:
++ item = self.GetNextItem(item,wxLIST_NEXT_ALL,wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED)
++ if item == -1:
++ break
++ self.transfers.getFile(self.results[item][1],self.results[item][6]+self.results[item][0])
++
++ def OnDownloadFolder(self,event):
++ self.processrequest(self.selecteduser, slskmessages.FolderContentsRequest(None,self.results[self.id][6]))
++
++ def AddResult(self, msg, username):
++ """ Add a result to the list."""
++ import string
++ for i in msg.list:
++ s = string.split(i[1],'\\')
++ name = s[-1]
++ dir = i[1][:-len(name)]
++ user = username
++ size = i[2]
++
++ if i[3] == 'mp3' and len(i[4]) == 3:
++ attrs = i[4]
++ if attrs[2] == 1:
++ brs = 'VBR'
++ else:
++ brs = 'Bitrate'
++ br = attrs[0]
++ length = '%i:%02i' %(attrs[1] / 60, attrs[1] % 60)
++ attributes = '%s: %i, Length: %s' %(brs,br,length)
++ elif i[3] == '':
++ attributes = ""
++ else:
++ attributes = str(i[4])
++ self.results.append([name,user,size,msg.ulspeed,msg.inqueue,attributes,dir,msg.freeulslots])
++ self.SetItemCount(len(self.results))
++
++ def OnGetItemText(self, item, col):
++ import types
++ text = self.results[item][col]
++ if type(text) == types.StringType:
++ if len(text) > 0:
++ return self.frame.np.decode(text,wxUSE_UNICODE)
++ else:
++ return ''
++ else:
++ return Humanize(text,self.frame.np.config.sections["ui"]["decimalsep"])
++
++ def OnGetItemAttr(self, item):
++ if self.results[item][7] == 0:
++ return self.grey
++ else:
++ return self.normal
++
++ def OnGetItemImage(self, item):
++ return -1
++
++ def SortList(self, col, order):
++ if order == 0:
++ self.results.sort(lambda x,y: self.cmp(x[col],y[col]))
++ else:
++ self.results.sort(lambda y,x: self.cmp(x[col],y[col]))
++
+Files pyslsk-1.0.0/pysoulseek/wxgui/search.pyc and slsk-tmp/pysoulseek/wxgui/search.pyc differ
+Files pyslsk-1.0.0/pysoulseek/wxgui/sortablelist.pyc and slsk-tmp/pysoulseek/wxgui/sortablelist.pyc differ
+diff -rNu3 pyslsk-1.0.0/pysoulseek/wxgui/transfers.py slsk-tmp/pysoulseek/wxgui/transfers.py
+--- pyslsk-1.0.0/pysoulseek/wxgui/transfers.py 2003-03-12 19:03:40.000000000 +0100
++++ slsk-tmp/pysoulseek/wxgui/transfers.py 2003-03-12 21:47:39.000000000 +0100
+@@ -10,7 +10,7 @@
+ import string
+ import time
+ from sortablelist import sortableListCtrl
+-import locale
++from types import StringType
+
+ class TransfersList(sortableListCtrl):
+ """ This is a list control for transfers. Gets transfer data from transfer
+@@ -30,6 +30,26 @@
+ self.update(None)
+
+
++ def Humanize(self, size):
++ if size is None:
++ return None
++ priv = ""
++ if type(size) is StringType and size[-13:] == " (privileged)":
++ size, priv = size[:-13], size[-13:]
++ try:
++ s = int(size)
++ if s > 1024*1024*1024:
++ r = "%.2f GB" % ((float(s) / (1024.0*1024.0*1024.0)))
++ elif s > 1024*1024:
++ r = "%.2f MB" % ((float(s) / (1024.0*1024.0)))
++ elif s > 1024:
++ r = "%.2f KB" % ((float(s) / 1024.0))
++ else:
++ r = str(size)
++ return r + priv
++ except:
++ return size + priv
++
+ def update(self, item):
+ if item is not None:
+ if item in self.list:
+@@ -53,9 +73,9 @@
+ if col == 1:
+ return item.user
+ if col == 2:
+- return item.status
++ return self.Humanize(item.status)
+ if col == 3:
+- return locale.format("%s",item.size,1)
++ return self.Humanize(item.size)
+ if col == 4:
+ if item.speed is not None:
+ return "%.1f" %(item.speed)
+@@ -105,15 +125,15 @@
+ sendmessageID=wxNewId()
+ self.menu.Append(sendmessageID, 'Send Message')
+ EVT_MENU(self,sendmessageID, self.OnSendMessage)
+- getaddrID=wxNewId()
+- self.menu.Append(getaddrID, 'Show IP address')
+- EVT_MENU(self,getaddrID, self.OnGetAddr)
+ getinfoID=wxNewId()
+ self.menu.Append(getinfoID, 'Get User Info')
+ EVT_MENU(self,getinfoID, self.OnGetInfo)
+ browseID=wxNewId()
+ self.menu.Append(browseID, 'Browse Files')
+ EVT_MENU(self,browseID, self.OnBrowse)
++ getaddrID=wxNewId()
++ self.menu.Append(getaddrID, 'Show IP address')
++ EVT_MENU(self,getaddrID, self.OnGetAddr)
+ addtolistID=wxNewId()
+ self.menu.Append(addtolistID, 'Add to User List')
+ EVT_MENU(self,addtolistID, self.OnAddToList)
+Files pyslsk-1.0.0/pysoulseek/wxgui/transfers.pyc and slsk-tmp/pysoulseek/wxgui/transfers.pyc differ
+diff -rNu3 pyslsk-1.0.0/pysoulseek/wxgui/userinfobrowse.py slsk-tmp/pysoulseek/wxgui/userinfobrowse.py
+--- pyslsk-1.0.0/pysoulseek/wxgui/userinfobrowse.py 2003-03-12 19:24:40.000000000 +0100
++++ slsk-tmp/pysoulseek/wxgui/userinfobrowse.py 2003-03-13 00:18:04.000000000 +0100
+@@ -16,7 +16,8 @@
+ import notebook
+ from sortablelist import sortableListCtrl
+ from wxPython.wx import *
+-import locale
++from pysoulseek.utils import Humanize
++import string
+
+ class UserNotebook(notebook.IconNotebook):
+ """ This is a notebook with user's information. Used to show either
+@@ -86,16 +87,15 @@
+ self.image = None
+
+ buttonsizer = wxBoxSizer(wxVERTICAL)
++ buttonsizer.Add(self.chat, 0, wxEXPAND|wxTOP|wxLEFT|wxRIGHT, border=10)
++ buttonsizer.Add(self.browse,0,wxEXPAND|wxTOP|wxLEFT|wxRIGHT, border=10)
++ buttonsizer.Add(self.getip, 0, wxEXPAND|wxTOP|wxLEFT|wxRIGHT, border=10)
++ buttonsizer.Add(self.addtolist,0,wxEXPAND|wxTOP|wxLEFT|wxRIGHT, border=10)
++ buttonsizer.Add(self.ban,0,wxEXPAND|wxTOP|wxLEFT|wxRIGHT, border=10)
++ buttonsizer.Add(self.savepic,0,wxEXPAND|wxTOP|wxLEFT|wxRIGHT, border=10)
+ buttonsizer.Add(0,0,1,wxEXPAND)
+- buttonsizer.Add(self.chat, 0, wxEXPAND, border=10)
+- buttonsizer.Add(self.browse,0,wxEXPAND, border=10)
+- buttonsizer.Add(self.addtolist,0,wxEXPAND, border=10)
+- buttonsizer.Add(self.getip, 0, wxEXPAND, border=10)
+- buttonsizer.Add(self.ban,0,wxEXPAND, border=10)
+- buttonsizer.Add(self.savepic,0,wxEXPAND, border=10)
+- buttonsizer.Add(self.refresh,0,wxEXPAND, border=10)
+- buttonsizer.Add(10,10,0,wxEXPAND)
+- buttonsizer.Add(self.close,0,wxEXPAND, border=10)
++ buttonsizer.Add(self.refresh,0,wxEXPAND|wxLEFT|wxRIGHT, border=10)
++ buttonsizer.Add(self.close,0,wxEXPAND|wxALL, border=10)
+
+ sizerv = wxBoxSizer(wxVERTICAL)
+ sizerv.Add(wxStaticText(self,-1, "Self-description:"))
+@@ -184,9 +184,15 @@
+ def OnBan(self, event):
+ self.frame.BanUser(self.user)
+
++ def OnChat(self, event):
++ self.frame.np.privatechat.SendMessage(self.user)
++
+ def OnBrowse(self, event):
+ self.frame.np.ProcessRequestToPeer(self.user, slskmessages.GetSharedFileList(None), self.frame.np.userbrowse)
+
++ def OnBan(self, event):
++ self.frame.BanUser(self.user)
++
+ def OnClose(self, event):
+ """ Closes the window"""
+ del self.parent.users[self.user]
+@@ -202,9 +208,15 @@
+ self.frame.np.ProcessRequestToPeer(self.user, slskmessages.UserInfoRequest(None), self.frame.np.userinfo)
+
+
++ def OnShowIp(self, event):
++ self.frame.np.queue.put(slskmessages.GetPeerAddress(self.user))
++
++ def OnRefresh(self, event):
++ self.frame.np.ProcessRequestToPeer(self.user, slskmessages.UserInfoRequest(None), self.frame.np.userinfo)
++
+ class FileListCtrl(sortableListCtrl):
+ """ This is a list of a user's files in a particular directory"""
+- def __init__(self, parent, id, style = wxLC_REPORT|wxLC_VIRTUAL|wxSUNKEN_BORDER|wxLC_VRULES):
++ def __init__(self, parent, id, frame, style = wxLC_REPORT|wxLC_VIRTUAL|wxSUNKEN_BORDER|wxLC_VRULES):
+ sortableListCtrl.__init__(self,parent,id,style = style)
+ self.InsertColumn(0,"Filename", width=200)
+ self.InsertColumn(1,"Size",width=100,format=wxLIST_FORMAT_RIGHT)
+@@ -214,6 +226,8 @@
+ self.dir = None
+
+ self.parent = parent
++ self.frame = frame
++ self.curtreeitem = None
+
+ def SetFileList(self, list):
+ """ Actually sets the list."""
+@@ -237,7 +251,7 @@
+ return item[1]
+ if col == 1:
+ if not sort:
+- return locale.format("%s", item[2], 1)
++ return Humanize(item[2],self.frame.np.config.sections["ui"]["decimalsep"])
+ else:
+ return item[2]
+ if col == 2:
+@@ -290,7 +304,7 @@
+ self.refresh = wxButton(self, -1, "Refresh")
+ splitter = wxSplitterWindow(self,-1,style=wxNO_3D|wxSP_3DSASH)
+ self.tree = DirTreeCtrl(splitter, -1)
+- self.listctrl = FileListCtrl(splitter,-1)
++ self.listctrl = FileListCtrl(splitter,-1,frame)
+
+ sizerlowh = wxBoxSizer(wxHORIZONTAL)
+ sizerlowh.Add(self.gauge,0,wxEXPAND)
+@@ -339,6 +353,9 @@
+ downloaddirID=wxNewId()
+ self.treemenu.Append(downloaddirID, 'Download Directory')
+ EVT_MENU(self.tree,downloaddirID, self.OnDownloadDir)
++ recdownloaddirID=wxNewId()
++ self.treemenu.Append(recdownloaddirID, 'Recursively Download Dir')
++ EVT_MENU(self.tree,recdownloaddirID, self.OnRecDownloadDir)
+ downloadfileID=wxNewId()
+ self.listmenu.Append(downloadfileID, 'Download File(s)')
+ EVT_MENU(self.listctrl,downloadfileID, self.OnDownloadFile)
+@@ -348,14 +365,14 @@
+ self.treemenu.Append(sendmessageID, 'Send Message')
+ self.listmenu.Append(sendmessageID, 'Send Message')
+ EVT_MENU(self,sendmessageID, self.OnSendMessage)
+- showIp=wxNewId()
+- self.treemenu.Append(showIp, 'Show IP')
+- self.listmenu.Append(showIp, 'Show IP')
+- EVT_MENU(self,showIp, self.OnShowIp)
+ getinfoID=wxNewId()
+ self.treemenu.Append(getinfoID, 'Get User Info')
+ self.listmenu.Append(getinfoID, 'Get User Info')
+ EVT_MENU(self,getinfoID, self.OnGetInfo)
++ showIp=wxNewId()
++ self.treemenu.Append(showIp, 'Show IP')
++ self.listmenu.Append(showIp, 'Show IP')
++ EVT_MENU(self,showIp, self.OnShowIp)
+ addtolistID=wxNewId()
+ self.treemenu.Append(addtolistID, 'Add to User List')
+ self.listmenu.Append(addtolistID, 'Add to User List')
+@@ -428,7 +445,8 @@
+
+ def OnTreeSelChanged(self, event):
+ """ On selection of a tree item, display the filelist"""
+- self.listctrl.SetFileList(self.tree.GetPyData(event.GetItem()))
++ self.curtreeitem = event.GetItem()
++ self.listctrl.SetFileList(self.tree.GetPyData(self.curtreeitem))
+
+ def OnDownloadDir(self,event):
+ """ Get every file in the directory """
+@@ -466,6 +484,12 @@
+ def OnShowIp(self, event):
+ self.frame.np.queue.put(slskmessages.GetPeerAddress(self.user))
+
++ def OnRefresh(self, event):
++ self.frame.np.ProcessRequestToPeer(self.user, slskmessages.GetSharedFileList(None), self.frame.np.userbrowse)
++
++ def OnShowIp(self, event):
++ self.frame.np.queue.put(slskmessages.GetPeerAddress(self.user))
++
+ def OnSearch(self,event):
+ """ Process search request"""
+ text = self.search.GetLineText(0)
+@@ -506,3 +530,21 @@
+ list.extend(self.FindNodes(node[i][1],text))
+ return list
+
++ def OnRecDownloadDir(self,event):
++ """ Download directory recursively """
++ self.RecDownloadDir(self.curtreeitem)
++
++ def RecDownloadDir(self,item, path = ""):
++ """ Download directory recursively """
++ import os
++ (dir, flist) = self.tree.GetPyData(item)
++ ldir = string.split(dir,'\\')[-1] # local directory
++ ldir = os.path.join(path,ldir)
++ for i in flist: # Get the files in this dir
++ self.transfers.getFile(self.user, dir+'\\'+i[1], ldir)
++
++ if self.tree.ItemHasChildren(item): # Get the other dirs
++ i, cookie = self.tree.GetFirstChild(item, 0)
++ while i.IsOk():
++ self.RecDownloadDir(i, ldir)
++ i, cookie = self.tree.GetNextChild(item, cookie)
+Files pyslsk-1.0.0/pysoulseek/wxgui/userinfobrowse.pyc and slsk-tmp/pysoulseek/wxgui/userinfobrowse.pyc differ
+diff -rNu3 pyslsk-1.0.0/pysoulseek/wxgui/userlist.py slsk-tmp/pysoulseek/wxgui/userlist.py
+--- pyslsk-1.0.0/pysoulseek/wxgui/userlist.py 2003-03-12 19:04:53.000000000 +0100
++++ slsk-tmp/pysoulseek/wxgui/userlist.py 2003-03-12 21:55:17.000000000 +0100
+@@ -64,25 +64,25 @@
+ self.SetItemCount(len(self.parent.frame.userlist))
+
+ self.menu = wxMenu()
++ removeID=wxNewId()
++ self.menu.Append(removeID, 'Remove')
++ EVT_MENU(self,removeID,self.OnRemove)
++ self.menu.AppendSeparator()
+ sendmessageID=wxNewId()
+ self.menu.Append(sendmessageID, 'Send Message')
+ EVT_MENU(self,sendmessageID, self.OnSendMessage)
+- showipID=wxNewId()
+- self.menu.Append(showipID, 'Show IP address')
+- EVT_MENU(self,showipID, self.OnShowIp)
+ getinfoID=wxNewId()
+ self.menu.Append(getinfoID, 'Get User Info')
+ EVT_MENU(self,getinfoID, self.OnGetInfo)
+ browseID=wxNewId()
+ self.menu.Append(browseID, 'Browse Files')
+ EVT_MENU(self,browseID, self.OnBrowse)
++ showipID=wxNewId()
++ self.menu.Append(showipID, 'Show IP address')
++ EVT_MENU(self,showipID, self.OnShowIp)
+ banuserID=wxNewId()
+ self.menu.Append(banuserID, 'Ban this User')
+ EVT_MENU(self,banuserID, self.OnBanUser)
+- self.menu.AppendSeparator()
+- removeID=wxNewId()
+- self.menu.Append(removeID, 'Remove')
+- EVT_MENU(self,removeID,self.OnRemove)
+
+ EVT_RIGHT_UP(self,self.OnRightUp)
+
+@@ -121,6 +121,9 @@
+ def OnBanUser(self,event):
+ self.parent.frame.BanUser(self.focuseduser)
+
++ def OnBanUser(self,event):
++ self.parent.frame.BanUser(self.focuseduser)
++
+ def OnGetItemText(self, item, col):
+ user = self.parent.frame.userlist[item]
+ return self.GetColumnValue(user,col)
+Files pyslsk-1.0.0/pysoulseek/wxgui/userlist.pyc and slsk-tmp/pysoulseek/wxgui/userlist.pyc differ
+diff -rNu3 pyslsk-1.0.0/TODO.hyriand slsk-tmp/TODO.hyriand
+--- pyslsk-1.0.0/TODO.hyriand 1970-01-01 01:00:00.000000000 +0100
++++ slsk-tmp/TODO.hyriand 2003-03-12 21:31:36.000000000 +0100
+@@ -0,0 +1,6 @@
++ - Keyboard shortcuts (tabs/etc)
++
++ - Omni-present birdies? (to reduce flicker)
++ - Wishlist?
++ - Search filtering?
++ - wxHTML chat?
diff --git a/net-p2p/pysoulseek/pysoulseek-1.0.0-r1.ebuild b/net-p2p/pysoulseek/pysoulseek-1.0.0-r1.ebuild
new file mode 100644
index 000000000000..d103d5857fde
--- /dev/null
+++ b/net-p2p/pysoulseek/pysoulseek-1.0.0-r1.ebuild
@@ -0,0 +1,35 @@
+# Copyright 1999-2003 Gentoo Technologies, Inc.
+# Distributed under the terms of the GNU General Public License v2
+# $Header: /var/cvsroot/gentoo-x86/net-p2p/pysoulseek/pysoulseek-1.0.0-r1.ebuild,v 1.1 2003/03/16 16:59:04 liquidx Exp $
+
+IUSE="oggvorbis"
+
+inherit eutils
+
+MY_PN="${PN/soulseek/slsk}"
+MY_P="${MY_PN}-${PV}"
+DESCRIPTION="client for SoulSeek filesharing"
+HOMEPAGE="http://www.sensi.org/~ak/pyslsk/"
+SRC_URI="http://www.sensi.org/~ak/pyslsk/${MY_P}.tar.gz"
+
+LICENSE="GPL-2"
+SLOT="0"
+KEYWORDS="~x86 ~ppc ~sparc"
+
+DEPEND="=x11-libs/gtk+-1.2*
+ >=dev-lang/python-2.1
+ >=dev-python/wxPython-2.4.0.1
+ ~x11-libs/wxGTK-2.4.0
+ oggvorbis? ( media-libs/pyvorbis media-libs/pyogg )"
+
+S="${WORKDIR}/${MY_P}"
+
+src_compile() {
+ epatch ${FILESDIR}/${P}-hyriand-11.patch
+ python setup.py build || die "compile failed"
+}
+
+src_install() {
+ python setup.py install --prefix=/usr --root=${D} || die "install failed"
+ dodoc CHANGELOG KNOWN_BUGS MAINTAINERS MANIFEST PKG-INFO README TODO VERSION
+}
diff --git a/net-p2p/pysoulseek/pysoulseek-1.0.0.ebuild b/net-p2p/pysoulseek/pysoulseek-1.0.0.ebuild
index 0def49e38c61..99bc4445307f 100644
--- a/net-p2p/pysoulseek/pysoulseek-1.0.0.ebuild
+++ b/net-p2p/pysoulseek/pysoulseek-1.0.0.ebuild
@@ -1,6 +1,6 @@
# Copyright 1999-2003 Gentoo Technologies, Inc.
# Distributed under the terms of the GNU General Public License v2
-# $Header: /var/cvsroot/gentoo-x86/net-p2p/pysoulseek/pysoulseek-1.0.0.ebuild,v 1.1 2003/03/14 23:15:51 tantive Exp $
+# $Header: /var/cvsroot/gentoo-x86/net-p2p/pysoulseek/pysoulseek-1.0.0.ebuild,v 1.2 2003/03/16 16:59:04 liquidx Exp $
MY_PN="${PN/soulseek/slsk}"
MY_P="${MY_PN}-${PV}"
@@ -15,7 +15,7 @@ IUSE="oggvorbis"
DEPEND="=x11-libs/gtk+-1.2*
>=dev-lang/python-2.1
- ~dev-python/wxPython-2.4.0.1
+ >=dev-python/wxPython-2.4.0.1
~x11-libs/wxGTK-2.4.0
oggvorbis? ( media-libs/pyvorbis media-libs/pyogg )"