if __name__=="__main__":
    import wxversion
    try:
        wxversion.select('wx-3.0.1-msw-classic', True)
    except:
        print "wxPython 3.0.1 not installed"
import wx
import re
import traceback

__all__ = ['ComboSearchVListBox'] # Export only the ComboSearchVListBox widget

import logging
l = logging.getLogger()
l.setLevel(logging.CRITICAL) #change CRITICAL to DEBUG to print a ton of output


# ---------------------------------------------------------------------------
class MyVListBox(wx.VListBox):
    def __init__(self, parent, wordWrap=False):
        wx.VListBox.__init__(self, parent, -1, style=wx.LC_SINGLE_SEL|wx.SIMPLE_BORDER )#| wx.WANTS_CHARS)
        #self.SetExtraStyle(wx.WS_EX_BLOCK_EVENTS)
        self._wordWrap = wordWrap
        self.SetSelectionBackground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT))
        # VListBox is virtualized, meaning all the GUI elements are drawn-on-demand as they're displayed
        # This means no data is stored in the list widget, so we'll use a simple list instead
        self.itemList = []
        self.hoverEnabled = True
        self._previousSelection = 0
        self._previousValue = ''


    def _pageup(self):
        start = self.GetVisibleRowsBegin()
        i=start
        h=self.GetSize()[1]
        heightOfPrevPageRows = 0
        while True:
            if i==0:
                break
            i-=1
            heightOfPrevPageRows += self.OnMeasureItem(i)

            if heightOfPrevPageRows>h:
                i+=1
                break
        #print start, i, start-i
        self.ScrollRows(i-start)
        #self.SetSelection(self.GetVisibleRowsBegin())
        return self.GetVisibleRowsBegin()

    def _pagedown(self):
        numShown = self.GetVisibleRowsEnd() - self.GetVisibleRowsBegin()
        #print numShown
        success = self.ScrollRows(numShown-1)
        if success:            
            item = self.GetVisibleRowsEnd()-2
        else:
            item = self.GetItemCount()-1
        #self.SetSelection(item)
        return item
    
    # This method must be overridden.  When called it should draw the
    # n'th item on the dc within the rect.  How it is drawn, and what
    # is drawn is entirely up to you.
    def OnDrawItem(self, dc, rect, n):
        #logging.debug("OnDrawItem %s %s" % (rect, n))
        if self.GetSelection() == n:
            c = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT)
            b = wx.Colour(154,179,209)
            if not hasattr(self, 'originalBackgroundColor'):
                self.originalBackgroundColor = dc.GetTextBackground()
            dc.SetTextBackground(b)
        else:
            c = self.GetForegroundColour()
            if hasattr(self, 'originalBackgroundColor'):
                dc.SetTextBackground(self.originalBackgroundColor)
        dc.SetFont(self.GetFont())
        dc.SetTextForeground(c)
        
        #dc.SetBrush(wx.Brush(item.GetBackgroundColour()))
        #dc.SetPen(wx.Pen(item.GetBackgroundColour()))
    
        #draw lines separating each entry
        pen = wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DLIGHT), 1, wx.SOLID)
        dc.SetPen((self.GetBackgroundColour() == wx.WHITE and [pen] or [wx.WHITE_PEN])[0])
        dc.DrawLine(rect[0],rect[1],rect[2],rect[1])
        
        dc.DrawLabel(self._getItemText(n), rect,
                     wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL)

    # This method must be overridden.  It should return the height
    # required to draw the n'th item.
    def OnMeasureItem(self, n):
        #logging.debug('OnMeasureItem')


        #get the current string
        text = self.GetItemText(n)
        
        #w, h = self.GetTextExtent(text)
        #return h
        #text = self._currentTextToBeDrawn

        height = 0
        for line in text.split('\n'):
            w, h = super(wx.VListBox, self).GetTextExtent(line)
            height += h
        return height + 5

    #def GetTextExtent(self, text):
    def prepDataForWordWrap(self, text):
        #print 'prepDataForWordWrap'
        #get the current string
        #text = self.GetItemText(n)
        #self.actualItems = self.itemList
        dc = wx.ClientDC(self)
 
        #OnMeasureItem event occurs before OnDrawItem
        #Check if wordWrap is turned on, split the text up
        #Then save the text to a temp variable for 
        #OnDrawItem to consume
        #if self._wordWrap:
        #for i, text in enumerate(self.actualItems):
        idealw, idealh = dc.GetTextExtent(text)
        w=self.GetRect().GetWidth()
        
        height=idealh
        width=w 
        #reset newlines
        text.replace("\n", "")
        
        #if i==11:
        #    print text
        idealw, idealh = dc.GetTextExtent(text)
        height = idealh

        curW = idealw
        fractionIndex=0
        while curW>w:
            lenFraction = float(w)/idealw
            fractionIndex = int(lenFraction*len(text)) + fractionIndex
            text = "%s\n%s" % (text[:fractionIndex], text[fractionIndex:] )
            curW-=w
            height += idealh
        #print text
        return text
        
        #return width, height

    def _getItemText(self, n):
        return self.OnGetItem(n)

    def AddItem(self, txt):
        self.InsertItem(self.GetItemCount(), txt)

    def InsertItem(self, index, item):
        self.itemList.insert(index, item)
        self.SetItemCount(len(self.itemList))
        self.Refresh()

    def Append(self, item):
        return self.AppendItem(item)

    def AppendItem(self, item):
        self.itemList.append(item)
        self.SetItemCount(len(self.itemList))
        self.Refresh()
        #return the index
        return len(self.itemList)-1

    def Clear(self):
        self.itemList=[]
        self.SetItemCount(0)
        try:
            del self.originalData
        except:
            logging.debug(traceback.format_exc())
        self.Refresh()        

    def DeleteAllItems(self):
        return self.Clear()

    def GetItemCount(self):
        return len(self.itemList)

    def OnGetItem(self, n):
        #logging.debug("OnGetItem %d" % n)
        if n>=0:
            if self._wordWrap:
                return self.prepDataForWordWrap(self.itemList[n])
            return self.itemList[n]
        else:
            return None

    def GetItemText(self, n):
        return self.OnGetItem(n)
    
    def SetItemText(self, n, val):
        self.itemList[n]=val

    def FindItem(self, val):
        try:
            return self.itemList.index(val)
        except ValueError:
            return wx.NOT_FOUND


