1 # 12/20/2003 - Jeff Grimmett (grimmtooth@softhome.net)
3 # o wxPyInformationalMessagesFrame -> PyInformationalMessagesFrame
4 # o dummy_wxPyInformationalMessagesFrame -> dummy_PyInformationalMessagesFrame
9 Released under wxWindows license etc.
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...
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
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...
36 from wxPython.lib.infoframe import *
37 ... # ... modify your wxApp as follows:
39 outputWindowClass = PyInformationalMessagesFrame
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).
45 If you don't want to redirect stdout/stderr, but use the class directly: do
48 InformationalMessagesFrame = PyInformationalMessagesFrame\
49 ([options from progname (default ""),
50 txt (default "informational
52 #^^^^ early in the program
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'
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.
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.
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,
84 InformationalMessagesFrame("... with lots of %<Character> constructs"
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...)
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...
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.
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...
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.
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.
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)
140 self
.SetStatusText("",0)
142 self
.button1
= wx
.Button(self
, -1, "Dismiss", style
=wx
.TAB_TRAVERSAL
)
143 self
.Bind(wx
.EVT_BUTTON
, self
.OnButton1
, self
.button1
)
145 if not useopenbutton
:
146 self
.button2
= wx
.Button(self
, -1, "Close File", style
=wx
.TAB_TRAVERSAL
)
148 self
.button2
= wx
.Button(self
, -1, "Open New File", style
=wx
.TAB_TRAVERSAL
)
150 self
.Bind(wx
.EVT_BUTTON
, self
.OnButton2
, self
.button2
)
151 self
.useopenbutton
= useopenbutton
152 self
.callbacks
= callbacks
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')
159 self
.SetSize((100, h
))
161 self
.Bind(wx
.EVT_SIZE
, self
.OnSize
)
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))
174 def CalculateSizes(self
):
175 dc
= wx
.ClientDC(self
.button1
)
176 dc
.SetFont(self
.button1
.GetFont())
177 (w1
,h
) = dc
.GetTextExtent(self
.button1
.GetLabel())
179 dc
= wx
.ClientDC(self
.button2
)
180 dc
.SetFont(self
.button2
.GetFont())
181 (w2
,h
) = dc
.GetTextExtent(self
.button2
.GetLabel())
183 self
.SetStatusWidths([-1,w1
+15,w2
+15])
185 def OnButton1(self
,event
):
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")
194 self
.useopenbutton
= 1 - self
.useopenbutton
196 self
.button2
.Refresh(True)
201 class PyInformationalMessagesFrame
:
204 text
="informational messages",
207 enableitem
="Enable output",
208 disableitem
="Disable output",
211 self
.SetOtherMenuBar(othermenubar
,
213 enableitem
=enableitem
,
214 disableitem
=disableitem
)
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)
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
228 self
.SetOutputDirectory(dir)
231 def SetParent(self
, parent
):
235 def SetOtherMenuBar(self
,
238 enableitem
="Enable output",
239 disableitem
="Disable output"):
240 self
.othermenu
= othermenu
241 self
.menuname
= menuname
242 self
.enableitem
= enableitem
243 self
.disableitem
= disableitem
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.
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()
265 if (hasattr(self
,"text")
266 and self
.text
is not None
267 and self
.text
.GetInsertionPoint() != self
.text
.GetLastPosition()):
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
)
274 self
.text
= wx
.TextCtrl(self
.frame
, -1, "",
275 style
= wx
.TE_MULTILINE|wx
.TE_READONLY|wx
.TE_RICH
)
277 self
.frame
.sb
= _MyStatusBar(self
.frame
,
278 callbacks
=[self
.DisableOutput
,
281 useopenbutton
=hasattr(self
,
283 self
.frame
.SetStatusBar(self
.frame
.sb
)
284 self
.frame
.Show(True)
285 self
.frame
.Bind(wx
.EVT_CLOSE
, self
.OnCloseWindow
)
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 "
297 tempfile
.tempdir
= self
.dir
298 filename
= os
.path
.abspath(tempfile
.mktemp ())
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
)
310 self
.f
= open(filename
, 'w')
311 self
.frame
.sb
.SetStatusText("File '%s' opened..."
314 except EnvironmentError:
315 self
.frame
.sb
.SetStatusText("File creation failed "
319 self
.text
.AppendText(string
)
322 self
.text
.ShowPosition(self
.text
.GetLastPosition())
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
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)
338 def OnCloseWindow(self
, event
, exiting
=0):
343 if (hasattr(self
,"othermenu") and self
.othermenu
is not None
344 and self
.frame
is not None
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)
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
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...
363 self
.frame
= self
.text
= None
368 def EnableOutput(self
,
369 event
=None,# event must be the first optional argument...
372 enableitem
="Enable output",
373 disableitem
="Disable output"):
375 if othermenubar
is not None:
376 self
.SetOtherMenuBar(othermenubar
,
378 enableitem
=enableitem
,
379 disableitem
=disableitem
)
390 self
.frame
.sb
.SetStatusText("File '%s' closed..."
391 % os
.path
.abspath(self
.f
.name
),
397 self
.frame
.sb
.SetStatusText("")
399 self
.frame
.sb
.Refresh()
403 def OpenNewFile(self
):
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
:
413 self
.f
= open(os
.path
.abspath(dlg
.GetPath()),'w')
414 except EnvironmentError:
419 self
.frame
.sb
.SetStatusText("File '%s' opened..."
420 % os
.path
.abspath(self
.f
.name
),
422 if hasattr(self
,"nofile"):
423 self
.frame
.sb
= _MyStatusBar(self
.frame
,
424 callbacks
=[self
.DisableOutput
,
427 self
.frame
.SetStatusBar(self
.frame
.sb
)
428 if hasattr(self
,"nofile"):
429 delattr(self
,"nofile")
433 def DisableOutput(self
,
434 event
=None,# event must be the first optional argument...
436 self
.write("<InformationalMessagesFrame>.DisableOutput()\n")
437 if hasattr(self
,"frame") \
438 and self
.frame
is not None:
439 self
.OnCloseWindow("Dummy",exiting
=exiting
)
449 self
.text
.SetInsertionPointEnd()
453 def __call__(self
,* args
):
458 def SetOutputDirectory(self
,dir):
459 self
.dir = os
.path
.abspath(dir)
460 ## sys.__stderr__.write("Directory: os.path.abspath(%s) = %s\n"
465 class Dummy_PyInformationalMessagesFrame
:
466 def __init__(self
,progname
=""):
468 def __call__(self
,*args
):
476 def EnableOutput(self
):
478 def __call__(self
,* args
):
480 def DisableOutput(self
,exiting
=0):
482 def SetParent(self
,wX
):