]> git.saurik.com Git - wxWidgets.git/blame_incremental - wxPython/wx/lib/infoframe.py
"wxWindows" --> "wxWidgets"
[wxWidgets.git] / wxPython / wx / lib / infoframe.py
... / ...
CommitLineData
1# 12/20/2003 - Jeff Grimmett (grimmtooth@softhome.net)
2#
3# o wxPyInformationalMessagesFrame -> PyInformationalMessagesFrame
4# o dummy_wxPyInformationalMessagesFrame -> dummy_PyInformationalMessagesFrame
5#
6
7"""
8infoframe.py
9Released under wxWindows license etc.
10
11This is a fairly rudimentary, but slightly fancier tha
12wxPyOnDemandOutputWindow (on which it's based; thanks Robin), version
13of the same sort of thing: a file-like class called
14wxInformationalMessagesFrame. This window also has a status bar with a
15couple of buttons for controlling the echoing of all output to a file
16with a randomly-chosen filename...
17
18The class behaves similarly to wxPyOnDemandOutputWindow in that (at
19least by default) the frame does not appear until written to, but is
20somewhat different in that, either under programmatic (the
21DisableOutput method) or user (the frame's close button, it's status
22bar's "Dismiss" button, or a "Disable output" item of some menu,
23perhaps of some other frame), the frame will be destroyed, an
24associated file closed, and writing to it will then do nothing. This
25can be reversed: either under programmatic (the EnableOutput method)
26or user (an "Enable output" item of some menu), a new frame will be
27opened,And an associated file (with a "randomly"selected filename)
28opened for writing [to which all subsequent displayed messages will be
29echoed].
30
31Please note that, like wxPyOnDemandOutputWindow, the instance is not
32itself a subclass of wxWindow: when the window is open (and ONLY
33then), it's "frame" attribute is the actual instance of wFrame...
34
35Typical usage:
36 from wxPython.lib.infoframe import *
37 ... # ... modify your wxApp as follows:
38 class myApp(wxApp):
39 outputWindowClass = PyInformationalMessagesFrame
40 ...
41If you're running on Linux, you'll also have to supply an argument 1 to your
42constructor of myApp to redirect stdout/stderr to this window (it's done
43automatically for you on Windows).
44
45If you don't want to redirect stdout/stderr, but use the class directly: do
46it this way:
47
48 InformationalMessagesFrame = PyInformationalMessagesFrame\
49 ([options from progname (default ""),
50 txt (default "informational
51 messages"])
52#^^^^ early in the program
53...
54InformationalMessagesFrame([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
60The latter statement, of course, may be repeated arbitrarily often.
61The window will not appear until it is written to, and it may be
62manually closed by the user, after which it will reappear again until
63written to... Also note that all output is echoed to a file with a
64randomly-generated name [see the mktemp module in the standard
65library], in the directory given as the 'dir' keyword argument to the
66InformationalMessagesFrame constructor [which has a default value of
67'.'), or set via the method SetOutputDirectory... This file will be
68closed with the window--a new one will be created [by default] upon
69each subsequent reopening.
70
71Please also note the methods EnableOutput and DisableOutput, and the
72possible arguments for the constructor in the code below... (* TO DO:
73explain this here...*) Neither of these methods need be used at all,
74and in this case the frame will only be displayed once it has been
75written to, like wxPyOnDemandOutputWindow.
76
77The former, EnableOutput, displays the frame with an introductory
78message, opens a random file to which future displayed output also
79goes (unless the nofile attribute is present), and sets the __debug__
80variable of each module to 1 (unless the no __debug__ attribute is
81present]. 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
87without worrying about the overhead of evaluating the arguments, and
88calling the wxInformationalMessagesFrame instance, in the case where
89debugging is not turned on. (This won't happen if the instance has an
90attribute no__debug__; you can arrange this by sub-classing...)
91
92"Debug mode" can also be turned on by selecting the item-"Enable
93output" from the "Debug" menu [the default--see the optional arguments
94to the SetOtherMenuBar method] of a frame which has been either passed
95appropriately to the constructor of the wxInformationalMessagesFrame
96(see the code), or set via the SetOtherMenuBar method thereof. This
97also writes an empty string to the instance, meaning that the frame
98will open (unless DisablOutput has been called) with an appropriate
99introductory message (which will vary according to whether the
100instance/class has the "no __debug__" attribute)^ I have found this to
101be an extremely useful tool, in lieu of a full wxPython debugger...
102
103Following this, the menu item is also disabled, and an item "Disable
104output" (again, by default) is enabled. Note that these need not be
105done: e.g., you don't NEED to have a menu with appropriate items; in
106this case simply do not call the SetOtherMenuBar method or use the
107othermenubar keyword argument of the class instance constructor.
108
109The DisableOutput method does the reverse of this; it closes the
110window (and associated file), and sets the __debug__ variable of each
111module whose name begins with a capital letter {this happens to be the
112author's personal practice; all my python module start with capital
113letters} to 0. It also enables/disabled the appropriate menu items,
114if this was done previously (or SetOtherMenuBar has been called...).
115Note too that after a call to DisableOutput, nothing further will be
116done upon subsequent write()'s, until the EnableOutput method is
117called, either explicitly or by the menu selection above...
118
119Finally, note that the file-like method close() destroys the window
120(and closes any associated file) and there is a file-like method
121write() which displays it's argument.
122
123All (well, most) of this is made clear by the example code at the end
124of this file, which is run if the file is run by itself; otherwise,
125see the appropriate "stub" file in the wxPython demo.
126
127"""
128
129import os
130import sys
131import tempfile
132
133import wx
134
135class _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
201class 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
465class 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