Adding support for in POU graphical and textual editor
authorLaurent Bessard
Fri, 10 Aug 2012 00:32:05 +0200
changeset 738 1ccd08cfae0c
parent 737 85a4bc7dc31e
child 739 ed87f96c7c12
Adding support for in POU graphical and textual editor
PLCControler.py
PLCOpenEditor.py
TextViewer.py
Viewer.py
controls/CustomTable.py
controls/EditorPanel.py
controls/VariablePanel.py
dialogs/FindInPouDialog.py
dialogs/SearchInProjectDialog.py
dialogs/__init__.py
--- a/PLCControler.py	Wed Aug 01 12:44:51 2012 +0200
+++ b/PLCControler.py	Fri Aug 10 00:32:05 2012 +0200
@@ -2960,6 +2960,12 @@
     def SearchInProject(self, criteria):
         return self.Project.Search(criteria)
 
+    def SearchInPou(self, tagname, criteria, debug=False):
+        pou = self.GetEditedElement(tagname, debug)
+        if pou is not None:
+            return pou.Search(criteria)
+        return []
+
 #-------------------------------------------------------------------------------
 #                      Current Buffering Management Functions
 #-------------------------------------------------------------------------------
--- a/PLCOpenEditor.py	Wed Aug 01 12:44:51 2012 +0200
+++ b/PLCOpenEditor.py	Fri Aug 10 00:32:05 2012 +0200
@@ -113,7 +113,7 @@
 from PLCControler import *
 from SearchResultPanel import SearchResultPanel
 from controls import CustomTree, LibraryPanel, PouInstanceVariablesPanel, DebugVariablePanel
-from dialogs import ProjectDialog, PouTransitionDialog, PouActionDialog
+from dialogs import ProjectDialog, PouTransitionDialog, PouActionDialog, FindInPouDialog
 
 # Define PLCOpenEditor controls id
 [ID_PLCOPENEDITOR, ID_PLCOPENEDITORLEFTNOTEBOOK, 
@@ -135,8 +135,9 @@
 [ID_PLCOPENEDITOREDITMENUENABLEUNDOREDO, ID_PLCOPENEDITOREDITMENUADDDATATYPE, 
  ID_PLCOPENEDITOREDITMENUADDFUNCTION, ID_PLCOPENEDITOREDITMENUADDFUNCTIONBLOCK, 
  ID_PLCOPENEDITOREDITMENUADDPROGRAM, ID_PLCOPENEDITOREDITMENUADDCONFIGURATION,
+ ID_PLCOPENEDITOREDITMENUFINDNEXT, ID_PLCOPENEDITOREDITMENUFINDPREVIOUS,
  ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT, 
-] = [wx.NewId() for _init_coll_EditMenu_Items in range(7)]
+] = [wx.NewId() for _init_coll_EditMenu_Items in range(9)]
 
 # Define PLCOpenEditor DisplayMenu extra items id
 [ID_PLCOPENEDITORDISPLAYMENURESETPERSPECTIVE, 
@@ -446,8 +447,15 @@
         AppendMenu(parent, help='', id=wx.ID_PASTE,
               kind=wx.ITEM_NORMAL, text=_(u'Paste\tCTRL+V'))
         parent.AppendSeparator()
+        AppendMenu(parent, help='', id=wx.ID_FIND,
+              kind=wx.ITEM_NORMAL, text=_(u'Find\tCTRL+F'))
+        AppendMenu(parent, help='', id=ID_PLCOPENEDITOREDITMENUFINDNEXT,
+              kind=wx.ITEM_NORMAL, text=_(u'Find Next\tCTRL+K'))
+        AppendMenu(parent, help='', id=ID_PLCOPENEDITOREDITMENUFINDPREVIOUS,
+              kind=wx.ITEM_NORMAL, text=_(u'Find Previous\tCTRL+SHIFT+K'))
+        parent.AppendSeparator()
         AppendMenu(parent, help='', id=ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT,
-              kind=wx.ITEM_NORMAL, text=_(u'Search in Project\tCTRL+F'))
+              kind=wx.ITEM_NORMAL, text=_(u'Search in Project\tCTRL+SHIFT+F'))
         parent.AppendSeparator()
         add_menu = wx.Menu(title='')
         self._init_coll_AddMenu_Items(add_menu)
