]>
Commit | Line | Data |
---|---|---|
1 | # 12/20/2003 - Jeff Grimmett (grimmtooth@softhome.net) | |
2 | # | |
3 | # o wxPyInformationalMessagesFrame -> PyInformationalMessagesFrame | |
4 | # o dummy_wxPyInformationalMessagesFrame -> dummy_PyInformationalMessagesFrame | |
5 | # | |
6 | ||
7 | """ | |
8 | infoframe.py | |
9 | Released under wxWindows license etc. | |
10 | ||
11 | This is a fairly rudimentary, but slightly fancier tha | |
12 | wxPyOnDemandOutputWindow (on which it's based; thanks Robin), version | |
13 | of the same sort of thing: a file-like class called | |
14 | wxInformationalMessagesFrame. This window also has a status bar with a | |
15 | couple of buttons for controlling the echoing of all output to a file | |
16 | with a randomly-chosen filename... | |
17 | ||
18 | The class behaves similarly to wxPyOnDemandOutputWindow in that (at | |
19 | least by default) the frame does not appear until written to, but is | |
20 | somewhat different in that, either under programmatic (the | |
21 | DisableOutput method) or user (the frame's close button, it's status | |
22 | bar's "Dismiss" button, or a "Disable output" item of some menu, | |
23 | perhaps of some other frame), the frame will be destroyed, an | |
24 | associated file closed, and writing to it will then do nothing. This | |
25 | can be reversed: either under programmatic (the EnableOutput method) | |
26 | or user (an "Enable output" item of some menu), a new frame will be | |
27 | opened,And an associated file (with a "randomly"selected filename) | |
28 | opened for writing [to which all subsequent displayed messages will be | |
29 | echoed]. | |
30 | ||
31 | Please note that, like wxPyOnDemandOutputWindow, the instance is not | |
32 | itself a subclass of wxWindow: when the window is open (and ONLY | |
33 | then), it's "frame" attribute is the actual instance of wFrame... | |
34 | ||
35 | Typical usage: | |
36 | from wxPython.lib.infoframe import * | |
37 | ... # ... modify your wxApp as follows: | |
38 | class myApp(wxApp): | |
39 | outputWindowClass = PyInformationalMessagesFrame | |
40 | ... | |
41 | If you're running on Linux, you'll also have to supply an argument 1 to your | |
42 | constructor of myApp to redirect stdout/stderr to this window (it's done | |
43 | automatically for you on Windows). | |
44 | ||
45 | If you don't want to redirect stdout/stderr, but use the class directly: do | |
46 | it this way: | |
47 | ||
48 | InformationalMessagesFrame = PyInformationalMessagesFrame\ | |
49 | ([options from progname (default ""), | |
50 | txt (default "informational | |
51 | messages"]) | |
52 | #^^^^ early in the program | |
53 | ... | |
54 | InformationalMessagesFrame([comma-separated list of items to | |
55 | display. Note that these will never | |
56 | be separated by spaces as they may | |
57 | be when used in the Python 'print' | |
58 | command]) | |
59 | ||
60 | The latter statement, of course, may be repeated arbitrarily often. | |
61 | The window will not appear until it is written to, and it may be | |
62 | manually closed by the user, after which it will reappear again until | |
63 | written to... Also note that all output is echoed to a file with a | |
64 | randomly-generated name [see the mktemp module in the standard | |
65 | library], in the directory given as the 'dir' keyword argument to the | |
66 | InformationalMessagesFrame constructor [which has a default value of | |
67 | '.'), or set via the method SetOutputDirectory... This file will be | |
68 | closed with the window--a new one will be created [by default] upon | |
69 | each subsequent reopening. | |
70 | ||
71 | Please also note the methods EnableOutput and DisableOutput, and the | |
72 | possible arguments for the constructor in the code below... (* TO DO: | |
73 | explain this here...*) Neither of these methods need be used at all, | |
74 | and in this case the frame will only be displayed once it has been | |
75 | written to, like wxPyOnDemandOutputWindow. | |
76 | ||
77 | The former, EnableOutput, displays the frame with an introductory | |
78 | message, opens a random file to which future displayed output also | |
79 | goes (unless the nofile attribute is present), and sets the __debug__ | |
80 | variable of each module to 1 (unless the no __debug__ attribute is | |
81 | present]. This is so that you can say, in any module whatsoever, | |
82 | ||
83 | if __debug__: | |
84 | InformationalMessagesFrame("... with lots of %<Character> constructs" | |
85 | % TUPLE) | |
86 | ||
87 | without worrying about the overhead of evaluating the arguments, and | |
88 | calling the wxInformationalMessagesFrame instance, in the case where | |
89 | debugging is not turned on. (This won't happen if the instance has an | |
90 | attribute no__debug__; you can arrange this by sub-classing...) | |
91 | ||
92 | "Debug mode" can also be turned on by selecting the item-"Enable | |
93 | output" from the "Debug" menu [the default--see the optional arguments | |
94 | to the SetOtherMenuBar method] of a frame which has been either passed | |
95 | appropriately to the constructor of the wxInformationalMessagesFrame | |
96 | (see the code), or set via the SetOtherMenuBar method thereof. This | |
97 | also writes an empty string to the instance, meaning that the frame | |
98 | will open (unless DisablOutput has been called) with an appropriate | |
99 | introductory message (which will vary according to whether the | |
100 | instance/class has the "no __debug__" attribute)^ I have found this to | |
101 | be an extremely useful tool, in lieu of a full wxPython debugger... | |
102 | ||
103 | Following this, the menu item is also disabled, and an item "Disable | |
104 | output" (again, by default) is enabled. Note that these need not be | |
105 | done: e.g., you don't NEED to have a menu with appropriate items; in | |
106 | this case simply do not call the SetOtherMenuBar method or use the | |
107 | othermenubar keyword argument of the class instance constructor. | |
108 | ||
109 | The DisableOutput method does the reverse of this; it closes the | |
110 | window (and associated file), and sets the __debug__ variable of each | |
111 | module whose name begins with a capital letter {this happens to be the | |
112 | author's personal practice; all my python module start with capital | |
113 | letters} to 0. It also enables/disabled the appropriate menu items, | |
114 | if this was done previously (or SetOtherMenuBar has been called...). | |
115 | Note too that after a call to DisableOutput, nothing further will be | |
116 | done upon subsequent write()'s, until the EnableOutput method is | |
117 | called, either explicitly or by the menu selection above... | |
118 | ||
119 | Finally, note that the file-like method close() destroys the window | |
120 | (and closes any associated file) and there is a file-like method | |
121 | write() which displays it's argument. | |
122 | ||
123 | All (well, most) of this is made clear by the example code at the end | |
124 | of this file, which is run if the file is run by itself; otherwise, | |
125 | see the appropriate "stub" file in the wxPython demo. | |
126 | ||
127 | """ | |
128 | ||
129 | import os | |
130 | import sys | |
131 | import tempfile | |
132 | ||
133 | import wx | |
134 | ||
135 | class _MyStatusBar(wx.StatusBar): | |
136 | def __init__(self, parent, callbacks=None, useopenbutton=0): | |
137 | wx.StatusBar.__init__(self, parent, -1, style=wx.TAB_TRAVERSAL) | |
138 | self.SetFieldsCount(3) | |
139 | ||
140 | self.SetStatusText("",0) | |
141 | ||
142 | self.button1 = wx.Button(self, -1, "Dismiss", style=wx.TAB_TRAVERSAL) | |
143 | self.Bind(wx.EVT_BUTTON, self.OnButton1, self.button1) | |
144 | ||
145 | if not useopenbutton: | |
146 | self.button2 = wx.Button(self, -1, "Close File", style=wx.TAB_TRAVERSAL) | |
147 | else: | |
148 | self.button2 = wx.Button(self, -1, "Open New File", style=wx.TAB_TRAVERSAL) | |
149 | ||
150 | self.Bind(wx.EVT_BUTTON, self.OnButton2, self.button2) | |
151 | self.useopenbutton = useopenbutton | |
152 | self.callbacks = callbacks | |
153 | ||
154 | # figure out how tall to make the status bar | |
155 | dc = wx.ClientDC(self) | |
156 | dc.SetFont(self.GetFont()) | |
157 | (w,h) = dc.GetTextExtent('X') | |
158 | h = int(h * 1.8) | |
159 | self.SetSize((100, h)) | |
160 | self.OnSize("dummy") | |
161 | self.Bind(wx.EVT_SIZE, self.OnSize) | |
162 | ||
163 | # reposition things... | |
164 | def OnSize(self, event): | |
165 | self.CalculateSizes() | |
166 | rect = self.GetFieldRect(1) | |
167 | self.button1.SetPosition((rect.x+5, rect.y+2)) | |
168 | self.button1.SetSize((rect.width-10, rect.height-4)) | |
169 | rect = self.GetFieldRect(2) | |
170 | self.button2.SetPosition((rect.x+5, rect.y+2)) | |
171 | self.button2.SetSize((rect.width-10, rect.height-4)) | |
172 | ||
173 | # widths........ | |
174 | def CalculateSizes(self): | |
175 | dc = wx.ClientDC(self.button1) | |
176 | dc.SetFont(self.button1.GetFont()) | |
177 | (w1,h) = dc.GetTextExtent(self.button1.GetLabel()) | |
178 | ||
179 | dc = wx.ClientDC(self.button2) | |
180 | dc.SetFont(self.button2.GetFont()) | |
181 | (w2,h) = dc.GetTextExtent(self.button2.GetLabel()) | |
182 | ||
183 | self.SetStatusWidths([-1,w1+15,w2+15]) | |
184 | ||
185 | def OnButton1(self,event): | |
186 | self.callbacks[0] () | |
187 | ||
188 | def OnButton2(self,event): | |
189 | if self.useopenbutton and self.callbacks[2] (): | |
190 | self.button2.SetLabel ("Close File") | |
191 | elif self.callbacks[1] (): | |
192 | self.button2.SetLabel ("Open New File") | |
193 | ||
194 | self.useopenbutton = 1 - self.useopenbutton | |
195 | self.OnSize("") | |
196 | self.button2.Refresh(True) | |
197 | self.Refresh() | |
198 | ||
199 | ||
200 | ||
201 | class PyInformationalMessagesFrame: | |
202 | def __init__(self, | |
203 | progname="", | |
204 | text="informational messages", | |
205 | dir='.', | |
206 | menuname="Debug", | |
207 | enableitem="Enable output", | |
208 | disableitem="Disable output", | |
209 | othermenubar=None): | |
210 | ||
211 | self.SetOtherMenuBar(othermenubar, | |
212 | menuname=menuname, | |
213 | enableitem=enableitem, | |
214 | disableitem=disableitem) | |
215 | ||
216 | if hasattr(self,"othermenu") and self.othermenu is not None: | |
217 | i = self.othermenu.FindMenuItem(self.menuname,self.disableitem) | |
218 | self.othermenu.Enable(i,0) | |
219 | i = self.othermenu.FindMenuItem(self.menuname,self.enableitem) | |
220 | self.othermenu.Enable(i,1) | |
221 | ||
222 | self.frame = None | |
223 | self.title = "%s %s" % (progname,text) | |
224 | self.parent = None # use the SetParent method if desired... | |
225 | self.softspace = 1 # of rather limited use | |
226 | ||
227 | if dir: | |
228 | self.SetOutputDirectory(dir) | |
229 | ||
230 | ||
231 | def SetParent(self, parent): | |
232 | self.parent = parent | |
233 | ||
234 | ||
235 | def SetOtherMenuBar(self, | |
236 | othermenu, | |
237 | menuname="Debug", | |
238 | enableitem="Enable output", | |
239 | disableitem="Disable output"): | |
240 | self.othermenu = othermenu | |
241 | self.menuname = menuname | |
242 | self.enableitem = enableitem | |
243 | self.disableitem = disableitem | |
244 | ||
245 | ||
246 | f = None | |
247 | ||
248 | ||
249 | def write(self, string): | |
250 | if not wx.Thread_IsMain(): | |
251 | # Aquire the GUI mutex before making GUI calls. Mutex is released | |
252 | # when locker is deleted at the end of this function. | |
253 | # | |
254 | # TODO: This should be updated to use wx.CallAfter similarly to how | |
255 | # PyOnDemandOutputWindow.write was so it is not necessary | |
256 | # to get the gui mutex | |
257 | locker = wx.MutexGuiLocker() | |
258 | ||
259 | if self.Enabled: | |
260 | if self.f: | |
261 | self.f.write(string) | |
262 | self.f.flush() | |
263 | ||
264 | move = 1 | |
265 | if (hasattr(self,"text") | |
266 | and self.text is not None | |
267 | and self.text.GetInsertionPoint() != self.text.GetLastPosition()): | |
268 | move = 0 | |
269 | ||
270 | if not self.frame: | |
271 | self.frame = wx.Frame(self.parent, -1, self.title, size=(450, 300), | |
272 | style=wx.DEFAULT_FRAME_STYLE|wx.NO_FULL_REPAINT_ON_RESIZE) | |
273 | ||
274 | self.text = wx.TextCtrl(self.frame, -1, "", | |
275 | style = wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_RICH) | |
276 | ||
277 | self.frame.sb = _MyStatusBar(self.frame, | |
278 | callbacks=[self.DisableOutput, | |
279 | self.CloseFile, | |
280 | self.OpenNewFile], | |
281 | useopenbutton=hasattr(self, | |
282 | "nofile")) | |
283 | self.frame.SetStatusBar(self.frame.sb) | |
284 | self.frame.Show(True) | |
285 | self.frame.Bind(wx.EVT_CLOSE, self.OnCloseWindow) | |
286 | ||
287 | if hasattr(self,"nofile"): | |
288 | self.text.AppendText( | |
289 | "Please close this window (or select the " | |
290 | "'Dismiss' button below) when desired. By " | |
291 | "default all messages written to this window " | |
292 | "will NOT be written to a file--you " | |
293 | "may change this by selecting 'Open New File' " | |
294 | "below, allowing you to select a " | |
295 | "new file...\n\n") | |
296 | else: | |
297 | tempfile.tempdir = self.dir | |
298 | filename = os.path.abspath(tempfile.mktemp ()) | |
299 | ||
300 | self.text.AppendText( | |
301 | "Please close this window (or select the " | |
302 | "'Dismiss' button below) when desired. By " | |
303 | "default all messages written to this window " | |
304 | "will also be written to the file '%s'--you " | |
305 | "may close this file by selecting 'Close " | |
306 | "File' below, whereupon this button will be " | |
307 | "replaced with one allowing you to select a " | |
308 | "new file...\n\n" % filename) | |
309 | try: | |
310 | self.f = open(filename, 'w') | |
311 | self.frame.sb.SetStatusText("File '%s' opened..." | |
312 | % filename, | |
313 | 0) | |
314 | except EnvironmentError: | |
315 | self.frame.sb.SetStatusText("File creation failed " | |
316 | "(filename '%s')..." | |
317 | % filename, | |
318 | 0) | |
319 | self.text.AppendText(string) | |
320 | ||
321 | if move: | |
322 | self.text.ShowPosition(self.text.GetLastPosition()) | |
323 | ||
324 | if not hasattr(self,"no__debug__"): | |
325 | for m in sys.modules.values(): | |
326 | if m is not None:# and m.__dict__.has_key("__debug__"): | |
327 | m.__dict__["__debug__"] = 1 | |
328 | ||
329 | if hasattr(self,"othermenu") and self.othermenu is not None: | |
330 | i = self.othermenu.FindMenuItem(self.menuname,self.disableitem) | |
331 | self.othermenu.Enable(i,1) | |
332 | i = self.othermenu.FindMenuItem(self.menuname,self.enableitem) | |
333 | self.othermenu.Enable(i,0) | |
334 | ||
335 | ||
336 | Enabled = 1 | |
337 | ||
338 | def OnCloseWindow(self, event, exiting=0): | |
339 | if self.f: | |
340 | self.f.close() | |
341 | self.f = None | |
342 | ||
343 | if (hasattr(self,"othermenu") and self.othermenu is not None | |
344 | and self.frame is not None | |
345 | and not exiting): | |
346 | ||
347 | i = self.othermenu.FindMenuItem(self.menuname,self.disableitem) | |
348 | self.othermenu.Enable(i,0) | |
349 | i = self.othermenu.FindMenuItem(self.menuname,self.enableitem) | |
350 | self.othermenu.Enable(i,1) | |
351 | ||
352 | if not hasattr(self,"no__debug__"): | |
353 | for m in sys.modules.values(): | |
354 | if m is not None:# and m.__dict__.has_key("__debug__"): | |
355 | m.__dict__["__debug__"] = 0 | |
356 | ||
357 | if self.frame is not None: # typically True, but, e.g., allows | |
358 | # DisableOutput method (which calls this | |
359 | # one) to be called when the frame is not | |
360 | # actually open, so that it is always safe | |
361 | # to call this method... | |
362 | frame = self.frame | |
363 | self.frame = self.text = None | |
364 | frame.Destroy() | |
365 | self.Enabled = 1 | |
366 | ||
367 | ||
368 | def EnableOutput(self, | |
369 | event=None,# event must be the first optional argument... | |
370 | othermenubar=None, | |
371 | menuname="Debug", | |
372 | enableitem="Enable output", | |
373 | disableitem="Disable output"): | |
374 | ||
375 | if othermenubar is not None: | |
376 | self.SetOtherMenuBar(othermenubar, | |
377 | menuname=menuname, | |
378 | enableitem=enableitem, | |
379 | disableitem=disableitem) | |
380 | self.Enabled = 1 | |
381 | if self.f: | |
382 | self.f.close() | |
383 | self.f = None | |
384 | self.write("") | |
385 | ||
386 | ||
387 | def CloseFile(self): | |
388 | if self.f: | |
389 | if self.frame: | |
390 | self.frame.sb.SetStatusText("File '%s' closed..." | |
391 | % os.path.abspath(self.f.name), | |
392 | 0) | |
393 | self.f.close () | |
394 | self.f = None | |
395 | else: | |
396 | if self.frame: | |
397 | self.frame.sb.SetStatusText("") | |
398 | if self.frame: | |
399 | self.frame.sb.Refresh() | |
400 | return 1 | |
401 | ||
402 | ||
403 | def OpenNewFile(self): | |
404 | self.CloseFile() | |
405 | dlg = wx.FileDialog(self.frame, | |
406 | "Choose a new log file", self.dir,"","*", | |
407 | wx.SAVE | wx.HIDE_READONLY | wx.OVERWRITE_PROMPT) | |
408 | if dlg.ShowModal() == wx.ID_CANCEL: | |
409 | dlg.Destroy() | |
410 | return 0 | |
411 | else: | |
412 | try: | |
413 | self.f = open(os.path.abspath(dlg.GetPath()),'w') | |
414 | except EnvironmentError: | |
415 | dlg.Destroy() | |
416 | return 0 | |
417 | dlg.Destroy() | |
418 | if self.frame: | |
419 | self.frame.sb.SetStatusText("File '%s' opened..." | |
420 | % os.path.abspath(self.f.name), | |
421 | 0) | |
422 | if hasattr(self,"nofile"): | |
423 | self.frame.sb = _MyStatusBar(self.frame, | |
424 | callbacks=[self.DisableOutput, | |
425 | self.CloseFile, | |
426 | self.OpenNewFile]) | |
427 | self.frame.SetStatusBar(self.frame.sb) | |
428 | if hasattr(self,"nofile"): | |
429 | delattr(self,"nofile") | |
430 | return 1 | |
431 | ||
432 | ||
433 | def DisableOutput(self, | |
434 | event=None,# event must be the first optional argument... | |
435 | exiting=0): | |
436 | self.write("<InformationalMessagesFrame>.DisableOutput()\n") | |
437 | if hasattr(self,"frame") \ | |
438 | and self.frame is not None: | |
439 | self.OnCloseWindow("Dummy",exiting=exiting) | |
440 | self.Enabled = 0 | |
441 | ||
442 | ||
443 | def close(self): | |
444 | self.DisableOutput() | |
445 | ||
446 | ||
447 | def flush(self): | |
448 | if self.text: | |
449 | self.text.SetInsertionPointEnd() | |
450 | wx.Yield() | |
451 | ||
452 | ||
453 | def __call__(self,* args): | |
454 | for s in args: | |
455 | self.write (str (s)) | |
456 | ||
457 | ||
458 | def SetOutputDirectory(self,dir): | |
459 | self.dir = os.path.abspath(dir) | |
460 | ## sys.__stderr__.write("Directory: os.path.abspath(%s) = %s\n" | |
461 | ## % (dir,self.dir)) | |
462 | ||
463 | ||
464 | ||
465 | class Dummy_PyInformationalMessagesFrame: | |
466 | def __init__(self,progname=""): | |
467 | self.softspace = 1 | |
468 | def __call__(self,*args): | |
469 | pass | |
470 | def write(self,s): | |
471 | pass | |
472 | def flush(self): | |
473 | pass | |
474 | def close(self): | |
475 | pass | |
476 | def EnableOutput(self): | |
477 | pass | |
478 | def __call__(self,* args): | |
479 | pass | |
480 | def DisableOutput(self,exiting=0): | |
481 | pass | |
482 | def SetParent(self,wX): | |
483 | pass | |
484 |