Tutorial 34: RichEdit Control: More Text Operations

You'll learn more about text operations under RichEdit control. Specifically, you'll know how to search for/replace text, jumping to specific line number.
 

Theory

Searching for Text

There are several text operations under RichEdit control. Searching for text is one such operation. Searching for text is done by sending EM_FINDTEXT or EM_FINDTEXTEX. These messages has a small difference.
EM_FINDTEXT
wParam == Search options. Can be any combination of the values in the table below.These options are identical for both 
                        EM_FINDTEXT and EM_FINDTEXTEX
FR_DOWN If this flag is specified, the search starts from the end of the current selection to the end of the text in the control (downward). This flag has effect only for RichEdit 2.0 or later: This behavior is the default for RichEdit 1.0. The default behavior of RichEdit 2.0 or later is to search from the end of the current selection to the beginning of the text (upward).
In summary, if you use RichEdit 1.0, you can't do anything about the search direction: it always searches downward. If you use RichEdit 2.0 and you want to search downward, you must specify this flag else the search would be upward.
FR_MATCHCASE If this flag is specified, the search is case-sensitive.
FR_WHOLEWORD If this flag is set, the search finds the whole word that matches the specified search string.
Actually, there are a few more flags but they are relevant to non-English languages.
lParam == pointer to the FINDTEXT structure.

                        [FINDTEXT:
                          chrg:          CHARRANGE  <>
                          lpstrText:           ?]
                        

chrg is a CHARRANGE structure which is defined as follows:

                        [CHARRANGE:
                          cpMin:        ?
                          cpMax:        ?]
                        

cpMin contains the character index of the first character in the character array (range).
cpMax contains the character index of the character immediately following the last character in the character array.

In essence, to search for a text string, you have to specify the character range in which to search. The meaning of cpMin 
and cpMax differ according to whether the search is downward or upward. If the search is downward, cpMin 
specifies the starting character index to search in and cpMax the ending character index. If the search is upward, the 
reverse is true, ie. cpMin contains the ending character index while cpMax the starting character index.

lpstrText is the pointer to the text string to search for.

EM_FINDTEXT returns the character index of the first character in the matching text string in the richedit control. It returns -1 if
no match is found.

EM_FINDTEXTEX
wParam == the search options. Same as those of EM_FINDTEXT.
lParam == pointer to the FINDTEXTEX structure.

                        [FINDTEXTEX:
                          chrg:          CHARRANGE  <>
                          lpstrText:           ?
                          chrgText:      CHARRANGE <>]
                        

The first two members of FINDTEXTEX are identical to those of FINDTEXT structure. chrgText is a CHARRANGE structure that will
be filled with the starting/ending characterindices if a match is found.

The return value of EM_FINDTEXTEX is the same as that of EM_FINDTEXT.

The difference between EM_FINDTEXT and EM_FINDTEXTEX is that the FINDTEXTEX structure has an additional member, 
chrgText, which will be filled with the starting/ending character indices if a match is found. This is convenient if we want to do 
more text operations on the string.

Replace/Insert Text

RichEdit control provides EM_SETTEXTEX for replacing/inserting text. This message combines the functionality of WM_SETTEXT and EM_REPLACESEL. It has the following syntax:
        EM_SETTEXTEX
        wParam == pointer to SETTEXTEX structure.

                        [SETTEXTEX:
                          flags:                ?
                          codepage:             ?]
                        

        flags can be the combination of the following values:
ST_DEFAULT Deletes the undo stack, discards rich-text formatting, replaces all text.
ST_KEEPUNDO Keeps the undo stack
ST_SELECTION Replaces selection and keeps rich-text formatting
        codepage is the constant that specifies the code page you want to text to be. Usually, we simply use CP_ACP.

Text Selection

We can select the text programmatically with EM_SETSEL or EM_EXSETSEL. Either one works fine. Choosing which message to use depends on the available format of the character indices. If they are already stored in a CHARRANGE structure, it's easier to use EM_EXSETSEL.
        EM_EXSETSEL
        wParam == not used. Must be 0
        lParam == pointer to a CHARRANGE structure that contains the character range to be selected.

Event Notification

In the case of a multiline edit control, you have to subclass it in order to obtain the input messages such as mouse/keyboard events. RichEdit control provides a better scheme that will notify the parent window of such events. In order to register for notifications, the parent window sends EM_SETEVENTMASK message to the RichEdit control, specifying which events it's interested in. EM_SETEVENTMASK has the following syntax:
        EM_SETEVENTMASK
        wParam == not used. Must be 0
        lParam == event mask value. It can be the combination of the flags in the table below.
ENM_CHANGE Sends EN_CHANGE notifications
ENM_CORRECTTEXT Sends EN_CORRECTTEXT notifications
ENM_DRAGDROPDONE Sends EN_DRAGDROPDONE notifications
ENM_DROPFILES Sends EN_DROPFILES notifications.
ENM_KEYEVENTS Sends EN_MSGFILTER notifications for keyboard events
ENM_LINK Rich Edit 2.0 and later: Sends EN_LINK notifications when the mouse pointer is over text that has the CFE_LINK and one of several mouse actions is performed.
ENM_MOUSEEVENTS Sends EN_MSGFILTER notifications for mouse events
ENM_OBJECTPOSITIONS Sends EN_OBJECTPOSITIONS notifications
ENM_PROTECTED Sends EN_PROTECTED notifications
ENM_REQUESTRESIZE Sends EN_REQUESTRESIZE notifications
ENM_SCROLL Sends EN_HSCROLL and EN_VSCROLL notifications
ENM_SCROLLEVENTS Sends EN_MSGFILTER notifications for mouse wheel events
ENM_SELCHANGE Sends EN_SELCHANGE notifications
ENM_UPDATE Sends EN_UPDATE notifications.
Rich Edit 2.0 and later: this flag is ignored and the EN_UPDATE notifications are always sent. However, if Rich Edit 3.0 emulates Rich Edit 1.0, you must use this flag to send EN_UPDATE notifications 