@@ -462,6 +470,13 @@
         self.Bind(wx.EVT_MENU, self.OnCutMenu, id=wx.ID_CUT)
         self.Bind(wx.EVT_MENU, self.OnCopyMenu, id=wx.ID_COPY)
         self.Bind(wx.EVT_MENU, self.OnPasteMenu, id=wx.ID_PASTE)
+        self.Bind(wx.EVT_MENU, self.OnFindMenu, id=wx.ID_FIND)
+        self.Bind(wx.EVT_MENU, self.OnFindNextMenu, 
+              id=ID_PLCOPENEDITOREDITMENUFINDNEXT)
+        self.Bind(wx.EVT_MENU, self.OnFindPreviousMenu, 
+              id=ID_PLCOPENEDITOREDITMENUFINDPREVIOUS)
+        self.Bind(wx.EVT_MENU, self.OnSearchInProjectMenu, 
+              id=ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT)
         self.Bind(wx.EVT_MENU, self.OnSearchInProjectMenu, 
               id=ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT)
         self.Bind(wx.EVT_MENU, self.OnAddDataTypeMenu,
@@ -693,6 +708,9 @@
         
         self.AUIManager.Update()
     
+        self.FindDialog = FindInPouDialog(self)
+        self.FindDialog.Hide()
+    
     ## Constructor of the PLCOpenEditor class.
     #  @param parent The parent window.
     #  @param controler The controler been used by PLCOpenEditor (default: None).
@@ -750,6 +768,7 @@
         self.CurrentEditorToolBar = []
         self.CurrentMenu = None
         self.SelectedItem = None
+        self.SearchParams = None
         self.Highlights = {}
         self.DrawingMode = FREEDRAWING_MODE
         #self.DrawingMode = DRIVENDRAWING_MODE
@@ -766,6 +785,9 @@
         
         self.SetRefreshFunctions()
     
+    def __del__(self):
+        self.FindDialog.Destroy()
+    
     def ResetStarting(self):
 		self.Starting = False
     
@@ -1066,9 +1088,11 @@
                 # Refresh all window elements that have changed
                 wx.CallAfter(self._Refresh, TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU)
                 wx.CallAfter(self.RefreshTabCtrlEvent)
+                wx.CallAfter(self.CloseFindInPouDialog)
                 event.Skip()
             else:
                 event.Veto()
+        
 
     def GetCopyBuffer(self):
         data = None
@@ -1278,6 +1302,11 @@
             #self.EditMenu.Enable(ID_PLCOPENEDITOREDITMENUENABLEUNDOREDO, True)
             #self.EditMenu.Check(ID_PLCOPENEDITOREDITMENUENABLEUNDOREDO, 
             #                self.Controler.IsProjectBufferEnabled())
+            self.EditMenu.Enable(wx.ID_FIND, selected > -1)
+            self.EditMenu.Enable(ID_PLCOPENEDITOREDITMENUFINDNEXT, 
+                  selected > -1 and self.SearchParams is not None)
+            self.EditMenu.Enable(ID_PLCOPENEDITOREDITMENUFINDPREVIOUS, 
+                  selected > -1 and self.SearchParams is not None)
             self.EditMenu.Enable(ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT, True)
             MenuToolBar.EnableTool(ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT, True)
             self.EditMenu.Enable(wx.ID_ADD, True)
@@ -1315,6 +1344,9 @@
             self.EditMenu.Enable(wx.ID_PASTE, False)
             MenuToolBar.EnableTool(wx.ID_PASTE, False)
             self.EditMenu.Enable(wx.ID_SELECTALL, False)
+            self.EditMenu.Enable(wx.ID_FIND, False)
+            self.EditMenu.Enable(ID_PLCOPENEDITOREDITMENUFINDNEXT, False)
+            self.EditMenu.Enable(ID_PLCOPENEDITOREDITMENUFINDPREVIOUS, False)
             self.EditMenu.Enable(ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT, False)
             MenuToolBar.EnableTool(ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT, False)
             self.EditMenu.Enable(wx.ID_ADD, False)
@@ -1394,6 +1426,29 @@
             event.m_keyCode = wx.WXK_DELETE
             window.ProcessEvent(event)
 
+    def OnFindMenu(self, event):
+        if not self.FindDialog.IsShown():
+            self.FindDialog.Show()
+    
+    def CloseFindInPouDialog(self):
+        selected = self.TabsOpened.GetSelection()
+        if selected == -1 and self.FindDialog.IsShown():
+            self.FindDialog.Hide()
+    
+    def OnFindNextMenu(self, event):
+        self.FindInPou(1)
+    
+    def OnFindPreviousMenu(self, event):
+        self.FindInPou(-1)
+    
+    def FindInPou(self, direction, search_params=None):
+        if search_params is not None:
+            self.SearchParams = search_params
+        selected = self.TabsOpened.GetSelection()
+        if selected != -1:
+            window = self.TabsOpened.GetPage(selected)
+            window.Find(direction, self.SearchParams)
+    
     def OnSearchInProjectMenu(self, event):
         dialog = SearchInProjectDialog(self)
         if dialog.ShowModal() == wx.ID_OK:
--- a/TextViewer.py	Wed Aug 01 12:44:51 2012 +0200
+++ b/TextViewer.py	Fri Aug 10 00:32:05 2012 +0200
@@ -197,6 +197,9 @@
         self.TextSyntax = None
         self.CurrentAction = None
         self.Highlights = []
+        self.SearchParams = None
+        self.SearchResults = None
+        self.CurrentFindHighlight = None
         self.InstancePath = instancepath
         self.ContextStack = []
         self.CallStack = []
@@ -739,6 +742,43 @@
         self.RefreshModel()
         self.RefreshBuffer()
     
+    def Find(self, direction, search_params):
+        if self.SearchParams != search_params:
+            self.ClearHighlights(SEARCH_RESULT_HIGHLIGHT)
+            
+            self.SearchParams = search_params
+            criteria = {
+                "raw_pattern": search_params["find_pattern"], 
+                "pattern": re.compile(search_params["find_pattern"]),
+                "case_sensitive": search_params["case_sensitive"],
+                "regular_expression": search_params["regular_expression"],
+                "filter": "all"}
+            
+            self.SearchResults = [
+                (infos[1:], start, end, SEARCH_RESULT_HIGHLIGHT)
+                for infos, start, end, text in 
+                self.Controler.SearchInPou(self.TagName, criteria, self.Debug)]
+        
+        if len(self.SearchResults) > 0:
+            if self.CurrentFindHighlight is not None:
+                old_idx = self.SearchResults.index(self.CurrentFindHighlight)
+                if self.SearchParams["wrap"]:
+                    idx = (old_idx + direction) % len(self.SearchResults)
+                else:
+                    idx = max(0, min(old_idx + direction, len(self.SearchResults) - 1))
+                if idx != old_idx:
+                    self.RemoveHighlight(*self.CurrentFindHighlight)
+                    self.CurrentFindHighlight = self.SearchResults[idx]
+                    self.AddHighlight(*self.CurrentFindHighlight)
+            else:
+                self.CurrentFindHighlight = self.SearchResults[0]
+                self.AddHighlight(*self.CurrentFindHighlight)
+            
+        else:
+            if self.CurrentFindHighlight is not None:
+                self.RemoveHighlight(*self.CurrentFindHighlight)
+            self.CurrentFindHighlight = None
+    
     def RefreshModel(self):
         self.RefreshJumpList()
         self.Controler.SetEditedElementText(self.TagName, self.GetText())
@@ -832,8 +872,18 @@
         highlight_type = HIGHLIGHT_TYPES.get(highlight_type, None)
         if infos[0] == "body" and highlight_type is not None:
             self.Highlights.append((infos[1], start, end, highlight_type))
+            self.Editor.GotoPos(self.Editor.PositionFromLine(start[0]) + start[1])
             self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
 
+    def RemoveHighlight(self, infos, start, end, highlight_type):
+        EditorPanel.RemoveHighlight(self, infos, start, end, highlight_type)
+        
+        highlight_type = HIGHLIGHT_TYPES.get(highlight_type, None)
+        if (infos[0] == "body" and highlight_type is not None and 
+            (infos[1], start, end, highlight_type) in self.Highlights):
+            self.Highlights.remove((infos[1], start, end, highlight_type))
+            self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
+    
     def ShowHighlights(self, start_pos, end_pos):
         for indent, start, end, highlight_type in self.Highlights:
             if start[0] == 0:
--- a/Viewer.py	Wed Aug 01 12:44:51 2012 +0200
+++ b/Viewer.py	Fri Aug 10 00:32:05 2012 +0200
@@ -22,6 +22,7 @@
 #License along with this library; if not, write to the Free Software
 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
+import re
 import math
 import time
 from types import TupleType
@@ -170,6 +171,14 @@
     "actionBlock": actionBlockCreationFunction,
 }
 
