]> git.saurik.com Git - wxWidgets.git/commitdiff
Added wx.lib.expando, a multi-line textctrl that exands as more lines
authorRobin Dunn <robin@alldunn.com>
Sat, 23 Sep 2006 04:15:20 +0000 (04:15 +0000)
committerRobin Dunn <robin@alldunn.com>
Sat, 23 Sep 2006 04:15:20 +0000 (04:15 +0000)
are needed.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@41380 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775

wxPython/demo/ExpandoTextCtrl.py [new file with mode: 0644]
wxPython/demo/Main.py
wxPython/docs/CHANGES.txt
wxPython/wx/lib/expando.py [new file with mode: 0644]

diff --git a/wxPython/demo/ExpandoTextCtrl.py b/wxPython/demo/ExpandoTextCtrl.py
new file mode 100644 (file)
index 0000000..0315a39
--- /dev/null
@@ -0,0 +1,133 @@
+
+import wx
+from wx.lib.expando import ExpandoTextCtrl, EVT_ETC_LAYOUT_NEEDED
+
+#----------------------------------------------------------------------
+
+class TestFrame(wx.Frame):
+    def __init__(self, parent):
+        wx.Frame.__init__(self, parent, title="Test ExpandoTextCtrl")
+        self.pnl = p = wx.Panel(self)
+        self.eom = ExpandoTextCtrl(p, size=(250,-1),
+                                   value="This control will expand as you type")
+        self.Bind(EVT_ETC_LAYOUT_NEEDED, self.OnRefit, self.eom)
+
+        # create some buttons and sizers to use in testing some
+        # features and also the layout
+        vBtnSizer = wx.BoxSizer(wx.VERTICAL)
+
+        btn = wx.Button(p, -1, "Set MaxHeight")
+        self.Bind(wx.EVT_BUTTON, self.OnSetMaxHeight, btn)
+        vBtnSizer.Add(btn, 0, wx.ALL|wx.EXPAND, 5)
+
+        btn = wx.Button(p, -1, "Set Font")
+        self.Bind(wx.EVT_BUTTON, self.OnSetFont, btn)
+        vBtnSizer.Add(btn, 0, wx.ALL|wx.EXPAND, 5)
+
+        btn = wx.Button(p, -1, "Write Text")
+        self.Bind(wx.EVT_BUTTON, self.OnWriteText, btn)
+        vBtnSizer.Add(btn, 0, wx.ALL|wx.EXPAND, 5)
+
+        for x in range(5):
+            btn = wx.Button(p, -1, " ")
+            vBtnSizer.Add(btn, 0, wx.ALL|wx.EXPAND, 5)
+
+        hBtnSizer = wx.BoxSizer(wx.HORIZONTAL)
+        for x in range(3):
+            btn = wx.Button(p, -1, " ")
+            hBtnSizer.Add(btn, 0, wx.ALL, 5)
+
+        sizer = wx.BoxSizer(wx.HORIZONTAL)
+        col1 = wx.BoxSizer(wx.VERTICAL)
+        col1.Add(self.eom, 0, wx.ALL, 10)
+        col1.Add(hBtnSizer)
+        sizer.Add(col1)
+        sizer.Add(vBtnSizer)
+        p.SetSizer(sizer)
+
+        # Put the panel in a sizer for the frame so we can use self.Fit()
+        frameSizer = wx.BoxSizer()
+        frameSizer.Add(p, 1, wx.EXPAND)
+        self.SetSizer(frameSizer)
+        
+        self.Fit()
+
+
+    def OnRefit(self, evt):
+        # The Expando control will redo the layout of the
+        # sizer it belongs to, but sometimes this may not be
+        # enough, so it will send us this event so we can do any
+        # other layout adjustments needed.  In this case we'll
+        # just resize the frame to fit the new needs of the sizer.
+        self.Fit()
+
+    def OnSetMaxHeight(self, evt):
+        mh = self.eom.GetMaxHeight()
+        dlg = wx.NumberEntryDialog(self, "", "Enter new max height:",
+                                   "MaxHeight", mh, -1, 1000)
+        if dlg.ShowModal() == wx.ID_OK:
+            self.eom.SetMaxHeight(dlg.GetValue())
+        dlg.Destroy()
+        
+        
+    def OnSetFont(self, evt):
+        dlg = wx.FontDialog(self, wx.FontData())
+        dlg.GetFontData().SetInitialFont(self.eom.GetFont())
+        if dlg.ShowModal() == wx.ID_OK:
+            self.eom.SetFont(dlg.GetFontData().GetChosenFont())
+        dlg.Destroy()
+        
+        
+    def OnWriteText(self, evt):
+        self.eom.WriteText("\nThis is a test...  Only a test.  If this had "
+                           "been a real emergency you would have seen the "
+                           "quick brown fox jump over the lazy dog.\n")
+    
+
+#----------------------------------------------------------------------
+
+class TestPanel(wx.Panel):
+    def __init__(self, parent, log):
+        self.log = log
+        wx.Panel.__init__(self, parent, -1)
+
+        b = wx.Button(self, -1, " Test ExpandoTextCtrl ", (50,50))
+        self.Bind(wx.EVT_BUTTON, self.OnButton, b)
+
+
+    def OnButton(self, evt):
+        win = TestFrame(self)
+        win.Show(True)
+
+  
+#----------------------------------------------------------------------
+
+def runTest(frame, nb, log):
+    win = TestPanel(nb, log)
+    return win
+
+#----------------------------------------------------------------------
+
+
+
+overview = """<html><body>
+<h2><center>ExpandoTextCtrl</center></h2>
+
+The ExpandoTextCtrl is a multi-line wx.TextCtrl that will
+adjust its height on the fly as needed to accomodate the number of
+lines needed to display the current content of the control.  It is
+assumed that the width of the control will be a fixed value and
+that only the height will be adjusted automatically.  If the
+control is used in a sizer then the width should be set as part of
+the initial or min size of the control.
+
+</body></html>
+"""
+
+
+
+if __name__ == '__main__':
+    import sys,os
+    import run
+    run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])
+
index 11ea138078b91f5bdd11e2e6fbcd14f0fca9d492..c7bbb1b03e63c49e60880a7cff46eecdb171f8ac 100644 (file)
@@ -62,6 +62,7 @@ _treeList = [
         'DragScroller',
 ##        'AlphaDrawing',
         'DelayedResult',
+        'ExpandoTextCtrl',
         ]),
 
     # managed windows == things with a (optional) caption you can close
