from string import Template import re from PyQt4 import QtGui, QtCore from datetime import time, timedelta, datetime from dataobjs import PesterProfile, Mood from generic import PesterIcon from convo import PesterConvo, PesterInput, PesterText, PesterTabWindow from parsetools import convertTags, escapeBrackets, addTimeInitial, timeProtocol def delta2txt(d, format="pc"): if format == "pc": sign = "+" if d >= timedelta(0) else "-" else: if d == timedelta(0): return "" sign = "F" if d >= timedelta(0) else "P" d = abs(d) totalminutes = (d.days*86400 + d.seconds) // 60 hours = totalminutes // 60 leftovermins = totalminutes % 60 if hours < 100: if format == "pc": return "%s%d:%02d" % (sign, hours, leftovermins) else: return "%s%02d:%02d" % (sign, hours, leftovermins) else: if format == "pc": return "%s%d" % (sign, hours) else: return "%s%02d:%02d" % (sign, hours, leftovermins) def txt2delta(txt): sign = 1 if txt[0] == '+': txt = txt[1:] elif txt[0] == '-': sign = -1 txt = txt[1:] l = txt.split(":") try: h = int(l[0]) m = 0 if len(l) > 1: m = int(l[1]) timed = timedelta(0, h*3600+m*60) except ValueError: timed = timedelta(0) return sign*timed def pcfGrammar(td): if td > timedelta(0): when = "FROM NOW" temporal = "FUTURE" pcf = "F" elif td < timedelta(0): when = "AGO" temporal = "PAST" pcf = "P" else: when = "RIGHT NOW" temporal = "CURRENT" pcf = "C" return (temporal, pcf, when) class TimeGrammar(object): def __init__(self, temporal, pcf, when, number="0"): self.temporal = temporal self.pcf = pcf self.when = when if number == "0" or number == 0: self.number = "" else: self.number = str(number) class TimeTracker(list): def __init__(self, time=None): self.timerecord = {"P": [], "F": []} if time is not None: self.append(time) self.current=0 self.addRecord(time) else: self.current=-1 def addTime(self, timed): try: i = self.index(timed) self.current = i except ValueError: self.current = len(self) self.append(timed) self.addRecord(timed) def setCurrent(self, timed): self.current = self.index(timed) def addRecord(self, timed): (temporal, pcf, when) = pcfGrammar(timed - timedelta(0)) if pcf == "C": return if timed in self.timerecord[pcf]: return self.timerecord[pcf].append(timed) def getRecord(self, timed): (temporal, pcf, when) = pcfGrammar(timed - timedelta(0)) if pcf == "C": return 0 if len(self.timerecord[pcf]) > 1: return self.timerecord[pcf].index(timed)+1 else: return 0 def removeTime(self, timed): try: self.pop(self.index(timed)) self.current = len(self)-1 except ValueError: pass def getTime(self): if self.current >= 0: return self[self.current] else: return None def getGrammar(self): timed = self.getTime() mytime = timedelta(0) (temporal, pcf, when) = pcfGrammar(timed - mytime) if timed == mytime: return TimeGrammar(temporal, pcf, when, 0) return TimeGrammar(temporal, pcf, when, self.getRecord(timed)) class TimeInput(QtGui.QLineEdit): def __init__(self, timeslider, parent): QtGui.QLineEdit.__init__(self, parent) self.timeslider = timeslider self.setText("+0:00") self.connect(self.timeslider, QtCore.SIGNAL('valueChanged(int)'), self, QtCore.SLOT('setTime(int)')) self.connect(self, QtCore.SIGNAL('editingFinished()'), self, QtCore.SLOT('setSlider()')) @QtCore.pyqtSlot(int) def setTime(self, sliderval): self.setText(self.timeslider.getTime()) @QtCore.pyqtSlot() def setSlider(self): value = unicode(self.text()) timed = txt2delta(value) sign = 1 if timed >= timedelta(0) else -1 abstimed = abs(txt2delta(value)) index = 50 for i, td in enumerate(timedlist): if abstimed < td: index = i-1 break self.timeslider.setValue(sign*index) text = delta2txt(timed) self.setText(text) class TimeSlider(QtGui.QSlider): def __init__(self, orientation, parent): QtGui.QSlider.__init__(self, orientation, parent) self.setTracking(True) self.setMinimum(-50) self.setMaximum(50) self.setValue(0) self.setPageStep(1) def getTime(self): time = timelist[abs(self.value())] sign = "+" if self.value() >= 0 else "-" return sign+time class MemoTabWindow(PesterTabWindow): def __init__(self, mainwindow, parent=None): PesterTabWindow.__init__(self, mainwindow, parent, "memos") def addChat(self, convo): self.convos[convo.channel] = convo # either addTab or setCurrentIndex will trigger changed() newindex = self.tabs.addTab(convo.channel) self.tabIndices[convo.channel] = newindex self.tabs.setCurrentIndex(newindex) self.tabs.setTabIcon(newindex, PesterIcon(self.mainwindow.theme["memos/memoicon"])) def updateBlocked(self): pass def updateMood(self): pass _ctag_begin = re.compile(r'') class MemoText(PesterText): def __init__(self, theme, parent=None): QtGui.QTextEdit.__init__(self, parent) self.setStyleSheet(theme["memos/textarea/style"]) self.setReadOnly(True) self.setMouseTracking(True) def addMessage(self, text, chum): parent = self.parent() window = parent.mainwindow me = window.profile() msg = unicode(text) chumdb = window.chumdb if chum is not me: # SO MUCH WH1T3SP4C3 >:] mobj = _ctag_begin.match(text) # get color from tag if mobj: try: color = QtGui.QColor(*[int(c) for c in mobj.group(1).split(",")]) except ValueError: color = QtGui.QColor("black") else: chumdb.setColor(chum.handle, color) parent.updateColor(chum.handle, color) else: color = chumdb.getColor(chum.handle) else: color = me.color chum.color = color systemColor = QtGui.QColor(window.theme["memos/systemMsgColor"]) if chum is not me: if parent.times.has_key(chum.handle): time = parent.times[chum.handle] else: # new chum! time current newtime = timedelta(0) time = TimeTracker(newtime) parent.times[chum.handle] = time timeGrammar = time.getGrammar() self.append(convertTags(chum.memojoinmsg(systemColor, time.getTime(), timeGrammar, window.theme["convo/text/joinmemo"]))) else: time = parent.time if msg[0:3] == "/me" or msg[0:13] == "PESTERCHUM:ME": if msg[0:3] == "/me": start = 3 else: start = 13 space = msg.find(" ") msg = chum.memsg(systemColor, msg[start:space], msg[space:], timegrammar=time.getGrammar()) window.chatlog.log(parent.channel, convertTags(msg, "bbcode")) self.append(convertTags(msg)) else: if chum is not me: msg = addTimeInitial(msg, parent.times[chum.handle].getGrammar()) msg = escapeBrackets(msg) self.append(convertTags(msg)) window.chatlog.log(parent.channel, convertTags(msg, "bbcode")) def changeTheme(self): pass class MemoInput(PesterInput): def __init__(self, theme, parent=None): QtGui.QLineEdit.__init__(self, parent) self.setStyleSheet(theme["memos/input/style"]) def changeTheme(self, theme): self.setStyleSheet(theme["memos/input/style"]) class PesterMemo(PesterConvo): def __init__(self, channel, timestr, mainwindow, parent=None): QtGui.QFrame.__init__(self, parent) self.channel = channel self.mainwindow = mainwindow self.time = TimeTracker(txt2delta(timestr)) self.setWindowTitle(channel) self.channelLabel = QtGui.QLabel(self) self.channelLabel.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Expanding)) self.textArea = MemoText(self.mainwindow.theme, self) self.textInput = MemoInput(self.mainwindow.theme, self) self.textInput.setFocus() self.userlist = QtGui.QListWidget(self) self.userlist.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Expanding)) self.timeslider = TimeSlider(QtCore.Qt.Horizontal, self) self.timeinput = TimeInput(self.timeslider, self) self.timeinput.setText(timestr) self.timeinput.setSlider() self.timetravel = QtGui.QPushButton("GO", self) self.times = {} self.initTheme(self.mainwindow.theme) # connect self.connect(self.textInput, QtCore.SIGNAL('returnPressed()'), self, QtCore.SLOT('sentMessage()')) layout_0 = QtGui.QVBoxLayout() layout_0.addWidget(self.textArea) layout_0.addWidget(self.textInput) layout_1 = QtGui.QHBoxLayout() layout_1.addLayout(layout_0) layout_1.addWidget(self.userlist) # layout_1 = QtGui.QGridLayout() # layout_1.addWidget(self.timeslider, 0, 1, QtCore.Qt.AlignHCenter) # layout_1.addWidget(self.timeinput, 1, 0, 1, 3) layout_2 = QtGui.QHBoxLayout() layout_2.addWidget(self.timeslider) layout_2.addWidget(self.timeinput) layout_2.addWidget(self.timetravel) self.layout = QtGui.QVBoxLayout() self.layout.addWidget(self.channelLabel) self.layout.addLayout(layout_1) self.layout.addLayout(layout_2) self.layout.setSpacing(0) margins = self.mainwindow.theme["memos/margins"] self.layout.setContentsMargins(margins["left"], margins["top"], margins["right"], margins["bottom"]) self.setLayout(self.layout) if parent: parent.addChat(self) self.newmessage = False def title(self): return self.channel def icon(self): return PesterIcon(self.mainwindow.theme["memos/memoicon"]) def sendTimeInfo(self, newChum=False): if newChum: self.messageSent.emit("PESTERCHUM:TIME>%s" % (delta2txt(self.time.getTime(), "server")+"i"), self.title()) else: self.messageSent.emit("PESTERCHUM:TIME>%s" % (delta2txt(self.time.getTime(), "server")), self.title()) def updateMood(self): pass def updateBlocked(self): pass def updateColor(self, handle, color): chums = self.userlist.findItems(handle, QtCore.Qt.MatchFlags(0)) for c in chums: c.setTextColor(color) def addMessage(self, text, handle): if type(handle) is bool: chum = self.mainwindow.profile() else: chum = PesterProfile(handle) self.notifyNewMessage() self.textArea.addMessage(text, chum) def initTheme(self, theme): memo = theme["memos"] self.resize(*memo["size"]) self.setStyleSheet(memo["style"]) self.setWindowIcon(PesterIcon(theme["memos/memoicon"])) t = Template(theme["memos/label/text"]) self.channelLabel.setText(t.safe_substitute(channel=self.channel)) self.channelLabel.setStyleSheet(theme["memos/label/style"]) self.channelLabel.setAlignment(self.aligndict["h"][theme["memos/label/align/h"]] | self.aligndict["v"][theme["memos/label/align/v"]]) self.channelLabel.setMaximumHeight(theme["memos/label/maxheight"]) self.channelLabel.setMinimumHeight(theme["memos/label/minheight"]) self.userlist.setStyleSheet(theme["memos/userlist/style"]) self.userlist.setFixedWidth(theme["memos/userlist/width"]) self.timeinput.setFixedWidth(theme["memos/time/text/width"]) self.timeinput.setStyleSheet(theme["memos/time/text/style"]) slidercss = "QSlider { %s } QSlider::groove { %s } QSlider::handle { %s }" % (theme["memos/time/slider/style"], theme["memos/time/slider/groove"], theme["memos/time/slider/handle"]) self.timeslider.setStyleSheet(slidercss) def changeTheme(self, theme): self.initTheme(theme) self.textArea.changeTheme(theme) self.textInput.changeTheme(theme) margins = theme["memos/margins"] self.layout.setContentsMargins(margins["left"], margins["top"], margins["right"], margins["bottom"]) def addUser(self, handle): chumdb = self.mainwindow.chumdb defaultcolor = QtGui.QColor("black") op = False if handle[0] == '@': op = True handle = handle[1:] item = QtGui.QListWidgetItem(handle) if handle == self.mainwindow.profile().handle: color = self.mainwindow.profile().color else: color = chumdb.getColor(handle, defaultcolor) item.setTextColor(color) self.userlist.addItem(item) def timeUpdate(self, handle, cmd): window = self.mainwindow chum = PesterProfile(handle) systemColor = QtGui.QColor(window.theme["memos/systemMsgColor"]) close = None # old TC command? try: secs = int(cmd) time = datetime.fromtimestamp(secs) timed = time - datetime.now() s = (timed // 60)*60 timed = timedelta(timed.days, s) except ValueError: if cmd == "i": timed = timedelta(0) else: if cmd[len(cmd)-1] == 'c': close = timeProtocol(cmd) timed = None else: timed = timeProtocol(cmd) if self.times.has_key(handle): if close is not None: if close in self.times[handle]: self.times[handle].setCurrent(close) grammar = self.times[handle].getGrammar() self.times[handle].removeTime(close) self.textArea.append(convertTags(chum.memoclosemsg(systemColor, grammar, window.theme["convo/text/closememo"]))) elif timed not in self.times[handle]: self.times[handle].addTime(timed) grammar = self.times[handle].getGrammar() self.textArea.append(convertTags(chum.memojoinmsg(systemColor, timed, grammar, window.theme["convo/text/joinmemo"]))) else: self.times[handle].setCurrent(timed) else: if timed is not None: ttracker = TimeTracker(timed) grammar = ttracker.getGrammar() self.textArea.append(convertTags(chum.memojoinmsg(systemColor, timed, grammar, window.theme["convo/text/joinmemo"]))) self.times[handle] = ttracker @QtCore.pyqtSlot() def sentMessage(self): text = self.textInput.text() if text == "": return grammar = self.time.getGrammar() # deal with quirks here qtext = self.mainwindow.userprofile.quirks.apply(unicode(text)) if qtext[0:3] != "/me": initials = self.mainwindow.profile().initials() colorcmd = self.mainwindow.profile().colorcmd() clientText = "%s%s%s: %s" % (colorcmd, grammar.pcf, initials, grammar.number, qtext) # account for TC's parsing error serverText = "%s: %s " % (colorcmd, initials, qtext) else: clientText = qtext serverText = clientText self.textInput.setText("") self.addMessage(clientText, True) # convert color tags text = convertTags(unicode(serverText), "ctag") self.messageSent.emit(serverText, self.title()) @QtCore.pyqtSlot() def namesUpdated(self): # get namesdb namesdb = self.mainwindow.namesdb # reload names self.userlist.clear() for n in self.mainwindow.namesdb[self.channel]: self.addUser(n) @QtCore.pyqtSlot(QtCore.QString, QtCore.QString, QtCore.QString) def userPresentChange(self, handle, channel, update): if channel != self.channel: return chums = self.userlist.findItems(handle, QtCore.Qt.MatchFlags(0)) h = unicode(handle) c = unicode(channel) systemColor = QtGui.QColor(self.mainwindow.theme["memos/systemMsgColor"]) # print exit if update == "quit" or update == "left": for c in chums: chum = PesterProfile(h) self.userlist.takeItem(self.userlist.row(c)) while self.times[h].getTime() is not None: t = self.times[h] grammar = t.getGrammar() self.textArea.append(convertTags(chum.memoclosemsg(systemColor, grammar, self.mainwindow.theme["convo/text/closememo"]))) self.times[h].removeTime(t.getTime()) elif update == "join": self.addUser(h) def closeEvent(self, event): self.mainwindow.waitingMessages.messageAnswered(self.channel) self.windowClosed.emit(self.title()) windowClosed = QtCore.pyqtSignal(QtCore.QString) timelist = ["0:00", "0:01", "0:02", "0:04", "0:06", "0:10", "0:14", "0:22", "0:30", "0:41", "1:00", "1:34", "2:16", "3:14", "4:13", "4:20", "5:25", "6:12", "7:30", "8:44", "10:25", "11:34", "14:13", "16:12", "17:44", "22:22", "25:10", "33:33", "42:00", "43:14", "50:00", "62:12", "75:00", "88:44", "100", "133", "143", "188", "200", "222", "250", "314", "333", "413", "420", "500", "600", "612", "888", "1000", "1025"] timedlist = [timedelta(0), timedelta(0, 60), timedelta(0, 120), timedelta(0, 240), timedelta(0, 360), timedelta(0, 600), timedelta(0, 840), timedelta(0, 1320), timedelta(0, 1800), timedelta(0, 2460), timedelta(0, 3600), timedelta(0, 5640), timedelta(0, 8160), timedelta(0, 11640), timedelta(0, 15180), timedelta(0, 15600), timedelta(0, 19500), timedelta(0, 22320), timedelta(0, 27000), timedelta(0, 31440), timedelta(0, 37500), timedelta(0, 41640), timedelta(0, 51180), timedelta(0, 58320), timedelta(0, 63840), timedelta(0, 80520), timedelta(1, 4200), timedelta(1, 34380), timedelta(1, 64800), timedelta(1, 69240), timedelta(2, 7200), timedelta(2, 51120), timedelta(3, 10800), timedelta(3, 60240), timedelta(4, 14400), timedelta(5, 46800), timedelta(5, 82800), timedelta(7, 72000), timedelta(8, 28800), timedelta(9, 21600), timedelta(10, 36000), timedelta(13, 7200), timedelta(13, 75600), timedelta(17, 18000), timedelta(17, 43200), timedelta(20, 72000), timedelta(25), timedelta(25, 43200), timedelta(37), timedelta(41, 57600), timedelta(42, 61200)]