+def sort_blocks(block_infos1, block_infos2):
+    x1, y1 = block_infos1[0].GetPosition()
+    x2, y2 = block_infos2[0].GetPosition()
+    if y1 == y2:
+        return cmp(x1, x2)
+    else:
+        return cmp(y1, y2)
+
 #-------------------------------------------------------------------------------
 #                       Graphic elements Viewer base class
 #-------------------------------------------------------------------------------
@@ -557,6 +566,9 @@
         self.current_id = 0
         self.TagName = tagname
         self.Highlights = []
+        self.SearchParams = None
+        self.SearchResults = None
+        self.CurrentFindHighlight = None
         self.InstancePath = instancepath
         self.StartMousePos = None
         self.StartScreenPos = None
@@ -1107,6 +1119,29 @@
             round(maxx / SCROLLBAR_UNIT) + width_incr, round(maxy / SCROLLBAR_UNIT) + height_incr, 
             xstart, ystart, True)
     
+    def EnsureVisible(self, block):
+        xstart, ystart = self.GetViewStart()
+        window_size = self.Editor.GetClientSize()
+        block_bbx = block.GetBoundingBox()
+        
+        screen_minx, screen_miny = xstart * SCROLLBAR_UNIT, ystart * SCROLLBAR_UNIT
+        screen_maxx, screen_maxy = screen_minx + window_size[0], screen_miny + window_size[1]
+        block_minx = int(block_bbx.x * self.ViewScale[0]) 
+        block_miny = int(block_bbx.y * self.ViewScale[1])
+        block_maxx = int(round((block_bbx.x + block_bbx.width) * self.ViewScale[0]))
+        block_maxy = int(round((block_bbx.y + block_bbx.height) * self.ViewScale[1]))
+        
+        xpos, ypos = xstart, ystart
+        if block_minx < screen_minx and block_maxx < screen_maxx:
+            xpos -= (screen_minx - block_minx) / SCROLLBAR_UNIT + 1
+        elif block_maxx > screen_maxx and block_minx > screen_minx:
+            xpos += (block_maxx - screen_maxx) / SCROLLBAR_UNIT + 1
+        if block_miny < screen_miny and block_maxy < screen_maxy:
+            ypos -= (screen_miny - block_miny) / SCROLLBAR_UNIT + 1
+        elif block_maxy > screen_maxy and block_miny > screen_miny:
+            ypos += (block_maxy - screen_maxy) / SCROLLBAR_UNIT + 1
+        self.Scroll(xpos, ypos)
+    
     def SelectInGroup(self, element):
         element.SetSelected(True)
         if self.SelectedElement is None:
