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.skvortzov@gmail.com>
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: 
andrej@1881: 
andrej@1881: from __future__ import absolute_import
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: 
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: #                                   Helpers
andrej@1782: # -------------------------------------------------------------------------------
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: 
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: #                            Browse Locations Dialog
andrej@1782: # -------------------------------------------------------------------------------
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
andrej@1847:         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)