#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import wx
import wx.combo

class MyChoice(wx.combo.ComboCtrl):
    def __init__(self,parent,choices,selection,size,pos):
        wx.combo.ComboCtrl.__init__(self,parent,size=size,pos=pos)
        self.UseAltPopupWindow()
        MyComboPopup(self,choices,selection)
        self.TextCtrl.SetEditable(False)
        self.TextCtrl.Bind(wx.EVT_SET_FOCUS,self.onGotFocus)
        self.TextCtrl.Bind(wx.EVT_KILL_FOCUS,self.onLostFocus)
        self.nav = False

    def onGotFocus(self,evt):
        if self.nav:  # user has made a selection, so move on
            self.nav = False
            wx.CallAfter(self.Navigate)
        else:
            self.TextCtrl.SetSelection(-1,-1)
        evt.Skip()

    def onLostFocus(self,evt):
        self.TextCtrl.SetSelection(0,0)
        evt.Skip()

class MyComboPopup(wx.combo.ComboPopup):
    def __init__(self,comboCtrl,choices,selection):
        self.comboCtrl = comboCtrl
        self.choices = choices
        self.curitem = self.selection = selection
        wx.combo.ComboPopup.__init__(self)
        comboCtrl.SetPopupControl(self)
        comboCtrl.SetValue(choices[selection])

    def Create(self, parent):
        # Create the popup child control.  Return true for success.
        self.listBox = wx.ListBox(parent,choices=self.choices)
        self.listBox.WindowStyle |= wx.WANTS_CHARS
        self.listBox.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
        self.listBox.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
        return True

    def OnKeyDown(self, evt):
        code = evt.KeyCode
        if code == wx.WXK_DOWN:
            if self.curitem < len(self.choices)-1:
                self.curitem += 1
                self.listBox.Select(self.curitem)
        elif code == wx.WXK_UP:
            if self.curitem > 0:
                self.curitem -= 1
                self.listBox.Select(self.curitem)
        elif code == wx.WXK_ESCAPE:
            self.Dismiss()
        elif code in (wx.WXK_F4, wx.WXK_TAB, wx.WXK_SPACE,
                wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER):
            self.selection = self.curitem
            self.comboCtrl.nav = True
            self.Dismiss()
#       evt.Skip()

    def OnLeftUp(self, evt):
        item = self.listBox.HitTest(evt.Position)
        if item > -1:
            self.selection = item
            self.Dismiss()

    def GetControl(self):
        # Return the widget that is to be used for the popup
        return self.listBox

    def GetStringValue(self):
        # Return a string representation of the current item.
        return self.choices[self.selection]

    def OnPopup(self):
        # Called immediately after the popup is shown
        self.comboCtrl.TextCtrl.SetSelection(0,0)
        self.comboCtrl.TextCtrl.Editable = False
        self.curitem = self.selection
        self.listBox.Select(self.selection)
        wx.combo.ComboPopup.OnPopup(self)

    def OnDismiss(self):
        # Called when popup is dismissed
        self.comboCtrl.TextCtrl.Editable = True
        wx.combo.ComboPopup.OnDismiss(self)

    def OnComboKeyEvent(self, evt):
        # Receives key events from the parent ComboCtrl.  Events not
        # handled should be skipped, as usual.
        code = evt.KeyCode
        if code in (wx.WXK_SPACE, wx.WXK_F4) and not evt.AltDown():
            self.comboCtrl.ShowPopup()
        elif code == wx.WXK_DOWN:
            if self.selection < len(self.choices)-1:
                self.selection += 1
                self.comboCtrl.SetValue(self.choices[self.selection])
                wx.CallAfter(self.comboCtrl.TextCtrl.SetSelection,-1,-1)
        elif code == wx.WXK_UP:
            if self.selection > 0:
                self.selection -= 1
                self.comboCtrl.SetValue(self.choices[self.selection])
                wx.CallAfter(self.comboCtrl.TextCtrl.SetSelection,-1,-1)
#       evt.Skip()

    def GetAdjustedSize(self, minWidth, prefHeight, maxHeight):
        # Return final size of popup. Called on every popup, just prior to OnPopup.
        # minWidth = preferred minimum width for window
        # prefHeight = preferred height. Only applies if > 0,
        # maxHeight = max height for window, as limited by screen size
        #   and should only be rounded down, if necessary.
        if sys.platform == 'win32':
            prefHeight = 15 * len(self.choices)
        else:
            prefHeight = 26 * len(self.choices)
        return wx.combo.ComboPopup.GetAdjustedSize(self, minWidth, prefHeight, maxHeight)

class MySpecialTextCtrl(wx.TextCtrl):
    def __init__(self, parent, id, value, pos, size, choices):
        wx.TextCtrl.__init__(self, parent, id, value, pos, size)
        self.Choices = wx.ListBox(parent, -1, (pos[0], pos[1]+30),
            (size[0], -1), choices=choices)
        self.Choices.Hide()
        self.Bind(wx.EVT_SET_FOCUS, self.OnGotFocus)
        self.Bind(wx.EVT_KILL_FOCUS, self.OnLostFocus)

    def OnGotFocus(self, evt):
        self.Choices.Show()
        evt.Skip()

    def OnLostFocus(self, evt):
        self.Choices.Hide()
        evt.Skip()

class MyPanel(wx.Panel):
    def __init__(self,parent):
        wx.Panel.__init__(self,parent,-1)

        choices = ['Category 1','Category 2','Category 3']
        selection = 1  # i.e. Category 2

        wx.StaticText(self,-1,'Field1',pos=(50,30))
        self.t1 = wx.TextCtrl(self,-1,'',size=(180,-1),pos=(150,30))

        wx.StaticText(self,-1,'wx.ComboBox',pos=(50,80))
        MyChoice(self,choices,selection,size=(180,-1),pos=(150,80))

        wx.StaticText(self,-1,'Field2',pos=(50,130))
        MySpecialTextCtrl(self, -1, '', size=(180,-1), pos=(150,130),
            choices=choices)

        wx.StaticText(self,-1,'wx.Choice',pos=(50,180))
        ch = wx.Choice(self, -1, (150,180), (180,-1), choices)
        ch.Selection = 1

        wx.StaticText(self,-1,'Field3',pos=(50,230))
        wx.TextCtrl(self,-1,'',size=(180,-1),pos=(150,230))

        self.SetFocus()

class MyFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self,None,-1,"Custom ComboCtrl test",size=(400,300))
        panel = MyPanel(self)
        self.CentreOnScreen()

class MyApp(wx.App):
    def OnInit(self):
        frame = MyFrame()
        frame.Show(1)
        return 1

app = MyApp(0)
app.MainLoop()