@@ -3002,7 +3037,54 @@
                 self.Controler.AddEditedElementActionBlock(self.TagName, block.GetId())
                 self.RefreshActionBlockModel(block)
 
-
+#-------------------------------------------------------------------------------
+#                         Find and Replace functions
+#-------------------------------------------------------------------------------
+
+    def Find(self, direction, search_params):
+        if self.SearchParams != search_params:
+            self.ClearHighlights(SEARCH_RESULT_HIGHLIGHT)
+            
+            self.SearchParams = search_params
+            criteria = {
+                "raw_pattern": search_params["find_pattern"], 
+                "pattern": re.compile(search_params["find_pattern"]),
+                "case_sensitive": search_params["case_sensitive"],
+                "regular_expression": search_params["regular_expression"],
+                "filter": "all"}
+            
+            self.SearchResults = []
+            blocks = []
+            for infos, start, end, text in self.Controler.SearchInPou(self.TagName, criteria, self.Debug):
+                if infos[1] in ["var_local", "var_input", "var_output", "var_inout"]:
+                    self.SearchResults.append((infos[1:], start, end, SEARCH_RESULT_HIGHLIGHT))
+                else:
+                    block = self.Blocks.get(infos[2])
+                    if block is not None:
+                        blocks.append((block, (infos[1:], start, end, SEARCH_RESULT_HIGHLIGHT)))
+            blocks.sort(sort_blocks)
+            self.SearchResults.extend([infos for block, infos in blocks])
+        
+        if len(self.SearchResults) > 0:
+            if self.CurrentFindHighlight is not None:
+                old_idx = self.SearchResults.index(self.CurrentFindHighlight)
+                if self.SearchParams["wrap"]:
+                    idx = (old_idx + direction) % len(self.SearchResults)
+                else:
+                    idx = max(0, min(old_idx + direction, len(self.SearchResults) - 1))
+                if idx != old_idx:
+                    self.RemoveHighlight(*self.CurrentFindHighlight)
+                    self.CurrentFindHighlight = self.SearchResults[idx]
+                    self.AddHighlight(*self.CurrentFindHighlight)
+            else:
+                self.CurrentFindHighlight = self.SearchResults[0]
+                self.AddHighlight(*self.CurrentFindHighlight)
+            
+        else:
+            if self.CurrentFindHighlight is not None:
+                self.RemoveHighlight(*self.CurrentFindHighlight)
+            self.CurrentFindHighlight = None
+        
 #-------------------------------------------------------------------------------
 #                        Highlights showing functions
 #-------------------------------------------------------------------------------