class BasePopupFrame(wx.Frame):
    """ 
    The PopupFrame is the frame that is popped up by ComboSearchVListBox.
    It contains the VListBox of items that the user can select one item from. Upon
    selection, or when escape is pressed, the frame is hidden.
    

    If the user cancels selecting a new item from the VListBox,
    e.g. by hitting escape, the previous value 
    (the one that was selected before the PopupFrame was popped up) is restored.
    """

    platform = wx.PlatformInfo[0][4:7]
    def __init__(self, parent, wordWrap):
        super(BasePopupFrame, self).__init__(parent,
            style= wx.NO_BORDER| wx.FRAME_NO_TASKBAR | wx.FRAME_FLOAT_ON_PARENT &#(wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT )&
                  ~(wx.RESIZE_BORDER | wx.CAPTION)) 
        self._createInterior(wordWrap)
        self._layoutInterior()
        self._bindEventHandlers()
        self._IsPopupShown = False

    if platform == 'GTK':
        # On wxGTK, Alt-Up also closes the popup:
        def _keyShouldHidePopup(self, keyEvent):
            return self._keyShouldHidePopupDefault(keyEvent) or \
                (keyEvent.AltDown() and keyEvent.GetKeyCode() == wx.WXK_UP)
    else:
        def _keyShouldHidePopup(self, keyEvent):
            return self._keyShouldHidePopupDefault(keyEvent)

    def _keyShouldHidePopupDefault(self, keyEvent):
        return keyEvent.GetKeyCode() == wx.WXK_ESCAPE

    def _createInterior(self, wordWrap):
        self._VListBox = MyVListBox(self, wordWrap)
        #self._VListBox.AddRoot('Hidden root node')

    def _layoutInterior(self):
        frameSizer = wx.BoxSizer(wx.HORIZONTAL)
        frameSizer.Add(self._VListBox, flag=wx.EXPAND, proportion=1)
        self.SetSizerAndFit(frameSizer)

    def _bindEventHandlers(self):
        self._VListBox.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
        self._VListBox.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
        #self._VListBox.Bind(wx.EVT_LISTBOX, self.OnItemActivated)
        #self._VListBox.Bind(wx.EVT_LISTBOX_DCLICK , self.OnItemActivated)
        self._VListBox.Bind(wx.EVT_MOTION , self.OnMouseHover)
        self._VListBox.Bind(wx.EVT_LEFT_DOWN, self.OnMouseClick)
        

    def OnKeyDown(self, keyEvent):
        logging.debug('@BasePopupFrame OnKeyDown')
        
        if self._keyShouldHidePopup(keyEvent):
            logging.debug("@BasePopupFrame if self._keyShouldHidePopup(keyEvent):")
            logging.debug('@BasePopupFrame OnKeyDown escape pressed, popup detected as open')
            self.GetParent()._text.SetFocus()
            self.GetParent().NotifyNoItemSelected()
            self.Hide()
            return
        if keyEvent.GetKeyCode() in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
            logging.debug("@BasePopupFrame should process enter")
            item = self._VListBox.GetSelection()
            self.GetParent().NotifyItemSelected(self._VListBox.GetItemText(item))
            self.GetParent()._text.SetFocus()
        else:
            logging.debug(keyEvent.GetKeyCode())
        
        keyEvent.Skip()

    def OnKeyUp(self, keyEvent):
        item = self._VListBox.GetSelection()
        self.GetParent()._text.ChangeValue(self._VListBox.GetItemText(item))


    def OnMouseHover(self, event):
        if not self._VListBox.hoverEnabled:
            return
        item = self._VListBox.HitTest(event.GetPosition())
        if item>=0:
            self.GetParent()._text.ChangeValue(self._VListBox.GetItemText(item))
            self._VListBox.SetSelection(item)
            #self._text.SetFocus()            
        else:
            event.Skip()
    
    def OnMouseClick(self, event):
        logging.debug('@BasePopupFrame OnMouseClick')
        item = self._VListBox.HitTest(event.GetPosition())
        if item>=0:
            #self._VListBox.SetSelection(item)
            self.GetParent().NotifyItemSelected(self._VListBox.GetItemText(item))
            #self.Hide()
        else:
            event.Skip()

    def OnItemActivated(self, event):
        item = event.GetEventObject()
        logging.debug("@BasePopupFrame OnItemActivated item: %s" % repr(item))
        logging.debug(dir(event))
        item = item.GetSelection()
        print type(event)
        print repr(event)
        if hasattr(event, "GetKeyCode"):
            self.GetParent()._text.ChangeValue(self._VListBox.GetItemText(item))
        else:
            self.GetParent().NotifyItemSelected(self._VListBox.GetItemText(item))
        event.Skip()

    def Show(self):
        logging.debug("@BasePopupFrame Show")
        super(BasePopupFrame, self).Show()
        self._IsPopupShown = True
        wx.Yield()
        wx.CallAfter(self.GetParent()._text.SetFocus)

    def Hide(self):
        logging.debug("@BasePopupFrame Hide")
        #call wx.Frame's Hide method
        super(BasePopupFrame, self).Hide()

        #swap the button arrow direction
        self.GetParent()._hideButton.Hide()
        self.GetParent()._showButton.Show()
        self.GetParent()._showButton.GetParent().Layout()
        self._IsPopupShown = False
        
    def GetVListBox(self):
        return self._VListBox

    def GetTextCtrl(self):
        return self._text

    def GetShowButton(self):
        return self._showButton

    def GetHideButton(self):
        return self._hideButton        