All the above notifications will be sent as WM_NOTIFY message: you have to check the code member of NMHDR structure for the notification message. For example, if you want to register for mouse events (eg. you want to provide a context sensitive popup menu), you must do something like this:

        call 'USER32.SendMessageA' D§hwndRichEdit  &EM_SETEVENTMASK 0  &ENM_MOUSEEVENTS
        .....
        .....
        Proc MainWindowProc:
             Arguments @hWnd, @uMsg, @wParam, @lParam
        .....
        ....
                .elseif D@uMsg = &WM_NOTIFY
                    push esi
                        mov esi  D@lParam
                        .if D§esi+NMHDR.code = &EN_MSGFILTER
                                ....
                                [ do something here]
                                ....
                        .endif
                    pop esi

Example:

The following example is the update of IczEdit in tutorial no. 33. It adds search/replace functionality and accelerator keys to the program. It also processes the mouse events and provides a popup menu on right mouse click.
____________________________________________________________________________________________

[IDD_OPTIONDLG                  1000
 IDC_BACKCOLORBOX               1001
 IDC_TEXTCOLORBOX               1002
 IDD_FINDDLG                    2000
 IDD_GOTODLG                    3000
 IDD_REPLACEDLG                 4000
 IDC_FINDEDIT                   1000
 IDC_MATCHCASE                  1001
 IDC_REPLACEEDIT                1001
 IDC_WHOLEWORD                  1002
 IDC_DOWN                       1003
 IDC_UP                         1004
 IDC_LINENO                     1005]

[RichEditID 300]

[ClassName:         B§ 'IczEditClass',0
 AppName :          B§ 'IczEdit version 1.0',0
 RichEditDLL:       B§ 'riched20.dll',0
 RichEditClass:     B§ 'RichEdit20A',0
 NoRichEdit:        B§ 'Cannot find riched20.dll',0
 ASMFilterString:   B§ 'ASM Source code (*.asm)',0,'*.asm',0
                    B§ 'All Files (*.*)',0,'*.*',0,0
 OpenFileFail:      B§ 'Cannot open the file',0
 WannaSave:         B§ 'The data in the control is modified. Want to save it?',0]
 
[FileOpened:        D§ &FALSE
 BackgroundColor:   D§ 0FFFFFF          ; default to white
 TextColor:         D§ 0]               ; default to black

[hInstance:         D§ ?
 hRichEdit:         D§ ?
 hwndRichEdit:      D§ ?]
