Guido van Rossum | 3a80c8d | 1994-10-02 11:33:59 +0000 | [diff] [blame] | 1 | # Pass this program the Holy Grail script on stdin. |
| 2 | import sys |
| 3 | import string |
| 4 | import stdwin |
| 5 | from stdwinevents import * |
| 6 | |
| 7 | try: |
| 8 | import macspeech |
| 9 | except ImportError: |
| 10 | macspeech = None |
| 11 | |
| 12 | WINWIDTH = 1000 |
| 13 | scrw, scrh = stdwin.getscrsize() |
| 14 | if WINWIDTH > 0.8*scrw: |
| 15 | WINWIDTH = int(0.8*scrw) |
| 16 | BLACK = stdwin.fetchcolor('black') |
| 17 | RED = stdwin.fetchcolor('red') |
| 18 | BLUE = stdwin.fetchcolor('blue') |
| 19 | |
| 20 | done='done' |
| 21 | |
| 22 | class MacSpeaker: |
| 23 | def __init__(self): |
| 24 | self.voices = [] |
| 25 | self.nvoices = macspeech.CountVoices() |
| 26 | self.curvoice = 1 |
| 27 | self.rate = 1.0 |
| 28 | |
| 29 | def _newvoice(self): |
| 30 | vd = macspeech.GetIndVoice(self.curvoice) |
| 31 | sc = vd.NewChannel() |
| 32 | self.curvoice = self.curvoice + 1 |
| 33 | if self.curvoice > self.nvoices: |
| 34 | self.curvoice = 1 |
| 35 | return sc |
| 36 | |
| 37 | def newvoices(self, n): |
| 38 | self.voices = [] |
| 39 | for i in range(n): |
| 40 | self.voices.append(self._newvoice()) |
| 41 | if self.rate <> 1.0: |
| 42 | self.setrate(1.0) |
| 43 | |
| 44 | def setrate(self, factor): |
| 45 | self.rate = self.rate*factor |
| 46 | for v in self.voices: |
| 47 | r = v.GetRate() |
| 48 | v.SetRate(r*factor) |
| 49 | |
| 50 | def speak(self, i, text): |
| 51 | self.voices[i-1].SpeakText(text) |
| 52 | |
| 53 | def busy(self): |
| 54 | return macspeech.Busy() |
| 55 | |
| 56 | [NOTHING, NEWSCENE, ACT, TEXT, MORETEXT] = range(5) |
| 57 | def parseline(line): |
| 58 | stripline = string.strip(line) |
| 59 | if not stripline: |
| 60 | return NOTHING, '' |
| 61 | if stripline[:5] == 'Scene': |
| 62 | return NEWSCENE, stripline |
| 63 | if line[0] == '[': |
| 64 | return ACT, stripline |
| 65 | if line[0] == ' ' and ':' in line: |
| 66 | splitline = string.splitfields(stripline, ':') |
| 67 | stripline = string.joinfields(splitline[1:], ':') |
| 68 | return TEXT, (splitline[0], string.strip(stripline)) |
| 69 | return MORETEXT, stripline |
| 70 | |
| 71 | def readscript(file): |
| 72 | lines = file.readlines() |
| 73 | acts = [] |
| 74 | actor_dict = {} |
| 75 | longest = 0 |
| 76 | prev_act = 0 |
| 77 | for i in range(len(lines)): |
| 78 | tp, data = parseline(lines[i]) |
| 79 | if tp == NEWSCENE: |
| 80 | acts.append(actor_dict.keys(), lines[prev_act:i]) |
| 81 | prev_act = i |
| 82 | actor_dict = {} |
| 83 | elif tp == TEXT: |
| 84 | actor_dict[data[0]] = 1 |
| 85 | lines[i] = tp, data |
| 86 | return acts[1:] |
| 87 | |
| 88 | class Main: |
| 89 | def __init__(self): |
| 90 | if macspeech: |
| 91 | self.speaker = MacSpeaker() |
| 92 | else: |
| 93 | self.speaker = None |
| 94 | sys.stdin = open('SCRIPT', 'r') |
| 95 | self.acts = readscript(sys.stdin) |
| 96 | maxactor = 0 |
| 97 | for actorlist, actdata in self.acts: |
| 98 | if len(actorlist) > maxactor: |
| 99 | maxactor = len(actorlist) |
| 100 | if not self.loadnextact(): |
| 101 | print 'No acts to play!' |
| 102 | raise done |
| 103 | self.lh = stdwin.lineheight() |
| 104 | self.winheight = (maxactor+2)*self.lh |
| 105 | stdwin.setdefwinsize(WINWIDTH, self.winheight) |
| 106 | self.win = stdwin.open('The Play') |
| 107 | self.win.setdocsize(WINWIDTH, self.winheight) |
| 108 | self.win.change(((0,0),(WINWIDTH, self.winheight))) |
| 109 | self.menu = self.win.menucreate('Play') |
| 110 | self.menu.additem('Faster', '+') |
| 111 | self.menu.additem('Slower', '-') |
| 112 | self.menu.additem('Quit', 'Q') |
| 113 | self.speed = 4 |
| 114 | |
| 115 | def done(self): |
| 116 | del self.win |
| 117 | del self.menu |
| 118 | |
| 119 | def loadnextact(self): |
| 120 | if not self.acts: return 0 |
| 121 | actors, lines = self.acts[0] |
| 122 | del self.acts[0] |
| 123 | prevactor = 0 |
| 124 | for i in range(len(lines)): |
| 125 | tp, data = lines[i] |
| 126 | if tp == NOTHING: |
| 127 | continue |
| 128 | elif tp in (NEWSCENE, ACT): |
| 129 | lines[i] = 0, data |
| 130 | elif tp == TEXT: |
| 131 | prevactor = actors.index(data[0]) |
| 132 | lines[i] = prevactor+1, data[1] |
| 133 | else: |
| 134 | lines[i] = prevactor+1, data |
| 135 | self.lines = lines |
| 136 | self.actors = [''] + actors |
| 137 | self.actorlines = [''] * len(self.actors) |
| 138 | if self.speaker: |
| 139 | self.speaker.newvoices(len(self.actors)-1) |
| 140 | self.prevline = 0 |
| 141 | self.actwidth = 0 |
| 142 | for a in self.actors: |
| 143 | w = stdwin.textwidth(a) |
| 144 | if w > self.actwidth: |
| 145 | self.actwidth = w |
| 146 | return 1 |
| 147 | |
| 148 | def loadnextline(self): |
| 149 | if not self.lines: return 0 |
| 150 | self.actorlines[self.prevline] = '' |
| 151 | top = self.lh*self.prevline |
| 152 | self.win.change(((0, top), (WINWIDTH, top+self.lh))) |
| 153 | line, data = self.lines[0] |
| 154 | del self.lines[0] |
| 155 | self.actorlines[line] = data |
| 156 | self.prevline = line |
| 157 | top = self.lh*self.prevline |
| 158 | self.win.change(((0, top), (WINWIDTH, top+self.lh))) |
| 159 | if line == 0: |
| 160 | self.win.settimer(5*self.speed) |
| 161 | else: |
| 162 | if self.speaker: |
| 163 | self.speaker.speak(line, data) |
| 164 | tv = 1 |
| 165 | else: |
| 166 | nwords = len(string.split(data)) |
| 167 | tv = self.speed*(nwords+1) |
| 168 | self.win.settimer(tv) |
| 169 | return 1 |
| 170 | |
| 171 | def timerevent(self): |
| 172 | if self.speaker and self.speaker.busy(): |
| 173 | self.win.settimer(1) |
| 174 | return |
| 175 | while 1: |
| 176 | if self.loadnextline(): return |
| 177 | if not self.loadnextact(): |
| 178 | stdwin.message('The END') |
| 179 | self.win.close() |
| 180 | raise done |
| 181 | self.win.change(((0,0), (WINWIDTH, self.winheight))) |
| 182 | |
| 183 | def redraw(self, top, bottom, draw): |
| 184 | for i in range(len(self.actors)): |
| 185 | tpos = i*self.lh |
| 186 | bpos = (i+1)*self.lh-1 |
| 187 | if tpos < bottom and bpos > top: |
| 188 | draw.setfgcolor(BLUE) |
| 189 | draw.text((0, tpos), self.actors[i]) |
| 190 | if i == 0: |
| 191 | draw.setfgcolor(RED) |
| 192 | else: |
| 193 | draw.setfgcolor(BLACK) |
| 194 | draw.text((self.actwidth+5, tpos), self.actorlines[i]) |
| 195 | |
| 196 | def run(self): |
| 197 | self.win.settimer(10) |
| 198 | while 1: |
| 199 | ev, win, arg = stdwin.getevent() |
| 200 | if ev == WE_DRAW: |
| 201 | ((left, top), (right, bot)) = arg |
| 202 | self.redraw(top, bot, self.win.begindrawing()) |
| 203 | elif ev == WE_TIMER: |
| 204 | self.timerevent() |
| 205 | elif ev == WE_CLOSE: |
| 206 | self.win.close() |
| 207 | raise done |
| 208 | elif ev == WE_MENU and arg[0] == self.menu: |
| 209 | if arg[1] == 0: |
| 210 | if self.speed > 1: |
| 211 | self.speed = self.speed/2 |
| 212 | if self.speaker: |
| 213 | self.speaker.setrate(1.4) |
| 214 | elif arg[1] == 1: |
| 215 | self.speed = self.speed * 2 |
| 216 | if self.speaker: |
| 217 | self.speaker.setrate(0.7) |
| 218 | elif arg[1] == 2: |
| 219 | self.win.close() |
| 220 | raise done |
| 221 | |
| 222 | if 1: |
| 223 | main = Main() |
| 224 | try: |
| 225 | main.run() |
| 226 | except done: |
| 227 | pass |
| 228 | del main |