@@ -3024,8 +3106,19 @@
         EditorPanel.AddHighlight(self, infos, start, end, highlight_type)
         
         self.Highlights.append((infos, start, end, highlight_type))
-        self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
-        
+        if infos[0] not in ["var_local", "var_input", "var_output", "var_inout"]:
+            block = self.Blocks.get(infos[1])
+            if block is not None:
+                self.EnsureVisible(block)
+            self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
+    
+    def RemoveHighlight(self, infos, start, end, highlight_type):
+        EditorPanel.RemoveHighlight(self, infos, start, end, highlight_type)
+        
+        if (infos, start, end, highlight_type) in self.Highlights:
+            self.Highlights.remove((infos, start, end, highlight_type))
+            self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
+    
     def ShowHighlights(self):
         for infos, start, end, highlight_type in self.Highlights:
             if infos[0] in ["comment", "io_variable", "block", "connector", "coil", "contact", "step", "transition", "jump", "action_block"]:
--- a/controls/CustomTable.py	Wed Aug 01 12:44:51 2012 +0200
+++ b/controls/CustomTable.py	Fri Aug 10 00:32:05 2012 +0200
@@ -168,6 +168,15 @@
         col_highlights = row_highlights.setdefault(infos[1], [])
         col_highlights.append(highlight_type)
 
+    def RemoveHighlight(self, infos, highlight_type):
+        row_highlights = self.Highlights.get(infos[0])
+        if row_highlights is not None:
+            col_highlights = row_highlights.get(infos[1])
+            if col_highlights is not None and highlight_type in col_highlights:
+                col_highlights.remove(highlight_type)
+            if len(col_highlights) == 0:
+                row_highlights.pop(infos[1])
+
     def ClearHighlights(self, highlight_type=None):
         if highlight_type is None:
             self.Highlights = {}
--- a/controls/EditorPanel.py	Wed Aug 01 12:44:51 2012 +0200
+++ b/controls/EditorPanel.py	Fri Aug 10 00:32:05 2012 +0200
@@ -130,6 +130,9 @@
             self.Controler.LoadNext()
             self.RefreshView()
     
+    def Find(self, direction, search_params):
+        pass
+        
     def HasNoModel(self):
         return False
     
@@ -157,6 +160,10 @@
         if self.VariableEditor is not None and infos[0] in ["var_local", "var_input", "var_output", "var_inout"]:
             self.VariableEditor.AddVariableHighlight(infos[1:], highlight_type)
 
