]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/expando.py
A patch from Frame Niessink which adds an additional style that
[wxWidgets.git] / wxPython / wx / lib / expando.py
1 #---------------------------------------------------------------------------
2 # Name: expando.py
3 # Purpose: A multi-line text control that expands and collapses as more
4 # or less lines are needed to display its content.
5 #
6 # Author: Robin Dunn
7 #
8 # Created: 18-Sept-2006
9 # RCS-ID: $Id$
10 # Copyright: (c) 2006 by Total Control Software
11 # Licence: wxWindows license
12 #
13 #---------------------------------------------------------------------------
14 """
15 This module contains the `ExpandoTextCtrl` which is a multi-line
16 text control that will expand its height on the fly to be able to show
17 all the lines of the content of the control.
18 """
19
20 import wx
21 import wx.lib.newevent
22
23
24 # This event class and binder object can be used to catch
25 # notifications that the ExpandoTextCtrl has resized itself and
26 # that layout adjustments may need to be made.
27 wxEVT_ETC_LAYOUT_NEEDED = wx.NewEventType()
28 EVT_ETC_LAYOUT_NEEDED = wx.PyEventBinder( wxEVT_ETC_LAYOUT_NEEDED, 1 )
29
30
31 #---------------------------------------------------------------------------
32
33 class ExpandoTextCtrl(wx.TextCtrl):
34 """
35 The ExpandoTextCtrl is a multi-line wx.TextCtrl that will
36 adjust its height on the fly as needed to accomodate the number of
37 lines needed to display the current content of the control. It is
38 assumed that the width of the control will be a fixed value and
39 that only the height will be adjusted automatically. If the
40 control is used in a sizer then the width should be set as part of
41 the initial or min size of the control.
42
43 When the control resizes itself it will attempt to also make
44 necessary adjustments in the sizer hierarchy it is a member of (if
45 any) but if that is not suffiecient then the programmer can catch
46 the EVT_ETC_LAYOUT_NEEDED event in the container and make any
47 other layout adjustments that may be needed.
48 """
49 _defaultHeight = -1
50
51 def __init__(self, parent, id=-1, value="",
52 pos=wx.DefaultPosition, size=wx.DefaultSize,
53 style=0, validator=wx.DefaultValidator, name="expando"):
54 # find the default height of a single line control
55 self.defaultHeight = self._getDefaultHeight(parent)
56 # make sure we default to that height if none was given
57 w, h = size
58 if h == -1:
59 h = self.defaultHeight
60 # always use the multi-line style
61 style = style | wx.TE_MULTILINE | wx.TE_NO_VSCROLL | wx.TE_RICH2
62 # init the base class
63 wx.TextCtrl.__init__(self, parent, id, value, pos, (w, h),
64 style, validator, name)
65 # save some basic metrics
66 self.extraHeight = self.defaultHeight - self.GetCharHeight()
67 self.numLines = 1
68 self.maxHeight = -1
69 if value:
70 wx.CallAfter(self._adjustCtrl)
71
72 self.Bind(wx.EVT_TEXT, self.OnTextChanged)
73
74
75 def SetMaxHeight(self, h):
76 """
77 Sets the max height that the control will expand to on its
78 own, and adjusts it down if needed.
79 """
80 self.maxHeight = h
81 if h != -1 and self.GetSize().height > h:
82 self.SetSize((-1, h))
83
84 def GetMaxHeight(self):
85 """Sets the max height that the control will expand to on its own"""
86 return self.maxHeight
87
88
89 def SetFont(self, font):
90 wx.TextCtrl.SetFont(self, font)
91 self.numLines = -1
92 self._adjustCtrl()
93
94 def WriteText(self, text):
95 # work around a bug of a lack of a EVT_TEXT when calling
96 # WriteText on wxMac
97 wx.TextCtrl.WriteText(self, text)
98 self._adjustCtrl()
99
100 def AppendText(self, text):
101 # Instead of using wx.TextCtrl.AppendText append and set the
102 # insertion point ourselves. This works around a bug on wxMSW
103 # where it scrolls the old text out of view, and since there
104 # is no scrollbar there is no way to get back to it.
105 self.SetValue(self.GetValue() + text)
106 self.SetInsertionPointEnd()
107
108
109 def OnTextChanged(self, evt):
110 # check if any adjustments are needed on every text update
111 self._adjustCtrl()
112 evt.Skip()
113
114
115 def _adjustCtrl(self):
116 # if the current number of lines is different than before
117 # then recalculate the size needed and readjust
118 numLines = self.GetNumberOfLines()
119 if numLines != self.numLines:
120 self.numLines = numLines
121 charHeight = self.GetCharHeight()
122 height = numLines * charHeight + self.extraHeight
123 if not (self.maxHeight != -1 and height > self.maxHeight):
124 # The size is changing... if the control is not in a
125 # sizer then we just want to change the size and
126 # that's it, the programmer will need to deal with
127 # potential layout issues. If it is being managed by
128 # a sizer then we'll change the min size setting and
129 # then try to do a layout. In either case we'll also
130 # send an event so the parent can handle any special
131 # layout issues that it wants to deal with.
132 if self.GetContainingSizer() is not None:
133 mw, mh = self.GetMinSize()
134 self.SetMinSize((mw, height))
135 if self.GetParent().GetSizer() is not None:
136 self.GetParent().Layout()
137 else:
138 self.GetContainingSizer().Layout()
139 else:
140 self.SetSize((-1, height))
141 # send notification that layout is needed
142 evt = wx.PyCommandEvent(wxEVT_ETC_LAYOUT_NEEDED, self.GetId())
143 evt.SetEventObject(self)
144 evt.height = height
145 evt.numLines = numLines
146 self.GetEventHandler().ProcessEvent(evt)
147
148
149 def _getDefaultHeight(self, parent):
150 # checked for cached value
151 if self.__class__._defaultHeight != -1:
152 return self.__class__._defaultHeight
153 # otherwise make a single line textctrl and find out its default height
154 tc = wx.TextCtrl(parent)
155 sz = tc.GetSize()
156 tc.Destroy()
157 self.__class__._defaultHeight = sz.height
158 return sz.height
159
160
161 if wx.VERSION < (2,7) and 'wxGTK' in wx.PlatformInfo:
162 # the wxGTK version of GetNumberOfLines in 2.6 doesn't count
163 # wrapped lines, so we need to implement our own. This is
164 # fixed in 2.7.
165 def GetNumberOfLines(self):
166 text = self.GetValue()
167 width = self.GetSize().width
168 dc = wx.ClientDC(self)
169 dc.SetFont(self.GetFont())
170 count = 0
171 for line in text.split('\n'):
172 count += 1
173 w, h = dc.GetTextExtent(line)
174 if w > width:
175 # the width of the text is wider than the control,
176 # calc how many lines it will be wrapped to
177 count += self._wrapLine(line, dc, width)
178
179 if not count:
180 count = 1
181 return count
182
183
184 def _wrapLine(self, line, dc, width):
185 # Estimate where the control will wrap the lines and
186 # return the count of extra lines needed.
187 pte = dc.GetPartialTextExtents(line)
188 width -= wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X)
189 idx = 0
190 start = 0
191 count = 0
192 spc = -1
193 while idx < len(pte):
194 if line[idx] == ' ':
195 spc = idx
196 if pte[idx] - start > width:
197 # we've reached the max width, add a new line
198 count += 1
199 # did we see a space? if so restart the count at that pos
200 if spc != -1:
201 idx = spc + 1
202 spc = -1
203 start = pte[idx]
204 else:
205 idx += 1
206 return count
207
208 #---------------------------------------------------------------------------