3 Released under wxWindows license etc. 
   5 This is a fairly rudimentary, but slightly fancier tha 
   6 wxPyOnDemandOutputWindow (on which it's based; thanks Robin), version 
   7 of the same sort of thing: a file-like class called 
   8 wxInformationalMessagesFrame. This window also has a status bar with a 
   9 couple of buttons for controlling the echoing of all output to a file 
  10 with a randomly-chosen filename... 
  12 The class behaves similarly to wxPyOnDemandOutputWindow in that (at 
  13 least by default) the frame does not appear until written to, but is 
  14 somewhat different in that, either under programmatic (the 
  15 DisableOutput method) or user (the frame's close button, it's status 
  16 bar's "Dismiss" button, or a "Disable output" item of some menu, 
  17 perhaps of some other frame), the frame will be destroyed, an 
  18 associated file closed, and writing to it will then do nothing.  This 
  19 can be reversed: either under programmatic (the EnableOutput method) 
  20 or user (an "Enable output" item of some menu), a new frame will be 
  21 opened,And an associated file (with a "randomly"selected filename) 
  22 opened for writing [to which all subsequent displayed messages will be 
  25 Please note that, like wxPyOnDemandOutputWindow, the instance is not 
  26 itself a subclass of wxWindow: when the window is open (and ONLY 
  27 then), it's "frame" attribute is the actual instance of wFrame... 
  30     from wxPython.lib.infoframe import * 
  31     ... # ... modify your wxApp as follows: 
  33         outputWindowClass = wxPyInformationalMessagesFrame 
  35 If you're running on Linux, you'll also have to supply an argument 1 to your 
  36 constructor of myApp to redirect stdout/stderr to this window (it's done 
  37 automatically for you on Windows). 
  39 If you don't want to redirect stdout/stderr, but use the class directly: do 
  42  InformationalMessagesFrame = wxPyInformationalMessagesFrame\ 
  43                                          ([options from progname (default ""), 
  44                                            txt (default "informational 
  46 #^^^^ early in the program 
  48 InformationalMessagesFrame([comma-separated list of items to 
  49                              display.  Note that these will never 
  50                              be separated by spaces as they may 
  51                              be when used in the Python 'print' 
  54 The latter statement, of course, may be repeated arbitrarily often. 
  55 The window will not appear until it is written to, and it may be 
  56 manually closed by the user, after which it will reappear again until 
  57 written to... Also note that all output is echoed to a file with a 
  58 randomly-generated name [see the mktemp module in the standard 
  59 library], in the directory given as the 'dir' keyword argument to the 
  60 InformationalMessagesFrame constructor [which has a default value of 
  61 '.'), or set via the method SetOutputDirectory... This file will be 
  62 closed with the window--a new one will be created [by default] upon 
  63 each subsequent reopening. 
  65 Please also note the methods EnableOutput and DisableOutput, and the 
  66 possible arguments for the constructor in the code below... (* TO DO: 
  67 explain this here...*) Neither of these methods need be used at all, 
  68 and in this case the frame will only be displayed once it has been 
  69 written to, like wxPyOnDemandOutputWindow. 
  71 The former, EnableOutput, displays the frame with an introductory 
  72 message, opens a random file to which future displayed output also 
  73 goes (unless the nofile attribute is present), and sets the __debug__ 
  74 variable of each module to 1 (unless the no __debug__ attribute is 
  75 present].  This is so that you can say, in any module whatsoever, 
  78         InformationalMessagesFrame("... with lots of %<Character> constructs" 
  81 without worrying about the overhead of evaluating the arguments, and 
  82 calling the wxInformationalMessagesFrame instance, in the case where 
  83 debugging is not turned on.  (This won't happen if the instance has an 
  84 attribute no__debug__; you can arrange this by sub-classing...) 
  86 "Debug mode" can also be turned on by selecting the item-"Enable 
  87 output" from the "Debug" menu [the default--see the optional arguments 
  88 to the SetOtherMenuBar method] of a frame which has been either passed 
  89 appropriately to the constructor of the wxInformationalMessagesFrame 
  90 (see the code), or set via the SetOtherMenuBar method thereof.  This 
  91 also writes an empty string to the instance, meaning that the frame 
  92 will open (unless DisablOutput has been called) with an appropriate 
  93 introductory message (which will vary according to whether the 
  94 instance/class has the "no __debug__" attribute)^  I have found this to 
  95 be an extremely useful tool, in lieu of a full wxPython debugger... 
  97 Following this, the menu item is also disabled, and an item "Disable 
  98 output" (again, by default) is enabled.  Note that these need not be 
  99 done: e.g., you don't NEED to have a menu with appropriate items; in 
 100 this case simply do not call the SetOtherMenuBar method or use the 
 101 othermenubar keyword argument of the class instance constructor. 
 103 The DisableOutput method does the reverse of this; it closes the 
 104 window (and associated file), and sets the __debug__ variable of each 
 105 module whose name begins with a capital letter {this happens to be the 
 106 author's personal practice; all my python module start with capital 
 107 letters} to 0.  It also enables/disabled the appropriate menu items, 
 108 if this was done previously (or SetOtherMenuBar has been called...). 
 109 Note too that after a call to DisableOutput, nothing further will be 
 110 done upon subsequent write()'s, until the EnableOutput method is 
 111 called, either explicitly or by the menu selection above... 
 113 Finally, note that the file-like method close() destroys the window 
 114 (and closes any associated file) and there is a file-like method 
 115 write() which displays it's argument. 
 117 All (well, most) of this is made clear by the example code at the end 
 118 of this file, which is run if the file is run by itself; otherwise, 
 119 see the appropriate "stub" file in the wxPython demo. 
 129 class _MyStatusBar(wx
.StatusBar
): 
 130     def __init__(self
, parent
, callbacks
=None, useopenbutton
=0): 
 131         wx
.StatusBar
.__init
__(self
, parent
, -1, style
=wx
.TAB_TRAVERSAL
) 
 132         self
.SetFieldsCount(3) 
 134         self
.SetStatusText("",0) 
 136         self
.button1 
= wx
.Button(self
, -1, "Dismiss", style
=wx
.TAB_TRAVERSAL
) 
 137         self
.Bind(wx
.EVT_BUTTON
, self
.OnButton1
, self
.button1
) 
 139         if not useopenbutton
: 
 140             self
.button2 
= wx
.Button(self
, -1, "Close File", style
=wx
.TAB_TRAVERSAL
) 
 142             self
.button2 
= wx
.Button(self
, -1, "Open New File", style
=wx
.TAB_TRAVERSAL
) 
 144         self
.Bind(wx
.EVT_BUTTON
, self
.OnButton2
, self
.button2
) 
 145         self
.useopenbutton 
= useopenbutton
 
 146         self
.callbacks 
= callbacks
 
 148         # figure out how tall to make the status bar 
 149         dc 
= wx
.ClientDC(self
) 
 150         dc
.SetFont(self
.GetFont()) 
 151         (w
,h
) = dc
.GetTextExtent('X') 
 153         self
.SetSize((100, h
)) 
 155         self
.Bind(wx
.EVT_SIZE
, self
.OnSize
) 
 157     # reposition things... 
 158     def OnSize(self
, event
): 
 159         self
.CalculateSizes() 
 160         rect 
= self
.GetFieldRect(1) 
 161         self
.button1
.SetPosition((rect
.x
+5, rect
.y
+2)) 
 162         self
.button1
.SetSize((rect
.width
-10, rect
.height
-4)) 
 163         rect 
= self
.GetFieldRect(2) 
 164         self
.button2
.SetPosition((rect
.x
+5, rect
.y
+2)) 
 165         self
.button2
.SetSize((rect
.width
-10, rect
.height
-4)) 
 168     def CalculateSizes(self
): 
 169         dc 
= wx
.ClientDC(self
.button1
) 
 170         dc
.SetFont(self
.button1
.GetFont()) 
 171         (w1
,h
) = dc
.GetTextExtent(self
.button1
.GetLabel()) 
 173         dc 
= wx
.ClientDC(self
.button2
) 
 174         dc
.SetFont(self
.button2
.GetFont()) 
 175         (w2
,h
) = dc
.GetTextExtent(self
.button2
.GetLabel()) 
 177         self
.SetStatusWidths([-1,w1
+15,w2
+15]) 
 179     def OnButton1(self
,event
): 
 182     def OnButton2(self
,event
): 
 183         if self
.useopenbutton 
and self
.callbacks
[2] (): 
 184                 self
.button2
.SetLabel ("Close File") 
 185         elif self
.callbacks
[1] (): 
 186                 self
.button2
.SetLabel ("Open New File") 
 188         self
.useopenbutton 
= 1 - self
.useopenbutton
 
 190         self
.button2
.Refresh(True) 
 195 class wxPyInformationalMessagesFrame
: 
 198                  text
="informational messages", 
 201                  enableitem
="Enable output", 
 202                  disableitem
="Disable output", 
 205         self
.SetOtherMenuBar(othermenubar
, 
 207                              enableitem
=enableitem
, 
 208                              disableitem
=disableitem
) 
 210         if hasattr(self
,"othermenu") and self
.othermenu 
is not None: 
 211             i 
= self
.othermenu
.FindMenuItem(self
.menuname
,self
.disableitem
) 
 212             self
.othermenu
.Enable(i
,0) 
 213             i 
= self
.othermenu
.FindMenuItem(self
.menuname
,self
.enableitem
) 
 214             self
.othermenu
.Enable(i
,1) 
 217         self
.title  
= "%s %s" % (progname
,text
) 
 218         self
.parent 
= None # use the SetParent method if desired... 
 219         self
.softspace 
= 1 # of rather limited use 
 222             self
.SetOutputDirectory(dir) 
 225     def SetParent(self
, parent
): 
 229     def SetOtherMenuBar(self
, 
 232                         enableitem
="Enable output", 
 233                         disableitem
="Disable output"): 
 234         self
.othermenu 
= othermenu
 
 235         self
.menuname 
= menuname
 
 236         self
.enableitem 
= enableitem
 
 237         self
.disableitem 
= disableitem
 
 243     def write(self
, string
): 
 244         if not wx
.Thread_IsMain(): 
 245             # Aquire the GUI mutex before making GUI calls.  Mutex is released 
 246             # when locker is deleted at the end of this function. 
 248             # TODO: This should be updated to use wx.CallAfter similarly to how 
 249             # PyOnDemandOutputWindow.write was so it is not necessary 
 250             # to get the gui mutex 
 251             locker 
= wx
.MutexGuiLocker() 
 259             if (hasattr(self
,"text") 
 260                 and self
.text 
is not None 
 261                 and self
.text
.GetInsertionPoint() != self
.text
.GetLastPosition()): 
 265                 self
.frame 
= wx
.Frame(self
.parent
, -1, self
.title
, size
=(450, 300), 
 266                                      style
=wx
.DEFAULT_FRAME_STYLE|wx
.NO_FULL_REPAINT_ON_RESIZE
) 
 268                 self
.text  
= wx
.TextCtrl(self
.frame
, -1, "", 
 269                                         style 
= wx
.TE_MULTILINE|wx
.TE_READONLY|wx
.TE_RICH
) 
 271                 self
.frame
.sb 
= _MyStatusBar(self
.frame
, 
 272                                              callbacks
=[self
.DisableOutput
, 
 275                                              useopenbutton
=hasattr(self
, 
 277                 self
.frame
.SetStatusBar(self
.frame
.sb
) 
 278                 self
.frame
.Show(True) 
 279                 self
.frame
.Bind(wx
.EVT_CLOSE
, self
.OnCloseWindow
) 
 281                 if hasattr(self
,"nofile"): 
 282                     self
.text
.AppendText( 
 283                         "Please close this window (or select the " 
 284                         "'Dismiss' button below) when desired.  By " 
 285                         "default all messages written to this window " 
 286                         "will NOT be written to a file--you " 
 287                         "may change this by selecting 'Open New File' " 
 288                         "below, allowing you to select a " 
 291                     tempfile
.tempdir 
= self
.dir 
 292                     filename 
= os
.path
.abspath(tempfile
.mktemp ()) 
 294                     self
.text
.AppendText( 
 295                         "Please close this window (or select the " 
 296                         "'Dismiss' button below) when desired.  By " 
 297                         "default all messages written to this window " 
 298                         "will also be written to the file '%s'--you " 
 299                         "may close this file by selecting 'Close " 
 300                         "File' below, whereupon this button will be " 
 301                         "replaced with one allowing you to select a " 
 302                         "new file...\n\n" % filename
) 
 304                         self
.f 
= open(filename
, 'w') 
 305                         self
.frame
.sb
.SetStatusText("File '%s' opened..." 
 308                     except EnvironmentError: 
 309                         self
.frame
.sb
.SetStatusText("File creation failed " 
 313             self
.text
.AppendText(string
) 
 316                 self
.text
.ShowPosition(self
.text
.GetLastPosition()) 
 318             if  not hasattr(self
,"no__debug__"): 
 319                 for m 
in sys
.modules
.values(): 
 320                     if m 
is  not None:# and m.__dict__.has_key("__debug__"): 
 321                         m
.__dict
__["__debug__"] = 1 
 323             if hasattr(self
,"othermenu") and self
.othermenu 
is not None: 
 324                 i 
= self
.othermenu
.FindMenuItem(self
.menuname
,self
.disableitem
) 
 325                 self
.othermenu
.Enable(i
,1) 
 326                 i 
= self
.othermenu
.FindMenuItem(self
.menuname
,self
.enableitem
) 
 327                 self
.othermenu
.Enable(i
,0) 
 332     def OnCloseWindow(self
, event
, exiting
=0): 
 337         if (hasattr(self
,"othermenu") and self
.othermenu 
is not None 
 338             and self
.frame 
is not None 
 341             i 
= self
.othermenu
.FindMenuItem(self
.menuname
,self
.disableitem
) 
 342             self
.othermenu
.Enable(i
,0) 
 343             i 
= self
.othermenu
.FindMenuItem(self
.menuname
,self
.enableitem
) 
 344             self
.othermenu
.Enable(i
,1) 
 346         if  not hasattr(self
,"no__debug__"): 
 347             for m 
in sys
.modules
.values(): 
 348                 if m 
is  not None:# and m.__dict__.has_key("__debug__"): 
 349                     m
.__dict
__["__debug__"] = 0 
 351         if self
.frame 
is not None: # typically True, but, e.g., allows 
 352                                    # DisableOutput method (which calls this 
 353                                    # one) to be called when the frame is not 
 354                                    # actually open, so that it is always safe 
 355                                    # to call this method... 
 357             self
.frame 
= self
.text 
= None 
 362     def EnableOutput(self
, 
 363                      event
=None,# event must be the first optional argument... 
 366                      enableitem
="Enable output", 
 367                      disableitem
="Disable output"): 
 369         if othermenubar 
is not None: 
 370             self
.SetOtherMenuBar(othermenubar
, 
 372                                  enableitem
=enableitem
, 
 373                                  disableitem
=disableitem
) 
 384                 self
.frame
.sb
.SetStatusText("File '%s' closed..." 
 385                                             % os
.path
.abspath(self
.f
.name
), 
 391                 self
.frame
.sb
.SetStatusText("") 
 393             self
.frame
.sb
.Refresh() 
 397     def OpenNewFile(self
): 
 399         dlg 
= wx
.FileDialog(self
.frame
, 
 400                           "Choose a new log file", self
.dir,"","*", 
 401                            wx
.SAVE | wx
.HIDE_READONLY | wx
.OVERWRITE_PROMPT
) 
 402         if dlg
.ShowModal() == wx
.ID_CANCEL
: 
 407                 self
.f 
= open(os
.path
.abspath(dlg
.GetPath()),'w') 
 408             except EnvironmentError: 
 413                 self
.frame
.sb
.SetStatusText("File '%s' opened..." 
 414                                             % os
.path
.abspath(self
.f
.name
), 
 416                 if hasattr(self
,"nofile"): 
 417                     self
.frame
.sb 
= _MyStatusBar(self
.frame
, 
 418                                                 callbacks
=[self
.DisableOutput
, 
 421                     self
.frame
.SetStatusBar(self
.frame
.sb
) 
 422             if hasattr(self
,"nofile"): 
 423                 delattr(self
,"nofile") 
 427     def DisableOutput(self
, 
 428                       event
=None,# event must be the first optional argument... 
 430         self
.write("<InformationalMessagesFrame>.DisableOutput()\n") 
 431         if hasattr(self
,"frame") \
 
 432            and self
.frame 
is not None: 
 433             self
.OnCloseWindow("Dummy",exiting
=exiting
) 
 443             self
.text
.SetInsertionPointEnd() 
 447     def __call__(self
,* args
): 
 452     def SetOutputDirectory(self
,dir): 
 453         self
.dir = os
.path
.abspath(dir) 
 454 ##        sys.__stderr__.write("Directory: os.path.abspath(%s) = %s\n" 
 459 class Dummy_wxPyInformationalMessagesFrame
: 
 460     def __init__(self
,progname
=""): 
 462     def __call__(self
,*args
): 
 470     def EnableOutput(self
): 
 472     def __call__(self
,* args
): 
 474     def DisableOutput(self
,exiting
=0): 
 476     def SetParent(self
,wX
):