1 | ############################################################################ |
---|
2 | # |
---|
3 | # SAGE UI - A Graphical User Interface for SAGE |
---|
4 | # Copyright (C) 2005 Electronic Visualization Laboratory, |
---|
5 | # University of Illinois at Chicago |
---|
6 | # |
---|
7 | # All rights reserved. |
---|
8 | # |
---|
9 | # Redistribution and use in source and binary forms, with or without |
---|
10 | # modification, are permitted provided that the following conditions are met: |
---|
11 | # |
---|
12 | # * Redistributions of source code must retain the above copyright |
---|
13 | # notice, this list of conditions and the following disclaimer. |
---|
14 | # * Redistributions in binary form must reproduce the above |
---|
15 | # copyright notice, this list of conditions and the following disclaimer |
---|
16 | # in the documentation and/or other materials provided with the distribution. |
---|
17 | # * Neither the name of the University of Illinois at Chicago nor |
---|
18 | # the names of its contributors may be used to endorse or promote |
---|
19 | # products derived from this software without specific prior written permission. |
---|
20 | # |
---|
21 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
---|
22 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
---|
23 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
---|
24 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
---|
25 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
---|
26 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
---|
27 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
---|
28 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
---|
29 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
---|
30 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
---|
31 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
---|
32 | # |
---|
33 | # Direct questions, comments etc about SAGE UI to www.evl.uic.edu/cavern/forum |
---|
34 | # |
---|
35 | # Author: Ratko Jagodic |
---|
36 | # |
---|
37 | ############################################################################ |
---|
38 | |
---|
39 | |
---|
40 | import os.path |
---|
41 | import os |
---|
42 | import time |
---|
43 | from math import ceil |
---|
44 | import string |
---|
45 | import wx |
---|
46 | from Mywx import MyBitmapButton |
---|
47 | from threading import Timer |
---|
48 | from globals import * |
---|
49 | |
---|
50 | # some global vars |
---|
51 | hashRecordingWords = {} #RJ 2005-04-07 |
---|
52 | hashRecordingWords[1001] = "exec" |
---|
53 | hashRecordingWords[1002] = "kill" |
---|
54 | hashRecordingWords[1003] = "move" |
---|
55 | hashRecordingWords[1004] = "resize" |
---|
56 | hashRecordingWords[1007] = "bg" |
---|
57 | hashRecordingWords[1008] = "depth" |
---|
58 | hashRecordingWords[1100] = "shutdown" |
---|
59 | |
---|
60 | |
---|
61 | |
---|
62 | ############################################################################ |
---|
63 | # |
---|
64 | # CLASS: SessionRecorder |
---|
65 | # |
---|
66 | # DESCRIPTION: A class for recording user actions to a file in a directory. |
---|
67 | # To use just create an instance of this class and call |
---|
68 | # RecordAction(code, data) to write a line to a file. "Code" is |
---|
69 | # the message code sent to SAGE and it gets mapped to a text string |
---|
70 | # according to a hash defined here. "Data" is whatever you want to |
---|
71 | # record in a file and it's code dependent. The last (optional) |
---|
72 | # parameter to RecordAction is a boolean signifying if you want |
---|
73 | # to insert a pause after an action. By default pauses are inserted |
---|
74 | # in the file with the value of the elapsed time since the last |
---|
75 | # recorded message. When done recording, just call Close() and |
---|
76 | # destroy the object. |
---|
77 | # |
---|
78 | # DATE: April, 2005 |
---|
79 | # |
---|
80 | ############################################################################ |
---|
81 | |
---|
82 | class SessionRecorder: |
---|
83 | |
---|
84 | def __init__(self, filename): |
---|
85 | #recDir = ConvertPath("./sessions/") # a dir to write session files to |
---|
86 | print "Session recording started. Session will be saved in ", filename |
---|
87 | self.file = open(filename, "w") |
---|
88 | |
---|
89 | self.prevTime = 0 |
---|
90 | |
---|
91 | |
---|
92 | |
---|
93 | def RecordAction(self, code, data, insertPause = True): |
---|
94 | # only record if the action is supported |
---|
95 | if hashRecordingWords.has_key( int(code) ): |
---|
96 | #record a pause, a session always starts with a 2 second pause! |
---|
97 | if self.prevTime == 0: |
---|
98 | pauseTime = 2 |
---|
99 | else: |
---|
100 | pauseTime = int( ceil( time.time() - self.prevTime ) ) |
---|
101 | self.prevTime = time.time() |
---|
102 | |
---|
103 | # finally, do the writing |
---|
104 | if insertPause: |
---|
105 | self.file.write("pause " + str(pauseTime) + "\n") |
---|
106 | self.file.write( hashRecordingWords[int(code)] +" "+ data+"\n" ) |
---|
107 | |
---|
108 | |
---|
109 | def Close(self): |
---|
110 | print "Session recording stopped" |
---|
111 | self.file.close() |
---|
112 | |
---|
113 | |
---|
114 | |
---|
115 | |
---|
116 | |
---|
117 | ############################################################################ |
---|
118 | # |
---|
119 | # CLASS: SessionReader |
---|
120 | # |
---|
121 | # DESCRIPTION: A class for reading user actions from a specified file. |
---|
122 | # To use, create an instance of this class and pass in a |
---|
123 | # filename to read from. Once created, keep calling ReadAction() |
---|
124 | # in order to read new lines from a file. It returns data in this |
---|
125 | # format: (code, data). It is your job to use this data then. |
---|
126 | # This class also has options to pause, stop, speed up and slow |
---|
127 | # down the playback of the session file. It does all that by |
---|
128 | # playing with the length of the pause (speed), repeatedly |
---|
129 | # returning pause (pausing) or returning a fake EOF (stopping |
---|
130 | # prematurely). |
---|
131 | # |
---|
132 | # |
---|
133 | # DATE: April, 2005 |
---|
134 | # |
---|
135 | ############################################################################ |
---|
136 | |
---|
137 | class SessionReader: |
---|
138 | |
---|
139 | def __init__(self, sessionFile): |
---|
140 | if os.path.isfile(sessionFile): |
---|
141 | self.file = open(sessionFile, "r") |
---|
142 | else: |
---|
143 | print "ERROR: Sessions file \"", sessionFile, "\" doesn't exist." |
---|
144 | |
---|
145 | # control variables |
---|
146 | self.speed = 1.0 |
---|
147 | self.stop = False |
---|
148 | self.paused = False |
---|
149 | |
---|
150 | |
---|
151 | def SetPause(self, doPause): |
---|
152 | self.paused = doPause |
---|
153 | |
---|
154 | |
---|
155 | |
---|
156 | # reads the file one line at a time |
---|
157 | # it returns a tuple in this format: (code, data) |
---|
158 | # code is: -1 (paused), 0 (EOF) or **** (message code) |
---|
159 | # data depends on the message |
---|
160 | def ReadAction(self): |
---|
161 | |
---|
162 | # this is how we stop session playback |
---|
163 | if self.stop: |
---|
164 | return (0, "EOF") |
---|
165 | |
---|
166 | # this is how we pause playback (pretend that we encountered a |
---|
167 | # pause in the file. Instead we are pausing the playback and |
---|
168 | # not reading any new lines from the file |
---|
169 | if self.paused: |
---|
170 | return (-1, "0.5") |
---|
171 | |
---|
172 | if not hasattr(self, "file"): |
---|
173 | return |
---|
174 | |
---|
175 | line = self.file.readline() |
---|
176 | line = string.strip(line) # remove the newline char |
---|
177 | |
---|
178 | # EOF case |
---|
179 | if line == "": |
---|
180 | self.file.close() |
---|
181 | return (0, "EOF") |
---|
182 | |
---|
183 | # split the line into action and data |
---|
184 | action, data = string.split(line, " ", 1) |
---|
185 | |
---|
186 | # send a pause separately |
---|
187 | if action == "pause": |
---|
188 | data = float(data) * self.speed |
---|
189 | return (-1, str(data)) |
---|
190 | |
---|
191 | # this is the actual message to be sent to SAGE |
---|
192 | code = -2 # any non-existent code |
---|
193 | for k, v in hashRecordingWords.iteritems(): |
---|
194 | if v == action: |
---|
195 | code = int(k) #extract the code |
---|
196 | return (code, data) |
---|
197 | |
---|
198 | |
---|
199 | |
---|
200 | def GoSlower(self): |
---|
201 | self.speed = self.speed * 2 |
---|
202 | if self.speed > 8: |
---|
203 | self.speed = 8 |
---|
204 | |
---|
205 | if self.speed > 1: |
---|
206 | return "1/" + str(int(self.speed)) |
---|
207 | elif self.speed == 1: |
---|
208 | return "1" |
---|
209 | elif self.speed < 1: |
---|
210 | return str(int((0.125 / self.speed) * 8)) |
---|
211 | |
---|
212 | |
---|
213 | def GoFaster(self): |
---|
214 | self.speed = float(self.speed) / 2 |
---|
215 | |
---|
216 | if self.speed < 0.125: |
---|
217 | self.speed = 0.125 |
---|
218 | |
---|
219 | if self.speed > 1: |
---|
220 | return "1/" + str(int(self.speed)) |
---|
221 | elif self.speed == 1: |
---|
222 | return "1" |
---|
223 | elif self.speed < 1: |
---|
224 | return str(int((0.125 / self.speed) * 8)) |
---|
225 | |
---|
226 | |
---|
227 | def Stop(self): |
---|
228 | self.stop = True |
---|
229 | |
---|
230 | def Close(self): |
---|
231 | if hasattr(self, "file"): |
---|
232 | self.file.close() |
---|
233 | |
---|
234 | |
---|
235 | |
---|
236 | |
---|
237 | |
---|
238 | ############################################################################ |
---|
239 | # |
---|
240 | # CLASS: PlaybackDialog |
---|
241 | # |
---|
242 | # DESCRIPTION: This class describes the dialog box that controls the session |
---|
243 | # playback. It's a modal dialog so the UI can't be interacted |
---|
244 | # with during the playback. However, you can speed up or slow |
---|
245 | # down the playback, pause it or quit it prematurely. |
---|
246 | # |
---|
247 | # NOTE: This dialog is not neccessary for the operation of playback |
---|
248 | # but it gives user more control over it. |
---|
249 | # |
---|
250 | # DATE: April, 2005 |
---|
251 | # |
---|
252 | ############################################################################ |
---|
253 | |
---|
254 | class PlaybackDialog(wx.Dialog): |
---|
255 | |
---|
256 | def __init__(self, parent, sessionReader): |
---|
257 | wx.Dialog.__init__(self, parent, -1, "Session Playback")#, style = wx.STATIC_BORDER) |
---|
258 | self.sessionReader = sessionReader |
---|
259 | self.SetSize((250, 120)) |
---|
260 | self.SetBackgroundColour(wx.Colour(0, 51, 52)) #003334 |
---|
261 | self.SetForegroundColour(wx.Colour(255, 255, 255)) |
---|
262 | self.CenterOnParent(wx.BOTH) |
---|
263 | self.SetFont(StandardFont()) |
---|
264 | wx.StaticText(self, -1, "Playback in progress...", (50, 20)) |
---|
265 | #self.CaptureMouse() |
---|
266 | self.pause = False |
---|
267 | |
---|
268 | # create the controls for the dialog |
---|
269 | self.pauseBtn = MyBitmapButton( self, (30, 50), (30, 30), "images/pause_up.jpg", "images/pause_down.jpg", "images/pause_over.jpg") |
---|
270 | self.stopBtn = MyBitmapButton( self, (70, 50), (30, 30), "images/stop_up.jpg", "images/stop_down.jpg", "images/stop_over.jpg") |
---|
271 | self.slowerBtn = MyBitmapButton( self, (135, 50), (30, 30), "images/slower_up.jpg", "images/slower_down.jpg", "images/slower_over.jpg") |
---|
272 | self.speedText = wx.StaticText( self, -1, "x 1", (172, 60), style = wx.ALIGN_CENTRE) |
---|
273 | self.fasterBtn = MyBitmapButton( self, (208, 50), (30, 30), "images/faster_up.jpg", "images/faster_down.jpg", "images/faster_over.jpg") |
---|
274 | |
---|
275 | # bind the events and event handlers for the buttons |
---|
276 | self.pauseBtn.Bind( wx.EVT_LEFT_UP, self.OnPause) |
---|
277 | self.stopBtn.Bind( wx.EVT_LEFT_UP, self.OnClose) |
---|
278 | self.fasterBtn.Bind( wx.EVT_LEFT_UP, self.OnFaster) |
---|
279 | self.slowerBtn.Bind( wx.EVT_LEFT_UP, self.OnSlower) |
---|
280 | self.Bind( wx.EVT_CLOSE, self.OnClose) |
---|
281 | |
---|
282 | self.Show() |
---|
283 | |
---|
284 | |
---|
285 | def OnPause(self, evt): |
---|
286 | if self.pause: |
---|
287 | self.pauseBtn.SetUpBitmap("images/pause_up.jpg") |
---|
288 | self.pauseBtn.SetDownBitmap("images/pause_down.jpg") |
---|
289 | self.pauseBtn.SetOverBitmap("images/pause_over.jpg") |
---|
290 | self.pause = False |
---|
291 | self.sessionReader.SetPause(False) |
---|
292 | else: |
---|
293 | self.pauseBtn.SetUpBitmap("images/play_up.jpg") |
---|
294 | self.pauseBtn.SetDownBitmap("images/play_down.jpg") |
---|
295 | self.pauseBtn.SetOverBitmap("images/play_over.jpg") |
---|
296 | self.pause = True |
---|
297 | self.sessionReader.SetPause(True) |
---|
298 | |
---|
299 | MyBitmapButton.OnLeftUp(evt.GetEventObject(), evt) |
---|
300 | |
---|
301 | |
---|
302 | # this just hides the window since sageGate will close it when it's done |
---|
303 | # it also tells the sessionReader to stop the playback |
---|
304 | def OnClose(self, evt): |
---|
305 | self.sessionReader.Stop() |
---|
306 | #self.Destroy() |
---|
307 | self.Show(False) # SAGEGate is the one that destroys the window so just hide it for now |
---|
308 | |
---|
309 | |
---|
310 | # sageGate calls this function when EOF has been reached |
---|
311 | def Close(self): |
---|
312 | #self.ReleaseMouse() |
---|
313 | self.Destroy() |
---|
314 | |
---|
315 | |
---|
316 | # this calls SessionReader and sets the speed of playback (basically it |
---|
317 | # changes the multiplier of the pauses) |
---|
318 | def OnFaster(self, evt): |
---|
319 | MyBitmapButton.OnLeftUp(evt.GetEventObject(), evt) |
---|
320 | newSpeed = self.sessionReader.GoFaster() |
---|
321 | self.speedText.SetLabel("x " + newSpeed) |
---|
322 | |
---|
323 | |
---|
324 | # this calls SessionReader and sets the speed of playback (basically it |
---|
325 | # changes the multiplier of the pauses) |
---|
326 | def OnSlower(self, evt): |
---|
327 | MyBitmapButton.OnLeftUp(evt.GetEventObject(), evt) |
---|
328 | newSpeed = self.sessionReader.GoSlower() |
---|
329 | self.speedText.SetLabel("x " + newSpeed) |
---|
330 | |
---|
331 | |
---|
332 | |
---|
333 | def ShowLoadSessionDialog(): |
---|
334 | if not os.path.isdir("sessions"): #make the directory if it doesnt exist |
---|
335 | try: #in case the file and directory permissions are not right |
---|
336 | os.mkdir("sessions") |
---|
337 | except: |
---|
338 | dlg = wx.FileDialog(None, "Session to load:", os.path.abspath("."), "", "*.ses", style=wx.OPEN ) |
---|
339 | else: |
---|
340 | dlg = wx.FileDialog(None, "Session to load:", os.path.abspath("sessions"), "", "*.ses", style=wx.OPEN ) |
---|
341 | else: |
---|
342 | dlg = wx.FileDialog(None, "Session to load:", os.path.abspath("sessions"), "", "*.ses", style=wx.OPEN ) |
---|
343 | |
---|
344 | |
---|
345 | if dlg.ShowModal() == wx.ID_OK: |
---|
346 | if "__WXMSW__" in wx.PlatformInfo: |
---|
347 | chosenFile = ConvertPath(dlg.GetPath()) |
---|
348 | elif not ConvertPath(dlg.GetPath())[0] == "/": |
---|
349 | chosenFile = "/"+ConvertPath(dlg.GetPath()) |
---|
350 | else: |
---|
351 | chosenFile = ConvertPath(dlg.GetPath()) |
---|
352 | |
---|
353 | #make sure that the extension is ".ses" |
---|
354 | (path, ext) = os.path.splitext(chosenFile) |
---|
355 | if not ext == ".ses": |
---|
356 | chosenFile = chosenFile+".ses" |
---|
357 | |
---|
358 | else: |
---|
359 | chosenFile = None |
---|
360 | dlg.Destroy() |
---|
361 | return chosenFile |
---|
362 | |
---|
363 | |
---|
364 | def ShowSaveSessionDialog(): |
---|
365 | if not os.path.isdir("sessions"): #make the directory if it doesnt exist |
---|
366 | try: #in case the file and directory permissions are not right |
---|
367 | os.mkdir("sessions") |
---|
368 | except: |
---|
369 | dlg = wx.FileDialog(None, "Save session as:", os.path.abspath("."), "", "*.ses", style=wx.SAVE | wx.OVERWRITE_PROMPT ) |
---|
370 | else: |
---|
371 | dlg = wx.FileDialog(None, "Save session as:", os.path.abspath("sessions"), "", "*.ses", style=wx.SAVE | wx.OVERWRITE_PROMPT ) |
---|
372 | else: |
---|
373 | dlg = wx.FileDialog(None, "Save session as:", os.path.abspath("sessions"), "", "*.ses", style=wx.SAVE | wx.OVERWRITE_PROMPT ) |
---|
374 | |
---|
375 | |
---|
376 | if dlg.ShowModal() == wx.ID_OK: |
---|
377 | if "__WXMSW__" in wx.PlatformInfo: |
---|
378 | chosenFile = ConvertPath(dlg.GetPath()) |
---|
379 | elif not ConvertPath(dlg.GetPath())[0] == "/": |
---|
380 | chosenFile = "/"+ConvertPath(dlg.GetPath()) |
---|
381 | else: |
---|
382 | chosenFile = ConvertPath(dlg.GetPath()) |
---|
383 | |
---|
384 | #make sure that the extension is ".ses" |
---|
385 | (path, ext) = os.path.splitext(chosenFile) |
---|
386 | if not ext == ".ses": |
---|
387 | chosenFile = chosenFile+".ses" |
---|
388 | |
---|
389 | # check if we have a permission to write to this folder |
---|
390 | (head, tail) = os.path.split(chosenFile) |
---|
391 | if not os.access(head, os.W_OK): |
---|
392 | ShowWriteFailedDialog(tail) |
---|
393 | chosenFile = None |
---|
394 | else: |
---|
395 | chosenFile = None |
---|
396 | dlg.Destroy() |
---|
397 | return chosenFile |
---|
398 | |
---|
399 | |
---|
400 | |
---|
401 | |
---|