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