diff -r f390e9fdd2cf -r 3f285782ac9b TextCtrlAutoComplete.py --- a/TextCtrlAutoComplete.py Thu Oct 22 11:28:11 2009 +0200 +++ b/TextCtrlAutoComplete.py Thu Oct 22 17:20:24 2009 +0200 @@ -27,11 +27,88 @@ MAX_ITEM_COUNT = 10 MAX_ITEM_SHOWN = 6 -ITEM_HEIGHT = 25 - +if wx.Platform == '__WXMSW__': + ITEM_INTERVAL_HEIGHT = 3 +else: + ITEM_INTERVAL_HEIGHT = 6 + +if wx.Platform == '__WXMSW__': + popupclass = wx.PopupTransientWindow +else: + popupclass = wx.PopupWindow + +class PopupWithListbox(popupclass): + + def __init__(self, parent, choices=[]): + popupclass.__init__(self, parent, wx.SIMPLE_BORDER) + + self.ListBox = wx.ListBox(self, -1, style=wx.LB_HSCROLL|wx.LB_SINGLE|wx.LB_SORT) + if not wx.Platform == '__WXMSW__': + self.ListBox.Bind(wx.EVT_LISTBOX, self.OnListBoxClick) + self.ListBox.Bind(wx.EVT_LISTBOX_DCLICK, self.OnListBoxClick) + + self.SetChoices(choices) + + self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) + + def SetChoices(self, choices): + max_text_width = 0 + max_text_height = 0 + + self.ListBox.Clear() + for choice in choices: + self.ListBox.Append(choice) + w, h = self.ListBox.GetTextExtent(choice) + max_text_width = max(max_text_width, w) + max_text_height = max(max_text_height, h) + + itemcount = min(len(choices), MAX_ITEM_SHOWN) + width = self.Parent.GetSize()[0] + height = max_text_height * itemcount + ITEM_INTERVAL_HEIGHT * (itemcount + 1) + if max_text_width + 10 > width: + height += 15 + size = wx.Size(width, height) + self.ListBox.SetSize(size) + self.SetClientSize(size) + + def MoveSelection(self, direction): + selected = self.ListBox.GetSelection() + if selected == wx.NOT_FOUND: + if direction >= 0: + selected = 0 + else: + selected = self.ListBox.GetCount() - 1 + else: + selected = (selected + direction) % (self.ListBox.GetCount() + 1) + if selected == self.ListBox.GetCount(): + selected = wx.NOT_FOUND + self.ListBox.SetSelection(selected) + + def GetSelection(self): + return self.ListBox.GetStringSelection() + + def ProcessLeftDown(self, event): + selected = self.ListBox.HitTest(wx.Point(event.m_x, event.m_y)) + if selected != wx.NOT_FOUND: + wx.CallAfter(self.Parent.SetValueFromSelected, self.ListBox.GetString(selected)) + return False + + def OnListBoxClick(self, event): + selected = event.GetSelection() + if selected != wx.NOT_FOUND: + wx.CallAfter(self.Parent.SetValueFromSelected, self.ListBox.GetString(selected)) + event.Skip() + + def OnKeyDown(self, event): + self.Parent.ProcessEvent(event) + + def OnDismiss(self): + self.Parent.listbox = None + wx.CallAfter(self.Parent.DismissListBox) + class TextCtrlAutoComplete(wx.TextCtrl): - def __init__ (self, parent, choices=None, dropDownClick=True, + def __init__ (self, parent, appframe, choices=None, dropDownClick=True, element_path=None, **therest): """ Constructor works just like wx.TextCtrl except you can pass in a @@ -42,7 +119,8 @@ therest['style'] = wx.TE_PROCESS_ENTER | therest.get('style', 0) wx.TextCtrl.__init__(self, parent, **therest) - + self.AppFrame = appframe + #Some variables self._dropDownClick = dropDownClick self._lastinsertionpoint = None @@ -50,15 +128,7 @@ self._screenheight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y) self.element_path = element_path - #widgets - self.dropdown = wx.PopupWindow(self) - - #Control the style - flags = wx.LB_HSCROLL | wx.LB_SINGLE | wx.LB_SORT - - #Create the list and bind the events - self.dropdownlistbox = wx.ListBox(self.dropdown, style=flags, - pos=wx.Point(0, 0)) + self.listbox = None self.SetChoices(choices) @@ -68,66 +138,56 @@ # gp.Bind ( wx.EVT_SIZE , self.onControlChanged, gp ) # gp = gp.GetParent() - self.Bind(wx.EVT_KILL_FOCUS, self.onControlChanged, self) - self.Bind(wx.EVT_TEXT_ENTER, self.onControlChanged, self) - self.Bind(wx.EVT_TEXT, self.onEnteredText, self) - self.Bind(wx.EVT_KEY_DOWN, self.onKeyDown, self) + self.Bind(wx.EVT_KILL_FOCUS, self.OnControlChanged) + self.Bind(wx.EVT_TEXT_ENTER, self.OnControlChanged) + self.Bind(wx.EVT_TEXT, self.OnEnteredText) + self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) #If need drop down on left click if dropDownClick: - self.Bind(wx.EVT_LEFT_DOWN , self.onClickToggleDown, self) - self.Bind(wx.EVT_LEFT_UP , self.onClickToggleUp, self) - - self.dropdownlistbox.Bind(wx.EVT_LISTBOX, self.onListItemSelected) - self.dropdownlistbox.Bind(wx.EVT_LISTBOX_DCLICK, self.onListItemSelected) + self.Bind(wx.EVT_LEFT_DOWN, self.OnClickToggleDown) + self.Bind(wx.EVT_LEFT_UP, self.OnClickToggleUp) + + def __del__(self): + self.AppFrame = None def ChangeValue(self, value): wx.TextCtrl.ChangeValue(self, value) - self._refreshListBoxChoices() - - def onEnteredText(self, event): - wx.CallAfter(self._refreshListBoxChoices) - event.Skip() - - def onKeyDown(self, event) : + self.RefreshListBoxChoices() + + def OnEnteredText(self, event): + wx.CallAfter(self.RefreshListBoxChoices) + event.Skip() + + def OnKeyDown(self, event): """ Do some work when the user press on the keys: up and down: move the cursor """ - visible = self.dropdown.IsShown() keycode = event.GetKeyCode() if keycode in [wx.WXK_DOWN, wx.WXK_UP]: - if not visible: - self._showDropDown() - elif keycode == wx.WXK_DOWN: - self._moveSelection(1) - else: - self._moveSelection(-1) - elif keycode in [wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_RETURN] and visible: - if self.dropdownlistbox.GetSelection() != wx.NOT_FOUND: - self._setValueFromSelected() - else: - self._showDropDown(False) - event.Skip() + self.PopupListBox() + if keycode == wx.WXK_DOWN: + self.listbox.MoveSelection(1) + else: + self.listbox.MoveSelection(-1) + elif keycode in [wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_RETURN] and self.listbox is not None: + self.SetValueFromSelected(self.listbox.GetSelection()) elif event.GetKeyCode() == wx.WXK_ESCAPE: - self._showDropDown(False) + self.DismissListBox() else: event.Skip() - def onListItemSelected(self, event): - self._setValueFromSelected() - event.Skip() - - def onClickToggleDown(self, event): + def OnClickToggleDown(self, event): self._lastinsertionpoint = self.GetInsertionPoint() event.Skip() - def onClickToggleUp(self, event): + def OnClickToggleUp(self, event): if self.GetInsertionPoint() == self._lastinsertionpoint: - self._showDropDown(not self.dropdown.IsShown()) + wx.CallAfter(self.PopupListBox) self._lastinsertionpoint = None event.Skip() - def onControlChanged(self, event): + def OnControlChanged(self, event): res = self.GetValue() config = wx.ConfigBase.Get() listentries = cPickle.loads(str(config.Read(self.element_path, cPickle.dumps([])))) @@ -136,72 +196,55 @@ config.Write(self.element_path, cPickle.dumps(listentries)) config.Flush() self.SetChoices(listentries) - self._showDropDown(False) + self.DismissListBox() event.Skip() def SetChoices(self, choices): self._choices = choices - self._refreshListBoxChoices() + self.RefreshListBoxChoices() def GetChoices(self): return self._choices -#------------------------------------------------------------------------------- -# Internal methods -#------------------------------------------------------------------------------- - - def _refreshListBoxChoices(self): - text = self.GetValue() - - self.dropdownlistbox.Clear() - for choice in self._choices: - if choice.startswith(text): - self.dropdownlistbox.Append(choice) - - itemcount = min(len(self.dropdownlistbox.GetStrings()), MAX_ITEM_SHOWN) - self.popupsize = wx.Size(self.GetSize()[0], ITEM_HEIGHT * itemcount + 4) - self.dropdownlistbox.SetSize(self.popupsize) - self.dropdown.SetClientSize(self.popupsize) - - def _moveSelection(self, direction): - selected = self.dropdownlistbox.GetSelection() - if selected == wx.NOT_FOUND: - if direction >= 0: - selected = 0 - else: - selected = self.dropdownlistbox.GetCount() - 1 - else: - selected = (selected + direction) % (self.dropdownlistbox.GetCount() + 1) - if selected == self.dropdownlistbox.GetCount(): - selected = wx.NOT_FOUND - self.dropdownlistbox.SetSelection(selected) - - def _setValueFromSelected(self): + def SetValueFromSelected(self, selected): """ Sets the wx.TextCtrl value from the selected wx.ListCtrl item. Will do nothing if no item is selected in the wx.ListCtrl. """ - selected = self.dropdownlistbox.GetStringSelection() - if selected: + if selected != "": self.SetValue(selected) - self._showDropDown(False) - - - def _showDropDown(self, show=True) : - """ - Either display the drop down list (show = True) or hide it (show = False). - """ - if show : - size = self.dropdown.GetSize() - width, height = self.GetSizeTuple() - x, y = self.ClientToScreenXY(0, height) - if size.GetWidth() != width : - size.SetWidth(width) - self.dropdown.SetSize(size) - self.dropdownlistbox.SetSize(self.dropdown.GetClientSize()) - if (y + size.GetHeight()) < self._screenheight : - self.dropdown.SetPosition(wx.Point(x, y)) - else: - self.dropdown.SetPosition(wx.Point(x, y - height - size.GetHeight())) - self.dropdown.Show(show) - + self.DismissListBox() + + def RefreshListBoxChoices(self): + if self.listbox is not None: + text = self.GetValue() + choices = [choice for choice in self._choices if choice.startswith(text)] + self.listbox.SetChoices(choices) + + def PopupListBox(self): + if self.listbox is None: + self.listbox = PopupWithListbox(self) + + # Show the popup right below or above the button + # depending on available screen space... + pos = self.ClientToScreen((0, 0)) + sz = self.GetSize() + self.listbox.Position(pos, (0, sz[1])) + + self.RefreshListBoxChoices() + + if wx.Platform == '__WXMSW__': + self.listbox.Popup() + else: + self.listbox.Show() + self.AppFrame.EnableScrolling(False) + + def DismissListBox(self): + if self.listbox is not None: + if wx.Platform == '__WXMSW__': + self.listbox.Dismiss() + else: + self.listbox.Destroy() + self.listbox = None + self.AppFrame.EnableScrolling(True) +