diff --git a/unittests/test_lib_expando.py b/unittests/test_lib_expando.py new file mode 100644 index 00000000..ac9f16dd --- /dev/null +++ b/unittests/test_lib_expando.py @@ -0,0 +1,25 @@ +import imp_unittest, unittest +import wtc +import wx +import wx.lib.expando + +#--------------------------------------------------------------------------- + +class lib_expando_Tests(wtc.WidgetTestCase): + + def test_lib_expando1(self): + pnl = wx.Panel(self.frame) + w = wx.lib.expando.ExpandoTextCtrl(pnl, value="This is a test", pos=(10,10)) + bs1 = w.GetSize() + + w.AppendText("\nThis is a New Label") + bs2 = w.GetSize() + + self.assertEqual(w.GetValue(), "This is a test\nThis is a New Label") + self.assertEqual(w.GetNumberOfLines(), 3) + self.assertTrue(bs2.height > bs1.height) + +#--------------------------------------------------------------------------- + +if __name__ == '__main__': + unittest.main() diff --git a/wx/lib/expando.py b/wx/lib/expando.py index 72fcab04..7b1c8045 100644 --- a/wx/lib/expando.py +++ b/wx/lib/expando.py @@ -9,12 +9,101 @@ # RCS-ID: $Id$ # Copyright: (c) 2006 by Total Control Software # Licence: wxWindows license +# Tags: phoenix-port, unittest, documented # #--------------------------------------------------------------------------- + """ -This module contains the `ExpandoTextCtrl` which is a multi-line +This module contains the :class:`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. + + +Description +=========== + +The :class:`ExpandoTextCtrl` is a multi-line :class:`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. + + +Usage +===== + +Sample usage:: + + import wx + from wx.lib.expando import ExpandoTextCtrl, EVT_ETC_LAYOUT_NEEDED + + class MyFrame(wx.Frame): + + def __init__(self): + + wx.Frame.__init__(self, None, 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, "Write Text") + self.Bind(wx.EVT_BUTTON, self.OnWriteText, btn) + vBtnSizer.Add(btn, 0, wx.ALL|wx.EXPAND, 5) + + btn = wx.Button(p, -1, "Append Text") + self.Bind(wx.EVT_BUTTON, self.OnAppendText, btn) + vBtnSizer.Add(btn, 0, wx.ALL|wx.EXPAND, 5) + + sizer = wx.BoxSizer(wx.HORIZONTAL) + col1 = wx.BoxSizer(wx.VERTICAL) + col1.Add(self.eom, 0, wx.ALL, 10) + 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 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") + + def OnAppendText(self, evt): + self.eom.AppendText("\nAppended text.") + + app = wx.App(0) + frame = MyFrame() + frame.Show() + app.MainLoop() + """ import wx @@ -25,7 +114,7 @@ import wx.lib.newevent # 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 ) +EVT_ETC_LAYOUT_NEEDED = wx.PyEventBinder(wxEVT_ETC_LAYOUT_NEEDED, 1) #--------------------------------------------------------------------------- @@ -46,12 +135,32 @@ class ExpandoTextCtrl(wx.TextCtrl): the EVT_ETC_LAYOUT_NEEDED event in the container and make any other layout adjustments that may be needed. """ + _defaultHeight = -1 _leading = 1 # TODO: find a way to calculate this, it may vary by platform def __init__(self, parent, id=-1, value="", pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, validator=wx.DefaultValidator, name="expando"): + """ + Default class constructor. + + :param `parent`: parent window, must not be ``None``; + :param integer `id`: window identifier. A value of -1 indicates a default value; + :param string `value`: the control text label; + :param `pos`: the control position. A value of (-1, -1) indicates a default position, + chosen by either the windowing system or wxPython, depending on platform; + :param `size`: the control size. A value of (-1, -1) indicates a default size, + chosen by either the windowing system or wxPython, depending on platform; + :param integer `style`: the underlying :class:`Control` style; + :param Validator `validator`: the window validator; + :param string `name`: the widget name. + + :type parent: :class:`Window` + :type pos: tuple or :class:`Point` + :type size: tuple or :class:`Size` + """ + # 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 @@ -76,30 +185,78 @@ class ExpandoTextCtrl(wx.TextCtrl): def SetMaxHeight(self, h): """ - Sets the max height that the control will expand to on its + Sets the maximum height that the control will expand to on its own, and adjusts it down if needed. + + :param integer `h`: the maximum control height, in pixels. """ + 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""" + """ + Returns the maximum height that the control will expand to on its own. + + :rtype: int + """ + return self.maxHeight def SetFont(self, font): - wx.TextCtrl.SetFont(self, font) + """ + Sets the font for the :class:`ExpandoTextCtrl`. + + :param Font font: font to associate with the :class:`ExpandoTextCtrl`, pass + ``NullFont`` to reset to the default font. + + :rtype: bool + :returns: ``True`` if the font was really changed, ``False`` if it was already + set to this font and nothing was done. + """ + + retVal = wx.TextCtrl.SetFont(self, font) self.numLines = -1 self._adjustCtrl() + return retVal + + def WriteText(self, text): + """ + Writes the text into the text control at the current insertion position. + + :param string `text`: text to write to the text control. + + .. note:: + + Newlines in the text string are the only control characters allowed, and they + will cause appropriate line breaks. See :meth:`AppendText` for more convenient + ways of writing to the window. After the write operation, the insertion point + will be at the end of the inserted text, so subsequent write operations will + be appended. To append text after the user may have interacted with the control, + call :meth:`TextCtrl.SetInsertionPointEnd` before writing. + + """ + # 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): + """ + Appends the text to the end of the text control. + + :param string `text`: text to write to the text control. + + .. seealso:: :meth:`WriteText` + """ + # 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 @@ -109,12 +266,24 @@ class ExpandoTextCtrl(wx.TextCtrl): def OnTextChanged(self, evt): + """ + Handles the ``wx.EVT_TEXT`` event for :class:`ExpandoTextCtrl`. + + :param `event`: a :class:`CommandEvent` event to be processed. + """ + # check if any adjustments are needed on every text update self._adjustCtrl() evt.Skip() def OnSize(self, evt): + """ + Handles the ``wx.EVT_SIZE`` event for :class:`ExpandoTextCtrl`. + + :param `event`: a :class:`SizeEvent` event to be processed. + """ + # The number of lines needed can change when the ctrl is resized too. self._adjustCtrl() evt.Skip() @@ -147,7 +316,7 @@ class ExpandoTextCtrl(wx.TextCtrl): else: self.SetSize((-1, height)) # send notification that layout may be needed - evt = wx.PyCommandEvent(wxEVT_ETC_LAYOUT_NEEDED, self.GetId()) + evt = wx.CommandEvent(wxEVT_ETC_LAYOUT_NEEDED, self.GetId()) evt.SetEventObject(self) evt.height = height evt.numLines = numLines diff --git a/wx/lib/newevent.py b/wx/lib/newevent.py index 72aecaa2..ac807e70 100644 --- a/wx/lib/newevent.py +++ b/wx/lib/newevent.py @@ -1,4 +1,108 @@ -"""Easy generation of new events classes and binder objects""" +#--------------------------------------------------------------------------- +# Name: newevent.py +# Purpose: Easy generation of new events classes and binder objects. +# +# Author: Miki Tebeka +# +# Created: 18-Sept-2006 +# RCS-ID: $Id$ +# Copyright: (c) 2006 by Total Control Software +# Licence: wxWindows license +# Tags: phoenix-port, documented +# +#--------------------------------------------------------------------------- + +""" +Easy generation of new events classes and binder objects. + + +Description +=========== + +This module contains two functions which makes the generation of custom wxPython events +particularly easy. + + +Usage +===== + +Sample usage:: + + import wx + import time + import threading + + import wx.lib.newevent as NE + + MooEvent, EVT_MOO = NE.NewEvent() + GooEvent, EVT_GOO = NE.NewCommandEvent() + + DELAY = 0.7 + + def evt_thr(win): + time.sleep(DELAY) + wx.PostEvent(win, MooEvent(moo=1)) + + def cmd_thr(win, id): + time.sleep(DELAY) + wx.PostEvent(win, GooEvent(id, goo=id)) + + ID_CMD1 = wx.NewId() + ID_CMD2 = wx.NewId() + + class Frame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, "MOO") + sizer = wx.BoxSizer(wx.VERTICAL) + self.Bind(EVT_MOO, self.on_moo) + b = wx.Button(self, -1, "Generate MOO") + sizer.Add(b, 1, wx.EXPAND) + b.Bind(wx.EVT_BUTTON, self.on_evt_click) + b = wx.Button(self, ID_CMD1, "Generate GOO with %d" % ID_CMD1) + sizer.Add(b, 1, wx.EXPAND) + b.bind(wx.EVT_BUTTON, self.on_cmd_click) + b = wx.Button(self, ID_CMD2, "Generate GOO with %d" % ID_CMD2) + sizer.Add(b, 1, wx.EXPAND) + b.Bind(wx.EVT_BUTTON, self.on_cmd_click) + + self.Bind(EVT_GOO, self.on_cmd1, ID_CMD1) + self.Bind(EVT_GOO, self.on_cmd2, ID_CMD2) + + self.SetSizer(sizer) + self.SetAutoLayout(True) + sizer.Fit(self) + + def on_evt_click(self, e): + t = threading.Thread(target=evt_thr, args=(self, )) + t.setDaemon(True) + t.start() + + def on_cmd_click(self, e): + t = threading.Thread(target=cmd_thr, args=(self, e.GetId())) + t.setDaemon(True) + t.start() + + def show(self, msg, title): + dlg = wx.MessageDialog(self, msg, title, wx.OK) + dlg.ShowModal() + dlg.Destroy() + + def on_moo(self, e): + self.show("MOO = %s" % e.moo, "Got Moo") + + def on_cmd1(self, e): + self.show("goo = %s" % e.goo, "Got Goo (cmd1)") + + def on_cmd2(self, e): + self.show("goo = %s" % e.goo, "Got Goo (cmd2)") + + + app = wx.App(0) + f = Frame() + f.Show(True) + app.MainLoop() + +""" __author__ = "Miki Tebeka " @@ -7,30 +111,37 @@ import wx #--------------------------------------------------------------------------- def NewEvent(): - """Generate new (Event, Binder) tuple - e.g. MooEvent, EVT_MOO = NewEvent() """ + Generates a new `(event, binder)` tuple:: + + MooEvent, EVT_MOO = NewEvent() + + """ + evttype = wx.NewEventType() - class _Event(wx.PyEvent): + class _Event(wx.Event): def __init__(self, **kw): - wx.PyEvent.__init__(self) + wx.Event.__init__(self) self.SetEventType(evttype) self.__dict__.update(kw) return _Event, wx.PyEventBinder(evttype) - def NewCommandEvent(): - """Generate new (CmdEvent, Binder) tuple - e.g. MooCmdEvent, EVT_MOO = NewCommandEvent() """ + Generates a new `(command_event, binder)` tuple:: + + MooCmdEvent, EVT_MOO = NewCommandEvent() + + """ + evttype = wx.NewEventType() - class _Event(wx.PyCommandEvent): + class _Event(wx.CommandEvent): def __init__(self, id, **kw): - wx.PyCommandEvent.__init__(self, evttype, id) + wx.CommandEvent.__init__(self, evttype, id) self.__dict__.update(kw) return _Event, wx.PyEventBinder(evttype, 1) @@ -106,7 +217,7 @@ def _test(): self.show("goo = %s" % e.goo, "Got Goo (cmd2)") - app = wx.PySimpleApp() + app = wx.App(0) f = Frame() f.Show(True) app.MainLoop()