# HG changeset patch # User greg # Date 1225809884 -3600 # Node ID 66843376a982d81fea75bda4b2ef603adeb789d3 # Parent e12fb29fe79773798851d94320a978d0cf0ec07d add autocomplete support for beremiz's textctrl diff -r e12fb29fe797 -r 66843376a982 Beremiz.py --- a/Beremiz.py Fri Oct 31 09:56:14 2008 +0100 +++ b/Beremiz.py Tue Nov 04 15:44:44 2008 +0100 @@ -73,6 +73,7 @@ wx.Yield() import wx.lib.buttons, wx.lib.statbmp +import TextCtrlAutoComplete, cPickle import types, time, re, platform, time, traceback, commands from plugger import PluginsRoot from wxPopen import ProcessLogger @@ -438,8 +439,7 @@ if projectOpen: self.PluginRoot = PluginsRoot(self, self.Log, self.runtime_port) self.PluginRoot.LoadProject(projectOpen, buildpath) - self.RefreshPLCParams() - self.RefreshPluginTree() + self.RefreshAll() else: self.PluginRoot = None @@ -1172,8 +1172,17 @@ spinctrl.SetValue(element_infos["value"]) spinctrl.Bind(wx.EVT_SPINCTRL, self.GetTextCtrlCallBackFunction(spinctrl, plugin, element_path), id=id) else: - textctrl = wx.TextCtrl(id=id, name=element_infos["name"], parent=parent, - pos=wx.Point(0, 0), size=wx.Size(150, 25), style=0)#wx.TE_PROCESS_ENTER) + choices = cPickle.loads(str(config.Read(element_path, cPickle.dumps([""])))) + textctrl = TextCtrlAutoComplete.TextCtrlAutoComplete(id=id, + name=element_infos["name"], + parent=parent, + choices=choices, + selectCallback = None, + element_path=element_path, + pos=wx.Point(0, 0), + size=wx.Size(150, 25), + style=0) + boxsizer.AddWindow(textctrl, 0, border=0, flag=0) textctrl.ChangeValue(str(element_infos["value"])) textctrl.Bind(wx.EVT_TEXT, self.GetTextCtrlCallBackFunction(textctrl, plugin, element_path)) @@ -1194,8 +1203,7 @@ self.PluginRoot = PluginsRoot(self, self.Log, self.runtime_port) res = self.PluginRoot.NewProject(projectpath) if not res : - self.RefreshPLCParams() - self.RefreshPluginTree() + self.RefreshAll() self.RefreshMainMenu() else: message = wx.MessageDialog(self, res, "ERROR", wx.OK|wx.ICON_ERROR) @@ -1218,8 +1226,7 @@ self.PluginRoot = PluginsRoot(self, self.Log, self.runtime_port) result = self.PluginRoot.LoadProject(projectpath) if not result: - self.RefreshPLCParams() - self.RefreshPluginTree() + self.RefreshAll() self.RefreshMainMenu() else: message = wx.MessageDialog(self, result, "Error", wx.OK|wx.ICON_ERROR) @@ -1248,8 +1255,7 @@ self.PluginInfos = {} self.PluginRoot = None self.Log.flush() - self.RefreshPLCParams() - self.RefreshPluginTree() + self.RefreshAll() self.RefreshMainMenu() event.Skip() diff -r e12fb29fe797 -r 66843376a982 TextCtrlAutoComplete.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TextCtrlAutoComplete.py Tue Nov 04 15:44:44 2008 +0100 @@ -0,0 +1,586 @@ + +''' + +wxPython Custom Widget Collection 20060207 +Written By: Edward Flick (eddy -=at=- cdf-imaging -=dot=- com) + Michele Petrazzo (michele -=dot=- petrazzo -=at=- unipex -=dot=- it) + Will Sadkin (wsadkin-=at=- nameconnector -=dot=- com) +Copyright 2006 (c) CDF Inc. ( http://www.cdf-imaging.com ) +Contributed to the wxPython project under the wxPython project's license. + +''' + +import locale, wx, sys, cStringIO + +import wx.lib.mixins.listctrl as listmix +import cPickle +from wx import ImageFromStream, BitmapFromImage +#---------------------------------------------------------------------- +def getSmallUpArrowData(): + return \ +'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\ +\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\ +\x00\x00C\xb0\x89\ +\xd3.\x10\xd1m\xc3\xe5*\xbc.\x80i\xc2\x17.\x8c\xa3y\x81\x01\x00\xa1\x0e\x04e\ +?\x84B\xef\x00\x00\x00\x00IEND\xaeB`\x82" + +def getSmallDnArrowBitmap(): + return BitmapFromImage(getSmallDnArrowImage()) + +def getSmallDnArrowImage(): + stream = cStringIO.StringIO(getSmallDnArrowData()) + return ImageFromStream(stream) +#---------------------------------------------------------------------- + +class myListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin): + def __init__(self, parent, ID=-1, pos=wx.DefaultPosition, + size=wx.DefaultSize, style=0): + wx.ListCtrl.__init__(self, parent, ID, pos, size, style) + listmix.ListCtrlAutoWidthMixin.__init__(self) + +class TextCtrlAutoComplete (wx.TextCtrl, listmix.ColumnSorterMixin ): + + def __init__ ( self, parent, colNames=None, choices = None, + multiChoices=None, showHead=True, dropDownClick=True, + colFetch=-1, colSearch=0, hideOnNoMatch=True, + selectCallback=None, entryCallback=None, matchFunction=None, + element_path=None, **therest) : + ''' + Constructor works just like wx.TextCtrl except you can pass in a + list of choices. You can also change the choice list at any time + by calling setChoices. + ''' + + if therest.has_key('style'): + therest['style']=wx.TE_PROCESS_ENTER | therest['style'] + else: + therest['style']=wx.TE_PROCESS_ENTER + + wx.TextCtrl.__init__(self, parent, **therest ) + + #Some variables + self._dropDownClick = dropDownClick + self._colNames = colNames + self._multiChoices = multiChoices + self._showHead = showHead + self._choices = choices + self._lastinsertionpoint = 0 + self._hideOnNoMatch = hideOnNoMatch + self._selectCallback = selectCallback + self._entryCallback = entryCallback + self._matchFunction = matchFunction + + self._screenheight = wx.SystemSettings.GetMetric( wx.SYS_SCREEN_Y ) + self.element_path = element_path + #sort variable needed by listmix + self.itemDataMap = dict() + + #widgets + self.dropdown = wx.PopupWindow( self ) + + #Control the style + flags = wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_SORT_ASCENDING + if not (showHead and multiChoices) : + flags = flags | wx.LC_NO_HEADER + + #Create the list and bind the events + self.dropdownlistbox = myListCtrl( self.dropdown, style=flags, + pos=wx.Point( 0, 0) ) + + #initialize the parent + if multiChoices: ln = len(multiChoices) + else: ln = 1 + #else: ln = len(choices) + listmix.ColumnSorterMixin.__init__(self, ln) + + #load the data + if multiChoices: self.SetMultipleChoices (multiChoices, colSearch=colSearch, colFetch=colFetch) + else: self.SetChoices ( choices ) + + #gp = self + #while ( gp != None ) : + # gp.Bind ( wx.EVT_MOVE , self.onControlChanged, gp ) + # gp.Bind ( wx.EVT_SIZE , self.onControlChanged, gp ) + # gp = gp.GetParent() + + self.Bind( wx.EVT_KILL_FOCUS, self.onControlChanged, self ) + self.Bind( wx.EVT_TEXT , self.onEnteredText, self ) + self.Bind( wx.EVT_KEY_DOWN , self.onKeyDown, self ) + + #If need drop down on left click + if dropDownClick: + self.Bind ( wx.EVT_LEFT_DOWN , self.onClickToggleDown, self ) + self.Bind ( wx.EVT_LEFT_UP , self.onClickToggleUp, self ) + + self.dropdown.Bind( wx.EVT_LISTBOX , self.onListItemSelected, self.dropdownlistbox ) + self.dropdownlistbox.Bind(wx.EVT_LEFT_DOWN, self.onListClick) + self.dropdownlistbox.Bind(wx.EVT_LEFT_DCLICK, self.onListDClick) + self.dropdownlistbox.Bind(wx.EVT_LIST_COL_CLICK, self.onListColClick) + + self.il = wx.ImageList(16, 16) + + self.sm_dn = self.il.Add(getSmallDnArrowBitmap()) + self.sm_up = self.il.Add(getSmallUpArrowBitmap()) + + self.dropdownlistbox.SetImageList(self.il, wx.IMAGE_LIST_SMALL) + self._ascending = True + + + #-- methods called from mixin class + def GetSortImages(self): + return (self.sm_dn, self.sm_up) + + def GetListCtrl(self): + return self.dropdownlistbox + + # -- event methods + def onListClick(self, evt): + toSel, flag = self.dropdownlistbox.HitTest( evt.GetPosition() ) + #no values on poition, return + if toSel == -1: return + self.dropdownlistbox.Select(toSel) + + def onListDClick(self, evt): + self._setValueFromSelected() + + def onListColClick(self, evt): + col = evt.GetColumn() + + #reverse the sort + if col == self._colSearch: + self._ascending = not self._ascending + + self.SortListItems( evt.GetColumn(), ascending=self._ascending ) + self._colSearch = evt.GetColumn() + evt.Skip() + + def onEnteredText(self, event): + text = event.GetString() + + if self._entryCallback: + self._entryCallback() + + if not text: + # control is empty; hide dropdown if shown: + if self.dropdown.IsShown(): + self._showDropDown(False) + event.Skip() + return + + + found = False + if self._multiChoices: + #load the sorted data into the listbox + dd = self.dropdownlistbox + choices = [dd.GetItem(x, self._colSearch).GetText() + for x in xrange(dd.GetItemCount())] + else: + choices = self._choices + + for numCh, choice in enumerate(choices): + if self._matchFunction and self._matchFunction(text, choice): + found = True + elif choice.lower().startswith(text.lower()) : + found = True + if found: + self._showDropDown(True) + item = self.dropdownlistbox.GetItem(numCh) + toSel = item.GetId() + self.dropdownlistbox.Select(toSel) + break + + if not found: + self.dropdownlistbox.Select(self.dropdownlistbox.GetFirstSelected(), False) + if self._hideOnNoMatch: + self._showDropDown(False) + + self._listItemVisible() + + event.Skip () + + def onKeyDown ( self, event ) : + """ Do some work when the user press on the keys: + up and down: move the cursor + left and right: move the search + """ + skip = True + sel = self.dropdownlistbox.GetFirstSelected() + visible = self.dropdown.IsShown() + + KC = event.GetKeyCode() + if KC == wx.WXK_DOWN : + if sel < (self.dropdownlistbox.GetItemCount () - 1) : + self.dropdownlistbox.Select ( sel+1 ) + self._listItemVisible() + self._showDropDown () + skip = False + elif KC == wx.WXK_UP : + if sel > 0 : + self.dropdownlistbox.Select ( sel - 1 ) + self._listItemVisible() + self._showDropDown () + skip = False + elif KC == wx.WXK_LEFT : + if not self._multiChoices: return + if self._colSearch > 0: + self._colSearch -=1 + self._showDropDown () + elif KC == wx.WXK_RIGHT: + if not self._multiChoices: return + if self._colSearch < self.dropdownlistbox.GetColumnCount() -1: + self._colSearch += 1 + self._showDropDown() + + if visible : + if event.GetKeyCode() == wx.WXK_RETURN : + self._setValueFromSelected() + skip = False + if event.GetKeyCode() == wx.WXK_ESCAPE : + self._showDropDown( False ) + skip = False + if skip : + event.Skip() + + def onListItemSelected (self, event): + self._setValueFromSelected() + event.Skip() + + def onClickToggleDown(self, event): + self._lastinsertionpoint = self.GetInsertionPoint() + event.Skip () + + def onClickToggleUp ( self, event ) : + if ( self.GetInsertionPoint() == self._lastinsertionpoint ) : + self._showDropDown ( not self.dropdown.IsShown() ) + event.Skip () + + def onControlChanged(self, event): + res = self.GetValue() + config = wx.ConfigBase.Get() + listentries = cPickle.loads(str(config.Read(self.element_path, cPickle.dumps([])))) + if len(res) and res not in listentries: + config.Write(self.element_path, cPickle.dumps((listentries + [res])[-10:])) + config.Flush() + self.SetChoices((listentries + [res])[-10:]) + self._showDropDown( False ) + event.Skip() + + + # -- Interfaces methods + def SetMultipleChoices(self, choices, colSearch=0, colFetch=-1): + ''' Set multi-column choice + ''' + self._multiChoices = choices + self._choices = None + if not isinstance(self._multiChoices, list): + self._multiChoices = [ x for x in self._multiChoices] + + flags = wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_SORT_ASCENDING + if not self._showHead: + flags |= wx.LC_NO_HEADER + self.dropdownlistbox.SetWindowStyleFlag(flags) + + #prevent errors on "old" systems + if sys.version.startswith("2.3"): + self._multiChoices.sort(lambda x, y: cmp(x[0].lower(), y[0].lower())) + else: + self._multiChoices.sort(key=lambda x: locale.strxfrm(x[0]).lower() ) + + self._updateDataList(self._multiChoices) + + lChoices = len(choices) + if lChoices < 2: + raise ValueError, "You have to pass me a multi-dimension list" + + for numCol, rowValues in enumerate(choices[0]): + + if self._colNames: colName = self._colNames[numCol] + else: colName = "Select %i" % numCol + + self.dropdownlistbox.InsertColumn(numCol, colName) + + for numRow, valRow in enumerate(choices): + + for numCol, colVal in enumerate(valRow): + if numCol == 0: + index = self.dropdownlistbox.InsertImageStringItem(sys.maxint, colVal, -1) + self.dropdownlistbox.SetStringItem(index, numCol, colVal) + self.dropdownlistbox.SetItemData(index, numRow) + + self._setListSize() + self._colSearch = colSearch + self._colFetch = colFetch + + def SetChoices(self, choices): + ''' + Sets the choices available in the popup wx.ListBox. + The items will be sorted case insensitively. + ''' + self._choices = choices + self._multiChoices = None + flags = wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_SORT_ASCENDING | wx.LC_NO_HEADER + self.dropdownlistbox.SetWindowStyleFlag(flags) + + #if not isinstance(choices, list): + # self._choices = [ x for x in choices if len(x)] + self._choices = [ x for x in choices if len(x)] + #prevent errors on "old" systems + if sys.version.startswith("2.3"): + self._choices.sort(lambda x, y: cmp(x.lower(), y.lower())) + else: + self._choices.sort(key=lambda x: locale.strxfrm(x).lower()) + + self._updateDataList(self._choices) + + self.dropdownlistbox.InsertColumn(0, "") + + for num, colVal in enumerate(self._choices): + index = self.dropdownlistbox.InsertImageStringItem(sys.maxint, colVal, -1) + + self.dropdownlistbox.SetStringItem(index, 0, colVal) + self.dropdownlistbox.SetItemData(index, num) + + self._setListSize() + + # there is only one choice for both search and fetch if setting a single column: + self._colSearch = 0 + self._colFetch = -1 + + def GetChoices(self): + if self._choices: + return self._choices + else: + return self._multiChoices + + def SetSelectCallback(self, cb=None): + self._selectCallback = cb + + def SetEntryCallback(self, cb=None): + self._entryCallback = cb + + def SetMatchFunction(self, mf=None): + self._matchFunction = mf + + + #-- Internal methods + def _setValueFromSelected( self ) : + ''' + Sets the wx.TextCtrl value from the selected wx.ListCtrl item. + Will do nothing if no item is selected in the wx.ListCtrl. + ''' + sel = self.dropdownlistbox.GetFirstSelected() + if sel > -1: + if self._colFetch != -1: col = self._colFetch + else: col = self._colSearch + + itemtext = self.dropdownlistbox.GetItem(sel, col).GetText() + if self._selectCallback: + dd = self.dropdownlistbox + values = [dd.GetItem(sel, x).GetText() + for x in xrange(dd.GetColumnCount())] + self._selectCallback( values ) + + self.SetValue (itemtext) + self.SetInsertionPointEnd () + self.SetSelection ( -1, -1 ) + self._showDropDown ( False ) + + + def _showDropDown ( self, show = True ) : + ''' + Either display the drop down list (show = True) or hide it (show = False). + ''' + if show : + size = self.dropdown.GetSize() + width, height = self . GetSizeTuple() + x, y = self . ClientToScreenXY ( 0, height ) + if size.GetWidth() != width : + size.SetWidth(width) + self.dropdown.SetSize(size) + self.dropdownlistbox.SetSize(self.dropdown.GetClientSize()) + if (y + size.GetHeight()) < self._screenheight : + self.dropdown . SetPosition ( wx.Point(x, y) ) + else: + self.dropdown . SetPosition ( wx.Point(x, y - height - size.GetHeight()) ) + self.dropdown.Show ( show ) + + def _listItemVisible( self ) : + ''' + Moves the selected item to the top of the list ensuring it is always visible. + ''' + toSel = self.dropdownlistbox.GetFirstSelected () + if toSel == -1: return + self.dropdownlistbox.EnsureVisible( toSel ) + + def _updateDataList(self, choices): + #delete, if need, all the previous data + if self.dropdownlistbox.GetColumnCount() != 0: + self.dropdownlistbox.DeleteAllColumns() + self.dropdownlistbox.DeleteAllItems() + + #and update the dict + if choices: + for numVal, data in enumerate(choices): + self.itemDataMap[numVal] = data + else: + numVal = 0 + self.SetColumnCount(numVal) + + def _setListSize(self): + if self._multiChoices: + choices = self._multiChoices + else: + choices = self._choices + + longest = 0 + for choice in choices : + longest = max(len(choice), longest) + + longest += 3 + itemcount = min( len( choices ) , 7 ) + 2 + charheight = self.dropdownlistbox.GetCharHeight() + charwidth = self.dropdownlistbox.GetCharWidth() + self.popupsize = wx.Size( charwidth*longest, charheight*itemcount ) + self.dropdownlistbox.SetSize ( self.popupsize ) + self.dropdown.SetClientSize( self.popupsize ) + + + + +class test: + def __init__(self): + args = dict() + if 1: + args["colNames"] = ("col1", "col2") + args["multiChoices"] = [ ("Zoey","WOW"), ("Alpha", "wxPython"), + ("Ceda","Is"), ("Beta", "fantastic"), + ("zoebob", "!!")] + args["colFetch"] = 1 + else: + args["choices"] = ["123", "cs", "cds", "Bob","Marley","Alpha"] + + args["selectCallback"] = self.selectCallback + + self.dynamic_choices = [ + 'aardvark', 'abandon', 'acorn', 'acute', 'adore', + 'aegis', 'ascertain', 'asteroid', + 'beautiful', 'bold', 'classic', + 'daring', 'dazzling', 'debonair', 'definitive', + 'effective', 'elegant', + 'http://python.org', 'http://www.google.com', + 'fabulous', 'fantastic', 'friendly', 'forgiving', 'feature', + 'sage', 'scarlet', 'scenic', 'seaside', 'showpiece', 'spiffy', + 'www.wxPython.org', 'www.osafoundation.org' + ] + + + app = wx.PySimpleApp() + frm = wx.Frame(None,-1,"Test",style=wx.TAB_TRAVERSAL|wx.DEFAULT_FRAME_STYLE) + panel = wx.Panel(frm) + sizer = wx.BoxSizer(wx.VERTICAL) + + self._ctrl = TextCtrlAutoComplete(panel, **args) + but = wx.Button(panel,label="Set other multi-choice") + but.Bind(wx.EVT_BUTTON, self.onBtMultiChoice) + but2 = wx.Button(panel,label="Set other one-colum choice") + but2.Bind(wx.EVT_BUTTON, self.onBtChangeChoice) + but3 = wx.Button(panel,label="Set the starting choices") + but3.Bind(wx.EVT_BUTTON, self.onBtStartChoices) + but4 = wx.Button(panel,label="Enable dynamic choices") + but4.Bind(wx.EVT_BUTTON, self.onBtDynamicChoices) + + sizer.Add(but, 0, wx.ADJUST_MINSIZE, 0) + sizer.Add(but2, 0, wx.ADJUST_MINSIZE, 0) + sizer.Add(but3, 0, wx.ADJUST_MINSIZE, 0) + sizer.Add(but4, 0, wx.ADJUST_MINSIZE, 0) + sizer.Add(self._ctrl, 0, wx.EXPAND|wx.ADJUST_MINSIZE, 0) + panel.SetAutoLayout(True) + panel.SetSizer(sizer) + sizer.Fit(panel) + sizer.SetSizeHints(panel) + panel.Layout() + app.SetTopWindow(frm) + frm.Show() + but.SetFocus() + app.MainLoop() + + def onBtChangeChoice(self, event): + #change the choices + self._ctrl.SetChoices(["123", "cs", "cds", "Bob","Marley","Alpha"]) + self._ctrl.SetEntryCallback(None) + self._ctrl.SetMatchFunction(None) + + def onBtMultiChoice(self, event): + #change the choices + self._ctrl.SetMultipleChoices( [ ("Test","Hello"), ("Other word","World"), + ("Yes!","it work?") ], colFetch = 1 ) + self._ctrl.SetEntryCallback(None) + self._ctrl.SetMatchFunction(None) + + def onBtStartChoices(self, event): + #change the choices + self._ctrl.SetMultipleChoices( [ ("Zoey","WOW"), ("Alpha", "wxPython"), + ("Ceda","Is"), ("Beta", "fantastic"), + ("zoebob", "!!")], colFetch = 1 ) + self._ctrl.SetEntryCallback(None) + self._ctrl.SetMatchFunction(None) + + def onBtDynamicChoices(self, event): + ''' + Demonstrate dynamic adjustment of the auto-complete list, based on what's + been typed so far: + ''' + self._ctrl.SetChoices(self.dynamic_choices) + self._ctrl.SetEntryCallback(self.setDynamicChoices) + self._ctrl.SetMatchFunction(self.match) + + + def match(self, text, choice): + ''' + Demonstrate "smart" matching feature, by ignoring http:// and www. when doing + matches. + ''' + t = text.lower() + c = choice.lower() + if c.startswith(t): return True + if c.startswith(r'http://'): c = c[7:] + if c.startswith(t): return True + if c.startswith('www.'): c = c[4:] + return c.startswith(t) + + def setDynamicChoices(self): + ctrl = self._ctrl + text = ctrl.GetValue().lower() + current_choices = ctrl.GetChoices() + choices = [choice for choice in self.dynamic_choices if self.match(text, choice)] + if choices != current_choices: + ctrl.SetChoices(choices) + + def selectCallback(self, values): + """ Simply function that receive the row values when the + user select an item + """ + print "Select Callback called...:", values + + +if __name__ == "__main__": + test() +