laurent@428: #!/usr/bin/env python
laurent@428: # -*- coding: utf-8 -*-
laurent@428: 
laurent@428: #This file is part of Beremiz, a Integrated Development Environment for
laurent@428: #programming IEC 61131-3 automates supporting plcopen standard and CanFestival. 
laurent@428: #
laurent@428: #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
laurent@428: #
laurent@428: #See COPYING file for copyrights details.
laurent@428: #
laurent@428: #This library is free software; you can redistribute it and/or
laurent@428: #modify it under the terms of the GNU General Public
laurent@428: #License as published by the Free Software Foundation; either
laurent@428: #version 2.1 of the License, or (at your option) any later version.
laurent@428: #
laurent@428: #This library is distributed in the hope that it will be useful,
laurent@428: #but WITHOUT ANY WARRANTY; without even the implied warranty of
laurent@428: #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
laurent@428: #General Public License for more details.
laurent@428: #
laurent@428: #You should have received a copy of the GNU General Public
laurent@428: #License along with this library; if not, write to the Free Software
laurent@428: #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
laurent@428: 
laurent@428: import wx
laurent@428: import cPickle
laurent@428: 
laurent@428: MAX_ITEM_COUNT = 10
laurent@428: MAX_ITEM_SHOWN = 6
laurent@428: if wx.Platform == '__WXMSW__':
Laurent@1180:     LISTBOX_BORDER_HEIGHT = 2
Laurent@1180:     LISTBOX_INTERVAL_HEIGHT = 0
laurent@428: else:
Laurent@1180:     LISTBOX_BORDER_HEIGHT = 4
Laurent@1180:     LISTBOX_INTERVAL_HEIGHT = 6
Laurent@1180: 
Laurent@1180: class PopupWithListbox(wx.PopupWindow):
laurent@428:     
laurent@428:     def __init__(self, parent, choices=[]):
Laurent@1180:         wx.PopupWindow.__init__(self, parent, wx.BORDER_SIMPLE)
laurent@428:         
laurent@428:         self.ListBox = wx.ListBox(self, -1, style=wx.LB_HSCROLL|wx.LB_SINGLE|wx.LB_SORT)
Laurent@1180:         
laurent@428:         self.SetChoices(choices)
laurent@428:         
Laurent@1180:         self.ListBox.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
Laurent@1180:         self.ListBox.Bind(wx.EVT_MOTION, self.OnMotion)
laurent@428:     
laurent@428:     def SetChoices(self, choices):
laurent@428:         max_text_width = 0
laurent@428:         max_text_height = 0
laurent@428:         
laurent@428:         self.ListBox.Clear()
laurent@428:         for choice in choices:
laurent@428:             self.ListBox.Append(choice)
laurent@428:             w, h = self.ListBox.GetTextExtent(choice)
laurent@428:             max_text_width = max(max_text_width, w)
laurent@428:             max_text_height = max(max_text_height, h)
laurent@428:         
laurent@428:         itemcount = min(len(choices), MAX_ITEM_SHOWN)
laurent@428:         width = self.Parent.GetSize()[0]
Laurent@1180:         height = max_text_height * itemcount + \
Laurent@1180:                  LISTBOX_INTERVAL_HEIGHT * max(0, itemcount - 1) + \
Laurent@1180:                  2 * LISTBOX_BORDER_HEIGHT
laurent@428:         if max_text_width + 10 > width:
laurent@428:             height += 15
laurent@428:         size = wx.Size(width, height)
Laurent@1180:         if wx.Platform == '__WXMSW__':
Laurent@1180:             size.width -= 2
laurent@428:         self.ListBox.SetSize(size)
laurent@428:         self.SetClientSize(size)
laurent@428:     
laurent@428:     def MoveSelection(self, direction):
laurent@428:         selected = self.ListBox.GetSelection()
laurent@428:         if selected == wx.NOT_FOUND:
laurent@428:             if direction >= 0:
laurent@428:                 selected = 0
laurent@428:             else:
laurent@428:                 selected = self.ListBox.GetCount() - 1
laurent@428:         else:
laurent@428:             selected = (selected + direction) % (self.ListBox.GetCount() + 1)
laurent@428:         if selected == self.ListBox.GetCount():
laurent@428:             selected = wx.NOT_FOUND
laurent@428:         self.ListBox.SetSelection(selected)
laurent@428:     
laurent@428:     def GetSelection(self):
laurent@428:         return self.ListBox.GetStringSelection()
laurent@428:     
Laurent@1180:     def OnLeftDown(self, event):
laurent@428:         selected = self.ListBox.HitTest(wx.Point(event.m_x, event.m_y))
Laurent@1180:         parent_size = self.Parent.GetSize()
Laurent@1180:         parent_rect = wx.Rect(0, -parent_size[1], parent_size[0], parent_size[1])
laurent@428:         if selected != wx.NOT_FOUND:
laurent@428:             wx.CallAfter(self.Parent.SetValueFromSelected, self.ListBox.GetString(selected))
Laurent@1180:         elif parent_rect.InsideXY(event.m_x, event.m_y):
Laurent@1180:             result, x, y = self.Parent.HitTest(wx.Point(event.m_x, event.m_y + parent_size[1]))
Laurent@1180:             if result != wx.TE_HT_UNKNOWN:
Laurent@1180:                 self.Parent.SetInsertionPoint(self.Parent.XYToPosition(x, y))
Laurent@1180:         else:
Laurent@1180:             wx.CallAfter(self.Parent.DismissListBox)
Laurent@1180:         event.Skip()
Laurent@1180:     
Laurent@1180:     def OnMotion(self, event):
Laurent@1180:         self.ListBox.SetSelection(
Laurent@1180:             self.ListBox.HitTest(wx.Point(event.m_x, event.m_y)))
Laurent@1180:         event.Skip()
laurent@428:     
laurent@428: class TextCtrlAutoComplete(wx.TextCtrl):
laurent@428: 
Laurent@1180:     def __init__ (self, parent, choices=None, dropDownClick=True,
laurent@428:                   element_path=None, **therest):
laurent@428:         """
laurent@428:         Constructor works just like wx.TextCtrl except you can pass in a
laurent@428:         list of choices.  You can also change the choice list at any time
laurent@428:         by calling setChoices.
laurent@428:         """
laurent@428: 
laurent@428:         therest['style'] = wx.TE_PROCESS_ENTER | therest.get('style', 0)
laurent@428: 
laurent@428:         wx.TextCtrl.__init__(self, parent, **therest)
laurent@428:         
laurent@428:         #Some variables
laurent@428:         self._dropDownClick = dropDownClick
laurent@428:         self._lastinsertionpoint = None
Laurent@1180:         self._hasfocus = False
laurent@428:         
laurent@428:         self._screenheight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
laurent@428:         self.element_path = element_path
laurent@428:         
laurent@428:         self.listbox = None
laurent@428:         
laurent@428:         self.SetChoices(choices)
laurent@428: 
laurent@428:         #gp = self
laurent@428:         #while ( gp != None ) :
laurent@428:         #    gp.Bind ( wx.EVT_MOVE , self.onControlChanged, gp )
laurent@428:         #    gp.Bind ( wx.EVT_SIZE , self.onControlChanged, gp )
laurent@428:         #    gp = gp.GetParent()
laurent@428: 
laurent@428:         self.Bind(wx.EVT_KILL_FOCUS, self.OnControlChanged)
laurent@428:         self.Bind(wx.EVT_TEXT_ENTER, self.OnControlChanged)
laurent@428:         self.Bind(wx.EVT_TEXT, self.OnEnteredText)
laurent@428:         self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
laurent@428: 
laurent@428:         #If need drop down on left click
laurent@428:         if dropDownClick:
laurent@428:             self.Bind(wx.EVT_LEFT_DOWN, self.OnClickToggleDown)
laurent@428:             self.Bind(wx.EVT_LEFT_UP, self.OnClickToggleUp)
laurent@428: 
laurent@428:     def ChangeValue(self, value):
laurent@428:         wx.TextCtrl.ChangeValue(self, value)
laurent@428:         self.RefreshListBoxChoices()
laurent@428: 
laurent@428:     def OnEnteredText(self, event):
laurent@428:         wx.CallAfter(self.RefreshListBoxChoices)
laurent@428:         event.Skip()
laurent@428: 
laurent@428:     def OnKeyDown(self, event):
laurent@428:         """ Do some work when the user press on the keys:
laurent@428:             up and down: move the cursor
laurent@428:         """
laurent@428:         keycode = event.GetKeyCode()
laurent@428:         if keycode in [wx.WXK_DOWN, wx.WXK_UP]:
laurent@428:             self.PopupListBox()
laurent@428:             if keycode == wx.WXK_DOWN:
laurent@428:                 self.listbox.MoveSelection(1)
laurent@428:             else:
laurent@428:                 self.listbox.MoveSelection(-1)
laurent@428:         elif keycode in [wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_RETURN] and self.listbox is not None:
Laurent@1180:             selected = self.listbox.GetSelection()
Laurent@1180:             if selected != "":
Laurent@1180:                 self.SetValueFromSelected(selected)
Laurent@1180:             else:
Laurent@1180:                 event.Skip()
laurent@428:         elif event.GetKeyCode() == wx.WXK_ESCAPE:
laurent@428:             self.DismissListBox()
laurent@428:         else:
laurent@428:             event.Skip()
laurent@428: 
laurent@428:     def OnClickToggleDown(self, event):
laurent@428:         self._lastinsertionpoint = self.GetInsertionPoint()
laurent@428:         event.Skip()
laurent@428: 
laurent@428:     def OnClickToggleUp(self, event):
Laurent@1180:         if not self._hasfocus:
Laurent@1180:             self._hasfocus = True
Laurent@1180:         elif self.GetInsertionPoint() == self._lastinsertionpoint:
laurent@428:             wx.CallAfter(self.PopupListBox)
laurent@428:         self._lastinsertionpoint = None
laurent@428:         event.Skip()
laurent@428: 
laurent@428:     def OnControlChanged(self, event):
laurent@428:         res = self.GetValue()
laurent@428:         config = wx.ConfigBase.Get()
laurent@428:         listentries = cPickle.loads(str(config.Read(self.element_path, cPickle.dumps([]))))
laurent@428:         if res and res not in listentries:
laurent@428:             listentries = (listentries + [res])[-MAX_ITEM_COUNT:]
laurent@428:             config.Write(self.element_path, cPickle.dumps(listentries))
laurent@428:             config.Flush()
laurent@428:             self.SetChoices(listentries)
laurent@428:         self.DismissListBox()
Laurent@1180:         self._hasfocus = False
laurent@428:         event.Skip()
laurent@428:     
laurent@428:     def SetChoices(self, choices):
laurent@428:         self._choices = choices
laurent@428:         self.RefreshListBoxChoices()
laurent@428:         
laurent@428:     def GetChoices(self):
laurent@428:         return self._choices
laurent@428:     
laurent@428:     def SetValueFromSelected(self, selected):
Laurent@1180:         """
Laurent@1180:         Sets the wx.TextCtrl value from the selected wx.ListCtrl item.
Laurent@1180:         Will do nothing if no item is selected in the wx.ListCtrl.
Laurent@1180:         """
Laurent@1180:         if selected != "":
laurent@428:             self.SetValue(selected)
Laurent@1180:         self.DismissListBox()
laurent@428:     
laurent@428:     def RefreshListBoxChoices(self):
laurent@428:         if self.listbox is not None:
laurent@428:             text = self.GetValue()
laurent@428:             choices = [choice for choice in self._choices if choice.startswith(text)]
laurent@428:             self.listbox.SetChoices(choices)
laurent@428: 
laurent@428:     def PopupListBox(self):
laurent@428:         if self.listbox is None:
laurent@428:             self.listbox = PopupWithListbox(self)
laurent@428:             
laurent@428:             # Show the popup right below or above the button
laurent@428:             # depending on available screen space...
laurent@428:             pos = self.ClientToScreen((0, 0))
laurent@428:             sz = self.GetSize()
Laurent@1180:             if wx.Platform == '__WXMSW__':
Laurent@1180:                 pos.x -= 2
Laurent@1180:                 pos.y -= 2
laurent@428:             self.listbox.Position(pos, (0, sz[1]))
laurent@428:             
laurent@428:             self.RefreshListBoxChoices()
laurent@428:             
Laurent@1180:             self.listbox.Show()
laurent@428: 
laurent@428:     def DismissListBox(self):
laurent@428:         if self.listbox is not None:
Laurent@1180:             if self.listbox.ListBox.HasCapture():
Laurent@1180:                 self.listbox.ListBox.ReleaseMouse()
Laurent@1180:             self.listbox.Destroy()
laurent@428:             self.listbox = None