laurent@428: #!/usr/bin/env python laurent@428: # -*- coding: utf-8 -*- laurent@428: andrej@1571: # This file is part of Beremiz, a Integrated Development Environment for andrej@1571: # programming IEC 61131-3 automates supporting plcopen standard and CanFestival. andrej@1571: # andrej@1571: # Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD andrej@1571: # andrej@1571: # See COPYING file for copyrights details. andrej@1571: # andrej@1571: # This program is free software; you can redistribute it and/or andrej@1571: # modify it under the terms of the GNU General Public License andrej@1571: # as published by the Free Software Foundation; either version 2 andrej@1571: # of the License, or (at your option) any later version. andrej@1571: # andrej@1571: # This program is distributed in the hope that it will be useful, andrej@1571: # but WITHOUT ANY WARRANTY; without even the implied warranty of andrej@1571: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the andrej@1571: # GNU General Public License for more details. andrej@1571: # andrej@1571: # You should have received a copy of the GNU General Public License andrej@1571: # along with this program; if not, write to the Free Software andrej@1571: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. laurent@428: andrej@1881: andrej@1881: from __future__ import absolute_import andrej@2431: from six.moves import cPickle laurent@428: import wx 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: andrej@1736: Laurent@1180: class PopupWithListbox(wx.PopupWindow): andrej@1735: andrej@1852: def __init__(self, parent, choices=None): Laurent@1180: wx.PopupWindow.__init__(self, parent, wx.BORDER_SIMPLE) andrej@1735: andrej@1745: self.ListBox = wx.ListBox(self, -1, style=wx.LB_HSCROLL | wx.LB_SINGLE | wx.LB_SORT) andrej@1735: andrej@1852: choices = [] if choices is None else choices laurent@428: self.SetChoices(choices) andrej@1735: Laurent@1180: self.ListBox.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) Laurent@1180: self.ListBox.Bind(wx.EVT_MOTION, self.OnMotion) andrej@1735: laurent@428: def SetChoices(self, choices): laurent@428: max_text_width = 0 laurent@428: max_text_height = 0 andrej@1735: 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) andrej@1735: laurent@428: itemcount = min(len(choices), MAX_ITEM_SHOWN) laurent@428: width = self.Parent.GetSize()[0] andrej@1767: height = \ andrej@1767: max_text_height * itemcount + \ andrej@1767: LISTBOX_INTERVAL_HEIGHT * max(0, itemcount - 1) + \ andrej@1767: 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) andrej@1735: 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) andrej@1735: laurent@428: def GetSelection(self): laurent@428: return self.ListBox.GetStringSelection() andrej@1735: Laurent@1180: def OnLeftDown(self, event): andrej@1498: selected = self.ListBox.HitTest(wx.Point(event.GetX(), event.GetY())) 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)) edouard@3303: elif parent_rect.Contains(event.GetX(), event.GetY()): andrej@1498: result, x, y = self.Parent.HitTest(wx.Point(event.GetX(), event.GetY() + 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() andrej@1735: Laurent@1180: def OnMotion(self, event): Laurent@1180: self.ListBox.SetSelection( andrej@1498: self.ListBox.HitTest(wx.Point(event.GetX(), event.GetY()))) Laurent@1180: event.Skip() andrej@1735: andrej@1736: laurent@428: class TextCtrlAutoComplete(wx.TextCtrl): laurent@428: andrej@1771: def __init__(self, parent, choices=None, dropDownClick=True, andrej@1771: 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) andrej@1735: andrej@1733: # Some variables laurent@428: self._dropDownClick = dropDownClick laurent@428: self._lastinsertionpoint = None Laurent@1180: self._hasfocus = False andrej@1735: laurent@428: self._screenheight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y) laurent@428: self.element_path = element_path andrej@1735: laurent@428: self.listbox = None andrej@1735: laurent@428: self.SetChoices(choices) laurent@428: andrej@1782: # gp = self andrej@1782: # 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: andrej@1733: # 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() andrej@1735: laurent@428: def SetChoices(self, choices): laurent@428: self._choices = choices laurent@428: self.RefreshListBoxChoices() andrej@1735: laurent@428: def GetChoices(self): laurent@428: return self._choices andrej@1735: 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() andrej@1735: 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) andrej@1735: 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])) andrej@1735: laurent@428: self.RefreshListBoxChoices() andrej@1735: 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