controls/SearchResultPanel.py
changeset 814 5743cbdff669
child 899 64aa66d481c5
equal deleted inserted replaced
813:1460273f40ed 814:5743cbdff669
       
     1 #!/usr/bin/env python
       
     2 # -*- coding: utf-8 -*-
       
     3 
       
     4 #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
       
     5 #based on the plcopen standard. 
       
     6 #
       
     7 #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
       
     8 #
       
     9 #See COPYING file for copyrights details.
       
    10 #
       
    11 #This library is free software; you can redistribute it and/or
       
    12 #modify it under the terms of the GNU General Public
       
    13 #License as published by the Free Software Foundation; either
       
    14 #version 2.1 of the License, or (at your option) any later version.
       
    15 #
       
    16 #This library is distributed in the hope that it will be useful,
       
    17 #but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    18 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
       
    19 #General Public License for more details.
       
    20 #
       
    21 #You should have received a copy of the GNU General Public
       
    22 #License along with this library; if not, write to the Free Software
       
    23 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
       
    24 
       
    25 from types import TupleType
       
    26 
       
    27 import wx
       
    28 import wx.lib.buttons
       
    29 import wx.lib.agw.customtreectrl as CT
       
    30 
       
    31 from PLCControler import *
       
    32 from util.BitmapLibrary import GetBitmap
       
    33 
       
    34 def GenerateName(infos):
       
    35     if infos[0] in ["input", "output", "value"]:
       
    36         return "%s %d:" % (infos[0], infos[1])
       
    37     elif infos[0] == "range":
       
    38         return "%s %d %s" % (infos[0], infos[1], infos[2])
       
    39     elif infos[0] == "struct":
       
    40         return "element %d %s" % (infos[1], infos[2])
       
    41     return "%s:" % infos[0]
       
    42 
       
    43 #-------------------------------------------------------------------------------
       
    44 #                            Search Result Panel
       
    45 #-------------------------------------------------------------------------------
       
    46 
       
    47 [ID_SEARCHRESULTPANEL, ID_SEARCHRESULTPANELHEADERLABEL,
       
    48  ID_SEARCHRESULTPANELSEARCHRESULTSTREE, ID_SEARCHRESULTPANELRESETBUTTON, 
       
    49 ] = [wx.NewId() for _init_ctrls in range(4)]
       
    50 
       
    51 class SearchResultPanel(wx.Panel):
       
    52 
       
    53     if wx.VERSION < (2, 6, 0):
       
    54         def Bind(self, event, function, id = None):
       
    55             if id is not None:
       
    56                 event(self, id, function)
       
    57             else:
       
    58                 event(self, function)
       
    59 
       
    60     def _init_coll_MainSizer_Items(self, parent):
       
    61         parent.AddSizer(self.HeaderSizer, 0, border=0, flag=wx.GROW)
       
    62         parent.AddWindow(self.SearchResultsTree, 1, border=0, flag=wx.GROW)
       
    63     
       
    64     def _init_coll_MainSizer_Growables(self, parent):
       
    65         parent.AddGrowableCol(0)
       
    66         parent.AddGrowableRow(1)
       
    67 
       
    68     def _init_coll_HeaderSizer_Items(self, parent):
       
    69         parent.AddWindow(self.HeaderLabel, 1, border=5, flag=wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
       
    70         parent.AddWindow(self.ResetButton, 0, border=0, flag=0)
       
    71     
       
    72     def _init_coll_HeaderSizer_Growables(self, parent):
       
    73         parent.AddGrowableCol(0)
       
    74     
       
    75     def _init_sizers(self):
       
    76         self.MainSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
       
    77         self.HeaderSizer = wx.BoxSizer(wx.HORIZONTAL)
       
    78         
       
    79         self._init_coll_MainSizer_Items(self.MainSizer)
       
    80         self._init_coll_MainSizer_Growables(self.MainSizer)
       
    81         self._init_coll_HeaderSizer_Items(self.HeaderSizer)
       
    82         
       
    83         self.SetSizer(self.MainSizer)
       
    84 
       
    85     def _init_ctrls(self, prnt):
       
    86         wx.Panel.__init__(self, id=ID_SEARCHRESULTPANEL,
       
    87               name='SearchResultPanel', parent=prnt, pos=wx.Point(0, 0),
       
    88               size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL)
       
    89 
       
    90         self.HeaderLabel = wx.StaticText(id=ID_SEARCHRESULTPANELHEADERLABEL,
       
    91               name='HeaderLabel', parent=self,
       
    92               pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0)
       
    93         
       
    94         search_results_tree_style = CT.TR_HAS_BUTTONS|CT.TR_NO_LINES|CT.TR_HAS_VARIABLE_ROW_HEIGHT
       
    95         self.SearchResultsTree = CT.CustomTreeCtrl(id=ID_SEARCHRESULTPANELSEARCHRESULTSTREE,
       
    96               name="SearchResultsTree", parent=self,
       
    97               pos=wx.Point(0, 0), style=search_results_tree_style)
       
    98         if wx.VERSION >= (2, 8, 11):
       
    99             self.SearchResultsTree.SetAGWWindowStyleFlag(search_results_tree_style)
       
   100         self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnSearchResultsTreeItemActivated,
       
   101               id=ID_SEARCHRESULTPANELSEARCHRESULTSTREE)
       
   102         
       
   103         self.ResetButton = wx.lib.buttons.GenBitmapButton(self,
       
   104               bitmap=GetBitmap("reset"), size=wx.Size(28, 28), style=wx.NO_BORDER)
       
   105         self.ResetButton.SetToolTipString(_("Reset search result"))
       
   106         self.Bind(wx.EVT_BUTTON, self.OnResetButton, self.ResetButton)
       
   107         
       
   108         self._init_sizers()
       
   109 
       
   110     def __init__(self, parent, window):
       
   111         self.ParentWindow = window
       
   112         
       
   113         self._init_ctrls(parent)
       
   114         
       
   115         # Define Tree item icon list
       
   116         self.TreeImageList = wx.ImageList(16, 16)
       
   117         self.TreeImageDict = {}
       
   118         
       
   119         # Icons for other items
       
   120         for imgname, itemtype in [
       
   121             #editables
       
   122             ("PROJECT",        ITEM_PROJECT),
       
   123             ("TRANSITION",     ITEM_TRANSITION),
       
   124             ("ACTION",         ITEM_ACTION),
       
   125             ("CONFIGURATION",  ITEM_CONFIGURATION),
       
   126             ("RESOURCE",       ITEM_RESOURCE),
       
   127             ("DATATYPE",       ITEM_DATATYPE),
       
   128             ("ACTION",         "action_block"),
       
   129             ("IL",             "IL"),
       
   130             ("ST",             "ST")]:
       
   131             self.TreeImageDict[itemtype] = self.TreeImageList.Add(GetBitmap(imgname))
       
   132         
       
   133         for itemtype in ["function", "functionBlock", "program",
       
   134                          "comment", "block", "io_variable",
       
   135                          "connector", "contact", "coil",
       
   136                          "step", "transition", "jump", 
       
   137                          "var_local", "var_input", 
       
   138                          "var_inout", "var_output"]:
       
   139             self.TreeImageDict[itemtype] = self.TreeImageList.Add(GetBitmap(itemtype.upper()))
       
   140         
       
   141         # Assign icon list to TreeCtrl
       
   142         self.SearchResultsTree.SetImageList(self.TreeImageList)
       
   143         
       
   144         self.ResetSearchResults()
       
   145 
       
   146     def SetSearchResults(self, criteria, search_results):
       
   147         self.Criteria = criteria
       
   148         self.SearchResults = {}
       
   149         self.ElementsOrder = []
       
   150         
       
   151         for infos, start, end, text in search_results:
       
   152             if infos[0] not in self.ElementsOrder:
       
   153                 self.ElementsOrder.append(infos[0])
       
   154             
       
   155             results = self.SearchResults.setdefault(infos[0], [])
       
   156             results.append((infos, start, end, text))
       
   157         
       
   158         self.RefreshView()
       
   159     
       
   160     def ResetSearchResults(self):
       
   161         self.Criteria = None
       
   162         self.ElementsOrder = []
       
   163         self.SearchResults = {}
       
   164         self.RefreshView()
       
   165     
       
   166     def RefreshView(self):
       
   167         self.SearchResultsTree.DeleteAllItems()
       
   168         if self.Criteria is None:
       
   169             self.HeaderLabel.SetLabel(_("No search results available."))
       
   170             self.ResetButton.Enable(False)
       
   171         else:
       
   172             matches_number = 0
       
   173             search_results_tree_infos = {"name": _("Project '%s':") % self.ParentWindow.Controler.GetProjectName(),
       
   174                                          "type": ITEM_PROJECT,
       
   175                                          "data": None,
       
   176                                          "text": None,
       
   177                                          "matches": None,
       
   178                                         }
       
   179             search_results_tree_children = search_results_tree_infos.setdefault("children", [])
       
   180             for tagname in self.ElementsOrder:
       
   181                 results = self.SearchResults.get(tagname, [])
       
   182                 matches_number += len(results)
       
   183                 
       
   184                 words = tagname.split("::")
       
   185                 
       
   186                 element_type = self.ParentWindow.Controler.GetElementType(tagname)
       
   187                 if element_type == ITEM_POU:
       
   188                     element_type = self.ParentWindow.Controler.GetPouType(words[1])
       
   189                 
       
   190                 element_infos = {"name": words[-1],
       
   191                                  "type": element_type,
       
   192                                  "data": tagname,
       
   193                                  "text": None,
       
   194                                  "matches": len(results)}
       
   195                 
       
   196                 children = element_infos.setdefault("children", [])
       
   197                 for infos, start, end, text in results:
       
   198                     if infos[1] == "name" or element_type == ITEM_DATATYPE:
       
   199                         child_name = GenerateName(infos[1:])
       
   200                         child_type = element_type
       
   201                     else:
       
   202                         if element_type == ITEM_RESOURCE:
       
   203                             child_type = element_type
       
   204                         else:
       
   205                             child_type = infos[1]
       
   206                         if child_type == "name":
       
   207                             child_name = "name"
       
   208                         elif child_type == "body":
       
   209                             child_name = "body"
       
   210                             if element_type == ITEM_TRANSITION:
       
   211                                 child_type = self.ParentWindow.Controler.GetTransitionBodyType(words[1], words[2])
       
   212                             elif element_type == ITEM_ACTION:
       
   213                                 child_type = self.ParentWindow.Controler.GetActionBodyType(words[1], words[2])
       
   214                             else:
       
   215                                 child_type = self.ParentWindow.Controler.GetPouBodyType(words[1])
       
   216                         else:
       
   217                             child_name = GenerateName(infos[3:])
       
   218                     child_infos = {"name": child_name,
       
   219                                    "type": child_type,
       
   220                                    "data": (infos, start, end ,None),
       
   221                                    "text": text,
       
   222                                    "matches": 1,
       
   223                                    "children": [],
       
   224                                   }
       
   225                     children.append(child_infos)
       
   226                 
       
   227                 if len(words) > 2:
       
   228                     for _element_infos in search_results_tree_children:
       
   229                         if _element_infos["name"] == words[1]:
       
   230                             _element_infos["matches"] += len(children)
       
   231                             _element_infos["children"].append(element_infos)
       
   232                             break
       
   233                 else:
       
   234                     search_results_tree_children.append(element_infos)
       
   235             
       
   236             if matches_number < 2:
       
   237                 header_format = _("'%s' - %d match in project")
       
   238             else:
       
   239                 header_format = _("'%s' - %d matches in project")
       
   240             
       
   241             self.HeaderLabel.SetLabel(header_format % (self.Criteria["raw_pattern"], matches_number))
       
   242             self.ResetButton.Enable(True)
       
   243             
       
   244             if matches_number > 0:
       
   245                 root = self.SearchResultsTree.GetRootItem()
       
   246                 if root is None:
       
   247                     root = self.SearchResultsTree.AddRoot(search_results_tree_infos["name"])
       
   248                 self.GenerateSearchResultsTreeBranch(root, search_results_tree_infos)
       
   249                 self.SearchResultsTree.Expand(root)
       
   250     
       
   251     def GetTextCtrlClickFunction(self, item):
       
   252         def OnTextCtrlClick(event):
       
   253             self.SearchResultsTree.SelectItem(item)
       
   254             event.Skip()
       
   255         return OnTextCtrlClick
       
   256     
       
   257     def GetTextCtrlDClickFunction(self, item):
       
   258         def OnTextCtrlDClick(event):
       
   259             self.ShowSearchResults(item)
       
   260             event.Skip()
       
   261         return OnTextCtrlDClick
       
   262     
       
   263     def GenerateSearchResultsTreeBranch(self, root, infos):
       
   264         to_delete = []
       
   265         if infos["name"] == "body":
       
   266             item_name = "%d:" % infos["data"][1][0]
       
   267         else:
       
   268             item_name = infos["name"]
       
   269         
       
   270         self.SearchResultsTree.SetItemText(root, item_name)
       
   271         self.SearchResultsTree.SetPyData(root, infos["data"])
       
   272         self.SearchResultsTree.SetItemBackgroundColour(root, wx.WHITE)
       
   273         self.SearchResultsTree.SetItemTextColour(root, wx.BLACK)
       
   274         if infos["type"] is not None:
       
   275             if infos["type"] == ITEM_POU:
       
   276                 self.SearchResultsTree.SetItemImage(root, self.TreeImageDict[self.ParentWindow.Controler.GetPouType(infos["name"])])
       
   277             else:
       
   278                 self.SearchResultsTree.SetItemImage(root, self.TreeImageDict[infos["type"]])
       
   279         
       
   280         text = None
       
   281         if infos["text"] is not None:
       
   282             text = infos["text"]
       
   283             start, end = infos["data"][1:3]
       
   284             text_lines = infos["text"].splitlines()
       
   285             start_idx = start[1]
       
   286             end_idx = reduce(lambda x, y: x + y, map(lambda x: len(x) + 1, text_lines[:end[0] - start[0]]), end[1] + 1)
       
   287             style = wx.TextAttr(wx.BLACK, wx.Colour(206, 204, 247))
       
   288         elif infos["type"] is not None and infos["matches"] > 1:
       
   289             text = _("(%d matches)") % infos["matches"]
       
   290             start_idx, end_idx = 0, len(text)
       
   291             style = wx.TextAttr(wx.Colour(0, 127, 174))
       
   292         
       
   293         if text is not None:
       
   294             text_ctrl_style = wx.BORDER_NONE|wx.TE_READONLY|wx.TE_RICH2
       
   295             if wx.Platform != '__WXMSW__' or len(text.splitlines()) > 1:
       
   296                 text_ctrl_style |= wx.TE_MULTILINE
       
   297             text_ctrl = wx.TextCtrl(id=-1, parent=self.SearchResultsTree, pos=wx.Point(0, 0), 
       
   298                     value=text, style=text_ctrl_style)
       
   299             width, height = text_ctrl.GetTextExtent(text)
       
   300             text_ctrl.SetClientSize(wx.Size(width + 1, height))
       
   301             text_ctrl.SetBackgroundColour(self.SearchResultsTree.GetBackgroundColour())
       
   302             text_ctrl.Bind(wx.EVT_LEFT_DOWN, self.GetTextCtrlClickFunction(root))
       
   303             text_ctrl.Bind(wx.EVT_LEFT_DCLICK, self.GetTextCtrlDClickFunction(root))
       
   304             text_ctrl.SetInsertionPoint(0)
       
   305             text_ctrl.SetStyle(start_idx, end_idx, style)
       
   306             self.SearchResultsTree.SetItemWindow(root, text_ctrl)
       
   307             
       
   308         if wx.VERSION >= (2, 6, 0):
       
   309             item, root_cookie = self.SearchResultsTree.GetFirstChild(root)
       
   310         else:
       
   311             item, root_cookie = self.SearchResultsTree.GetFirstChild(root, 0)
       
   312         for child in infos["children"]:
       
   313             if item is None:
       
   314                 item = self.SearchResultsTree.AppendItem(root, "")
       
   315                 item, root_cookie = self.SearchResultsTree.GetNextChild(root, root_cookie)
       
   316             self.GenerateSearchResultsTreeBranch(item, child)
       
   317             item, root_cookie = self.SearchResultsTree.GetNextChild(root, root_cookie)
       
   318     
       
   319     def ShowSearchResults(self, item):
       
   320         data = self.SearchResultsTree.GetPyData(item)
       
   321         if isinstance(data, TupleType):
       
   322             search_results = [data]
       
   323         else:
       
   324             search_results = self.SearchResults.get(data, [])
       
   325         for infos, start, end, text in search_results:
       
   326             self.ParentWindow.ShowSearchResult(infos, start, end)
       
   327     
       
   328     def OnSearchResultsTreeItemActivated(self, event):
       
   329         self.ShowSearchResults(event.GetItem())
       
   330         event.Skip()
       
   331     
       
   332     def OnResetButton(self, event):
       
   333         self.ResetSearchResults()
       
   334         self.ParentWindow.ClearSearchResults()
       
   335         event.Skip()