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
 | |
| 
 |