laurent@566: #!/usr/bin/env python laurent@566: # -*- coding: utf-8 -*- laurent@566: laurent@566: #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor laurent@566: #based on the plcopen standard. laurent@566: # laurent@566: #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD laurent@566: # laurent@566: #See COPYING file for copyrights details. laurent@566: # laurent@566: #This library is free software; you can redistribute it and/or laurent@566: #modify it under the terms of the GNU General Public laurent@566: #License as published by the Free Software Foundation; either laurent@566: #version 2.1 of the License, or (at your option) any later version. laurent@566: # laurent@566: #This library is distributed in the hope that it will be useful, laurent@566: #but WITHOUT ANY WARRANTY; without even the implied warranty of laurent@566: #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU laurent@566: #General Public License for more details. laurent@566: # laurent@566: #You should have received a copy of the GNU General Public laurent@566: #License along with this library; if not, write to the Free Software laurent@566: #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA laurent@566: laurent@566: from types import TupleType laurent@566: laurent@566: import wx Laurent@714: import wx.lib.buttons laurent@566: import wx.lib.agw.customtreectrl as CT laurent@566: laurent@566: from PLCControler import * Laurent@714: from utils.BitmapLibrary import GetBitmap laurent@566: laurent@566: def GenerateName(infos): laurent@576: if infos[0] in ["input", "output", "value"]: laurent@566: return "%s %d:" % (infos[0], infos[1]) laurent@576: elif infos[0] == "range": laurent@576: return "%s %d %s" % (infos[0], infos[1], infos[2]) laurent@576: elif infos[0] == "struct": laurent@576: return "element %d %s" % (infos[1], infos[2]) laurent@566: return "%s:" % infos[0] laurent@566: laurent@566: #------------------------------------------------------------------------------- laurent@566: # Search Result Panel laurent@566: #------------------------------------------------------------------------------- laurent@566: laurent@566: [ID_SEARCHRESULTPANEL, ID_SEARCHRESULTPANELHEADERLABEL, laurent@566: ID_SEARCHRESULTPANELSEARCHRESULTSTREE, ID_SEARCHRESULTPANELRESETBUTTON, laurent@566: ] = [wx.NewId() for _init_ctrls in range(4)] laurent@566: laurent@566: class SearchResultPanel(wx.Panel): laurent@566: laurent@566: if wx.VERSION < (2, 6, 0): laurent@566: def Bind(self, event, function, id = None): laurent@566: if id is not None: laurent@566: event(self, id, function) laurent@566: else: laurent@566: event(self, function) laurent@566: laurent@566: def _init_coll_MainSizer_Items(self, parent): laurent@566: parent.AddSizer(self.HeaderSizer, 0, border=0, flag=wx.GROW) laurent@566: parent.AddWindow(self.SearchResultsTree, 1, border=0, flag=wx.GROW) laurent@566: laurent@566: def _init_coll_MainSizer_Growables(self, parent): laurent@566: parent.AddGrowableCol(0) laurent@566: parent.AddGrowableRow(1) laurent@566: laurent@566: def _init_coll_HeaderSizer_Items(self, parent): laurent@566: parent.AddWindow(self.HeaderLabel, 1, border=5, flag=wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL) laurent@566: parent.AddWindow(self.ResetButton, 0, border=0, flag=0) laurent@566: laurent@566: def _init_coll_HeaderSizer_Growables(self, parent): laurent@566: parent.AddGrowableCol(0) laurent@566: laurent@566: def _init_sizers(self): laurent@566: self.MainSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0) laurent@566: self.HeaderSizer = wx.BoxSizer(wx.HORIZONTAL) laurent@566: laurent@566: self._init_coll_MainSizer_Items(self.MainSizer) laurent@566: self._init_coll_MainSizer_Growables(self.MainSizer) laurent@566: self._init_coll_HeaderSizer_Items(self.HeaderSizer) laurent@566: laurent@566: self.SetSizer(self.MainSizer) laurent@566: laurent@566: def _init_ctrls(self, prnt): laurent@566: wx.Panel.__init__(self, id=ID_SEARCHRESULTPANEL, laurent@566: name='SearchResultPanel', parent=prnt, pos=wx.Point(0, 0), laurent@566: size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL) laurent@566: laurent@566: self.HeaderLabel = wx.StaticText(id=ID_SEARCHRESULTPANELHEADERLABEL, laurent@566: name='HeaderLabel', parent=self, laurent@566: pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) laurent@574: laurent@574: search_results_tree_style = CT.TR_HAS_BUTTONS|CT.TR_NO_LINES|CT.TR_HAS_VARIABLE_ROW_HEIGHT laurent@566: self.SearchResultsTree = CT.CustomTreeCtrl(id=ID_SEARCHRESULTPANELSEARCHRESULTSTREE, laurent@566: name="SearchResultsTree", parent=self, laurent@574: pos=wx.Point(0, 0), style=search_results_tree_style) laurent@574: if wx.VERSION >= (2, 8, 11): laurent@574: self.SearchResultsTree.SetAGWWindowStyleFlag(search_results_tree_style) laurent@566: self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnSearchResultsTreeItemActivated, laurent@566: id=ID_SEARCHRESULTPANELSEARCHRESULTSTREE) laurent@566: Laurent@714: self.ResetButton = wx.lib.buttons.GenBitmapButton(self, Laurent@714: bitmap=GetBitmap("reset"), size=wx.Size(28, 28), style=wx.NO_BORDER) Laurent@714: self.ResetButton.SetToolTipString(_("Reset search result")) Laurent@714: self.Bind(wx.EVT_BUTTON, self.OnResetButton, self.ResetButton) laurent@566: laurent@566: self._init_sizers() laurent@566: laurent@566: def __init__(self, parent, window): Laurent@714: self.ParentWindow = window Laurent@714: laurent@566: self._init_ctrls(parent) laurent@566: laurent@566: # Define Tree item icon list laurent@566: self.TreeImageList = wx.ImageList(16, 16) laurent@566: self.TreeImageDict = {} laurent@566: laurent@566: # Icons for other items laurent@566: for imgname, itemtype in [ laurent@566: #editables laurent@566: ("PROJECT", ITEM_PROJECT), laurent@566: ("TRANSITION", ITEM_TRANSITION), laurent@566: ("ACTION", ITEM_ACTION), laurent@566: ("CONFIGURATION", ITEM_CONFIGURATION), laurent@566: ("RESOURCE", ITEM_RESOURCE), laurent@566: ("DATATYPE", ITEM_DATATYPE), laurent@566: ("ACTION", "action_block"), laurent@566: ("IL", "IL"), laurent@566: ("ST", "ST")]: Laurent@714: self.TreeImageDict[itemtype] = self.TreeImageList.Add(GetBitmap(imgname)) laurent@566: laurent@566: for itemtype in ["function", "functionBlock", "program", laurent@566: "comment", "block", "io_variable", laurent@566: "connector", "contact", "coil", laurent@566: "step", "transition", "jump", laurent@566: "var_local", "var_input", laurent@566: "var_inout", "var_output"]: Laurent@714: self.TreeImageDict[itemtype] = self.TreeImageList.Add(GetBitmap(itemtype.upper())) laurent@566: laurent@566: # Assign icon list to TreeCtrl laurent@566: self.SearchResultsTree.SetImageList(self.TreeImageList) laurent@566: laurent@566: self.ResetSearchResults() laurent@566: laurent@566: def SetSearchResults(self, criteria, search_results): laurent@566: self.Criteria = criteria laurent@571: self.SearchResults = {} laurent@571: self.ElementsOrder = [] laurent@566: laurent@566: for infos, start, end, text in search_results: laurent@566: if infos[0] not in self.ElementsOrder: laurent@566: self.ElementsOrder.append(infos[0]) laurent@566: laurent@566: results = self.SearchResults.setdefault(infos[0], []) laurent@566: results.append((infos, start, end, text)) laurent@566: laurent@566: self.RefreshView() laurent@566: laurent@566: def ResetSearchResults(self): laurent@566: self.Criteria = None laurent@566: self.ElementsOrder = [] laurent@566: self.SearchResults = {} laurent@566: self.RefreshView() laurent@566: laurent@566: def RefreshView(self): laurent@571: self.SearchResultsTree.DeleteAllItems() laurent@566: if self.Criteria is None: laurent@566: self.HeaderLabel.SetLabel(_("No search results available.")) laurent@566: self.ResetButton.Enable(False) laurent@566: else: laurent@566: matches_number = 0 laurent@566: search_results_tree_infos = {"name": _("Project '%s':") % self.ParentWindow.Controler.GetProjectName(), laurent@566: "type": ITEM_PROJECT, laurent@566: "data": None, laurent@566: "text": None, laurent@566: "matches": None, laurent@566: } laurent@566: search_results_tree_children = search_results_tree_infos.setdefault("children", []) laurent@566: for tagname in self.ElementsOrder: laurent@566: results = self.SearchResults.get(tagname, []) laurent@566: matches_number += len(results) laurent@566: laurent@566: words = tagname.split("::") laurent@566: laurent@566: element_type = self.ParentWindow.Controler.GetElementType(tagname) laurent@566: if element_type == ITEM_POU: laurent@566: element_type = self.ParentWindow.Controler.GetPouType(words[1]) laurent@566: laurent@566: element_infos = {"name": words[-1], laurent@566: "type": element_type, laurent@566: "data": tagname, laurent@566: "text": None, laurent@566: "matches": len(results)} laurent@566: laurent@566: children = element_infos.setdefault("children", []) laurent@566: for infos, start, end, text in results: Laurent@753: if infos[1] == "name" or element_type == ITEM_DATATYPE: laurent@576: child_name = GenerateName(infos[1:]) laurent@576: child_type = element_type laurent@576: else: laurent@581: if element_type == ITEM_RESOURCE: laurent@581: child_type = element_type laurent@581: else: laurent@581: child_type = infos[1] Laurent@753: if child_type == "name": Laurent@753: child_name = "name" Laurent@753: elif child_type == "body": laurent@576: child_name = "body" laurent@576: if element_type == ITEM_TRANSITION: laurent@576: child_type = self.ParentWindow.Controler.GetTransitionBodyType(words[1], words[2]) laurent@576: elif element_type == ITEM_ACTION: laurent@576: child_type = self.ParentWindow.Controler.GetActionBodyType(words[1], words[2]) laurent@576: else: laurent@576: child_type = self.ParentWindow.Controler.GetPouBodyType(words[1]) laurent@566: else: laurent@576: child_name = GenerateName(infos[3:]) laurent@566: child_infos = {"name": child_name, laurent@566: "type": child_type, laurent@566: "data": (infos, start, end ,None), laurent@566: "text": text, laurent@566: "matches": 1, laurent@566: "children": [], laurent@566: } laurent@566: children.append(child_infos) laurent@566: laurent@566: if len(words) > 2: laurent@566: for _element_infos in search_results_tree_children: laurent@566: if _element_infos["name"] == words[1]: laurent@566: _element_infos["matches"] += len(children) laurent@566: _element_infos["children"].append(element_infos) laurent@566: break laurent@566: else: laurent@566: search_results_tree_children.append(element_infos) laurent@566: laurent@566: if matches_number < 2: laurent@566: header_format = _("'%s' - %d match in project") laurent@566: else: laurent@566: header_format = _("'%s' - %d matches in project") laurent@566: laurent@566: self.HeaderLabel.SetLabel(header_format % (self.Criteria["raw_pattern"], matches_number)) laurent@566: self.ResetButton.Enable(True) laurent@566: laurent@566: if matches_number > 0: laurent@566: root = self.SearchResultsTree.GetRootItem() laurent@566: if root is None: laurent@566: root = self.SearchResultsTree.AddRoot(search_results_tree_infos["name"]) laurent@566: self.GenerateSearchResultsTreeBranch(root, search_results_tree_infos) laurent@566: self.SearchResultsTree.Expand(root) laurent@566: laurent@566: def GetTextCtrlClickFunction(self, item): laurent@566: def OnTextCtrlClick(event): laurent@566: self.SearchResultsTree.SelectItem(item) laurent@566: event.Skip() laurent@566: return OnTextCtrlClick laurent@566: laurent@566: def GetTextCtrlDClickFunction(self, item): laurent@566: def OnTextCtrlDClick(event): laurent@566: self.ShowSearchResults(item) laurent@566: event.Skip() laurent@566: return OnTextCtrlDClick laurent@566: laurent@566: def GenerateSearchResultsTreeBranch(self, root, infos): laurent@566: to_delete = [] laurent@566: if infos["name"] == "body": laurent@566: item_name = "%d:" % infos["data"][1][0] laurent@566: else: laurent@566: item_name = infos["name"] laurent@566: laurent@566: self.SearchResultsTree.SetItemText(root, item_name) laurent@566: self.SearchResultsTree.SetPyData(root, infos["data"]) laurent@566: self.SearchResultsTree.SetItemBackgroundColour(root, wx.WHITE) laurent@566: self.SearchResultsTree.SetItemTextColour(root, wx.BLACK) laurent@566: if infos["type"] is not None: laurent@566: if infos["type"] == ITEM_POU: laurent@566: self.SearchResultsTree.SetItemImage(root, self.TreeImageDict[self.ParentWindow.Controler.GetPouType(infos["name"])]) laurent@566: else: laurent@566: self.SearchResultsTree.SetItemImage(root, self.TreeImageDict[infos["type"]]) laurent@566: laurent@571: text = None laurent@566: if infos["text"] is not None: laurent@571: text = infos["text"] laurent@571: start, end = infos["data"][1:3] laurent@571: text_lines = infos["text"].splitlines() laurent@571: start_idx = start[1] laurent@571: end_idx = reduce(lambda x, y: x + y, map(lambda x: len(x) + 1, text_lines[:end[0] - start[0]]), end[1] + 1) laurent@571: style = wx.TextAttr(wx.BLACK, wx.Colour(206, 204, 247)) laurent@571: elif infos["type"] is not None and infos["matches"] > 1: laurent@571: text = _("(%d matches)") % infos["matches"] laurent@571: start_idx, end_idx = 0, len(text) laurent@571: style = wx.TextAttr(wx.Colour(0, 127, 174)) laurent@571: laurent@571: if text is not None: laurent@572: text_ctrl_style = wx.BORDER_NONE|wx.TE_READONLY|wx.TE_RICH2 laurent@572: if wx.Platform != '__WXMSW__' or len(text.splitlines()) > 1: laurent@572: text_ctrl_style |= wx.TE_MULTILINE laurent@572: text_ctrl = wx.TextCtrl(id=-1, parent=self.SearchResultsTree, pos=wx.Point(0, 0), laurent@572: value=text, style=text_ctrl_style) laurent@572: width, height = text_ctrl.GetTextExtent(text) laurent@572: text_ctrl.SetClientSize(wx.Size(width + 1, height)) laurent@572: text_ctrl.SetBackgroundColour(self.SearchResultsTree.GetBackgroundColour()) laurent@566: text_ctrl.Bind(wx.EVT_LEFT_DOWN, self.GetTextCtrlClickFunction(root)) laurent@566: text_ctrl.Bind(wx.EVT_LEFT_DCLICK, self.GetTextCtrlDClickFunction(root)) laurent@566: text_ctrl.SetInsertionPoint(0) laurent@571: text_ctrl.SetStyle(start_idx, end_idx, style) laurent@572: self.SearchResultsTree.SetItemWindow(root, text_ctrl) laurent@566: laurent@566: if wx.VERSION >= (2, 6, 0): laurent@566: item, root_cookie = self.SearchResultsTree.GetFirstChild(root) laurent@566: else: laurent@566: item, root_cookie = self.SearchResultsTree.GetFirstChild(root, 0) laurent@566: for child in infos["children"]: laurent@566: if item is None: laurent@566: item = self.SearchResultsTree.AppendItem(root, "") laurent@571: item, root_cookie = self.SearchResultsTree.GetNextChild(root, root_cookie) laurent@566: self.GenerateSearchResultsTreeBranch(item, child) laurent@566: item, root_cookie = self.SearchResultsTree.GetNextChild(root, root_cookie) laurent@566: laurent@566: def ShowSearchResults(self, item): laurent@566: data = self.SearchResultsTree.GetPyData(item) laurent@566: if isinstance(data, TupleType): laurent@566: search_results = [data] laurent@566: else: laurent@566: search_results = self.SearchResults.get(data, []) laurent@566: for infos, start, end, text in search_results: laurent@566: self.ParentWindow.ShowSearchResult(infos, start, end) laurent@566: laurent@566: def OnSearchResultsTreeItemActivated(self, event): laurent@566: self.ShowSearchResults(event.GetItem()) laurent@566: event.Skip() laurent@566: laurent@566: def OnResetButton(self, event): laurent@566: self.ResetSearchResults() laurent@566: self.ParentWindow.ClearSearchResults() laurent@566: event.Skip()