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