git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@43142 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
		
			
				
	
	
		
			209 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			209 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#---------------------------------------------------------------------------
 | 
						|
# 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 = 1
 | 
						|
        self.maxHeight = -1
 | 
						|
        if value:
 | 
						|
            wx.CallAfter(self._adjustCtrl)
 | 
						|
                        
 | 
						|
        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 WriteText(self, text):
 | 
						|
        # work around a bug of a lack of a EVT_TEXT when calling
 | 
						|
        # WriteText on wxMac
 | 
						|
        wx.TextCtrl.WriteText(self, text)
 | 
						|
        self._adjustCtrl()
 | 
						|
 | 
						|
    def AppendText(self, text):
 | 
						|
        # Instead of using wx.TextCtrl.AppendText append and set the
 | 
						|
        # insertion point ourselves.  This works around a bug on wxMSW
 | 
						|
        # where it scrolls the old text out of view, and since there
 | 
						|
        # is no scrollbar there is no way to get back to it.
 | 
						|
        self.SetValue(self.GetValue() + text)
 | 
						|
        self.SetInsertionPointEnd()
 | 
						|
 | 
						|
 | 
						|
    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 'wxGTK' in wx.PlatformInfo: ## and wx.VERSION < (2,7):   it's broke again in 2.7.2...
 | 
						|
        # 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:
 | 
						|
                    # 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
 | 
						|
 | 
						|
#---------------------------------------------------------------------------
 |