# ---------------------------------------------------------------------------

class ComboSearchVListBox(wx.Panel):
    """
    ComboSearchVListBox is the base class for platform specific versions of the
    ComboSearchVListBox.
    """
    platform = wx.PlatformInfo[0][4:7]
    def __init__(self, *args, **kwargs):
        style = kwargs.pop('style', 0)
        wordWrap = kwargs.pop('wordWrap') if 'wordWrap' in kwargs else False
        if style & wx.CB_READONLY:
            style &= ~wx.CB_READONLY # We manage readonlyness ourselves
            self._readOnly = True
        else:
            self._readOnly = False
        if style & wx.CB_SORT:
            style &= ~wx.CB_SORT # We manage sorting ourselves
            self._sort = True
        else:
            self._sort = False

        super(ComboSearchVListBox, self).__init__(style=style, *args, **kwargs)
        self._createInterior(wordWrap)
        self._layoutInterior()
        self._bindEventHandlers()

    # Methods to construct the widget.

    def _createInterior(self, wordWrap):
        self._lastString = ''
        self._popupFrame = self._createPopupFrame(wordWrap)
        self._text = self._createTextCtrl()
        self._showButton = self._createDropDownButton()
        self._hideButton = self._createPullUpButton()
        self._VListBox = self._popupFrame.GetVListBox()

    def _createTextCtrl(self):
        if self._readOnly:
            style = wx.TE_READONLY
        else:
            style = 0
        return wx.TextCtrl(self, style=style|wx.TE_PROCESS_ENTER)

    def _createPullUpButton(self):
        downBitmap = self.MakeDropDownBitmap()
        img = downBitmap.ConvertToImage()
        img_centre = wx.Point( img.GetWidth()/2, img.GetHeight()/2 )
        #img = img.Rotate( 3.14, img_centre, False )
        img = img.Rotate90()
        img = img.Rotate90()
        upBitmap = img.ConvertToBitmap() #  wx.BitmapFromImage(img)
        #del downBitmap
        btn = wx.BitmapButton(self, bitmap=upBitmap, name ='_hideButton')
        btn.name = '_hideButton'
        btn.Hide()
        return btn

    def _createDropDownButton(self):
        return wx.BitmapButton(self, bitmap=self.MakeDropDownBitmap())

    def MakeDropDownBitmap(self, name=''):
        #Because we're faking a ComboBox, we need to draw the down-arrow for the button ourselves
        bw, bh = 10, 16
        bmp = wx.EmptyBitmap(bw,bh)

        bmp.name = name

        dc = wx.MemoryDC(bmp)
        render = wx.RendererNative.Get()
        dc.SetBrush(wx.BLACK_BRUSH)
        dc.SetTextForeground(wx.BLACK)
        dc.SetFont(wx.NORMAL_FONT)
        # clear to a specific background colour
        bgcolor = wx.Colour(255,254,255)
        dc.SetBackground(wx.Brush(bgcolor))
        dc.Clear()

        #draw the down arrow on the dc
        render.DrawDropArrow(self, dc, (0, 0, 10, 16), wx.CONTROL_CURRENT)
        #render.DrawDropArrow(self, dc, (35, 100, 24, 24), wx.CONTROL_PRESSED)
        #render.DrawDropArrow(self, dc, (55, 100, 24, 24), wx.CONTROL_CURRENT | wx.CONTROL_DISABLED)

        del dc

        # now apply a mask using the bgcolor
        bmp.SetMaskColour(bgcolor)
        return bmp

    def _layoutInterior(self):
        self.panelSizer = wx.BoxSizer(wx.HORIZONTAL)
        self.panelSizer.Add(self._text, flag=wx.EXPAND, proportion=1)
        self.panelSizer.Add(self._showButton,0)#, wx.LEFT, 10)
        self.panelSizer.Add(self._hideButton, 0)# , wx.LEFT, 10)
        self.SetSizerAndFit(self.panelSizer)

    def _createPopupFrame(self, wordWrap):
        return BasePopupFrame(self, wordWrap)

    def _bindEventHandlers(self):
        for eventSource, eventType, eventHandler in self._eventsToBind():
            eventSource.Bind(eventType, eventHandler)

    def _eventsToBind(self):
        """ 
        _eventsToBind returns a list of eventSource, eventType,
        eventHandlers tuples that will be bound. This method can be 
        extended to bind additional events. In that case, don't 
        forget to call _eventsToBind on the super class.
        
        :return: [(eventSource, eventType, eventHandlers), ]
        :rtype: list
        
        """
        return [(self._text, wx.EVT_KEY_DOWN, self.OnKeyDown),
                (self._showButton, wx.EVT_BUTTON, self.OnDropDownButtonClick),
                (self._hideButton, wx.EVT_BUTTON, self.OnDropDownButtonClick),
                #enable the following line to popup the list when the text-box is clicked
                #(self._text, wx.EVT_LEFT_DOWN, self.OnTextCtrlClick),
                (self._text, wx.EVT_CHAR, self.ProcessKeyboard),
                (self._text, wx.EVT_KILL_FOCUS, self.OnKillFocus),
                #(self._popupFrame,wx.EVT_CHAR, self.ProcessKeyboard),
                #(self._VListBox,wx.EVT_KEY_DOWN, self.OnKeyDown),
                (self._popupFrame,wx.EVT_KEY_DOWN, self.OnKeyDown),
                (self._text, wx.EVT_MOUSEWHEEL, self.ScrollViaMouseWheel),
                (self._popupFrame, wx.EVT_MOUSEWHEEL, self.ScrollViaMouseWheel),
                (self._VListBox, wx.EVT_SCROLLWIN_THUMBRELEASE, self.ScrollBarClickRelease),
                
                (wx.GetTopLevelParent(self) , wx.EVT_MOVE, self.OnComboBoxMove),
                #(self.GetParent().GetParent(), wx.EVT_SIZE, self.OnComboBoxMove)
                (wx.GetTopLevelParent(self), wx.EVT_SIZE, self.OnComboBoxMove)
                ]
    
    def ScrollBarClickRelease(self, event):
        logging.debug('ComboSearchVListBox ScrollBarClickRelease')
        self._text.SetFocus()
        event.Skip()

    def re_enable_hover(self, event):
        self._VListBox.hoverEnabled=True

    def ScrollViaMouseWheel(self, event):
        self._VListBox.hoverEnabled=False
        self.hoverDisableTimer = wx.Timer(self, -1)
        self.hoverDisableTimer.Start(500, oneShot = True)
        self.Bind(wx.EVT_TIMER, self.re_enable_hover)

        direction = event.GetWheelRotation()
        #logging.debug('ScrollViaMouseWheel direction: %d' % direction)

        def GetKeyCode():
            return wx.WXK_DOWN if direction<0 else wx.WXK_UP
        
        hitLowerScrollLimit = direction<0 and self._VListBox.GetItemCount()-1 == self._VListBox.GetSelection()
        hitUpperScrollLimit = direction>0 and 0 == self._VListBox.GetSelection()
        if hitLowerScrollLimit or hitUpperScrollLimit:
            return
        event.GetKeyCode = GetKeyCode
        
        d=abs(direction)
        #account for scroll speed, user wants to scroll faster!
        d= ((d/120))*d
        while d>0:
            self._navigateUpOrDown(event)
            d-=120
            wx.SafeYield(onlyIfNeeded=True)

    def ProcessKeyboard(self, event):
        logging.debug('**********************')
        event.Skip()

        if not hasattr(event, "GetKeyCode"):
            logging.debug("text changed PROGRAMMATICALLY")
        #if not self._text.IsEditable():
            return
        else:
            keycode = event.GetKeyCode()
            string = self._text.GetValue()
            logging.debug('keycode pressed: %s' % str(keycode))
            logging.debug("searchFtestList STRING BEFORE: %s" % string)
            insertPosition = self._text.GetInsertionPoint()
            selectionInd1, selectionInd2 = self._text.GetSelection()

            if (keycode>31 and keycode<256 and keycode!=127):
                logging.debug("alphanumeric key pressed")
                #handle if the user had highlighted text already
                if selectionInd1!=selectionInd2:
                    string = string[:selectionInd1] + chr(keycode) + string[selectionInd2:]
                else:
                    string = string[:selectionInd1] + chr(keycode) + string[selectionInd2:]
            elif keycode in [314, 316] or (keycode in [312, 317, 366, 367] and event.ShiftDown()):
                logging.debug('keycode: %d and shift pressed, returning' % keycode)
                return
            else:
                
                logging.debug("insertPosition==selectionInd1 %s" % str(insertPosition==selectionInd1))
                logging.debug(selectionInd1)
                logging.debug(selectionInd2)
                if keycode == wx.WXK_DELETE or keycode == wx.WXK_NUMPAD_DELETE:
                    logging.debug("delete pressed")
                    if selectionInd1!=selectionInd2:
                        string = string[:selectionInd1] + string[selectionInd2:]
                    else:
                        string = string[:selectionInd1] + string[selectionInd2+1:]
                elif keycode == wx.WXK_BACK:
                    logging.debug("backspace pressed")
                    if selectionInd1!=selectionInd2:
                        string = string[:selectionInd1] + string[selectionInd2:]
                    else:
                        string = string[:selectionInd1-1] + string[selectionInd2:]
                elif keycode == 1:
                    self._text.SelectAll()
                    return
                else:
                    self._text.SetInsertionPointEnd()
                    return
            
            logging.debug("searchFtestList STRING AFTER: %s\n" % string)
        #combo = self._text.GetParent()
        popupList = self._VListBox # vlist.GetParent().GetParent()

        """
        if string == self._lastString:
            logging.debug('string == self._lastString')
            #self._VListBox.SetSelection(self._VListBox.FindItem(string))
            if not self.IsPopupShown():
                self.Popup()

            return
        """
        self._lastString = string

        if string.strip() == '':
            self._VListBox.itemList = self._VListBox.originalData
            self._VListBox.SetItemCount(len(self._VListBox.itemList))

            if not self.IsPopupShown():
                self.Popup()

                #self.Hide()
            self._VListBox.SetSelection(0)
            return False

        #only set the original data the first time it doesn't exist
        if not hasattr(self._VListBox, 'originalData'):
            self._VListBox.originalData = self._VListBox.itemList
        
        if hasattr(self._VListBox, 'originalData'):
            self._VListBox.itemList = self._VListBox.originalData
            self._VListBox.SetItemCount(len(self._VListBox.itemList))
        
        
        #compile the regec
        try:
            regex = re.compile(string, re.IGNORECASE)
        except Exception as e:
            logging.debug(traceback.format_exc())
            return
        
        #search the item list
        newItemList = []
        for test in self._VListBox.itemList:
            if regex.search(test):
                newItemList.append(test)

        #set the vlistbox's item list directly
        self._VListBox.itemList = newItemList
        self._VListBox.SetItemCount(len(self._VListBox.itemList))

        #only show the popup list if it is hidden
        logging.debug('IsPopupShown == %s' % self.IsPopupShown())
        if not self.IsPopupShown():
            logging.debug('supposed to Show Popup')
            self.Popup()
            self._text.SetFocus()
            self._text.SetInsertionPointEnd()            
        self._VListBox.Refresh()
        if len(self._VListBox.itemList):
            self._VListBox.SetSelection(0)

    # Event handlers    
    def OnTextCtrlClick(self, event):
        if not self.IsPopupShown():
            self.Popup()
        event.Skip()

    def OnComboBoxMove(self, event):
        if self.IsPopupShown():
            logging.debug('hiding from OnComboBoxMove')
            self.HidePopup()
        event.Skip()

    def OnKillFocus(self, event):
        nextWidget = event.GetWindow()
        logging.debug('OnKillFocus, %s was clicked' % nextWidget )
        selfWidgets = [self._popupFrame, self._VListBox, self._text, self._showButton, self._hideButton]
        if not (nextWidget in selfWidgets):
            print "OnKillFocus hiding"
            self.NotifyNoItemSelected()
            self.HidePopup()
        event.Skip()

    def OnDropDownButtonClick(self, event):
        if event.GetEventObject().GetName() == '_hideButton':
            logging.debug('pullup button clicked')
            #self._hideButton.Hide()
            
            self.NotifyNoItemSelected()
            self.HidePopup()
            self._text.SetFocus()
        else:
            if not hasattr(self._VListBox, 'originalData'):
                self._VListBox.originalData = self._VListBox.itemList
            self._VListBox.itemList = self._VListBox.originalData
            self._VListBox.SetItemCount(len(self._VListBox.itemList))
            self.Popup()
            self._text.SetFocus()
        # Note that we don't call event.Skip() to prevent popping up the
        # ComboBox's own box.

    def OnKeyDown(self, keyEvent):
        logging.debug("ComboSearchVListBox OnKeyDown")
        if self._keyShouldNavigate(keyEvent):
            logging.debug("ComboSearchVListBox OnKeyDown self._keyShouldNavigate")
            self._navigateUpOrDown(keyEvent)
            return
        elif self._keyShouldPopUpVListBox(keyEvent):
            logging.debug('ComboSearchVListBox OnKeyDown self._keyShouldPopUpVListBox')
            self.Popup()
        elif self._popupFrame._keyShouldHidePopup(keyEvent):
            logging.debug('ComboSearchVListBox OnKeyDown _keyShouldHidePopup')
            print self.IsPopupShown()
            if self.IsPopupShown():
                self._text.SetFocus()
                self.NotifyNoItemSelected()
                self.HidePopup()
            return
        elif keyEvent.GetKeyCode() in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
            
            item = self._VListBox.GetSelection()
            logging.debug("ComboSearchVListBox OnKeyDown RETURN PRESSED on item: %s --- itemText: %s" % (repr(item), self._VListBox.GetItemText(item)))
            if self.IsPopupShown():
                #self.NotifyItemSelected(self._VListBox.GetItemText(item))
                
                self.NotifyItemSelected(self._text.GetValue())
                self.HidePopup()
                self._text.SetFocus()
                return
            else:
                #self._VListBox.SetSelection(self._VListBox.FindItem(self._text.GetValue()))
                self.NotifyItemSelected(self._text.GetValue())
            
            #return
            #else:
            #    self._VListBox.SetSelection(-1)
        else:
            logging.debug("ComboSearchVListBox else Skip")
        keyEvent.Skip()

    def _keyShouldPopUpVListBoxDefault(self, keyEvent):
        return (keyEvent.AltDown() or keyEvent.MetaDown()) and \
                keyEvent.GetKeyCode() == wx.WXK_DOWN

    if platform == 'MSW':

        def _keyShouldPopUpVListBox(self, keyEvent):
            return self._keyShouldPopUpVListBoxDefault(keyEvent) or \
                (keyEvent.GetKeyCode() == wx.WXK_F4 and not keyEvent.HasModifiers()) or \
                ((keyEvent.AltDown() or keyEvent.MetaDown()) and \
                  keyEvent.GetKeyCode() == wx.WXK_UP)       
    
    else:

        def _keyShouldPopUpVListBox(self, keyEvent):
            return self._keyShouldPopUpVListBoxDefault(keyEvent)

    def _keyShouldNavigate(self, keyEvent):
        return keyEvent.GetKeyCode() in (wx.WXK_DOWN, wx.WXK_UP, wx.WXK_PAGEUP, wx.WXK_PAGEDOWN) and not \
            self._keyShouldPopUpVListBox(keyEvent) and not keyEvent.ShiftDown()

    def _navigateUpOrDown(self, keyEvent):
        if not self.IsPopupShown():
            item = self._VListBox._previousSelection
        else:
            item = self.GetSelection()
        itemText = self._VListBox.GetItemText(item)
        logging.debug('_navigateUpOrDown previousSelection: %d, _previousValue: %s' % (self._VListBox._previousSelection, self._VListBox._previousValue))
        logging.debug('_navigateUpOrDown item: %d, text: %s' % (item, itemText))

        if not self.IsPopupShown():
            if not hasattr(self._VListBox, 'originalData'):
                self._VListBox.originalData = self._VListBox.itemList    
            self._VListBox.itemList = self._VListBox.originalData
            self._VListBox.SetItemCount(len(self._VListBox.itemList))
            self._VListBox.SetSelection(self._VListBox._previousSelection)
        itemOriginalLocation = item # self._VListBox.FindItem(itemText)

        #only popup the list if it wasn't already shown, if it was already open, get the next/prev item (or page)
        if self.IsPopupShown():
            if item>=0:
                logging.debug("_navigateUpOrDown popup already shown, trying to get next item")
                navigationMethods = {wx.WXK_DOWN: itemOriginalLocation+1, 
                                     wx.WXK_UP: itemOriginalLocation-1 if itemOriginalLocation>0 else 0,
                                     #pgup
                                     366 : self._VListBox._pageup,
                                     #pgdown
                                     367 : self._VListBox._pagedown }
                possibleNextItem = navigationMethods[keyEvent.GetKeyCode()]
                #call it if it's a function

                if hasattr(possibleNextItem, '__call__'):
                    #this will perform page-up or page-down using the functions above
                    item = possibleNextItem()
                else:
                    item = possibleNextItem #(item)
            else:
                item = 0

        if item>=0:
            logging.debug('_navigateUpOrDown item>=0')
            
            logging.debug('***************')
            newText = self._VListBox.GetItemText(item)
            logging.debug('_navigateUpOrDown newText: %s newItem: %d' %(newText, item))
            if newText:
                self._text.ChangeValue(newText)
            self.SetSelection(item)
        if not self._popupFrame.IsShown():
            #disale mouse hover selection on the list before popup
            #if the mouse is over the area where the popup /will/ be,
            #even if not moving, EVT_MOTION is generated
            self._VListBox.hoverEnabled=False
            self.hoverDisableTimer = wx.Timer(self, -1)
            self.hoverDisableTimer.Start(50, oneShot = True)
            self.Bind(wx.EVT_TIMER, self.re_enable_hover)
            self.Popup()
        self._text.SetFocus()
        self._text.SetInsertionPointEnd()

    def OnText(self, event):    
        textValue = self._text.GetValue()
        selection = self._VListBox.GetSelection()
        logging.debug("!OnText textValue %s, selection: %s" % (textValue, selection))
        logging.debug("OnText self._VListBox.GetItemText(selection) %s" % self._VListBox.GetItemText(selection))
        logging.debug("GetItemText(selection) == textValue %s" % str(self._VListBox.GetItemText(selection) == textValue))
        if not selection>=0 or self._VListBox.GetItemText(selection) != textValue:
            logging.debug("not selection or self._VListBox.GetItemText(selection) != textValue:")
            # We need to change the selection because it doesn't match the
            # text just entered
            item = self.FindString(textValue)
            if item>=0:
                if not self._popupFrame.IsShown():
                    self.Popup()
                #self._VListBox.SetSelection(item)
            else:
                pass
                #self._VListBox.SetSelection(0)
                #self._VListBox.Unselect()
        event.Skip()
    # Methods called by the PopupFrame, to let the ComboSearchVListBox know
    # about what the user did.
    def NotifyNoItemSelected(self):
        """
        This is called by the PopupFrame when the user closes the 
        PopupFrame, without selecting an item.
        """
        """
        Restore the value copied previously, because the user has
        not selected a new value.
        """
        logging.debug('NotifyNoItemSelected, _previousValue: %s previousSelection %s' % (self._VListBox._previousValue, self._VListBox._previousSelection))
        self._text.ChangeValue(self._VListBox._previousValue)
        if hasattr(self._VListBox, 'originalData'):
            self._VListBox.itemList = self._VListBox.originalData
            self._VListBox.SetItemCount(len(self._VListBox.itemList))
        self._VListBox.SetSelection(self._VListBox._previousSelection)
        #else:
        #    print 'is this acting weird?'
        #    self._VListBox.SetSelection(self._VListBox.FindItem(self._VListBox._previousValue))
        #self.SetFocus()


    def NotifyItemSelected(self, text):
        """
        Simulate selection of an item by the user. This is meant to 
        be called by the PopupFrame when the user selects an item.
        """
        logging.debug("ComboSearchVListBox NotifyItemSelected text: %s"%(text))
        
        #set the originalData if it wasn't already (if user clicked drop-down button before typing/searching)
        if not hasattr(self._VListBox, 'originalData'):
            self._VListBox.originalData = self._VListBox.itemList

        popupShowDuplicateItems = self.list_duplicates_of(self._VListBox.itemList, text)
        logging.debug('ComboSearchVListBox NotifyItemSelected popupShowDuplicateItems %s' % popupShowDuplicateItems)

        origDuplicateItems = self.list_duplicates_of(self._VListBox.originalData, text)
        logging.debug('ComboSearchVListBox NotifyItemSelected origDuplicateItems %s' % origDuplicateItems)

        indexInPopup = self._VListBox.GetSelection()# - firstOccurenceIndex
        logging.debug('ComboSearchVListBox NotifyItemSelected indexInPopup %s' % indexInPopup)

        if indexInPopup in popupShowDuplicateItems:
            itemOccurenceInShowDupes = popupShowDuplicateItems.index(indexInPopup)
            logging.debug('ComboSearchVListBox NotifyItemSelected itemOccurenceInShowDupes %s' % itemOccurenceInShowDupes)

            indexInOriginalItems = origDuplicateItems[itemOccurenceInShowDupes]
            logging.debug('ComboSearchVListBox NotifyItemSelected indexInOriginalItems %s' % indexInOriginalItems)
        else:
            indexInOriginalItems = indexInPopup
        #firstOccurenceIndex = self._VListBox.originalData.index(text)
        print ''

        
        self._text.ChangeValue(text)
        self._VListBox._previousValue = self._text.GetValue()
        """
        indexInOriginalItems = firstOccurenceIndex
        i=0
        
        print 'indexInPopup', indexInPopup
        while True:
            print indexInOriginalItems
            if self._VListBox.originalData[indexInOriginalItems]==text:
                logging.debug("ComboSearchVListBox text matched! i==%d" % i)
                if i==indexInPopup:
                    logging.debug("ComboSearchVListBox i: %d indexInOriginalItems: %d"%(i, indexInOriginalItems))
                    break
                i+=1
            indexInOriginalItems+=1
        logging.debug("ComboSearchVListBox ALMOST DONE i: %d indexInOriginalItems: %d"%(i, indexInOriginalItems))
        """
        self._VListBox.itemList = self._VListBox.originalData
        self._VListBox.SetItemCount(len(self._VListBox.itemList))
        self._VListBox.SetSelection(indexInOriginalItems)
        self._VListBox._previousSelection = indexInOriginalItems
        self.HidePopup()
        self._postComboBoxSelectedEvent(text)
        

    def _postComboBoxSelectedEvent(self, text):
        """Simulate a selection event. """
        logging.debug('_postComboBoxSelectedEvent')
        event = wx.CommandEvent(wx.wxEVT_COMMAND_COMBOBOX_SELECTED, 
                                self.GetId())
        event.SetString(text)
        event.SetEventObject(self._text)
        self.GetEventHandler().ProcessEvent(event)
        
    def list_duplicates_of(self, seq,item):
        start_at = -1
        locs = []
        while True:
            try:
                loc = seq.index(item,start_at+1)
            except ValueError:
                break
            else:
                locs.append(loc)
                start_at = loc
        return locs

    # Misc methods, not part of the ComboBox API.

    def Popup(self):
        """Pops up the frame with the VListBox."""
        """
        Popup stores a copy of the current value, so we can
        restore it later (in NotifyNoItemSelected).
        """
        logging.debug("ComboSearchVListBox POPUP")
        
        comboBoxSize = self.GetSize()
        
        x, y = self.GetParent().ClientToScreen(self.GetPosition())
        y += comboBoxSize[1]
        width = comboBoxSize[0]
        height = 300
        self._popupFrame.SetDimensions(x, y, width, height)
        # On wxGTK, when the Combobox width has been increased a call 
        # to SetMinSize is needed to force a resize of the popupFrame: 
        self._popupFrame.SetMinSize((width, height)) 

        try:
            self._popupFrame.Show()
            self._popupFrame._IsPopupShown = True
            self._hideButton.Show()
            self._showButton.Hide()
            self._showButton.GetParent().Layout()
        except:
            #self._VListBox.itemList = self._VListBox.actualItems
            logging.debug(traceback.format_exc())
        
    def HidePopup(self):
        """Hide the popped up frame with the VListBox."""
        logging.debug('ComboSearchVListBox Hide')
        #self._VListBox.itemList = self._VListBox.actualItems
        self._popupFrame.Hide()
    
    def IsPopupShown(self):
        return self._popupFrame._IsPopupShown

    def GetVListBox(self):
        """Returns the VListBox control that is popped up."""
        return self._popupFrame.GetVListBox()

    def FindClientData(self, clientData, parent=None):
        """ 
        Finds the *first* item in the VListBox with client data equal to the
        given clientData. If no such item exists, an invalid item is
        returned. 
        
        :param PyObject `clientData`: the client data to find
        :keyword VListBoxItemId `parent`: :class:`VListBoxItemId` parent or None
        :return: :class:`VListBoxItemId`
        :rtype: :class:`VListBoxItemId`
        
        """
        parent = parent or self._VListBox.GetRootItem()
        child, cookie = self._VListBox.GetFirstChild(parent)
        while child:
            if self.GetClientData(child) == clientData:
                return child
            else:
                result = self.FindClientData(clientData, child)
                if result:
                    return result
            child, cookie = self._VListBox.GetNextChild(parent, cookie)
        return child

    def SetClientDataSelection(self, clientData):
        """
        Selects the item with the provided clientData in the control. 
        Returns True if the item belonging to the clientData has been 
        selected, False if it wasn't found in the control.
        
        :param PyObject `clientData`: the client data to find
        :return: True if an item has been selected, otherwise False
        :rtype: bool
        
        """
        item = self.FindClientData(clientData)
        if item:
            self._VListBox.SetSelection(item)
            string = self._VListBox.GetItemText(item)
            if self._text.GetValue() != string:
                self._text.SetValue(string)
            return True
        else:
            return False

    # The following methods are all part of the ComboBox API (actually
    # the ControlWithItems API) and have been adapted to take VListBoxItemIds 
    # as parameter and return :class:`VListBoxItemId`s, rather than indices.

    def Append(self, itemText):#, clientData=None):
        """ 
        Adds the itemText to the control, associating the given clientData 
        with the item if not None. If parent is None, itemText is added
        as a root item, else itemText is added as a child item of
        parent. The return value is the :class:`VListBoxItemId` of the newly added
        item.

        :param string `itemText`: text to add to the control
        :keyword VListBoxItemId `parent`: if None item is added as a root, else it
          is added as a child of the parent.
        :keyword PyObject `clientData`: the client data to find
        :return: :class:`VListBoxItemId` of newly added item
        :rtype: :class:`VListBoxItemId`
       
        """    
        return self._VListBox.AppendItem(itemText)#, data=wx.VListBoxItemData(clientData))
        

    def Clear(self):
        """Removes all items from the control."""
        return self._VListBox.DeleteAllItems()
        
    def Delete(self, item):
        """Deletes the item from the control."""
        return self._VListBox.Delete(item)

    def FindString(self, string, parent=None):
        return self._VListBox.FindItem(string)

    def GetSelection(self):
        """
        Returns the :class:`VListBoxItemId` of the selected item or an invalid item
        if no item is selected.
        
        :return: a VListBoxItemId
        :rtype: :class:`VListBoxItemId`
        
        """
        selectedItem = self._VListBox.GetSelection()
        return selectedItem
 
    def GetString(self, item):
        """
        Returns the label of the given item.
        
        :param VListBoxItemId `item`: :class:`VListBoxItemId` for which to get the label
        :return: label
        :rtype: string
        
        """
        if item:
            return self._VListBox.GetItemText(item)
        else:
            return ''

    def GetStringSelection(self):
        """
        Returns the label of the selected item or an empty string if no item 
        is selected.
        
        :return: the label of the selected item or an empty string
        :rtype: string
        
        """
        return self.GetValue()

    def Insert(self, itemText, previous=None, parent=None, clientData=None):
        """
        Insert an item into the control before the ``previous`` item 
        and/or as child of the ``parent`` item. The itemText is associated 
        with clientData when not None.
        
        :param string `itemText`: the items label
        :keyword VListBoxItemId `previous`: the previous item
        :keyword VListBoxItemId `parent`: the parent item
        :keyword PyObject `clientData`: the data to associate
        :return: the create :class:`VListBoxItemId`
        :rtype: :class:`VListBoxItemId`
        
        """
        data = wx.VListBoxItemData(clientData)
        if parent is None:
            parent = self._VListBox.GetRootItem()
        if previous is None:
            item = self._VListBox.InsertItemBefore(parent, 0, itemText, data=data)
        else:
            item = self._VListBox.InsertItem(parent, previous, itemText, data=data)
        if self._sort:
            self._VListBox.SortChildren(parent)
        return item

    def IsEmpty(self):
        """
        Returns True if the control is empty or False if it has some items.
        
        :return: True if control is empty
        :rtype: boolean
        
        """
        return self.GetCount() == 0

    def GetCount(self):
        """
        Returns the number of items in the control.
        
        :return: items in control
        :rtype: integer
        
        """
        return self._VListBox.GetCount() 

    def SetSelection(self, item):
        """ 
        Sets the provided item to be the selected item.
        
        :param VListBoxItemId `item`: Select this item
        
        """
        logging.debug('ComboSearchVListBox SetSelection %d' % item)
        if self._VListBox.GetItemCount()>0 and item<self._VListBox.GetItemCount():
            logging.debug("SetSelection VListBox.GetItemCount()>0")
            
            self._VListBox.SetSelection(item)
            self._VListBox.Raise()
            self._text.ChangeValue(self._VListBox.GetItemText(item))

            #item = self._VListBox.GetSelection()
            #self.GetParent().NotifyItemSelected(self._VListBox.GetItemText(item))    
        else:
            logging.debug("ComboSearchVListBox SetSelection else")
            self._text.ChangeValue('')



    def SetString(self, item, string):
        """
        Sets the label for the provided item.
        
        :param VListBoxItemId `item`: item on which to set the label
        :param string `string`: the label to set
        
        """
        self._VListBox.SetItemText(item, string)
        if self._sort:
            self._VListBox.SortChildren(self._VListBox.GetItemParent(item))

    def SetStringSelection(self, string):
        """
        Selects the item with the provided string in the control. 
        Returns True if the provided string has been selected, False if
        it wasn't found in the control.
        
        :param string `string`: try to select the item with this string
        :return: True if an item has been selected
        :rtype: boolean
        
        """
        item = self.FindString(string)
        if item:
            if self._text.GetValue() != string:
                self._text.ChangeValue(string)
            self._VListBox.SetSelection(item)
            return True
        else:
            return False

    def GetClientData(self, item):
        """
        Returns the client data associated with the given item, if any.
        
        :param VListBoxItemId `item`: item for which to get clientData
        :return: the client data
        :rtype: PyObject
        
        """
        return self._VListBox.GetItemPyData(item)

    def SetClientData(self, item, clientData):
        """
        Associate the given client data with the provided item.
        
        :param VListBoxItemId `item`: item for which to set the clientData
        :param PyObject `clientData`: the data to set
        
        """
        self._VListBox.SetItemPyData(item, clientData)

    def GetValue(self):
        """
        Returns the current value in the combobox text field.
        
        :return: the current value in the combobox text field
        :rtype: string
        
        """
        return self._text.GetValue()

    def SetValue(self, value):
        logging.debug('$SetValue BaseComboVList')
        """
        Sets the text for the combobox text field.

        NB: For a combobox with wxCB_READONLY style the string must be
        in the combobox choices list, otherwise the call to SetValue()
        is ignored.
        
        :param string `value`: set the combobox text field
        
        """
        item = self._VListBox.GetSelection()
        if not item or (item >= self._VListBox.GetItemCount()) or (self._VListBox.GetItemText(item) != value ):
            item = self.FindString(value)
        if self._readOnly and not item:
            return
        
        self._text.SetValue(value)

