]> git.saurik.com Git - wxWidgets.git/blob - wxPython/samples/pydocview/FindService.py
applied patch #1185523: "Replace GetTextExtentPoint with GetTextExtentPoint32"
[wxWidgets.git] / wxPython / samples / pydocview / FindService.py
1 #----------------------------------------------------------------------------
2 # Name: FindService.py
3 # Purpose: Find Service for pydocview
4 #
5 # Author: Peter Yared, Morgan Hua
6 #
7 # Created: 8/15/03
8 # CVS-ID: $Id$
9 # Copyright: (c) 2003-2005 ActiveGrid, Inc.
10 # License: wxWindows License
11 #----------------------------------------------------------------------------
12
13 import wx
14 import wx.lib.docview
15 import wx.lib.pydocview
16 import re
17 _ = wx.GetTranslation
18
19
20 #----------------------------------------------------------------------------
21 # Constants
22 #----------------------------------------------------------------------------
23 FIND_MATCHPATTERN = "FindMatchPattern"
24 FIND_MATCHREPLACE = "FindMatchReplace"
25 FIND_MATCHCASE = "FindMatchCase"
26 FIND_MATCHWHOLEWORD = "FindMatchWholeWordOnly"
27 FIND_MATCHREGEXPR = "FindMatchRegularExpr"
28 FIND_MATCHWRAP = "FindMatchWrap"
29 FIND_MATCHUPDOWN = "FindMatchUpDown"
30
31 FIND_SYNTAXERROR = -2
32
33 SPACE = 10
34 HALF_SPACE = 5
35
36
37 #----------------------------------------------------------------------------
38 # Classes
39 #----------------------------------------------------------------------------
40
41 class FindService(wx.lib.pydocview.DocService):
42
43 #----------------------------------------------------------------------------
44 # Constants
45 #----------------------------------------------------------------------------
46 FIND_ID = wx.NewId() # for bringing up Find dialog box
47 FINDONE_ID = wx.NewId() # for doing Find
48 FIND_PREVIOUS_ID = wx.NewId() # for doing Find Next
49 FIND_NEXT_ID = wx.NewId() # for doing Find Prev
50 REPLACE_ID = wx.NewId() # for bringing up Replace dialog box
51 REPLACEONE_ID = wx.NewId() # for doing a Replace
52 REPLACEALL_ID = wx.NewId() # for doing Replace All
53 GOTO_LINE_ID = wx.NewId() # for bringing up Goto dialog box
54
55 # Extending bitmasks: wx.FR_WHOLEWORD, wx.FR_MATCHCASE, and wx.FR_DOWN
56 FR_REGEXP = max([wx.FR_WHOLEWORD, wx.FR_MATCHCASE, wx.FR_DOWN]) << 1
57 FR_WRAP = FR_REGEXP << 1
58
59
60 def __init__(self):
61 self._replaceDialog = None
62 self._findDialog = None
63 self._findReplaceData = wx.FindReplaceData()
64 self._findReplaceData.SetFlags(wx.FR_DOWN)
65
66
67 def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None):
68 """ Install Find Service Menu Items """
69 editMenu = menuBar.GetMenu(menuBar.FindMenu(_("&Edit")))
70 editMenu.AppendSeparator()
71 editMenu.Append(FindService.FIND_ID, _("&Find...\tCtrl+F"), _("Finds the specified text"))
72 wx.EVT_MENU(frame, FindService.FIND_ID, frame.ProcessEvent)
73 wx.EVT_UPDATE_UI(frame, FindService.FIND_ID, frame.ProcessUpdateUIEvent)
74 editMenu.Append(FindService.FIND_PREVIOUS_ID, _("Find &Previous\tShift+F3"), _("Finds the specified text"))
75 wx.EVT_MENU(frame, FindService.FIND_PREVIOUS_ID, frame.ProcessEvent)
76 wx.EVT_UPDATE_UI(frame, FindService.FIND_PREVIOUS_ID, frame.ProcessUpdateUIEvent)
77 editMenu.Append(FindService.FIND_NEXT_ID, _("Find &Next\tF3"), _("Finds the specified text"))
78 wx.EVT_MENU(frame, FindService.FIND_NEXT_ID, frame.ProcessEvent)
79 wx.EVT_UPDATE_UI(frame, FindService.FIND_NEXT_ID, frame.ProcessUpdateUIEvent)
80 editMenu.Append(FindService.REPLACE_ID, _("R&eplace...\tCtrl+H"), _("Replaces specific text with different text"))
81 wx.EVT_MENU(frame, FindService.REPLACE_ID, frame.ProcessEvent)
82 wx.EVT_UPDATE_UI(frame, FindService.REPLACE_ID, frame.ProcessUpdateUIEvent)
83 editMenu.Append(FindService.GOTO_LINE_ID, _("&Go to Line...\tCtrl+G"), _("Goes to a certain line in the file"))
84 wx.EVT_MENU(frame, FindService.GOTO_LINE_ID, frame.ProcessEvent)
85 wx.EVT_UPDATE_UI(frame, FindService.GOTO_LINE_ID, frame.ProcessUpdateUIEvent)
86
87 # wxBug: wxToolBar::GetToolPos doesn't exist, need it to find cut tool and then insert find in front of it.
88 toolBar.InsertTool(6, FindService.FIND_ID, getFindBitmap(), shortHelpString = _("Find"), longHelpString = _("Finds the specified text"))
89 toolBar.InsertSeparator(6)
90 toolBar.Realize()
91
92 frame.Bind(wx.EVT_FIND, frame.ProcessEvent)
93 frame.Bind(wx.EVT_FIND_NEXT, frame.ProcessEvent)
94 frame.Bind(wx.EVT_FIND_REPLACE, frame.ProcessEvent)
95 frame.Bind(wx.EVT_FIND_REPLACE_ALL, frame.ProcessEvent)
96
97
98 def ProcessUpdateUIEvent(self, event):
99 id = event.GetId()
100 if (id == FindService.FIND_ID
101 or id == FindService.FIND_PREVIOUS_ID
102 or id == FindService.FIND_NEXT_ID
103 or id == FindService.REPLACE_ID
104 or id == FindService.GOTO_LINE_ID):
105 event.Enable(False)
106 return True
107 else:
108 return False
109
110
111 def ShowFindReplaceDialog(self, findString="", replace = False):
112 """ Display find/replace dialog box.
113
114 Parameters: findString is the default value shown in the find/replace dialog input field.
115 If replace is True, the replace dialog box is shown, otherwise only the find dialog box is shown.
116 """
117 if replace:
118 if self._findDialog != None:
119 # No reason to have both find and replace dialogs up at the same time
120 self._findDialog.DoClose()
121 self._findDialog = None
122
123 self._replaceDialog = FindReplaceDialog(self.GetDocumentManager().FindSuitableParent(), -1, _("Replace"), size=(320,200), findString=findString)
124 self._replaceDialog.Show(True)
125 else:
126 if self._replaceDialog != None:
127 # No reason to have both find and replace dialogs up at the same time
128 self._replaceDialog.DoClose()
129 self._replaceDialog = None
130
131 self._findDialog = FindDialog(self.GetDocumentManager().FindSuitableParent(), -1, _("Find"), size=(320,200), findString=findString)
132 self._findDialog.Show(True)
133
134
135
136 def OnFindClose(self, event):
137 """ Cleanup handles when find/replace dialog is closed """
138 if self._findDialog != None:
139 self._findDialog = None
140 elif self._replaceDialog != None:
141 self._replaceDialog = None
142
143
144 def GetCurrentDialog(self):
145 """ return handle to either the find or replace dialog """
146 if self._findDialog != None:
147 return self._findDialog
148 return self._replaceDialog
149
150
151 def GetLineNumber(self, parent):
152 """ Display Goto Line Number dialog box """
153 line = -1
154 dialog = wx.TextEntryDialog(parent, _("Enter line number to go to:"), _("Go to Line"))
155 if dialog.ShowModal() == wx.ID_OK:
156 try:
157 line = int(dialog.GetValue())
158 if line > 65535:
159 line = 65535
160 except:
161 pass
162 dialog.Destroy()
163 # This one is ugly: wx.GetNumberFromUser("", _("Enter line number to go to:"), _("Go to Line"), 1, min = 1, max = 65535, parent = parent)
164 return line
165
166
167 def DoFind(self, findString, replaceString, text, startLoc, endLoc, down, matchCase, wholeWord, regExpr = False, replace = False, replaceAll = False, wrap = False):
168 """ Do the actual work of the find/replace.
169
170 Returns the tuple (count, start, end, newText).
171 count = number of string replacements
172 start = start position of found string
173 end = end position of found string
174 newText = new replaced text
175 """
176 flags = 0
177 if regExpr:
178 pattern = findString
179 else:
180 pattern = re.escape(findString) # Treat the strings as a literal string
181 if not matchCase:
182 flags = re.IGNORECASE
183 if wholeWord:
184 pattern = r"\b%s\b" % pattern
185
186 try:
187 reg = re.compile(pattern, flags)
188 except:
189 # syntax error of some sort
190 import sys
191 msgTitle = wx.GetApp().GetAppName()
192 if not msgTitle:
193 msgTitle = _("Regular Expression Search")
194 wx.MessageBox(_("Invalid regular expression \"%s\". %s") % (pattern, sys.exc_value),
195 msgTitle,
196 wx.OK | wx.ICON_EXCLAMATION,
197 self.GetView())
198 return FIND_SYNTAXERROR, None, None, None
199
200 if replaceAll:
201 newText, count = reg.subn(replaceString, text)
202 if count == 0:
203 return -1, None, None, None
204 else:
205 return count, None, None, newText
206
207 start = -1
208 if down:
209 match = reg.search(text, endLoc)
210 if match == None:
211 if wrap: # try again, but this time from top of file
212 match = reg.search(text, 0)
213 if match == None:
214 return -1, None, None, None
215 else:
216 return -1, None, None, None
217 start = match.start()
218 end = match.end()
219 else:
220 match = reg.search(text)
221 if match == None:
222 return -1, None, None, None
223 found = None
224 i, j = match.span()
225 while i < startLoc and j <= startLoc:
226 found = match
227 if i == j:
228 j = j + 1
229 match = reg.search(text, j)
230 if match == None:
231 break
232 i, j = match.span()
233 if found == None:
234 if wrap: # try again, but this time from bottom of file
235 match = reg.search(text, startLoc)
236 if match == None:
237 return -1, None, None, None
238 found = None
239 i, j = match.span()
240 end = len(text)
241 while i < end and j <= end:
242 found = match
243 if i == j:
244 j = j + 1
245 match = reg.search(text, j)
246 if match == None:
247 break
248 i, j = match.span()
249 if found == None:
250 return -1, None, None, None
251 else:
252 return -1, None, None, None
253 start = found.start()
254 end = found.end()
255
256 if replace and start != -1:
257 newText, count = reg.subn(replaceString, text, 1)
258 return count, start, end, newText
259
260 return 0, start, end, None
261
262
263 def SaveFindConfig(self, findString, wholeWord, matchCase, regExpr = None, wrap = None, upDown = None, replaceString = None):
264 """ Save find/replace patterns and search flags to registry.
265
266 findString = search pattern
267 wholeWord = match whole word only
268 matchCase = match case
269 regExpr = use regular expressions in search pattern
270 wrap = return to top/bottom of file on search
271 upDown = search up or down from current cursor position
272 replaceString = replace string
273 """
274 config = wx.ConfigBase_Get()
275
276 config.Write(FIND_MATCHPATTERN, findString)
277 config.WriteInt(FIND_MATCHCASE, matchCase)
278 config.WriteInt(FIND_MATCHWHOLEWORD, wholeWord)
279 if replaceString != None:
280 config.Write(FIND_MATCHREPLACE, replaceString)
281 if regExpr != None:
282 config.WriteInt(FIND_MATCHREGEXPR, regExpr)
283 if wrap != None:
284 config.WriteInt(FIND_MATCHWRAP, wrap)
285 if upDown != None:
286 config.WriteInt(FIND_MATCHUPDOWN, upDown)
287
288
289 def GetFindString(self):
290 """ Load the search pattern from registry """
291 return wx.ConfigBase_Get().Read(FIND_MATCHPATTERN, "")
292
293
294 def GetReplaceString(self):
295 """ Load the replace pattern from registry """
296 return wx.ConfigBase_Get().Read(FIND_MATCHREPLACE, "")
297
298
299 def GetFlags(self):
300 """ Load search parameters from registry """
301 config = wx.ConfigBase_Get()
302
303 flags = 0
304 if config.ReadInt(FIND_MATCHWHOLEWORD, False):
305 flags = flags | wx.FR_WHOLEWORD
306 if config.ReadInt(FIND_MATCHCASE, False):
307 flags = flags | wx.FR_MATCHCASE
308 if config.ReadInt(FIND_MATCHUPDOWN, False):
309 flags = flags | wx.FR_DOWN
310 if config.ReadInt(FIND_MATCHREGEXPR, False):
311 flags = flags | FindService.FR_REGEXP
312 if config.ReadInt(FIND_MATCHWRAP, False):
313 flags = flags | FindService.FR_WRAP
314 return flags
315
316
317 class FindDialog(wx.Dialog):
318 """ Find Dialog with regular expression matching and wrap to top/bottom of file. """
319
320 def __init__(self, parent, id, title, size, findString=None):
321 wx.Dialog.__init__(self, parent, id, title, size=size)
322
323 config = wx.ConfigBase_Get()
324 borderSizer = wx.BoxSizer(wx.VERTICAL)
325 gridSizer = wx.GridBagSizer(SPACE, SPACE)
326
327 lineSizer = wx.BoxSizer(wx.HORIZONTAL)
328 lineSizer.Add(wx.StaticText(self, -1, _("Find what:")), 0, wx.ALIGN_CENTER_VERTICAL|wx.RIGHT, SPACE)
329 if not findString:
330 findString = config.Read(FIND_MATCHPATTERN, "")
331 self._findCtrl = wx.TextCtrl(self, -1, findString, size=(200,-1))
332 lineSizer.Add(self._findCtrl, 0)
333 gridSizer.Add(lineSizer, pos=(0,0), span=(1,2))
334 choiceSizer = wx.BoxSizer(wx.VERTICAL)
335 self._wholeWordCtrl = wx.CheckBox(self, -1, _("Match whole word only"))
336 self._wholeWordCtrl.SetValue(config.ReadInt(FIND_MATCHWHOLEWORD, False))
337 self._matchCaseCtrl = wx.CheckBox(self, -1, _("Match case"))
338 self._matchCaseCtrl.SetValue(config.ReadInt(FIND_MATCHCASE, False))
339 self._regExprCtrl = wx.CheckBox(self, -1, _("Regular expression"))
340 self._regExprCtrl.SetValue(config.ReadInt(FIND_MATCHREGEXPR, False))
341 self._wrapCtrl = wx.CheckBox(self, -1, _("Wrap"))
342 self._wrapCtrl.SetValue(config.ReadInt(FIND_MATCHWRAP, False))
343 choiceSizer.Add(self._wholeWordCtrl, 0, wx.BOTTOM, SPACE)
344 choiceSizer.Add(self._matchCaseCtrl, 0, wx.BOTTOM, SPACE)
345 choiceSizer.Add(self._regExprCtrl, 0, wx.BOTTOM, SPACE)
346 choiceSizer.Add(self._wrapCtrl, 0)
347 gridSizer.Add(choiceSizer, pos=(1,0), span=(2,1))
348
349 self._radioBox = wx.RadioBox(self, -1, _("Direction"), choices = ["Up", "Down"])
350 self._radioBox.SetSelection(config.ReadInt(FIND_MATCHUPDOWN, 1))
351 gridSizer.Add(self._radioBox, pos=(1,1), span=(2,1))
352
353 buttonSizer = wx.BoxSizer(wx.VERTICAL)
354 findBtn = wx.Button(self, FindService.FINDONE_ID, _("Find Next"))
355 findBtn.SetDefault()
356 wx.EVT_BUTTON(self, FindService.FINDONE_ID, self.OnActionEvent)
357 cancelBtn = wx.Button(self, wx.ID_CANCEL)
358 wx.EVT_BUTTON(self, wx.ID_CANCEL, self.OnClose)
359 buttonSizer.Add(findBtn, 0, wx.BOTTOM, HALF_SPACE)
360 buttonSizer.Add(cancelBtn, 0)
361 gridSizer.Add(buttonSizer, pos=(0,2), span=(3,1))
362
363 borderSizer.Add(gridSizer, 0, wx.ALL, SPACE)
364
365 self.Bind(wx.EVT_CLOSE, self.OnClose)
366
367 self.SetSizer(borderSizer)
368 self.Fit()
369 self._findCtrl.SetFocus()
370
371 def SaveConfig(self):
372 """ Save find patterns and search flags to registry. """
373 findService = wx.GetApp().GetService(FindService)
374 if findService:
375 findService.SaveFindConfig(self._findCtrl.GetValue(),
376 self._wholeWordCtrl.IsChecked(),
377 self._matchCaseCtrl.IsChecked(),
378 self._regExprCtrl.IsChecked(),
379 self._wrapCtrl.IsChecked(),
380 self._radioBox.GetSelection(),
381 )
382
383
384 def DoClose(self):
385 self.SaveConfig()
386 self.Destroy()
387
388
389 def OnClose(self, event):
390 findService = wx.GetApp().GetService(FindService)
391 if findService:
392 findService.OnFindClose(event)
393 self.DoClose()
394
395
396 def OnActionEvent(self, event):
397 self.SaveConfig()
398
399 if wx.GetApp().GetDocumentManager().GetFlags() & wx.lib.docview.DOC_MDI:
400 if wx.GetApp().GetTopWindow().ProcessEvent(event):
401 return True
402 else:
403 view = wx.GetApp().GetDocumentManager().GetLastActiveView()
404 if view and view.ProcessEvent(event):
405 return True
406 return False
407
408
409 class FindReplaceDialog(FindDialog):
410 """ Find/Replace Dialog with regular expression matching and wrap to top/bottom of file. """
411
412 def __init__(self, parent, id, title, size, findString=None):
413 wx.Dialog.__init__(self, parent, id, title, size=size)
414
415 config = wx.ConfigBase_Get()
416 borderSizer = wx.BoxSizer(wx.VERTICAL)
417 gridSizer = wx.GridBagSizer(SPACE, SPACE)
418
419 gridSizer2 = wx.GridBagSizer(SPACE, SPACE)
420 gridSizer2.Add(wx.StaticText(self, -1, _("Find what:")), flag=wx.ALIGN_CENTER_VERTICAL, pos=(0,0))
421 if not findString:
422 findString = config.Read(FIND_MATCHPATTERN, "")
423 self._findCtrl = wx.TextCtrl(self, -1, findString, size=(200,-1))
424 gridSizer2.Add(self._findCtrl, pos=(0,1))
425 gridSizer2.Add(wx.StaticText(self, -1, _("Replace with:")), flag=wx.ALIGN_CENTER_VERTICAL, pos=(1,0))
426 self._replaceCtrl = wx.TextCtrl(self, -1, config.Read(FIND_MATCHREPLACE, ""), size=(200,-1))
427 gridSizer2.Add(self._replaceCtrl, pos=(1,1))
428 gridSizer.Add(gridSizer2, pos=(0,0), span=(1,2))
429 choiceSizer = wx.BoxSizer(wx.VERTICAL)
430 self._wholeWordCtrl = wx.CheckBox(self, -1, _("Match whole word only"))
431 self._wholeWordCtrl.SetValue(config.ReadInt(FIND_MATCHWHOLEWORD, False))
432 self._matchCaseCtrl = wx.CheckBox(self, -1, _("Match case"))
433 self._matchCaseCtrl.SetValue(config.ReadInt(FIND_MATCHCASE, False))
434 self._regExprCtrl = wx.CheckBox(self, -1, _("Regular expression"))
435 self._regExprCtrl.SetValue(config.ReadInt(FIND_MATCHREGEXPR, False))
436 self._wrapCtrl = wx.CheckBox(self, -1, _("Wrap"))
437 self._wrapCtrl.SetValue(config.ReadInt(FIND_MATCHWRAP, False))
438 choiceSizer.Add(self._wholeWordCtrl, 0, wx.BOTTOM, SPACE)
439 choiceSizer.Add(self._matchCaseCtrl, 0, wx.BOTTOM, SPACE)
440 choiceSizer.Add(self._regExprCtrl, 0, wx.BOTTOM, SPACE)
441 choiceSizer.Add(self._wrapCtrl, 0)
442 gridSizer.Add(choiceSizer, pos=(1,0), span=(2,1))
443
444 self._radioBox = wx.RadioBox(self, -1, _("Direction"), choices = ["Up", "Down"])
445 self._radioBox.SetSelection(config.ReadInt(FIND_MATCHUPDOWN, 1))
446 gridSizer.Add(self._radioBox, pos=(1,1), span=(2,1))
447
448 buttonSizer = wx.BoxSizer(wx.VERTICAL)
449 findBtn = wx.Button(self, FindService.FINDONE_ID, _("Find Next"))
450 findBtn.SetDefault()
451 wx.EVT_BUTTON(self, FindService.FINDONE_ID, self.OnActionEvent)
452 cancelBtn = wx.Button(self, wx.ID_CANCEL)
453 wx.EVT_BUTTON(self, wx.ID_CANCEL, self.OnClose)
454 replaceBtn = wx.Button(self, FindService.REPLACEONE_ID, _("Replace"))
455 wx.EVT_BUTTON(self, FindService.REPLACEONE_ID, self.OnActionEvent)
456 replaceAllBtn = wx.Button(self, FindService.REPLACEALL_ID, _("Replace All"))
457 wx.EVT_BUTTON(self, FindService.REPLACEALL_ID, self.OnActionEvent)
458 buttonSizer.Add(findBtn, 0, wx.BOTTOM, HALF_SPACE)
459 buttonSizer.Add(replaceBtn, 0, wx.BOTTOM, HALF_SPACE)
460 buttonSizer.Add(replaceAllBtn, 0, wx.BOTTOM, HALF_SPACE)
461 buttonSizer.Add(cancelBtn, 0)
462 gridSizer.Add(buttonSizer, pos=(0,2), span=(3,1))
463
464 borderSizer.Add(gridSizer, 0, wx.ALL, SPACE)
465
466 self.Bind(wx.EVT_CLOSE, self.OnClose)
467
468 self.SetSizer(borderSizer)
469 self.Fit()
470 self._findCtrl.SetFocus()
471
472
473 def SaveConfig(self):
474 """ Save find/replace patterns and search flags to registry. """
475 findService = wx.GetApp().GetService(FindService)
476 if findService:
477 findService.SaveFindConfig(self._findCtrl.GetValue(),
478 self._wholeWordCtrl.IsChecked(),
479 self._matchCaseCtrl.IsChecked(),
480 self._regExprCtrl.IsChecked(),
481 self._wrapCtrl.IsChecked(),
482 self._radioBox.GetSelection(),
483 self._replaceCtrl.GetValue()
484 )
485
486
487 #----------------------------------------------------------------------------
488 # Menu Bitmaps - generated by encode_bitmaps.py
489 #----------------------------------------------------------------------------
490 from wx import ImageFromStream, BitmapFromImage
491 import cStringIO
492
493
494 def getFindData():
495 return \
496 '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\
497 \x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\
498 \x00\x00\x81IDAT8\x8d\xa5S\xc1\x16\xc0\x10\x0ckk\xff\xff\xc7d\x87\xad^U\r\
499 \x93S\xe5U$\n\xb3$:\xc1e\x17(\x19Z\xb3$\x9e\xf1DD\xe2\x15\x01x\xea\x93\xef\
500 \x04\x989\xea\x1b\xf2U\xc0\xda\xb4\xeb\x11\x1f:\xd8\xb5\xff8\x93\xd4\xa9\xae\
501 @/S\xaaUwJ3\x85\xc0\x81\xee\xeb.q\x17C\x81\xd5XU \x1a\x93\xc6\x18\x8d\x90\
502 \xe8}\x89\x00\x9a&\x9b_k\x94\x0c\xdf\xd78\xf8\x0b\x99Y\xb4\x08c\x9e\xfe\xc6\
503 \xe3\x087\xf9\xd0D\x180\xf1#\x8e\x00\x00\x00\x00IEND\xaeB`\x82'
504
505
506 def getFindBitmap():
507 return BitmapFromImage(getFindImage())
508
509
510 def getFindImage():
511 stream = cStringIO.StringIO(getFindData())
512 return ImageFromStream(stream)
513