diff options
author | Alastair Tse <liquidx@gentoo.org> | 2003-03-16 16:59:04 +0000 |
---|---|---|
committer | Alastair Tse <liquidx@gentoo.org> | 2003-03-16 16:59:04 +0000 |
commit | 24228640448df7bf9a8c32f2800754756a5ab4fa (patch) | |
tree | d512b16a22fae76d5e24ba39371ab2133eba4332 /net-p2p | |
parent | ~x86 -> x86 (diff) | |
download | gentoo-2-24228640448df7bf9a8c32f2800754756a5ab4fa.tar.gz gentoo-2-24228640448df7bf9a8c32f2800754756a5ab4fa.tar.bz2 gentoo-2-24228640448df7bf9a8c32f2800754756a5ab4fa.zip |
patch added and deps touched
Diffstat (limited to 'net-p2p')
-rw-r--r-- | net-p2p/pysoulseek/ChangeLog | 7 | ||||
-rw-r--r-- | net-p2p/pysoulseek/files/digest-pysoulseek-1.0.0-r1 | 1 | ||||
-rw-r--r-- | net-p2p/pysoulseek/files/pysoulseek-1.0.0-hyriand-11.patch | 2566 | ||||
-rw-r--r-- | net-p2p/pysoulseek/pysoulseek-1.0.0-r1.ebuild | 35 | ||||
-rw-r--r-- | net-p2p/pysoulseek/pysoulseek-1.0.0.ebuild | 4 |
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 )" |