[FileName:          B§ ? #260]
[AlternateFileName: B§ ? #260]
[CustomColors:      D§ ? #16]
[FindBuffer: B§ ? #260]
[ReplaceBuffer: B§ ? #260]
[uFlags: ?]
[FINDTEXT:
 @chrg: @chrg@cpMin: D§ 0
        @chrg@cpMax: D§ 0
 @lpstrText: D§ 0
 @chrgText:  @chrgText@cpMin: D§ 0
             @chrgText@cpMax: D§ 0]
____________________________________________________________________________________________
____________________________________________________________________________________________

[WNDCLASSEX:
 @cbSize: D§ len
 @style: D§ &CS_HREDRAW__&CS_VREDRAW
 @lpfnWndProc: D§ MainWindowProc
 @cbClsExtra: D§ 0
 @cbWndExtra: D§ 0
 @hInstance: D§ 0
 @hIcon: D§ 0
 @hCursor: D§ 0
 @hbrBackground: D§ &COLOR_WINDOW+1
 @lpszMenuName: D§ M00_Menu
 @lpszClassName: D§ ClassName
 @hIconSm: D§ 0]

[MSG:
 @hwnd: D§ 0
 @message: D§ 0
 @wParam: D§ 0
 @lParam: D§ 0
 @time: D§ 0
 @MSG@pt: @MSG@pt@x: D§ 0
          @MSG@pt@y: D§ 0]

[hwnd: D§ 0]

[M00_Menu  1000                  M00_Open  1001                  M00_Close  1002
 M00_Save  1003                  M00_save_As  1004               M00_Exit  1005
 M00_Undo  1006                  M00_Redo  1007                  M00_Copy  1008
 M00_Cut  1009                   M00_Paste  1010                 M00_Delete  1011
 M00_select_All  1012            M00_Find  1013                  M00_Find_Next  1014
 M00_Find_Prev  1015             M00_Replace  1016               M00_Go_To_Line  1017
 M00_Options  1018]

____________________________________________________________________________________________
____________________________________________________________________________________________

[&SCF_ALL 4]

[ACCELERATORS: 
 U§ &FVIRTKEY__&FNOINVERT                       &VK_F3  M00_Find_Next
    &FVIRTKEY__&FCONTROL__&FNOINVERT            &VK_F3  M00_Find_Prev
    
    &FVIRTKEY__&FCONTROL__&FNOINVERT            'F'     M00_Find
    &FVIRTKEY__&FCONTROL__&FNOINVERT            'G'     M00_Go_To_Line
    &FVIRTKEY__&FCONTROL__&FNOINVERT+FLAGLAST   'R'     M00_Replace]

[ACCELNUMBER 5   FLAGLAST 080]

[AccelHandle: 0    hSearch: 0]

____________________________________________________________________________________________
____________________________________________________________________________________________

 
Main:
    call 'KERNEL32.GetModuleHandleA'  &NULL
    mov D§hInstance eax, D§WNDCLASSEX@hInstance eax
    
    call 'KERNEL32.LoadLibraryA' RichEditDLL
    .If eax <> 0
        mov D§hRichEdit eax
    .Else
        call 'USER32.MessageBoxA' 0 NoRichEdit AppName &MB_OK__&MB_ICONERROR | jmp L9>>
    .End_If
    
    call 'USER32.LoadIconA' &NULL &IDI_APPLICATION
    mov D§WNDCLASSEX@hIcon eax, D§WNDCLASSEX@hIconSm eax
    call 'USER32.LoadCursorA' &NULL &IDC_ARROW
    mov D§WNDCLASSEX@hCursor eax
    
    call 'USER32.RegisterClassExA' WNDCLASSEX
    call 'USER32.CreateWindowExA' &NULL ClassName AppName,
                                  &WS_OVERLAPPEDWINDOW  &CW_USEDEFAULT,
                                  &CW_USEDEFAULT &CW_USEDEFAULT &CW_USEDEFAULT &NULL &NULL,
                                  D§hInstance &NULL
    mov   D§hwnd eax
    
    call 'USER32.ShowWindow' D§hwnd &SW_SHOWNORMAL
    call 'USER32.UpdateWindow' D§hwnd
    
    
    call 'USER32.CreateAcceleratorTableA' ACCELERATORS ACCELNUMBER
    mov D§AccelHandle eax
 
L1: call 'USER32.GetMessageA' MSG  0 0 0 | On eax = 0, jmp L8>

        call 'USER32.IsDialogMessage' D§hSearch MSG
        
        .If eax = 0
            call 'USER32.TranslateAccelerator' D§hwnd D§AccelHandle MSG
            If eax = 0
                call 'USER32.TranslateMessage' MSG
                call 'USER32.DispatchMessageA' MSG
            End_If
        
        .End_If
        
        jmp L1<
        
L8: call 'KERNEL32.FreeLibrary' D§hRichEdit
    call 'USER32.DestroyAcceleratorTable' D§AccelHandle
 
L9: call 'KERNEL32.ExitProcess' 0

____________________________________________________________________________________________
____________________________________________________________________________________________

Proc StreamInProc:
    arguments @hFile @pBuffer @NumBytes @pBytesRead

        call 'KERNEL32.ReadFile' D@hFile D@pBuffer D@NumBytes D@pBytesRead 0
        xor eax 1
EndP


Proc StreamOutProc:
    Arguments @hFile @pBuffer @NumBytes @pBytesWritten
    
        call 'KERNEL32.WriteFile' D@hFile D@pBuffer D@NumBytes D@pBytesWritten 0
        xor eax 1
EndP


Proc CheckModifyState:
    Argument @hWnd
    
        call 'USER32.SendMessageA' D§hwndRichEdit &EM_GETMODIFY 0 0
        .If eax <> 0
            call 'USER32.MessageBoxA' D§hWnd WannaSave AppName &MB_YESNOCANCEL
            If eax = &IDYES
                call 'USER32.SendMessageA' D@hWnd &WM_COMMAND M00_SAVE 0
            Else _If eax = &IDCANCEL
                mov eax &FALSE | Exit
            End_If
        .Endif
        mov eax &TRUE
EndP


[CHARFORMAT:
 @cbSize:           D§ 60
 @dwMask:           D§ &CFM_COLOR
 @dwEffects:        D§ 0  ; &CFE_AUTOCOLOR ; ?
 @yHeight:          D§ 0
 @yOffset:          D§ 0
 @crTextColor:      D§ 0
 @bCharSet:         B§ 0
 @bPitchAndFamily:  B§ 0]
[@szFaceName:       B§ 0 #32] ; &LF_FACESIZE
[wPad2:             W§ 0]


SetColor:
    call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETBKGNDCOLOR 0 D§BackgroundColor

    move D§CHARFORMAT@crTextColor D§TextColor
    call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETCHARFORMAT &SCF_ALL CHARFORMAT
ret


[CHOOSECOLOR:
 @lStructSize:      D§ len
 @hwndOwner:        D§ 0
 @hInstance:        D§ 0
 @rgbResult:        D§ 0
 @lpCustColors:     D§ CustomColors
 @Flags:            D§ &CC_RGBINIT
 @lCustData:        D§ 0
 @lpfnHook:         D§ 0
 @lpTemplateName:   D§ 0]

Proc OptionProc:
    Arguments @hWnd @uMsg @wParam @lParam
    
    pushad
    
        ...If D@uMsg = &WM_INITDIALOG
            ; returns &TRUE
        ...Else_If D@uMsg = &WM_COMMAND
            mov eax D@wParam | shr eax 16
            ..If ax = &BN_CLICKED
                mov eax D@wParam
                .If ax = &IDCANCEL
                    call 'USER32.SendMessageA' D@hWnd &WM_CLOSE 0 0
                    
                .Else_If ax = IDC_BACKCOLORBOX
                    move D§CHOOSECOLOR@hwndOwner D@hwnd
                    move D§ CHOOSECOLOR@hInstance D§hInstance
                    Move D§CHOOSECOLOR@rgbResult D§BackgroundColor
                    call 'COMDLG32.ChooseColorA' CHOOSECOLOR
                    If eax <> 0
                        move D§BackgroundColor D§CHOOSECOLOR@rgbResult
                        call 'USER32.GetDlgItem' D@hWnd IDC_BACKCOLORBOX
                        call 'USER32.InvalidateRect' eax 0 &TRUE
                    End_If
                    
                .Else_If ax = IDC_TEXTCOLORBOX
                    move D§CHOOSECOLOR@hwndOwner D@hwnd
                    move D§ CHOOSECOLOR@hInstance D§hInstance
                    move D§CHOOSECOLOR@rgbResult D§TextColor
                    call 'COMDLG32.ChooseColorA' CHOOSECOLOR
                    If eax <> 0
                        move D§TextColor D§CHOOSECOLOR@rgbResult
                        call 'USER32.GetDlgItem' D@hWnd IDC_TEXTCOLORBOX
                        call 'USER32.InvalidateRect' eax 0 &TRUE
                    End_If
                    
                .Else_If ax = &IDOK
    ;================================================================================
    ; Save the modify state of the richedit control because changing the text color changes the
    ; modify state of the richedit control.
    ;==================================================================================
                    call 'USER32.SendMessageA' D§hwndRichEdit &EM_GETMODIFY 0 0
                    push eax
                        call SetColor
                    pop eax
                    call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETMODIFY eax 0
                    call 'USER32.EndDialog' D@hWnd 0
                .End_If
                
            ..End_If
                
        ...Else_If D@uMsg = &WM_CTLCOLORSTATIC
            call 'USER32.GetDlgItem' D@hWnd IDC_BACKCOLORBOX
            .If eax = D@lParam
                popad
                call 'GDI32.CreateSolidBrush' D§BackgroundColor 
                Exit
            .Else
                call 'USER32.GetDlgItem' D@hWnd IDC_TEXTCOLORBOX
                If eax = D@lParam
                    popad
                    call 'GDI32.CreateSolidBrush' D§TextColor
                    Exit
                End_If
            .End_If
            popad | mov eax &FALSE | Exit
            
        ...Else_If D@uMsg = &WM_CLOSE
            call 'USER32.EndDialog' D@hWnd 0
        ...Else
            popad | mov eax &FALSE | Exit
        ...End_If
        popad | mov eax &TRUE
EndP
____________________________________________________________________________________________
____________________________________________________________________________________________

Proc SearchProc:
    Arguments @hWnd, @uMsg, @wParam, @lParam
    
    pushad
    
    ...If D@uMsg = &WM_INITDIALOG
        move D§hSearch D@hWnd
  ;===================================================
  ; Select the default search down option
  ;===================================================
        call 'USER32.CheckRadioButton' D@hWnd IDC_DOWN IDC_UP IDC_DOWN
        call 'USER32.SendDlgItemMessageA' D@hWnd IDC_FINDEDIT &WM_SETTEXT 0 FindBuffer
        
    ...Else_If D@uMsg = &WM_COMMAND
        mov eax D@wParam | shr eax 16
        ..If ax = &BN_CLICKED
            mov eax D@wParam
            On ax = &IDCANCEL, call 'USER32.SendMessageA' D@hWnd &WM_CLOSE 0 0
            If ax <> &IDOK
                mov eax &FALSE | Exit
            End_If
                mov D§uFlags 0
                call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXGETSEL 0 FINDTEXT@chrg
                call 'USER32.GetDlgItemTextA' D@hWnd IDC_FINDEDIT FindBuffer 260
                If eax = 0
                    mov eax &TRUE | Exit
                End_If
                    call 'USER32.IsDlgButtonChecked' D@hWnd IDC_DOWN
                    If eax = &BST_CHECKED
                        or D§uFlags &FR_DOWN
                        mov eax D§FINDTEXT@chrg@cpMin
                        On eax <> D§FINDTEXT@chrg@cpMax,
                            move D§FINDTEXT@chrg@cpMin D§FINDTEXT@chrg@cpMax
                        mov D§FINDTEXT@chrg@cpMax 0-1
                    Else
                        mov D§FINDTEXT@chrg@cpMax 0
                    End_If
                    call 'USER32.IsDlgButtonChecked' D@hWnd IDC_MATCHCASE
                    On eax = &BST_CHECKED, or D§uFlags &FR_MATCHCASE
                    call 'USER32.IsDlgButtonChecked' D@hWnd IDC_WHOLEWORD
                    On eax = &BST_CHECKED, or D§uFlags &FR_WHOLEWORD
                    mov D§FINDTEXT@lpstrText FindBuffer
                    call 'USER32.SendMessageA' D§hwndRichEdit &EM_FINDTEXTEX D§uFlags FINDTEXT
                    On eax <> 0-1,
                        call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXSETSEL 0,
                                                   FINDTEXT@chrgText
      
        ..Else
           popad | mov eax &FALSE | Exit
        ..End_If
        
    ...Else_If D@uMsg = &WM_CLOSE
        mov D§hSearch 0
        call 'USER32.EndDialog' D@hWnd 0
        
    ...Else
        popad | mov eax &FALSE | Exit
        
    ...End_If
  
    popad | mov eax &TRUE
EndP


[&ST_SELECTION 2    &CP_ACP 0   &EM_SETTEXTEX 0461]

Proc ReplaceProc:
    Arguments @hWnd, @uMsg, @wParam, @lParam
    Structure @SETTEXT 8, @SETTEXT.flags 0, @SETTEXT.codepage 4
    
    ...If D@uMsg = &WM_INITDIALOG
        move D§hSearch D@hwnd
        call 'USER32.SetDlgItemTextA' D@hWnd IDC_FINDEDIT FindBuffer
        call 'USER32.SetDlgItemTextA' D@hWnd IDC_REPLACEEDIT ReplaceBuffer
        call 'USER32.GetDlgItem' D@hWnd IDC_REPLACEEDIT
        call 'USER32.SetFocus' eax
        
    ...Else_If D@uMsg = &WM_COMMAND
        mov eax D@wParam | shr eax 16
        ..If ax = &BN_CLICKED
            mov eax D@wParam
            .If ax = &IDCANCEL
                call 'USER32.SendMessageA' D@hWnd &WM_CLOSE 0 0
                
            .Else_If ax = &IDOK
                call 'USER32.GetDlgItemTextA' D@hWnd IDC_FINDEDIT FindBuffer 260
                call 'USER32.GetDlgItemTextA' D@hWnd IDC_REPLACEEDIT ReplaceBuffer 260
                mov D§FINDTEXT@chrg@cpMin 0
                mov D§FINDTEXT@chrg@cpMax 0-1
                mov D§FINDTEXT@lpstrText FindBuffer
                mov D@SETTEXT.flags &ST_SELECTION
                mov D@SETTEXT.codepage &CP_ACP
 
L1:             call 'USER32.SendMessageA' D§hwndRichEdit &EM_FINDTEXTEX &FR_DOWN FINDTEXT
                On eax = 0-1, jp L2>
                call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXSETSEL 0 FINDTEXT@chrgText
                call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETTEXTEX D@SETTEXT ReplaceBuffer
                jmp L1<
L2:                
            .End_If
        ..End_If
        
    ...Else_If D@uMsg = &WM_CLOSE
        mov D§hSearch 0 | call 'USER32.EndDialog' D@hWnd 0
        
    ...Else
        mov eax &FALSE | Exit

    ...End_If
    mov eax &TRUE
EndP



Proc GoToProc:
    Arguments @hWnd, @uMsg, @wParam, @lParam
    LOCAL @LineNo
    
    ...If D@uMsg = &WM_INITDIALOG
        move D§hSearch D@hwnd
        
    ...Else_If D@uMsg = &WM_COMMAND
        mov eax D@wParam | shr eax 16
        ..If ax = &BN_CLICKED
            mov eax D@wParam
            .If ax = &IDCANCEL
                call 'USER32.SendMessageA' D@hWnd &WM_CLOSE 0 0
            .Else_If ax = &IDOK
                call 'USER32.GetDlgItemInt' D@hWnd IDC_LINENO &NULL &FALSE
                mov D@LineNo eax
                call 'USER32.SendMessageA' D§hwndRichEdit &EM_GETLINECOUNT 0 0
                If eax > D@LineNo
                    call 'USER32.SendMessageA' D§hwndRichEdit &EM_LINEINDEX D@LineNo 0
                    call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETSEL eax eax
                    call 'USER32.SetFocus' D§hwndRichEdit
                End_If
            .End_If
        ..End_If
        
    ...Else_If D@uMsg = &WM_CLOSE
        mov D§hSearch 0 | call 'USER32.EndDialog' D@hWnd 0
        
    ...Else
        mov eax &FALSE | Exit
 
    ...End_If
    mov eax &TRUE
EndP



Proc PrepareEditMenu:
    Argument @hSubMenu
    Structure @CHARRANGE 8, @CHARRANGE.cpMin 0, @CHARRANGE.cpMax 4

 ;=============================================================================
 ; Check whether there is some text in the clipboard. If so, we enable the paste menuitem
 ;=============================================================================
    call 'USER32.SendMessageA' D§hwndRichEdit &EM_CANPASTE &CF_TEXT 0
    If eax = 0  ; no text in the clipboard
        call 'USER32.EnableMenuItem' D@hSubMenu M00_PASTE &MF_GRAYED
    Else
        call 'USER32.EnableMenuItem' D@hSubMenu M00_PASTE &MF_ENABLED
    End_If
 ;==========================================================
 ; check whether the undo queue is empty
 ;==========================================================
    call 'USER32.SendMessageA' D§hwndRichEdit &EM_CANUNDO 0 0
    If eax = 0
        call 'USER32.EnableMenuItem' D@hSubMenu M00_UNDO &MF_GRAYED
    Else
        call 'USER32.EnableMenuItem' D@hSubMenu M00_UNDO &MF_ENABLED
    End_If
 ;=========================================================
 ; check whether the redo queue is empty
 ;=========================================================
    call 'USER32.SendMessageA' D§hwndRichEdit &EM_CANREDO 0 0
    If eax = 0
        call 'USER32.EnableMenuItem' D@hSubMenu M00_REDO &MF_GRAYED
    Else
        call 'USER32.EnableMenuItem' D@hSubMenu M00_REDO &MF_ENABLED
    End_If
 ;=========================================================
 ; check whether there is a current selection in the richedit control.
 ; If there is, we enable the cut/copy/delete menuitem
 ;=========================================================
    call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXGETSEL 0 D@CHARRANGE
    mov eax D@CHARRANGE.cpMin
    If eax = D@CHARRANGE.cpMax  ; no current selection
        call 'USER32.EnableMenuItem' D@hSubMenu M00_COPY &MF_GRAYED
        call 'USER32.EnableMenuItem' D@hSubMenu M00_CUT &MF_GRAYED
        call 'USER32.EnableMenuItem' D@hSubMenu M00_DELETE &MF_GRAYED
    Else
        call 'USER32.EnableMenuItem' D@hSubMenu M00_COPY &MF_ENABLED
        call 'USER32.EnableMenuItem' D@hSubMenu M00_CUT &MF_ENABLED
        call 'USER32.EnableMenuItem' D@hSubMenu M00_DELETE &MF_ENABLED
    End_If
EndP
____________________________________________________________________________________________


____________________________________________________________________________________________
____________________________________________________________________________________________

[CHARRANGE:
 @cpMin: D§ 0
 @cpMax: D§ 0]

[OPENFILENAME:
 @lStructSize: D§ Len
 @hWndOwner: D§ 0
 @hInstance: D§ 0
 @lpstrFilter: D§ ASMFilterString
 @lpstrCustomFilter: D§ 0
 @nMaxCustFilter: D§ 0
 @nFilterIndex: D§ 0
 @lpstrFile: D§ FileName
 @nMaxFile: D§ 260
 @lpstrFileTitle: D§ 0
 @nMaxFileTitle: D§ 260
 @lpstrInitialDir: D§ 0
 @lpstrTitle: D§ 0
 @Flags: D§ 0
 @nFileOffset: W§ 0
 @nFileExtension: W§ 0
 @lpstrDefExt: D§ 0
 @lCustData: D§ 0
 @lpfnHook: D§ 0
 @lpTemplateName: D§ 0]

[Buffer: B§ ? #256]

[EDITSTREAM:
 @dwCookie: D§ 0
 @dwError: D§ 0
 @pfnCallback: D§ 0]

[hFile: 0       hPopup: 0]

[Pt: Pt.X: 0    Pt.Y: 0]


Proc MainWindowProc:
    Arguments @hWnd @uMsg @wParam @lParam
    
    pushad

    ...If D@uMsg = &WM_CREATE
        call 'USER32.CreateWindowExA' &WS_EX_CLIENTEDGE RichEditClass 0,
               &WS_CHILD__&WS_VISIBLE__&ES_MULTILINE__&WS_VSCROLL__&WS_HSCROLL__&ES_NOHIDESEL,
               &CW_USEDEFAULT &CW_USEDEFAULT &CW_USEDEFAULT &CW_USEDEFAULT D@hWnd,
               RichEditID D§hInstance 0
        mov D§hwndRichEdit eax
  ;=============================================================
  ; Set the text limit. The default is 64K
  ;=============================================================
        call 'USER32.SendMessageA' D§hwndRichEdit &EM_LIMITTEXT 0-1 0
  ;=============================================================
  ; Set the default text/background color
  ;=============================================================
        call SetColor
        call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETMODIFY &FALSE 0
        call 'USER32.SendMessageA' D§hwndRichEdit &EM_EMPTYUNDOBUFFER 0 0
        
    ...Else_If D@uMsg = &WM_NOTIFY
        ;[MSGFILTER:
        ; @nmhdr: @NMHDR@hwndFrom: D§ 0 ; +0
        ;         @NMHDR@idfrom: D§ 0   ; +4
        ;         @NMHDR@code: D§ 0     ; +8
        ; @msg: D§ 0                    ; +12
        ; @wParam: D§ 0                 ; +16
        ; @lParam: D§ 0]                ; +20
        push esi
            mov esi D@lParam
            .If D§esi+8 = &EN_MSGFILTER
                .If D§esi+12 = &WM_RBUTTONDOWN
                    call 'USER32.GetMenu' D@hWnd
                    call 'USER32.GetSubMenu' eax 1 | mov D§hPopup eax
                    call PrepareEditMenu D§hPopup
                    mov edx D§esi+20, ecx edx
                    and edx 0FFFF | shr ecx 16
                    mov D§Pt.X edx, D§Pt.Y ecx
                    call 'USER32.ClientToScreen' D@hWnd Pt
                    call 'USER32.TrackPopupMenu' D§hPopup &TPM_LEFTALIGN__&TPM_BOTTOMALIGN,
                                                 D§Pt.X D§Pt.Y &NULL D@hWnd &NULL
                .End_If
            .End_If
        pop esi
  
    ...Else_If D@uMsg = &WM_INITMENUPOPUP
        mov eax D@lParam
        ..If ax = 0  ; file menu   
            .If D§FileOpened = &TRUE ; a file is already opened
                call 'USER32.EnableMenuItem' D@wParam M00_OPEN &MF_GRAYED
                call 'USER32.EnableMenuItem' D@wParam M00_CLOSE &MF_ENABLED
                call 'USER32.EnableMenuItem' D@wParam M00_SAVE &MF_ENABLED
                call 'USER32.EnableMenuItem' D@wParam M00_SAVEAS &MF_ENABLED
            .Else
                call 'USER32.EnableMenuItem' D@wParam M00_OPEN &MF_ENABLED
                call 'USER32.EnableMenuItem' D@wParam M00_CLOSE &MF_GRAYED
                call 'USER32.EnableMenuItem' D@wParam M00_SAVE &MF_GRAYED
                call 'USER32.EnableMenuItem' D@wParam M00_SAVEAS &MF_GRAYED
            .End_If
 
        ..Else_If ax = 1 ; edit menu
            call PrepareEditMenu D@wParam
            
        ..Else_If ax = 2  ; search menu bar
            .If B§FileOpened = &TRUE
                call 'USER32.EnableMenuItem' D@wParam M00_FIND &MF_ENABLED
                call 'USER32.EnableMenuItem' D@wParam M00_FINDNEXT &MF_ENABLED
                call 'USER32.EnableMenuItem' D@wParam M00_FINDPREV &MF_ENABLED
                call 'USER32.EnableMenuItem' D@wParam M00_REPLACE &MF_ENABLED
                call 'USER32.EnableMenuItem' D@wParam M00_GOTOLINE &MF_ENABLED
            .Else
                call 'USER32.EnableMenuItem' D@wParam M00_FIND &MF_GRAYED
                call 'USER32.EnableMenuItem' D@wParam M00_FINDNEXT &MF_GRAYED
                call 'USER32.EnableMenuItem' D@wParam M00_FINDPREV &MF_GRAYED
                call 'USER32.EnableMenuItem' D@wParam M00_REPLACE &MF_GRAYED
                call 'USER32.EnableMenuItem' D@wParam M00_GOTOLINE &MF_GRAYED
            .End_If

        ..End_If
        
    ...Else_If D@uMsg = &WM_COMMAND
    
        ..If D@lParam = 0  ; menu commands
            mov eax D@wParam
            .If ax = M00_OPEN
                call OpenCommand D@hWnd

            .Else_If ax = M00_CLOSE
                call CheckModifyState D@hWnd
                If eax = &TRUE
                    call 'USER32.SetWindowTextA' D§hwndRichEdit 0
                    mov D§FileOpened &FALSE
                End_If
                
            .Else_If ax = M00_SAVE
                call 'KERNEL32.CreateFileA' FileName &GENERIC_WRITE &FILE_SHARE_READ,
                                           &NULL &CREATE_ALWAYS &FILE_ATTRIBUTE_NORMAL 0
                If eax <> &INVALID_HANDLE_VALUE
                    call InvalidFile

                Else
                    call 'USER32.MessageBoxA' D@hWnd OpenFileFail AppName,
                                              &MB_OK__&MB_ICONERROR
                End_If
                
            .Else_If ax = M00_COPY
                call 'USER32.SendMessageA' D§hwndRichEdit &WM_COPY 0 0
                
            .Else_If ax = M00_CUT
                call 'USER32.SendMessageA' D§hwndRichEdit &WM_CUT 0 0
                
            .Else_If ax = M00_PASTE
                call 'USER32.SendMessageA' D§hwndRichEdit &WM_PASTE 0 0
                
            .Else_If ax = M00_DELETE
                call 'USER32.SendMessageA' D§hwndRichEdit &EM_REPLACESEL &TRUE 0
                
            .Else_If ax = M00_SELECTALL
                mov D§CHARRANGE@cpMin 0
                mov D§CHARRANGE@cpMax 0-1
                call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXSETSEL 0 CHARRANGE
                
            .Else_If ax = M00_UNDO
                call 'USER32.SendMessageA' D§hwndRichEdit &EM_UNDO 0 0
            
            .Else_If ax = M00_REDO
                call 'USER32.SendMessageA' D§hwndRichEdit &EM_REDO 0 0
                
            .Else_If ax = M00_OPTIONS
                call 'USER32.DialogBoxParamA' D§hInstance IDD_OPTIONDLG D@hWnd OptionProc 0
                
            .Else_If ax = M00_SAVEAS
                move D§OPENFILENAME@hwndOwner D@hwnd
                move D§OPENFILENAME@hInstance D§hInstance
                mov D§OPENFILENAME@lpstrFilter ASMFilterString
                mov D§OPENFILENAME@lpstrFile AlternateFileName
                mov D§AlternateFileName 0
                mov D§OPENFILENAME@Flags &OFN_FILEMUSTEXIST__&OFN_HIDEREADONLY__&OFN_PATHMUSTEXIST
                call 'COMDLG32.GetSaveFileNameA' OPENFILENAME
                If eax <> 0
                    call 'KERNEL32.CreateFileA' AlternateFileName &GENERIC_WRITE,
                                                &FILE_SHARE_READ &NULL &CREATE_ALWAYS,
                                                &FILE_ATTRIBUTE_NORMAL 0
                    On eax <> &INVALID_HANDLE_VALUE, call InvalidFile
                End_If
    
            .Else_If ax = M00_FIND
                On D§hSearch = 0,
                    call 'USER32.CreateDialogParamA' D§hInstance IDD_FINDDLG D@hWnd SearchProc 0
                    call 'USER32.GetDlgItem' eax IDC_FINDEDIT
                    call 'USER32.SetFocus' eax

            .Else_If ax = M00_REPLACE
                On D§hSearch = 0,
                    call 'USER32.CreateDialogParamA' D§hInstance IDD_REPLACEDLG D@hWnd,
                                                     ReplaceProc 0
                    call 'USER32.GetDlgItem' eax IDC_FINDEDIT
                    call 'USER32.SetFocus' eax
   
            .Else_If ax = M00_GO_TO_LINE
                On D§hSearch = 0
                    call 'USER32.CreateDialogParamA' D§hInstance IDD_GOTODLG D@hWnd GoToProc 0
                    call 'USER32.GetDlgItem' eax IDC_LINENO
                    call 'USER32.SetFocus' eax

            .Else_If ax = M00_FIND_NEXT
                call 'KERNEL32.lstrlen' FindBuffer
                If eax = 0
                    popad | mov eax &FALSE | Exit
                End_If
                    call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXGETSEL 0 FINDTEXT@chrg
                    mov eax D§FINDTEXT@chrg@cpMin
                    On eax <> D§FINDTEXT@chrg@cpMax,
                        move D§FINDTEXT@chrg@cpMin, D§FINDTEXT@chrg@cpMax
                    mov D§FINDTEXT@chrg@cpMax 0-1
                    mov D§FINDTEXT@lpstrText FindBuffer
                    call 'USER32.SendMessageA' D§hwndRichEdit &EM_FINDTEXTEX &FR_DOWN,
                                               findtext
                    On eax <> 0-1,
                        call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXSETSEL 0,
                                                   FINDTEXT@chrgText
                                                   
            .Else_If ax = M00_FIND_PREV
                call 'KERNEL32.lstrlen' FindBuffer
                If eax <> 0
                    call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXGETSEL 0 FINDTEXT@chrg
                    mov D§FINDTEXT@chrg@cpMax 0
                    mov D§FINDTEXT@lpstrText FindBuffer
                    call 'USER32.SendMessageA' D§hwndRichEdit &EM_FINDTEXTEX 0 findtext
                    On eax <> 0-1,
                        call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXSETSEL 0,
                                                   FINDTEXT@chrgText
                End_If
            
            .Else_If ax = M00_EXIT
                call 'USER32.SendMessageA' D@hWnd &WM_CLOSE 0 0
            .End_If
        ..End_If
        
    ...Else_If D@uMsg = &WM_CLOSE
        call CheckModifyState D@hWnd
        On eax = &TRUE, call 'USER32.DestroyWindow' D@hWnd
        
    ...Else_If D@uMsg = &WM_SIZE
        mov eax D@lParam, edx eax
        and eax 0FFFF | shr edx 16
        call 'USER32.MoveWindow' D§hwndRichEdit 0 0 eax edx &TRUE  
        
    ...Else_If D@uMsg = &WM_DESTROY
        call 'USER32.PostQuitMessage' &NULL
        
    ...Else
        popad
        call 'USER32.DefWindowProcA' D@hWnd D@uMsg D@wParam D@lParam  
        Exit
        
    ...End_If
    
    popad | mov eax &FALSE
EndP


InvalidFile:
    mov D§hFile eax
     _____________________________
     ; stream the text to the file
     _____________________________
    mov D§EDITSTREAM@dwCookie eax
    mov D§EDITSTREAM@pfnCallback StreamOutProc
    call 'USER32.SendMessageA' D§hwndRichEdit &EM_STREAMOUT &SF_TEXT EDITSTREAM
     ______________________________________
     ; Initialize the modify state to false
     ______________________________________
    call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETMODIFY &FALSE 0
    call 'KERNEL32.CloseHandle' D§hFile
ret


Proc OpenCommand:
    Argument @hWnd

    move D§OPENFILENAME@hwndOwner D@hWnd
    move D§OPENFILENAME@hInstance D§hInstance
    mov D§FileName 0
    mov D§OPENFILENAME@Flags &OFN_FILEMUSTEXIST__&OFN_HIDEREADONLY__&OFN_PATHMUSTEXIST
    call 'COMDLG32.GetOpenFileNameA' OPENFILENAME
    
    ...If eax <> 0
        call 'KERNEL32.CreateFileA' FileName &GENERIC_READ,
                                    &FILE_SHARE_READ &NULL,                                    
                                    &OPEN_EXISTING  &FILE_ATTRIBUTE_NORMAL 0
        .If eax <> &INVALID_HANDLE_VALUE
            mov D§hFile eax
      ___________________________________________
      ; stream the text into the richedit control
      ___________________________________________
            mov D§EDITSTREAM@dwCookie eax
            mov D§EDITSTREAM@pfnCallback StreamInProc
            call 'USER32.SendMessageA' D§hwndRichEdit &EM_STREAMIN,
                                       &SF_TEXT EDITSTREAM
      ______________________________________
      ; Initialize the modify state to false
      ______________________________________
            call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETMODIFY &FALSE 0
            call 'KERNEL32.CloseHandle' D§hFile
            mov B§FileOpened &TRUE
            
        .Else
            call 'USER32.MessageBoxA' D@hWnd OpenFileFail AppName,
                                      &MB_OK__&MB_ICONERROR
                                      
        .End_If
    ...End_If
EndP

Analysis

The search-for-text capability is implemented with EM_FINDTEXTEX. When the user clicks on Find menuitem, IDM_FIND message is sent and the Find dialog box is displayed.

call 'USER32.GetDlgItemTextA' D@hWnd IDC_FINDEDIT FindBuffer 260
                If eax = 0
When the user types the text to search for and then press OK button, we get the text to be searched for into FindBuffer.
               mov D§uFlags 0
                call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXGETSEL 0 FINDTEXT@chrg
If the text string is not null, we continue to initialize uFlags variable to 0.This variable is used to store the search flags used withEM_FINDTEXTEX. After that, we obtain the current selection with EM_EXGETSEL because we need to know the starting point of the search operation.
                    call 'USER32.IsDlgButtonChecked' D@hWnd IDC_DOWN
                    If eax = &BST_CHECKED
                        or D§uFlags &FR_DOWN
                        mov eax D§FINDTEXT@chrg@cpMin
                        On eax <> D§FINDTEXT@chrg@cpMax,
                            move D§FINDTEXT@chrg@cpMin D§FINDTEXT@chrg@cpMax
                        mov D§FINDTEXT@chrg@cpMax 0-1
                    Else
                        mov D§FINDTEXT@chrg@cpMax 0
                    End_If
The next part is a little tricky. We check the direction radio button to ascertain which direction the search should go. If the downward search is indicated, we set FR_DOWN flag to uFlags. After that, we check whether a selection is currently in effect by comparing the values of cpMin and cpMax. If both values are not equal, it means there is a current selection and we must continue the search from the end of that selection to the end of text in the control. Thus we need to replace the value of cpMax with that of cpMin and change the value of cpMax to -1 (0FFFFFFFFh). If there is no current selection, the range to search is from the current caret position to the end of text.

If the user chooses to search upward, we use the range from the start of the selection to the beginning of the text in the control. That's why we only modify the value of cpMax to 0. In the case of upward search, cpMin contains the character index of the last character in the search range and cpMax the character index of the first char in the search range. It's the inverse of the downward search.

                    call 'USER32.IsDlgButtonChecked' D@hWnd IDC_MATCHCASE
                    On eax = &BST_CHECKED, or D§uFlags &FR_MATCHCASE
                    call 'USER32.IsDlgButtonChecked' D@hWnd IDC_WHOLEWORD
                    On eax = &BST_CHECKED, or D§uFlags &FR_WHOLEWORD
                    mov D§FINDTEXT@lpstrText FindBuffer
We continue to check the checkboxes for the search flags, ie, FR_MATCHCASE and FR_WHOLEWORD. Lastly, we put the offset of the text to search for in lpstrText member.
                    call 'USER32.SendMessageA' D§hwndRichEdit &EM_FINDTEXTEX D§uFlags FINDTEXT
                    On eax <> 0-1,
                        call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXSETSEL 0,
                                                   FINDTEXT@chrgText
We are now ready to issue EM_FINDTEXTEX. After that, we examine the search result returned by SendMessage. If the return value is -1, no match is found in the search range. Otherwise, chrgText member of FINDTEXTEX structure is filled with the character indices of the matching text. We thus proceed to select it with EM_EXSETSEL.

The replace operation is done in much the same manner.

                call 'USER32.GetDlgItemTextA' D@hWnd IDC_FINDEDIT FindBuffer 260
                call 'USER32.GetDlgItemTextA' D@hWnd IDC_REPLACEEDIT ReplaceBuffer 260
We retrieve the text to search for and the text used to replace.
                mov D§FINDTEXT@chrg@cpMin 0
                mov D§FINDTEXT@chrg@cpMax 0-1
                mov D§FINDTEXT@lpstrText FindBuffer
To make it easy, the replace operation affects all the text in the control. Thus the starting index is 0 and the ending index is -1.
                mov D@SETTEXT.flags &ST_SELECTION
                mov D@SETTEXT.codepage &CP_ACP
We initialize SETTEXTEX structure to indicate that we want to replace the current selection and use the default system code page.
L1:             call 'USER32.SendMessageA' D§hwndRichEdit &EM_FINDTEXTEX &FR_DOWN FINDTEXT
                On eax = 0-1, jp L2>
                call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXSETSEL 0 FINDTEXT@chrgText
                call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETTEXTEX D@SETTEXT ReplaceBuffer
                jmp L1<
We enter an infinite loop, searching for the matching text. If one is found, we select it with EM_EXSETSEL and replace it with EM_SETTEXTEX. When no more match is found, we exit the loop.

Find Next and Find Prev. features use EM_FINDTEXTEX message in the similar manner to the find operation.

We will examine the Go to Line feature next. When the user clicks Go To Line menuitem, we display a dialog box below:

When the user types a line number and presses Ok button, we begin the operation.

                call 'USER32.GetDlgItemInt' D@hWnd IDC_LINENO &NULL &FALSE
                mov D@LineNo eax
Obtain the line number from the edit control
                call 'USER32.SendMessageA' D§hwndRichEdit &EM_GETLINECOUNT 0 0
                If eax > D@LineNo
Obtain the number of lines in the control. Check whether the user specifies the line number that is out of the range.
                    call 'USER32.SendMessageA' D§hwndRichEdit &EM_LINEINDEX D@LineNo 0
If the line number is valid, we want to move the caret to the first character of that line. So we send EM_LINEINDEX message to the richedit control. This message returns the character index of the first character in the specified line. We send the line number in wParam and in return, we has the character index.
                    call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETSEL eax eax
To set the current selection, this time we use EM_SETSEL because the character indices are not already in a CHARRANGE structure thus it saves us two instructions (to put those indices into a CHARRANGE structure).
                    call 'USER32.SetFocus' D§hwndRichEdit
                End_If
The caret will not be displayed unless the richedit control has the focus. So we call SetFocus on it.

[Iczelion's Win32 Assembly Homepage]