Laurent@814: #!/usr/bin/env python Laurent@814: # -*- coding: utf-8 -*- Laurent@814: Laurent@814: #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor Laurent@814: #based on the plcopen standard. Laurent@814: # Laurent@814: #Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD Laurent@814: # Laurent@814: #See COPYING file for copyrights details. Laurent@814: # Laurent@814: #This library is free software; you can redistribute it and/or Laurent@814: #modify it under the terms of the GNU General Public Laurent@814: #License as published by the Free Software Foundation; either Laurent@814: #version 2.1 of the License, or (at your option) any later version. Laurent@814: # Laurent@814: #This library is distributed in the hope that it will be useful, Laurent@814: #but WITHOUT ANY WARRANTY; without even the implied warranty of Laurent@814: #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Laurent@814: #General Public License for more details. Laurent@814: # Laurent@814: #You should have received a copy of the GNU General Public Laurent@814: #License along with this library; if not, write to the Free Software Laurent@814: #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Laurent@814: Laurent@1343: from collections import namedtuple Laurent@1343: Laurent@814: import wx Edouard@1281: import wx.lib.agw.customtreectrl as CT Laurent@814: import wx.lib.buttons Laurent@814: Laurent@1343: # Customize CustomTreeItem for adding icon on item right Laurent@1343: CT.GenericTreeItem._rightimages = [] Laurent@1343: Laurent@1343: def SetRightImages(self, images): Laurent@1343: self._rightimages = images Laurent@1343: CT.GenericTreeItem.SetRightImages = SetRightImages Laurent@1343: Laurent@1343: def GetRightImages(self): Laurent@1343: return self._rightimages Laurent@1343: CT.GenericTreeItem.GetRightImages = GetRightImages Laurent@1343: Laurent@1343: Laurent@1343: class CustomTreeCtrlWithRightImage(CT.CustomTreeCtrl): Laurent@1343: Laurent@1343: def SetRightImageList(self, imageList): Laurent@1343: self._imageListRight = imageList Laurent@1343: Laurent@1343: def GetLineHeight(self, item): Laurent@1343: height = CT.CustomTreeCtrl.GetLineHeight(self, item) Laurent@1343: rightimages = item.GetRightImages() Laurent@1343: if len(rightimages) > 0: Laurent@1343: r_image_w, r_image_h = self._imageListRight.GetSize(rightimages[0]) Laurent@1343: return max(height, r_image_h + 8) Laurent@1343: return height Laurent@1343: Laurent@1343: def GetItemRightImagesBBox(self, item): Laurent@1343: rightimages = item.GetRightImages() Laurent@1343: if len(rightimages) > 0: Laurent@1343: w, h = self.GetClientSize() Laurent@1343: total_h = self.GetLineHeight(item) Laurent@1343: r_image_w, r_image_h = self._imageListRight.GetSize(rightimages[0]) Laurent@1343: Laurent@1343: bbox_width = (r_image_w + 4) * len(rightimages) + 4 Laurent@1343: bbox_height = r_image_h + 8 Laurent@1343: bbox_x = w - bbox_width Laurent@1343: bbox_y = item.GetY() + ((total_h > r_image_h) and [(total_h-r_image_h)/2] or [0])[0] Laurent@1343: Laurent@1343: return wx.Rect(bbox_x, bbox_y, bbox_width, bbox_height) Laurent@1343: Laurent@1343: return None Laurent@1343: Laurent@1343: def IsOverItemRightImage(self, item, point): Laurent@1343: rightimages = item.GetRightImages() Laurent@1343: if len(rightimages) > 0: Laurent@1343: point = self.CalcUnscrolledPosition(point) Laurent@1343: r_image_w, r_image_h = self._imageListRight.GetSize(rightimages[0]) Laurent@1343: images_bbx = self.GetItemRightImagesBBox(item) Laurent@1343: Laurent@1343: rect = wx.Rect(images_bbx.x + 4, images_bbx.y + 4, Laurent@1343: r_image_w, r_image_h) Laurent@1343: for r_image in rightimages: Laurent@1343: if rect.Inside(point): Laurent@1343: return r_image Laurent@1343: rect.x += r_image_w + 4 Laurent@1343: Laurent@1343: return None Laurent@1343: Laurent@1343: def PaintItem(self, item, dc, level, align): Laurent@1343: CT.CustomTreeCtrl.PaintItem(self, item, dc, level, align) Laurent@1343: Laurent@1343: rightimages = item.GetRightImages() Laurent@1343: if len(rightimages) > 0: Laurent@1343: images_bbx = self.GetItemRightImagesBBox(item) Laurent@1343: r_image_w, r_image_h = self._imageListRight.GetSize(rightimages[0]) Laurent@1343: Laurent@1343: dc.SetBrush(wx.WHITE_BRUSH) Laurent@1343: dc.SetPen(wx.TRANSPARENT_PEN) Laurent@1343: Laurent@1343: bg_width = (r_image_w + 4) * len(rightimages) + 4 Laurent@1343: bg_height = r_image_h + 8 Laurent@1343: dc.DrawRectangle(images_bbx.x, images_bbx.y, Laurent@1343: images_bbx.width, images_bbx.height) Laurent@1343: x_pos = images_bbx.x + 4 Laurent@1343: for r_image in rightimages: Laurent@1343: self._imageListRight.Draw( Laurent@1343: r_image, dc, x_pos, images_bbx.y + 4, Laurent@1343: wx.IMAGELIST_DRAW_TRANSPARENT) Laurent@1343: x_pos += r_image_w + 4 Laurent@1343: Laurent@1343: _ButtonCallbacks = namedtuple("ButtonCallbacks", ["leftdown", "dclick"]) Laurent@1343: laurent@826: from PLCControler import ITEMS_VARIABLE, ITEM_CONFIGURATION, ITEM_RESOURCE, ITEM_POU, ITEM_TRANSITION, ITEM_ACTION Laurent@814: from util.BitmapLibrary import GetBitmap Laurent@814: Laurent@814: class PouInstanceVariablesPanel(wx.Panel): Edouard@1362: Laurent@814: def __init__(self, parent, window, controller, debug): Laurent@814: wx.Panel.__init__(self, name='PouInstanceTreePanel', Laurent@814: parent=parent, pos=wx.Point(0, 0), Laurent@814: size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL) Laurent@814: Laurent@814: self.ParentButton = wx.lib.buttons.GenBitmapButton(self, Laurent@814: bitmap=GetBitmap("top"), size=wx.Size(28, 28), style=wx.NO_BORDER) Laurent@814: self.ParentButton.SetToolTipString(_("Parent instance")) Laurent@814: self.Bind(wx.EVT_BUTTON, self.OnParentButtonClick, Laurent@814: self.ParentButton) Laurent@814: laurent@821: self.InstanceChoice = wx.ComboBox(self, size=wx.Size(0, 0), style=wx.CB_READONLY) Laurent@814: self.Bind(wx.EVT_COMBOBOX, self.OnInstanceChoiceChanged, Laurent@814: self.InstanceChoice) Laurent@814: Laurent@814: self.DebugButton = wx.lib.buttons.GenBitmapButton(self, Laurent@814: bitmap=GetBitmap("debug_instance"), size=wx.Size(28, 28), style=wx.NO_BORDER) Laurent@1082: self.DebugButton.SetToolTipString(_("Debug instance")) Laurent@814: self.Bind(wx.EVT_BUTTON, self.OnDebugButtonClick, Laurent@814: self.DebugButton) Laurent@814: Laurent@1343: self.VariablesList = CustomTreeCtrlWithRightImage(self, Laurent@814: style=wx.SUNKEN_BORDER, Laurent@814: agwStyle=CT.TR_NO_BUTTONS| Laurent@814: CT.TR_SINGLE| Laurent@814: CT.TR_HAS_VARIABLE_ROW_HEIGHT| Laurent@814: CT.TR_HIDE_ROOT| Laurent@814: CT.TR_NO_LINES| Laurent@814: getattr(CT, "TR_ALIGN_WINDOWS_RIGHT", CT.TR_ALIGN_WINDOWS)) Laurent@814: self.VariablesList.SetIndent(0) Laurent@814: self.VariablesList.SetSpacing(5) Laurent@814: self.VariablesList.DoSelectItem = lambda *x,**y:True Laurent@814: self.VariablesList.Bind(CT.EVT_TREE_ITEM_ACTIVATED, Laurent@814: self.OnVariablesListItemActivated) Laurent@814: self.VariablesList.Bind(wx.EVT_LEFT_DOWN, self.OnVariablesListLeftDown) Laurent@1031: self.VariablesList.Bind(wx.EVT_KEY_DOWN, self.OnVariablesListKeyDown) Laurent@814: Laurent@1343: self.TreeRightImageList = wx.ImageList(24, 24) Laurent@1343: self.EditImage = self.TreeRightImageList.Add(GetBitmap("edit")) Laurent@1343: self.DebugInstanceImage = self.TreeRightImageList.Add(GetBitmap("debug_instance")) Laurent@1343: self.VariablesList.SetRightImageList(self.TreeRightImageList) Laurent@1343: Laurent@1343: self.ButtonCallBacks = { Laurent@1343: self.EditImage: _ButtonCallbacks( Laurent@1343: self.EditButtonCallback, None), Laurent@1343: self.DebugInstanceImage: _ButtonCallbacks( Laurent@1343: self.DebugButtonCallback, self.DebugButtonDClickCallback)} Laurent@1343: Laurent@814: buttons_sizer = wx.FlexGridSizer(cols=3, hgap=0, rows=1, vgap=0) Laurent@814: buttons_sizer.AddWindow(self.ParentButton) Laurent@814: buttons_sizer.AddWindow(self.InstanceChoice, flag=wx.GROW) Laurent@814: buttons_sizer.AddWindow(self.DebugButton) Laurent@814: buttons_sizer.AddGrowableCol(1) Laurent@814: buttons_sizer.AddGrowableRow(0) Laurent@814: Laurent@814: main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0) Laurent@814: main_sizer.AddSizer(buttons_sizer, flag=wx.GROW) Laurent@814: main_sizer.AddWindow(self.VariablesList, flag=wx.GROW) Laurent@814: main_sizer.AddGrowableCol(0) Laurent@814: main_sizer.AddGrowableRow(1) Laurent@814: Laurent@814: self.SetSizer(main_sizer) Laurent@814: Laurent@814: self.ParentWindow = window Laurent@814: self.Controller = controller Laurent@814: self.Debug = debug Laurent@814: if not self.Debug: Laurent@814: self.DebugButton.Hide() Laurent@814: Laurent@814: self.PouTagName = None Laurent@814: self.PouInfos = None Laurent@814: self.PouInstance = None Laurent@814: Laurent@814: def __del__(self): Laurent@814: self.Controller = None Laurent@814: Laurent@814: def SetTreeImageList(self, tree_image_list): Laurent@814: self.VariablesList.SetImageList(tree_image_list) Laurent@814: Laurent@814: def SetController(self, controller): Laurent@814: self.Controller = controller Laurent@814: Laurent@814: self.RefreshView() Laurent@814: Laurent@814: def SetPouType(self, tagname, pou_instance=None): Laurent@1222: if self.Controller is not None: Laurent@1233: if tagname == "Project": Laurent@915: config_name = self.Controller.GetProjectMainConfigurationName() Laurent@915: if config_name is not None: Laurent@1233: tagname = self.Controller.ComputeConfigurationName(config_name) Laurent@915: if pou_instance is not None: Laurent@915: self.PouInstance = pou_instance Laurent@1233: Laurent@1233: if self.PouTagName != tagname: Laurent@1233: self.PouTagName = tagname Laurent@1233: self.RefreshView() Laurent@1233: else: Laurent@1233: self.RefreshInstanceChoice() Laurent@1233: else: Laurent@1233: self.RefreshView() Laurent@814: Laurent@814: def ResetView(self): Laurent@814: self.Controller = None Laurent@814: Laurent@814: self.PouTagName = None Laurent@814: self.PouInfos = None Laurent@814: self.PouInstance = None Laurent@814: Laurent@814: self.RefreshView() Laurent@814: Laurent@814: def RefreshView(self): Laurent@1222: self.Freeze() Laurent@814: self.VariablesList.DeleteAllItems() Laurent@814: Laurent@814: if self.Controller is not None and self.PouTagName is not None: Laurent@814: self.PouInfos = self.Controller.GetPouVariables(self.PouTagName, self.Debug) Laurent@814: else: Laurent@814: self.PouInfos = None Laurent@814: if self.PouInfos is not None: Laurent@814: root = self.VariablesList.AddRoot("") Laurent@1348: for var_infos in self.PouInfos.variables: Laurent@1348: if var_infos.type is not None: Laurent@1348: text = "%s (%s)" % (var_infos.name, var_infos.type) Laurent@814: else: Laurent@1348: text = var_infos.name Laurent@814: Laurent@1343: right_images = [] Laurent@1364: if var_infos.edit: Laurent@1343: right_images.append(self.EditImage) Laurent@814: Laurent@1348: if var_infos.debug and self.Debug: Laurent@1343: right_images.append(self.DebugInstanceImage) Laurent@1343: Laurent@1343: item = self.VariablesList.AppendItem(root, text) Laurent@1343: item.SetRightImages(right_images) Laurent@1348: self.VariablesList.SetItemImage(item, self.ParentWindow.GetTreeImage(var_infos.var_class)) Laurent@814: self.VariablesList.SetPyData(item, var_infos) Laurent@814: Laurent@1238: self.RefreshInstanceChoice() Laurent@1233: self.RefreshButtons() Laurent@1233: Laurent@1233: self.Thaw() Laurent@1233: Laurent@1233: def RefreshInstanceChoice(self): Laurent@1238: self.InstanceChoice.Clear() Laurent@1238: self.InstanceChoice.SetValue("") Laurent@1233: if self.Controller is not None and self.PouInfos is not None: Laurent@814: instances = self.Controller.SearchPouInstances(self.PouTagName, self.Debug) Laurent@814: for instance in instances: Laurent@814: self.InstanceChoice.Append(instance) Laurent@814: if len(instances) == 1: Laurent@814: self.PouInstance = instances[0] Laurent@1348: if self.PouInfos.var_class in [ITEM_CONFIGURATION, ITEM_RESOURCE]: Laurent@814: self.PouInstance = None Laurent@814: self.InstanceChoice.SetSelection(0) Laurent@814: elif self.PouInstance in instances: Laurent@814: self.InstanceChoice.SetStringSelection(self.PouInstance) Laurent@814: else: Laurent@814: self.PouInstance = None Laurent@814: self.InstanceChoice.SetValue(_("Select an instance")) Laurent@1233: Laurent@814: def RefreshButtons(self): Laurent@814: enabled = self.InstanceChoice.GetSelection() != -1 Laurent@1348: self.ParentButton.Enable(enabled and self.PouInfos.var_class != ITEM_CONFIGURATION) Laurent@1348: self.DebugButton.Enable(enabled and self.PouInfos.debug and self.Debug) Laurent@814: Laurent@814: root = self.VariablesList.GetRootItem() Laurent@814: if root is not None and root.IsOk(): Laurent@814: item, item_cookie = self.VariablesList.GetFirstChild(root) Laurent@814: while item is not None and item.IsOk(): Laurent@814: panel = self.VariablesList.GetItemWindow(item) Laurent@814: if panel is not None: Laurent@814: for child in panel.GetChildren(): Laurent@814: if child.GetName() != "edit": Laurent@814: child.Enable(enabled) Laurent@814: item, item_cookie = self.VariablesList.GetNextChild(root, item_cookie) Laurent@814: Laurent@1343: def EditButtonCallback(self, infos): Laurent@1348: var_class = infos.var_class Laurent@1343: if var_class == ITEM_RESOURCE: Laurent@1343: tagname = self.Controller.ComputeConfigurationResourceName( Laurent@1343: self.InstanceChoice.GetStringSelection(), Laurent@1348: infos.name) Laurent@1343: elif var_class == ITEM_TRANSITION: Laurent@1343: tagname = self.Controller.ComputePouTransitionName( Laurent@1343: self.PouTagName.split("::")[1], Laurent@1348: infos.name) Laurent@1343: elif var_class == ITEM_ACTION: Laurent@1343: tagname = self.Controller.ComputePouActionName( Laurent@1343: self.PouTagName.split("::")[1], Laurent@1348: infos.name) Laurent@1343: else: Laurent@1343: var_class = ITEM_POU Laurent@1348: tagname = self.Controller.ComputePouName(infos.type) Laurent@1343: self.ParentWindow.EditProjectElement(var_class, tagname) Laurent@1343: Laurent@1343: def DebugButtonCallback(self, infos): Laurent@1343: if self.InstanceChoice.GetSelection() != -1: Laurent@1348: var_class = infos.var_class Laurent@1343: var_path = "%s.%s" % (self.InstanceChoice.GetStringSelection(), Laurent@1348: infos.name) Laurent@1343: if var_class in ITEMS_VARIABLE: Laurent@1343: self.ParentWindow.AddDebugVariable(var_path, force=True) laurent@826: elif var_class == ITEM_TRANSITION: Laurent@1343: self.ParentWindow.OpenDebugViewer( Laurent@1343: var_class, Laurent@1343: var_path, Laurent@1343: self.Controller.ComputePouTransitionName( Laurent@1343: self.PouTagName.split("::")[1], Laurent@1348: infos.name)) laurent@826: elif var_class == ITEM_ACTION: Laurent@1343: self.ParentWindow.OpenDebugViewer( Laurent@1343: var_class, Laurent@1343: var_path, Laurent@1343: self.Controller.ComputePouActionName( Laurent@1343: self.PouTagName.split("::")[1], Laurent@1348: infos.name)) Laurent@814: else: Laurent@1343: self.ParentWindow.OpenDebugViewer( Laurent@1343: var_class, Laurent@1343: var_path, Laurent@1348: self.Controller.ComputePouName(infos.type)) Laurent@1343: Laurent@1343: def DebugButtonDClickCallback(self, infos): Laurent@1343: if self.InstanceChoice.GetSelection() != -1: Laurent@1348: if infos.var_class in ITEMS_VARIABLE: Laurent@1343: self.ParentWindow.AddDebugVariable( Laurent@1343: "%s.%s" % (self.InstanceChoice.GetStringSelection(), Laurent@1348: infos.name), Laurent@1343: force=True, Laurent@1343: graph=True) Laurent@1343: Laurent@814: def ShowInstanceChoicePopup(self): Laurent@814: self.InstanceChoice.SetFocusFromKbd() Laurent@814: size = self.InstanceChoice.GetSize() Laurent@814: event = wx.MouseEvent(wx.EVT_LEFT_DOWN._getEvtType()) Laurent@814: event.m_x = size.width / 2 Laurent@814: event.m_y = size.height / 2 Laurent@814: event.SetEventObject(self.InstanceChoice) Laurent@814: #event = wx.KeyEvent(wx.EVT_KEY_DOWN._getEvtType()) Laurent@814: #event.m_keyCode = wx.WXK_SPACE Laurent@814: self.InstanceChoice.GetEventHandler().ProcessEvent(event) Laurent@814: Laurent@814: def OnParentButtonClick(self, event): Laurent@814: if self.InstanceChoice.GetSelection() != -1: Laurent@814: parent_path = self.InstanceChoice.GetStringSelection().rsplit(".", 1)[0] Laurent@814: tagname = self.Controller.GetPouInstanceTagName(parent_path, self.Debug) Laurent@814: if tagname is not None: Laurent@814: wx.CallAfter(self.SetPouType, tagname, parent_path) Laurent@814: wx.CallAfter(self.ParentWindow.SelectProjectTreeItem, tagname) Laurent@814: event.Skip() Laurent@814: Laurent@814: def OnInstanceChoiceChanged(self, event): Laurent@814: self.RefreshButtons() Laurent@814: event.Skip() Laurent@814: Laurent@814: def OnDebugButtonClick(self, event): Laurent@814: if self.InstanceChoice.GetSelection() != -1: Laurent@814: self.ParentWindow.OpenDebugViewer( Laurent@1348: self.PouInfos.var_class, Laurent@814: self.InstanceChoice.GetStringSelection(), Laurent@814: self.PouTagName) Laurent@814: event.Skip() Laurent@1343: Laurent@814: def OnVariablesListItemActivated(self, event): Laurent@1189: selected_item = event.GetItem() Laurent@1189: if selected_item is not None and selected_item.IsOk(): Laurent@1189: item_infos = self.VariablesList.GetPyData(selected_item) Laurent@1343: if item_infos is not None: Laurent@1343: Laurent@1343: item_button = self.VariablesList.IsOverItemRightImage( Laurent@1343: selected_item, event.GetPoint()) Laurent@1343: if item_button is not None: Laurent@1343: callback = self.ButtonCallBacks[item_button].dclick Laurent@1343: if callback is not None: Laurent@1343: callback(item_infos) Laurent@1343: Laurent@1348: elif item_infos.var_class not in ITEMS_VARIABLE: Laurent@1343: instance_path = self.InstanceChoice.GetStringSelection() Laurent@1348: if item_infos.var_class == ITEM_RESOURCE: Laurent@1343: if instance_path != "": Laurent@1343: tagname = self.Controller.ComputeConfigurationResourceName( Laurent@1343: instance_path, Laurent@1348: item_infos.name) Laurent@1343: else: Laurent@1343: tagname = None Laurent@814: else: Laurent@1348: tagname = self.Controller.ComputePouName(item_infos.type) Laurent@1343: if tagname is not None: Laurent@1343: if instance_path != "": Laurent@1348: item_path = "%s.%s" % (instance_path, item_infos.name) Laurent@1343: else: Laurent@1343: item_path = None Laurent@1343: self.SetPouType(tagname, item_path) Laurent@1343: self.ParentWindow.SelectProjectTreeItem(tagname) Laurent@814: event.Skip() Laurent@814: Laurent@814: def OnVariablesListLeftDown(self, event): Laurent@814: if self.InstanceChoice.GetSelection() == -1: Laurent@814: wx.CallAfter(self.ShowInstanceChoicePopup) Laurent@898: else: Laurent@898: instance_path = self.InstanceChoice.GetStringSelection() Laurent@898: item, flags = self.VariablesList.HitTest(event.GetPosition()) Laurent@1343: if item is not None: Laurent@898: item_infos = self.VariablesList.GetPyData(item) Laurent@1343: if item_infos is not None: Laurent@1343: Laurent@1343: item_button = self.VariablesList.IsOverItemRightImage( Laurent@1343: item, event.GetPosition()) Laurent@1343: if item_button is not None: Laurent@1343: callback = self.ButtonCallBacks[item_button].leftdown Laurent@1343: if callback is not None: Laurent@1343: callback(item_infos) Laurent@1343: Laurent@1369: elif (flags & CT.TREE_HITTEST_ONITEMLABEL and Laurent@1369: item_infos.var_class in ITEMS_VARIABLE): Laurent@1369: self.ParentWindow.EnsureTabVisible( Laurent@1369: self.ParentWindow.DebugVariablePanel) Laurent@1369: item_path = "%s.%s" % (instance_path, item_infos.name) Laurent@1369: data = wx.TextDataObject(str((item_path, "debug"))) Laurent@1369: dragSource = wx.DropSource(self.VariablesList) Laurent@1369: dragSource.SetData(data) Laurent@1369: dragSource.DoDragDrop() Laurent@814: event.Skip() Laurent@1031: Laurent@1031: def OnVariablesListKeyDown(self, event): Laurent@1031: keycode = event.GetKeyCode() Laurent@1031: if keycode != wx.WXK_LEFT: Laurent@1031: event.Skip() Laurent@1277: