pesterchum/pesterchum.py
Stephen Dranger 666c2b88c0 fdsa
2011-02-14 17:54:44 -06:00

1880 lines
76 KiB
Python

# pesterchum
import logging
import os, sys
import os.path
from datetime import *
from string import Template
import random
import json
import codecs
import re
import socket
import platform
from PyQt4 import QtGui, QtCore
import pygame
from menus import PesterChooseQuirks, PesterChooseTheme, \
PesterChooseProfile, PesterOptions, PesterUserlist, PesterMemoList, \
LoadingScreen, AboutPesterchum
from dataobjs import PesterProfile, Mood, pesterQuirk, pesterQuirks
from generic import PesterIcon, RightClickList, MultiTextDialog, PesterList
from convo import PesterTabWindow, PesterText, PesterInput, PesterConvo
from parsetools import convertTags, addTimeInitial
from memos import PesterMemo, MemoTabWindow, TimeTracker
from irc import PesterIRC
class waitingMessageHolder(object):
def __init__(self, mainwindow, **msgfuncs):
self.mainwindow = mainwindow
self.funcs = msgfuncs
self.queue = msgfuncs.keys()
if len(self.queue) > 0:
self.mainwindow.updateSystemTray()
def waitingHandles(self):
return self.queue
def answerMessage(self):
func = self.funcs[self.queue[0]]
func()
def messageAnswered(self, handle):
if handle not in self.queue:
return
self.queue = [q for q in self.queue if q != handle]
del self.funcs[handle]
if len(self.queue) == 0:
self.mainwindow.updateSystemTray()
def addMessage(self, handle, func):
if not self.funcs.has_key(handle):
self.queue.append(handle)
self.funcs[handle] = func
if len(self.queue) > 0:
self.mainwindow.updateSystemTray()
def __len__(self):
return len(self.queue)
class NoneSound(object):
def play(self): pass
class PesterLog(object):
def __init__(self, handle):
self.handle = handle
self.convos = {}
def log(self, handle, msg):
bbcodemsg = convertTags(msg, "bbcode")
html = convertTags(msg, "html")+"<br />"
msg = convertTags(msg, "text")
modes = {"bbcode": bbcodemsg, "html": html, "text": msg}
if not self.convos.has_key(handle):
time = datetime.now().strftime("%Y-%m-%d.%H.%M")
self.convos[handle] = {}
for (format, t) in modes.iteritems():
if not os.path.exists("logs/%s/%s/%s" % (self.handle, handle, format)):
os.makedirs("logs/%s/%s/%s" % (self.handle, handle, format))
fp = codecs.open("logs/%s/%s/%s/%s.%s.txt" % (self.handle, handle, format, handle, time), encoding='utf-8', mode='a')
self.convos[handle][format] = fp
for (format, t) in modes.iteritems():
f = self.convos[handle][format]
if platform.system() == "Windows":
f.write(t+"\r\n")
else:
f.write(t+"\r\n")
f.flush()
def finish(self, handle):
if not self.convos.has_key(handle):
return
for f in self.convos[handle].values():
f.close()
del self.convos[handle]
def close(self):
for h in self.convos.keys():
for f in self.convos[h].values():
f.close()
class PesterProfileDB(dict):
def __init__(self):
try:
fp = open("logs/chums.js", 'r')
chumdict = json.load(fp)
fp.close()
except IOError:
chumdict = {}
fp = open("logs/chums.js", 'w')
json.dump(chumdict, fp)
fp.close()
converted = dict([(handle, PesterProfile(handle, color=QtGui.QColor(c['color']), mood=Mood(c['mood']))) for (handle, c) in chumdict.iteritems()])
self.update(converted)
def save(self):
fp = open("logs/chums.js", 'w')
chumdict = dict([p.plaindict() for p in self.itervalues()])
json.dump(chumdict, fp)
fp.close()
def getColor(self, handle, default=None):
if not self.has_key(handle):
return default
else:
return self[handle].color
def setColor(self, handle, color):
if self.has_key(handle):
self[handle].color = color
else:
self[handle] = PesterProfile(handle, color)
def __setitem__(self, key, val):
dict.__setitem__(self, key, val)
self.save()
class pesterTheme(dict):
def __init__(self, name, default=False):
self.path = "themes/%s" % (name)
self.name = name
fp = open(self.path+"/style.js")
theme = json.load(fp, object_hook=self.pathHook)
self.update(theme)
fp.close()
if self.has_key("inherits"):
self.inheritedTheme = pesterTheme(self["inherits"])
if not default:
self.defaultTheme = pesterTheme("pesterchum", default=True)
def __getitem__(self, key):
keys = key.split("/")
try:
v = dict.__getitem__(self, keys.pop(0))
except KeyError, e:
if hasattr(self, 'inheritedTheme'):
return self.inheritedTheme[key]
if hasattr(self, 'defaultTheme'):
return self.defaultTheme[key]
else:
raise e
for k in keys:
try:
v = v[k]
except KeyError, e:
if hasattr(self, 'inheritedTheme'):
return self.inheritedTheme[key]
if hasattr(self, 'defaultTheme'):
return self.defaultTheme[key]
else:
raise e
return v
def pathHook(self, d):
for (k, v) in d.iteritems():
if type(v) is unicode:
s = Template(v)
d[k] = s.safe_substitute(path=self.path)
return d
def get(self, key, default):
keys = key.split("/")
try:
v = dict.__getitem__(self, keys.pop(0))
except KeyError:
return default
for k in keys:
try:
v = v[k]
except KeyError:
return default
return v
def has_key(self, key):
keys = key.split("/")
try:
v = dict.__getitem__(self, keys.pop(0))
except KeyError:
return False
for k in keys:
try:
v = v[k]
except KeyError:
return False
return True
class userConfig(object):
def __init__(self):
fp = open("pesterchum.js")
self.config = json.load(fp)
fp.close()
if self.config.has_key("defaultprofile"):
self.userprofile = userProfile(self.config["defaultprofile"])
else:
self.userprofile = None
def chums(self):
return self.config['chums']
def defaultprofile(self):
try:
return self.config['defaultprofile']
except KeyError:
return None
def tabs(self):
return self.config["tabs"]
def addChum(self, chum):
if chum.handle not in self.config['chums']:
newchums = self.config['chums'] + [chum.handle]
self.set("chums", newchums)
def removeChum(self, chum):
if type(chum) is PesterProfile:
handle = chum.handle
else:
handle = chum
newchums = [c for c in self.config['chums'] if c != handle]
self.set("chums", newchums)
def getBlocklist(self):
if not self.config.has_key('block'):
self.set('block', [])
return self.config['block']
def addBlocklist(self, handle):
l = self.getBlocklist()
if handle not in l:
l.append(handle)
self.set('block', l)
def delBlocklist(self, handle):
l = self.getBlocklist()
l.pop(l.index(handle))
self.set('block', l)
def server(self):
return self.config.get('server', 'irc.tymoon.eu')
def port(self):
return self.config.get('port', '6667')
def soundOn(self):
if not self.config.has_key('soundon'):
self.set('soundon', True)
return self.config['soundon']
def set(self, item, setting):
self.config[item] = setting
try:
jsonoutput = json.dumps(self.config)
except ValueError, e:
raise e
fp = open("pesterchum.js", 'w')
fp.write(jsonoutput)
fp.close()
def availableThemes(self):
themes = []
for dirname, dirnames, filenames in os.walk('themes'):
for d in dirnames:
themes.append(d)
themes.sort()
return themes
def availableProfiles(self):
profs = []
for dirname, dirnames, filenames in os.walk('profiles'):
for filename in filenames:
l = len(filename)
if filename[l-3:l] == ".js":
profs.append(filename[0:l-3])
profs.sort()
return [userProfile(p) for p in profs]
class userProfile(object):
def __init__(self, user):
if type(user) is PesterProfile:
self.chat = user
self.userprofile = {"handle":user.handle,
"color": unicode(user.color.name()),
"quirks": [],
"theme": "pesterchum"}
self.theme = pesterTheme("pesterchum")
self.chat.mood = Mood(self.theme["main/defaultmood"])
self.quirks = pesterQuirks([])
else:
fp = open("profiles/%s.js" % (user))
self.userprofile = json.load(fp)
fp.close()
try:
self.theme = pesterTheme(self.userprofile["theme"])
except ValueError, e:
self.theme = pesterTheme("pesterchum")
self.chat = PesterProfile(self.userprofile["handle"],
QtGui.QColor(self.userprofile["color"]),
Mood(self.theme["main/defaultmood"]))
self.quirks = pesterQuirks(self.userprofile["quirks"])
def setMood(self, mood):
self.chat.mood = mood
def setTheme(self, theme):
self.theme = theme
self.userprofile["theme"] = theme.name
self.save()
def setColor(self, color):
self.chat.color = color
self.userprofile["color"] = unicode(color.name())
self.save()
def setQuirks(self, quirks):
self.quirks = quirks
self.userprofile["quirks"] = self.quirks.plainList()
self.save()
def getTheme(self):
return self.theme
def save(self):
handle = self.chat.handle
try:
jsonoutput = json.dumps(self.userprofile)
except ValueError, e:
raise e
fp = open("profiles/%s.js" % (handle), 'w')
fp.write(jsonoutput)
fp.close()
@staticmethod
def newUserProfile(chatprofile):
if os.path.exists("profiles/%s.js" % (chatprofile.handle)):
newprofile = userProfile(chatprofile.handle)
else:
newprofile = userProfile(chatprofile)
newprofile.save()
return newprofile
class WMButton(QtGui.QPushButton):
def __init__(self, icon, parent=None):
QtGui.QPushButton.__init__(self, icon, "", parent)
self.setIconSize(icon.realsize())
self.setFlat(True)
self.setStyleSheet("QPushButton { padding: 0px; }")
self.setAutoDefault(False)
class chumListing(QtGui.QListWidgetItem):
def __init__(self, chum, window):
QtGui.QListWidgetItem.__init__(self, chum.handle)
self.mainwindow = window
self.chum = chum
self.handle = chum.handle
self.setMood(Mood("offline"))
def setMood(self, mood):
self.chum.mood = mood
self.updateMood()
def setColor(self, color):
self.chum.color = color
def updateMood(self, unblock=False):
mood = self.chum.mood
self.mood = mood
icon = self.mood.icon(self.mainwindow.theme)
self.setIcon(icon)
try:
self.setTextColor(QtGui.QColor(self.mainwindow.theme["main/chums/moods"][self.mood.name()]["color"]))
except KeyError:
self.setTextColor(QtGui.QColor(self.mainwindow.theme["main/chums/moods/chummy/color"]))
def changeTheme(self, theme):
icon = self.mood.icon(theme)
self.setIcon(icon)
try:
self.setTextColor(QtGui.QColor(self.mainwindow.theme["main/chums/moods"][self.mood.name()]["color"]))
except KeyError:
self.setTextColor(QtGui.QColor(self.mainwindow.theme["main/chums/moods/chummy/color"]))
def __lt__(self, cl):
h1 = self.handle.lower()
h2 = cl.handle.lower()
return (h1 < h2)
class chumArea(RightClickList):
def __init__(self, chums, parent=None):
QtGui.QListWidget.__init__(self, parent)
self.mainwindow = parent
theme = self.mainwindow.theme
self.chums = chums
for c in self.chums:
chandle = c.handle
if not self.findItems(chandle, QtCore.Qt.MatchFlags(0)):
chumLabel = chumListing(c, self.mainwindow)
self.addItem(chumLabel)
self.optionsMenu = QtGui.QMenu(self)
self.pester = QtGui.QAction(self.mainwindow.theme["main/menus/rclickchumlist/pester"], self)
self.connect(self.pester, QtCore.SIGNAL('triggered()'),
self, QtCore.SLOT('activateChum()'))
self.removechum = QtGui.QAction(self.mainwindow.theme["main/menus/rclickchumlist/removechum"], self)
self.connect(self.removechum, QtCore.SIGNAL('triggered()'),
self, QtCore.SLOT('removeChum()'))
self.blockchum = QtGui.QAction(self.mainwindow.theme["main/menus/rclickchumlist/blockchum"], self)
self.connect(self.blockchum, QtCore.SIGNAL('triggered()'),
self, QtCore.SLOT('blockChum()'))
self.optionsMenu.addAction(self.pester)
self.optionsMenu.addAction(self.blockchum)
self.optionsMenu.addAction(self.removechum)
self.initTheme(theme)
self.sortItems()
def addChum(self, chum):
if len([c for c in self.chums if c.handle == chum.handle]) != 0:
return
self.chums.append(chum)
chumLabel = chumListing(chum, self.mainwindow)
self.addItem(chumLabel)
self.sortItems()
def getChums(self, handle):
chums = self.findItems(handle, QtCore.Qt.MatchFlags(0))
return chums
def updateMood(self, handle, mood):
chums = self.getChums(handle)
oldmood = None
for c in chums:
oldmood = c.mood
c.setMood(mood)
return oldmood
def updateColor(self, handle, color):
chums = self.findItems(handle, QtCore.Qt.MatchFlags(0))
for c in chums:
c.setColor(color)
def initTheme(self, theme):
self.setGeometry(*(theme["main/chums/loc"]+theme["main/chums/size"]))
if theme.has_key("main/chums/scrollbar"):
self.setStyleSheet("QListWidget { %s } QScrollBar { %s } QScrollBar::handle { %s } QScrollBar::add-line { %s } QScrollBar::sub-line { %s } QScrollBar:up-arrow { %s } QScrollBar:down-arrow { %s }" % (theme["main/chums/style"], theme["main/chums/scrollbar/style"], theme["main/chums/scrollbar/handle"], theme["main/chums/scrollbar/downarrow"], theme["main/chums/scrollbar/uparrow"], theme["main/chums/scrollbar/uarrowstyle"], theme["main/chums/scrollbar/darrowstyle"] ))
else:
self.setStyleSheet(theme["main/chums/style"])
self.pester.setText(theme["main/menus/rclickchumlist/pester"])
self.removechum.setText(theme["main/menus/rclickchumlist/removechum"])
self.blockchum.setText(theme["main/menus/rclickchumlist/blockchum"])
def changeTheme(self, theme):
self.initTheme(theme)
chumlistings = [self.item(i) for i in range(0, self.count())]
for c in chumlistings:
c.changeTheme(theme)
@QtCore.pyqtSlot()
def activateChum(self):
self.itemActivated.emit(self.currentItem())
@QtCore.pyqtSlot()
def removeChum(self, handle = None):
if handle:
clistings = self.getChums(handle)
for c in clistings:
self.setCurrentItem(c)
if not self.currentItem():
return
currentChum = self.currentItem().chum
self.chums = [c for c in self.chums if c.handle != currentChum.handle]
self.removeChumSignal.emit(self.currentItem())
oldlist = self.takeItem(self.currentRow())
del oldlist
@QtCore.pyqtSlot()
def blockChum(self):
currentChum = self.currentItem()
if not currentChum:
return
self.blockChumSignal.emit(self.currentItem().chum.handle)
removeChumSignal = QtCore.pyqtSignal(QtGui.QListWidgetItem)
blockChumSignal = QtCore.pyqtSignal(QtCore.QString)
class trollSlum(chumArea):
def __init__(self, trolls, mainwindow, parent=None):
QtGui.QListWidget.__init__(self, parent)
self.mainwindow = mainwindow
theme = self.mainwindow.theme
self.setStyleSheet(theme["main/trollslum/chumroll/style"])
self.chums = trolls
for c in self.chums:
chandle = c.handle
if not self.findItems(chandle, QtCore.Qt.MatchFlags(0)):
chumLabel = chumListing(c, self.mainwindow)
self.addItem(chumLabel)
self.optionsMenu = QtGui.QMenu(self)
self.unblockchum = QtGui.QAction(self.mainwindow.theme["main/menus/rclickchumlist/unblockchum"], self)
self.connect(self.unblockchum, QtCore.SIGNAL('triggered()'),
self, QtCore.SIGNAL('unblockChumSignal()'))
self.optionsMenu.addAction(self.unblockchum)
self.sortItems()
def changeTheme(self, theme):
self.setStyleSheet(theme["main/trollslum/chumroll/style"])
self.removechum.setText(theme["main/menus/rclickchumlist/removechum"])
self.unblockchum.setText(theme["main/menus/rclickchumlist/blockchum"])
chumlistings = [self.item(i) for i in range(0, self.count())]
for c in chumlistings:
c.changeTheme(theme)
unblockChumSignal = QtCore.pyqtSignal(QtCore.QString)
class TrollSlumWindow(QtGui.QFrame):
def __init__(self, trolls, mainwindow, parent=None):
QtGui.QFrame.__init__(self, parent)
self.mainwindow = mainwindow
theme = self.mainwindow.theme
self.slumlabel = QtGui.QLabel(self)
self.initTheme(theme)
self.trollslum = trollSlum(trolls, self.mainwindow, self)
self.connect(self.trollslum, QtCore.SIGNAL('unblockChumSignal()'),
self, QtCore.SLOT('removeCurrentTroll()'))
layout_1 = QtGui.QHBoxLayout()
self.addButton = QtGui.QPushButton("ADD", self)
self.connect(self.addButton, QtCore.SIGNAL('clicked()'),
self, QtCore.SLOT('addTrollWindow()'))
self.removeButton = QtGui.QPushButton("REMOVE", self)
self.connect(self.removeButton, QtCore.SIGNAL('clicked()'),
self, QtCore.SLOT('removeCurrentTroll()'))
layout_1.addWidget(self.addButton)
layout_1.addWidget(self.removeButton)
layout_0 = QtGui.QVBoxLayout()
layout_0.addWidget(self.slumlabel)
layout_0.addWidget(self.trollslum)
layout_0.addLayout(layout_1)
self.setLayout(layout_0)
def initTheme(self, theme):
self.resize(*theme["main/trollslum/size"])
self.setStyleSheet(theme["main/trollslum/style"])
self.slumlabel.setText(theme["main/trollslum/label/text"])
self.slumlabel.setStyleSheet(theme["main/trollslum/label/style"])
if not self.parent():
self.setWindowTitle(theme["main/menus/profile/block"])
self.setWindowIcon(self.mainwindow.windowIcon())
def changeTheme(self, theme):
self.initTheme(theme)
self.trollslum.changeTheme(theme)
# move unblocked trolls from slum to chumarea
def closeEvent(self, event):
self.mainwindow.closeTrollSlum()
def updateMood(self, handle, mood):
self.trollslum.updateMood(handle, mood)
def addTroll(self, chum):
self.trollslum.addChum(chum)
def removeTroll(self, handle):
self.trollslum.removeChum(handle)
@QtCore.pyqtSlot()
def removeCurrentTroll(self):
currentListing = self.trollslum.currentItem()
if not currentListing:
return
self.unblockChumSignal.emit(currentListing.chum.handle)
@QtCore.pyqtSlot()
def addTrollWindow(self):
if not hasattr(self, 'addtrolldialog'):
self.addtrolldialog = None
if self.addtrolldialog:
return
self.addtrolldialog = QtGui.QInputDialog(self)
(handle, ok) = self.addtrolldialog.getText(self, "Add Troll", "Enter Troll Handle:")
if ok:
handle = unicode(handle)
if not (PesterProfile.checkLength(handle) and
PesterProfile.checkValid(handle)):
errormsg = QtGui.QErrorMessage(self)
errormsg.showMessage("THIS IS NOT A VALID CHUMTAG!")
self.addchumdialog = None
return
self.blockChumSignal.emit(handle)
self.addtrolldialog = None
blockChumSignal = QtCore.pyqtSignal(QtCore.QString)
unblockChumSignal = QtCore.pyqtSignal(QtCore.QString)
class PesterMoodAction(QtCore.QObject):
def __init__(self, m, func):
QtCore.QObject.__init__(self)
self.mood = m
self.func = func
@QtCore.pyqtSlot()
def updateMood(self):
self.func(self.mood)
class PesterMoodHandler(QtCore.QObject):
def __init__(self, parent, *buttons):
QtCore.QObject.__init__(self)
self.buttons = {}
self.mainwindow = parent
for b in buttons:
self.buttons[b.mood.value()] = b
if b.mood.value() == self.mainwindow.profile().mood.value():
b.setSelected(True)
self.connect(b, QtCore.SIGNAL('clicked()'),
b, QtCore.SLOT('updateMood()'))
self.connect(b, QtCore.SIGNAL('moodUpdated(int)'),
self, QtCore.SLOT('updateMood(int)'))
def removeButtons(self):
for b in self.buttons.values():
b.close()
def showButtons(self):
for b in self.buttons.values():
b.show()
b.raise_()
@QtCore.pyqtSlot(int)
def updateMood(self, m):
oldmood = self.mainwindow.profile().mood
try:
oldbutton = self.buttons[oldmood.value()]
oldbutton.setSelected(False)
except KeyError:
pass
try:
newbutton = self.buttons[m]
newbutton.setSelected(True)
except KeyError:
pass
newmood = Mood(m)
self.mainwindow.userprofile.chat.mood = newmood
if self.mainwindow.currentMoodIcon:
moodicon = newmood.icon(self.mainwindow.theme)
self.mainwindow.currentMoodIcon.setPixmap(moodicon.pixmap(moodicon.realsize()))
self.mainwindow.moodUpdated.emit()
class PesterMoodButton(QtGui.QPushButton):
def __init__(self, parent, **options):
icon = PesterIcon(options["icon"])
QtGui.QPushButton.__init__(self, icon, options["text"], parent)
self.setIconSize(icon.realsize())
self.setFlat(True)
self.resize(*options["size"])
self.move(*options["loc"])
self.unselectedSheet = options["style"]
self.selectedSheet = options["selected"]
self.setStyleSheet(self.unselectedSheet)
self.mainwindow = parent
self.mood = Mood(options["mood"])
def setSelected(self, selected):
if selected:
self.setStyleSheet(self.selectedSheet)
else:
self.setStyleSheet(self.unselectedSheet)
@QtCore.pyqtSlot()
def updateMood(self):
self.moodUpdated.emit(self.mood.value())
moodUpdated = QtCore.pyqtSignal(int)
class MovingWindow(QtGui.QFrame):
def __init__(self, *x, **y):
QtGui.QFrame.__init__(self, *x, **y)
self.moving = None
self.moveupdate = 0
def mouseMoveEvent(self, event):
if self.moving:
move = event.globalPos() - self.moving
self.move(move)
self.moveupdate += 1
if self.moveupdate > 5:
self.moveupdate = 0
self.update()
def mousePressEvent(self, event):
if event.button() == 1:
self.moving = event.globalPos() - self.pos()
def mouseReleaseEvent(self, event):
if event.button() == 1:
self.update()
self.moving = None
class PesterWindow(MovingWindow):
def __init__(self, parent=None):
MovingWindow.__init__(self, parent,
flags=(QtCore.Qt.CustomizeWindowHint |
QtCore.Qt.FramelessWindowHint))
self.convos = {}
self.memos = {}
self.tabconvo = None
self.tabmemo = None
self.setAutoFillBackground(True)
self.setObjectName("main")
self.config = userConfig()
if self.config.defaultprofile():
self.userprofile = userProfile(self.config.defaultprofile())
self.theme = self.userprofile.getTheme()
else:
self.userprofile = userProfile(PesterProfile("pesterClient%d" % (random.randint(100,999)), QtGui.QColor("black"), Mood(0)))
self.theme = self.userprofile.getTheme()
self.chatlog = PesterLog(self.profile().handle)
self.move(100, 100)
opts = QtGui.QAction(self.theme["main/menus/client/options"], self)
self.opts = opts
self.connect(opts, QtCore.SIGNAL('triggered()'),
self, QtCore.SLOT('openOpts()'))
exitaction = QtGui.QAction(self.theme["main/menus/client/exit"], self)
self.exitaction = exitaction
self.connect(exitaction, QtCore.SIGNAL('triggered()'),
self, QtCore.SLOT('close()'))
userlistaction = QtGui.QAction(self.theme["main/menus/client/userlist"], self)
self.userlistaction = userlistaction
self.connect(userlistaction, QtCore.SIGNAL('triggered()'),
self, QtCore.SLOT('showAllUsers()'))
memoaction = QtGui.QAction(self.theme["main/menus/client/memos"], self)
self.memoaction = memoaction
self.connect(memoaction, QtCore.SIGNAL('triggered()'),
self, QtCore.SLOT('showMemos()'))
self.importaction = QtGui.QAction(self.theme["main/menus/client/import"], self)
self.connect(self.importaction, QtCore.SIGNAL('triggered()'),
self, QtCore.SLOT('importExternalConfig()'))
self.idleaction = QtGui.QAction(self.theme["main/menus/client/idle"], self)
self.idleaction.setCheckable(True)
self.connect(self.idleaction, QtCore.SIGNAL('toggled(bool)'),
self, QtCore.SLOT('toggleIdle(bool)'))
self.reconnectAction = QtGui.QAction(self.theme["main/menus/client/reconnect"], self)
self.connect(self.reconnectAction, QtCore.SIGNAL('triggered()'),
self, QtCore.SIGNAL('reconnectIRC()'))
self.menu = QtGui.QMenuBar(self)
filemenu = self.menu.addMenu(self.theme["main/menus/client/_name"])
self.filemenu = filemenu
filemenu.addAction(opts)
filemenu.addAction(memoaction)
filemenu.addAction(userlistaction)
filemenu.addAction(self.idleaction)
filemenu.addAction(self.importaction)
filemenu.addAction(self.reconnectAction)
filemenu.addAction(exitaction)
changetheme = QtGui.QAction(self.theme["main/menus/profile/theme"], self)
self.changetheme = changetheme
self.connect(changetheme, QtCore.SIGNAL('triggered()'),
self, QtCore.SLOT('pickTheme()'))
changequirks = QtGui.QAction(self.theme["main/menus/profile/quirks"], self)
self.changequirks = changequirks
self.connect(changequirks, QtCore.SIGNAL('triggered()'),
self, QtCore.SLOT('openQuirks()'))
loadslum = QtGui.QAction(self.theme["main/menus/profile/block"], self)
self.loadslum = loadslum
self.connect(loadslum, QtCore.SIGNAL('triggered()'),
self, QtCore.SLOT('showTrollSlum()'))
changecoloraction = QtGui.QAction(self.theme["main/menus/profile/color"], self)
self.changecoloraction = changecoloraction
self.connect(changecoloraction, QtCore.SIGNAL('triggered()'),
self, QtCore.SLOT('changeMyColor()'))
switch = QtGui.QAction(self.theme["main/menus/profile/switch"], self)
self.switch = switch
self.connect(switch, QtCore.SIGNAL('triggered()'),
self, QtCore.SLOT('switchProfile()'))
profilemenu = self.menu.addMenu(self.theme["main/menus/profile/_name"])
self.profilemenu = profilemenu
profilemenu.addAction(changetheme)
profilemenu.addAction(changequirks)
profilemenu.addAction(loadslum)
profilemenu.addAction(changecoloraction)
profilemenu.addAction(switch)
self.aboutAction = QtGui.QAction(self.theme["main/menus/help/about"], self)
self.connect(self.aboutAction, QtCore.SIGNAL('triggered()'),
self, QtCore.SLOT('aboutPesterchum()'))
helpmenu = self.menu.addMenu(self.theme["main/menus/help/_name"])
self.helpmenu = helpmenu
self.helpmenu.addAction(self.aboutAction)
self.closeButton = WMButton(PesterIcon(self.theme["main/close/image"]), self)
self.connect(self.closeButton, QtCore.SIGNAL('clicked()'),
self, QtCore.SLOT('closeToTray()'))
self.miniButton = WMButton(PesterIcon(self.theme["main/minimize/image"]), self)
self.connect(self.miniButton, QtCore.SIGNAL('clicked()'),
self, QtCore.SLOT('showMinimized()'))
self.namesdb = {}
self.chumdb = PesterProfileDB()
chums = [PesterProfile(c, chumdb=self.chumdb) for c in set(self.config.chums())]
self.chumList = chumArea(chums, self)
self.connect(self.chumList,
QtCore.SIGNAL('itemActivated(QListWidgetItem *)'),
self,
QtCore.SLOT('newConversationWindow(QListWidgetItem *)'))
self.connect(self.chumList,
QtCore.SIGNAL('removeChumSignal(QListWidgetItem *)'),
self,
QtCore.SLOT('removeChum(QListWidgetItem *)'))
self.connect(self.chumList,
QtCore.SIGNAL('blockChumSignal(QString)'),
self,
QtCore.SLOT('blockChum(QString)'))
self.addChumButton = QtGui.QPushButton(self.theme["main/addchum/text"], self)
self.connect(self.addChumButton, QtCore.SIGNAL('clicked()'),
self, QtCore.SLOT('addChumWindow()'))
self.pesterButton = QtGui.QPushButton(self.theme["main/pester/text"], self)
self.connect(self.pesterButton, QtCore.SIGNAL('clicked()'),
self, QtCore.SLOT('pesterSelectedChum()'))
self.blockButton = QtGui.QPushButton(self.theme["main/block/text"], self)
self.connect(self.blockButton, QtCore.SIGNAL('clicked()'),
self, QtCore.SLOT('blockSelectedChum()'))
self.moodsLabel = QtGui.QLabel(self.theme["main/moodlabel/text"], self)
self.mychumhandleLabel = QtGui.QLabel(self.theme["main/mychumhandle/label/text"], self)
self.mychumhandle = QtGui.QPushButton(self.profile().handle, self)
self.mychumhandle.setFlat(True)
self.connect(self.mychumhandle, QtCore.SIGNAL('clicked()'),
self, QtCore.SLOT('switchProfile()'))
self.mychumcolor = QtGui.QPushButton(self)
self.connect(self.mychumcolor, QtCore.SIGNAL('clicked()'),
self, QtCore.SLOT('changeMyColor()'))
self.initTheme(self.theme)
self.waitingMessages = waitingMessageHolder(self)
self.autoidle = False
self.idlethreshold = 600
self.idletimer = QtCore.QTimer(self)
self.idleposition = QtGui.QCursor.pos()
self.idletime = 0
self.connect(self.idletimer, QtCore.SIGNAL('timeout()'),
self, QtCore.SLOT('checkIdle()'))
self.idletimer.start(1000)
if not self.config.defaultprofile():
self.changeProfile()
self.loadingscreen = LoadingScreen(self)
self.connect(self.loadingscreen, QtCore.SIGNAL('rejected()'),
self, QtCore.SLOT('close()'))
def profile(self):
return self.userprofile.chat
def closeConversations(self):
if not hasattr(self, 'tabconvo'):
self.tabconvo = None
if self.tabconvo:
self.tabconvo.close()
else:
for c in self.convos.values():
c.close()
if self.tabmemo:
self.tabmemo.close()
else:
for m in self.memos.values():
m.close()
def paintEvent(self, event):
palette = QtGui.QPalette()
palette.setBrush(QtGui.QPalette.Window, QtGui.QBrush(self.backgroundImage))
self.setPalette(palette)
@QtCore.pyqtSlot()
def closeToTray(self):
self.hide()
self.closeToTraySignal.emit()
def closeEvent(self, event):
self.closeConversations()
if hasattr(self, 'trollslum') and self.trollslum:
self.trollslum.close()
self.closeSignal.emit()
event.accept()
def newMessage(self, handle, msg):
if handle in self.config.getBlocklist():
#yeah suck on this
self.sendMessage.emit("PESTERCHUM:BLOCKED", handle)
return
if not self.convos.has_key(handle):
if msg == "PESTERCHUM:CEASE": # ignore cease after we hang up
return
matchingChums = [c for c in self.chumList.chums if c.handle == handle]
if len(matchingChums) > 0:
mood = matchingChums[0].mood
else:
mood = Mood(0)
chum = PesterProfile(handle, mood=mood, chumdb=self.chumdb)
self.newConversation(chum, False)
if len(matchingChums) == 0:
self.moodRequest.emit(chum)
convo = self.convos[handle]
convo.addMessage(msg, False)
# play sound here
if self.config.soundOn():
if msg in ["PESTERCHUM:CEASE", "PESTERCHUM:BLOCK"]:
self.ceasesound.play()
else:
self.alarm.play()
def newMemoMsg(self, chan, handle, msg):
if not self.memos.has_key(chan):
# silently ignore in case we forgot to /part
return
memo = self.memos[chan]
msg = unicode(msg)
if not memo.times.has_key(handle):
# new chum! time current
newtime = timedelta(0)
time = TimeTracker(newtime)
memo.times[handle] = time
if msg[0:3] != "/me" and msg[0:13] != "PESTERCHUM:ME":
msg = addTimeInitial(msg, memo.times[handle].getGrammar())
memo.addMessage(msg, handle)
self.alarm.play()
def changeColor(self, handle, color):
# pesterconvo and chumlist
self.chumList.updateColor(handle, color)
if self.convos.has_key(handle):
self.convos[handle].updateColor(color)
self.chumdb.setColor(handle, color)
def updateMood(self, handle, mood):
oldmood = self.chumList.updateMood(handle, mood)
if self.convos.has_key(handle):
self.convos[handle].updateMood(mood, old=oldmood)
if hasattr(self, 'trollslum') and self.trollslum:
self.trollslum.updateMood(handle, mood)
def newConversation(self, chum, initiated=True):
if self.convos.has_key(chum.handle):
self.convos[chum.handle].showChat()
return
if self.config.tabs():
if not self.tabconvo:
self.createTabWindow()
convoWindow = PesterConvo(chum, initiated, self, self.tabconvo)
self.tabconvo.show()
else:
convoWindow = PesterConvo(chum, initiated, self)
self.connect(convoWindow, QtCore.SIGNAL('messageSent(QString, QString)'),
self, QtCore.SIGNAL('sendMessage(QString, QString)'))
self.connect(convoWindow, QtCore.SIGNAL('windowClosed(QString)'),
self, QtCore.SLOT('closeConvo(QString)'))
self.convos[chum.handle] = convoWindow
self.newConvoStarted.emit(QtCore.QString(chum.handle), initiated)
convoWindow.show()
def createTabWindow(self):
self.tabconvo = PesterTabWindow(self)
self.connect(self.tabconvo, QtCore.SIGNAL('windowClosed()'),
self, QtCore.SLOT('tabsClosed()'))
def createMemoTabWindow(self):
self.tabmemo = MemoTabWindow(self)
self.connect(self.tabmemo, QtCore.SIGNAL('windowClosed()'),
self, QtCore.SLOT('memoTabsClosed()'))
def newMemo(self, channel, timestr, secret=False):
if channel == "#pesterchum":
return
if self.memos.has_key(channel):
self.memos[channel].showChat()
return
# do slider dialog then set
if self.config.tabs():
if not self.tabmemo:
self.createMemoTabWindow()
memoWindow = PesterMemo(channel, timestr, self, self.tabmemo)
self.tabmemo.show()
else:
memoWindow = PesterMemo(channel, timestr, self, None)
# connect signals
self.connect(memoWindow, QtCore.SIGNAL('messageSent(QString, QString)'),
self, QtCore.SIGNAL('sendMessage(QString, QString)'))
self.connect(memoWindow, QtCore.SIGNAL('windowClosed(QString)'),
self, QtCore.SLOT('closeMemo(QString)'))
self.connect(self, QtCore.SIGNAL('namesUpdated()'),
memoWindow, QtCore.SLOT('namesUpdated()'))
self.connect(self,
QtCore.SIGNAL('userPresentSignal(QString, QString, QString)'),
memoWindow, QtCore.SLOT('userPresentChange(QString, QString, QString)'))
# chat client send memo open
self.memos[channel] = memoWindow
self.joinChannel.emit(channel) # race condition?
self.secret = secret
if self.secret:
self.secret = True
self.setChannelMode.emit(channel, "+s", "")
memoWindow.sendTimeInfo()
memoWindow.show()
def addChum(self, chum):
self.chumList.addChum(chum)
self.config.addChum(chum)
self.moodRequest.emit(chum)
def changeProfile(self, collision=None):
if not hasattr(self, 'chooseprofile'):
self.chooseprofile = None
if not self.chooseprofile:
self.chooseprofile = PesterChooseProfile(self.userprofile, self.config, self.theme, self, collision=collision)
self.chooseprofile.exec_()
def themePicker(self):
if not hasattr(self, 'choosetheme'):
self.choosetheme = None
if not self.choosetheme:
self.choosetheme = PesterChooseTheme(self.config, self.theme, self)
self.choosetheme.exec_()
def initTheme(self, theme):
self.resize(*theme["main/size"])
self.setWindowIcon(PesterIcon(theme["main/icon"]))
self.setWindowTitle(theme["main/windowtitle"])
self.setStyleSheet("QFrame#main { %s }" % (theme["main/style"]))
self.backgroundImage = QtGui.QPixmap(theme["main/background-image"])
self.backgroundMask = self.backgroundImage.mask()
self.setMask(self.backgroundMask)
self.menu.setStyleSheet("QMenuBar { background: transparent; %s } QMenuBar::item { background: transparent; %s } " % (theme["main/menubar/style"], theme["main/menu/menuitem"]) + "QMenu { background: transparent; %s } QMenu::item::selected { %s }" % (theme["main/menu/style"], theme["main/menu/selected"]))
newcloseicon = PesterIcon(theme["main/close/image"])
self.closeButton.setIcon(newcloseicon)
self.closeButton.setIconSize(newcloseicon.realsize())
self.closeButton.move(*theme["main/close/loc"])
newminiicon = PesterIcon(theme["main/minimize/image"])
self.miniButton.setIcon(newminiicon)
self.miniButton.setIconSize(newminiicon.realsize())
self.miniButton.move(*theme["main/minimize/loc"])
# menus
self.menu.move(*theme["main/menu/loc"])
self.opts.setText(theme["main/menus/client/options"])
self.exitaction.setText(theme["main/menus/client/exit"])
self.userlistaction.setText(theme["main/menus/client/userlist"])
self.memoaction.setText(theme["main/menus/client/memos"])
self.importaction.setText(theme["main/menus/client/import"])
self.idleaction.setText(theme["main/menus/client/idle"])
self.reconnectAction.setText(theme["main/menus/client/reconnect"])
self.filemenu.setTitle(theme["main/menus/client/_name"])
self.changetheme.setText(theme["main/menus/profile/theme"])
self.changequirks.setText(theme["main/menus/profile/quirks"])
self.loadslum.setText(theme["main/menus/profile/block"])
self.changecoloraction.setText(theme["main/menus/profile/color"])
self.switch.setText(theme["main/menus/profile/switch"])
self.profilemenu.setTitle(theme["main/menus/profile/_name"])
self.aboutAction.setText(self.theme["main/menus/help/about"])
self.helpmenu.setTitle(self.theme["main/menus/help/_name"])
# moods
self.moodsLabel.setText(theme["main/moodlabel/text"])
self.moodsLabel.move(*theme["main/moodlabel/loc"])
self.moodsLabel.setStyleSheet(theme["main/moodlabel/style"])
if hasattr(self, 'moods'):
self.moods.removeButtons()
self.moods = PesterMoodHandler(self, *[PesterMoodButton(self, **d) for d in theme["main/moods"]])
self.moods.showButtons()
# chum
addChumStyle = "QPushButton { %s }" % (theme["main/addchum/style"])
if theme.has_key("main/addchum/pressed"):
addChumStyle += "QPushButton:pressed { %s }" % (theme["main/addchum/pressed"])
pesterButtonStyle = "QPushButton { %s }" % (theme["main/pester/style"])
if theme.has_key("main/pester/pressed"):
pesterButtonStyle += "QPushButton:pressed { %s }" % (theme["main/pester/pressed"])
blockButtonStyle = "QPushButton { %s }" % (theme["main/block/style"])
if theme.has_key("main/block/pressed"):
pesterButtonStyle += "QPushButton:pressed { %s }" % (theme["main/block/pressed"])
self.addChumButton.setText(theme["main/addchum/text"])
self.addChumButton.resize(*theme["main/addchum/size"])
self.addChumButton.move(*theme["main/addchum/loc"])
self.addChumButton.setStyleSheet(addChumStyle)
self.pesterButton.setText(theme["main/pester/text"])
self.pesterButton.resize(*theme["main/pester/size"])
self.pesterButton.move(*theme["main/pester/loc"])
self.pesterButton.setStyleSheet(pesterButtonStyle)
self.blockButton.setText(theme["main/block/text"])
self.blockButton.resize(*theme["main/block/size"])
self.blockButton.move(*theme["main/block/loc"])
self.blockButton.setStyleSheet(blockButtonStyle)
# buttons
self.mychumhandleLabel.setText(theme["main/mychumhandle/label/text"])
self.mychumhandleLabel.move(*theme["main/mychumhandle/label/loc"])
self.mychumhandleLabel.setStyleSheet(theme["main/mychumhandle/label/style"])
self.mychumhandle.setText(self.profile().handle)
self.mychumhandle.move(*theme["main/mychumhandle/handle/loc"])
self.mychumhandle.resize(*theme["main/mychumhandle/handle/size"])
self.mychumhandle.setStyleSheet(theme["main/mychumhandle/handle/style"])
self.mychumcolor.resize(*theme["main/mychumhandle/colorswatch/size"])
self.mychumcolor.move(*theme["main/mychumhandle/colorswatch/loc"])
self.mychumcolor.setStyleSheet("background: %s" % (self.profile().colorhtml()))
if self.theme.has_key("main/mychumhandle/currentMood"):
moodicon = self.profile().mood.icon(theme)
if hasattr(self, 'currentMoodIcon') and self.currentMoodIcon:
self.currentMoodIcon.hide()
self.currentMoodIcon = None
self.currentMoodIcon = QtGui.QLabel(self)
self.currentMoodIcon.setPixmap(moodicon.pixmap(moodicon.realsize()))
self.currentMoodIcon.move(*theme["main/mychumhandle/currentMood"])
self.currentMoodIcon.show()
else:
if hasattr(self, 'currentMoodIcon') and self.currentMoodIcon:
self.currentMoodIcon.hide()
self.currentMoodIcon = None
if theme["main/mychumhandle/colorswatch/text"]:
self.mychumcolor.setText(theme["main/mychumhandle/colorswatch/text"])
# sounds
if not pygame.mixer:
self.alarm = NoneSound()
self.ceasesound = NoneSound()
else:
self.alarm = pygame.mixer.Sound(theme["main/sounds/alertsound"])
self.ceasesound = pygame.mixer.Sound(theme["main/sounds/ceasesound"])
def changeTheme(self, theme):
self.theme = theme
# do self
self.initTheme(theme)
# set mood
self.moods.updateMood(theme['main/defaultmood'])
# chum area
self.chumList.changeTheme(theme)
# do open windows
if self.tabconvo:
self.tabconvo.changeTheme(theme)
if self.tabmemo:
self.tabmemo.changeTheme(theme)
for c in self.convos.values():
c.changeTheme(theme)
for m in self.memos.values():
m.changeTheme(theme)
if hasattr(self, 'trollslum') and self.trollslum:
self.trollslum.changeTheme(theme)
if hasattr(self, 'allusers') and self.allusers:
self.allusers.changeTheme(theme)
# system tray icon
self.updateSystemTray()
def updateSystemTray(self):
if len(self.waitingMessages) == 0:
self.trayIconSignal.emit(0)
else:
self.trayIconSignal.emit(1)
def systemTrayFunction(self):
if len(self.waitingMessages) == 0:
if self.isMinimized():
self.showNormal()
elif self.isHidden():
self.show()
else:
if self.isActiveWindow():
self.hide()
else:
self.raise_()
self.activateWindow()
else:
self.waitingMessages.answerMessage()
@QtCore.pyqtSlot()
def connected(self):
if self.loadingscreen:
self.loadingscreen.accept()
self.loadingscreen = None
@QtCore.pyqtSlot()
def blockSelectedChum(self):
curChumListing = self.chumList.currentItem()
if curChumListing:
curChum = curChumListing.chum
self.blockChum(curChum.handle)
@QtCore.pyqtSlot()
def pesterSelectedChum(self):
curChum = self.chumList.currentItem()
if curChum:
self.newConversationWindow(curChum)
@QtCore.pyqtSlot(QtGui.QListWidgetItem)
def newConversationWindow(self, chumlisting):
# check chumdb
chum = chumlisting.chum
color = self.chumdb.getColor(chum)
if color:
chum.color = color
self.newConversation(chum)
@QtCore.pyqtSlot(QtCore.QString)
def closeConvo(self, handle):
h = unicode(handle)
chum = self.convos[h].chum
chumopen = self.convos[h].chumopen
if chumopen:
self.chatlog.log(chum.handle, self.profile().pestermsg(chum, QtGui.QColor(self.theme["convo/systemMsgColor"]), self.theme["convo/text/ceasepester"]))
self.convoClosed.emit(handle)
self.chatlog.finish(h)
del self.convos[h]
@QtCore.pyqtSlot(QtCore.QString)
def closeMemo(self, channel):
c = unicode(channel)
self.chatlog.finish(c)
self.leftChannel.emit(channel)
del self.memos[c]
@QtCore.pyqtSlot()
def tabsClosed(self):
del self.tabconvo
self.tabconvo = None
@QtCore.pyqtSlot()
def memoTabsClosed(self):
del self.tabmemo
self.tabmemo = None
@QtCore.pyqtSlot(QtCore.QString, Mood)
def updateMoodSlot(self, handle, mood):
h = unicode(handle)
self.updateMood(h, mood)
@QtCore.pyqtSlot(QtCore.QString, QtGui.QColor)
def updateColorSlot(self, handle, color):
h = unicode(handle)
self.changeColor(h, color)
@QtCore.pyqtSlot(QtCore.QString, QtCore.QString)
def deliverMessage(self, handle, msg):
h = unicode(handle)
m = unicode(msg)
self.newMessage(h, m)
@QtCore.pyqtSlot(QtCore.QString, QtCore.QString, QtCore.QString)
def deliverMemo(self, chan, handle, msg):
(c, h, m) = (unicode(chan), unicode(handle), unicode(msg))
self.newMemoMsg(c,h,m)
@QtCore.pyqtSlot(QtCore.QString, QtCore.QString, QtCore.QString)
def timeCommand(self, chan, handle, command):
(c, h, cmd) = (unicode(chan), unicode(handle), unicode(command))
if self.memos[c]:
self.memos[c].timeUpdate(h, cmd)
@QtCore.pyqtSlot(QtCore.QString, PesterList)
def updateNames(self, channel, names):
c = unicode(channel)
# update name DB
self.namesdb[c] = names
# warn interested party of names
self.namesUpdated.emit()
@QtCore.pyqtSlot(QtCore.QString, QtCore.QString, QtCore.QString)
def userPresentUpdate(self, handle, channel, update):
c = unicode(channel)
n = unicode(handle)
if update == "nick":
l = n.split(":")
oldnick = l[0]
newnick = l[1]
if update == "quit":
for c in self.namesdb.keys():
try:
i = self.namesdb[c].index(n)
self.namesdb[c].pop(i)
except ValueError:
pass
except KeyError:
self.namesdb[c] = []
elif update == "left":
try:
i = self.namesdb[c].index(n)
self.namesdb[c].pop(i)
except ValueError:
pass
except KeyError:
self.namesdb[c] = []
elif update == "nick":
for c in self.namesdb.keys():
try:
i = self.namesdb[c].index(oldnick)
self.namesdb[c].pop(i)
self.namesdb[c].append(newnick)
except ValueError:
pass
except KeyError:
pass
elif update == "join":
try:
i = self.namesdb[c].index(n)
except ValueError:
self.namesdb[c].append(n)
except KeyError:
self.namesdb[c] = [n]
self.userPresentSignal.emit(handle, channel, update)
@QtCore.pyqtSlot()
def addChumWindow(self):
if not hasattr(self, 'addchumdialog'):
self.addchumdialog = None
if not self.addchumdialog:
self.addchumdialog = QtGui.QInputDialog(self)
(handle, ok) = self.addchumdialog.getText(self, "New Chum", "Enter Chum Handle:")
if ok:
handle = unicode(handle)
if not (PesterProfile.checkLength(handle) and
PesterProfile.checkValid(handle)):
errormsg = QtGui.QErrorMessage(self)
errormsg.showMessage("THIS IS NOT A VALID CHUMTAG!")
self.addchumdialog = None
return
chum = PesterProfile(handle, chumdb=self.chumdb)
self.addChum(chum)
self.addchumdialog = None
@QtCore.pyqtSlot(QtGui.QListWidgetItem)
def removeChum(self, chumlisting):
self.config.removeChum(chumlisting.chum)
@QtCore.pyqtSlot(QtCore.QString)
def blockChum(self, handle):
h = unicode(handle)
self.config.addBlocklist(h)
self.config.removeChum(h)
if self.convos.has_key(h):
convo = self.convos[h]
msg = self.profile().pestermsg(convo.chum, QtGui.QColor(self.theme["convo/systemMsgColor"]), self.theme["convo/text/blocked"])
convo.textArea.append(convertTags(msg))
self.chatlog.log(convo.chum.handle, msg)
convo.updateBlocked()
self.chumList.removeChum(h)
if hasattr(self, 'trollslum') and self.trollslum:
newtroll = PesterProfile(h)
self.trollslum.addTroll(newtroll)
self.moodRequest.emit(newtroll)
self.blockedChum.emit(handle)
@QtCore.pyqtSlot(QtCore.QString)
def unblockChum(self, handle):
h = unicode(handle)
self.config.delBlocklist(h)
if self.convos.has_key(h):
convo = self.convos[h]
msg = self.profile().pestermsg(convo.chum, QtGui.QColor(self.theme["convo/systemMsgColor"]), self.theme["convo/text/unblocked"])
convo.textArea.append(convertTags(msg))
self.chatlog.log(convo.chum.handle, msg)
convo.updateMood(convo.chum.mood, unblocked=True)
chum = PesterProfile(h, chumdb=self.chumdb)
if hasattr(self, 'trollslum') and self.trollslum:
self.trollslum.removeTroll(handle)
self.config.addChum(chum)
self.chumList.addChum(chum)
self.moodRequest.emit(chum)
self.unblockedChum.emit(handle)
@QtCore.pyqtSlot(bool)
def toggleIdle(self, idle):
if idle:
sysColor = QtGui.QColor(self.theme["convo/systemMsgColor"])
verb = self.theme["convo/text/idle"]
for (h, convo) in self.convos.iteritems():
if convo.chumopen:
msg = self.profile().idlemsg(sysColor, verb)
convo.textArea.append(convertTags(msg))
self.chatlog.log(h, msg)
self.sendMessage.emit("PESTERCHUM:IDLE", h)
else:
self.idletime = 0
@QtCore.pyqtSlot()
def checkIdle(self):
newpos = QtGui.QCursor.pos()
if newpos == self.idleposition:
self.idletime += 1
else:
self.idletime = 0
if self.idletime >= self.idlethreshold:
if not self.idleaction.isChecked():
self.idleaction.toggle()
self.autoidle = True
else:
if self.autoidle:
if self.idleaction.isChecked():
self.idleaction.toggle()
self.autoidle = False
self.idleposition = newpos
@QtCore.pyqtSlot()
def importExternalConfig(self):
f = QtGui.QFileDialog.getOpenFileName(self)
fp = open(f, 'r')
for l in fp.xreadlines():
# import chumlist
chum_mo = re.match("handle: ([A-Za-z0-9]+)", l)
if chum_mo is not None:
chum = PesterProfile(chum_mo.group(1))
self.addChum(chum)
@QtCore.pyqtSlot()
def showMemos(self, channel=""):
if not hasattr(self, 'memochooser'):
self.memochooser = None
if self.memochooser:
return
self.memochooser = PesterMemoList(self, channel)
self.connect(self.memochooser, QtCore.SIGNAL('accepted()'),
self, QtCore.SLOT('joinSelectedMemo()'))
self.connect(self.memochooser, QtCore.SIGNAL('rejected()'),
self, QtCore.SLOT('memoChooserClose()'))
self.requestChannelList.emit()
self.memochooser.show()
@QtCore.pyqtSlot()
def joinSelectedMemo(self):
newmemo = self.memochooser.newmemoname()
selectedmemo = self.memochooser.selectedmemo()
time = unicode(self.memochooser.timeinput.text())
secret = self.memochooser.secretChannel.isChecked()
if newmemo:
channel = "#"+unicode(newmemo).replace(" ", "_")
channel = re.sub(r"[^A-Za-z0-9#_]", "", channel)
self.newMemo(channel, time, secret=secret)
elif selectedmemo:
channel = "#"+unicode(selectedmemo.text())
self.newMemo(channel, time)
self.memochooser = None
@QtCore.pyqtSlot()
def memoChooserClose(self):
self.memochooser = None
@QtCore.pyqtSlot()
def memoChooserClose(self):
self.memochooser = None
@QtCore.pyqtSlot(PesterList)
def updateChannelList(self, channels):
if hasattr(self, 'memochooser') and self.memochooser:
self.memochooser.updateChannels(channels)
@QtCore.pyqtSlot()
def showAllUsers(self):
if not hasattr(self, 'allusers'):
self.allusers = None
if not self.allusers:
self.allusers = PesterUserlist(self.config, self.theme, self)
self.connect(self.allusers, QtCore.SIGNAL('accepted()'),
self, QtCore.SLOT('userListClose()'))
self.connect(self.allusers, QtCore.SIGNAL('rejected()'),
self, QtCore.SLOT('userListClose()'))
self.connect(self.allusers, QtCore.SIGNAL('addChum(QString)'),
self, QtCore.SLOT('userListAdd(QString)'))
self.requestNames.emit("#pesterchum")
self.allusers.show()
@QtCore.pyqtSlot(QtCore.QString)
def userListAdd(self, handle):
h = unicode(handle)
chum = PesterProfile(h, chumdb=self.chumdb)
self.addChum(chum)
@QtCore.pyqtSlot()
def userListClose(self):
self.allusers = None
@QtCore.pyqtSlot()
def openQuirks(self):
if not hasattr(self, 'quirkmenu'):
self.quirkmenu = None
if not self.quirkmenu:
self.quirkmenu = PesterChooseQuirks(self.config, self.theme, self)
self.connect(self.quirkmenu, QtCore.SIGNAL('accepted()'),
self, QtCore.SLOT('updateQuirks()'))
self.connect(self.quirkmenu, QtCore.SIGNAL('rejected()'),
self, QtCore.SLOT('closeQuirks()'))
self.quirkmenu.show()
self.quirkmenu.raise_()
self.quirkmenu.activateWindow()
@QtCore.pyqtSlot()
def updateQuirks(self):
quirks = pesterQuirks(self.quirkmenu.quirks())
self.userprofile.setQuirks(quirks)
self.quirkmenu = None
@QtCore.pyqtSlot()
def closeQuirks(self):
self.quirkmenu = None
@QtCore.pyqtSlot()
def openOpts(self):
if not hasattr(self, 'optionmenu'):
self.optionmenu = None
if not self.optionmenu:
self.optionmenu = PesterOptions(self.config, self.theme, self)
self.connect(self.optionmenu, QtCore.SIGNAL('accepted()'),
self, QtCore.SLOT('updateOptions()'))
self.connect(self.optionmenu, QtCore.SIGNAL('rejected()'),
self, QtCore.SLOT('closeOptions()'))
self.optionmenu.show()
self.optionmenu.raise_()
self.optionmenu.activateWindow()
@QtCore.pyqtSlot()
def closeOptions(self):
self.optionmenu.close()
self.optionmenu = None
@QtCore.pyqtSlot()
def updateOptions(self):
# tabs
curtab = self.config.tabs()
tabsetting = self.optionmenu.tabcheck.isChecked()
if curtab and not tabsetting:
# split tabs into windows
windows = []
if self.tabconvo:
windows = list(self.tabconvo.convos.values())
if self.tabmemo:
windows += list(self.tabmemo.convos.values())
for w in windows:
w.setParent(None)
w.show()
w.raiseChat()
if self.tabconvo:
self.tabconvo.closeSoft()
if self.tabmemo:
self.tabmemo.closeSoft()
# save options
self.config.set("tabs", tabsetting)
elif tabsetting and not curtab:
# combine
self.createTabWindow()
newconvos = {}
for (h,c) in self.convos.iteritems():
c.setParent(self.tabconvo)
self.tabconvo.addChat(c)
self.tabconvo.show()
newconvos[h] = c
self.convos = newconvos
newmemos = {}
self.createMemoTabWindow()
for (h,m) in self.memos.iteritems():
m.setParent(self.tabmemo)
self.tabmemo.addChat(m)
self.tabmemo.show()
newmemos[h] = m
self.memos = newmemos
# save options
self.config.set("tabs", tabsetting)
# sound
soundsetting = self.optionmenu.soundcheck.isChecked()
self.config.set("soundon", soundsetting)
self.optionmenu = None
@QtCore.pyqtSlot()
def themeSelected(self):
themename = unicode(self.choosetheme.themeBox.currentText())
if themename != self.theme.name:
try:
self.changeTheme(pesterTheme(themename))
except ValueError, e:
themeWarning = QtGui.QMessageBox(self)
themeWarning.setText("Theme Error: %s" % (e))
themeWarning.exec_()
self.choosetheme = None
return
# update profile
self.userprofile.setTheme(self.theme)
self.choosetheme = None
@QtCore.pyqtSlot()
def closeTheme(self):
self.choosetheme = None
@QtCore.pyqtSlot()
def profileSelected(self):
if self.chooseprofile.profileBox and \
self.chooseprofile.profileBox.currentIndex() > 0:
handle = unicode(self.chooseprofile.profileBox.currentText())
if handle == self.profile().handle:
self.chooseprofile = None
return
self.userprofile = userProfile(handle)
self.changeTheme(self.userprofile.getTheme())
else:
handle = unicode(self.chooseprofile.chumHandle.text())
if handle == self.profile().handle:
self.chooseprofile = None
return
profile = PesterProfile(handle,
self.chooseprofile.chumcolor)
self.userprofile = userProfile.newUserProfile(profile)
self.changeTheme(self.userprofile.getTheme())
self.chatlog.close()
self.chatlog = PesterLog(handle)
# is default?
if self.chooseprofile.defaultcheck.isChecked():
self.config.set("defaultprofile", self.userprofile.chat.handle)
# this may have to be fixed
self.closeConversations()
if hasattr(self, 'trollslum') and self.trollslum:
self.trollslum.close()
self.chooseprofile = None
self.profileChanged.emit()
@QtCore.pyqtSlot()
def showTrollSlum(self):
if not hasattr(self, 'trollslum'):
self.trollslum = None
if self.trollslum:
return
trolls = [PesterProfile(h) for h in self.config.getBlocklist()]
self.trollslum = TrollSlumWindow(trolls, self)
self.connect(self.trollslum, QtCore.SIGNAL('blockChumSignal(QString)'),
self, QtCore.SLOT('blockChum(QString)'))
self.connect(self.trollslum,
QtCore.SIGNAL('unblockChumSignal(QString)'),
self, QtCore.SLOT('unblockChum(QString)'))
self.moodsRequest.emit(PesterList(trolls))
self.trollslum.show()
@QtCore.pyqtSlot()
def closeTrollSlum(self):
self.trollslum = None
@QtCore.pyqtSlot()
def changeMyColor(self):
if not hasattr(self, 'colorDialog'):
self.colorDialog = None
if self.colorDialog:
return
self.colorDialog = QtGui.QColorDialog(self)
color = self.colorDialog.getColor(initial=self.profile().color)
if not color.isValid():
color = self.profile().color
self.mychumcolor.setStyleSheet("background: %s" % color.name())
self.userprofile.setColor(color)
self.mycolorUpdated.emit()
self.colorDialog = None
@QtCore.pyqtSlot()
def closeProfile(self):
self.chooseprofile = None
@QtCore.pyqtSlot()
def switchProfile(self):
if self.convos:
closeWarning = QtGui.QMessageBox()
closeWarning.setText("WARNING: CHANGING PROFILES WILL CLOSE ALL CONVERSATION WINDOWS!")
closeWarning.setInformativeText("i warned you about windows bro!!!! i told you dog!")
closeWarning.setStandardButtons(QtGui.QMessageBox.Cancel | QtGui.QMessageBox.Ok)
closeWarning.setDefaultButton(QtGui.QMessageBox.Ok)
ret = closeWarning.exec_()
if ret == QtGui.QMessageBox.Cancel:
return
self.changeProfile()
@QtCore.pyqtSlot()
def aboutPesterchum(self):
if hasattr(self, 'aboutwindow') and self.aboutwindow:
return
self.aboutwindow = AboutPesterchum(self)
self.aboutwindow.exec_()
self.aboutwindow = None
@QtCore.pyqtSlot(QtCore.QString, QtCore.QString)
def nickCollision(self, handle, tmphandle):
self.mychumhandle.setText(tmphandle)
if not hasattr(self, 'chooseprofile'):
self.chooseprofile = None
if not self.chooseprofile:
h = unicode(handle)
self.changeProfile(collision=h)
@QtCore.pyqtSlot()
def pickTheme(self):
self.themePicker()
@QtCore.pyqtSlot(QtGui.QSystemTrayIcon.ActivationReason)
def systemTrayActivated(self, reason):
if reason == QtGui.QSystemTrayIcon.Trigger:
self.systemTrayFunction()
elif reason == QtGui.QSystemTrayIcon.Context:
pass
# show context menu i guess
#self.showTrayContext.emit()
closeToTraySignal = QtCore.pyqtSignal()
newConvoStarted = QtCore.pyqtSignal(QtCore.QString, bool, name="newConvoStarted")
sendMessage = QtCore.pyqtSignal(QtCore.QString, QtCore.QString)
convoClosed = QtCore.pyqtSignal(QtCore.QString)
profileChanged = QtCore.pyqtSignal()
moodRequest = QtCore.pyqtSignal(PesterProfile)
moodsRequest = QtCore.pyqtSignal(PesterList)
moodUpdated = QtCore.pyqtSignal()
requestChannelList = QtCore.pyqtSignal()
requestNames = QtCore.pyqtSignal(QtCore.QString)
namesUpdated = QtCore.pyqtSignal()
userPresentSignal = QtCore.pyqtSignal(QtCore.QString,QtCore.QString,QtCore.QString)
mycolorUpdated = QtCore.pyqtSignal()
trayIconSignal = QtCore.pyqtSignal(int)
blockedChum = QtCore.pyqtSignal(QtCore.QString)
unblockedChum = QtCore.pyqtSignal(QtCore.QString)
kickUser = QtCore.pyqtSignal(QtCore.QString, QtCore.QString)
joinChannel = QtCore.pyqtSignal(QtCore.QString)
leftChannel = QtCore.pyqtSignal(QtCore.QString)
setChannelMode = QtCore.pyqtSignal(QtCore.QString, QtCore.QString, QtCore.QString)
closeSignal = QtCore.pyqtSignal()
reconnectIRC = QtCore.pyqtSignal()
class IRCThread(QtCore.QThread):
def __init__(self, ircobj):
QtCore.QThread.__init__(self)
self.irc = ircobj
def run(self):
irc = self.irc
irc.IRCConnect()
while 1:
if irc.brokenConnection:
irc.brokenConnection = False
self.restartIRC.emit()
irc.closeConnection()
irc.IRCConnect()
try:
irc.updateIRC()
except socket.error:
irc.setConnectionBroken()
restartIRC = QtCore.pyqtSignal()
class PesterTray(QtGui.QSystemTrayIcon):
def __init__(self, icon, mainwindow, parent):
QtGui.QSystemTrayIcon.__init__(self, icon, parent)
self.mainwindow = mainwindow
@QtCore.pyqtSlot(int)
def changeTrayIcon(self, i):
if i == 0:
self.setIcon(PesterIcon(self.mainwindow.theme["main/icon"]))
else:
self.setIcon(PesterIcon(self.mainwindow.theme["main/newmsgicon"]))
@QtCore.pyqtSlot()
def mainWindowClosed(self):
self.hide()
class MainProgram(QtCore.QObject):
def __init__(self):
QtCore.QObject.__init__(self)
self.app = QtGui.QApplication(sys.argv)
if pygame.mixer:
# we could set the frequency higher but i love how cheesy it sounds
try:
pygame.mixer.init()
except pygame.error, e:
print "Warning: No sound! %s" % (e)
else:
print "Warning: No sound!"
self.widget = PesterWindow()
self.widget.show()
self.trayicon = PesterTray(PesterIcon(self.widget.theme["main/icon"]), self.widget, self.app)
self.traymenu = QtGui.QMenu()
moodMenu = self.traymenu.addMenu("SET MOOD")
self.moodactions = {}
for (i,m) in enumerate(Mood.moods):
maction = QtGui.QAction(m.upper(), self)
mobj = PesterMoodAction(i, self.widget.moods.updateMood)
self.trayicon.connect(maction, QtCore.SIGNAL('triggered()'),
mobj, QtCore.SLOT('updateMood()'))
self.moodactions[i] = mobj
moodMenu.addAction(maction)
exitAction = QtGui.QAction("EXIT", self)
self.trayicon.connect(exitAction, QtCore.SIGNAL('triggered()'),
self.widget, QtCore.SLOT('close()'))
self.traymenu.addAction(exitAction)
self.trayicon.setContextMenu(self.traymenu)
self.trayicon.show()
self.trayicon.connect(self.trayicon,
QtCore.SIGNAL('activated(QSystemTrayIcon::ActivationReason)'),
self.widget,
QtCore.SLOT('systemTrayActivated(QSystemTrayIcon::ActivationReason)'))
self.trayicon.connect(self.widget,
QtCore.SIGNAL('trayIconSignal(int)'),
self.trayicon,
QtCore.SLOT('changeTrayIcon(int)'))
self.trayicon.connect(self.widget,
QtCore.SIGNAL('closeToTraySignal()'),
self.trayicon,
QtCore.SLOT('show()'))
self.trayicon.connect(self.widget,
QtCore.SIGNAL('closeSignal()'),
self.trayicon,
QtCore.SLOT('mainWindowClosed()'))
self.irc = PesterIRC(self.widget.config, self.widget)
self.connectWidgets(self.irc, self.widget)
self.ircapp = IRCThread(self.irc)
self.connect(self.ircapp, QtCore.SIGNAL('restartIRC()'),
self, QtCore.SLOT('restartIRC()'))
def connectWidgets(self, irc, widget):
irc.connect(widget, QtCore.SIGNAL('sendMessage(QString, QString)'),
irc, QtCore.SLOT('sendMessage(QString, QString)'))
irc.connect(widget,
QtCore.SIGNAL('newConvoStarted(QString, bool)'),
irc, QtCore.SLOT('startConvo(QString, bool)'))
irc.connect(widget,
QtCore.SIGNAL('convoClosed(QString)'),
irc, QtCore.SLOT('endConvo(QString)'))
irc.connect(widget,
QtCore.SIGNAL('profileChanged()'),
irc,
QtCore.SLOT('updateProfile()'))
irc.connect(widget,
QtCore.SIGNAL('moodRequest(PyQt_PyObject)'),
irc,
QtCore.SLOT('getMood(PyQt_PyObject)'))
irc.connect(widget,
QtCore.SIGNAL('moodsRequest(PyQt_PyObject)'),
irc,
QtCore.SLOT('getMoods(PyQt_PyObject)'))
irc.connect(widget,
QtCore.SIGNAL('moodUpdated()'),
irc,
QtCore.SLOT('updateMood()'))
irc.connect(widget,
QtCore.SIGNAL('mycolorUpdated()'),
irc,
QtCore.SLOT('updateColor()'))
irc.connect(widget,
QtCore.SIGNAL('blockedChum(QString)'),
irc,
QtCore.SLOT('blockedChum(QString)'))
irc.connect(widget,
QtCore.SIGNAL('unblockedChum(QString)'),
irc,
QtCore.SLOT('unblockedChum(QString)'))
irc.connect(widget,
QtCore.SIGNAL('requestNames(QString)'),
irc,
QtCore.SLOT('requestNames(QString)'))
irc.connect(widget,
QtCore.SIGNAL('requestChannelList()'),
irc,
QtCore.SLOT('requestChannelList()'))
irc.connect(widget,
QtCore.SIGNAL('joinChannel(QString)'),
irc,
QtCore.SLOT('joinChannel(QString)'))
irc.connect(widget,
QtCore.SIGNAL('leftChannel(QString)'),
irc,
QtCore.SLOT('leftChannel(QString)'))
irc.connect(widget,
QtCore.SIGNAL('kickUser(QString, QString)'),
irc,
QtCore.SLOT('kickUser(QString, QString)'))
irc.connect(widget,
QtCore.SIGNAL('setChannelMode(QString, QString, QString)'),
irc,
QtCore.SLOT('setChannelMode(QString, QString, QString)'))
irc.connect(widget,
QtCore.SIGNAL('reconnectIRC()'),
irc,
QtCore.SLOT('reconnectIRC()'))
# IRC --> Main window
irc.connect(irc, QtCore.SIGNAL('connected()'),
widget, QtCore.SLOT('connected()'))
irc.connect(irc,
QtCore.SIGNAL('moodUpdated(QString, PyQt_PyObject)'),
widget,
QtCore.SLOT('updateMoodSlot(QString, PyQt_PyObject)'))
irc.connect(irc,
QtCore.SIGNAL('colorUpdated(QString, QColor)'),
widget,
QtCore.SLOT('updateColorSlot(QString, QColor)'))
irc.connect(irc,
QtCore.SIGNAL('messageReceived(QString, QString)'),
widget,
QtCore.SLOT('deliverMessage(QString, QString)'))
irc.connect(irc,
QtCore.SIGNAL('memoReceived(QString, QString, QString)'),
widget,
QtCore.SLOT('deliverMemo(QString, QString, QString)'))
irc.connect(irc,
QtCore.SIGNAL('nickCollision(QString, QString)'),
widget,
QtCore.SLOT('nickCollision(QString, QString)'))
irc.connect(irc,
QtCore.SIGNAL('namesReceived(QString, PyQt_PyObject)'),
widget,
QtCore.SLOT('updateNames(QString, PyQt_PyObject)'))
irc.connect(irc,
QtCore.SIGNAL('userPresentUpdate(QString, QString, QString)'),
widget,
QtCore.SLOT('userPresentUpdate(QString, QString, QString)'))
irc.connect(irc,
QtCore.SIGNAL('channelListReceived(PyQt_PyObject)'),
widget,
QtCore.SLOT('updateChannelList(PyQt_PyObject)'))
irc.connect(irc,
QtCore.SIGNAL('timeCommand(QString, QString, QString)'),
widget,
QtCore.SLOT('timeCommand(QString, QString, QString)'))
@QtCore.pyqtSlot()
def restartIRC(self):
self.widget.show()
self.widget.activateWindow()
self.widget.loadingscreen = LoadingScreen(self.widget)
self.connect(self.widget.loadingscreen, QtCore.SIGNAL('rejected()'),
self.widget, QtCore.SLOT('close()'))
status = self.widget.loadingscreen.exec_()
if status == QtGui.QDialog.Rejected:
sys.exit(0)
def run(self):
self.ircapp.start()
self.widget.loadingscreen = LoadingScreen(self.widget)
self.connect(self.widget.loadingscreen, QtCore.SIGNAL('rejected()'),
self.widget, QtCore.SLOT('close()'))
status = self.widget.loadingscreen.exec_()
if status == QtGui.QDialog.Rejected:
sys.exit(0)
sys.exit(self.app.exec_())
pesterchum = MainProgram()
pesterchum.run()