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@428: ITEM_INTERVAL_HEIGHT = 3 laurent@428: else: laurent@428: ITEM_INTERVAL_HEIGHT = 6 laurent@428: laurent@428: if wx.Platform == '__WXMSW__': laurent@428: popupclass = wx.PopupTransientWindow laurent@428: else: laurent@428: popupclass = wx.PopupWindow laurent@428: laurent@428: class PopupWithListbox(popupclass): laurent@428: laurent@428: def __init__(self, parent, choices=[]): laurent@428: popupclass.__init__(self, parent, wx.SIMPLE_BORDER) laurent@428: laurent@428: self.ListBox = wx.ListBox(self, -1, style=wx.LB_HSCROLL|wx.LB_SINGLE|wx.LB_SORT) laurent@428: if not wx.Platform == '__WXMSW__': laurent@428: self.ListBox.Bind(wx.EVT_LISTBOX, self.OnListBoxClick) laurent@428: self.ListBox.Bind(wx.EVT_LISTBOX_DCLICK, self.OnListBoxClick) laurent@428: laurent@428: self.SetChoices(choices) laurent@428: laurent@428: self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) 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@428: height = max_text_height * itemcount + ITEM_INTERVAL_HEIGHT * (itemcount + 1) laurent@428: if max_text_width + 10 > width: laurent@428: height += 15 laurent@428: size = wx.Size(width, height) 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@428: def ProcessLeftDown(self, event): laurent@428: selected = self.ListBox.HitTest(wx.Point(event.m_x, event.m_y)) laurent@428: if selected != wx.NOT_FOUND: laurent@428: wx.CallAfter(self.Parent.SetValueFromSelected, self.ListBox.GetString(selected)) laurent@428: return False laurent@428: laurent@428: def OnListBoxClick(self, event): laurent@428: selected = event.GetSelection() laurent@428: if selected != wx.NOT_FOUND: laurent@428: wx.CallAfter(self.Parent.SetValueFromSelected, self.ListBox.GetString(selected)) laurent@428: event.Skip() laurent@428: laurent@428: def OnKeyDown(self, event): laurent@428: self.Parent.ProcessEvent(event) laurent@428: laurent@428: def OnDismiss(self): laurent@428: self.Parent.listbox = None laurent@428: wx.CallAfter(self.Parent.DismissListBox) laurent@428: laurent@428: class TextCtrlAutoComplete(wx.TextCtrl): laurent@428: laurent@428: def __init__ (self, parent, appframe, 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: self.AppFrame = appframe laurent@428: laurent@428: #Some variables laurent@428: self._dropDownClick = dropDownClick laurent@428: self._lastinsertionpoint = None 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 __del__(self): laurent@428: self.AppFrame = None 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@428: self.SetValueFromSelected(self.listbox.GetSelection()) 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@428: if 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@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@428: """ laurent@428: Sets the wx.TextCtrl value from the selected wx.ListCtrl item. laurent@428: Will do nothing if no item is selected in the wx.ListCtrl. laurent@428: """ laurent@428: if selected != "": laurent@428: self.SetValue(selected) laurent@428: 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@428: self.listbox.Position(pos, (0, sz[1])) laurent@428: laurent@428: self.RefreshListBoxChoices() laurent@428: laurent@428: if wx.Platform == '__WXMSW__': laurent@428: self.listbox.Popup() laurent@428: else: laurent@428: self.listbox.Show() laurent@428: self.AppFrame.EnableScrolling(False) laurent@428: laurent@428: def DismissListBox(self): laurent@428: if self.listbox is not None: laurent@428: if wx.Platform == '__WXMSW__': laurent@428: self.listbox.Dismiss() laurent@428: else: laurent@428: self.listbox.Destroy() laurent@428: self.listbox = None laurent@428: self.AppFrame.EnableScrolling(True) laurent@428: