marmots/whiteboard.py

649 lines
23 KiB
Python
Raw Normal View History

2011-03-19 00:10:02 +00:00
from engine import *
from basetoken import *
from tpers import *
from datetime import *
from util import *
import telnet
import time
import login
import weakref
class Layer(TPrs):
def InitPersistent(self, ascr, stName):
self.ascr = ascr
self.stName = stName
def StName(self):
return self.stName
def SetName(self, st):
self.stName = st
def Draw(self, ascr):
ascr.PutAscr(self.ascr)
def FSelectable(self):
return True
@Version(3)
class Board(Ownable):
def InitPersistent(self, owner, stName, user, rglayer=None, dtLastModified=None):
Ownable.InitPersistent(self, owner)
if rglayer == None:
rglayer = [Layer(ansi.Ascr(self.W(), self.H()), "Layer 1")]
self.rglayer = rglayer
self.stName = stName
self.user = user
self.chat = Chat()
if dtLastModified == None:
self.SetTimestamp()
else:
self.dtLastModified = dtLastModified
def StName(self):
return self.owner.StNameBoard(self)
def StLastModified(self):
return self.dtLastModified.strftime("%b %d, %Y at %I:%M%p")
def LayerByName(self, stName):
for layer in self.rglayer:
if layer.StName().lower() == stName.lower():
return layer
return None
def NewLayer(self, stName):
if len(self.rglayer) < 8:
layerNew = Layer(ansi.Ascr(self.W(), self.H()), stName)
self.rglayer.append(layerNew)
return layerNew
def MoveLayer(self, layer, fUp):
ilayer = self.rglayer.index(layer)
if fUp and ilayer > 0:
ilayer = ilayer - 1
elif not fUp and ilayer < len(self.rglayer) - 1:
ilayer = ilayer + 1
self.rglayer.remove(layer)
self.rglayer.insert(ilayer, layer)
def W(self): return config.W
def H(self): return config.H
def Touch(self):
self.SetTimestamp()
self.owner.BoardTouched(self)
def SetTimestamp(self):
self.dtLastModified = datetime.now()
def draw(self, ascr):
for layer in reversed(self.rglayer):
layer.Draw(ascr)
2011-04-12 15:37:52 +00:00
2011-03-19 00:10:02 +00:00
def Save(self):
fn = config.DIR_ANSI + "/" + "_".join([str(x) for x in time.localtime()])
ascr = ansi.Ascr(self.W(), self.H())
self.draw(ascr)
with open(fn + ".html", "w") as fl:
fl.write("<html><body bgcolor='#000000'><center>")
fl.write(ascr.Hst())
fl.write("</center></body></html>")
with open(fn + ".ans", "w") as fl:
fl.write(ascr.Ast())
2011-04-12 15:37:52 +00:00
2011-03-19 00:10:02 +00:00
def UpgradeFrom(self, versionOld):
if versionOld < 2:
gameWB = [gameWB for gameWB in self.owner.rggameWB if gameWB.rgtoken("whiteboard")[0].board == self][0]
self.stName = gameWB.stName
self.user = gameWB.user
if versionOld < 3:
self.chat = Chat()
@Version(5)
class Whiteboard(Token):
def InitPersistent(self, game, board):
Token.InitPersistent(self, game, "whiteboard")
self.board = board
def UpgradeFrom(self, versionOld):
if versionOld < 3:
dt = None
else:
dt = self.dtLastModified
del self.dtLastModified
if versionOld < 4:
rglayer = [Layer(self.ascr, "Layer 1")]
del self.ascr
self.untag("drawable")
self.untag("solid")
else:
rglayer = self.rglayer
del self.rglayer
if versionOld < 5:
self.board = Board(self.game.gameLobby, self.game.stName, self.game.user, rglayer, dt)
2011-04-12 15:37:52 +00:00
2011-03-19 00:10:02 +00:00
class Chl(object):
def __init__(self, client, st):
self.user = client.cld.user
self.st = st.rstrip()
class ChlMsg(Chl):
def StUser(self):
return "<" + self.user + ">"
def Col(self):
return ansi.WHITE
class ChlAct(Chl):
def StUser(self):
return "* " + self.user
def Col(self):
return ansi.MAGENTA
class ChlSys(Chl):
def StUser(self):
return self.user
def Col(self):
return ansi.BLUE
class Chat(TPrs):
def InitTransient(self):
TPrs.InitTransient(self)
self.cchl = 100
self.DetachFromGame()
def InitPersistent(self):
TPrs.InitPersistent(self)
self.rgchl = []
def AttachToGame(self, game):
self.game = game
self.evChat = Event(game)
def DetachFromGame(self):
self.game = None
self.evChat = None
def Add(self, chl):
if len(chl.st) > 0:
self.rgchl.insert(0, chl)
if len(self.rgchl) > self.cchl:
self.rgchl = self.rgchl[:self.cchl]
self.evChat.fire(chl)
def CChl(self):
return len(self.rgchl)
def CUsers(self):
return len(self.game.rgclient)
def DrawMsg(self, ascr, x, y, w, h, colBg=ansi.BLACK, ichl=0):
yDraw = y + h - 1
for chl in self.rgchl[ichl:]:
stUser = chl.StUser()
colFg = chl.Col()
wMsg = w - len(stUser) - 1
rgstMsg = RgstWrapped(chl.st, wMsg)
rgstMsg.reverse()
for istMsg, stMsg in enumerate(rgstMsg):
if istMsg == len(rgstMsg) - 1:
st = "{0} {1:{2}}".format(stUser, stMsg, wMsg)
else:
st = "{0:{1}} {2:{3}}".format("", len(stUser), stMsg, wMsg)
ascr.PutSt(st, x, yDraw, colFg, colBg)
yDraw = yDraw - 1
if yDraw == y - 1:
return
while yDraw >= y:
ascr.PutSt("{0:{1}}".format("", w), x, yDraw, ansi.WHITE, colBg)
yDraw = yDraw - 1
def DrawUsers(self, ascr, x, y, w, h, colBg=ansi.BLACK, iuser=0):
yDraw = y
for client in sorted(self.game.rgclient, lambda c1, c2: cmp(c1.Cldg().iclient, c2.Cldg().iclient))[iuser:]:
ascr.PutSt("{0:{1}}".format(str(client.Cldg().iclient) + ") " + client.cld.user, w), x, yDraw, ansi.WHITE, colBg)
yDraw = yDraw + 1
if yDraw == y + h:
break
while yDraw < y + h:
ascr.PutSt("{0:{1}}".format("", w), x, yDraw, ansi.WHITE, colBg)
yDraw = yDraw + 1
2011-04-12 15:37:52 +00:00
2011-03-19 00:10:02 +00:00
class Cursor(TokenClient):
def InitPersistent(self, game, client):
TokenClient.InitPersistent(self, game, client, "drawable", "cursor")
self.fBlink = False
self.cDontBlink = 0
self.whiteboard = self.game.rgtoken("whiteboard")[0].board
self.pos = ansi.Pos(self.whiteboard.W(), self.whiteboard.H())
miFg = MiColour("Foreground", ansi.rgcolorFg, ansi.WHITE)
miBg = MiColour("Background", ansi.rgcolorBg, ansi.BLACK)
miChars = MiChars([[49, 50, 51, 52, 53, 54, 55, 56, 57, 48], #digits
[176, 177, 178, 219, 223, 220, 221, 222, 254, 249], #blocks
[218, 191, 192, 217, 196, 179, 195, 180, 193, 194], #single lines
[201, 187, 200, 188, 205, 186, 204, 185, 202, 203], #double lines
[213, 184, 212, 190, 205, 179, 198, 181, 207, 209], #sing vert, doub horiz
[214, 183, 211, 189, 196, 186, 199, 182, 208, 210], #doub vert, sing horiz
[197, 206, 216, 215, 173, 168, 169, 170, 174, 175], #crosses
#[ 1, 2, 3, 4, 5, 6, 14, 15, 19, 20], #smiles
[224, 225, 226, 227, 228, 229, 230, 231, 232, 233], #greek
[234, 235, 236, 237, 238, 239, 240, 241, 242, 243], #crap
[244, 245, 246, 247, 248, 249, 251, 252, 253, 167], #more crap
[155, 156, 157, 158, 159, 171, 172, 166, 32, 32], #$
[142, 132, 131, 143, 134, 133, 160, 145, 146, 32], #a
[128, 135, 137, 136, 138, 144, 130, 164, 165, 32], #c, e, n
[139, 140, 141, 161, 153, 148, 147, 149, 162, 32], #i, o
[154, 129, 150, 151, 163, 32, 32, 32, 32, 32], #u
], miFg, miBg)
miQuit = MiButton("Back to Menu", self.client.leaveGame)
self.cf = MiMenu([None, miFg, miBg, None, miChars, None, MiButton("Save", self.Save), None, miQuit, None])
self.miChat = MiMenu([None, MiChat(client, 13, self.whiteboard.chat), MiTypein(self.SendMsg), None, miQuit, None])
self.rgmiLayer = RgmiProjected(self.whiteboard.rglayer, DgProjectMiButton(self.SelectLayer))
self.miLayers = MiMenu(Rgseq([None], self.rgmiLayer, [None, MiButton("New Layer", self.NewLayer), MiButton("Move up", self.LayerUp), MiButton("Move down", self.LayerDown),
None, MiToggle("Show One"), None, MiStatic("Rename selected layer:"), MiTypein(self.RenameLayer, "LayerName", 80), None, miQuit, None]))
self.ovPopup = OvPopup(self, self.client, MiTab.MiTabbed(["Drawing", self.cf], ["Layers", self.miLayers], ["Chat", self.miChat]))
miStatus = MiMenuHoriz([MiStatic("Hit <Tab> for menu"), MiValue(miChars)])
miStatus.ListenForNotice(self.miChat)
self.ovStatus = OvStatic(self, self.client, miStatus, OvStatic.BOTTOM)
self.SelectLayer(self.whiteboard.rglayer[0])
WhiteboardDraw(self, client)
def Save(self):
self.whiteboard.Save()
msgscroller = self.game.rgtoken("msgscroller")[0]
msgscroller.evPost.fire("Saved picture")
def SelectLayer(self, layer):
self.ovPopup.SetSelection("layer", self.rgmiLayer.MiFromValue(layer))
miRename = self.miLayers.MiByName("LayerName").SetValue(layer.StName())
return True
def GetLayer(self):
return self.ovPopup.GetSelection("layer").Value()
def NewLayer(self):
ilayer = 1
stLayer = None
while stLayer == None:
stLayer = "Layer " + str(ilayer)
if self.whiteboard.LayerByName(stLayer) != None:
stLayer = None
ilayer = ilayer + 1
layerNew = self.whiteboard.NewLayer(stLayer)
if layerNew != None:
self.SelectLayer(layerNew)
return True
def LayerUp(self):
self.whiteboard.MoveLayer(self.GetLayer(), True)
return True
def LayerDown(self):
self.whiteboard.MoveLayer(self.GetLayer(), False)
return True
def RenameLayer(self, st):
if st != "":
self.GetLayer().SetName(st)
return True
def blinkCursor(self, fBlink):
self.fBlink = fBlink
if not fBlink and self.cDontBlink > 0:
self.cDontBlink = self.cDontBlink - 1
def SendMsg(self, st):
if st.lower().startswith("/me ") and len(st.rstrip()) > 3:
self.whiteboard.chat.Add(ChlAct(self.client, st[4:]))
else:
self.whiteboard.chat.Add(ChlMsg(self.client, st))
def OnChat(self, chl):
if type(chl) != ChlSys:
self.miChat.FlagNotice()
def run(self):
with self.whiteboard.chat.evChat.oob(self, self.OnChat):
while True:
key = self.client.evKey.receive(self)
fDontBlink = True
if key == ansi.K_LEFT:
self.pos.Left()
elif key == ansi.K_RIGHT:
self.pos.Right()
elif key == ansi.K_UP:
self.pos.Up()
elif key == ansi.K_DOWN:
self.pos.Down()
elif key == ansi.K_BACKSPACE:
self.pos.Left()
self.PutWb(ansi.achBlank)
elif key == ansi.K_DEL:
self.PutWb(ansi.achBlank)
self.pos.Right()
elif key == ansi.K_TAB:
fDontBlink = False
self.ovPopup.evShow.fire()
self.ovPopup.evDone.receive(self)
elif key == ansi.K_HOME:
self.Skip(self.pos.Left)
elif key == ansi.K_END:
self.Skip(self.pos.Right)
elif key == ansi.K_PGUP:
self.Skip(self.pos.Up)
elif key == ansi.K_PGDN:
self.Skip(self.pos.Down)
elif ansi.FKeyPrintable(key):
if key.isdigit():
self.PutWb(chr(self.cf.Value("Characters")[IchFromCh(key)]))
else:
self.PutWb(key)
self.pos.Right()
else:
print "weird key:", ansi.StrKey(key)
fDontBlink = False
if fDontBlink:
self.cDontBlink = 2 if self.fBlink else 1
if self.pos.Y() < self.whiteboard.H() / 2:
self.ovStatus.kplace = OvStatic.BOTTOM
else:
self.ovStatus.kplace = OvStatic.TOP
def PutWb(self, ch):
self.Put(ch, self.GetLayer().ascr)
self.whiteboard.Touch()
def Put(self, ch, ascr):
if type(ch) == str:
ach = ansi.MkAch(ch, self.cf.Value("Foreground"), self.cf.Value("Background"))
else:
ach = ch
ascr.PutAch(ach, self.pos.X(), self.pos.Y())
def GetAch(self):
return self.GetLayer().ascr.GetAch(self.pos.X(), self.pos.Y())
def Skip(self, dgMove):
self.SkipI(dgMove, self.GetAch())
def SkipI(self, dgMove, ach):
fStop = False
while not fStop:
fStop = not dgMove() or self.GetAch() != ach
def draw(self, ascr, client):
if self.fBlink or self.cDontBlink > 0:
if client == self.client:
chCursor = chr(176)
ach = ascr.GetAch(self.pos.X(), self.pos.Y())
if ach != None and ansi.ChFromAch(ach) == chCursor:
chCursor = chr(177)
self.Put(chCursor, ascr)
elif self.fBlink:
self.Put(str(self.client.Cldg().iclient)[0], ascr)
class WhiteboardDraw(TokenClient):
def InitPersistent(self, owner, client):
TokenClient.InitPersistent(self, owner, client, "drawable", "solid")
def draw(self, ascr, client):
if client == self.client:
if self.owner.miLayers.Value("Show One"):
self.owner.GetLayer().Draw(ascr)
else:
self.owner.whiteboard.draw(ascr)
class CursorBlinker(Token):
def InitPersistent(self, owner):
Token.InitPersistent(self, owner)
Blinker(self, self.BlinkAllCursors)
def BlinkAllCursors(self, fBlink):
for tok in self.game.rgtoken("cursor"):
tok.blinkCursor(fBlink)
2011-04-12 15:37:52 +00:00
2011-03-19 00:10:02 +00:00
class MiColour(MiList):
def InitPersistent(self, stName, rgcolor, col):
self.rgcolor = rgcolor
MiList.InitPersistent(self, rgcolor, rgcolor.index(col))
self.stName = stName
def StName(self):
return self.stName
def DrawValue(self, ascr, x, y, selor):
for icol in range(len(self.rgcolor)):
col = self.rgcolor[icol]
ascr.PutAch(ansi.MkAch(chr(219) if icol != self.ival else chr(254), col, ansi.BLACK if col != ansi.BLACK else ansi.WHITE), x + icol, y)
2011-04-12 15:37:52 +00:00
2011-03-19 00:10:02 +00:00
class MiChars(MiList):
def InitPersistent(self, rgchar, miFg, miBg):
MiList.InitPersistent(self, rgchar)
self.miFg = miFg
self.miBg = miBg
def StName(self):
return "Characters"
def DrawValue(self, ascr, x, y, selor):
for ich, ch in enumerate(self.Value()):
ascr.PutSt(ChFromIch(ich) + ":", x, y, self.ColFg(), self.ColBg(selor))
ascr.PutAch(ansi.MkAch(chr(ch), self.miFg.Value(), self.miBg.Value()), x + 2, y)
x = x + 4
def ChFromIch(ich):
if ich == 9:
return '0'
return str(ich + 1)
def IchFromCh(ch):
if ch == '0':
return 9
return int(ch) - 1
2011-04-12 15:37:52 +00:00
2011-03-19 00:10:02 +00:00
class MiChat(Mi):
KWNDMSG = 0
KWNDUSERS = 1
def InitPersistent(self, client, height, chat):
Mi.InitPersistent(self)
self.client = client
self.chat = chat
self.height = height
assert height > 2
self.kwnd = self.KWNDMSG
self.imsg = 0
self.iuser = 0
def StName(self):
return "Chat"
def HandleKey(self, key):
if self.kwnd == self.KWNDUSERS:
if key == ansi.K_LEFT:
self.kwnd = self.KWNDMSG
elif (key == 'a' or key == 'A') and self.iuser > 0:
self.iuser = self.iuser - 1
elif (key == 'z' or key == 'Z') and self.iuser < self.chat.CUsers() - 1:
self.iuser = self.iuser + 1
else:
return False
else: #KWNDMSG
if key == ansi.K_RIGHT:
self.kwnd = self.KWNDUSERS
elif (key == 'a' or key == 'A') and self.imsg < self.chat.CChl() - 1:
self.imsg = self.imsg + 1
elif (key == 'z' or key == 'Z') and self.imsg > 0:
self.imsg = self.imsg - 1
else:
return False
return True
def Height(self, w):
return self.height
def ColBgDefault(self):
return ansi.BLACK
def ColBgSelected(self, fPri):
return ansi.BLACK
def ColFg(self):
return ansi.WHITE
def FIgnoreParent(self):
return True
def Draw(self, ascr, x, y, w, selor):
wChat = (w / 4) * 3
wUser = (w / 4) - 1
self.chat.DrawMsg(ascr, x, y, wChat, self.height - 1, self.ColBg(selor), self.imsg)
for yT in range(y, y + self.height - 1):
ascr.PutAch(ansi.MkAch(chr(179)), x + wChat, yT)
ascr.PutSt("{0:{1}<{2}}{3}{0:{1}<{4}}".format("", chr(196), wChat, chr(193), wUser), x, y + self.height - 1)
self.chat.DrawUsers(ascr, x + wChat + 1, y, wUser, self.height - 1, self.ColBg(selor), self.iuser)
if selor.KSel() == Selor.PRI:
if self.kwnd == self.KWNDMSG:
xBar = x + wChat - 1
pct = 1 - (self.imsg / float(self.chat.CChl()))
else: # KWNDUSERS
xBar = x + w - 1
pct = self.iuser / float(self.chat.CUsers())
yBar = int(y + (pct * (self.height - 4)))
ascr.PutAch(ansi.MkAch("A", ansi.RED, ansi.WHITE), xBar, yBar)
ascr.PutAch(ansi.MkAch(chr(186), ansi.RED, ansi.WHITE), xBar, yBar + 1)
ascr.PutAch(ansi.MkAch("Z", ansi.RED, ansi.WHITE), xBar, yBar + 2)
2011-04-12 15:37:52 +00:00
2011-03-19 00:10:02 +00:00
class Joiner(Token):
def InitPersistent(self, owner):
Token.InitPersistent(self, owner)
self.msgScroller = self.game.rgtoken("msgscroller")[0]
self.board = self.game.rgtoken("whiteboard")[0].board
def OnJoin(self, client):
with client.Cldg().SetTransiently() as cldg:
cldg.iclient = -1
iclient = 1
rgiclient = [clientT.Cldg().iclient for clientT in self.game.rgclient]
while iclient in rgiclient:
iclient = iclient + 1
cldg.iclient = iclient
self.msgScroller.evPost.fire(client.cld.user + "(#" + str(cldg.iclient) + ") has joined! :)")
self.board.chat.Add(ChlSys(client, "has joined"))
def OnLeave(self, client):
self.msgScroller.evPost.fire(client.cld.user + " has left! :(")
self.board.chat.Add(ChlSys(client, "has left"))
def run(self):
while True:
with self.game.evJoin.oob(self, self.OnJoin):
self.OnLeave(self.game.evLeave.receive(self))
class Lobby(TokenClient):
def InitPersistent(self, owner, client):
TokenClient.InitPersistent(self, owner, client)
self.miDrawings = MiMenu(RgmiProjected(self.game.rgboard, DgProjectMiButton(self.Goto)))
self.miCmds = MiMenu([None, None, MiTypein(self.New, "DrawingName"), MiButton("New Drawing", self.NewBtn),
None, None, None, None, None, None, None, None, MiStatic("Last modified:"), MiDgText(self.StLastModified)])
OvMenu(self, client, MiMenuHoriz([self.miDrawings, self.miCmds], [65, 35]), OvMenu.FILL)
def New(self, stName):
if stName != "":
self.game.NewBoard(stName, self.client.cld.user)
def NewBtn(self):
self.New(self.miCmds.Value("DrawingName"))
def StLastModified(self):
return self.miDrawings.Value().StLastModified()
def Goto(self, board):
self.client.leaveGame(board)
2011-04-12 15:37:52 +00:00
2011-03-19 00:10:02 +00:00
class RunnerWB(Runner):
def InitPersistent(self):
self.gameLobby = GameLobby()
Runner.InitPersistent(self)
def RunGame(self, client):
if (client.joinGame(GameLogin())):
while True:
board = client.joinGame(self.gameLobby)
client.joinGame(self.gameLobby.GameFromBoard(board))
class GameLogin(Game):
def GetRgclsTokTrans(self):
return [[AutoJoiner, login.LoginTerm]]
@Version(5)
class GameLobby(Game):
def InitPersistent(self):
self.rgboard = []
Game.InitPersistent(self)
def InitTransient(self):
Game.InitTransient(self)
self.mpboard_gameWB = {}
def GetRgclsTokTrans(self):
return [[AutoJoiner, Lobby]]
def GameFromBoard(self, board):
if not (board in self.mpboard_gameWB):
gameWB = GameWB(self, board)
def KillBoard():
del self.mpboard_gameWB[board]
gameWB.rgdgdie.append(KillBoard)
self.mpboard_gameWB[board] = gameWB
return gameWB
return self.mpboard_gameWB[board]
def NewBoard(self, stName, user):
self.rgboard.insert(0, Board(self, stName, user))
def StNameBoard(self, board):
cclient = 0
if board in self.mpboard_gameWB:
cclient = len(self.mpboard_gameWB[board].rgclient)
return board.stName + " (by " + board.user + ") [" + str(cclient) + "]"
def BoardTouched(self, board):
#haaaack
if board != self.rgboard[0]:
self.rgboard.remove(board)
self.rgboard.insert(0, board)
self.ensureRun() # redraw
def UpgradeFrom(self, versionOld):
if versionOld < 3:
for gameWB in self.rggameWB:
gameWB.gameLobby = self
if versionOld < 4:
self.rgboard = [game.rgtoken("whiteboard")[0].board for game in self.rggameWB]
for gameWB in self.rggameWB:
gameWB.finish()
if versionOld < 5:
del self.rggameWB
class GameWB(Game):
def InitPersistent(self, gameLobby, board):
self.gameLobby = gameLobby
self.board = board
Game.InitPersistent(self)
def InitTransient(self):
Game.InitTransient(self)
if not self.FDead():
self.board.chat.AttachToGame(self)
self.rgdgdie.append(self.board.chat.DetachFromGame)
def GetRgclsTokPers(self):
return [[Whiteboard, self.board]]
def GetRgclsTokTrans(self):
return [MsgScroller, Joiner, [AutoJoiner, Cursor], [AliveWithPlayers, CursorBlinker], AutoKiller]
def GetRgtagDraw(self):
return ["background", "solid", "cursor", "menu", "msgscroller"]
if __name__ == "__main__":
Run(RunnerWB, "whiteboard.marm")