# ---------------------------------------------------------------------------

class myFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, "myFrame")
        self.panel = wx.Panel(self, -1)
        
        comboCtrl  = ComboSearchVListBox(self.panel, wx.ID_ANY, wordWrap=False)
        self.populateComboCtrl(comboCtrl)

        comboCtrl2  = ComboSearchVListBox(self.panel, wx.ID_ANY, wordWrap=True)
        self.populateComboCtrl(comboCtrl2)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(comboCtrl, 0, wx.EXPAND)
        sizer.Add(comboCtrl2, 0, wx.EXPAND)
        self.panel.SetSizer(sizer)
        comboCtrl.Bind(wx.EVT_COMBOBOX, self.OnComboSelect)
        
    def OnComboSelect(self, event):
        print 'selection is: %s' % event.GetString()

    def populateComboCtrl(self, comboCtrl):
        theVListBox = comboCtrl.GetVListBox()
        
        
        theVListBox.InsertItem(theVListBox.GetItemCount(), "First Item" )
        theVListBox.InsertItem(theVListBox.GetItemCount(), "Second Item")
        theVListBox.InsertItem(theVListBox.GetItemCount(), "Third Item")

        theVListBox.InsertItem(theVListBox.GetItemCount(), "double Item")
        theVListBox.InsertItem(theVListBox.GetItemCount(), "double Item")
        
        for x in range(60):
            theVListBox.AppendItem("%d Item" % x)
        for x in range(15):
            x=str(x)*(x*x)
            theVListBox.AppendItem("Item-%s" % x)

import wx.lib.mixins.inspection as wit
class AppWInspection(wx.App, wit.InspectionMixin):
    def OnInit(self):
        self.Init()  # enable Inspection tool
        return True



if __name__ == "__main__":
    app = AppWInspection()
    #import wx.lib.inspection
    #wx.lib.inspection.InspectionTool().Show()
    f = myFrame()
    f.Show()
    app.MainLoop()
