controls/TextCtrlAutoComplete.py
changeset 814 5743cbdff669
parent 724 e0630d262ac3
child 1180 276a30c68eaa
equal deleted inserted replaced
813:1460273f40ed 814:5743cbdff669
       
     1 #!/usr/bin/env python
       
     2 # -*- coding: utf-8 -*-
       
     3 
       
     4 #This file is part of Beremiz, a Integrated Development Environment for
       
     5 #programming IEC 61131-3 automates supporting plcopen standard and CanFestival. 
       
     6 #
       
     7 #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
       
     8 #
       
     9 #See COPYING file for copyrights details.
       
    10 #
       
    11 #This library is free software; you can redistribute it and/or
       
    12 #modify it under the terms of the GNU General Public
       
    13 #License as published by the Free Software Foundation; either
       
    14 #version 2.1 of the License, or (at your option) any later version.
       
    15 #
       
    16 #This library is distributed in the hope that it will be useful,
       
    17 #but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    18 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
       
    19 #General Public License for more details.
       
    20 #
       
    21 #You should have received a copy of the GNU General Public
       
    22 #License along with this library; if not, write to the Free Software
       
    23 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
       
    24 
       
    25 import wx
       
    26 import cPickle
       
    27 
       
    28 MAX_ITEM_COUNT = 10
       
    29 MAX_ITEM_SHOWN = 6
       
    30 if wx.Platform == '__WXMSW__':
       
    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     
       
   109 class TextCtrlAutoComplete(wx.TextCtrl):
       
   110 
       
   111     def __init__ (self, parent, appframe, choices=None, dropDownClick=True,
       
   112                   element_path=None, **therest):
       
   113         """
       
   114         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
       
   116         by calling setChoices.
       
   117         """
       
   118 
       
   119         therest['style'] = wx.TE_PROCESS_ENTER | therest.get('style', 0)
       
   120 
       
   121         wx.TextCtrl.__init__(self, parent, **therest)
       
   122         self.AppFrame = appframe
       
   123         
       
   124         #Some variables
       
   125         self._dropDownClick = dropDownClick
       
   126         self._lastinsertionpoint = None
       
   127         
       
   128         self._screenheight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
       
   129         self.element_path = element_path
       
   130         
       
   131         self.listbox = None
       
   132         
       
   133         self.SetChoices(choices)
       
   134 
       
   135         #gp = self
       
   136         #while ( gp != None ) :
       
   137         #    gp.Bind ( wx.EVT_MOVE , self.onControlChanged, gp )
       
   138         #    gp.Bind ( wx.EVT_SIZE , self.onControlChanged, gp )
       
   139         #    gp = gp.GetParent()
       
   140 
       
   141         self.Bind(wx.EVT_KILL_FOCUS, self.OnControlChanged)
       
   142         self.Bind(wx.EVT_TEXT_ENTER, self.OnControlChanged)
       
   143         self.Bind(wx.EVT_TEXT, self.OnEnteredText)
       
   144         self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
       
   145 
       
   146         #If need drop down on left click
       
   147         if dropDownClick:
       
   148             self.Bind(wx.EVT_LEFT_DOWN, self.OnClickToggleDown)
       
   149             self.Bind(wx.EVT_LEFT_UP, self.OnClickToggleUp)
       
   150 
       
   151     def __del__(self):
       
   152         self.AppFrame = None
       
   153 
       
   154     def ChangeValue(self, value):
       
   155         wx.TextCtrl.ChangeValue(self, value)
       
   156         self.RefreshListBoxChoices()
       
   157 
       
   158     def OnEnteredText(self, event):
       
   159         wx.CallAfter(self.RefreshListBoxChoices)
       
   160         event.Skip()
       
   161 
       
   162     def OnKeyDown(self, event):
       
   163         """ Do some work when the user press on the keys:
       
   164             up and down: move the cursor
       
   165         """
       
   166         keycode = event.GetKeyCode()
       
   167         if keycode in [wx.WXK_DOWN, wx.WXK_UP]:
       
   168             self.PopupListBox()
       
   169             if keycode == wx.WXK_DOWN:
       
   170                 self.listbox.MoveSelection(1)
       
   171             else:
       
   172                 self.listbox.MoveSelection(-1)
       
   173         elif keycode in [wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_RETURN] and self.listbox is not None:
       
   174             self.SetValueFromSelected(self.listbox.GetSelection())
       
   175         elif event.GetKeyCode() == wx.WXK_ESCAPE:
       
   176             self.DismissListBox()
       
   177         else:
       
   178             event.Skip()
       
   179 
       
   180     def OnClickToggleDown(self, event):
       
   181         self._lastinsertionpoint = self.GetInsertionPoint()
       
   182         event.Skip()
       
   183 
       
   184     def OnClickToggleUp(self, event):
       
   185         if self.GetInsertionPoint() == self._lastinsertionpoint:
       
   186             wx.CallAfter(self.PopupListBox)
       
   187         self._lastinsertionpoint = None
       
   188         event.Skip()
       
   189 
       
   190     def OnControlChanged(self, event):
       
   191         res = self.GetValue()
       
   192         config = wx.ConfigBase.Get()
       
   193         listentries = cPickle.loads(str(config.Read(self.element_path, cPickle.dumps([]))))
       
   194         if res and res not in listentries:
       
   195             listentries = (listentries + [res])[-MAX_ITEM_COUNT:]
       
   196             config.Write(self.element_path, cPickle.dumps(listentries))
       
   197             config.Flush()
       
   198             self.SetChoices(listentries)
       
   199         self.DismissListBox()
       
   200         event.Skip()
       
   201     
       
   202     def SetChoices(self, choices):
       
   203         self._choices = choices
       
   204         self.RefreshListBoxChoices()
       
   205         
       
   206     def GetChoices(self):
       
   207         return self._choices
       
   208     
       
   209     def SetValueFromSelected(self, selected):
       
   210          """
       
   211          Sets the wx.TextCtrl value from the selected wx.ListCtrl item.
       
   212          Will do nothing if no item is selected in the wx.ListCtrl.
       
   213          """
       
   214          if selected != "":
       
   215             self.SetValue(selected)
       
   216          self.DismissListBox()
       
   217     
       
   218     def RefreshListBoxChoices(self):
       
   219         if self.listbox is not None:
       
   220             text = self.GetValue()
       
   221             choices = [choice for choice in self._choices if choice.startswith(text)]
       
   222             self.listbox.SetChoices(choices)
       
   223 
       
   224     def PopupListBox(self):
       
   225         if self.listbox is None:
       
   226             self.listbox = PopupWithListbox(self)
       
   227             
       
   228             # Show the popup right below or above the button
       
   229             # depending on available screen space...
       
   230             pos = self.ClientToScreen((0, 0))
       
   231             sz = self.GetSize()
       
   232             self.listbox.Position(pos, (0, sz[1]))
       
   233             
       
   234             self.RefreshListBoxChoices()
       
   235             
       
   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