497 lines
19 KiB
Python
497 lines
19 KiB
Python
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'<c=(.*?)>')
|
|
|
|
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 = "<c=%s>%s%s%s: %s</c>" % (colorcmd, grammar.pcf, initials, grammar.number, qtext)
|
|
# account for TC's parsing error
|
|
serverText = "<c=%s>%s: %s</c> " % (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)]
|