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) 2007: 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@814: from types import TupleType
Laurent@814: 
Laurent@814: import wx
Laurent@814: import wx.lib.buttons
Laurent@814: import wx.lib.agw.customtreectrl as CT
Laurent@814: 
Laurent@814: from PLCControler import *
Laurent@814: from util.BitmapLibrary import GetBitmap
Laurent@814: 
Laurent@814: def GenerateName(infos):
Laurent@814:     if infos[0] in ["input", "output", "value"]:
Laurent@814:         return "%s %d:" % (infos[0], infos[1])
Laurent@814:     elif infos[0] == "range":
Laurent@814:         return "%s %d %s" % (infos[0], infos[1], infos[2])
Laurent@814:     elif infos[0] == "struct":
Laurent@814:         return "element %d %s" % (infos[1], infos[2])
Laurent@814:     return "%s:" % infos[0]
Laurent@814: 
Laurent@814: #-------------------------------------------------------------------------------
Laurent@814: #                            Search Result Panel
Laurent@814: #-------------------------------------------------------------------------------
Laurent@814: 
Laurent@814: [ID_SEARCHRESULTPANEL, ID_SEARCHRESULTPANELHEADERLABEL,
Laurent@814:  ID_SEARCHRESULTPANELSEARCHRESULTSTREE, ID_SEARCHRESULTPANELRESETBUTTON, 
Laurent@814: ] = [wx.NewId() for _init_ctrls in range(4)]
Laurent@814: 
Laurent@814: class SearchResultPanel(wx.Panel):
Laurent@814: 
Laurent@814:     if wx.VERSION < (2, 6, 0):
Laurent@814:         def Bind(self, event, function, id = None):
Laurent@814:             if id is not None:
Laurent@814:                 event(self, id, function)
Laurent@814:             else:
Laurent@814:                 event(self, function)
Laurent@814: 
Laurent@814:     def _init_coll_MainSizer_Items(self, parent):
Laurent@814:         parent.AddSizer(self.HeaderSizer, 0, border=0, flag=wx.GROW)
Laurent@814:         parent.AddWindow(self.SearchResultsTree, 1, border=0, flag=wx.GROW)
Laurent@814:     
Laurent@814:     def _init_coll_MainSizer_Growables(self, parent):
Laurent@814:         parent.AddGrowableCol(0)
Laurent@814:         parent.AddGrowableRow(1)
Laurent@814: 
Laurent@814:     def _init_coll_HeaderSizer_Items(self, parent):
Laurent@814:         parent.AddWindow(self.HeaderLabel, 1, border=5, flag=wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
Laurent@814:         parent.AddWindow(self.ResetButton, 0, border=0, flag=0)
Laurent@814:     
Laurent@814:     def _init_coll_HeaderSizer_Growables(self, parent):
Laurent@814:         parent.AddGrowableCol(0)
Laurent@814:     
Laurent@814:     def _init_sizers(self):
Laurent@814:         self.MainSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
Laurent@814:         self.HeaderSizer = wx.BoxSizer(wx.HORIZONTAL)
Laurent@814:         
Laurent@814:         self._init_coll_MainSizer_Items(self.MainSizer)
Laurent@814:         self._init_coll_MainSizer_Growables(self.MainSizer)
Laurent@814:         self._init_coll_HeaderSizer_Items(self.HeaderSizer)
Laurent@814:         
Laurent@814:         self.SetSizer(self.MainSizer)
Laurent@814: 
Laurent@814:     def _init_ctrls(self, prnt):
Laurent@814:         wx.Panel.__init__(self, id=ID_SEARCHRESULTPANEL,
Laurent@814:               name='SearchResultPanel', parent=prnt, pos=wx.Point(0, 0),
Laurent@814:               size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL)
Laurent@814: 
Laurent@814:         self.HeaderLabel = wx.StaticText(id=ID_SEARCHRESULTPANELHEADERLABEL,
Laurent@814:               name='HeaderLabel', parent=self,
Laurent@814:               pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0)
Laurent@814:         
Laurent@814:         search_results_tree_style = CT.TR_HAS_BUTTONS|CT.TR_NO_LINES|CT.TR_HAS_VARIABLE_ROW_HEIGHT
Laurent@814:         self.SearchResultsTree = CT.CustomTreeCtrl(id=ID_SEARCHRESULTPANELSEARCHRESULTSTREE,
Laurent@814:               name="SearchResultsTree", parent=self,
Laurent@814:               pos=wx.Point(0, 0), style=search_results_tree_style)
Laurent@814:         if wx.VERSION >= (2, 8, 11):
Laurent@814:             self.SearchResultsTree.SetAGWWindowStyleFlag(search_results_tree_style)
Laurent@814:         self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnSearchResultsTreeItemActivated,
Laurent@814:               id=ID_SEARCHRESULTPANELSEARCHRESULTSTREE)
Laurent@814:         
Laurent@814:         self.ResetButton = wx.lib.buttons.GenBitmapButton(self,
Laurent@814:               bitmap=GetBitmap("reset"), size=wx.Size(28, 28), style=wx.NO_BORDER)
Laurent@814:         self.ResetButton.SetToolTipString(_("Reset search result"))
Laurent@814:         self.Bind(wx.EVT_BUTTON, self.OnResetButton, self.ResetButton)
Laurent@814:         
Laurent@814:         self._init_sizers()
Laurent@814: 
Laurent@814:     def __init__(self, parent, window):
Laurent@814:         self.ParentWindow = window
Laurent@814:         
Laurent@814:         self._init_ctrls(parent)
Laurent@814:         
Laurent@814:         # Define Tree item icon list
Laurent@814:         self.TreeImageList = wx.ImageList(16, 16)
Laurent@814:         self.TreeImageDict = {}
Laurent@814:         
Laurent@814:         # Icons for other items
Laurent@814:         for imgname, itemtype in [
Laurent@814:             #editables
Laurent@814:             ("PROJECT",        ITEM_PROJECT),
Laurent@814:             ("TRANSITION",     ITEM_TRANSITION),
Laurent@814:             ("ACTION",         ITEM_ACTION),
Laurent@814:             ("CONFIGURATION",  ITEM_CONFIGURATION),
Laurent@814:             ("RESOURCE",       ITEM_RESOURCE),
Laurent@814:             ("DATATYPE",       ITEM_DATATYPE),
Laurent@814:             ("ACTION",         "action_block"),
Laurent@814:             ("IL",             "IL"),
Laurent@814:             ("ST",             "ST")]:
Laurent@814:             self.TreeImageDict[itemtype] = self.TreeImageList.Add(GetBitmap(imgname))
Laurent@814:         
Laurent@814:         for itemtype in ["function", "functionBlock", "program",
Laurent@814:                          "comment", "block", "io_variable",
Laurent@814:                          "connector", "contact", "coil",
Laurent@814:                          "step", "transition", "jump", 
Laurent@814:                          "var_local", "var_input", 
Laurent@814:                          "var_inout", "var_output"]:
Laurent@814:             self.TreeImageDict[itemtype] = self.TreeImageList.Add(GetBitmap(itemtype.upper()))
Laurent@814:         
Laurent@814:         # Assign icon list to TreeCtrl
Laurent@814:         self.SearchResultsTree.SetImageList(self.TreeImageList)
Laurent@814:         
Laurent@814:         self.ResetSearchResults()
Laurent@814: 
Laurent@814:     def SetSearchResults(self, criteria, search_results):
Laurent@814:         self.Criteria = criteria
Laurent@814:         self.SearchResults = {}
Laurent@814:         self.ElementsOrder = []
Laurent@814:         
Laurent@814:         for infos, start, end, text in search_results:
Laurent@814:             if infos[0] not in self.ElementsOrder:
Laurent@814:                 self.ElementsOrder.append(infos[0])
Laurent@814:             
Laurent@814:             results = self.SearchResults.setdefault(infos[0], [])
Laurent@814:             results.append((infos, start, end, text))
Laurent@814:         
Laurent@814:         self.RefreshView()
Laurent@814:     
Laurent@814:     def ResetSearchResults(self):
Laurent@814:         self.Criteria = None
Laurent@814:         self.ElementsOrder = []
Laurent@814:         self.SearchResults = {}
Laurent@814:         self.RefreshView()
Laurent@814:     
Laurent@814:     def RefreshView(self):
Laurent@814:         self.SearchResultsTree.DeleteAllItems()
Laurent@814:         if self.Criteria is None:
Laurent@814:             self.HeaderLabel.SetLabel(_("No search results available."))
Laurent@814:             self.ResetButton.Enable(False)
Laurent@814:         else:
Laurent@814:             matches_number = 0
Laurent@814:             search_results_tree_infos = {"name": _("Project '%s':") % self.ParentWindow.Controler.GetProjectName(),
Laurent@814:                                          "type": ITEM_PROJECT,
Laurent@814:                                          "data": None,
Laurent@814:                                          "text": None,
Laurent@814:                                          "matches": None,
Laurent@814:                                         }
Laurent@814:             search_results_tree_children = search_results_tree_infos.setdefault("children", [])
Laurent@814:             for tagname in self.ElementsOrder:
Laurent@814:                 results = self.SearchResults.get(tagname, [])
Laurent@814:                 matches_number += len(results)
Laurent@814:                 
Laurent@814:                 words = tagname.split("::")
Laurent@814:                 
Laurent@814:                 element_type = self.ParentWindow.Controler.GetElementType(tagname)
Laurent@814:                 if element_type == ITEM_POU:
Laurent@814:                     element_type = self.ParentWindow.Controler.GetPouType(words[1])
Laurent@814:                 
Laurent@814:                 element_infos = {"name": words[-1],
Laurent@814:                                  "type": element_type,
Laurent@814:                                  "data": tagname,
Laurent@814:                                  "text": None,
Laurent@814:                                  "matches": len(results)}
Laurent@814:                 
Laurent@814:                 children = element_infos.setdefault("children", [])
Laurent@814:                 for infos, start, end, text in results:
Laurent@814:                     if infos[1] == "name" or element_type == ITEM_DATATYPE:
Laurent@814:                         child_name = GenerateName(infos[1:])
Laurent@814:                         child_type = element_type
Laurent@814:                     else:
Laurent@814:                         if element_type == ITEM_RESOURCE:
Laurent@814:                             child_type = element_type
Laurent@814:                         else:
Laurent@814:                             child_type = infos[1]
Laurent@814:                         if child_type == "name":
Laurent@814:                             child_name = "name"
Laurent@814:                         elif child_type == "body":
Laurent@814:                             child_name = "body"
Laurent@814:                             if element_type == ITEM_TRANSITION:
Laurent@814:                                 child_type = self.ParentWindow.Controler.GetTransitionBodyType(words[1], words[2])
Laurent@814:                             elif element_type == ITEM_ACTION:
Laurent@814:                                 child_type = self.ParentWindow.Controler.GetActionBodyType(words[1], words[2])
Laurent@814:                             else:
Laurent@814:                                 child_type = self.ParentWindow.Controler.GetPouBodyType(words[1])
Laurent@814:                         else:
Laurent@814:                             child_name = GenerateName(infos[3:])
Laurent@814:                     child_infos = {"name": child_name,
Laurent@814:                                    "type": child_type,
Laurent@814:                                    "data": (infos, start, end ,None),
Laurent@814:                                    "text": text,
Laurent@814:                                    "matches": 1,
Laurent@814:                                    "children": [],
Laurent@814:                                   }
Laurent@814:                     children.append(child_infos)
Laurent@814:                 
Laurent@814:                 if len(words) > 2:
Laurent@814:                     for _element_infos in search_results_tree_children:
Laurent@814:                         if _element_infos["name"] == words[1]:
Laurent@814:                             _element_infos["matches"] += len(children)
Laurent@814:                             _element_infos["children"].append(element_infos)
Laurent@814:                             break
Laurent@899:                     if element_type == ITEM_RESOURCE:
Laurent@899:                         search_results_tree_children.append(element_infos)
Laurent@899:                     else:
Laurent@899:                         _tagname = self.ParentWindow.Controler.ComputePouName(words[1])
Laurent@899:                         _element_type = self.ParentWindow.Controler.GetPouType(words[1])
Laurent@899:                     
Laurent@899:                         _element_infos = {"name": words[1],
Laurent@899:                                           "type": _element_type,
Laurent@899:                                           "data": _tagname,
Laurent@899:                                           "text": None,
Laurent@899:                                           "matches": 1,
Laurent@899:                                           "children": [element_infos]}
Laurent@899:                     
Laurent@899:                         search_results_tree_children.append(_element_infos)
Laurent@899:                 
Laurent@814:                 else:
Laurent@814:                     search_results_tree_children.append(element_infos)
Laurent@814:             
Laurent@814:             if matches_number < 2:
Laurent@814:                 header_format = _("'%s' - %d match in project")
Laurent@814:             else:
Laurent@814:                 header_format = _("'%s' - %d matches in project")
Laurent@814:             
Laurent@814:             self.HeaderLabel.SetLabel(header_format % (self.Criteria["raw_pattern"], matches_number))
Laurent@814:             self.ResetButton.Enable(True)
Laurent@814:             
Laurent@814:             if matches_number > 0:
Laurent@814:                 root = self.SearchResultsTree.GetRootItem()
Laurent@814:                 if root is None:
Laurent@814:                     root = self.SearchResultsTree.AddRoot(search_results_tree_infos["name"])
Laurent@814:                 self.GenerateSearchResultsTreeBranch(root, search_results_tree_infos)
Laurent@814:                 self.SearchResultsTree.Expand(root)
Laurent@814:     
Laurent@814:     def GetTextCtrlClickFunction(self, item):
Laurent@814:         def OnTextCtrlClick(event):
Laurent@814:             self.SearchResultsTree.SelectItem(item)
Laurent@814:             event.Skip()
Laurent@814:         return OnTextCtrlClick
Laurent@814:     
Laurent@814:     def GetTextCtrlDClickFunction(self, item):
Laurent@814:         def OnTextCtrlDClick(event):
Laurent@814:             self.ShowSearchResults(item)
Laurent@814:             event.Skip()
Laurent@814:         return OnTextCtrlDClick
Laurent@814:     
Laurent@814:     def GenerateSearchResultsTreeBranch(self, root, infos):
Laurent@814:         to_delete = []
Laurent@814:         if infos["name"] == "body":
Laurent@814:             item_name = "%d:" % infos["data"][1][0]
Laurent@814:         else:
Laurent@814:             item_name = infos["name"]
Laurent@814:         
Laurent@814:         self.SearchResultsTree.SetItemText(root, item_name)
Laurent@814:         self.SearchResultsTree.SetPyData(root, infos["data"])
Laurent@814:         self.SearchResultsTree.SetItemBackgroundColour(root, wx.WHITE)
Laurent@814:         self.SearchResultsTree.SetItemTextColour(root, wx.BLACK)
Laurent@814:         if infos["type"] is not None:
Laurent@814:             if infos["type"] == ITEM_POU:
Laurent@814:                 self.SearchResultsTree.SetItemImage(root, self.TreeImageDict[self.ParentWindow.Controler.GetPouType(infos["name"])])
Laurent@814:             else:
Laurent@814:                 self.SearchResultsTree.SetItemImage(root, self.TreeImageDict[infos["type"]])
Laurent@814:         
Laurent@814:         text = None
Laurent@814:         if infos["text"] is not None:
Laurent@814:             text = infos["text"]
Laurent@814:             start, end = infos["data"][1:3]
Laurent@814:             text_lines = infos["text"].splitlines()
Laurent@814:             start_idx = start[1]
Laurent@814:             end_idx = reduce(lambda x, y: x + y, map(lambda x: len(x) + 1, text_lines[:end[0] - start[0]]), end[1] + 1)
Laurent@814:             style = wx.TextAttr(wx.BLACK, wx.Colour(206, 204, 247))
Laurent@814:         elif infos["type"] is not None and infos["matches"] > 1:
Laurent@814:             text = _("(%d matches)") % infos["matches"]
Laurent@814:             start_idx, end_idx = 0, len(text)
Laurent@814:             style = wx.TextAttr(wx.Colour(0, 127, 174))
Laurent@814:         
Laurent@814:         if text is not None:
Laurent@814:             text_ctrl_style = wx.BORDER_NONE|wx.TE_READONLY|wx.TE_RICH2
Laurent@814:             if wx.Platform != '__WXMSW__' or len(text.splitlines()) > 1:
Laurent@814:                 text_ctrl_style |= wx.TE_MULTILINE
Laurent@814:             text_ctrl = wx.TextCtrl(id=-1, parent=self.SearchResultsTree, pos=wx.Point(0, 0), 
Laurent@814:                     value=text, style=text_ctrl_style)
Laurent@814:             width, height = text_ctrl.GetTextExtent(text)
Laurent@814:             text_ctrl.SetClientSize(wx.Size(width + 1, height))
Laurent@814:             text_ctrl.SetBackgroundColour(self.SearchResultsTree.GetBackgroundColour())
Laurent@814:             text_ctrl.Bind(wx.EVT_LEFT_DOWN, self.GetTextCtrlClickFunction(root))
Laurent@814:             text_ctrl.Bind(wx.EVT_LEFT_DCLICK, self.GetTextCtrlDClickFunction(root))
Laurent@814:             text_ctrl.SetInsertionPoint(0)
Laurent@814:             text_ctrl.SetStyle(start_idx, end_idx, style)
Laurent@814:             self.SearchResultsTree.SetItemWindow(root, text_ctrl)
Laurent@814:             
Laurent@814:         if wx.VERSION >= (2, 6, 0):
Laurent@814:             item, root_cookie = self.SearchResultsTree.GetFirstChild(root)
Laurent@814:         else:
Laurent@814:             item, root_cookie = self.SearchResultsTree.GetFirstChild(root, 0)
Laurent@814:         for child in infos["children"]:
Laurent@814:             if item is None:
Laurent@814:                 item = self.SearchResultsTree.AppendItem(root, "")
Laurent@814:                 item, root_cookie = self.SearchResultsTree.GetNextChild(root, root_cookie)
Laurent@814:             self.GenerateSearchResultsTreeBranch(item, child)
Laurent@814:             item, root_cookie = self.SearchResultsTree.GetNextChild(root, root_cookie)
Laurent@814:     
Laurent@814:     def ShowSearchResults(self, item):
Laurent@814:         data = self.SearchResultsTree.GetPyData(item)
Laurent@814:         if isinstance(data, TupleType):
Laurent@814:             search_results = [data]
Laurent@814:         else:
Laurent@814:             search_results = self.SearchResults.get(data, [])
Laurent@814:         for infos, start, end, text in search_results:
Laurent@814:             self.ParentWindow.ShowSearchResult(infos, start, end)
Laurent@814:     
Laurent@814:     def OnSearchResultsTreeItemActivated(self, event):
Laurent@814:         self.ShowSearchResults(event.GetItem())
Laurent@814:         event.Skip()
Laurent@814:     
Laurent@814:     def OnResetButton(self, event):
Laurent@814:         self.ResetSearchResults()
Laurent@814:         self.ParentWindow.ClearSearchResults()
Laurent@814:         event.Skip()