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