]> git.saurik.com Git - wxWidgets.git/blame - wxPython/wx/lib/infoframe.py
Don't scroll too far if the child getting the focus is large.
[wxWidgets.git] / wxPython / wx / lib / infoframe.py
CommitLineData
d4b73b1b
RD
1# 12/20/2003 - Jeff Grimmett (grimmtooth@softhome.net)
2#
3# o wxPyInformationalMessagesFrame -> PyInformationalMessagesFrame
4# o dummy_wxPyInformationalMessagesFrame -> dummy_PyInformationalMessagesFrame
5#
6
d14a1e28
RD
7"""
8infoframe.py
9Released under wxWindows license etc.
1fded56b 10
d14a1e28
RD
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):
d4b73b1b 39 outputWindowClass = PyInformationalMessagesFrame
d14a1e28
RD
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
d4b73b1b 48 InformationalMessagesFrame = PyInformationalMessagesFrame\
d14a1e28
RD
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.
b881fc78 126
d14a1e28
RD
127"""
128
b881fc78
RD
129import os
130import sys
131import tempfile
132
133import wx
d14a1e28 134
b881fc78
RD
135class _MyStatusBar(wx.StatusBar):
136 def __init__(self, parent, callbacks=None, useopenbutton=0):
137 wx.StatusBar.__init__(self, parent, -1, style=wx.TAB_TRAVERSAL)
d14a1e28
RD
138 self.SetFieldsCount(3)
139
140 self.SetStatusText("",0)
141
b881fc78
RD
142 self.button1 = wx.Button(self, -1, "Dismiss", style=wx.TAB_TRAVERSAL)
143 self.Bind(wx.EVT_BUTTON, self.OnButton1, self.button1)
d14a1e28 144
d14a1e28 145 if not useopenbutton:
b881fc78 146 self.button2 = wx.Button(self, -1, "Close File", style=wx.TAB_TRAVERSAL)
d14a1e28 147 else:
b881fc78
RD
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)
d14a1e28
RD
151 self.useopenbutton = useopenbutton
152 self.callbacks = callbacks
153
154 # figure out how tall to make the status bar
b881fc78 155 dc = wx.ClientDC(self)
d14a1e28
RD
156 dc.SetFont(self.GetFont())
157 (w,h) = dc.GetTextExtent('X')
158 h = int(h * 1.8)
b881fc78 159 self.SetSize((100, h))
d14a1e28 160 self.OnSize("dummy")
b881fc78 161 self.Bind(wx.EVT_SIZE, self.OnSize)
d14a1e28
RD
162
163 # reposition things...
164 def OnSize(self, event):
165 self.CalculateSizes()
166 rect = self.GetFieldRect(1)
b881fc78
RD
167 self.button1.SetPosition((rect.x+5, rect.y+2))
168 self.button1.SetSize((rect.width-10, rect.height-4))
d14a1e28 169 rect = self.GetFieldRect(2)
b881fc78
RD
170 self.button2.SetPosition((rect.x+5, rect.y+2))
171 self.button2.SetSize((rect.width-10, rect.height-4))
d14a1e28
RD
172
173 # widths........
174 def CalculateSizes(self):
b881fc78 175 dc = wx.ClientDC(self.button1)
d14a1e28
RD
176 dc.SetFont(self.button1.GetFont())
177 (w1,h) = dc.GetTextExtent(self.button1.GetLabel())
178
b881fc78 179 dc = wx.ClientDC(self.button2)
d14a1e28
RD
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")
b881fc78 193
d14a1e28
RD
194 self.useopenbutton = 1 - self.useopenbutton
195 self.OnSize("")
196 self.button2.Refresh(True)
197 self.Refresh()
198
199
200
d4b73b1b 201class PyInformationalMessagesFrame:
d14a1e28
RD
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
b881fc78 226
d14a1e28
RD
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
b881fc78
RD
249 def write(self, string):
250 if not wx.Thread_IsMain():
d14a1e28
RD
251 # Aquire the GUI mutex before making GUI calls. Mutex is released
252 # when locker is deleted at the end of this function.
b881fc78
RD
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()
d14a1e28
RD
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:
b881fc78
RD
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)
d14a1e28
RD
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)
b881fc78 285 self.frame.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
d14a1e28
RD
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 ())
b881fc78 299
d14a1e28
RD
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()
b881fc78 405 dlg = wx.FileDialog(self.frame,
d14a1e28 406 "Choose a new log file", self.dir,"","*",
b881fc78
RD
407 wx.SAVE | wx.HIDE_READONLY | wx.OVERWRITE_PROMPT)
408 if dlg.ShowModal() == wx.ID_CANCEL:
d14a1e28
RD
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()
b881fc78 450 wx.Yield()
d14a1e28
RD
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
d4b73b1b 465class Dummy_PyInformationalMessagesFrame:
d14a1e28
RD
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
1fded56b 484