git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@24965 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
		
			
				
	
	
		
			485 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			485 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.HIDE_READONLY | 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
 | 
						|
 |