]> git.saurik.com Git - wxWidgets.git/blob - wxPython/samples/pydocview/FindService.py
Updates to doc/view modules and sample apps from ActiveGrid.
[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 event.Enable(False)
102 return True
103 elif id == FindService.FIND_PREVIOUS_ID:
104 event.Enable(False)
105 return True
106 elif id == FindService.FIND_NEXT_ID:
107 event.Enable(False)
108 return True
109 elif id == FindService.REPLACE_ID:
110 event.Enable(False)
111 return True
112 elif id == FindService.GOTO_LINE_ID:
113 event.Enable(False)
114 return True
115 else:
116 return False
117
118
119 def ShowFindReplaceDialog(self, findString="", replace = False):
120 """ Display find/replace dialog box.
121
122 Parameters: findString is the default value shown in the find/replace dialog input field.
123 If replace is True, the replace dialog box is shown, otherwise only the find dialog box is shown.
124 """
125 if replace:
126 if self._findDialog != None:
127 # No reason to have both find and replace dialogs up at the same time
128 self._findDialog.DoClose()
129 self._findDialog = None
130
131 self._replaceDialog = FindReplaceDialog(self.GetDocumentManager().FindSuitableParent(), -1, _("Replace"), size=(320,200), findString=findString)
132 self._replaceDialog.Show(True)
133 else:
134 if self._replaceDialog != None:
135 # No reason to have both find and replace dialogs up at the same time
136 self._replaceDialog.DoClose()
137 self._replaceDialog = None
138
139 self._findDialog = FindDialog(self.GetDocumentManager().FindSuitableParent(), -1, _("Find"), size=(320,200), findString=findString)
140 self._findDialog.Show(True)
141
142
143
144 def OnFindClose(self, event):
145 """ Cleanup handles when find/replace dialog is closed """
146 if self._findDialog != None:
147 self._findDialog = None
148 elif self._replaceDialog != None:
149 self._replaceDialog = None
150
151
152 def GetCurrentDialog(self):
153 """ return handle to either the find or replace dialog """
154 if self._findDialog != None:
155 return self._findDialog
156 return self._replaceDialog
157
158
159 def GetLineNumber(self, parent):
160 """ Display Goto Line Number dialog box """
161 line = -1
162 dialog = wx.TextEntryDialog(parent, _("Enter line number to go to:"), _("Go to Line"))
163 if dialog.ShowModal() == wx.ID_OK:
164 try:
165 line = int(dialog.GetValue())
166 if line > 65535:
167 line = 65535
168 except:
169 pass
170 dialog.Destroy()
171 # This one is ugly: wx.GetNumberFromUser("", _("Enter line number to go to:"), _("Go to Line"), 1, min = 1, max = 65535, parent = parent)
172 return line
173
174
175 def DoFind(self, findString, replaceString, text, startLoc, endLoc, down, matchCase, wholeWord, regExpr = False, replace = False, replaceAll = False, wrap = False):
176 """ Do the actual work of the find/replace.
177
178 Returns the tuple (count, start, end, newText).
179 count = number of string replacements
180 start = start position of found string
181 end = end position of found string
182 newText = new replaced text
183 """
184 flags = 0
185 if regExpr:
186 pattern = findString
187 else:
188 pattern = re.escape(findString) # Treat the strings as a literal string
189 if not matchCase:
190 flags = re.IGNORECASE
191 if wholeWord:
192 pattern = r"\b%s\b" % pattern
193
194 try:
195 reg = re.compile(pattern, flags)
196 except:
197 # syntax error of some sort
198 import sys
199 msgTitle = wx.GetApp().GetAppName()
200 if not msgTitle:
201 msgTitle = _("Regular Expression Search")
202 wx.MessageBox(_("Invalid regular expression \"%s\". %s") % (pattern, sys.exc_value),
203 msgTitle,
204 wx.OK | wx.ICON_EXCLAMATION,
205 self.GetView())
206 return FIND_SYNTAXERROR, None, None, None
207
208 if replaceAll:
209 newText, count = reg.subn(replaceString, text)
210 if count == 0:
211 return -1, None, None, None
212 else:
213 return count, None, None, newText
214
215 start = -1
216 if down:
217 match = reg.search(text, endLoc)
218 if match == None:
219 if wrap: # try again, but this time from top of file
220 match = reg.search(text, 0)
221 if match == None:
222 return -1, None, None, None
223 else:
224 return -1, None, None, None
225 start = match.start()
226 end = match.end()
227 else:
228 match = reg.search(text)
229 if match == None:
230 return -1, None, None, None
231 found = None
232 i, j = match.span()
233 while i < startLoc and j <= startLoc:
234 found = match
235 if i == j:
236 j = j + 1
237 match = reg.search(text, j)
238 if match == None:
239 break
240 i, j = match.span()
241 if found == None:
242 if wrap: # try again, but this time from bottom of file
243 match = reg.search(text, startLoc)
244 if match == None:
245 return -1, None, None, None
246 found = None
247 i, j = match.span()
248 end = len(text)
249 while i < end and j <= end:
250 found = match
251 if i == j:
252 j = j + 1
253 match = reg.search(text, j)
254 if match == None:
255 break
256 i, j = match.span()
257 if found == None:
258 return -1, None, None, None
259 else:
260 return -1, None, None, None
261 start = found.start()
262 end = found.end()
263
264 if replace and start != -1:
265 newText, count = reg.subn(replaceString, text, 1)
266 return count, start, end, newText
267
268 return 0, start, end, None
269
270
271 def SaveFindConfig(self, findString, wholeWord, matchCase, regExpr = None, wrap = None, upDown = None, replaceString = None):
272 """ Save find/replace patterns and search flags to registry.
273
274 findString = search pattern
275 wholeWord = match whole word only
276 matchCase = match case
277 regExpr = use regular expressions in search pattern
278 wrap = return to top/bottom of file on search
279 upDown = search up or down from current cursor position
280 replaceString = replace string
281 """
282 config = wx.ConfigBase_Get()
283
284 config.Write(FIND_MATCHPATTERN, findString)
285 config.WriteInt(FIND_MATCHCASE, matchCase)
286 config.WriteInt(FIND_MATCHWHOLEWORD, wholeWord)
287 if replaceString != None:
288 config.Write(FIND_MATCHREPLACE, replaceString)
289 if regExpr != None:
290 config.WriteInt(FIND_MATCHREGEXPR, regExpr)
291 if wrap != None:
292 config.WriteInt(FIND_MATCHWRAP, wrap)
293 if upDown != None:
294 config.WriteInt(FIND_MATCHUPDOWN, upDown)
295
296
297 def GetFindString(self):
298 """ Load the search pattern from registry """
299 return wx.ConfigBase_Get().Read(FIND_MATCHPATTERN, "")
300
301
302 def GetReplaceString(self):
303 """ Load the replace pattern from registry """
304 return wx.ConfigBase_Get().Read(FIND_MATCHREPLACE, "")
305
306
307 def GetFlags(self):
308 """ Load search parameters from registry """
309 config = wx.ConfigBase_Get()
310
311 flags = 0
312 if config.ReadInt(FIND_MATCHWHOLEWORD, False):
313 flags = flags | wx.FR_WHOLEWORD
314 if config.ReadInt(FIND_MATCHCASE, False):
315 flags = flags | wx.FR_MATCHCASE
316 if config.ReadInt(FIND_MATCHUPDOWN, False):
317 flags = flags | wx.FR_DOWN
318 if config.ReadInt(FIND_MATCHREGEXPR, False):
319 flags = flags | FindService.FR_REGEXP
320 if config.ReadInt(FIND_MATCHWRAP, False):
321 flags = flags | FindService.FR_WRAP
322 return flags
323
324
325 class FindDialog(wx.Dialog):
326 """ Find Dialog with regular expression matching and wrap to top/bottom of file. """
327
328 def __init__(self, parent, id, title, size, findString=None):
329 wx.Dialog.__init__(self, parent, id, title, size=size)
330
331 config = wx.ConfigBase_Get()
332 borderSizer = wx.BoxSizer(wx.VERTICAL)
333 gridSizer = wx.GridBagSizer(SPACE, SPACE)
334
335 lineSizer = wx.BoxSizer(wx.HORIZONTAL)
336 lineSizer.Add(wx.StaticText(self, -1, _("Find what:")), 0, wx.ALIGN_CENTER_VERTICAL|wx.RIGHT, SPACE)
337 if not findString:
338 findString = config.Read(FIND_MATCHPATTERN, "")
339 self._findCtrl = wx.TextCtrl(self, -1, findString, size=(200,-1))
340 lineSizer.Add(self._findCtrl, 0)
341 gridSizer.Add(lineSizer, pos=(0,0), span=(1,2))
342 choiceSizer = wx.BoxSizer(wx.VERTICAL)
343 self._wholeWordCtrl = wx.CheckBox(self, -1, _("Match whole word only"))
344 self._wholeWordCtrl.SetValue(config.ReadInt(FIND_MATCHWHOLEWORD, False))
345 self._matchCaseCtrl = wx.CheckBox(self, -1, _("Match case"))
346 self._matchCaseCtrl.SetValue(config.ReadInt(FIND_MATCHCASE, False))
347 self._regExprCtrl = wx.CheckBox(self, -1, _("Regular expression"))
348 self._regExprCtrl.SetValue(config.ReadInt(FIND_MATCHREGEXPR, False))
349 self._wrapCtrl = wx.CheckBox(self, -1, _("Wrap"))
350 self._wrapCtrl.SetValue(config.ReadInt(FIND_MATCHWRAP, False))
351 choiceSizer.Add(self._wholeWordCtrl, 0, wx.BOTTOM, SPACE)
352 choiceSizer.Add(self._matchCaseCtrl, 0, wx.BOTTOM, SPACE)
353 choiceSizer.Add(self._regExprCtrl, 0, wx.BOTTOM, SPACE)
354 choiceSizer.Add(self._wrapCtrl, 0)
355 gridSizer.Add(choiceSizer, pos=(1,0), span=(2,1))
356
357 self._radioBox = wx.RadioBox(self, -1, _("Direction"), choices = ["Up", "Down"])
358 self._radioBox.SetSelection(config.ReadInt(FIND_MATCHUPDOWN, 1))
359 gridSizer.Add(self._radioBox, pos=(1,1), span=(2,1))
360
361 buttonSizer = wx.BoxSizer(wx.VERTICAL)
362 findBtn = wx.Button(self, FindService.FINDONE_ID, _("Find Next"))
363 findBtn.SetDefault()
364 wx.EVT_BUTTON(self, FindService.FINDONE_ID, self.OnActionEvent)
365 cancelBtn = wx.Button(self, wx.ID_CANCEL, _("Cancel"))
366 wx.EVT_BUTTON(self, wx.ID_CANCEL, self.OnClose)
367 buttonSizer.Add(findBtn, 0, wx.BOTTOM, HALF_SPACE)
368 buttonSizer.Add(cancelBtn, 0)
369 gridSizer.Add(buttonSizer, pos=(0,2), span=(3,1))
370
371 borderSizer.Add(gridSizer, 0, wx.ALL, SPACE)
372
373 self.Bind(wx.EVT_CLOSE, self.OnClose)
374
375 self.SetSizer(borderSizer)
376 self.Fit()
377 self._findCtrl.SetFocus()
378
379 def SaveConfig(self):
380 """ Save find patterns and search flags to registry. """
381 findService = wx.GetApp().GetService(FindService)
382 if findService:
383 findService.SaveFindConfig(self._findCtrl.GetValue(),
384 self._wholeWordCtrl.IsChecked(),
385 self._matchCaseCtrl.IsChecked(),
386 self._regExprCtrl.IsChecked(),
387 self._wrapCtrl.IsChecked(),
388 self._radioBox.GetSelection(),
389 )
390
391
392 def DoClose(self):
393 self.SaveConfig()
394 self.Destroy()
395
396
397 def OnClose(self, event):
398 findService = wx.GetApp().GetService(FindService)
399 if findService:
400 findService.OnFindClose(event)
401 self.DoClose()
402
403
404 def OnActionEvent(self, event):
405 self.SaveConfig()
406
407 if wx.GetApp().GetDocumentManager().GetFlags() & wx.lib.docview.DOC_MDI:
408 if wx.GetApp().GetTopWindow().ProcessEvent(event):
409 return True
410 else:
411 view = wx.GetApp().GetDocumentManager().GetLastActiveView()
412 if view and view.ProcessEvent(event):
413 return True
414 return False
415
416
417 class FindReplaceDialog(FindDialog):
418 """ Find/Replace Dialog with regular expression matching and wrap to top/bottom of file. """
419
420 def __init__(self, parent, id, title, size, findString=None):
421 wx.Dialog.__init__(self, parent, id, title, size=size)
422
423 config = wx.ConfigBase_Get()
424 borderSizer = wx.BoxSizer(wx.VERTICAL)
425 gridSizer = wx.GridBagSizer(SPACE, SPACE)
426
427 gridSizer2 = wx.GridBagSizer(SPACE, SPACE)
428 gridSizer2.Add(wx.StaticText(self, -1, _("Find what:")), flag=wx.ALIGN_CENTER_VERTICAL, pos=(0,0))
429 if not findString:
430 findString = config.Read(FIND_MATCHPATTERN, "")
431 self._findCtrl = wx.TextCtrl(self, -1, findString, size=(200,-1))
432 gridSizer2.Add(self._findCtrl, pos=(0,1))
433 gridSizer2.Add(wx.StaticText(self, -1, _("Replace with:")), flag=wx.ALIGN_CENTER_VERTICAL, pos=(1,0))
434 self._replaceCtrl = wx.TextCtrl(self, -1, config.Read(FIND_MATCHREPLACE, ""), size=(200,-1))
435 gridSizer2.Add(self._replaceCtrl, pos=(1,1))
436 gridSizer.Add(gridSizer2, pos=(0,0), span=(1,2))
437 choiceSizer = wx.BoxSizer(wx.VERTICAL)
438 self._wholeWordCtrl = wx.CheckBox(self, -1, _("Match whole word only"))
439 self._wholeWordCtrl.SetValue(config.ReadInt(FIND_MATCHWHOLEWORD, False))
440 self._matchCaseCtrl = wx.CheckBox(self, -1, _("Match case"))
441 self._matchCaseCtrl.SetValue(config.ReadInt(FIND_MATCHCASE, False))
442 self._regExprCtrl = wx.CheckBox(self, -1, _("Regular expression"))
443 self._regExprCtrl.SetValue(config.ReadInt(FIND_MATCHREGEXPR, False))
444 self._wrapCtrl = wx.CheckBox(self, -1, _("Wrap"))
445 self._wrapCtrl.SetValue(config.ReadInt(FIND_MATCHWRAP, False))
446 choiceSizer.Add(self._wholeWordCtrl, 0, wx.BOTTOM, SPACE)
447 choiceSizer.Add(self._matchCaseCtrl, 0, wx.BOTTOM, SPACE)
448 choiceSizer.Add(self._regExprCtrl, 0, wx.BOTTOM, SPACE)
449 choiceSizer.Add(self._wrapCtrl, 0)
450 gridSizer.Add(choiceSizer, pos=(1,0), span=(2,1))
451
452 self._radioBox = wx.RadioBox(self, -1, _("Direction"), choices = ["Up", "Down"])
453 self._radioBox.SetSelection(config.ReadInt(FIND_MATCHUPDOWN, 1))
454 gridSizer.Add(self._radioBox, pos=(1,1), span=(2,1))
455
456 buttonSizer = wx.BoxSizer(wx.VERTICAL)
457 findBtn = wx.Button(self, FindService.FINDONE_ID, _("Find Next"))
458 findBtn.SetDefault()
459 wx.EVT_BUTTON(self, FindService.FINDONE_ID, self.OnActionEvent)
460 cancelBtn = wx.Button(self, wx.ID_CANCEL, _("Cancel"))
461 wx.EVT_BUTTON(self, wx.ID_CANCEL, self.OnClose)
462 replaceBtn = wx.Button(self, FindService.REPLACEONE_ID, _("Replace"))
463 wx.EVT_BUTTON(self, FindService.REPLACEONE_ID, self.OnActionEvent)
464 replaceAllBtn = wx.Button(self, FindService.REPLACEALL_ID, _("Replace All"))
465 wx.EVT_BUTTON(self, FindService.REPLACEALL_ID, self.OnActionEvent)
466 buttonSizer.Add(findBtn, 0, wx.BOTTOM, HALF_SPACE)
467 buttonSizer.Add(replaceBtn, 0, wx.BOTTOM, HALF_SPACE)
468 buttonSizer.Add(replaceAllBtn, 0, wx.BOTTOM, HALF_SPACE)
469 buttonSizer.Add(cancelBtn, 0)
470 gridSizer.Add(buttonSizer, pos=(0,2), span=(3,1))
471
472 borderSizer.Add(gridSizer, 0, wx.ALL, SPACE)
473
474 self.Bind(wx.EVT_CLOSE, self.OnClose)
475
476 self.SetSizer(borderSizer)
477 self.Fit()
478 self._findCtrl.SetFocus()
479
480
481 def SaveConfig(self):
482 """ Save find/replace patterns and search flags to registry. """
483 findService = wx.GetApp().GetService(FindService)
484 if findService:
485 findService.SaveFindConfig(self._findCtrl.GetValue(),
486 self._wholeWordCtrl.IsChecked(),
487 self._matchCaseCtrl.IsChecked(),
488 self._regExprCtrl.IsChecked(),
489 self._wrapCtrl.IsChecked(),
490 self._radioBox.GetSelection(),
491 self._replaceCtrl.GetValue()
492 )
493
494
495 #----------------------------------------------------------------------------
496 # Menu Bitmaps - generated by encode_bitmaps.py
497 #----------------------------------------------------------------------------
498 from wx import ImageFromStream, BitmapFromImage
499 import cStringIO
500
501
502 def getFindData():
503 return \
504 '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\
505 \x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\
506 \x00\x00\x81IDAT8\x8d\xa5S\xc1\x16\xc0\x10\x0ckk\xff\xff\xc7d\x87\xad^U\r\
507 \x93S\xe5U$\n\xb3$:\xc1e\x17(\x19Z\xb3$\x9e\xf1DD\xe2\x15\x01x\xea\x93\xef\
508 \x04\x989\xea\x1b\xf2U\xc0\xda\xb4\xeb\x11\x1f:\xd8\xb5\xff8\x93\xd4\xa9\xae\
509 @/S\xaaUwJ3\x85\xc0\x81\xee\xeb.q\x17C\x81\xd5XU \x1a\x93\xc6\x18\x8d\x90\
510 \xe8}\x89\x00\x9a&\x9b_k\x94\x0c\xdf\xd78\xf8\x0b\x99Y\xb4\x08c\x9e\xfe\xc6\
511 \xe3\x087\xf9\xd0D\x180\xf1#\x8e\x00\x00\x00\x00IEND\xaeB`\x82'
512
513
514 def getFindBitmap():
515 return BitmapFromImage(getFindImage())
516
517
518 def getFindImage():
519 stream = cStringIO.StringIO(getFindData())
520 return ImageFromStream(stream)
521