229 lines
5.4 KiB
Python
229 lines
5.4 KiB
Python
|
# Pass this program the Holy Grail script on stdin.
|
||
|
import sys
|
||
|
import string
|
||
|
import stdwin
|
||
|
from stdwinevents import *
|
||
|
|
||
|
try:
|
||
|
import macspeech
|
||
|
except ImportError:
|
||
|
macspeech = None
|
||
|
|
||
|
WINWIDTH = 1000
|
||
|
scrw, scrh = stdwin.getscrsize()
|
||
|
if WINWIDTH > 0.8*scrw:
|
||
|
WINWIDTH = int(0.8*scrw)
|
||
|
BLACK = stdwin.fetchcolor('black')
|
||
|
RED = stdwin.fetchcolor('red')
|
||
|
BLUE = stdwin.fetchcolor('blue')
|
||
|
|
||
|
done='done'
|
||
|
|
||
|
class MacSpeaker:
|
||
|
def __init__(self):
|
||
|
self.voices = []
|
||
|
self.nvoices = macspeech.CountVoices()
|
||
|
self.curvoice = 1
|
||
|
self.rate = 1.0
|
||
|
|
||
|
def _newvoice(self):
|
||
|
vd = macspeech.GetIndVoice(self.curvoice)
|
||
|
sc = vd.NewChannel()
|
||
|
self.curvoice = self.curvoice + 1
|
||
|
if self.curvoice > self.nvoices:
|
||
|
self.curvoice = 1
|
||
|
return sc
|
||
|
|
||
|
def newvoices(self, n):
|
||
|
self.voices = []
|
||
|
for i in range(n):
|
||
|
self.voices.append(self._newvoice())
|
||
|
if self.rate <> 1.0:
|
||
|
self.setrate(1.0)
|
||
|
|
||
|
def setrate(self, factor):
|
||
|
self.rate = self.rate*factor
|
||
|
for v in self.voices:
|
||
|
r = v.GetRate()
|
||
|
v.SetRate(r*factor)
|
||
|
|
||
|
def speak(self, i, text):
|
||
|
self.voices[i-1].SpeakText(text)
|
||
|
|
||
|
def busy(self):
|
||
|
return macspeech.Busy()
|
||
|
|
||
|
[NOTHING, NEWSCENE, ACT, TEXT, MORETEXT] = range(5)
|
||
|
def parseline(line):
|
||
|
stripline = string.strip(line)
|
||
|
if not stripline:
|
||
|
return NOTHING, ''
|
||
|
if stripline[:5] == 'Scene':
|
||
|
return NEWSCENE, stripline
|
||
|
if line[0] == '[':
|
||
|
return ACT, stripline
|
||
|
if line[0] == ' ' and ':' in line:
|
||
|
splitline = string.splitfields(stripline, ':')
|
||
|
stripline = string.joinfields(splitline[1:], ':')
|
||
|
return TEXT, (splitline[0], string.strip(stripline))
|
||
|
return MORETEXT, stripline
|
||
|
|
||
|
def readscript(file):
|
||
|
lines = file.readlines()
|
||
|
acts = []
|
||
|
actor_dict = {}
|
||
|
longest = 0
|
||
|
prev_act = 0
|
||
|
for i in range(len(lines)):
|
||
|
tp, data = parseline(lines[i])
|
||
|
if tp == NEWSCENE:
|
||
|
acts.append(actor_dict.keys(), lines[prev_act:i])
|
||
|
prev_act = i
|
||
|
actor_dict = {}
|
||
|
elif tp == TEXT:
|
||
|
actor_dict[data[0]] = 1
|
||
|
lines[i] = tp, data
|
||
|
return acts[1:]
|
||
|
|
||
|
class Main:
|
||
|
def __init__(self):
|
||
|
if macspeech:
|
||
|
self.speaker = MacSpeaker()
|
||
|
else:
|
||
|
self.speaker = None
|
||
|
sys.stdin = open('SCRIPT', 'r')
|
||
|
self.acts = readscript(sys.stdin)
|
||
|
maxactor = 0
|
||
|
for actorlist, actdata in self.acts:
|
||
|
if len(actorlist) > maxactor:
|
||
|
maxactor = len(actorlist)
|
||
|
if not self.loadnextact():
|
||
|
print 'No acts to play!'
|
||
|
raise done
|
||
|
self.lh = stdwin.lineheight()
|
||
|
self.winheight = (maxactor+2)*self.lh
|
||
|
stdwin.setdefwinsize(WINWIDTH, self.winheight)
|
||
|
self.win = stdwin.open('The Play')
|
||
|
self.win.setdocsize(WINWIDTH, self.winheight)
|
||
|
self.win.change(((0,0),(WINWIDTH, self.winheight)))
|
||
|
self.menu = self.win.menucreate('Play')
|
||
|
self.menu.additem('Faster', '+')
|
||
|
self.menu.additem('Slower', '-')
|
||
|
self.menu.additem('Quit', 'Q')
|
||
|
self.speed = 4
|
||
|
|
||
|
def done(self):
|
||
|
del self.win
|
||
|
del self.menu
|
||
|
|
||
|
def loadnextact(self):
|
||
|
if not self.acts: return 0
|
||
|
actors, lines = self.acts[0]
|
||
|
del self.acts[0]
|
||
|
prevactor = 0
|
||
|
for i in range(len(lines)):
|
||
|
tp, data = lines[i]
|
||
|
if tp == NOTHING:
|
||
|
continue
|
||
|
elif tp in (NEWSCENE, ACT):
|
||
|
lines[i] = 0, data
|
||
|
elif tp == TEXT:
|
||
|
prevactor = actors.index(data[0])
|
||
|
lines[i] = prevactor+1, data[1]
|
||
|
else:
|
||
|
lines[i] = prevactor+1, data
|
||
|
self.lines = lines
|
||
|
self.actors = [''] + actors
|
||
|
self.actorlines = [''] * len(self.actors)
|
||
|
if self.speaker:
|
||
|
self.speaker.newvoices(len(self.actors)-1)
|
||
|
self.prevline = 0
|
||
|
self.actwidth = 0
|
||
|
for a in self.actors:
|
||
|
w = stdwin.textwidth(a)
|
||
|
if w > self.actwidth:
|
||
|
self.actwidth = w
|
||
|
return 1
|
||
|
|
||
|
def loadnextline(self):
|
||
|
if not self.lines: return 0
|
||
|
self.actorlines[self.prevline] = ''
|
||
|
top = self.lh*self.prevline
|
||
|
self.win.change(((0, top), (WINWIDTH, top+self.lh)))
|
||
|
line, data = self.lines[0]
|
||
|
del self.lines[0]
|
||
|
self.actorlines[line] = data
|
||
|
self.prevline = line
|
||
|
top = self.lh*self.prevline
|
||
|
self.win.change(((0, top), (WINWIDTH, top+self.lh)))
|
||
|
if line == 0:
|
||
|
self.win.settimer(5*self.speed)
|
||
|
else:
|
||
|
if self.speaker:
|
||
|
self.speaker.speak(line, data)
|
||
|
tv = 1
|
||
|
else:
|
||
|
nwords = len(string.split(data))
|
||
|
tv = self.speed*(nwords+1)
|
||
|
self.win.settimer(tv)
|
||
|
return 1
|
||
|
|
||
|
def timerevent(self):
|
||
|
if self.speaker and self.speaker.busy():
|
||
|
self.win.settimer(1)
|
||
|
return
|
||
|
while 1:
|
||
|
if self.loadnextline(): return
|
||
|
if not self.loadnextact():
|
||
|
stdwin.message('The END')
|
||
|
self.win.close()
|
||
|
raise done
|
||
|
self.win.change(((0,0), (WINWIDTH, self.winheight)))
|
||
|
|
||
|
def redraw(self, top, bottom, draw):
|
||
|
for i in range(len(self.actors)):
|
||
|
tpos = i*self.lh
|
||
|
bpos = (i+1)*self.lh-1
|
||
|
if tpos < bottom and bpos > top:
|
||
|
draw.setfgcolor(BLUE)
|
||
|
draw.text((0, tpos), self.actors[i])
|
||
|
if i == 0:
|
||
|
draw.setfgcolor(RED)
|
||
|
else:
|
||
|
draw.setfgcolor(BLACK)
|
||
|
draw.text((self.actwidth+5, tpos), self.actorlines[i])
|
||
|
|
||
|
def run(self):
|
||
|
self.win.settimer(10)
|
||
|
while 1:
|
||
|
ev, win, arg = stdwin.getevent()
|
||
|
if ev == WE_DRAW:
|
||
|
((left, top), (right, bot)) = arg
|
||
|
self.redraw(top, bot, self.win.begindrawing())
|
||
|
elif ev == WE_TIMER:
|
||
|
self.timerevent()
|
||
|
elif ev == WE_CLOSE:
|
||
|
self.win.close()
|
||
|
raise done
|
||
|
elif ev == WE_MENU and arg[0] == self.menu:
|
||
|
if arg[1] == 0:
|
||
|
if self.speed > 1:
|
||
|
self.speed = self.speed/2
|
||
|
if self.speaker:
|
||
|
self.speaker.setrate(1.4)
|
||
|
elif arg[1] == 1:
|
||
|
self.speed = self.speed * 2
|
||
|
if self.speaker:
|
||
|
self.speaker.setrate(0.7)
|
||
|
elif arg[1] == 2:
|
||
|
self.win.close()
|
||
|
raise done
|
||
|
|
||
|
if 1:
|
||
|
main = Main()
|
||
|
try:
|
||
|
main.run()
|
||
|
except done:
|
||
|
pass
|
||
|
del main
|