+    def RemoveHighlight(self, infos, start, end, highlight_type):
+        if self.VariableEditor is not None and infos[0] in ["var_local", "var_input", "var_output", "var_inout"]:
+            self.VariableEditor.RemoveVariableHighlight(infos[1:], highlight_type)
+        
     def ClearHighlights(self, highlight_type=None):
         if self.VariableEditor is not None:
             self.VariableEditor.ClearHighlights(highlight_type)
--- a/controls/VariablePanel.py	Wed Aug 01 12:44:51 2012 +0200
+++ b/controls/VariablePanel.py	Fri Aug 10 00:32:05 2012 +0200
@@ -819,8 +819,20 @@
         if isinstance(infos[0], TupleType):
             for i in xrange(*infos[0]):
                 self.Table.AddHighlight((i,) + infos[1:], highlight_type)
+            cell_visible = infos[0][0]
         else:
             self.Table.AddHighlight(infos, highlight_type)
+            cell_visible = infos[0]
+        colnames = [colname.lower() for colname in self.Table.colnames]
+        self.VariablesGrid.MakeCellVisible(cell_visible, colnames.index(infos[1]))
+        self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
+
+    def RemoveVariableHighlight(self, infos, highlight_type):
+        if isinstance(infos[0], TupleType):
+            for i in xrange(*infos[0]):
+                self.Table.RemoveHighlight((i,) + infos[1:], highlight_type)
+        else:
+            self.Table.RemoveHighlight(infos, highlight_type)
         self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
 
     def ClearHighlights(self, highlight_type=None):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/FindInPouDialog.py	Fri Aug 10 00:32:05 2012 +0200
