]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/wx/lib/infoframe.py
added wxDisplay::GetClientArea() (currently implemented for single display and MSW...
[wxWidgets.git] / wxPython / wx / lib / infoframe.py
index 196bfabd66e91e4c79237ed6a4fc02db43393344..b1352b437d777755bb3c9999473a9831209cc1cc 100644 (file)
+# 12/20/2003 - Jeff Grimmett (grimmtooth@softhome.net)
+#
+# o wxPyInformationalMessagesFrame -> PyInformationalMessagesFrame
+# o dummy_wxPyInformationalMessagesFrame -> dummy_PyInformationalMessagesFrame
+#
 
-"""Renamer stub: provides a way to drop the wx prefix from wxPython objects."""
+"""
+infoframe.py
+Released under wxWindows license etc.
 
-__cvsid__ = "$Id$"
-__revision__ = "$Revision$"[11:-2]
+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
 
-from wx import _rename
-from wxPython.lib import infoframe
-_rename(globals(), infoframe.__dict__, modulename='lib.infoframe')
-del infoframe
-del _rename