controls/TextCtrlAutoComplete.py
changeset 1180 276a30c68eaa
parent 814 5743cbdff669
child 1498 b11045a2f17c
equal deleted inserted replaced
1179:3e7bd88fcff7 1180:276a30c68eaa
    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 if wx.Platform == '__WXMSW__':
    30 if wx.Platform == '__WXMSW__':
    31     ITEM_INTERVAL_HEIGHT = 3
    31     LISTBOX_BORDER_HEIGHT = 2
       
    32     LISTBOX_INTERVAL_HEIGHT = 0
    32 else:
    33 else:
    33     ITEM_INTERVAL_HEIGHT = 6
    34     LISTBOX_BORDER_HEIGHT = 4
    34 
    35     LISTBOX_INTERVAL_HEIGHT = 6
    35 if wx.Platform == '__WXMSW__':
    36 
    36     popupclass = wx.PopupTransientWindow
    37 class PopupWithListbox(wx.PopupWindow):
    37 else:
       
    38     popupclass = wx.PopupWindow
       
    39 
       
    40 class PopupWithListbox(popupclass):
       
    41     
    38     
    42     def __init__(self, parent, choices=[]):
    39     def __init__(self, parent, choices=[]):
    43         popupclass.__init__(self, parent, wx.SIMPLE_BORDER)
    40         wx.PopupWindow.__init__(self, parent, wx.BORDER_SIMPLE)
    44         
    41         
    45         self.ListBox = wx.ListBox(self, -1, style=wx.LB_HSCROLL|wx.LB_SINGLE|wx.LB_SORT)
    42         self.ListBox = wx.ListBox(self, -1, style=wx.LB_HSCROLL|wx.LB_SINGLE|wx.LB_SORT)
    46         if not wx.Platform == '__WXMSW__':
    43         
    47             self.ListBox.Bind(wx.EVT_LISTBOX, self.OnListBoxClick)
       
    48             self.ListBox.Bind(wx.EVT_LISTBOX_DCLICK, self.OnListBoxClick)
       
    49             
       
    50         self.SetChoices(choices)
    44         self.SetChoices(choices)
    51         
    45         
    52         self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
    46         self.ListBox.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
       
    47         self.ListBox.Bind(wx.EVT_MOTION, self.OnMotion)
    53     
    48     
    54     def SetChoices(self, choices):
    49     def SetChoices(self, choices):
    55         max_text_width = 0
    50         max_text_width = 0
    56         max_text_height = 0
    51         max_text_height = 0
    57         
    52         
    62             max_text_width = max(max_text_width, w)
    57             max_text_width = max(max_text_width, w)
    63             max_text_height = max(max_text_height, h)
    58             max_text_height = max(max_text_height, h)
    64         
    59         
    65         itemcount = min(len(choices), MAX_ITEM_SHOWN)
    60         itemcount = min(len(choices), MAX_ITEM_SHOWN)
    66         width = self.Parent.GetSize()[0]
    61         width = self.Parent.GetSize()[0]
    67         height = max_text_height * itemcount + ITEM_INTERVAL_HEIGHT * (itemcount + 1)
    62         height = max_text_height * itemcount + \
       
    63                  LISTBOX_INTERVAL_HEIGHT * max(0, itemcount - 1) + \
       
    64                  2 * LISTBOX_BORDER_HEIGHT
    68         if max_text_width + 10 > width:
    65         if max_text_width + 10 > width:
    69             height += 15
    66             height += 15
    70         size = wx.Size(width, height)
    67         size = wx.Size(width, height)
       
    68         if wx.Platform == '__WXMSW__':
       
    69             size.width -= 2
    71         self.ListBox.SetSize(size)
    70         self.ListBox.SetSize(size)
    72         self.SetClientSize(size)
    71         self.SetClientSize(size)
    73     
    72     
    74     def MoveSelection(self, direction):
    73     def MoveSelection(self, direction):
    75         selected = self.ListBox.GetSelection()
    74         selected = self.ListBox.GetSelection()
    85         self.ListBox.SetSelection(selected)
    84         self.ListBox.SetSelection(selected)
    86     
    85     
    87     def GetSelection(self):
    86     def GetSelection(self):
    88         return self.ListBox.GetStringSelection()
    87         return self.ListBox.GetStringSelection()
    89     
    88     
    90     def ProcessLeftDown(self, event):
    89     def OnLeftDown(self, event):
    91         selected = self.ListBox.HitTest(wx.Point(event.m_x, event.m_y))
    90         selected = self.ListBox.HitTest(wx.Point(event.m_x, event.m_y))
       
    91         parent_size = self.Parent.GetSize()
       
    92         parent_rect = wx.Rect(0, -parent_size[1], parent_size[0], parent_size[1])
    92         if selected != wx.NOT_FOUND:
    93         if selected != wx.NOT_FOUND:
    93             wx.CallAfter(self.Parent.SetValueFromSelected, self.ListBox.GetString(selected))
    94             wx.CallAfter(self.Parent.SetValueFromSelected, self.ListBox.GetString(selected))
    94         return False
    95         elif parent_rect.InsideXY(event.m_x, event.m_y):
    95     
    96             result, x, y = self.Parent.HitTest(wx.Point(event.m_x, event.m_y + parent_size[1]))
    96     def OnListBoxClick(self, event):
    97             if result != wx.TE_HT_UNKNOWN:
    97         selected = event.GetSelection()
    98                 self.Parent.SetInsertionPoint(self.Parent.XYToPosition(x, y))
    98         if selected != wx.NOT_FOUND:
    99         else:
    99             wx.CallAfter(self.Parent.SetValueFromSelected, self.ListBox.GetString(selected))
   100             wx.CallAfter(self.Parent.DismissListBox)
   100         event.Skip()
   101         event.Skip()
   101     
   102     
   102     def OnKeyDown(self, event):
   103     def OnMotion(self, event):
   103         self.Parent.ProcessEvent(event)
   104         self.ListBox.SetSelection(
   104 
   105             self.ListBox.HitTest(wx.Point(event.m_x, event.m_y)))
   105     def OnDismiss(self):
   106         event.Skip()
   106         self.Parent.listbox = None
       
   107         wx.CallAfter(self.Parent.DismissListBox)
       
   108     
   107     
   109 class TextCtrlAutoComplete(wx.TextCtrl):
   108 class TextCtrlAutoComplete(wx.TextCtrl):
   110 
   109 
   111     def __init__ (self, parent, appframe, choices=None, dropDownClick=True,
   110     def __init__ (self, parent, choices=None, dropDownClick=True,
   112                   element_path=None, **therest):
   111                   element_path=None, **therest):
   113         """
   112         """
   114         Constructor works just like wx.TextCtrl except you can pass in a
   113         Constructor works just like wx.TextCtrl except you can pass in a
   115         list of choices.  You can also change the choice list at any time
   114         list of choices.  You can also change the choice list at any time
   116         by calling setChoices.
   115         by calling setChoices.
   117         """
   116         """
   118 
   117 
   119         therest['style'] = wx.TE_PROCESS_ENTER | therest.get('style', 0)
   118         therest['style'] = wx.TE_PROCESS_ENTER | therest.get('style', 0)
   120 
   119 
   121         wx.TextCtrl.__init__(self, parent, **therest)
   120         wx.TextCtrl.__init__(self, parent, **therest)
   122         self.AppFrame = appframe
       
   123         
   121         
   124         #Some variables
   122         #Some variables
   125         self._dropDownClick = dropDownClick
   123         self._dropDownClick = dropDownClick
   126         self._lastinsertionpoint = None
   124         self._lastinsertionpoint = None
       
   125         self._hasfocus = False
   127         
   126         
   128         self._screenheight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
   127         self._screenheight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
   129         self.element_path = element_path
   128         self.element_path = element_path
   130         
   129         
   131         self.listbox = None
   130         self.listbox = None
   145 
   144 
   146         #If need drop down on left click
   145         #If need drop down on left click
   147         if dropDownClick:
   146         if dropDownClick:
   148             self.Bind(wx.EVT_LEFT_DOWN, self.OnClickToggleDown)
   147             self.Bind(wx.EVT_LEFT_DOWN, self.OnClickToggleDown)
   149             self.Bind(wx.EVT_LEFT_UP, self.OnClickToggleUp)
   148             self.Bind(wx.EVT_LEFT_UP, self.OnClickToggleUp)
   150 
       
   151     def __del__(self):
       
   152         self.AppFrame = None
       
   153 
   149 
   154     def ChangeValue(self, value):
   150     def ChangeValue(self, value):
   155         wx.TextCtrl.ChangeValue(self, value)
   151         wx.TextCtrl.ChangeValue(self, value)
   156         self.RefreshListBoxChoices()
   152         self.RefreshListBoxChoices()
   157 
   153 
   169             if keycode == wx.WXK_DOWN:
   165             if keycode == wx.WXK_DOWN:
   170                 self.listbox.MoveSelection(1)
   166                 self.listbox.MoveSelection(1)
   171             else:
   167             else:
   172                 self.listbox.MoveSelection(-1)
   168                 self.listbox.MoveSelection(-1)
   173         elif keycode in [wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_RETURN] and self.listbox is not None:
   169         elif keycode in [wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_RETURN] and self.listbox is not None:
   174             self.SetValueFromSelected(self.listbox.GetSelection())
   170             selected = self.listbox.GetSelection()
       
   171             if selected != "":
       
   172                 self.SetValueFromSelected(selected)
       
   173             else:
       
   174                 event.Skip()
   175         elif event.GetKeyCode() == wx.WXK_ESCAPE:
   175         elif event.GetKeyCode() == wx.WXK_ESCAPE:
   176             self.DismissListBox()
   176             self.DismissListBox()
   177         else:
   177         else:
   178             event.Skip()
   178             event.Skip()
   179 
   179 
   180     def OnClickToggleDown(self, event):
   180     def OnClickToggleDown(self, event):
   181         self._lastinsertionpoint = self.GetInsertionPoint()
   181         self._lastinsertionpoint = self.GetInsertionPoint()
   182         event.Skip()
   182         event.Skip()
   183 
   183 
   184     def OnClickToggleUp(self, event):
   184     def OnClickToggleUp(self, event):
   185         if self.GetInsertionPoint() == self._lastinsertionpoint:
   185         if not self._hasfocus:
       
   186             self._hasfocus = True
       
   187         elif self.GetInsertionPoint() == self._lastinsertionpoint:
   186             wx.CallAfter(self.PopupListBox)
   188             wx.CallAfter(self.PopupListBox)
   187         self._lastinsertionpoint = None
   189         self._lastinsertionpoint = None
   188         event.Skip()
   190         event.Skip()
   189 
   191 
   190     def OnControlChanged(self, event):
   192     def OnControlChanged(self, event):
   195             listentries = (listentries + [res])[-MAX_ITEM_COUNT:]
   197             listentries = (listentries + [res])[-MAX_ITEM_COUNT:]
   196             config.Write(self.element_path, cPickle.dumps(listentries))
   198             config.Write(self.element_path, cPickle.dumps(listentries))
   197             config.Flush()
   199             config.Flush()
   198             self.SetChoices(listentries)
   200             self.SetChoices(listentries)
   199         self.DismissListBox()
   201         self.DismissListBox()
       
   202         self._hasfocus = False
   200         event.Skip()
   203         event.Skip()
   201     
   204     
   202     def SetChoices(self, choices):
   205     def SetChoices(self, choices):
   203         self._choices = choices
   206         self._choices = choices
   204         self.RefreshListBoxChoices()
   207         self.RefreshListBoxChoices()
   205         
   208         
   206     def GetChoices(self):
   209     def GetChoices(self):
   207         return self._choices
   210         return self._choices
   208     
   211     
   209     def SetValueFromSelected(self, selected):
   212     def SetValueFromSelected(self, selected):
   210          """
   213         """
   211          Sets the wx.TextCtrl value from the selected wx.ListCtrl item.
   214         Sets the wx.TextCtrl value from the selected wx.ListCtrl item.
   212          Will do nothing if no item is selected in the wx.ListCtrl.
   215         Will do nothing if no item is selected in the wx.ListCtrl.
   213          """
   216         """
   214          if selected != "":
   217         if selected != "":
   215             self.SetValue(selected)
   218             self.SetValue(selected)
   216          self.DismissListBox()
   219         self.DismissListBox()
   217     
   220     
   218     def RefreshListBoxChoices(self):
   221     def RefreshListBoxChoices(self):
   219         if self.listbox is not None:
   222         if self.listbox is not None:
   220             text = self.GetValue()
   223             text = self.GetValue()
   221             choices = [choice for choice in self._choices if choice.startswith(text)]
   224             choices = [choice for choice in self._choices if choice.startswith(text)]
   227             
   230             
   228             # Show the popup right below or above the button
   231             # Show the popup right below or above the button
   229             # depending on available screen space...
   232             # depending on available screen space...
   230             pos = self.ClientToScreen((0, 0))
   233             pos = self.ClientToScreen((0, 0))
   231             sz = self.GetSize()
   234             sz = self.GetSize()
       
   235             if wx.Platform == '__WXMSW__':
       
   236                 pos.x -= 2
       
   237                 pos.y -= 2
   232             self.listbox.Position(pos, (0, sz[1]))
   238             self.listbox.Position(pos, (0, sz[1]))
   233             
   239             
   234             self.RefreshListBoxChoices()
   240             self.RefreshListBoxChoices()
   235             
   241             
   236             if wx.Platform == '__WXMSW__':
   242             self.listbox.Show()
   237                 self.listbox.Popup()
       
   238             else:
       
   239                 self.listbox.Show()
       
   240             self.AppFrame.EnableScrolling(False)
       
   241 
   243 
   242     def DismissListBox(self):
   244     def DismissListBox(self):
   243         if self.listbox is not None:
   245         if self.listbox is not None:
   244             if wx.Platform == '__WXMSW__':
   246             if self.listbox.ListBox.HasCapture():
   245                 self.listbox.Dismiss()
   247                 self.listbox.ListBox.ReleaseMouse()
   246             else:
   248             self.listbox.Destroy()
   247                 self.listbox.Destroy()
       
   248             self.listbox = None
   249             self.listbox = None
   249         self.AppFrame.EnableScrolling(True)
       
   250