TextCtrlAutoComplete.py
changeset 426 3f285782ac9b
parent 326 386566f263f3
child 428 ea09f33ce717
equal deleted inserted replaced
425:f390e9fdd2cf 426:3f285782ac9b
    25 import wx
    25 import wx
    26 import cPickle
    26 import cPickle
    27 
    27 
    28 MAX_ITEM_COUNT = 10
    28 MAX_ITEM_COUNT = 10
    29 MAX_ITEM_SHOWN = 6
    29 MAX_ITEM_SHOWN = 6
    30 ITEM_HEIGHT = 25
    30 if wx.Platform == '__WXMSW__':
    31 
    31     ITEM_INTERVAL_HEIGHT = 3
       
    32 else:
       
    33     ITEM_INTERVAL_HEIGHT = 6
       
    34 
       
    35 if wx.Platform == '__WXMSW__':
       
    36     popupclass = wx.PopupTransientWindow
       
    37 else:
       
    38     popupclass = wx.PopupWindow
       
    39 
       
    40 class PopupWithListbox(popupclass):
       
    41     
       
    42     def __init__(self, parent, choices=[]):
       
    43         popupclass.__init__(self, parent, wx.SIMPLE_BORDER)
       
    44         
       
    45         self.ListBox = wx.ListBox(self, -1, style=wx.LB_HSCROLL|wx.LB_SINGLE|wx.LB_SORT)
       
    46         if not wx.Platform == '__WXMSW__':
       
    47             self.ListBox.Bind(wx.EVT_LISTBOX, self.OnListBoxClick)
       
    48             self.ListBox.Bind(wx.EVT_LISTBOX_DCLICK, self.OnListBoxClick)
       
    49             
       
    50         self.SetChoices(choices)
       
    51         
       
    52         self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
       
    53     
       
    54     def SetChoices(self, choices):
       
    55         max_text_width = 0
       
    56         max_text_height = 0
       
    57         
       
    58         self.ListBox.Clear()
       
    59         for choice in choices:
       
    60             self.ListBox.Append(choice)
       
    61             w, h = self.ListBox.GetTextExtent(choice)
       
    62             max_text_width = max(max_text_width, w)
       
    63             max_text_height = max(max_text_height, h)
       
    64         
       
    65         itemcount = min(len(choices), MAX_ITEM_SHOWN)
       
    66         width = self.Parent.GetSize()[0]
       
    67         height = max_text_height * itemcount + ITEM_INTERVAL_HEIGHT * (itemcount + 1)
       
    68         if max_text_width + 10 > width:
       
    69             height += 15
       
    70         size = wx.Size(width, height)
       
    71         self.ListBox.SetSize(size)
       
    72         self.SetClientSize(size)
       
    73     
       
    74     def MoveSelection(self, direction):
       
    75         selected = self.ListBox.GetSelection()
       
    76         if selected == wx.NOT_FOUND:
       
    77             if direction >= 0:
       
    78                 selected = 0
       
    79             else:
       
    80                 selected = self.ListBox.GetCount() - 1
       
    81         else:
       
    82             selected = (selected + direction) % (self.ListBox.GetCount() + 1)
       
    83         if selected == self.ListBox.GetCount():
       
    84             selected = wx.NOT_FOUND
       
    85         self.ListBox.SetSelection(selected)
       
    86     
       
    87     def GetSelection(self):
       
    88         return self.ListBox.GetStringSelection()
       
    89     
       
    90     def ProcessLeftDown(self, event):
       
    91         selected = self.ListBox.HitTest(wx.Point(event.m_x, event.m_y))
       
    92         if selected != wx.NOT_FOUND:
       
    93             wx.CallAfter(self.Parent.SetValueFromSelected, self.ListBox.GetString(selected))
       
    94         return False
       
    95     
       
    96     def OnListBoxClick(self, event):
       
    97         selected = event.GetSelection()
       
    98         if selected != wx.NOT_FOUND:
       
    99             wx.CallAfter(self.Parent.SetValueFromSelected, self.ListBox.GetString(selected))
       
   100         event.Skip()
       
   101     
       
   102     def OnKeyDown(self, event):
       
   103         self.Parent.ProcessEvent(event)
       
   104 
       
   105     def OnDismiss(self):
       
   106         self.Parent.listbox = None
       
   107         wx.CallAfter(self.Parent.DismissListBox)
       
   108     
    32 class TextCtrlAutoComplete(wx.TextCtrl):
   109 class TextCtrlAutoComplete(wx.TextCtrl):
    33 
   110 
    34     def __init__ (self, parent, choices=None, dropDownClick=True,
   111     def __init__ (self, parent, appframe, choices=None, dropDownClick=True,
    35                   element_path=None, **therest):
   112                   element_path=None, **therest):
    36         """
   113         """
    37         Constructor works just like wx.TextCtrl except you can pass in a
   114         Constructor works just like wx.TextCtrl except you can pass in a
    38         list of choices.  You can also change the choice list at any time
   115         list of choices.  You can also change the choice list at any time
    39         by calling setChoices.
   116         by calling setChoices.
    40         """
   117         """
    41 
   118 
    42         therest['style'] = wx.TE_PROCESS_ENTER | therest.get('style', 0)
   119         therest['style'] = wx.TE_PROCESS_ENTER | therest.get('style', 0)
    43 
   120 
    44         wx.TextCtrl.__init__(self, parent, **therest)
   121         wx.TextCtrl.__init__(self, parent, **therest)
    45 
   122         self.AppFrame = appframe
       
   123         
    46         #Some variables
   124         #Some variables
    47         self._dropDownClick = dropDownClick
   125         self._dropDownClick = dropDownClick
    48         self._lastinsertionpoint = None
   126         self._lastinsertionpoint = None
    49         
   127         
    50         self._screenheight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
   128         self._screenheight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
    51         self.element_path = element_path
   129         self.element_path = element_path
    52         
   130         
    53         #widgets
   131         self.listbox = None
    54         self.dropdown = wx.PopupWindow(self)
       
    55 
       
    56         #Control the style
       
    57         flags = wx.LB_HSCROLL | wx.LB_SINGLE | wx.LB_SORT
       
    58         
       
    59         #Create the list and bind the events
       
    60         self.dropdownlistbox = wx.ListBox(self.dropdown, style=flags,
       
    61                                  pos=wx.Point(0, 0))
       
    62         
   132         
    63         self.SetChoices(choices)
   133         self.SetChoices(choices)
    64 
   134 
    65         #gp = self
   135         #gp = self
    66         #while ( gp != None ) :
   136         #while ( gp != None ) :
    67         #    gp.Bind ( wx.EVT_MOVE , self.onControlChanged, gp )
   137         #    gp.Bind ( wx.EVT_MOVE , self.onControlChanged, gp )
    68         #    gp.Bind ( wx.EVT_SIZE , self.onControlChanged, gp )
   138         #    gp.Bind ( wx.EVT_SIZE , self.onControlChanged, gp )
    69         #    gp = gp.GetParent()
   139         #    gp = gp.GetParent()
    70 
   140 
    71         self.Bind(wx.EVT_KILL_FOCUS, self.onControlChanged, self)
   141         self.Bind(wx.EVT_KILL_FOCUS, self.OnControlChanged)
    72         self.Bind(wx.EVT_TEXT_ENTER, self.onControlChanged, self)
   142         self.Bind(wx.EVT_TEXT_ENTER, self.OnControlChanged)
    73         self.Bind(wx.EVT_TEXT, self.onEnteredText, self)
   143         self.Bind(wx.EVT_TEXT, self.OnEnteredText)
    74         self.Bind(wx.EVT_KEY_DOWN, self.onKeyDown, self)
   144         self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
    75 
   145 
    76         #If need drop down on left click
   146         #If need drop down on left click
    77         if dropDownClick:
   147         if dropDownClick:
    78             self.Bind(wx.EVT_LEFT_DOWN , self.onClickToggleDown, self)
   148             self.Bind(wx.EVT_LEFT_DOWN, self.OnClickToggleDown)
    79             self.Bind(wx.EVT_LEFT_UP , self.onClickToggleUp, self)
   149             self.Bind(wx.EVT_LEFT_UP, self.OnClickToggleUp)
    80 
   150 
    81         self.dropdownlistbox.Bind(wx.EVT_LISTBOX, self.onListItemSelected)
   151     def __del__(self):
    82         self.dropdownlistbox.Bind(wx.EVT_LISTBOX_DCLICK, self.onListItemSelected)
   152         self.AppFrame = None
    83 
   153 
    84     def ChangeValue(self, value):
   154     def ChangeValue(self, value):
    85         wx.TextCtrl.ChangeValue(self, value)
   155         wx.TextCtrl.ChangeValue(self, value)
    86         self._refreshListBoxChoices()
   156         self.RefreshListBoxChoices()
    87 
   157 
    88     def onEnteredText(self, event):
   158     def OnEnteredText(self, event):
    89         wx.CallAfter(self._refreshListBoxChoices)
   159         wx.CallAfter(self.RefreshListBoxChoices)
    90         event.Skip()
   160         event.Skip()
    91 
   161 
    92     def onKeyDown(self, event) :
   162     def OnKeyDown(self, event):
    93         """ Do some work when the user press on the keys:
   163         """ Do some work when the user press on the keys:
    94             up and down: move the cursor
   164             up and down: move the cursor
    95         """
   165         """
    96         visible = self.dropdown.IsShown()
       
    97         keycode = event.GetKeyCode()
   166         keycode = event.GetKeyCode()
    98         if keycode in [wx.WXK_DOWN, wx.WXK_UP]:
   167         if keycode in [wx.WXK_DOWN, wx.WXK_UP]:
    99             if not visible:
   168             self.PopupListBox()
   100                 self._showDropDown()
   169             if keycode == wx.WXK_DOWN:
   101             elif keycode == wx.WXK_DOWN:
   170                 self.listbox.MoveSelection(1)
   102                 self._moveSelection(1)
   171             else:
   103             else:
   172                 self.listbox.MoveSelection(-1)
   104                 self._moveSelection(-1)
   173         elif keycode in [wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_RETURN] and self.listbox is not None:
   105         elif keycode in [wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_RETURN] and visible:
   174             self.SetValueFromSelected(self.listbox.GetSelection())
   106             if self.dropdownlistbox.GetSelection() != wx.NOT_FOUND:
       
   107                 self._setValueFromSelected()
       
   108             else:
       
   109                 self._showDropDown(False)
       
   110                 event.Skip()
       
   111         elif event.GetKeyCode() == wx.WXK_ESCAPE:
   175         elif event.GetKeyCode() == wx.WXK_ESCAPE:
   112             self._showDropDown(False)
   176             self.DismissListBox()
   113         else:
   177         else:
   114             event.Skip()
   178             event.Skip()
   115 
   179 
   116     def onListItemSelected(self, event):
   180     def OnClickToggleDown(self, event):
   117         self._setValueFromSelected()
       
   118         event.Skip()
       
   119 
       
   120     def onClickToggleDown(self, event):
       
   121         self._lastinsertionpoint = self.GetInsertionPoint()
   181         self._lastinsertionpoint = self.GetInsertionPoint()
   122         event.Skip()
   182         event.Skip()
   123 
   183 
   124     def onClickToggleUp(self, event):
   184     def OnClickToggleUp(self, event):
   125         if self.GetInsertionPoint() == self._lastinsertionpoint:
   185         if self.GetInsertionPoint() == self._lastinsertionpoint:
   126             self._showDropDown(not self.dropdown.IsShown())
   186             wx.CallAfter(self.PopupListBox)
   127         self._lastinsertionpoint = None
   187         self._lastinsertionpoint = None
   128         event.Skip()
   188         event.Skip()
   129 
   189 
   130     def onControlChanged(self, event):
   190     def OnControlChanged(self, event):
   131         res = self.GetValue()
   191         res = self.GetValue()
   132         config = wx.ConfigBase.Get()
   192         config = wx.ConfigBase.Get()
   133         listentries = cPickle.loads(str(config.Read(self.element_path, cPickle.dumps([]))))
   193         listentries = cPickle.loads(str(config.Read(self.element_path, cPickle.dumps([]))))
   134         if res and res not in listentries:
   194         if res and res not in listentries:
   135             listentries = (listentries + [res])[-MAX_ITEM_COUNT:]
   195             listentries = (listentries + [res])[-MAX_ITEM_COUNT:]
   136             config.Write(self.element_path, cPickle.dumps(listentries))
   196             config.Write(self.element_path, cPickle.dumps(listentries))
   137             config.Flush()
   197             config.Flush()
   138             self.SetChoices(listentries)
   198             self.SetChoices(listentries)
   139         self._showDropDown(False)
   199         self.DismissListBox()
   140         event.Skip()
   200         event.Skip()
   141     
   201     
   142     def SetChoices(self, choices):
   202     def SetChoices(self, choices):
   143         self._choices = choices
   203         self._choices = choices
   144         self._refreshListBoxChoices()
   204         self.RefreshListBoxChoices()
   145         
   205         
   146     def GetChoices(self):
   206     def GetChoices(self):
   147         return self._choices
   207         return self._choices
   148     
   208     
   149 #-------------------------------------------------------------------------------
   209     def SetValueFromSelected(self, selected):
   150 #                           Internal methods
       
   151 #-------------------------------------------------------------------------------
       
   152 
       
   153     def _refreshListBoxChoices(self):
       
   154         text = self.GetValue()
       
   155 
       
   156         self.dropdownlistbox.Clear()
       
   157         for choice in self._choices:
       
   158             if choice.startswith(text):
       
   159                 self.dropdownlistbox.Append(choice)
       
   160         
       
   161         itemcount = min(len(self.dropdownlistbox.GetStrings()), MAX_ITEM_SHOWN)
       
   162         self.popupsize = wx.Size(self.GetSize()[0], ITEM_HEIGHT * itemcount + 4)
       
   163         self.dropdownlistbox.SetSize(self.popupsize)
       
   164         self.dropdown.SetClientSize(self.popupsize)
       
   165     
       
   166     def _moveSelection(self, direction):
       
   167         selected = self.dropdownlistbox.GetSelection()
       
   168         if selected == wx.NOT_FOUND:
       
   169             if direction >= 0:
       
   170                 selected = 0
       
   171             else:
       
   172                 selected = self.dropdownlistbox.GetCount() - 1
       
   173         else:
       
   174             selected = (selected + direction) % (self.dropdownlistbox.GetCount() + 1)
       
   175         if selected == self.dropdownlistbox.GetCount():
       
   176             selected = wx.NOT_FOUND
       
   177         self.dropdownlistbox.SetSelection(selected)
       
   178     
       
   179     def _setValueFromSelected(self):
       
   180          """
   210          """
   181          Sets the wx.TextCtrl value from the selected wx.ListCtrl item.
   211          Sets the wx.TextCtrl value from the selected wx.ListCtrl item.
   182          Will do nothing if no item is selected in the wx.ListCtrl.
   212          Will do nothing if no item is selected in the wx.ListCtrl.
   183          """
   213          """
   184          selected = self.dropdownlistbox.GetStringSelection()
   214          if selected != "":
   185          if selected:
       
   186             self.SetValue(selected)
   215             self.SetValue(selected)
   187             self._showDropDown(False)
   216          self.DismissListBox()
   188 
   217     
   189 
   218     def RefreshListBoxChoices(self):
   190     def _showDropDown(self, show=True) :
   219         if self.listbox is not None:
   191         """
   220             text = self.GetValue()
   192         Either display the drop down list (show = True) or hide it (show = False).
   221             choices = [choice for choice in self._choices if choice.startswith(text)]
   193         """
   222             self.listbox.SetChoices(choices)
   194         if show :
   223 
   195             size = self.dropdown.GetSize()
   224     def PopupListBox(self):
   196             width, height = self.GetSizeTuple()
   225         if self.listbox is None:
   197             x, y = self.ClientToScreenXY(0, height)
   226             self.listbox = PopupWithListbox(self)
   198             if size.GetWidth() != width :
   227             
   199                 size.SetWidth(width)
   228             # Show the popup right below or above the button
   200                 self.dropdown.SetSize(size)
   229             # depending on available screen space...
   201                 self.dropdownlistbox.SetSize(self.dropdown.GetClientSize())
   230             pos = self.ClientToScreen((0, 0))
   202             if (y + size.GetHeight()) < self._screenheight :
   231             sz = self.GetSize()
   203                 self.dropdown.SetPosition(wx.Point(x, y))
   232             self.listbox.Position(pos, (0, sz[1]))
   204             else:
   233             
   205                 self.dropdown.SetPosition(wx.Point(x, y - height - size.GetHeight()))
   234             self.RefreshListBoxChoices()
   206         self.dropdown.Show(show) 
   235             
   207 
   236             if wx.Platform == '__WXMSW__':
       
   237                 self.listbox.Popup()
       
   238             else:
       
   239                 self.listbox.Show()
       
   240             self.AppFrame.EnableScrolling(False)
       
   241 
       
   242     def DismissListBox(self):
       
   243         if self.listbox is not None:
       
   244             if wx.Platform == '__WXMSW__':
       
   245                 self.listbox.Dismiss()
       
   246             else:
       
   247                 self.listbox.Destroy()
       
   248             self.listbox = None
       
   249         self.AppFrame.EnableScrolling(True)
       
   250