Page 1 of 1

Find/Replace procedure

Posted: Sat Jan 13, 2007 12:10 am
by utopiomania
Code updated for 5.20+

Just finished this and thought I should share it with you in case you need something like it in one of your projects :)

The demo is a bit clunky, but I've just plugged the procedure into a program of my own, and it works as expected so far.

Let me hear it if you experience ANY kind of dislikesor problems with it!

Code: Select all

Procedure findReplace(id)
  flags = #PB_Window_SystemMenu | #PB_Window_ScreenCentered
  win = OpenWindow(#PB_Any, 0, 0, 400, 145, "Find/Replace", flags)
  If win
    StickyWindow(win, #True)
    ;draw the find icon       
    img1 = CreateImage(#PB_Any, 22, 22)
    If StartDrawing(ImageOutput(img1))
      Box(0, 0, 24, 24, GetSysColor_(15))
      ;icon index 176, 55
      ExtractIconEx_("shell32.dll", 176, 0, @icon, 1)
      If icon
        DrawImage(icon, 0, 0, 21, 21)
        DestroyIcon_(icon)
      EndIf
      StopDrawing()
    EndIf
    If 1
      ImageGadget(#PB_Any, 10, 15, 0, 0, ImageID(img1))
      TextGadget(#PB_Any, 40, 20, 75, 22, "Find:")
    Else
      TextGadget(#PB_Any, 10, 20, 75, 22, "Find What:")
    EndIf
    str1 = StringGadget(#PB_Any, 100, 15, 200, 21, "")
    TextGadget(#PB_Any, 10, 50, 75, 22, "Replace With:")
    str2 = StringGadget(#PB_Any, 100, 45, 200, 21, "")
    btn1 = ButtonGadget(#PB_Any, 310, 15, 80, 22, "Find Next", #PB_Button_Default)
    DisableGadget(btn1, #True)
    btn2 = ButtonGadget(#PB_Any, 310, 45, 80, 22, "Replace")
    DisableGadget(btn2, #True)
    btn3 = ButtonGadget(#PB_Any, 310, 80, 80, 22, "Replace all")
    DisableGadget(btn3, #True)
    btn4 = ButtonGadget(#PB_Any, 310, 110, 80, 22, "Cancel")
    
    ;get any selected text in the richedit
    SendMessage_(GadgetID(id), #EM_EXGETSEL, 0, @text.FINDTEXT\chrg)
    If (text\chrg\cpMin <> text\chrg\cpMax)
      ;selected text range found, check it's length
      len = text\chrg\cpMax - text\chrg\cpMin
      If len <= 128
        char.c
        *txt = AllocateMemory((len + 1) * SizeOf(char))
        ;get the selected text
        SendMessage_(GadgetID(id), #EM_GETSELTEXT, 0, *txt)
        SetGadgetText(str1, PeekS(*txt))
        FreeMemory(*txt)
        ;focus the replace input field
        SetActiveGadget(str2)
        ;enable the buttons
        DisableGadget(btn1, #False)
        DisableGadget(btn2, #False)
        DisableGadget(btn3, #False)
      EndIf
    Else
      ;focus the findstring input field
      SetActiveGadget(str1)
    EndIf
    
    chk1 = CheckBoxGadget(#PB_Any, 10, 80, 120, 22, "Match Case")
    chk2 = CheckBoxGadget(#PB_Any, 10, 112, 120, 22, "Whole Words only")
    chk3 = CheckBoxGadget(#PB_Any, 140, 112, 160, 22, "Search From Top")
    
    ;no text found yet:
    pos = -1
    ;default search flag
    flg | #FR_DOWN
    
    Repeat
      event = WaitWindowEvent()
      If EventWindow() = win
        Select event
          Case #PB_Event_Gadget
            Select EventGadget()
              Case str1
                ;find string
                GadgetToolTip(str1, GetGadgetText(str1))
                If Len(GetGadgetText(str1))
                  DisableGadget(btn1, #False)
                  DisableGadget(btn2, #False)
                  DisableGadget(btn3, #False)
                Else
                  DisableGadget(btn1, #True)
                  DisableGadget(btn2, #True)
                  DisableGadget(btn3, #True)
                EndIf
                
              Case str2
                ;replace string
                GadgetToolTip(str2, GetGadgetText(str2))
                
              Case btn1
                ;find button
                find.s = GetGadgetText(str1)
                text.FINDTEXT\lpstrText = @find
                ;get current position or selected range in the text
                SendMessage_(GadgetID(id), #EM_EXGETSEL, 0, @text\chrg)
                If (text\chrg\cpMin <> text\chrg\cpMax)
                  ;selected text range found, search from the end of it
                  text\chrg\cpMin = text\chrg\cpMax
                EndIf
                If GetGadgetState(chk3)
                  ;reset, search from the top
                  SetGadgetState(chk3, 0)
                  text\chrg\cpMin = 0
                EndIf
                ;search to the end of the text:
                text\chrg\cpMax = -1
                ;set the search flags
                flg = 0
                flg | #FR_DOWN
                If GetGadgetState(chk1)
                  flg | #FR_MATCHCASE
                EndIf
                If GetGadgetState(chk2)
                  flg | #FR_WHOLEWORD
                EndIf
                pos = SendMessage_(GadgetID(id), #EM_FINDTEXT, flg, @text)
                If pos <> -1
                  ;found, select the text range
                  SendMessage_(GadgetID(id), #EM_SETSEL, pos, pos + Len(find))
                Else
                  msg.s = "Cannot find " + find + Chr(13) + Chr(10)
                  msg + "Search again from the top ?"
                  flags = #MB_ICONQUESTION | #MB_YESNOCANCEL
                  Select MessageRequester("Find/Replace", msg, flags)
                    Case #IDYES
                      ;first position in the text
                      SendMessage_(GadgetID(id), #EM_SETSEL, 0, 0)
                      text\chrg\cpMin = 0
                      ;search to the end of the text:
                      text\chrg\cpMax = -1
                      pos = SendMessage_(GadgetID(id), #EM_FINDTEXT, flg, @text)
                      If pos <> -1
                        ;found, select the text range
                        SendMessage_(GadgetID(id), #EM_SETSEL, pos, pos + Len(find))
                      Else
                        msg.s = "Cannot find " + find
                        flags = #MB_ICONINFORMATION | #MB_OKCANCEL
                        Select MessageRequester("Find/Replace", msg, flags)
                          Case #IDCANCEL
                            Break
                        EndSelect
                      EndIf
                      SetActiveGadget(id)
                    Case #IDCANCEL
                      Break
                  EndSelect
                EndIf
                
              Case btn2
                ;replace button
                ;get selected range in the text
                SendMessage_(GadgetID(id), #EM_EXGETSEL, 0, @text\chrg)
                If (text\chrg\cpMin <> text\chrg\cpMax)
                  ;found selected text range
                  repl.s = GetGadgetText(str2)
                  If text\chrg\cpMax - text\chrg\cpMin = Len(GetGadgetText(str1))
                    ;same length as the find string, replace it
                    SendMessage_(GadgetID(id), #EM_REPLACESEL, 1, @repl)
                    ;advance the current position
                    text\chrg\cpMin + Len(repl)
                  EndIf
                Else
                  ;no selection made yet, search from the current position
                  text\chrg\cpMin = text\chrg\cpMax
                EndIf
                If GetGadgetState(chk3)
                  ;reset, search from the top
                  SetGadgetState(chk3, 0)
                  text\chrg\cpMin = 0
                EndIf
                ;search to the end of the text:
                text\chrg\cpMax = -1
                find.s = GetGadgetText(str1)
                text\lpstrText = @find
                ;set the search flags
                flg = 0
                flg | #FR_DOWN
                If GetGadgetState(chk1)
                  flg | #FR_MATCHCASE
                EndIf
                If GetGadgetState(chk2)
                  flg | #FR_WHOLEWORD
                EndIf
                pos = SendMessage_(GadgetID(id), #EM_FINDTEXT, flg, @text)
                If pos <> -1
                  ;found, select the text range
                  SendMessage_(GadgetID(id), #EM_SETSEL, pos, pos + Len(find))
                Else
                  msg.s = "Cannot find " + find + Chr(13) + Chr(10)
                  msg + "Search again from the top ?"
                  flags = #MB_ICONQUESTION | #MB_YESNOCANCEL
                  Select MessageRequester("Find/Replace", msg, flags)
                    Case #IDYES
                      ;first position in the text
                      SendMessage_(GadgetID(id), #EM_SETSEL, 0, 0)
                      text\chrg\cpMin = 0
                      ;search to the end of the text:
                      text\chrg\cpMax = -1
                      pos = SendMessage_(GadgetID(id), #EM_FINDTEXT, flg, @text)
                      If pos <> -1
                        ;found, select the text range
                        SendMessage_(GadgetID(id), #EM_SETSEL, pos, pos + Len(find))
                      Else
                        msg.s = "Cannot find " + find
                        flags = #MB_ICONINFORMATION | #MB_OKCANCEL
                        Select MessageRequester("Find/Replace", msg, flags)
                          Case #IDCANCEL
                            Break
                        EndSelect
                      EndIf
                    Case #IDCANCEL
                      Break
                  EndSelect
                EndIf
                
              Case btn3
                ;replace all button
                Repeat
                  ;get selected range in the text
                  SendMessage_(GadgetID(id), #EM_EXGETSEL, 0, @text\chrg)
                  If (text\chrg\cpMin <> text\chrg\cpMax)
                    ;found selected text range
                    repl.s = GetGadgetText(str2)
                    If text\chrg\cpMax - text\chrg\cpMin = Len(GetGadgetText(str1))
                      ;same length as the find string, replace it
                      SendMessage_(GadgetID(id), #EM_REPLACESEL, 1, @repl)
                      ;advance the current position
                      text\chrg\cpMin + Len(repl)
                    EndIf
                  Else
                    ;no selection made yet, search from the current position
                    text\chrg\cpMin = text\chrg\cpMax
                  EndIf
                  If GetGadgetState(chk3)
                    ;reset, search from the top
                    SetGadgetState(chk3, 0)
                    text\chrg\cpMin = 0
                  EndIf
                  ;search to the end of the text:
                  text\chrg\cpMax = -1
                  find.s = GetGadgetText(str1)
                  text\lpstrText = @find
                  ;set the search flags
                  flg = 0
                  flg | #FR_DOWN
                  If GetGadgetState(chk1)
                    flg | #FR_MATCHCASE
                  EndIf
                  If GetGadgetState(chk2)
                    flg | #FR_WHOLEWORD
                  EndIf
                  pos = SendMessage_(GadgetID(id), #EM_FINDTEXT, flg, @text)
                  If pos <> -1
                    ;found, select the text range
                    SendMessage_(GadgetID(id), #EM_SETSEL, pos, pos + Len(find))
                  EndIf
                Until pos = -1
                
                msg.s = "Cannot find " + find + Chr(13) + Chr(10)
                msg + "Search again from the top ?"
                flags = #MB_ICONQUESTION | #MB_YESNOCANCEL
                Select MessageRequester("Find/Replace", msg, flags)
                  Case #IDYES
                    ;first position in the text
                    SendMessage_(GadgetID(id), #EM_SETSEL, 0, 0)
                    text\chrg\cpMin = 0
                    ;search to the end of the text:
                    text\chrg\cpMax = -1
                    pos = SendMessage_(GadgetID(id), #EM_FINDTEXT, flg, @text)
                    If pos <> -1
                      ;found, select the text range
                      SendMessage_(GadgetID(id), #EM_SETSEL, pos, pos + Len(find))
                    Else
                      msg.s = "Cannot find " + find
                      flags = #MB_ICONINFORMATION | #MB_OKCANCEL
                      Select MessageRequester("Find/Replace", msg, flags)
                        Case #IDCANCEL
                          Break
                      EndSelect
                    EndIf
                  Case #IDCANCEL
                    Break
                EndSelect
                
              Case btn4
                ;cancel button
                Break
            EndSelect
          Case #PB_Event_CloseWindow
            Break
        EndSelect
      EndIf
    ForEver
    CloseWindow(win)
  EndIf
EndProcedure

;demo
flags = #PB_Window_SystemMenu | #PB_Window_ScreenCentered
win = OpenWindow(#PB_Any, 0, 0, 600, 400, "", flags)
edt1 = EditorGadget(#PB_Any, 0, 0, 600, 400)
For i = 0 To 40
  AddGadgetItem(edt1, i, "Editor gadget line " + Str(i))
Next
SendMessage_(GadgetID(edt1), #EM_SETSEL, 0, 0)

findReplace(edt1)
Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      End
  EndSelect
ForEver

Re: Find/Replace procedure

Posted: Sat Jan 13, 2007 12:29 am
by PB
I get a "gadget object not initialized" error for line 225 if the debugger is on.
@Fred/Freak: Does this mean a compiled exe would be prone to crashing?

@utopiomania: You do know there's a small API call that does this too, yeah?
I'll try to make a working example and post it here. Never had need to before,
but it would make your app look more professional due to using the Windows
API Common Dialog version.

Edit: Here's an example in Visual Basic on how to call it:
http://www.freevbcode.com/ShowCode.asp?ID=3304

Posted: Sat Jan 13, 2007 12:41 am
by utopiomania
Was an error in the demo code, hope it works now. As for the API, I replaced it with this because the API code
alternative I found needed a callback, and was virtually unreadable, so I dreamt up this in (almost) pure Basic
instead :)

EDIT: and, BTW it was designed to look like the Windows common dialog version too.To make your apps look
more professional. :wink:

Posted: Sat Jan 13, 2007 1:04 am
by PB
> BTW it was designed to look like the Windows common dialog version
> too. To make your apps look more professional. :wink:

I didn't mean your version looked unprofessional: I just used the wrong word,
I actually meant to say "standard", as in the standard dialog expected by users.
Sorry for any offense!

Posted: Sat Jan 13, 2007 1:08 am
by utopiomania
No problems at all :) and many thanks for taking the time to check it out.

Posted: Sat Jan 13, 2007 8:05 pm
by utopiomania
Just posted a slightly updated version. It now starts the 'search again from the top' correctly and
displays a dialog if it can't find more occurences of the search string.

I've also added a small icon to it. The code for this part is a few lines only, and easy to remove.

Posted: Mon Jan 15, 2007 10:01 am
by utopiomania
Just managed to make my code some 25 lines longer. Those lines fetch text that are selected in the richedit
when you call the procedure (if < 129 bytes) and put in in the 'find' field, then focus the 'replace' field.
Can make easier to use.

Posted: Mon Jan 22, 2007 6:03 pm
by utopiomania
And corrected a potential problem: freeMemory(txt) --> freeMemory(*txt)

Posted: Tue Jan 23, 2007 3:24 am
by rsts
Nice code.

Thanks for posting. I may well have a use for something like this.

cheers

Posted: Tue Jan 23, 2007 9:59 am
by utopiomania
Your'e welcome!

Please note that the code is updatet to work reliable with unicode executables.

There was an error with not allocating enough memory for unicode strings. This is
now fixed and the new code posted above.