a43dbe7260
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@39861 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
487 lines
19 KiB
Python
487 lines
19 KiB
Python
# 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 wxPython.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([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:
|
|
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:
|
|
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
|
|
|