# HG changeset patch # User Laurent Bessard # Date 1339152031 -7200 # Node ID 0c697b8fc443e1d7c2ba88be8db9cc4e50c36b72 # Parent 4f6d393cb36e488c00834bf82ac1118f2e41ed4a Fixing bugs introduced with the new Beremiz extension paradigm diff -r 4f6d393cb36e -r 0c697b8fc443 LPCManager.py --- a/LPCManager.py Sun May 20 14:57:52 2012 +0200 +++ b/LPCManager.py Fri Jun 08 12:40:31 2012 +0200 @@ -59,28 +59,32 @@ __builtin__.__dict__['_'] = wx.GetTranslation#unicode_translation _base_folder = os.path.split(sys.path[0])[0] -sys.path.append(os.path.join(base_folder, "beremiz")) - -_base_path = path.split(__file__)[0] +sys.path.append(os.path.join(_base_folder, "beremiz")) + +_base_path = os.path.split(__file__)[0] + +import connectors +from LPCconnector import LPC_connector_factory +connectors.connectors["LPC"]=lambda:LPC_connector_factory + +import targets +from LPCtarget import LPC_target +targets.targets["LPC"] = {"xsd": os.path.join(_base_path, "LPCtarget", "XSD"), + "class": lambda:LPC_target, + "code": os.path.join(_base_path,"LPCtarget","plc_LPC_main.c")} +targets.toolchains["makefile"] = os.path.join(_base_path, "LPCtarget", "XSD_toolchain_makefile") + +# helper func to get path to images +from util import misc +misc.opjimg = lambda imgname: os.path.join(_base_folder, "beremiz", "images", imgname+".png") from Beremiz import * from ProjectController import ProjectController from ConfigTreeNode import ConfigTreeNode - -import connectors -from LPCconnector import LPC_connector_factory -connectors.connectors["LPC"]=lambda:LPC_connector_factory - -import targets -from LPCtarget import LPC_target -targets.targets["LPC"]={"xsd": path.join(_base_path, "LPCtarget", "XSD"), - "class": LPC_target, - "code": path.join(_base_path,"LPCtarget","plc_LPC_main.c")} -targets.toolchains["makefile"]= path.join(_base_path, "LPCtarget", "XSD_toolchain_makefile"), - -from util import opjimg +from ProjectNodeEditor import ProjectNodeEditor + from plcopen.structures import LOCATIONDATATYPES -from PLCControler import LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP,\ +from PLCControler import PLCControler, LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP,\ LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY from PLCOpenEditor import IDEFrame, ProjectDialog @@ -88,10 +92,112 @@ try: from canfestival import RootClass as CanOpenRootClass from canfestival.canfestival import _SlaveCTN, _NodeListCTN, NodeManager + from canfestival.NetworkEditor import NetworkEditor + from canfestival.SlaveEditor import SlaveEditor havecanfestival = True except: havecanfestival = False +SCROLLBAR_UNIT = 10 +WINDOW_COLOUR = wx.Colour(240,240,240) +TITLE_COLOUR = wx.Colour(200,200,220) +CHANGED_TITLE_COLOUR = wx.Colour(220,200,220) +CHANGED_WINDOW_COLOUR = wx.Colour(255,240,240) + +if wx.Platform == '__WXMSW__': + faces = { 'times': 'Times New Roman', + 'mono' : 'Courier New', + 'helv' : 'Arial', + 'other': 'Comic Sans MS', + 'size' : 16, + } +else: + faces = { 'times': 'Times', + 'mono' : 'Courier', + 'helv' : 'Helvetica', + 'other': 'new century schoolbook', + 'size' : 18, + } + +# Some helpers to tweak GenBitmapTextButtons +# TODO: declare customized classes instead. +gen_mini_GetBackgroundBrush = lambda obj:lambda dc: wx.Brush(obj.GetParent().GetBackgroundColour(), wx.SOLID) +gen_textbutton_GetLabelSize = lambda obj:lambda:(wx.lib.buttons.GenButton._GetLabelSize(obj)[:-1] + (False,)) + +def make_genbitmaptogglebutton_flat(button): + button.GetBackgroundBrush = gen_mini_GetBackgroundBrush(button) + button.labelDelta = 0 + button.SetBezelWidth(0) + button.SetUseFocusIndicator(False) + +# Patch wx.lib.imageutils so that gray is supported on alpha images +import wx.lib.imageutils +from wx.lib.imageutils import grayOut as old_grayOut +def grayOut(anImage): + if anImage.HasAlpha(): + AlphaData = anImage.GetAlphaData() + else : + AlphaData = None + + old_grayOut(anImage) + + if AlphaData is not None: + anImage.SetAlphaData(AlphaData) + +wx.lib.imageutils.grayOut = grayOut + +class GenBitmapTextButton(wx.lib.buttons.GenBitmapTextButton): + def _GetLabelSize(self): + """ used internally """ + w, h = self.GetTextExtent(self.GetLabel()) + if not self.bmpLabel: + return w, h, False # if there isn't a bitmap use the size of the text + + w_bmp = self.bmpLabel.GetWidth()+2 + h_bmp = self.bmpLabel.GetHeight()+2 + height = h + h_bmp + if w_bmp > w: + width = w_bmp + else: + width = w + return width, height, False + + def DrawLabel(self, dc, width, height, dw=0, dy=0): + bmp = self.bmpLabel + if bmp != None: # if the bitmap is used + if self.bmpDisabled and not self.IsEnabled(): + bmp = self.bmpDisabled + if self.bmpFocus and self.hasFocus: + bmp = self.bmpFocus + if self.bmpSelected and not self.up: + bmp = self.bmpSelected + bw,bh = bmp.GetWidth(), bmp.GetHeight() + if not self.up: + dw = dy = self.labelDelta + hasMask = bmp.GetMask() != None + else: + bw = bh = 0 # no bitmap -> size is zero + + dc.SetFont(self.GetFont()) + if self.IsEnabled(): + dc.SetTextForeground(self.GetForegroundColour()) + else: + dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)) + + label = self.GetLabel() + tw, th = dc.GetTextExtent(label) # size of text + if not self.up: + dw = dy = self.labelDelta + + pos_x = (width-bw)/2+dw # adjust for bitmap and text to centre + pos_y = (height-bh-th)/2+dy + if bmp !=None: + dc.DrawBitmap(bmp, pos_x, pos_y, hasMask) # draw bitmap if available + pos_x = (width-tw)/2+dw # adjust for bitmap and text to centre + pos_y += bh + 2 + + dc.DrawText(label, pos_x, pos_y) # draw the text + #------------------------------------------------------------------------------- # CANFESTIVAL CONFNODE HACK @@ -397,6 +503,9 @@ "Master_NodeId": 1, } + class LPCSlaveEditor(SlaveEditor): + SHOW_PARAMS = False + class LPCCanOpenSlave(_SlaveCTN): XSD = """ @@ -418,6 +527,8 @@ """ % DEFAULT_SETTINGS + EditorType = LPCSlaveEditor + def __init__(self): # TODO change netname when name change NodeManager.__init__(self) @@ -437,6 +548,9 @@ def GetCanDevice(self): return str(self.BaseParams.getIEC_Channel()) + + class LPCNetworkEditor(NetworkEditor): + SHOW_PARAMS = False class LPCCanOpenMaster(_NodeListCTN): XSD = """ @@ -450,7 +564,9 @@ """ % DEFAULT_SETTINGS - + + EditorType = LPCNetworkEditor + def GetCanDevice(self): return str(self.BaseParams.getIEC_Channel()) @@ -468,10 +584,12 @@ if self.GetChildByName("Master") is None: master = self.CTNAddChild("Master", "CanOpenNode", 0) master.BaseParams.setEnabled(False) + master.CTNRequestSave() if self.GetChildByName("Slave") is None: slave = self.CTNAddChild("Slave", "CanOpenSlave", 1) slave.BaseParams.setEnabled(False) + slave.CTNRequestSave() #------------------------------------------------------------------------------- @@ -498,34 +616,42 @@ [SIMULATION_MODE, TRANSFER_MODE] = range(2) +class LPCProjectNodeEditor(ProjectNodeEditor): + SHOW_PARAMS = False + ENABLE_REQUIRED = False + class LPCProjectController(ProjectController): - ConfNodeMethods = [ - {"bitmap" : opjimg("Debug"), + StatusMethods = [ + {"bitmap" : "Debug", "name" : _("Simulate"), "tooltip" : _("Simulate PLC"), "method" : "_Simulate"}, - {"bitmap" : opjimg("Run"), + {"bitmap" : "Run", "name" : _("Run"), "shown" : False, "tooltip" : _("Start PLC"), "method" : "_Run"}, - {"bitmap" : opjimg("Stop"), + {"bitmap" : "Stop", "name" : _("Stop"), "shown" : False, "tooltip" : _("Stop Running PLC"), "method" : "_Stop"}, - {"bitmap" : opjimg("Build"), + {"bitmap" : "Build", "name" : _("Build"), "tooltip" : _("Build project into build folder"), "method" : "_Build"}, - {"bitmap" : opjimg("Transfer"), + {"bitmap" : "Transfer", "name" : _("Transfer"), "shown" : False, "tooltip" : _("Transfer PLC"), "method" : "_Transfer"}, ] - + + ConfNodeMethods = [] + + EditorType = LPCProjectNodeEditor + def __init__(self, frame, logger, buildpath): self.OrigBuildPath = buildpath @@ -547,6 +673,19 @@ self.AbortTransferTimer = None + def GetProjectInfos(self): + infos = PLCControler.GetProjectInfos(self) + configurations = infos["values"].pop(-1) + resources = None + for config_infos in configurations["values"]: + if resources is None: + resources = config_infos["values"][0] + else: + resources["values"].extend(config_infos["values"][0]["values"]) + if resources is not None: + infos["values"].append(resources) + return infos + def ConfNodeLibraryFilePath(self): if self.OrigBuildPath is not None: return os.path.join(self.OrigBuildPath, "pous.xml") @@ -717,6 +856,7 @@ canopen = self.CTNAddChild("CanOpen", "CanOpen", 0) canopen.BaseParams.setEnabled(False) canopen.LoadChildren() + canopen.CTNRequestSave() if self.CTNTestModified(): self.SaveProject() @@ -1084,9 +1224,6 @@ AppendMenu(parent, help='', id=wx.ID_PRINT, kind=wx.ITEM_NORMAL, text=_(u'Print')) parent.AppendSeparator() - AppendMenu(parent, help='', id=wx.ID_PROPERTIES, - kind=wx.ITEM_NORMAL, text=_(u'Properties')) - parent.AppendSeparator() AppendMenu(parent, help='', id=wx.ID_EXIT, kind=wx.ITEM_NORMAL, text=_(u'Quit\tCTRL+Q')) @@ -1095,18 +1232,20 @@ self.Bind(wx.EVT_MENU, self.OnPageSetupMenu, id=wx.ID_PAGE_SETUP) self.Bind(wx.EVT_MENU, self.OnPreviewMenu, id=wx.ID_PREVIEW) self.Bind(wx.EVT_MENU, self.OnPrintMenu, id=wx.ID_PRINT) - self.Bind(wx.EVT_MENU, self.OnPropertiesMenu, id=wx.ID_PROPERTIES) self.Bind(wx.EVT_MENU, self.OnQuitMenu, id=wx.ID_EXIT) self.AddToMenuToolBar([(wx.ID_SAVE, "save.png", _(u'Save'), None), (wx.ID_PRINT, "print.png", _(u'Print'), None)]) + def _init_coll_AddMenu_Items(self, parent): + IDEFrame._init_coll_AddMenu_Items(self, parent, False) + new_id = wx.NewId() + AppendMenu(parent, help='', id=new_id, + kind=wx.ITEM_NORMAL, text=_(u'&Resource')) + self.Bind(wx.EVT_MENU, self.AddResourceMenu, id=new_id) + def _init_ctrls(self, prnt): - IDEFrame._init_ctrls(self, prnt) - - self.Bind(wx.EVT_MENU, self.OnOpenWidgetInspector, id=ID_BEREMIZINSPECTOR) - accel = wx.AcceleratorTable([wx.AcceleratorEntry(wx.ACCEL_CTRL|wx.ACCEL_ALT, ord('I'), ID_BEREMIZINSPECTOR)]) - self.SetAcceleratorTable(accel) + Beremiz._init_ctrls(self, prnt) self.PLCConfig = wx.ScrolledWindow(id=ID_BEREMIZPLCCONFIG, name='PLCConfig', parent=self.LeftNoteBook, pos=wx.Point(0, 0), @@ -1114,16 +1253,29 @@ self.PLCConfig.SetBackgroundColour(wx.WHITE) self.PLCConfig.Bind(wx.EVT_LEFT_DOWN, self.OnPanelLeftDown) self.PLCConfig.Bind(wx.EVT_SIZE, self.OnMoveWindow) + self.MainTabs["PLCConfig"] = (self.PLCConfig, _("Topology")) self.LeftNoteBook.InsertPage(0, self.PLCConfig, _("Topology"), True) - self.LogConsole = wx.TextCtrl(id=ID_BEREMIZLOGCONSOLE, value='', - name='LogConsole', parent=self.BottomNoteBook, pos=wx.Point(0, 0), - size=wx.Size(0, 0), style=wx.TE_MULTILINE|wx.TE_RICH2) - self.LogConsole.Bind(wx.EVT_LEFT_DCLICK, self.OnLogConsoleDClick) - self.BottomNoteBook.AddPage(self.LogConsole, _("Log Console")) - - self._init_beremiz_sizers() - + self.PLCConfigMainSizer = wx.FlexGridSizer(cols=1, hgap=2, rows=2, vgap=2) + self.PLCParamsSizer = wx.BoxSizer(wx.VERTICAL) + self.ConfNodeTreeSizer = wx.FlexGridSizer(cols=2, hgap=0, rows=0, vgap=2) + self.ConfNodeTreeSizer.AddGrowableCol(0) + self.ConfNodeTreeSizer.AddGrowableCol(1) + + self.PLCConfigMainSizer.AddSizer(self.PLCParamsSizer, 0, border=10, flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT) + self.PLCConfigMainSizer.AddSizer(self.ConfNodeTreeSizer, 0, border=10, flag=wx.BOTTOM|wx.LEFT|wx.RIGHT) + self.PLCConfigMainSizer.AddGrowableCol(0) + self.PLCConfigMainSizer.AddGrowableRow(1) + + self.PLCConfig.SetSizer(self.PLCConfigMainSizer) + + self.AUIManager.Update() + + def __init__(self, parent, projectOpen=None, buildpath=None, ctr=None, debug=True): + self.ConfNodeInfos = {} + + Beremiz.__init__(self, parent, projectOpen, buildpath, ctr, debug) + def OnCloseFrame(self, event): global frame @@ -1144,18 +1296,16 @@ event.Veto() - def ShowProperties(self): - old_values = self.Controler.GetProjectProperties() - dialog = ProjectDialog(self ,False) - dialog.SetValues(old_values) - if dialog.ShowModal() == wx.ID_OK: - new_values = dialog.GetValues() - new_values["creationDateTime"] = old_values["creationDateTime"] - if new_values != old_values: - self.Controler.SetProjectProperties(None, new_values) - self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, - PROJECTTREE, POUINSTANCEVARIABLESPANEL, SCALING) - dialog.Destroy() + def OnMoveWindow(self, event): + self.GetBestSize() + self.RefreshScrollBars() + event.Skip() + + def OnPanelLeftDown(self, event): + focused = self.FindFocus() + if isinstance(focused, TextCtrlAutoComplete): + focused.DismissListBox() + event.Skip() def RefreshFileMenu(self): MenuToolBar = self.Panes["MenuToolBar"] @@ -1184,7 +1334,6 @@ project_modified = self.CTR.ProjectTestModified() self.FileMenu.Enable(wx.ID_SAVE, project_modified) MenuToolBar.EnableTool(wx.ID_SAVE, project_modified) - self.FileMenu.Enable(wx.ID_PROPERTIES, True) else: self.FileMenu.Enable(wx.ID_CLOSE, False) self.FileMenu.Enable(wx.ID_PAGE_SETUP, False) @@ -1193,8 +1342,19 @@ MenuToolBar.EnableTool(wx.ID_PRINT, False) self.FileMenu.Enable(wx.ID_SAVE, False) MenuToolBar.EnableTool(wx.ID_SAVE, False) - self.FileMenu.Enable(wx.ID_PROPERTIES, False) - + + def RefreshScrollBars(self): + xstart, ystart = self.PLCConfig.GetViewStart() + window_size = self.PLCConfig.GetClientSize() + sizer = self.PLCConfig.GetSizer() + if sizer: + maxx, maxy = sizer.GetMinSize() + posx = max(0, min(xstart, (maxx - window_size[0]) / SCROLLBAR_UNIT)) + posy = max(0, min(ystart, (maxy - window_size[1]) / SCROLLBAR_UNIT)) + self.PLCConfig.Scroll(posx, posy) + self.PLCConfig.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT, + maxx / SCROLLBAR_UNIT, maxy / SCROLLBAR_UNIT, posx, posy) + def RefreshPLCParams(self): self.Freeze() self.ClearSizer(self.PLCParamsSizer) @@ -1234,6 +1394,69 @@ self.RefreshScrollBars() self.Thaw() + def GenerateMethodButtonSizer(self, confnode, parent, horizontal = True): + normal_bt_font=wx.Font(faces["size"] / 3, wx.DEFAULT, wx.NORMAL, wx.NORMAL, faceName = faces["helv"]) + mouseover_bt_font=wx.Font(faces["size"] / 3, wx.DEFAULT, wx.NORMAL, wx.NORMAL, underline=True, faceName = faces["helv"]) + if horizontal: + msizer = wx.FlexGridSizer(cols=len(confnode.ConfNodeMethods)) + else: + msizer = wx.FlexGridSizer(cols=1) + for confnode_method in confnode.ConfNodeMethods: + if "method" in confnode_method and confnode_method.get("shown",True): + id = wx.NewId() + label = confnode_method["name"] + button = GenBitmapTextButton(id=id, parent=parent, + bitmap=wx.Bitmap(Bpath("images", "%s.png"%confnode_method.get("bitmap", "Unknown"))), label=label, + name=label, pos=wx.DefaultPosition, style=wx.NO_BORDER) + button.SetFont(normal_bt_font) + button.SetToolTipString(confnode_method["tooltip"]) + button.Bind(wx.EVT_BUTTON, self.GetButtonCallBackFunction(confnode, confnode_method["method"]), id=id) + # a fancy underline on mouseover + def setFontStyle(b, s): + def fn(event): + b.SetFont(s) + b.Refresh() + event.Skip() + return fn + button.Bind(wx.EVT_ENTER_WINDOW, setFontStyle(button, mouseover_bt_font)) + button.Bind(wx.EVT_LEAVE_WINDOW, setFontStyle(button, normal_bt_font)) + #hack to force size to mini + if not confnode_method.get("enabled",True): + button.Disable() + msizer.AddWindow(button, 0, border=0, flag=wx.ALIGN_CENTER) + return msizer + + def GenerateEnableButton(self, parent, sizer, confnode): + enabled = confnode.CTNEnabled() + if enabled is not None: + enablebutton_id = wx.NewId() + enablebutton = wx.lib.buttons.GenBitmapToggleButton(id=enablebutton_id, bitmap=wx.Bitmap(Bpath( 'images', 'Disabled.png')), + name='EnableButton', parent=parent, size=wx.Size(16, 16), pos=wx.Point(0, 0), style=0)#wx.NO_BORDER) + enablebutton.SetToolTipString(_("Enable/Disable this confnode")) + make_genbitmaptogglebutton_flat(enablebutton) + enablebutton.SetBitmapSelected(wx.Bitmap(Bpath( 'images', 'Enabled.png'))) + enablebutton.SetToggle(enabled) + def toggleenablebutton(event): + res = self.SetConfNodeParamsAttribute(confnode, "BaseParams.Enabled", enablebutton.GetToggle()) + enablebutton.SetToggle(res) + event.Skip() + enablebutton.Bind(wx.EVT_BUTTON, toggleenablebutton, id=enablebutton_id) + sizer.AddWindow(enablebutton, 0, border=0, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL) + else: + sizer.AddSpacer(wx.Size(16, 16)) + + def RefreshConfNodeTree(self): + self.Freeze() + self.ClearSizer(self.ConfNodeTreeSizer) + if self.CTR is not None: + for child in self.CTR.IECSortedChildren(): + self.GenerateTreeBranch(child) + if not self.ConfNodeInfos[child]["expanded"]: + self.CollapseConfNode(child) + self.PLCConfigMainSizer.Layout() + self.RefreshScrollBars() + self.Thaw() + def GenerateTreeBranch(self, confnode): leftwindow = wx.Panel(self.PLCConfig, -1, size=wx.Size(-1, -1)) if confnode.CTNTestModified(): @@ -1327,7 +1550,10 @@ st.SetLabel(confnode.MandatoryParams[1].getName()) leftwindowsizer.AddWindow(st, 0, border=5, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL) - rightwindow = self.GenerateParamsPanel(confnode, bkgdclr) + rightwindow = wx.Panel(self.PLCConfig, -1, size=wx.Size(-1, -1)) + rightwindow.SetBackgroundColour(bkgdclr) + rightwindowsizer = self.GenerateMethodButtonSizer(confnode, rightwindow, not self.ConfNodeInfos[confnode]["right_visible"]) + rightwindow.SetSizer(rightwindowsizer) self.ConfNodeTreeSizer.AddWindow(rightwindow, 0, border=0, flag=wx.GROW) self.ConfNodeInfos[confnode]["left"] = leftwindow @@ -1361,6 +1587,175 @@ if locations_infos["root"]["expanded"]: self.ExpandLocation(locations_infos, "root") + def ExpandConfNode(self, confnode, force = False): + for child in self.ConfNodeInfos[confnode]["children"]: + self.ConfNodeInfos[child]["left"].Show() + self.ConfNodeInfos[child]["right"].Show() + if force or self.ConfNodeInfos[child]["expanded"]: + self.ExpandConfNode(child, force) + if force: + self.ConfNodeInfos[child]["expanded"] = True + locations_infos = self.ConfNodeInfos[confnode].get("locations_infos", None) + if locations_infos is not None: + if force or locations_infos["root"]["expanded"]: + self.ExpandLocation(locations_infos, "root", force) + if force: + locations_infos["root"]["expanded"] = True + + def CollapseConfNode(self, confnode, force = False): + for child in self.ConfNodeInfos[confnode]["children"]: + self.ConfNodeInfos[child]["left"].Hide() + self.ConfNodeInfos[child]["right"].Hide() + self.CollapseConfNode(child, force) + if force: + self.ConfNodeInfos[child]["expanded"] = False + locations_infos = self.ConfNodeInfos[confnode].get("locations_infos", None) + if locations_infos is not None: + self.CollapseLocation(locations_infos, "root", force) + if force: + locations_infos["root"]["expanded"] = False + + def ExpandLocation(self, locations_infos, group, force = False, refresh_size=True): + locations_infos[group]["expanded"] = True + if group == "root": + if locations_infos[group]["left"] is not None: + locations_infos[group]["left"].Show() + if locations_infos[group]["right"] is not None: + locations_infos[group]["right"].Show() + elif locations_infos["root"]["left"] is not None: + locations_infos["root"]["left"].Expand(locations_infos[group]["item"]) + if force: + for child in locations_infos[group]["children"]: + self.ExpandLocation(locations_infos, child, force, False) + if locations_infos["root"]["left"] is not None and refresh_size: + self.RefreshTreeCtrlSize(locations_infos["root"]["left"]) + + def CollapseLocation(self, locations_infos, group, force = False, refresh_size=True): + locations_infos[group]["expanded"] = False + if group == "root": + if locations_infos[group]["left"] is not None: + locations_infos[group]["left"].Hide() + if locations_infos[group]["right"] is not None: + locations_infos[group]["right"].Hide() + elif locations_infos["root"]["left"] is not None: + locations_infos["root"]["left"].Collapse(locations_infos[group]["item"]) + if force: + for child in locations_infos[group]["children"]: + self.CollapseLocation(locations_infos, child, force, False) + if locations_infos["root"]["left"] is not None and refresh_size: + self.RefreshTreeCtrlSize(locations_infos["root"]["left"]) + + def GenerateLocationTreeBranch(self, treectrl, root, locations_infos, parent, location): + location_name = "%s.%s" % (parent, location["name"]) + if not locations_infos.has_key(location_name): + locations_infos[location_name] = {"expanded" : False} + + if location["type"] in [LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY]: + label = "%(name)s (%(location)s)" % location + elif location["location"] != "": + label = "%(location)s: %(name)s" % location + else: + label = location["name"] + item = treectrl.AppendItem(root, label) + treectrl.SetPyData(item, location_name) + treectrl.SetItemImage(item, self.LocationImageDict[location["type"]]) + + locations_infos[location_name]["item"] = item + locations_infos[location_name]["children"] = [] + infos = location.copy() + infos.pop("children") + locations_infos[location_name]["infos"] = infos + for child in location["children"]: + child_name = "%s.%s" % (location_name, child["name"]) + locations_infos[location_name]["children"].append(child_name) + self.GenerateLocationTreeBranch(treectrl, item, locations_infos, location_name, child) + if locations_infos[location_name]["expanded"]: + self.ExpandLocation(locations_infos, location_name) + + def GenerateLocationBeginDragFunction(self, locations_infos): + def OnLocationBeginDragFunction(event): + item = event.GetItem() + location_name = locations_infos["root"]["left"].GetPyData(item) + if location_name is not None: + infos = locations_infos[location_name]["infos"] + if infos["type"] in [LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY]: + data = wx.TextDataObject(str((infos["location"], "location", infos["IEC_type"], infos["var_name"], infos["description"]))) + dragSource = wx.DropSource(self) + dragSource.SetData(data) + dragSource.DoDragDrop() + return OnLocationBeginDragFunction + + def RefreshTreeCtrlSize(self, treectrl): + rect = self.GetTreeCtrlItemRect(treectrl, treectrl.GetRootItem()) + treectrl.SetMinSize(wx.Size(max(rect.width, rect.x + rect.width) + 20, max(rect.height, rect.y + rect.height))) + self.PLCConfigMainSizer.Layout() + self.PLCConfig.Refresh() + wx.CallAfter(self.RefreshScrollBars) + + def GetTreeCtrlItemRect(self, treectrl, item): + item_rect = treectrl.GetBoundingRect(item, True) + if item_rect is not None: + minx, miny = item_rect.x, item_rect.y + maxx, maxy = item_rect.x + item_rect.width, item_rect.y + item_rect.height + else: + minx = miny = maxx = maxy = 0 + + if treectrl.ItemHasChildren(item) and (item == treectrl.GetRootItem() or treectrl.IsExpanded(item)): + if wx.VERSION >= (2, 6, 0): + child, item_cookie = treectrl.GetFirstChild(item) + else: + child, item_cookie = treectrl.GetFirstChild(item, 0) + while child.IsOk(): + child_rect = self.GetTreeCtrlItemRect(treectrl, child) + minx = min(minx, child_rect.x) + miny = min(miny, child_rect.y) + maxx = max(maxx, child_rect.x + child_rect.width) + maxy = max(maxy, child_rect.y + child_rect.height) + child, item_cookie = treectrl.GetNextChild(item, item_cookie) + + return wx.Rect(minx, miny, maxx - minx, maxy - miny) + + def GenerateLocationExpandCollapseFunction(self, locations_infos, expand): + def OnLocationExpandedFunction(event): + item = event.GetItem() + location_name = locations_infos["root"]["left"].GetPyData(item) + if location_name is not None: + locations_infos[location_name]["expanded"] = expand + self.RefreshTreeCtrlSize(locations_infos["root"]["left"]) + event.Skip() + return OnLocationExpandedFunction + + def GetButtonCallBackFunction(self, confnode, method): + """ Generate the callbackfunc for a given confnode method""" + def OnButtonClick(event): + # Disable button to prevent re-entrant call + event.GetEventObject().Disable() + # Call + getattr(confnode,method)() + # Re-enable button + event.GetEventObject().Enable() + # Trigger refresh on Idle + wx.CallAfter(self.RefreshAll) + event.Skip() + return OnButtonClick + + def ClearSizer(self, sizer): + staticboxes = [] + for item in sizer.GetChildren(): + if item.IsSizer(): + item_sizer = item.GetSizer() + self.ClearSizer(item_sizer) + if isinstance(item_sizer, wx.StaticBoxSizer): + staticboxes.append(item_sizer.GetStaticBox()) + sizer.Clear(True) + for staticbox in staticboxes: + staticbox.Destroy() + + def RefreshAll(self): + Beremiz.RefreshAll(self) + self.RefreshPLCParams() + self.RefreshConfNodeTree() + class StdoutPseudoFile: def __init__(self, port):