ChoiceAutoFind implementation

Hi folks,

from the PythonCard List, Ray Allen wrote:

"""
Is there a way to configure choice boxes so they autofill using more than
the first letter?
eg. List of countries available from choice box. If I want to select
'United Kingdom', have to click U five times (after passing a number of
other countries beginning with U) to get there. I would like to type 'un'
and end up with countries beginning with 'un'. Currently, this would result
in me seeing countries beginning in N!
Hello, I have tried to implement a
"""

I tried to implement such a class (with the option, displaying the currently
typed in letters (the buffers) or not.

Any suggestions for improving the code or the design in common are highly welcome.
Also for bad coding.

thank you in advance!

#======= Begin Code

#!/usr/bin/env python

# Franz Steinhaeusler, 04.05.2006
# ChoiceAutoFind.py

import wx

class ChoiceAutoFind(wx.Choice):
    """ChoiceAutoFind: it will search for elements which fits a whole typed in word fragment."""
    def __init__(self, parent, id=-1, pos=wx.DefaultPosition,
                 size=wx.DefaultSize, choices=[], style=0, showbuffer=False):
        """Create the ChoiceAutoFind Control."""
        wx.Choice.__init__(self, parent, id, pos, size, choices, style)
        self.keybuffer = ""
        self.showbuffer = showbuffer

        if showbuffer:
            # create a static text to show current keybuffer.
            x, y = self.GetPosition()
            w, h = self.GetSize()
            self.StaticText = wx.StaticText(parent, pos=(x+w+5, y+3))
            self.StaticText.SetBackgroundColour(wx.Colour(255, 255, 192))

        self.Bind(wx.EVT_CHAR, self.EvtChar)
        self.Bind(wx.EVT_SET_FOCUS, self.OnFocus)
        #self.Bind(wx.EVT_CHOICE, self.EvtChoice)

    def OnFocus(self, event):
        """ChoiceAutoFind gets the focus. => Clear the buffer."""
        self.keybuffer = ""
        self.ShowBuffer()
        event.Skip()
        
    def EvtChar(self, event):
        """Process current typed in key."""
        if event.GetKeyCode() > 255 or event.GetKeyCode() == wx.WXK_RETURN:
            # example: cursor keys should work as expected.
            # and return should not fill the keybuffer list.
            event.Skip()
            return
        if event.GetKeyCode() == wx.WXK_BACK:
            # delete last key in buffer
            if len (self.keybuffer) > 0:
                self.keybuffer = self.keybuffer[:-1]
        elif event.GetKeyCode() == wx.WXK_ESCAPE:
            # delete buffer with Escape key.
            self.keybuffer = ""
        else:
            # extend buffer with current pressed key.
            self.keybuffer += chr (event.GetKeyCode())
        self.ShowBuffer()
          
        # try to find in the list
        for i in range(self.GetCount()):
            if self.GetString(i).find(self.keybuffer) == 0:
                self.SetSelection(i)
                return
                
    #def EvtChoice(self, event):
    # """This could be skipped."""
    # print '1. EvtChoice: %s\n' % event.GetString()
    # event.Skip()
        
    def ShowBuffer(self):
        """Display Key Buffer."""
        if self.showbuffer:
            if self.keybuffer == "":
                self.StaticText.SetLabel("")
            else:
                self.StaticText.SetLabel("'" + self.keybuffer + "'")

class TestChoiceAutoFindPanel(wx.Panel):
    """Test Panel for ChoiceAutoFind."""
    def __init__(self, parent, showbuffer=False):
        """Create the TestChoiceAutoFind Panel."""
        wx.Panel.__init__(self, parent, -1)

        sampleList = ['zero', 'one', 'two', 'three', 'thirteen', 'twenty']

        wx.StaticText(self, -1, "Choice Auto Find", (15, 10))
        wx.StaticText(self, -1, "Select one:", (15, 50), (75, -1))
        self.ch = ChoiceAutoFind(self, -1, (100, 50), choices=sampleList, showbuffer=showbuffer)
        self.ch.Bind(wx.EVT_CHOICE, self.EvtChoice)
        
    def EvtChoice(self, event):
        """Display currently selected entry.."""
        #print '2. EvtChoice: %s\n' % event.GetString()
        event.Skip()
        
if __name__ == "__main__":
    app = wx.App(0)
    frame = wx.Frame(None)
    #TestChoiceAutoFindPanel(frame)
    TestChoiceAutoFindPanel(frame, showbuffer=True)
    frame.Show()
    app.MainLoop()

#======= End Code

···

--

Franz Steinhaeusler

Franz Steinhaeusler:

I tried to implement such a class (with the option, displaying the currently
typed in letters (the buffers) or not.

Hi Franz, just a thought. If you'd make this a mixin class rather than a subclass of wx.Choice, maybe it would be relatively easy to mix it in with other widgets such as wx.Combobox and wx.ListCtrl?

Cheers, Frank

Some suggestions I can remember:

Maybe skip the tab key too.

Make the escape key clear only the buffer and not reset the choice, or
at least reset to the lastest choice, the one that is selected before
the user starts typing, and not always to the first one.

Ricardo

···

On Thu, 2006-05-04 at 15:33 +0200, Franz Steinhaeusler wrote:

Any suggestions for improving the code or the design in common are highly welcome.
Also for bad coding.

    def EvtChar(self, event):
        """Process current typed in key."""
        if event.GetKeyCode() > 255 or event.GetKeyCode() == wx.WXK_RETURN:
            # example: cursor keys should work as expected.
            # and return should not fill the keybuffer list.
            event.Skip()
            return

Any suggestions for improving the code or the design in common are highly welcome.
Also for bad coding.

    def EvtChar(self, event):
        """Process current typed in key."""
        if event.GetKeyCode() > 255 or event.GetKeyCode() == wx.WXK_RETURN:
            # example: cursor keys should work as expected.
            # and return should not fill the keybuffer list.
            event.Skip()
            return

Some suggestions I can remember:

Maybe skip the tab key too.

Make the escape key clear only the buffer and not reset the choice, or
at least reset to the lastest choice, the one that is selected before
the user starts typing, and not always to the first one.

Ricardo

···

On Thu, 04 May 2006 16:03:27 +0100, Ricardo Pedroso <ricardo.pedroso@netvisao.pt> wrote:

On Thu, 2006-05-04 at 15:33 +0200, Franz Steinhaeusler wrote:

===========================================

On Thu, 04 May 2006 17:00:28 +0200, Frank Niessink <frank@niessink.com> wrote:

Hi Franz, just a thought. If you'd make this a mixin class rather than a
subclass of wx.Choice, maybe it would be relatively easy to mix it in
with other widgets such as wx.Combobox and wx.ListCtrl?

Hello Frank and Ricardo,

thank you both for your suggestions.

I will try to implement this with a mixin class.

What is a proper example to study this?
the listctrl mixins? Also in the "Py" Package there are mixin
classes. I will take a look at these ones.

For the tab key: Now It jumps to the next ctrl, and this should be ok.
Or did you mean that another way?

Yes, this with the ESC key is a good idea.

--

Franz Steinhaeusler

No, it was the way I mean.

Ricardo

···

On Fri, 2006-05-05 at 08:33 +0200, Franz Steinhaeusler wrote:

On Thu, 04 May 2006 16:03:27 +0100, Ricardo Pedroso <ricardo.pedroso@netvisao.pt> wrote:

>On Thu, 2006-05-04 at 15:33 +0200, Franz Steinhaeusler wrote:
>
For the tab key: Now It jumps to the next ctrl, and this should be ok.
Or did you mean that another way?

Hello,

Second try:

implemented skipping Tab key, restore the old position after hitting esc,
and tried to implement this as Mixin class.

It should also work with Combobox, as you can see in the commented out code.

#!/usr/bin/env python

# Franz Steinhaeusler, 08.05.2006
# AutoFindMixin.py

import wx

class AutoFindMixin:
    """AutoFindMixin: it will search for elements which fits a whole typed in word fragment."""
    def __init__(self, parent, showbuffer=False):
        """Creates the AutoFindMixin Class."""
        #taken from listctrl Mixins class
        control = self.GetControl()
        if not control:
            raise ValueError, "No Control available"

        self.keybuffer = ""
        self.showbuffer = showbuffer
        self.lastpos = self.GetSelection()

        if showbuffer:
            # create a static text to show current keybuffer.
            x, y = self.GetPosition()
            w, h = self.GetSize()
            self.StaticText = wx.StaticText(parent, pos=(x+w+5,y+3))
            self.StaticText.SetBackgroundColour(wx.Colour(255, 255, 192))

        #bind the contrrol
        control.Bind(wx.EVT_CHAR, self.OnChar)
        control.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
        control.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)

        self.Bind(wx.EVT_CHOICE, self.OnChoice)
        
    def OnKillFocus(self, event):
        """AutoFindMixin control loses the focus. => save the last postion of the selection."""
        self.lastpos = self.GetSelection()
        event.Skip()

    def OnSetFocus(self, event):
        """AutoFindMixin control gets the focus. => Clear the buffer."""
        self.keybuffer = ""
        self.ShowBuffer()
        #print self.lastpos
        #self.SetSelection(self.lastpos)
        event.Skip()
        
    def OnChar(self, event):
        """Process current typed in key."""
        if event.GetKeyCode() > 255 or event.GetKeyCode() in (wx.WXK_RETURN, wx.WXK_TAB):
            # example: cursor keys should work as expected.
            # and return and tab should not fill the keybuffer list.
            event.Skip()
            return
        if event.GetKeyCode() == wx.WXK_BACK:
            # delete last key in buffer
            if len (self.keybuffer) > 0:
                self.keybuffer = self.keybuffer[:-1]
        elif event.GetKeyCode() == wx.WXK_ESCAPE:
            # delete buffer with Escape key.
            self.keybuffer = ""
            self.SetSelection(self.lastpos)
            self.ShowBuffer()
            return
        else:
            # extend buffer with current pressed key.
            self.keybuffer += chr (event.GetKeyCode())
            #print self.lastpos

        self.ShowBuffer()
          
        # try to find in the list
        for i in range(self.GetCount()):
            if self.GetString(i).find(self.keybuffer) == 0:
                # save lastpos before first typing a char
                if len(self.keybuffer)==1 and not event.GetKeyCode() == wx.WXK_BACK:
                    self.lastpos = self.GetSelection()
                self.SetSelection(i)
                #print self.lastpos
                return

    def OnChoice(self, event):
        """This could be skipped."""
        #print '1. EvtChoice: %s\n' % event.GetString(),
        self.lastpos = self.GetSelection()
        #print self.lastpos
        event.Skip()

    def ShowBuffer(self):
        """Display Key Buffer."""
        if self.showbuffer:
            if self.keybuffer == "":
                self.StaticText.SetLabel("")
            else:
                self.StaticText.SetLabel("'" + self.keybuffer + "'")

···

On Fri, 05 May 2006 14:45:34 +0100, Ricardo Pedroso <ricardo.pedroso@netvisao.pt> wrote:

On Fri, 2006-05-05 at 08:33 +0200, Franz Steinhaeusler wrote:

On Thu, 04 May 2006 16:03:27 +0100, Ricardo Pedroso <ricardo.pedroso@netvisao.pt> wrote:

>On Thu, 2006-05-04 at 15:33 +0200, Franz Steinhaeusler wrote:
>
For the tab key: Now It jumps to the next ctrl, and this should be ok.
Or did you mean that another way?

No, it was the way I mean.

Ricardo

===============================================================

#!/usr/bin/env python

# Franz Steinhaeusler, 08.05.2006
# ChoiceAutoFind.py Demo Application

import wx
from AutoFindMixin import AutoFindMixin

class ChoiceAutoFind(wx.Choice, AutoFindMixin):
#test class ChoiceAutoFind(wx.ComboBox, AutoFindMixin):
    """ChoiceAutoFind: Demo Sample to present the AutoFindMixin class with a wx.Choice."""
    def __init__(self, parent, id=-1, pos=wx.DefaultPosition,
                 size=wx.DefaultSize, choices=, style=0, showbuffer=False):
        """Create the ChoiceAutoFind Control."""
        wx.Choice.__init__(self, parent, id, pos, size, choices, style)
        #test: wx.ComboBox.__init__(self, parent, id, "", pos, size, choices, style)
        AutoFindMixin.__init__(self, parent, showbuffer)

    # this class must be overwritten
    def GetControl(self):
        """returns the assigned control."""
        return self

class TestChoiceAutoFindPanel(wx.Panel):
    """Test Panel for ChoiceAutoFind."""
    def __init__(self, parent, showbuffer=False):
        """Create the TestChoiceAutoFind Panel."""
        wx.Panel.__init__(self, parent, -1)

        wx.StaticText(self, -1, "Select one:", pos=(15, 10), size=(75, -1))

        sampleList = ['zero', 'one', 'two', 'three', 'thirteen', 'twenty']
        wx.StaticText(self, -1, "TextCtrl:", pos=(15, 50), size=(75, -1))
        txt = wx.TextCtrl(self, pos=(100,50))
        self.ch = ChoiceAutoFind(self, pos=(100, 10), choices=sampleList, showbuffer=showbuffer)
        self.ch.SetFocus()
        
        self.ch.Bind(wx.EVT_CHOICE, self.OnChoice)
        
    def OnChoice(self, event):
        """Display currently selected entry.."""
        #print '2. EvtChoice: %s\n' % event.GetString()
        event.Skip()
        
if __name__ == "__main__":
    app = wx.App(0)
    frame = wx.Frame(None, title="Choice Auto Find", size=(270, 110))
    frame.Center()
    #TestChoiceAutoFindPanel(frame)
    TestChoiceAutoFindPanel(frame, showbuffer=True)
    frame.Show()
    app.MainLoop()

--

Franz Steinhaeusler