lpcmanager

8e0514a914ab
Fixing bugs introduced with the new Beremiz extension paradigm
  • +446 -51
    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,13 +59,9 @@
    __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"))
    -from Beremiz import *
    -from ProjectController import ProjectController
    -from ConfigTreeNode import ConfigTreeNode
    +_base_path = os.path.split(__file__)[0]
    import connectors
    from LPCconnector import LPC_connector_factory
    @@ -73,14 +69,22 @@
    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"),
    +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")
    -from util import opjimg
    +# 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
    +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 = """<?xml version="1.0" encoding="ISO-8859-1" ?>
    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    @@ -418,6 +527,8 @@
    </xsd:schema>
    """ % 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 = """<?xml version="1.0" encoding="ISO-8859-1" ?>
    @@ -450,7 +564,9 @@
    </xsd:element>
    </xsd:schema>
    """ % 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.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._init_beremiz_sizers()
    + 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):