andrej@1571: #!/usr/bin/env python andrej@1571: # -*- coding: utf-8 -*- andrej@1571: 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@1696: # Copyright (C) 2017: Andrey Skvortsov 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@814: Laurent@814: import os Laurent@814: Laurent@814: import wx Laurent@814: Laurent@814: from plcopen.structures import LOCATIONDATATYPES Laurent@814: from PLCControler import LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY laurent@829: from util.BitmapLibrary import GetBitmap andrej@1762: from util.TranslationCatalogs import NoTranslate Laurent@814: Laurent@814: #------------------------------------------------------------------------------- Laurent@814: # Helpers Laurent@814: #------------------------------------------------------------------------------- Laurent@814: andrej@1736: Laurent@855: def GetDirFilterChoiceOptions(): andrej@1762: _ = NoTranslate andrej@1730: return [(_("All"), [LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY]), andrej@1730: (_("Input"), [LOCATION_VAR_INPUT]), andrej@1730: (_("Output"), [LOCATION_VAR_OUTPUT]), Laurent@814: (_("Memory"), [LOCATION_VAR_MEMORY])] andrej@1749: andrej@1749: Laurent@855: DIRFILTERCHOICE_OPTIONS = dict([(_(option), filter) for option, filter in GetDirFilterChoiceOptions()]) Laurent@855: andrej@1736: Laurent@855: def GetTypeFilterChoiceOptions(): andrej@1762: _ = NoTranslate andrej@1730: return [_("All"), andrej@1730: _("Type and derivated"), Laurent@855: _("Type strict")] Laurent@814: andrej@1749: Laurent@814: # turn LOCATIONDATATYPES inside-out Laurent@814: LOCATION_SIZES = {} Laurent@814: for size, types in LOCATIONDATATYPES.iteritems(): Laurent@814: for type in types: Laurent@814: LOCATION_SIZES[type] = size Laurent@814: Laurent@814: #------------------------------------------------------------------------------- Laurent@814: # Browse Locations Dialog Laurent@814: #------------------------------------------------------------------------------- Laurent@814: andrej@1736: Laurent@814: class BrowseLocationsDialog(wx.Dialog): andrej@1730: Laurent@855: def __init__(self, parent, var_type, controller): andrej@1696: wx.Dialog.__init__(self, parent, title=_('Browse Locations'), andrej@1768: style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) andrej@1730: Laurent@814: main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=10) Laurent@814: main_sizer.AddGrowableCol(0) Laurent@814: main_sizer.AddGrowableRow(1) andrej@1730: Laurent@814: locations_label = wx.StaticText(self, label=_('Locations available:')) andrej@1730: main_sizer.AddWindow(locations_label, border=20, andrej@1768: flag=wx.TOP | wx.LEFT | wx.RIGHT | wx.GROW) andrej@1730: andrej@1730: self.LocationsTree = wx.TreeCtrl(self, andrej@1768: style=(wx.TR_HAS_BUTTONS | andrej@1768: wx.TR_SINGLE | andrej@1768: wx.SUNKEN_BORDER | andrej@1768: wx.TR_HIDE_ROOT | andrej@1768: wx.TR_LINES_AT_ROOT)) andrej@1696: self.LocationsTree.SetInitialSize(wx.Size(-1, 300)) andrej@1730: self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnLocationsTreeItemActivated, Laurent@814: self.LocationsTree) andrej@1730: main_sizer.AddWindow(self.LocationsTree, border=20, andrej@1768: flag=wx.LEFT | wx.RIGHT | wx.GROW) andrej@1730: Laurent@855: button_gridsizer = wx.FlexGridSizer(cols=5, hgap=5, rows=1, vgap=0) Laurent@855: button_gridsizer.AddGrowableCol(1) Laurent@855: button_gridsizer.AddGrowableCol(3) Laurent@814: button_gridsizer.AddGrowableRow(0) andrej@1730: main_sizer.AddSizer(button_gridsizer, border=20, andrej@1768: flag=wx.BOTTOM | wx.LEFT | wx.RIGHT | wx.GROW) andrej@1730: Laurent@814: direction_label = wx.StaticText(self, label=_('Direction:')) Laurent@814: button_gridsizer.AddWindow(direction_label, andrej@1768: flag=wx.ALIGN_CENTER_VERTICAL) andrej@1730: andrej@1696: self.DirFilterChoice = wx.ComboBox(self, style=wx.CB_READONLY) Laurent@855: self.Bind(wx.EVT_COMBOBOX, self.OnFilterChoice, self.DirFilterChoice) Laurent@855: button_gridsizer.AddWindow(self.DirFilterChoice, andrej@1768: flag=wx.GROW | wx.ALIGN_CENTER_VERTICAL) andrej@1730: Laurent@855: filter_label = wx.StaticText(self, label=_('Type:')) Laurent@855: button_gridsizer.AddWindow(filter_label, andrej@1768: flag=wx.ALIGN_CENTER_VERTICAL) andrej@1730: andrej@1696: self.TypeFilterChoice = wx.ComboBox(self, style=wx.CB_READONLY) Laurent@855: self.Bind(wx.EVT_COMBOBOX, self.OnFilterChoice, self.TypeFilterChoice) Laurent@855: button_gridsizer.AddWindow(self.TypeFilterChoice, andrej@1768: flag=wx.GROW | wx.ALIGN_CENTER_VERTICAL) andrej@1745: andrej@1745: button_sizer = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.CENTRE) Laurent@814: self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton()) Laurent@855: button_gridsizer.AddSizer(button_sizer, flag=wx.ALIGN_RIGHT) andrej@1730: Laurent@814: self.SetSizer(main_sizer) andrej@1730: Laurent@855: self.Controller = controller Laurent@814: self.VarType = var_type Laurent@855: self.BaseVarType = self.Controller.GetBaseType(self.VarType) Laurent@855: self.VarTypeSize = LOCATION_SIZES[self.BaseVarType] Laurent@855: self.Locations = self.Controller.GetVariableLocationTree() andrej@1730: Laurent@814: # Define Tree item icon list Laurent@814: self.TreeImageList = wx.ImageList(16, 16) Laurent@814: self.TreeImageDict = {} andrej@1730: Laurent@814: # Icons for items Laurent@814: for imgname, itemtype in [ andrej@1766: ("CONFIGURATION", LOCATION_CONFNODE), andrej@1766: ("RESOURCE", LOCATION_MODULE), andrej@1766: ("PROGRAM", LOCATION_GROUP), andrej@1766: ("VAR_INPUT", LOCATION_VAR_INPUT), andrej@1766: ("VAR_OUTPUT", LOCATION_VAR_OUTPUT), andrej@1766: ("VAR_LOCAL", LOCATION_VAR_MEMORY)]: andrej@1742: self.TreeImageDict[itemtype] = self.TreeImageList.Add(GetBitmap(imgname)) andrej@1730: Laurent@814: # Assign icon list to TreeCtrls Laurent@814: self.LocationsTree.SetImageList(self.TreeImageList) andrej@1730: Laurent@814: # Set a options for the choice Laurent@855: for option, filter in GetDirFilterChoiceOptions(): Laurent@855: self.DirFilterChoice.Append(_(option)) Laurent@855: self.DirFilterChoice.SetStringSelection(_("All")) Laurent@855: for option in GetTypeFilterChoiceOptions(): Laurent@855: self.TypeFilterChoice.Append(_(option)) Laurent@855: self.TypeFilterChoice.SetStringSelection(_("All")) Laurent@855: self.RefreshFilters() andrej@1730: Laurent@814: self.RefreshLocationsTree() andrej@1696: self.Fit() andrej@1730: Laurent@855: def RefreshFilters(self): Laurent@855: self.DirFilter = DIRFILTERCHOICE_OPTIONS[self.DirFilterChoice.GetStringSelection()] Laurent@855: self.TypeFilter = self.TypeFilterChoice.GetSelection() andrej@1730: Laurent@814: def RefreshLocationsTree(self): Laurent@814: root = self.LocationsTree.GetRootItem() Laurent@814: if not root.IsOk(): Laurent@814: root = self.LocationsTree.AddRoot("") Laurent@814: self.GenerateLocationsTreeBranch(root, self.Locations) andrej@1730: Laurent@855: def FilterType(self, location_type, location_size): Laurent@855: if self.TypeFilter == 0: Laurent@855: return True andrej@1730: Laurent@855: if location_size != self.VarTypeSize: Laurent@855: return False andrej@1730: Laurent@855: if self.TypeFilter == 1: Laurent@855: return self.Controller.IsOfType(location_type, self.BaseVarType) Laurent@855: elif self.TypeFilter == 2: Laurent@855: return location_type == self.VarType andrej@1730: Laurent@855: return True andrej@1730: Laurent@814: def GenerateLocationsTreeBranch(self, root, locations): Laurent@814: to_delete = [] Laurent@814: item, root_cookie = self.LocationsTree.GetFirstChild(root) Laurent@814: for loc_infos in locations: Laurent@814: infos = loc_infos.copy() Laurent@814: if infos["type"] in [LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP] or\ Laurent@855: infos["type"] in self.DirFilter and self.FilterType(infos["IEC_type"], infos["size"]): Laurent@814: children = [child for child in infos.pop("children")] Laurent@814: if not item.IsOk(): Laurent@814: item = self.LocationsTree.AppendItem(root, infos["name"]) Laurent@814: if wx.Platform != '__WXMSW__': Laurent@814: item, root_cookie = self.LocationsTree.GetNextChild(root, root_cookie) Laurent@814: else: Laurent@814: self.LocationsTree.SetItemText(item, infos["name"]) Laurent@814: self.LocationsTree.SetPyData(item, infos) Laurent@814: self.LocationsTree.SetItemImage(item, self.TreeImageDict[infos["type"]]) Laurent@814: self.GenerateLocationsTreeBranch(item, children) Laurent@814: item, root_cookie = self.LocationsTree.GetNextChild(root, root_cookie) Laurent@814: while item.IsOk(): Laurent@814: to_delete.append(item) Laurent@814: item, root_cookie = self.LocationsTree.GetNextChild(root, root_cookie) Laurent@814: for item in to_delete: Laurent@814: self.LocationsTree.Delete(item) andrej@1730: Laurent@814: def OnLocationsTreeItemActivated(self, event): Laurent@814: infos = self.LocationsTree.GetPyData(event.GetItem()) Laurent@814: if infos["type"] not in [LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP]: Laurent@814: wx.CallAfter(self.EndModal, wx.ID_OK) Laurent@814: event.Skip() andrej@1730: Laurent@855: def OnFilterChoice(self, event): Laurent@855: self.RefreshFilters() Laurent@814: self.RefreshLocationsTree() andrej@1730: Laurent@814: def GetValues(self): Laurent@814: selected = self.LocationsTree.GetSelection() Laurent@814: return self.LocationsTree.GetPyData(selected) andrej@1730: Laurent@814: def OnOK(self, event): Laurent@814: selected = self.LocationsTree.GetSelection() Laurent@814: var_infos = None Laurent@814: if selected.IsOk(): Laurent@814: var_infos = self.LocationsTree.GetPyData(selected) Laurent@814: if var_infos is None or var_infos["type"] in [LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP]: andrej@1745: dialog = wx.MessageDialog(self, _("A location must be selected!"), _("Error"), wx.OK | wx.ICON_ERROR) Laurent@814: dialog.ShowModal() Laurent@814: dialog.Destroy() Laurent@814: else: Laurent@814: self.EndModal(wx.ID_OK)