@@ -170,6 +171,7 @@ _treeList = [
         'DatePickerCtrl',
         'DynamicSashWindow',
         'EditableListBox',
+        'ExpandoTextCtrl',
         'FancyText',
         'FileBrowseButton',
         'FloatBar',  
index 05e794bb65abf218464ce4e845fbe1a7ad332e57..428e13daa28ac95fe84671507ea8fd82823b97d7 100644 (file)
@@ -249,6 +249,8 @@ were worked around in the wrapper code.
 
 Added wx.lib.delayedresult from Oliver Schoenborn.
 
+Added wx.lib.expando, a multi-line textctrl that exands as more lines
+are needed.
 
 
 
diff --git a/wxPython/wx/lib/expando.py b/wxPython/wx/lib/expando.py
new file mode 100644 (file)
index 0000000..7545956
--- /dev/null
@@ -0,0 +1,288 @@
+#---------------------------------------------------------------------------
+# Name:        expando.py
+# Purpose:     A multi-line text control that expands and collapses as more
+#              or less lines are needed to display its content.
+#
+# Author:      Robin Dunn
+#
+# Created:     18-Sept-2006
+# RCS-ID:      $Id$
+# Copyright:   (c) 2006 by Total Control Software
+# Licence:     wxWindows license
+#
+#---------------------------------------------------------------------------
+"""
+This module contains the `ExpandoTextCtrl` which is a multi-line
+text control that will expand its height on the fly to be able to show
+all the lines of the content of the control.
+"""
+
+import wx
+import wx.lib.newevent
+
+
+# This event class and binder object can be used to catch
+# notifications that the ExpandoTextCtrl has resized itself and
+# that layout adjustments may need to be made.
+wxEVT_ETC_LAYOUT_NEEDED = wx.NewEventType()
+EVT_ETC_LAYOUT_NEEDED = wx.PyEventBinder( wxEVT_ETC_LAYOUT_NEEDED, 1 )
+
+
+#---------------------------------------------------------------------------
+
+class ExpandoTextCtrl(wx.TextCtrl):
+    """
+    The ExpandoTextCtrl is a multi-line wx.TextCtrl that will
+    adjust its height on the fly as needed to accomodate the number of
+    lines needed to display the current content of the control.  It is
+    assumed that the width of the control will be a fixed value and
+    that only the height will be adjusted automatically.  If the
+    control is used in a sizer then the width should be set as part of
+    the initial or min size of the control.
+
+    When the control resizes itself it will attempt to also make
+    necessary adjustments in the sizer hierarchy it is a member of (if
+    any) but if that is not suffiecient then the programmer can catch
+    the EVT_ETC_LAYOUT_NEEDED event in the container and make any
+    other layout adjustments that may be needed.
+    """
+    _defaultHeight = -1
+    
+    def __init__(self, parent, id=-1, value="",
+                 pos=wx.DefaultPosition,  size=wx.DefaultSize,
+                 style=0, validator=wx.DefaultValidator, name="expando"):
+        # find the default height of a single line control
+        self.defaultHeight = self._getDefaultHeight(parent)
+        # make sure we default to that height if none was given
+        w, h = size
+        if h == -1:
+            h = self.defaultHeight
+        # always use the multi-line style
+        style = style | wx.TE_MULTILINE | wx.TE_NO_VSCROLL | wx.TE_RICH2
+        # init the base class
+        wx.TextCtrl.__init__(self, parent, id, value, pos, (w, h),
+                             style, validator, name)
+        # save some basic metrics
+        self.extraHeight = self.defaultHeight - self.GetCharHeight()
+        self.numLines = self.GetNumberOfLines()
+        self.maxHeight = -1
+        
+        self.Bind(wx.EVT_TEXT, self.OnTextChanged)
+
+
+    def SetMaxHeight(self, h):
+        """
+        Sets the max height that the control will expand to on its
+        own, and adjusts it down if needed.
+        """
+        self.maxHeight = h
+        if h != -1 and self.GetSize().height > h:
+            self.SetSize((-1, h))
+        
+    def GetMaxHeight(self):
+        """Sets the max height that the control will expand to on its own"""
+        return self.maxHeight
+
+
+    def SetFont(self, font):
+        wx.TextCtrl.SetFont(self, font)
+        self.numLines = -1
+        self._adjustCtrl()
+        
+
+    def OnTextChanged(self, evt):
+        # check if any adjustments are needed on every text update
+        self._adjustCtrl()
+        evt.Skip()
+        
+
+    def _adjustCtrl(self):
+        # if the current number of lines is different than before
+        # then recalculate the size needed and readjust
+        numLines = self.GetNumberOfLines()
+        if numLines != self.numLines:
+            self.numLines = numLines
+            charHeight = self.GetCharHeight()
+            height = numLines * charHeight + self.extraHeight
+            if not (self.maxHeight != -1 and height > self.maxHeight):
+                # The size is changing...  if the control is not in a
+                # sizer then we just want to change the size and
+                # that's it, the programmer will need to deal with
+                # potential layout issues.  If it is being managed by
+                # a sizer then we'll change the min size setting and
+                # then try to do a layout.  In either case we'll also
+                # send an event so the parent can handle any special
+                # layout issues that it wants to deal with.
+                if self.GetContainingSizer() is not None:
+                    mw, mh = self.GetMinSize()
+                    self.SetMinSize((mw, height))
+                    if self.GetParent().GetSizer() is not None:
+                        self.GetParent().Layout()
+                    else:
+                        self.GetContainingSizer().Layout()
+                else:
+                    self.SetSize((-1, height))
+                # send notification that layout is needed
+                evt = wx.PyCommandEvent(wxEVT_ETC_LAYOUT_NEEDED, self.GetId())
+                evt.SetEventObject(self)
+                evt.height = height
+                evt.numLines = numLines
+                self.GetEventHandler().ProcessEvent(evt)
+                
+
+    def _getDefaultHeight(self, parent):
+        # checked for cached value
+        if self.__class__._defaultHeight != -1:
+            return self.__class__._defaultHeight
+        # otherwise make a single line textctrl and find out its default height
+        tc = wx.TextCtrl(parent)
+        sz = tc.GetSize()
+        tc.Destroy()
+        self.__class__._defaultHeight = sz.height
+        return sz.height
+
+
+    if wx.VERSION < (2,7) and 'wxGTK' in wx.PlatformInfo:
+        # the wxGTK version of GetNumberOfLines in 2.6 doesn't count
+        # wrapped lines, so we need to implement our own.  This is
+        # fixed in 2.7.
+        def GetNumberOfLines(self):
+            text = self.GetValue()
+            width = self.GetSize().width
+            dc = wx.ClientDC(self)
+            dc.SetFont(self.GetFont())
+            count = 0 
+            for line in text.split('\n'):
+                count += 1
+                w, h = dc.GetTextExtent(line)
+                if w > width:
+                    # the width of the text is wider than the control,
+                    # calc how many lines it will be wrapped to
+                    count += self._wrapLine(line, dc, width)
+                    
+            if not count:
+                count = 1
+            return count
+
+
+        def _wrapLine(self, line, dc, width):
+            # Estimate where the control will wrap the lines and
+            # return the count of extra lines needed.
+            pte = dc.GetPartialTextExtents(line)            
+            width -= wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X)
+            idx = 0
+            start = 0
+            count = 0
+            spc = -1
+            while idx < len(pte):
+                if line[idx] == ' ':
+                    spc = idx
+                if pte[idx] - start > width - _sbw:
+                    # we've reached the max width, add a new line
+                    count += 1
+                    # did we see a space? if so restart the count at that pos
+                    if spc != -1:
+                        idx = spc + 1
+                        spc = -1
+                    start = pte[idx]
+                else:
+                    idx += 1
+            return count
+
+#---------------------------------------------------------------------------
+
+
+if __name__ == '__main__':
+    print wx.VERSION
+    
+    class TestFrame(wx.Frame):
+        def __init__(self):
+            wx.Frame.__init__(self, None, title="Testing...")
+            self.pnl = p = wx.Panel(self)
+            self.eom = ExpandoTextCtrl(p, pos=(25,25), size=(250,-1))
+            self.Bind(EVT_ETC_LAYOUT_NEEDED, self.OnRefit, self.eom)
+
+            # create some buttons and sizers to use in testing some
+            # features and also the layout
+            vBtnSizer = wx.BoxSizer(wx.VERTICAL)
+
+            btn = wx.Button(p, -1, "Set MaxHeight")
+            self.Bind(wx.EVT_BUTTON, self.OnSetMaxHeight, btn)
+            vBtnSizer.Add(btn, 0, wx.ALL|wx.EXPAND, 5)
+
+            btn = wx.Button(p, -1, "Set Font")
+            self.Bind(wx.EVT_BUTTON, self.OnSetFont, btn)
+            vBtnSizer.Add(btn, 0, wx.ALL|wx.EXPAND, 5)
+
+            btn = wx.Button(p, -1, "Set Value")
+            self.Bind(wx.EVT_BUTTON, self.OnSetValue, btn)
+            vBtnSizer.Add(btn, 0, wx.ALL|wx.EXPAND, 5)
+
+            for x in range(5):
+                btn = wx.Button(p, -1, " ")
+                vBtnSizer.Add(btn, 0, wx.ALL|wx.EXPAND, 5)
+
+            hBtnSizer = wx.BoxSizer(wx.HORIZONTAL)
+            for x in range(3):
+                btn = wx.Button(p, -1, " ")
+                hBtnSizer.Add(btn, 0, wx.ALL, 5)
+
+            sizer = wx.BoxSizer(wx.HORIZONTAL)
+            col1 = wx.BoxSizer(wx.VERTICAL)
+            col1.Add(self.eom, 0, wx.ALL, 10)
+            col1.Add(hBtnSizer)
+            sizer.Add(col1)
+            sizer.Add(vBtnSizer)
+            p.SetSizer(sizer)
+
+            # Put the panel in a sizer for the frame so we can use self.Fit()
+            frameSizer = wx.BoxSizer()
+            frameSizer.Add(p, 1, wx.EXPAND)
+            self.SetSizer(frameSizer)
+            
+            self.Fit()
+
+
+        def OnRefit(self, evt):
+            # The Expando control will redo the layout of the
+            # sizer it belongs to, but sometimes this may not be
+            # enough, so it will send us this event so we can do any
+            # other layout adjustments needed.  In this case we'll
+            # just resize the frame to fit the new needs of the sizer.
+            self.Fit()
+
+        def OnSetMaxHeight(self, evt):
+            mh = self.eom.GetMaxHeight()
+            dlg = wx.NumberEntryDialog(self, "", "Enter new max height:",
+                                       "MaxHeight", mh, -1, 1000)
+            if dlg.ShowModal() == wx.ID_OK:
+                self.eom.SetMaxHeight(dlg.GetValue())
+            dlg.Destroy()
+            
+            
+        def OnSetFont(self, evt):
+            dlg = wx.FontDialog(self, wx.FontData())
+            dlg.GetFontData().SetInitialFont(self.eom.GetFont())
+            if dlg.ShowModal() == wx.ID_OK:
+                self.eom.SetFont(dlg.GetFontData().GetChosenFont())
+            dlg.Destroy()
+            
+            
+        def OnSetValue(self, evt):
+            self.eom.SetValue("This is a test...  Only a test.  If this had "
+                              "been a real emergency you would have seen the "
+                              "quick brown fox jump over the lazy dog.")
+        
+
+    app = wx.App(False)
+    frm = TestFrame()
+    frm.Show()
+    #import wx.py
+    #shell = wx.py.shell.ShellFrame(frm, size=(500,300),
+    #                               locals={'wx':wx, 'frm':frm})
+    #shell.Show()
+    frm.Raise()
+    app.MainLoop()
+
+
+