@@ -0,0 +1,139 @@
+#!/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
+
+import wx
+
+class FindInPouDialog(wx.Frame):
+
+    def __init__(self, parent):
+        wx.Frame.__init__(self, parent, title=_("Find"), 
+              size=wx.Size(400, 250), style=wx.CAPTION|
+                                            wx.CLOSE_BOX|
+                                            wx.CLIP_CHILDREN|
+                                            wx.TAB_TRAVERSAL|
+                                            wx.RESIZE_BORDER|
+                                            wx.STAY_ON_TOP)
+        
+        main_sizer = wx.FlexGridSizer(cols=1, hgap=5, rows=2, vgap=5)
+        main_sizer.AddGrowableCol(0)
+        main_sizer.AddGrowableRow(0)
+        
+        controls_sizer = wx.BoxSizer(wx.VERTICAL)
+        main_sizer.AddSizer(controls_sizer, border=20, 
+              flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+        
+        patterns_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=1, vgap=5)
+        patterns_sizer.AddGrowableCol(1)
+        controls_sizer.AddSizer(patterns_sizer, border=5, flag=wx.GROW|wx.BOTTOM)
+        
+        find_label = wx.StaticText(self, label=_("Find:"))
+        patterns_sizer.AddWindow(find_label, flag=wx.ALIGN_CENTER_VERTICAL)
+        
+        self.FindPattern = wx.TextCtrl(self)
+        self.Bind(wx.EVT_TEXT, self.OnFindPatternChanged, self.FindPattern)
+        patterns_sizer.AddWindow(self.FindPattern, flag=wx.GROW)
+        
+        params_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        controls_sizer.AddSizer(params_sizer, border=5, flag=wx.GROW|wx.BOTTOM)
+        
+        direction_staticbox = wx.StaticBox(self, label=_("Direction"))
+        direction_staticboxsizer = wx.StaticBoxSizer(
+              direction_staticbox, wx.VERTICAL)
+        params_sizer.AddSizer(direction_staticboxsizer, 1, border=5, 
+              flag=wx.GROW|wx.RIGHT)
+        
+        self.Forward = wx.RadioButton(self, label=_("Forward"), 
+              style=wx.RB_GROUP)
+        direction_staticboxsizer.AddWindow(self.Forward, border=5, 
+              flag=wx.ALL|wx.GROW)
+        
+        self.Backward = wx.RadioButton(self, label=_("Backward"))
+        direction_staticboxsizer.AddWindow(self.Backward, border=5, 
+              flag=wx.ALL|wx.GROW)
+        
+        options_staticbox = wx.StaticBox(self, label=_("Options"))
+        options_staticboxsizer = wx.StaticBoxSizer(
+              options_staticbox, wx.VERTICAL)
+        params_sizer.AddSizer(options_staticboxsizer, 1, flag=wx.GROW)
+        
+        self.CaseSensitive = wx.CheckBox(self, label=_("Case sensitive"))
+        self.CaseSensitive.SetValue(True)
+        options_staticboxsizer.AddWindow(self.CaseSensitive, border=5, 
+              flag=wx.ALL|wx.GROW)
+        
+        self.WrapSearch = wx.CheckBox(self, label=_("Wrap search"))
+        self.WrapSearch.SetValue(True)
+        options_staticboxsizer.AddWindow(self.WrapSearch, border=5, 
+              flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.GROW)
+        
+        self.RegularExpressions = wx.CheckBox(self, label=_("Regular expressions"))
+        options_staticboxsizer.AddWindow(self.RegularExpressions, border=5, 
+              flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.GROW)
+        
+        buttons_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        main_sizer.AddSizer(buttons_sizer, border=20, 
+              flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.ALIGN_RIGHT)
+        
+        self.FindButton = wx.Button(self, label=_("Find"))
+        self.Bind(wx.EVT_BUTTON, self.OnFindButton, self.FindButton)
+        buttons_sizer.AddWindow(self.FindButton, border=5, flag=wx.RIGHT)
+        
+        self.CloseButton = wx.Button(self, label=("Close"))
+        self.Bind(wx.EVT_BUTTON, self.OnCloseButton, self.CloseButton)
+        buttons_sizer.AddWindow(self.CloseButton)
+        
+        self.SetSizer(main_sizer)
+        
+        self.ParentWindow = parent
+        
+        self.Bind(wx.EVT_CLOSE, self.OnCloseFrame)
+    
+        self.RefreshButtonsState()
+    
+    def RefreshButtonsState(self):
+        find_pattern = self.FindPattern.GetValue()
+        self.FindButton.Enable(find_pattern != "")
+    
+    def OnCloseFrame(self, event):
+        self.Hide()
+        event.Veto()
+        
+    def OnCloseButton(self, event):
+        self.Hide()
+        event.Skip()
+
+    def OnFindPatternChanged(self, event):
+        self.RefreshButtonsState()
+        event.Skip()
+
+    def OnFindButton(self, event):
+        infos = {
+            "find_pattern": self.FindPattern.GetValue(),
+            "wrap": self.WrapSearch.GetValue(),
+            "case_sensitive": self.CaseSensitive.GetValue(),
+            "regular_expression": self.RegularExpressions.GetValue()}
+        wx.CallAfter(self.ParentWindow.FindInPou,
+            {True: 1, False:-1}[self.Forward.GetValue()],
+            infos)
+        event.Skip()
--- a/dialogs/SearchInProjectDialog.py	Wed Aug 01 12:44:51 2012 +0200
+++ b/dialogs/SearchInProjectDialog.py	Fri Aug 10 00:32:05 2012 +0200
@@ -67,7 +67,7 @@
         self.CaseSensitive = wx.CheckBox(self, label=_('Case sensitive'))
         pattern_sizer.AddWindow(self.CaseSensitive, flag=wx.GROW)
         
-        self.Pattern = wx.TextCtrl(self, size=wx.Size(0, 24))
+        self.Pattern = wx.TextCtrl(self)
         pattern_sizer.AddWindow(self.Pattern, flag=wx.GROW)
         
         self.RegularExpression = wx.CheckBox(self, label=_('Regular expression'))
@@ -140,6 +140,7 @@
         event.Skip()
     
     def OnOK(self, event):
+        message = None
         if self.Pattern.GetValue() == "":
             message = _("Form isn't complete. Pattern to search must be filled!")
         else:
--- a/dialogs/__init__.py	Wed Aug 01 12:44:51 2012 +0200
+++ b/dialogs/__init__.py	Fri Aug 10 00:32:05 2012 +0200
@@ -43,3 +43,4 @@
 from PouDialog import PouDialog
 from PouTransitionDialog import PouTransitionDialog
 from PouActionDialog import PouActionDialog
+from FindInPouDialog import FindInPouDialog