# pesterchum import os, shutil, sys, getopt if os.path.dirname(sys.argv[0]): os.chdir(os.path.dirname(sys.argv[0])) import version version.pcVerCalc() import logging from datetime import * import random import re from time import time import threading, Queue from pnc.dep.attrdict import AttrDict logging.basicConfig(level=logging.WARNING) def _py_version_check(): # == Python Version Checking == # Check that we're running the right version of Pesterchum. # This is the version we need. pyreq = {"major": 2, "minor": 7} # Just some preprocessing to make formatting the version info a little # easier. Note that the sys.version_info type doesn't convert to dict, # despite having named indices like a namedtuple, so we have to do it # manually. # This is the version we have. pyver = dict(zip(("major", "minor"), sys.version_info[:2])) # Compose the base of an error message that we may use in the future. errmsg = "ERROR: Pesterchum is designed to be run on Python \ {major}.{minor}.".format(**pyreq) errmsg = [ errmsg ] errmsg.append("It is not designed for use with Python {major}.{minor}.") # Now we have a list that we can further process into a more specific # error message. if pyver["major"] > pyreq["major"]: # We're using Python 3, which this script won't work with. errmsg = errmsg.extend([ "Due to syntax differences,", "it cannot be run with this version of Python." ]) errmsg = ' '.join(errmsg) errmsg = errmsg.format(**pyver) logging.critical(errmsg) exit() elif pyver["major"] != pyreq["major"] or pyver["minor"] < pyreq["minor"]: # We're either not running Python 2 (we have something earlier?!) or # we're below the minimum required minor version (e.g. 2.6 or 2.4 or # similar). # This means that we wouldn't have certain syntax improvements that we # need - like inline generators, 'with' statements, lambdas, etc. # NOTE: This MAY be lowered to 2.6 later, since there's little # difference. errmsg = errmsg.extend([ "This version of Python lacks certain features", "that are necessary for it to run." ]) errmsg = ' '.join(errmsg) errmsg = errmsg.format(**pyver) logging.critical(errmsg) exit() # Actually do the version check. _py_version_check() try: import console except ImportError: _CONSOLE = False logging.warning("Console file not shipped; skipping.") except Exception as err: _CONSOLE = False # Consider erroring? logging.error("Failed to load console!", exc_info=err) else: _CONSOLE = True reqmissing = [] optmissing = [] try: from PyQt4 import QtGui, QtCore except ImportError as e: module = str(e) if module.startswith("No module named ") or \ module.startswith("cannot import name "): reqmissing.append(module[module.rfind(" ")+1:]) else: print e del module try: import pygame except ImportError as e: pygame = None module = str(e) if module[:16] == "No module named ": optmissing.append(module[16:]) else: print e del module if reqmissing: print "ERROR: The following modules are required for Pesterchum to run and are missing on your system:" for m in reqmissing: print "* "+m exit() vnum = QtCore.qVersion() major = int(vnum[:vnum.find(".")]) if vnum.find(".", vnum.find(".")+1) != -1: minor = int(vnum[vnum.find(".")+1:vnum.find(".", vnum.find(".")+1)]) else: minor = int(vnum[vnum.find(".")+1:]) if not ((major > 4) or (major == 4 and minor >= 6)): print "ERROR: Pesterchum requires Qt version >= 4.6" print "You currently have version " + vnum + ". Please upgrade Qt." exit() import ostools # Placed here before importing the rest of pesterchum, since bits of it need # OSX's data directory and it doesn't hurt to have everything set up before # plowing on. :o) # ~Lex _datadir = ostools.getDataDir() # See, what I've done here is that _datadir is '' if we're not on OSX, so the # concatination is the same as if it wasn't there. # UPDATE 2011-11-28 : # Now using data directory as defined by QDesktopServices on all platforms # (on Linux, same as using xdg). To stay safe with older versions, copy any # data (profiles, logs, etc) from old location to new data directory. if _datadir: if not os.path.exists(_datadir): os.makedirs(_datadir) if not os.path.exists(_datadir+"profiles/") and os.path.exists("profiles/"): shutil.move("profiles/", _datadir+"profiles/") if not os.path.exists(_datadir+"pesterchum.js") and os.path.exists("pesterchum.js"): shutil.move("pesterchum.js", _datadir+"pesterchum.js") if not os.path.exists(_datadir+"logs/") and os.path.exists("logs/"): shutil.move("logs/", _datadir+"logs/") if not os.path.exists(_datadir+"profiles"): os.mkdir(_datadir+"profiles") if not os.path.exists(_datadir+"pesterchum.js"): f = open(_datadir+"pesterchum.js", 'w') f.write("{}") f.close() if not os.path.exists(_datadir+"logs"): os.mkdir(_datadir+"logs") from menus import PesterChooseQuirks, PesterChooseTheme, \ PesterChooseProfile, PesterOptions, PesterUserlist, PesterMemoList, \ LoadingScreen, AboutPesterchum, UpdatePesterchum, AddChumDialog from mood import Mood, PesterMoodAction, PesterMoodHandler, PesterMoodButton from dataobjs import PesterProfile, pesterQuirk, pesterQuirks from generic import PesterIcon, RightClickList, RightClickTree, \ MultiTextDialog, PesterList, CaseInsensitiveDict, MovingWindow, \ NoneSound, WMButton from convo import PesterTabWindow, PesterText, PesterInput, PesterConvo from parsetools import convertTags, addTimeInitial, themeChecker, ThemeException from memos import PesterMemo, MemoTabWindow, TimeTracker from irc import PesterIRC from logviewer import PesterLogUserSelect, PesterLogViewer from bugreport import BugReporter from randomer import RandomHandler, RANDNICK import nickservmsgs # Rawr, fuck you OSX leopard if not ostools.isOSXLeopard(): from updatecheck import MSPAChecker from toast import PesterToastMachine, PesterToast from libs import pytwmn from profile import * canon_handles = ["apocalypseArisen", "arsenicCatnip", "arachnidsGrip", "adiosToreador", \ "caligulasAquarium", "cuttlefishCuller", "carcinoGeneticist", "centaursTesticle", \ "grimAuxiliatrix", "gallowsCalibrator", "gardenGnostic", "ectoBiologist", \ "twinArmageddons", "terminallyCapricious", "turntechGodhead", "tentacleTherapist"] CUSTOMBOTS = ["CALSPRITE", RANDNICK.upper()] BOTNAMES = ["NICKSERV", "CHANSERV", "MEMOSERV", "OPERSERV", "HELPSERV"] BOTNAMES.extend(CUSTOMBOTS) # Save the main app. From here, we should be able to get everything else in # order, for console use. _CONSOLE_ENV = AttrDict() _CONSOLE_ENV.PAPP = None 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 chumListing(QtGui.QTreeWidgetItem): def __init__(self, chum, window): super(chumListing, self).__init__([chum.handle]) self.mainwindow = window self.chum = chum self.handle = chum.handle self.setMood(Mood("offline")) self.status = None self.setToolTip(0, "%s: %s" % (chum.handle, window.chumdb.getNotes(chum.handle))) def setMood(self, mood): if hasattr(self.mainwindow, "chumList") and self.mainwindow.chumList.notify: #print "%s -> %s" % (self.chum.mood.name(), mood.name()) if self.mainwindow.config.notifyOptions() & self.mainwindow.config.SIGNOUT and \ mood.name() == "offline" and self.chum.mood.name() != "offline": #print "OFFLINE NOTIFY: " + self.handle uri = self.mainwindow.theme["toasts/icon/signout"] n = self.mainwindow.tm.Toast(self.mainwindow.tm.appName, "%s is Offline" % (self.handle), uri) n.show() elif self.mainwindow.config.notifyOptions() & self.mainwindow.config.SIGNIN and \ mood.name() != "offline" and self.chum.mood.name() == "offline": #print "ONLINE NOTIFY: " + self.handle uri = self.mainwindow.theme["toasts/icon/signin"] n = self.mainwindow.tm.Toast(self.mainwindow.tm.appName, "%s is Online" % (self.handle), uri) n.show() login = False logout = False if mood.name() == "offline" and self.chum.mood.name() != "offline": logout = True elif mood.name() != "offline" and self.chum.mood.name() == "offline": login = True self.chum.mood = mood self.updateMood(login=login, logout=logout) def setColor(self, color): self.chum.color = color def updateMood(self, unblock=False, login=False, logout=False): mood = self.chum.mood self.mood = mood icon = self.mood.icon(self.mainwindow.theme) if login: self.login() elif logout: self.logout() else: self.setIcon(0, icon) try: self.setTextColor(0, QtGui.QColor(self.mainwindow.theme["main/chums/moods"][self.mood.name()]["color"])) except KeyError: self.setTextColor(0, QtGui.QColor(self.mainwindow.theme["main/chums/moods/chummy/color"])) def changeTheme(self, theme): icon = self.mood.icon(theme) self.setIcon(0, icon) try: self.setTextColor(0, QtGui.QColor(self.mainwindow.theme["main/chums/moods"][self.mood.name()]["color"])) except KeyError: self.setTextColor(0, QtGui.QColor(self.mainwindow.theme["main/chums/moods/chummy/color"])) def login(self): self.setIcon(0, PesterIcon("themes/arrow_right.png")) self.status = "in" QtCore.QTimer.singleShot(5000, self.doneLogin) def doneLogin(self): icon = self.mood.icon(self.mainwindow.theme) self.setIcon(0, icon) def logout(self): self.setIcon(0, PesterIcon("themes/arrow_left.png")) self.status = "out" QtCore.QTimer.singleShot(5000, self.doneLogout) def doneLogout(self): hideoff = self.mainwindow.config.hideOfflineChums() icon = self.mood.icon(self.mainwindow.theme) self.setIcon(0, icon) if hideoff and self.status and self.status == "out": self.mainwindow.chumList.takeItem(self) def __lt__(self, cl): h1 = self.handle.lower() h2 = cl.handle.lower() return (h1 < h2) class chumArea(RightClickTree): # This is the class that controls the actual main chumlist, I think. # Looking into how the groups work might be wise. def __init__(self, chums, parent=None): super(chumArea, self).__init__(parent) self.notify = False QtCore.QTimer.singleShot(30000, self, QtCore.SLOT('beginNotify()')) self.mainwindow = parent theme = self.mainwindow.theme self.chums = chums gTemp = self.mainwindow.config.getGroups() self.groups = [g[0] for g in gTemp] self.openGroups = [g[1] for g in gTemp] self.showAllGroups(True) if not self.mainwindow.config.hideOfflineChums(): self.showAllChums() if not self.mainwindow.config.showEmptyGroups(): self.hideEmptyGroups() self.groupMenu = QtGui.QMenu(self) self.canonMenu = QtGui.QMenu(self) 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.logchum = QtGui.QAction(self.mainwindow.theme["main/menus/rclickchumlist/viewlog"], self) self.connect(self.logchum, QtCore.SIGNAL('triggered()'), self, QtCore.SLOT('openChumLogs()')) self.reportchum = QtGui.QAction(self.mainwindow.theme["main/menus/rclickchumlist/report"], self) self.connect(self.reportchum, QtCore.SIGNAL('triggered()'), self, QtCore.SLOT('reportChum()')) self.findalts = QtGui.QAction("Find Alts", self) self.connect(self.findalts, QtCore.SIGNAL('triggered()'), self, QtCore.SLOT('findAlts()')) self.removegroup = QtGui.QAction(self.mainwindow.theme["main/menus/rclickchumlist/removegroup"], self) self.connect(self.removegroup, QtCore.SIGNAL('triggered()'), self, QtCore.SLOT('removeGroup()')) self.renamegroup = QtGui.QAction(self.mainwindow.theme["main/menus/rclickchumlist/renamegroup"], self) self.connect(self.renamegroup, QtCore.SIGNAL('triggered()'), self, QtCore.SLOT('renameGroup()')) self.notes = QtGui.QAction(self.mainwindow.theme["main/menus/rclickchumlist/notes"], self) self.connect(self.notes, QtCore.SIGNAL('triggered()'), self, QtCore.SLOT('editNotes()')) self.optionsMenu.addAction(self.pester) self.optionsMenu.addAction(self.logchum) self.optionsMenu.addAction(self.notes) self.optionsMenu.addAction(self.blockchum) self.optionsMenu.addAction(self.removechum) self.moveMenu = QtGui.QMenu(self.mainwindow.theme["main/menus/rclickchumlist/movechum"], self) self.optionsMenu.addMenu(self.moveMenu) self.optionsMenu.addAction(self.reportchum) self.moveGroupMenu() self.groupMenu.addAction(self.renamegroup) self.groupMenu.addAction(self.removegroup) self.canonMenu.addAction(self.pester) self.canonMenu.addAction(self.logchum) self.canonMenu.addAction(self.blockchum) self.canonMenu.addAction(self.removechum) self.canonMenu.addMenu(self.moveMenu) self.canonMenu.addAction(self.reportchum) self.canonMenu.addAction(self.findalts) self.initTheme(theme) #self.sortItems() #self.sortItems(1, QtCore.Qt.AscendingOrder) self.setSortingEnabled(False) self.header().hide() self.setDropIndicatorShown(True) self.setIndentation(4) self.setDragEnabled(True) self.setDragDropMode(QtGui.QAbstractItemView.InternalMove) self.setAnimated(True) self.setRootIsDecorated(False) self.connect(self, QtCore.SIGNAL('itemDoubleClicked(QTreeWidgetItem *, int)'), self, QtCore.SLOT('expandGroup()')) @QtCore.pyqtSlot() def beginNotify(self): print "BEGIN NOTIFY" self.notify = True def getOptionsMenu(self): if not self.currentItem(): return None text = unicode(self.currentItem().text(0)) if text.rfind(" (") != -1: text = text[0:text.rfind(" (")] if text == "Chums": return None elif text in self.groups: return self.groupMenu else: currenthandle = self.currentItem().chum.handle if currenthandle in canon_handles: return self.canonMenu else: return self.optionsMenu def startDrag(self, dropAction): # create mime data object mime = QtCore.QMimeData() mime.setData('application/x-item', '???') # start drag drag = QtGui.QDrag(self) drag.setMimeData(mime) drag.start(QtCore.Qt.MoveAction) def dragMoveEvent(self, event): if event.mimeData().hasFormat("application/x-item"): event.setDropAction(QtCore.Qt.MoveAction) event.accept() else: event.ignore() def dragEnterEvent(self, event): if (event.mimeData().hasFormat('application/x-item')): event.accept() else: event.ignore() def dropEvent(self, event): if (event.mimeData().hasFormat('application/x-item')): event.acceptProposedAction() else: event.ignore() return thisitem = str(event.source().currentItem().text(0)) if thisitem.rfind(" (") != -1: thisitem = thisitem[0:thisitem.rfind(" (")] # Drop item is a group thisitem = unicode(event.source().currentItem().text(0)) if thisitem.rfind(" (") != -1: thisitem = thisitem[0:thisitem.rfind(" (")] if thisitem == "Chums" or thisitem in self.groups: droppos = self.itemAt(event.pos()) if not droppos: return droppos = unicode(droppos.text(0)) if droppos.rfind(" ") != -1: droppos = droppos[0:droppos.rfind(" ")] if droppos == "Chums" or droppos in self.groups: saveOpen = event.source().currentItem().isExpanded() saveDrop = self.itemAt(event.pos()) saveItem = self.takeTopLevelItem(self.indexOfTopLevelItem(event.source().currentItem())) self.insertTopLevelItems(self.indexOfTopLevelItem(saveDrop)+1, [saveItem]) if saveOpen: saveItem.setExpanded(True) gTemp = [] for i in range(self.topLevelItemCount()): text = unicode(self.topLevelItem(i).text(0)) if text.rfind(" (") != -1: text = text[0:text.rfind(" (")] gTemp.append([unicode(text), self.topLevelItem(i).isExpanded()]) self.mainwindow.config.saveGroups(gTemp) # Drop item is a chum else: item = self.itemAt(event.pos()) if item: text = unicode(item.text(0)) # Figure out which group to drop into if text.rfind(" (") != -1: text = text[0:text.rfind(" (")] if text == "Chums" or text in self.groups: group = text gitem = item else: ptext = unicode(item.parent().text(0)) if ptext.rfind(" ") != -1: ptext = ptext[0:ptext.rfind(" ")] group = ptext gitem = item.parent() chumLabel = event.source().currentItem() chumLabel.chum.group = group self.mainwindow.chumdb.setGroup(chumLabel.chum.handle, group) self.takeItem(chumLabel) # Using manual chum reordering if self.mainwindow.config.sortMethod() == 2: insertIndex = gitem.indexOfChild(item) if insertIndex == -1: insertIndex = 0 gitem.insertChild(insertIndex, chumLabel) chums = self.mainwindow.config.chums() if item == gitem: item = gitem.child(0) inPos = chums.index(str(item.text(0))) if chums.index(thisitem) < inPos: inPos -= 1 chums.remove(thisitem) chums.insert(inPos, unicode(thisitem)) self.mainwindow.config.setChums(chums) else: self.addItem(chumLabel) if self.mainwindow.config.showOnlineNumbers(): self.showOnlineNumbers() def moveGroupMenu(self): currentGroup = self.currentItem() if currentGroup: if currentGroup.parent(): text = unicode(currentGroup.parent().text(0)) else: text = unicode(currentGroup.text(0)) if text.rfind(" (") != -1: text = text[0:text.rfind(" (")] currentGroup = text self.moveMenu.clear() actGroup = QtGui.QActionGroup(self) groups = self.groups[:] for gtext in groups: if gtext == currentGroup: continue movegroup = self.moveMenu.addAction(gtext) actGroup.addAction(movegroup) self.connect(actGroup, QtCore.SIGNAL('triggered(QAction *)'), self, QtCore.SLOT('moveToGroup(QAction *)')) def addChum(self, chum): if len([c for c in self.chums if c.handle == chum.handle]) != 0: return self.chums.append(chum) if not (self.mainwindow.config.hideOfflineChums() and chum.mood.name() == "offline"): chumLabel = chumListing(chum, self.mainwindow) self.addItem(chumLabel) #self.topLevelItem(0).addChild(chumLabel) #self.topLevelItem(0).sortChildren(0, QtCore.Qt.AscendingOrder) def getChums(self, handle): chums = self.findItems(handle, QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive) return chums def showAllChums(self): for c in self.chums: chandle = c.handle if not len(self.findItems(chandle, QtCore.Qt.MatchContains | QtCore.Qt.MatchRecursive)): chumLabel = chumListing(c, self.mainwindow) self.addItem(chumLabel) self.sort() def hideOfflineChums(self): for j in range(self.topLevelItemCount()): i = 0 listing = self.topLevelItem(j).child(i) while listing is not None: if listing.chum.mood.name() == "offline": self.topLevelItem(j).takeChild(i) else: i += 1 listing = self.topLevelItem(j).child(i) self.sort() def showAllGroups(self, first=False): if first: for i,g in enumerate(self.groups): child_1 = QtGui.QTreeWidgetItem(["%s" % (g)]) self.addTopLevelItem(child_1) if self.openGroups[i]: child_1.setExpanded(True) return curgroups = [] for i in range(self.topLevelItemCount()): text = unicode(self.topLevelItem(i).text(0)) if text.rfind(" (") != -1: text = text[0:text.rfind(" (")] curgroups.append(text) for i,g in enumerate(self.groups): if g not in curgroups: child_1 = QtGui.QTreeWidgetItem(["%s" % (g)]) j = 0 for h in self.groups: if h == g: self.insertTopLevelItem(j, child_1) break if h in curgroups: j += 1 if self.openGroups[i]: child_1.setExpanded(True) if self.mainwindow.config.showOnlineNumbers(): self.showOnlineNumbers() def showOnlineNumbers(self): if hasattr(self, 'groups'): self.hideOnlineNumbers() totals = {'Chums': 0} online = {'Chums': 0} for g in self.groups: totals[unicode(g)] = 0 online[unicode(g)] = 0 for c in self.chums: yes = c.mood.name() != "offline" if c.group == "Chums": totals[unicode(c.group)] = totals[unicode(c.group)]+1 if yes: online[unicode(c.group)] = online[unicode(c.group)]+1 elif c.group in totals: totals[unicode(c.group)] = totals[unicode(c.group)]+1 if yes: online[unicode(c.group)] = online[unicode(c.group)]+1 else: totals["Chums"] = totals["Chums"]+1 if yes: online["Chums"] = online["Chums"]+1 for i in range(self.topLevelItemCount()): text = unicode(self.topLevelItem(i).text(0)) if text.rfind(" (") != -1: text = text[0:text.rfind(" (")] if text in online: self.topLevelItem(i).setText(0, "%s (%i/%i)" % (text, online[text], totals[text])) def hideOnlineNumbers(self): for i in range(self.topLevelItemCount()): text = unicode(self.topLevelItem(i).text(0)) if text.rfind(" (") != -1: text = text[0:text.rfind(" (")] self.topLevelItem(i).setText(0, "%s" % (text)) def hideEmptyGroups(self): i = 0 listing = self.topLevelItem(i) while listing is not None: if listing.childCount() == 0: self.takeTopLevelItem(i) else: i += 1 listing = self.topLevelItem(i) @QtCore.pyqtSlot() def expandGroup(self): item = self.currentItem() text = unicode(item.text(0)) if text.rfind(" (") != -1: text = text[0:text.rfind(" (")] if text in self.groups: expand = item.isExpanded() self.mainwindow.config.expandGroup(text, not expand) def addItem(self, chumLabel): if hasattr(self, 'groups'): if chumLabel.chum.group not in self.groups: chumLabel.chum.group = "Chums" if "Chums" not in self.groups: self.mainwindow.config.addGroup("Chums") curgroups = [] for i in range(self.topLevelItemCount()): text = unicode(self.topLevelItem(i).text(0)) if text.rfind(" (") != -1: text = text[0:text.rfind(" (")] curgroups.append(text) if not self.findItems(chumLabel.handle, QtCore.Qt.MatchContains | QtCore.Qt.MatchRecursive): if chumLabel.chum.group not in curgroups: child_1 = QtGui.QTreeWidgetItem(["%s" % (chumLabel.chum.group)]) i = 0 for g in self.groups: if g == chumLabel.chum.group: self.insertTopLevelItem(i, child_1) break if g in curgroups: i += 1 if self.openGroups[self.groups.index("%s" % (chumLabel.chum.group))]: child_1.setExpanded(True) for i in range(self.topLevelItemCount()): text = unicode(self.topLevelItem(i).text(0)) if text.rfind(" (") != -1: text = text[0:text.rfind(" (")] if text == chumLabel.chum.group: break # Manual sorting if self.mainwindow.config.sortMethod() == 2: chums = self.mainwindow.config.chums() if chumLabel.chum.handle in chums: fi = chums.index(chumLabel.chum.handle) else: fi = 0 c = 1 # TODO: Rearrange chums list on drag-n-drop bestj = 0 bestname = "" if fi > 0: while not bestj: for j in xrange(self.topLevelItem(i).childCount()): if chums[fi-c] == str(self.topLevelItem(i).child(j).text(0)): bestj = j bestname = chums[fi-c] break c += 1 if fi-c < 0: break if bestname: self.topLevelItem(i).insertChild(bestj+1, chumLabel) else: self.topLevelItem(i).insertChild(bestj, chumLabel) #sys.exit(0) self.topLevelItem(i).addChild(chumLabel) else: # All other sorting self.topLevelItem(i).addChild(chumLabel) self.sort() if self.mainwindow.config.showOnlineNumbers(): self.showOnlineNumbers() else: # usually means this is now the trollslum if not self.findItems(chumLabel.handle, QtCore.Qt.MatchContains | QtCore.Qt.MatchRecursive): self.topLevelItem(0).addChild(chumLabel) self.topLevelItem(0).sortChildren(0, QtCore.Qt.AscendingOrder) def takeItem(self, chumLabel): r = None if not hasattr(chumLabel, 'chum'): return r for i in range(self.topLevelItemCount()): for j in range(self.topLevelItem(i).childCount()): if self.topLevelItem(i).child(j).text(0) == chumLabel.chum.handle: r = self.topLevelItem(i).takeChild(j) break if not self.mainwindow.config.showEmptyGroups(): self.hideEmptyGroups() if self.mainwindow.config.showOnlineNumbers(): self.showOnlineNumbers() return r def updateMood(self, handle, mood): hideoff = self.mainwindow.config.hideOfflineChums() chums = self.getChums(handle) oldmood = None if hideoff: if mood.name() != "offline" and \ len(chums) == 0 and \ handle in [p.handle for p in self.chums]: newLabel = chumListing([p for p in self.chums if p.handle == handle][0], self.mainwindow) self.addItem(newLabel) #self.sortItems() chums = [newLabel] elif mood.name() == "offline" and \ len(chums) > 0: for c in chums: if (hasattr(c, 'mood')): c.setMood(mood) #self.takeItem(c) chums = [] for c in chums: if (hasattr(c, 'mood')): oldmood = c.mood c.setMood(mood) if self.mainwindow.config.sortMethod() == 1: for i in range(self.topLevelItemCount()): saveCurrent = self.currentItem() self.moodSort(i) self.setCurrentItem(saveCurrent) if self.mainwindow.config.showOnlineNumbers(): self.showOnlineNumbers() 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.resize(*theme["main/chums/size"]) self.move(*theme["main/chums/loc"]) 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"]) self.logchum.setText(theme["main/menus/rclickchumlist/viewlog"]) self.reportchum.setText(theme["main/menus/rclickchumlist/report"]) self.notes.setText(theme["main/menus/rclickchumlist/notes"]) self.removegroup.setText(theme["main/menus/rclickchumlist/removegroup"]) self.renamegroup.setText(theme["main/menus/rclickchumlist/renamegroup"]) self.moveMenu.setTitle(theme["main/menus/rclickchumlist/movechum"]) def changeTheme(self, theme): self.initTheme(theme) chumlistings = [] for i in range(self.topLevelItemCount()): for j in range(self.topLevelItem(i).childCount()): chumlistings.append(self.topLevelItem(i).child(j)) #chumlistings = [self.item(i) for i in range(0, self.count())] for c in chumlistings: c.changeTheme(theme) def count(self): c = 0 for i in range(self.topLevelItemCount()): c = c + self.topLevelItem(i).childCount() return c def sort(self): if self.mainwindow.config.sortMethod() == 2: pass # Do nothing!!!!! :OOOOOOO It's manual, bitches elif self.mainwindow.config.sortMethod() == 1: for i in range(self.topLevelItemCount()): self.moodSort(i) else: for i in range(self.topLevelItemCount()): self.topLevelItem(i).sortChildren(0, QtCore.Qt.AscendingOrder) def moodSort(self, group): scrollPos = self.verticalScrollBar().sliderPosition() chums = [] listing = self.topLevelItem(group).child(0) while listing is not None: chums.append(self.topLevelItem(group).takeChild(0)) listing = self.topLevelItem(group).child(0) chums.sort(key=lambda x: ((999 if x.chum.mood.value() == 2 else x.chum.mood.value()), x.chum.handle), reverse=False) for c in chums: self.topLevelItem(group).addChild(c) self.verticalScrollBar().setSliderPosition(scrollPos) @QtCore.pyqtSlot() def activateChum(self): self.itemActivated.emit(self.currentItem(), 0) @QtCore.pyqtSlot() def removeChum(self, handle = None): if handle: clistings = self.getChums(handle) if len(clistings) <= 0: return 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().chum.handle) oldlist = self.takeItem(self.currentItem()) del oldlist @QtCore.pyqtSlot() def blockChum(self): currentChum = self.currentItem() if not currentChum: return self.blockChumSignal.emit(self.currentItem().chum.handle) @QtCore.pyqtSlot() def reportChum(self): currentChum = self.currentItem() if not currentChum: return self.mainwindow.reportChum(self.currentItem().chum.handle) @QtCore.pyqtSlot() def findAlts(self): currentChum = self.currentItem() if not currentChum: return self.mainwindow.sendMessage.emit("ALT %s" % (currentChum.chum.handle) , "calSprite") @QtCore.pyqtSlot() def openChumLogs(self): currentChum = self.currentItem() if not currentChum: return currentChum = currentChum.text(0) self.pesterlogviewer = PesterLogViewer(currentChum, self.mainwindow.config, self.mainwindow.theme, self.mainwindow) self.connect(self.pesterlogviewer, QtCore.SIGNAL('rejected()'), self, QtCore.SLOT('closeActiveLog()')) self.pesterlogviewer.show() self.pesterlogviewer.raise_() self.pesterlogviewer.activateWindow() @QtCore.pyqtSlot() def closeActiveLog(self): self.pesterlogviewer.close() self.pesterlogviewer = None @QtCore.pyqtSlot() def editNotes(self): currentChum = self.currentItem() if not currentChum: return (notes, ok) = QtGui.QInputDialog.getText(self, "Notes", "Enter your notes...") if ok: notes = unicode(notes) self.mainwindow.chumdb.setNotes(currentChum.handle, notes) currentChum.setToolTip(0, "%s: %s" % (currentChum.handle, notes)) @QtCore.pyqtSlot() def renameGroup(self): if not hasattr(self, 'renamegroupdialog'): self.renamegroupdialog = None if not self.renamegroupdialog: (gname, ok) = QtGui.QInputDialog.getText(self, "Rename Group", "Enter a new name for the group:") if ok: gname = unicode(gname) if re.search("[^A-Za-z0-9_\s]", gname) is not None: msgbox = QtGui.QMessageBox() msgbox.setInformativeText("THIS IS NOT A VALID GROUP NAME") msgbox.setStandardButtons(QtGui.QMessageBox.Ok) ret = msgbox.exec_() self.addgroupdialog = None return currentGroup = self.currentItem() if not currentGroup: return index = self.indexOfTopLevelItem(currentGroup) if index != -1: expanded = currentGroup.isExpanded() text = unicode(currentGroup.text(0)) if text.rfind(" (") != -1: text = text[0:text.rfind(" (")] self.mainwindow.config.delGroup(text) self.mainwindow.config.addGroup(gname, expanded) gTemp = self.mainwindow.config.getGroups() self.groups = [g[0] for g in gTemp] self.openGroups = [g[1] for g in gTemp] for i in range(currentGroup.childCount()): currentGroup.child(i).chum.group = gname self.mainwindow.chumdb.setGroup(currentGroup.child(i).chum.handle, gname) currentGroup.setText(0, gname) if self.mainwindow.config.showOnlineNumbers(): self.showOnlineNumbers() self.renamegroupdialog = None @QtCore.pyqtSlot() def removeGroup(self): currentGroup = self.currentItem() if not currentGroup: return text = unicode(currentGroup.text(0)) if text.rfind(" (") != -1: text = text[0:text.rfind(" (")] self.mainwindow.config.delGroup(text) gTemp = self.mainwindow.config.getGroups() self.groups = [g[0] for g in gTemp] self.openGroups = [g[1] for g in gTemp] for c in self.chums: if c.group == text: c.group = "Chums" self.mainwindow.chumdb.setGroup(c.handle, "Chums") for i in range(self.topLevelItemCount()): if self.topLevelItem(i).text(0) == currentGroup.text(0): break while self.topLevelItem(i) and self.topLevelItem(i).child(0): chumLabel = self.topLevelItem(i).child(0) self.takeItem(chumLabel) self.addItem(chumLabel) self.takeTopLevelItem(i) @QtCore.pyqtSlot(QtGui.QAction) def moveToGroup(self, item): if not item: return group = unicode(item.text()) chumLabel = self.currentItem() if not chumLabel: return chumLabel.chum.group = group self.mainwindow.chumdb.setGroup(chumLabel.chum.handle, group) self.takeItem(chumLabel) self.addItem(chumLabel) removeChumSignal = QtCore.pyqtSignal(QtCore.QString) blockChumSignal = QtCore.pyqtSignal(QtCore.QString) class trollSlum(chumArea): def __init__(self, trolls, mainwindow, parent=None): #~super(trollSlum, self).__init__(parent) # TODO: Rework inheritance here. QtGui.QTreeWidgetItem.__init__(self, parent) self.mainwindow = mainwindow theme = self.mainwindow.theme self.setStyleSheet(theme["main/trollslum/chumroll/style"]) self.chums = trolls child_1 = QtGui.QTreeWidgetItem([""]) self.addTopLevelItem(child_1) child_1.setExpanded(True) 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.setSortingEnabled(False) self.header().hide() self.setDropIndicatorShown(False) self.setIndentation(0) 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 contextMenuEvent(self, event): #fuckin Qt if event.reason() == QtGui.QContextMenuEvent.Mouse: listing = self.itemAt(event.pos()) self.setCurrentItem(listing) if self.currentItem().text(0) != "": self.optionsMenu.popup(event.globalPos()) 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): super(TrollSlumWindow, self).__init__(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 or not hasattr(currentListing, 'chum'): 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)[0]): 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 PesterWindow(MovingWindow): def __init__(self, options, parent=None, app=None): super(PesterWindow, self).__init__(parent, (QtCore.Qt.CustomizeWindowHint | QtCore.Qt.FramelessWindowHint)) # For debugging _CONSOLE_ENV.PAPP = self # TODO: karxi: SO! At the end of this function it seems like that # object is just made into None or.../something/. Somehow, it just # DIES, and I haven't the slightest idea why. I've tried multiple ways # to set it that shouldn't cause issues with globals; I honestly don't # know what to do. # Putting logging statements in here *gives me an object*, but I can't # carry it out of the function. I'll hvae to think of a way around # this.... # If I use a definition made in here without having another elsewhere, # it comes back as undefined. Just...what? self.autoJoinDone = False self.app = app self.convos = CaseInsensitiveDict() self.memos = CaseInsensitiveDict() self.tabconvo = None self.tabmemo = None if "advanced" in options: self.advanced = options["advanced"] else: self.advanced = False if "server" in options: self.serverOverride = options["server"] if "port" in options: self.portOverride = options["port"] if "honk" in options: self.honk = options["honk"] else: self.honk = True self.setAutoFillBackground(True) self.setObjectName("main") self.config = userConfig(self) 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.modes = "" self.randhandler = RandomHandler(self) try: themeChecker(self.theme) except ThemeException as inst: print "Caught: " + inst.parameter themeWarning = QtGui.QMessageBox(self) themeWarning.setText("Theme Error: %s" % inst) themeWarning.exec_() self.theme = pesterTheme("pesterchum") extraToasts = {'default': PesterToast} if pytwmn.confExists(): extraToasts['twmn'] = pytwmn.Notification self.tm = PesterToastMachine(self, lambda: self.theme["main/windowtitle"], on=self.config.notify(), type=self.config.notifyType(), extras=extraToasts) self.tm.run() self.chatlog = PesterLog(self.profile().handle, self) self.move(100, 100) talk = QtGui.QAction(self.theme["main/menus/client/talk"], self) self.talk = talk self.connect(talk, QtCore.SIGNAL('triggered()'), self, QtCore.SLOT('openChat()')) logv = QtGui.QAction(self.theme["main/menus/client/logviewer"], self) self.logv = logv self.connect(logv, QtCore.SIGNAL('triggered()'), self, QtCore.SLOT('openLogv()')) grps = QtGui.QAction(self.theme["main/menus/client/addgroup"], self) self.grps = grps self.connect(grps, QtCore.SIGNAL('triggered()'), self, QtCore.SLOT('addGroupWindow()')) self.rand = QtGui.QAction(self.theme["main/menus/client/randen"], self) self.connect(self.rand, QtCore.SIGNAL('triggered()'), self.randhandler, QtCore.SLOT('getEncounter()')) 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.app, QtCore.SLOT('quit()')) 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) self.menu.setNativeMenuBar(False) self.console = AttrDict() self.console.window = None self.console.action = QtGui.QAction("Console", self) self.connect(self.console.action, QtCore.SIGNAL('triggered()'), self, QtCore.SLOT('showConsole()')) self.console.is_open = False filemenu = self.menu.addMenu(self.theme["main/menus/client/_name"]) self.filemenu = filemenu filemenu.addAction(opts) filemenu.addAction(memoaction) filemenu.addAction(logv) filemenu.addAction(self.rand) if not self.randhandler.running: self.rand.setEnabled(False) filemenu.addAction(userlistaction) filemenu.addAction(talk) filemenu.addAction(self.idleaction) filemenu.addAction(grps) if _CONSOLE: filemenu.addAction(self.console.action) filemenu.addAction(self.importaction) filemenu.addAction(self.reconnectAction) filemenu.addAction(exitaction) 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(changequirks) profilemenu.addAction(loadslum) profilemenu.addAction(changecoloraction) profilemenu.addAction(switch) self.helpAction = QtGui.QAction(self.theme["main/menus/help/help"], self) self.connect(self.helpAction, QtCore.SIGNAL('triggered()'), self, QtCore.SLOT('launchHelp()')) self.botAction = QtGui.QAction(self.theme["main/menus/help/calsprite"], self) self.connect(self.botAction, QtCore.SIGNAL('triggered()'), self, QtCore.SLOT('loadCalsprite()')) self.nickServAction = QtGui.QAction(self.theme["main/menus/help/nickserv"], self) self.connect(self.nickServAction, QtCore.SIGNAL('triggered()'), self, QtCore.SLOT('loadNickServ()')) self.chanServAction = QtGui.QAction(self.theme["main/menus/help/chanserv"], self) self.connect(self.chanServAction, QtCore.SIGNAL('triggered()'), self, QtCore.SLOT('loadChanServ()')) self.aboutAction = QtGui.QAction(self.theme["main/menus/help/about"], self) self.connect(self.aboutAction, QtCore.SIGNAL('triggered()'), self, QtCore.SLOT('aboutPesterchum()')) self.reportBugAction = QtGui.QAction("REPORT BUG", self) self.connect(self.reportBugAction, QtCore.SIGNAL('triggered()'), self, QtCore.SLOT('reportBug()')) helpmenu = self.menu.addMenu(self.theme["main/menus/help/_name"]) self.helpmenu = helpmenu self.helpmenu.addAction(self.helpAction) self.helpmenu.addAction(self.botAction) self.helpmenu.addAction(self.chanServAction) self.helpmenu.addAction(self.nickServAction) self.helpmenu.addAction(self.aboutAction) self.helpmenu.addAction(self.reportBugAction) self.closeButton = WMButton(PesterIcon(self.theme["main/close/image"]), self) self.setButtonAction(self.closeButton, self.config.closeAction(), -1) self.miniButton = WMButton(PesterIcon(self.theme["main/minimize/image"]), self) self.setButtonAction(self.miniButton, self.config.minimizeAction(), -1) self.namesdb = CaseInsensitiveDict() 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(QTreeWidgetItem *, int)'), self, QtCore.SLOT('pesterSelectedChum()')) self.connect(self.chumList, QtCore.SIGNAL('removeChumSignal(QString)'), self, QtCore.SLOT('removeChum(QString)')) 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.idler = AttrDict(dict( # autoidle auto = False, # setidle manual = False, # idlethreshold threshold = 60*self.config.idleTime(), # idleaction action = self.idleaction, # idletimer timer = QtCore.QTimer(self), # idleposition pos = QtGui.QCursor.pos(), # idletime time = 0 )) self.connect(self.idler.timer, QtCore.SIGNAL('timeout()'), self, QtCore.SLOT('checkIdle()')) self.idler.timer.start(1000) if not self.config.defaultprofile(): self.changeProfile() # Fuck you some more OSX leopard! >:( if not ostools.isOSXLeopard(): QtCore.QTimer.singleShot(1000, self, QtCore.SLOT('mspacheck()')) self.connect(self, QtCore.SIGNAL('pcUpdate(QString, QString)'), self, QtCore.SLOT('updateMsg(QString, QString)')) self.pingtimer = QtCore.QTimer() self.connect(self.pingtimer, QtCore.SIGNAL('timeout()'), self, QtCore.SLOT('checkPing()')) self.lastping = int(time()) self.pingtimer.start(1000*90) @QtCore.pyqtSlot() def mspacheck(self): # Fuck you EVEN more OSX leopard! >:(((( if not ostools.isOSXLeopard(): checker = MSPAChecker(self) @QtCore.pyqtSlot(QtCore.QString, QtCore.QString) def updateMsg(self, ver, url): if not hasattr(self, 'updatemenu'): self.updatemenu = None if not self.updatemenu: self.updatemenu = UpdatePesterchum(ver, url, self) self.connect(self.updatemenu, QtCore.SIGNAL('accepted()'), self, QtCore.SLOT('updatePC()')) self.connect(self.updatemenu, QtCore.SIGNAL('rejected()'), self, QtCore.SLOT('noUpdatePC()')) self.updatemenu.show() self.updatemenu.raise_() self.updatemenu.activateWindow() @QtCore.pyqtSlot() def updatePC(self): version.updateDownload(unicode(self.updatemenu.url)) self.updatemenu = None @QtCore.pyqtSlot() def noUpdatePC(self): self.updatemenu = None @QtCore.pyqtSlot() def checkPing(self): curtime = int(time()) if curtime - self.lastping > 600: self.pingServer.emit() def profile(self): return self.userprofile.chat def closeConversations(self, switch=False): 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: if not switch: self.tabmemo.close() else: for m in self.tabmemo.convos: self.tabmemo.convos[m].sendtime() else: for m in self.memos.values(): if not switch: m.close() else: m.sendtime() 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 # notify if self.config.notifyOptions() & self.config.NEWMSG: if not self.convos.has_key(handle): t = self.tm.Toast("New Conversation", "From: %s" % handle) t.show() elif not self.config.notifyOptions() & self.config.NEWCONVO: if msg[:11] != "PESTERCHUM:": if handle.upper() not in BOTNAMES: t = self.tm.Toast("From: %s" % handle, re.sub("", "", msg)) t.show() else: if msg == "PESTERCHUM:CEASE": t = self.tm.Toast("Closed Conversation", handle) t.show() elif msg == "PESTERCHUM:BLOCK": t = self.tm.Toast("Blocked", handle) t.show() elif msg == "PESTERCHUM:UNBLOCK": t = self.tm.Toast("Unblocked", handle) t.show() 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 self.config.chatSound(): 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 # TODO: This is really bad practice. Fix it later. 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 not (msg.startswith("/me") or msg.startswith("PESTERCHUM:ME")): msg = addTimeInitial(msg, memo.times[handle].getGrammar()) if handle == "ChanServ": systemColor = QtGui.QColor(self.theme["memos/systemMsgColor"]) msg = "%s" % (systemColor.name(), msg) memo.addMessage(msg, handle) mentioned = False m = convertTags(msg, "text") if m.find(":") <= 3: m = m[m.find(":"):] for search in self.userprofile.getMentions(): if re.search(search, m): mentioned = True break if mentioned: if self.config.notifyOptions() & self.config.INITIALS: t = self.tm.Toast(chan, re.sub("", "", msg)) t.show() if self.config.soundOn(): if self.config.memoSound(): if self.config.nameSound(): if mentioned: self.namesound.play() return if not memo.notifications_muted: if self.honk and re.search(r"\bhonk\b", convertTags(msg, "text"), re.I): # TODO: I've got my eye on you, Gamzee. self.honksound.play() elif self.config.memoPing() or memo.always_beep: self.memosound.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): # updates OTHER chums' moods 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 type(chum) in [str, unicode]: matchingChums = [c for c in self.chumList.chums if c.handle == chum] if len(matchingChums) > 0: mood = matchingChums[0].mood else: mood = Mood(2) chum = PesterProfile(chum, mood=mood, chumdb=self.chumdb) if len(matchingChums) == 0: self.moodRequest.emit(chum) 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 if unicode(chum.handle).upper() in BOTNAMES: convoWindow.toggleQuirks(True) convoWindow.quirksOff.setChecked(True) if unicode(chum.handle).upper() in CUSTOMBOTS: self.newConvoStarted.emit(QtCore.QString(chum.handle), initiated) else: 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()')) @QtCore.pyqtSlot() def showConsole(self): if not _CONSOLE: # We don't have the module file for this at the moment. return win = self.console.window if win is None: # We have no console window; make one. logging.warning("Making a console....") self.console.window = win = console.ConsoleWindow(parent=self) logging.warning("Console made.") self.connect(win, QtCore.SIGNAL('windowClosed()'), self, QtCore.SLOT('consoleWindowClosed()')) if self.console.is_open: # Already open - hide the console. win.hide() self.console.is_open = False else: # Console isn't visible - show it. win.show() self.console.is_open = True @QtCore.pyqtSlot() def consoleWindowClosed(self): self.console.is_open = False self.console.window = None logging.warning("Console closed.") def newMemo(self, channel, timestr, secret=False, invite=False): if channel == "#pesterchum": return if self.memos.has_key(channel): self.memos[channel].showChat() return # do slider dialog then set if self.config.tabMemos(): 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(self, QtCore.SIGNAL('inviteOnlyChan(QString)'), memoWindow, QtCore.SLOT('closeInviteOnly(QString)')) 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(QString)'), memoWindow, QtCore.SLOT('namesUpdated(QString)')) self.connect(self, QtCore.SIGNAL('modesUpdated(QString, QString)'), memoWindow, QtCore.SLOT('modesUpdated(QString, QString)')) 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", "") if invite: self.setChannelMode.emit(channel, "+i", "") memoWindow.sendTimeInfo() memoWindow.show() def addChum(self, chum): self.chumList.addChum(chum) self.config.addChum(chum) self.moodRequest.emit(chum) def addGroup(self, gname): self.config.addGroup(gname) gTemp = self.config.getGroups() self.chumList.groups = [g[0] for g in gTemp] self.chumList.openGroups = [g[1] for g in gTemp] self.chumList.moveGroupMenu() self.chumList.showAllGroups() if not self.config.showEmptyGroups(): self.chumList.hideEmptyGroups() if self.config.showOnlineNumbers(): self.chumList.showOnlineNumbers() 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 } QMenu::item::disabled { %s }" % (theme["main/menu/style"], theme["main/menu/selected"], theme["main/menu/disabled"])) newcloseicon = PesterIcon(theme["main/close/image"]) self.closeButton.setIcon(newcloseicon) self.closeButton.setIconSize(newcloseicon.realsize()) self.closeButton.resize(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.resize(newminiicon.realsize()) self.miniButton.move(*theme["main/minimize/loc"]) # menus self.menu.move(*theme["main/menu/loc"]) self.talk.setText(theme["main/menus/client/talk"]) self.logv.setText(theme["main/menus/client/logviewer"]) self.grps.setText(theme["main/menus/client/addgroup"]) self.rand.setText(self.theme["main/menus/client/randen"]) 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.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.helpAction.setText(self.theme["main/menus/help/help"]) self.botAction.setText(self.theme["main/menus/help/calsprite"]) self.chanServAction.setText(self.theme["main/menus/help/chanserv"]) self.nickServAction.setText(self.theme["main/menus/help/nickserv"]) 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() mood_list = theme["main/moods"] mood_list = [dict([(str(k),v) for (k,v) in d.iteritems()]) for d in mood_list] self.moods = PesterMoodHandler(self, *[PesterMoodButton(self, **d) for d in mood_list]) 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"]) else: self.mychumcolor.setText("") # sounds self._setup_sounds() self.setVolume(self.config.volume()) def _setup_sounds(self, soundclass=None): """Set up the event sounds for later use.""" # Set up the sounds we're using. if soundclass is None: if (pygame and pygame.mixer): # We have pygame, so we may as well use it. soundclass = pygame.mixer.Sound elif QtGui.QSound.isAvailable(): # We don't have pygame; try to use QSound. soundclass = QtGui.QSound else: # We don't have any options...just use fillers. soundclass = NoneSound # Use the class we chose to build the sound set. try: # karxi: These all seem to be created using local paths. # Note that if QSound.isAvailable is False, we could still try # to play QSound objects - it'll just fail silently. self.alarm = soundclass(self.theme["main/sounds/alertsound"]) self.memosound = soundclass(self.theme["main/sounds/memosound"]) self.namesound = soundclass("themes/namealarm.wav") self.ceasesound = soundclass(self.theme["main/sounds/ceasesound"]) self.honksound = soundclass("themes/honk.wav") except Exception as err: print "Warning: Error loading sounds!" self.alarm = NoneSound() self.memosound = NoneSound() self.namesound = NoneSound() self.ceasesound = NoneSound() self.honksound = NoneSound() def setVolume(self, vol): # TODO: Find a way to make this usable. vol = vol/100.0 # TODO: Make these into an actual (stored) /dict/ later, for easier # iteration. sounds = [self.alarm, self.memosound, self.namesound, self.ceasesound, self.honksound] for sound in sounds: try: if pygame and pygame.mixer and \ isinstance(sound, pygame.mixer.sound): sound.set_volume(vol) elif not isinstance(sound, QtGui.QSound): # We can't set a volume on those.... sound.setVolume(vol) except Exception as err: logging.info("Couldn't set volume: {}".format(err)) def changeTheme(self, theme): # check theme try: themeChecker(theme) except ThemeException as inst: themeWarning = QtGui.QMessageBox(self) themeWarning.setText("Theme Error: %s" % inst) themeWarning.exec_() theme = pesterTheme("pesterchum") return 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) if self.config.ghostchum(): self.theme["main"]["icon"] = "themes/pesterchum/pesterdunk.png" self.theme["main"]["newmsgicon"] = "themes/pesterchum/ghostchum.png" self.setWindowIcon(PesterIcon(self.theme["main/icon"])) # 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.closeToTray() else: self.raise_() self.activateWindow() else: self.waitingMessages.answerMessage() def doAutoIdentify(self): if self.userprofile.getAutoIdentify(): self.sendMessage.emit("identify " + self.userprofile.getNickServPass(), "NickServ") def doAutoJoins(self): if not self.autoJoinDone: self.autoJoinDone = True for memo in self.userprofile.getAutoJoins(): self.newMemo(memo, "i") @QtCore.pyqtSlot() def connected(self): if self.loadingscreen: self.loadingscreen.done(QtGui.QDialog.Accepted) self.loadingscreen = None self.doAutoIdentify() self.doAutoJoins() @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: text = unicode(curChum.text(0)) if text.rfind(" (") != -1: text = text[0:text.rfind(" (")] if text not in self.chumList.groups and \ text != "Chums": 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) try: chum = self.convos[h].chum except KeyError: chum = self.convos[h.lower()].chum try: chumopen = self.convos[h].chumopen except KeyError: chumopen = self.convos[h.lower()].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) try: del self.memos[c] except KeyError: del self.memos[c.lower()] @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) def deliverNotice(self, handle, msg): h = unicode(handle) m = unicode(msg) if m.startswith("Your nickname is now being changed to"): changedto = m[39:-1] msgbox = QtGui.QMessageBox() msgbox.setText("This chumhandle has been registered; you may not use it.") msgbox.setInformativeText("Your handle is now being changed to %s." % (changedto)) msgbox.setStandardButtons(QtGui.QMessageBox.Ok) ret = msgbox.exec_() elif h == self.randhandler.randNick: self.randhandler.incoming(msg) elif self.convos.has_key(h): self.newMessage(h, m) elif h.upper() == "NICKSERV" and "PESTERCHUM:" not in m: m = nickservmsgs.translate(m) if m: t = self.tm.Toast("NickServ:", m) t.show() @QtCore.pyqtSlot(QtCore.QString, QtCore.QString) def deliverInvite(self, handle, channel): msgbox = QtGui.QMessageBox() msgbox.setText("You're invited!") msgbox.setInformativeText("%s has invited you to the memo: %s\nWould you like to join them?" % (handle, channel)) msgbox.setStandardButtons(QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel) ret = msgbox.exec_() if ret == QtGui.QMessageBox.Ok: self.newMemo(unicode(channel), "+0:00") @QtCore.pyqtSlot(QtCore.QString) def chanInviteOnly(self, channel): self.inviteOnlyChan.emit(channel) @QtCore.pyqtSlot(QtCore.QString, QtCore.QString) def cannotSendToChan(self, channel, msg): self.deliverMemo(channel, "ChanServ", msg) @QtCore.pyqtSlot(QtCore.QString, QtCore.QString) def modesUpdated(self, channel, modes): self.modesUpdated.emit(channel, modes) @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, QtCore.QString, QtCore.QString) def quirkDisable(self, channel, msg, op): (c, msg, op) = (unicode(channel), unicode(msg), unicode(op)) if not self.memos.has_key(c): return memo = self.memos[c] memo.quirkDisable(op, msg) @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(c) @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 in ("quit", "netsplit"): 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: available_groups = [g[0] for g in self.config.getGroups()] self.addchumdialog = AddChumDialog(available_groups, self) ok = self.addchumdialog.exec_() handle = unicode(self.addchumdialog.chumBox.text()).strip() newgroup = unicode(self.addchumdialog.newgroup.text()).strip() selectedGroup = self.addchumdialog.groupBox.currentText() group = newgroup if newgroup else selectedGroup if ok: handle = unicode(handle) if handle in [h.handle for h in self.chumList.chums]: self.addchumdialog = None return if not (PesterProfile.checkLength(handle) and PesterProfile.checkValid(handle)[0]): errormsg = QtGui.QErrorMessage(self) errormsg.showMessage("THIS IS NOT A VALID CHUMTAG!") self.addchumdialog = None return if re.search("[^A-Za-z0-9_\s]", group) is not None: errormsg = QtGui.QErrorMessage(self) errormsg.showMessage("THIS IS NOT A VALID GROUP NAME") self.addchumdialog = None return if newgroup: # make new group self.addGroup(group) chum = PesterProfile(handle, chumdb=self.chumdb, group=group) self.chumdb.setGroup(handle, group) self.addChum(chum) self.addchumdialog = None @QtCore.pyqtSlot(QtCore.QString) def removeChum(self, chumlisting): self.config.removeChum(chumlisting) def reportChum(self, handle): (reason, ok) = QtGui.QInputDialog.getText(self, "Report User", "Enter the reason you are reporting this user (optional):") if ok: self.sendMessage.emit("REPORT %s %s" % (handle, reason) , "calSprite") @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: # We checked the box to go idle. self.idler.manual = True self.setAway.emit(True) self.randhandler.setIdle(True) self._sendIdleMsgs() else: self.idler.manual = False self.setAway.emit(False) self.randhandler.setIdle(False) self.idler.time = 0 # karxi: TODO: Need to consider sticking an idle-setter here. @QtCore.pyqtSlot() def checkIdle(self): newpos = QtGui.QCursor.pos() oldpos = self.idler.pos # Save the new position. self.idler.pos = newpos if self.idler.manual: # We're already idle, because the user said to be. self.idler.time = 0 return elif self.idler.auto: self.idler.time = 0 if newpos != oldpos: # Cursor moved; unset idle. self.idler.auto = False self.setAway.emit(False) self.randhandler.setIdle(False) return if newpos != oldpos: # Our cursor has moved, which means we can't be idle. self.idler.time = 0 return # If we got here, WE ARE NOT IDLE, but may become so self.idler.time += 1 if self.idler.time >= self.idler.threshold: # We've been idle for long enough to fall automatically idle. self.idler.auto = True # We don't need this anymore. self.idler.time = 0 # Make it clear that we're idle. self.setAway.emit(True) self.randhandler.setIdle(True) self._sendIdleMsgs() def _sendIdleMsgs(self): # Tell everyone we're in a chat with that we just went idle. sysColor = QtGui.QColor(self.theme["convo/systemMsgColor"]) verb = self.theme["convo/text/idle"] for (h, convo) in self.convos.iteritems(): # karxi: There's an irritating issue here involving a lack of # consideration for case-sensitivity. # This fix is a little sloppy, and I need to look into what it # might affect, but I've been using it for months and haven't # noticed any issues.... handle = convo.chum.handle if self.isBot(handle): # Don't send these idle messages. continue # karxi: Now we just use 'handle' instead of 'h'. if convo.chumopen: msg = self.profile().idlemsg(sysColor, verb) convo.textArea.append(convertTags(msg)) self.chatlog.log(handle, msg) self.sendMessage.emit("PESTERCHUM:IDLE", handle) # Presented here so it can be called by other scripts. @staticmethod def isBot(handle): return handle.upper() in BOTNAMES @QtCore.pyqtSlot() def importExternalConfig(self): f = QtGui.QFileDialog.getOpenFileName(self) if f == "": return fp = open(f, 'r') regexp_state = None for l in fp.xreadlines(): # import chumlist l = l.rstrip() 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) continue if regexp_state is not None: replace_mo = re.match("replace: (.+)", l) if replace_mo is not None: replace = replace_mo.group(1) try: re.compile(regexp_state) except re.error, e: continue newquirk = pesterQuirk({"type": "regexp", "from": regexp_state, "to": replace}) qs = self.userprofile.quirks qs.addQuirk(newquirk) self.userprofile.setQuirks(qs) regexp_state = None continue search_mo = re.match("search: (.+)", l) if search_mo is not None: regexp_state = search_mo.group(1) continue other_mo = re.match("(prefix|suffix): (.+)", l) if other_mo is not None: newquirk = pesterQuirk({"type": other_mo.group(1), "value": other_mo.group(2)}) qs = self.userprofile.quirks qs.addQuirk(newquirk) self.userprofile.setQuirks(qs) @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): time = unicode(self.memochooser.timeinput.text()) secret = self.memochooser.secretChannel.isChecked() invite = self.memochooser.inviteChannel.isChecked() if self.memochooser.newmemoname(): newmemo = self.memochooser.newmemoname() channel = "#"+unicode(newmemo).replace(" ", "_") channel = re.sub(r"[^A-Za-z0-9#_]", "", channel) self.newMemo(channel, time, secret=secret, invite=invite) for SelectedMemo in self.memochooser.SelectedMemos(): channel = "#"+unicode(SelectedMemo.target) self.newMemo(channel, time) 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.connect(self.allusers, QtCore.SIGNAL('pesterChum(QString)'), self, QtCore.SLOT('userListPester(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(QtCore.QString) def userListPester(self, handle): h = unicode(handle) self.newConversation(h) @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): for i in range(self.quirkmenu.quirkList.topLevelItemCount()): curgroup = unicode(self.quirkmenu.quirkList.topLevelItem(i).text(0)) for j in range(self.quirkmenu.quirkList.topLevelItem(i).childCount()): item = self.quirkmenu.quirkList.topLevelItem(i).child(j) item.quirk.quirk["on"] = item.quirk.on = (item.checkState(0) == QtCore.Qt.Checked) item.quirk.quirk["group"] = item.quirk.group = curgroup quirks = pesterQuirks(self.quirkmenu.quirks()) self.userprofile.setQuirks(quirks) if hasattr(self.quirkmenu, 'quirktester') and self.quirkmenu.quirktester: self.quirkmenu.quirktester.close() self.quirkmenu = None @QtCore.pyqtSlot() def closeQuirks(self): if hasattr(self.quirkmenu, 'quirktester') and self.quirkmenu.quirktester: self.quirkmenu.quirktester.close() self.quirkmenu = None @QtCore.pyqtSlot() def openChat(self): if not hasattr(self, "openchatdialog"): self.openchatdialog = None if not self.openchatdialog: (chum, ok) = QtGui.QInputDialog.getText(self, "Pester Chum", "Enter a handle to pester:") try: if ok: self.newConversation(unicode(chum)) except: pass finally: self.openchatdialog = None @QtCore.pyqtSlot() def openLogv(self): if not hasattr(self, 'logusermenu'): self.logusermenu = None if not self.logusermenu: self.logusermenu = PesterLogUserSelect(self.config, self.theme, self) self.connect(self.logusermenu, QtCore.SIGNAL('accepted()'), self, QtCore.SLOT('closeLogUsers()')) self.connect(self.logusermenu, QtCore.SIGNAL('rejected()'), self, QtCore.SLOT('closeLogUsers()')) self.logusermenu.show() self.logusermenu.raise_() self.logusermenu.activateWindow() @QtCore.pyqtSlot() def closeLogUsers(self): self.logusermenu.close() self.logusermenu = None @QtCore.pyqtSlot() def addGroupWindow(self): if not hasattr(self, 'addgroupdialog'): self.addgroupdialog = None if not self.addgroupdialog: (gname, ok) = QtGui.QInputDialog.getText(self, "Add Group", "Enter a name for the new group:") if ok: gname = unicode(gname) if re.search("[^A-Za-z0-9_\s]", gname) is not None: msgbox = QtGui.QMessageBox() msgbox.setInformativeText("THIS IS NOT A VALID GROUP NAME") msgbox.setStandardButtons(QtGui.QMessageBox.Ok) ret = msgbox.exec_() self.addgroupdialog = None return self.addGroup(gname) self.addgroupdialog = 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): try: # 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()) for w in windows: w.setParent(None) w.show() w.raiseChat() if self.tabconvo: self.tabconvo.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 # save options self.config.set("tabs", tabsetting) # tabs memos curtabmemo = self.config.tabMemos() tabmemosetting = self.optionmenu.tabmemocheck.isChecked() if curtabmemo and not tabmemosetting: # split tabs into windows windows = [] if self.tabmemo: windows = list(self.tabmemo.convos.values()) for w in windows: w.setParent(None) w.show() w.raiseChat() if self.tabmemo: self.tabmemo.closeSoft() # save options self.config.set("tabmemos", tabmemosetting) elif tabmemosetting and not curtabmemo: # combine 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("tabmemos", tabmemosetting) # hidden chums chumsetting = self.optionmenu.hideOffline.isChecked() curchum = self.config.hideOfflineChums() if curchum and not chumsetting: self.chumList.showAllChums() elif chumsetting and not curchum: self.chumList.hideOfflineChums() self.config.set("hideOfflineChums", chumsetting) # sorting method sortsetting = self.optionmenu.sortBox.currentIndex() cursort = self.config.sortMethod() self.config.set("sortMethod", sortsetting) if sortsetting != cursort: self.chumList.sort() # sound soundsetting = self.optionmenu.soundcheck.isChecked() self.config.set("soundon", soundsetting) chatsoundsetting = self.optionmenu.chatsoundcheck.isChecked() curchatsound = self.config.chatSound() if chatsoundsetting != curchatsound: self.config.set('chatSound', chatsoundsetting) memosoundsetting = self.optionmenu.memosoundcheck.isChecked() curmemosound = self.config.memoSound() if memosoundsetting != curmemosound: self.config.set('memoSound', memosoundsetting) memopingsetting = self.optionmenu.memopingcheck.isChecked() curmemoping = self.config.memoPing() if memopingsetting != curmemoping: self.config.set('pingSound', memopingsetting) namesoundsetting = self.optionmenu.namesoundcheck.isChecked() curnamesound = self.config.nameSound() if namesoundsetting != curnamesound: self.config.set('nameSound', namesoundsetting) volumesetting = self.optionmenu.volume.value() curvolume = self.config.volume() if volumesetting != curvolume: self.config.set('volume', volumesetting) self.setVolume(volumesetting) # timestamps timestampsetting = self.optionmenu.timestampcheck.isChecked() self.config.set("showTimeStamps", timestampsetting) timeformatsetting = unicode(self.optionmenu.timestampBox.currentText()) if timeformatsetting == "12 hour": self.config.set("time12Format", True) else: self.config.set("time12Format", False) secondssetting = self.optionmenu.secondscheck.isChecked() self.config.set("showSeconds", secondssetting) # groups #groupssetting = self.optionmenu.groupscheck.isChecked() #self.config.set("useGroups", groupssetting) emptygroupssetting = self.optionmenu.showemptycheck.isChecked() curemptygroup = self.config.showEmptyGroups() if curemptygroup and not emptygroupssetting: self.chumList.hideEmptyGroups() elif emptygroupssetting and not curemptygroup: self.chumList.showAllGroups() self.config.set("emptyGroups", emptygroupssetting) # online numbers onlinenumsetting = self.optionmenu.showonlinenumbers.isChecked() curonlinenum = self.config.showOnlineNumbers() if onlinenumsetting and not curonlinenum: self.chumList.showOnlineNumbers() elif curonlinenum and not onlinenumsetting: self.chumList.hideOnlineNumbers() self.config.set("onlineNumbers", onlinenumsetting) # logging logpesterssetting = 0 if self.optionmenu.logpesterscheck.isChecked(): logpesterssetting = logpesterssetting | self.config.LOG if self.optionmenu.stamppestercheck.isChecked(): logpesterssetting = logpesterssetting | self.config.STAMP curlogpesters = self.config.logPesters() if logpesterssetting != curlogpesters: self.config.set('logPesters', logpesterssetting) logmemossetting = 0 if self.optionmenu.logmemoscheck.isChecked(): logmemossetting = logmemossetting | self.config.LOG if self.optionmenu.stampmemocheck.isChecked(): logmemossetting = logmemossetting | self.config.STAMP curlogmemos = self.config.logMemos() if logmemossetting != curlogmemos: self.config.set('logMemos', logmemossetting) # memo and user links linkssetting = self.optionmenu.userlinkscheck.isChecked() curlinks = self.config.disableUserLinks() if linkssetting != curlinks: self.config.set('userLinks', not linkssetting) # idle time idlesetting = self.optionmenu.idleBox.value() curidle = self.config.idleTime() if idlesetting != curidle: self.config.set('idleTime', idlesetting) self.idler.threshold = 60*idlesetting # theme ghostchumsetting = self.optionmenu.ghostchum.isChecked() curghostchum = self.config.ghostchum() self.config.set('ghostchum', ghostchumsetting) self.themeSelected(ghostchumsetting != curghostchum) # randoms if self.randhandler.running: self.randhandler.setRandomer(self.optionmenu.randomscheck.isChecked()) # button actions minisetting = self.optionmenu.miniBox.currentIndex() curmini = self.config.minimizeAction() if minisetting != curmini: self.config.set('miniAction', minisetting) self.setButtonAction(self.miniButton, minisetting, curmini) closesetting = self.optionmenu.closeBox.currentIndex() curclose = self.config.closeAction() if closesetting != curclose: self.config.set('closeAction', closesetting) self.setButtonAction(self.closeButton, closesetting, curclose) # op and voice messages opvmesssetting = self.optionmenu.memomessagecheck.isChecked() curopvmess = self.config.opvoiceMessages() if opvmesssetting != curopvmess: self.config.set('opvMessages', opvmesssetting) # animated smiles if ostools.isOSXBundle(): animatesetting = False; else: animatesetting = self.optionmenu.animationscheck.isChecked() curanimate = self.config.animations() if animatesetting != curanimate: self.config.set('animations', animatesetting) self.animationSetting.emit(animatesetting) # update checked updatechecksetting = self.optionmenu.updateBox.currentIndex() curupdatecheck = self.config.checkForUpdates() if updatechecksetting != curupdatecheck: self.config.set('checkUpdates', updatechecksetting) # mspa update check if ostools.isOSXLeopard(): mspachecksetting = false else: mspachecksetting = self.optionmenu.mspaCheck.isChecked() curmspacheck = self.config.checkMSPA() if mspachecksetting != curmspacheck: self.config.set('mspa', mspachecksetting) # Taskbar blink blinksetting = 0 if self.optionmenu.pesterBlink.isChecked(): blinksetting |= self.config.PBLINK if self.optionmenu.memoBlink.isChecked(): blinksetting |= self.config.MBLINK curblink = self.config.blink() if blinksetting != curblink: self.config.set('blink', blinksetting) # toast notifications self.tm.setEnabled(self.optionmenu.notifycheck.isChecked()) self.tm.setCurrentType(unicode(self.optionmenu.notifyOptions.currentText())) notifysetting = 0 if self.optionmenu.notifySigninCheck.isChecked(): notifysetting |= self.config.SIGNIN if self.optionmenu.notifySignoutCheck.isChecked(): notifysetting |= self.config.SIGNOUT if self.optionmenu.notifyNewMsgCheck.isChecked(): notifysetting |= self.config.NEWMSG if self.optionmenu.notifyNewConvoCheck.isChecked(): notifysetting |= self.config.NEWCONVO if self.optionmenu.notifyMentionsCheck.isChecked(): notifysetting |= self.config.INITIALS curnotify = self.config.notifyOptions() if notifysetting != curnotify: self.config.set('notifyOptions', notifysetting) # low bandwidth bandwidthsetting = self.optionmenu.bandwidthcheck.isChecked() curbandwidth = self.config.lowBandwidth() if bandwidthsetting != curbandwidth: self.config.set('lowBandwidth', bandwidthsetting) if bandwidthsetting: self.leftChannel.emit("#pesterchum") else: self.joinChannel.emit("#pesterchum") # nickserv autoidentify = self.optionmenu.autonickserv.isChecked() nickservpass = self.optionmenu.nickservpass.text() self.userprofile.setAutoIdentify(autoidentify) self.userprofile.setNickServPass(str(nickservpass)) # auto join memos autojoins = [] for i in range(self.optionmenu.autojoinlist.count()): autojoins.append(str(self.optionmenu.autojoinlist.item(i).text())) self.userprofile.setAutoJoins(autojoins) # advanced ## user mode if self.advanced: newmodes = self.optionmenu.modechange.text() if newmodes: self.setChannelMode.emit(self.profile().handle, newmodes, "") except Exception as e: logging.error(e) finally: self.optionmenu = None def setButtonAction(self, button, setting, old): if old == 0: # minimize to taskbar self.disconnect(button, QtCore.SIGNAL('clicked()'), self, QtCore.SLOT('showMinimized()')); elif old == 1: # minimize to tray self.disconnect(button, QtCore.SIGNAL('clicked()'), self, QtCore.SLOT('closeToTray()')); elif old == 2: # quit self.disconnect(button, QtCore.SIGNAL('clicked()'), self.app, QtCore.SLOT('quit()')); if setting == 0: # minimize to taskbar self.connect(button, QtCore.SIGNAL('clicked()'), self, QtCore.SLOT('showMinimized()')); elif setting == 1: # minimize to tray self.connect(button, QtCore.SIGNAL('clicked()'), self, QtCore.SLOT('closeToTray()')); elif setting == 2: # quit self.connect(button, QtCore.SIGNAL('clicked()'), self.app, QtCore.SLOT('quit()')); @QtCore.pyqtSlot() def themeSelectOverride(self): self.themeSelected(self.theme.name) @QtCore.pyqtSlot() def themeSelected(self, override=False): if not override: themename = unicode(self.optionmenu.themeBox.currentText()) else: themename = override if override or themename != self.theme.name: try: self.changeTheme(pesterTheme(themename)) except ValueError as 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, self) # is default? if self.chooseprofile.defaultcheck.isChecked(): self.config.set("defaultprofile", self.userprofile.chat.handle) 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() def loadCalsprite(self): self.newConversation("calSprite") @QtCore.pyqtSlot() def loadChanServ(self): self.newConversation("chanServ") @QtCore.pyqtSlot() def loadNickServ(self): self.newConversation("nickServ") @QtCore.pyqtSlot() def launchHelp(self): QtGui.QDesktopServices.openUrl(QtCore.QUrl("http://nova.xzibition.com/~illuminatedwax/help.html", QtCore.QUrl.TolerantMode)) @QtCore.pyqtSlot() def reportBug(self): if hasattr(self, 'bugreportwindow') and self.bugreportwindow: return self.bugreportwindow = BugReporter(self) self.bugreportwindow.exec_() self.bugreportwindow = None @QtCore.pyqtSlot(QtCore.QString, QtCore.QString) def nickCollision(self, handle, tmphandle): self.mychumhandle.setText(tmphandle) self.userprofile = userProfile(PesterProfile("pesterClient%d" % (random.randint(100,999)), QtGui.QColor("black"), Mood(0))) self.changeTheme(self.userprofile.getTheme()) if not hasattr(self, 'chooseprofile'): self.chooseprofile = None if not self.chooseprofile: h = unicode(handle) self.changeProfile(collision=h) @QtCore.pyqtSlot(QtCore.QString) def myHandleChanged(self, handle): if self.profile().handle == handle: self.doAutoIdentify() self.doAutoJoins() return else: self.nickCollision(self.profile().handle, handle) @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() @QtCore.pyqtSlot() def tooManyPeeps(self): msg = QtGui.QMessageBox(self) msg.setText("D: TOO MANY PEOPLE!!!") msg.setInformativeText("The server has hit max capacity. Please try again later.") msg.show() pcUpdate = QtCore.pyqtSignal(QtCore.QString, QtCore.QString) closeToTraySignal = QtCore.pyqtSignal() newConvoStarted = QtCore.pyqtSignal(QtCore.QString, bool, name="newConvoStarted") sendMessage = QtCore.pyqtSignal(QtCore.QString, QtCore.QString) sendNotice = QtCore.pyqtSignal(QtCore.QString, QtCore.QString) convoClosed = QtCore.pyqtSignal(QtCore.QString) profileChanged = QtCore.pyqtSignal() animationSetting = QtCore.pyqtSignal(bool) moodRequest = QtCore.pyqtSignal(PesterProfile) moodsRequest = QtCore.pyqtSignal(PesterList) moodUpdated = QtCore.pyqtSignal() requestChannelList = QtCore.pyqtSignal() requestNames = QtCore.pyqtSignal(QtCore.QString) namesUpdated = QtCore.pyqtSignal(QtCore.QString) modesUpdated = QtCore.pyqtSignal(QtCore.QString, QtCore.QString) 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) channelNames = QtCore.pyqtSignal(QtCore.QString) inviteChum = QtCore.pyqtSignal(QtCore.QString, QtCore.QString) inviteOnlyChan = QtCore.pyqtSignal(QtCore.QString) closeSignal = QtCore.pyqtSignal() reconnectIRC = QtCore.pyqtSignal() gainAttention = QtCore.pyqtSignal(QtGui.QWidget) pingServer = QtCore.pyqtSignal() setAway = QtCore.pyqtSignal(bool) killSomeQuirks = QtCore.pyqtSignal(QtCore.QString, QtCore.QString) class PesterTray(QtGui.QSystemTrayIcon): def __init__(self, icon, mainwindow, parent): super(PesterTray, self).__init__(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): super(MainProgram, self).__init__() self.app = QtGui.QApplication(sys.argv) self.app.setApplicationName("Pesterchum 3.14") self.app.setQuitOnLastWindowClosed(False) options = self.oppts(sys.argv[1:]) def doSoundInit(): # TODO: Make this more uniform, adapt it into a general function. if pygame and pygame.mixer: # we could set the frequency higher but i love how cheesy it sounds try: pygame.mixer.init() pygame.mixer.init() except pygame.error as err: print "Warning: No sound! (pygame error: %s)" % err else: # Sound works, we're done. return # ... Other alternatives here. ... # Last resort. (Always 'works' on Windows, no volume control.) if QtGui.QSound.isAvailable(): # Sound works, we're done. return else: print "Warning: No sound! (No pygame/QSound)" doSoundInit() self.widget = PesterWindow(options, app=self.app) 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") moodCategories = {} for k in Mood.moodcats: moodCategories[k] = moodMenu.addMenu(k.upper()) 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 moodCategories[Mood.revmoodcats[m]].addAction(maction) miniAction = QtGui.QAction("MINIMIZE", self) self.trayicon.connect(miniAction, QtCore.SIGNAL('triggered()'), self.widget, QtCore.SLOT('showMinimized()')) exitAction = QtGui.QAction("EXIT", self) self.trayicon.connect(exitAction, QtCore.SIGNAL('triggered()'), self.app, QtCore.SLOT('quit()')) self.traymenu.addAction(miniAction) 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, QtCore.SLOT('trayiconShow()')) self.trayicon.connect(self.widget, QtCore.SIGNAL('closeSignal()'), self.trayicon, QtCore.SLOT('mainWindowClosed()')) self.connect(self.trayicon, QtCore.SIGNAL('messageClicked()'), self, QtCore.SLOT('trayMessageClick()')) self.attempts = 0 self.irc = PesterIRC(self.widget.config, self.widget) self.connectWidgets(self.irc, self.widget) self.connect(self.widget, QtCore.SIGNAL('gainAttention(QWidget*)'), self, QtCore.SLOT('alertWindow(QWidget*)')) # 0 Once a day # 1 Once a week # 2 Only on start # 3 Never check = self.widget.config.checkForUpdates() if check == 2: self.runUpdateSlot() elif check == 0: seconds = 60 * 60 * 24 if int(time()) - self.widget.config.lastUCheck() < seconds: seconds -= int(time()) - self.widget.config.lastUCheck() if seconds < 0: seconds = 0 QtCore.QTimer.singleShot(1000*seconds, self, QtCore.SLOT('runUpdateSlot()')) elif check == 1: seconds = 60 * 60 * 24 * 7 if int(time()) - self.widget.config.lastUCheck() < seconds: seconds -= int(time()) - self.widget.config.lastUCheck() if seconds < 0: seconds = 0 QtCore.QTimer.singleShot(1000*seconds, self, QtCore.SLOT('runUpdateSlot()')) @QtCore.pyqtSlot() def runUpdateSlot(self): q = Queue.Queue(1) s = threading.Thread(target=version.updateCheck, args=(q,)) w = threading.Thread(target=self.showUpdate, args=(q,)) w.start() s.start() self.widget.config.set('lastUCheck', int(time())) check = self.widget.config.checkForUpdates() if check == 0: seconds = 60 * 60 * 24 elif check == 1: seconds = 60 * 60 * 24 * 7 else: return QtCore.QTimer.singleShot(1000*seconds, self, QtCore.SLOT('runUpdateSlot()')) @QtCore.pyqtSlot(QtGui.QWidget) def alertWindow(self, widget): self.app.alert(widget) @QtCore.pyqtSlot() def trayiconShow(self): self.trayicon.show() if self.widget.config.trayMessage(): self.trayicon.showMessage("Pesterchum", "Pesterchum is still running in the system tray.\n\ Right click to close it.\n\ Click this message to never see this again.") @QtCore.pyqtSlot() def trayMessageClick(self): self.widget.config.set('traymsg', False) widget2irc = [('sendMessage(QString, QString)', 'sendMessage(QString, QString)'), ('sendNotice(QString, QString)', 'sendNotice(QString, QString)'), ('newConvoStarted(QString, bool)', 'startConvo(QString, bool)'), ('convoClosed(QString)', 'endConvo(QString)'), ('profileChanged()', 'updateProfile()'), ('moodRequest(PyQt_PyObject)', 'getMood(PyQt_PyObject)'), ('moodsRequest(PyQt_PyObject)', 'getMoods(PyQt_PyObject)'), ('moodUpdated()', 'updateMood()'), ('mycolorUpdated()','updateColor()'), ('blockedChum(QString)', 'blockedChum(QString)'), ('unblockedChum(QString)', 'unblockedChum(QString)'), ('requestNames(QString)','requestNames(QString)'), ('requestChannelList()', 'requestChannelList()'), ('joinChannel(QString)', 'joinChannel(QString)'), ('leftChannel(QString)', 'leftChannel(QString)'), ('kickUser(QString, QString)', 'kickUser(QString, QString)'), ('setChannelMode(QString, QString, QString)', 'setChannelMode(QString, QString, QString)'), ('channelNames(QString)', 'channelNames(QString)'), ('inviteChum(QString, QString)', 'inviteChum(QString, QString)'), ('pingServer()', 'pingServer()'), ('setAway(bool)', 'setAway(bool)'), ('killSomeQuirks(QString, QString)', 'killSomeQuirks(QString, QString)'), ('reconnectIRC()', 'reconnectIRC()') ] # IRC --> Main window irc2widget = [('connected()', 'connected()'), ('moodUpdated(QString, PyQt_PyObject)', 'updateMoodSlot(QString, PyQt_PyObject)'), ('colorUpdated(QString, QColor)', 'updateColorSlot(QString, QColor)'), ('messageReceived(QString, QString)', 'deliverMessage(QString, QString)'), ('memoReceived(QString, QString, QString)', 'deliverMemo(QString, QString, QString)'), ('noticeReceived(QString, QString)', 'deliverNotice(QString, QString)'), ('inviteReceived(QString, QString)', 'deliverInvite(QString, QString)'), ('nickCollision(QString, QString)', 'nickCollision(QString, QString)'), ('myHandleChanged(QString)', 'myHandleChanged(QString)'), ('namesReceived(QString, PyQt_PyObject)', 'updateNames(QString, PyQt_PyObject)'), ('userPresentUpdate(QString, QString, QString)', 'userPresentUpdate(QString, QString, QString)'), ('channelListReceived(PyQt_PyObject)', 'updateChannelList(PyQt_PyObject)'), ('timeCommand(QString, QString, QString)', 'timeCommand(QString, QString, QString)'), ('chanInviteOnly(QString)', 'chanInviteOnly(QString)'), ('modesUpdated(QString, QString)', 'modesUpdated(QString, QString)'), ('cannotSendToChan(QString, QString)', 'cannotSendToChan(QString, QString)'), ('tooManyPeeps()', 'tooManyPeeps()'), ('quirkDisable(QString, QString, QString)', 'quirkDisable(QString, QString, QString)') ] def connectWidgets(self, irc, widget): self.connect(irc, QtCore.SIGNAL('finished()'), self, QtCore.SLOT('restartIRC()')) self.connect(irc, QtCore.SIGNAL('connected()'), self, QtCore.SLOT('connected()')) for c in self.widget2irc: self.connect(widget, QtCore.SIGNAL(c[0]), irc, QtCore.SLOT(c[1])) for c in self.irc2widget: self.connect(irc, QtCore.SIGNAL(c[0]), widget, QtCore.SLOT(c[1])) def disconnectWidgets(self, irc, widget): for c in self.widget2irc: self.disconnect(widget, QtCore.SIGNAL(c[0]), irc, QtCore.SLOT(c[1])) for c in self.irc2widget: self.disconnect(irc, QtCore.SIGNAL(c[0]), widget, QtCore.SLOT(c[1])) self.disconnect(irc, QtCore.SIGNAL('connected()'), self, QtCore.SLOT('connected()')) self.disconnect(self.irc, QtCore.SIGNAL('finished()'), self, QtCore.SLOT('restartIRC()')) def showUpdate(self, q): new_url = q.get() if new_url[0]: self.widget.pcUpdate.emit(new_url[0], new_url[1]) q.task_done() def showLoading(self, widget, msg="CONN3CT1NG"): self.widget.show() if len(msg) > 60: newmsg = [] while len(msg) > 60: s = msg.rfind(" ", 0, 60) if s == -1: break newmsg.append(msg[:s]) newmsg.append("\n") msg = msg[s+1:] newmsg.append(msg) msg = "".join(newmsg) if hasattr(self.widget, 'loadingscreen') and widget.loadingscreen: widget.loadingscreen.loadinglabel.setText(msg) if self.reconnectok: widget.loadingscreen.showReconnect() else: widget.loadingscreen.hideReconnect() else: widget.loadingscreen = LoadingScreen(widget) widget.loadingscreen.loadinglabel.setText(msg) self.connect(widget.loadingscreen, QtCore.SIGNAL('rejected()'), widget.app, QtCore.SLOT('quit()')) self.connect(self.widget.loadingscreen, QtCore.SIGNAL('tryAgain()'), self, QtCore.SLOT('tryAgain()')) if hasattr(self, 'irc') and self.irc.registeredIRC: return if self.reconnectok: widget.loadingscreen.showReconnect() else: widget.loadingscreen.hideReconnect() status = widget.loadingscreen.exec_() if status == QtGui.QDialog.Rejected: sys.exit(0) else: if self.widget.tabmemo: for c in self.widget.tabmemo.convos: self.irc.joinChannel(c) else: for c in self.widget.memos.values(): self.irc.joinChannel(c.channel) return True @QtCore.pyqtSlot() def connected(self): self.attempts = 0 @QtCore.pyqtSlot() def tryAgain(self): if not self.reconnectok: return if self.widget.loadingscreen: self.widget.loadingscreen.done(QtGui.QDialog.Accepted) self.widget.loadingscreen = None self.attempts += 1 if hasattr(self, 'irc') and self.irc: self.irc.reconnectIRC() self.irc.quit() else: self.restartIRC() @QtCore.pyqtSlot() def restartIRC(self): if hasattr(self, 'irc') and self.irc: self.disconnectWidgets(self.irc, self.widget) stop = self.irc.stopIRC del self.irc else: stop = None if stop is None: self.irc = PesterIRC(self.widget.config, self.widget) self.connectWidgets(self.irc, self.widget) self.irc.start() if self.attempts == 1: msg = "R3CONN3CT1NG" elif self.attempts > 1: msg = "R3CONN3CT1NG %d" % (self.attempts) else: msg = "CONN3CT1NG" self.reconnectok = False self.showLoading(self.widget, msg) else: self.reconnectok = True self.showLoading(self.widget, "F41L3D: %s" % stop) def oppts(self, argv): options = {} try: opts, args = getopt.getopt(argv, "s:p:", ["server=", "port=", "advanced", "no-honk"]) except getopt.GetoptError: return options for opt, arg in opts: if opt in ("-s", "--server"): options["server"] = arg elif opt in ("-p", "--port"): options["port"] = arg elif opt in ("--advanced"): options["advanced"] = True elif opt in ("--no-honk"): options["honk"] = False return options def run(self): self.irc.start() self.reconnectok = False self.showLoading(self.widget) sys.exit(self.app.exec_()) def _retrieveGlobals(): # NOTE: Yes, this is a terrible kludge so that the console can work # properly. I'm open to alternatives. return globals() if __name__ == "__main__": # We're being run as a script - not being imported. pesterchum = MainProgram() pesterchum.run()