.386
.model flat,stdcall

option casemap:none

; Huge data declaration support for a tiny program

include \masm32\include\windows.inc
include \masm32\include\masm32.inc
include \masm32\include\user32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\gdi32.inc
include \masm32\include\kernel32.inc
include \masm32\include\shlwapi.inc

; Almost as much binary support

includelib \masm32\lib\masm32.lib
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\shlwapi.lib

; All other functions sequenced to not need prototypes

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
SearchProc proto :DWORD,:DWORD,:DWORD,:DWORD

.const
IDR_MAINMENU                   equ   101
IDD_FINDDLG                    equ   102
RichEditID                     equ   300
IDM_OPEN                       equ 40001
IDM_SAVE                       equ 40002
IDM_CLOSE                      equ 40003
IDM_SAVEAS                     equ 40004
IDM_EXIT                       equ 40005
IDM_COPY                       equ 40006
IDM_CUT                        equ 40007
IDM_PASTE                      equ 40008
IDM_DELETE                     equ 40009
IDM_SELECTALL                  equ 40010
IDM_UNDO                       equ 40012
IDM_REDO                       equ 40013
IDM_FIND                       equ 40014
IDM_FINDNEXT                   equ 40015
IDM_ABOUT                      equ 40016
IDC_BACKCOLORBOX               equ  1000
IDC_TEXTCOLORBOX               equ  1001

      ;=============
      ; Local macros
      ;=============

      szText MACRO Name, Text:VARARG
        LOCAL lbl
          jmp lbl
            Name db Text,0
          lbl:
        ENDM

      m2m MACRO M1, M2
        push M2
        pop  M1
      ENDM

      return MACRO arg
        mov eax, arg
        ret
      ENDM

.data?
hInstance         dd ?
hRichEdit         dd ?
hwndRichEdit      dd ?
hIcon             dd ?
hCheck1           dd ?
hCheck2           dd ?
TextLen           dd ?
CaseFlag          dd ?
WholeWord         dd ?
CtrlFlag          dd ?
hFile             dd ?
CommandLine       dd ?
lpFileName        dd ?
FileName          db 256 dup(?)
AlternateFileName db 256 dup(?)
SearchText        db 128 dup(?)
editstream        EDITSTREAM   <?>

.data
ASMFilterString label byte   ; Add new file types here
	              db "All Files (*.*)",0,"*.*",0
	              db "ASM Files (*.asm)",0,"*.asm",0
	              db 0
ClassName         db "uRichEditClass",0
AppName           db "uRe 1.0",0
RichEditDLL       db "riched20.dll",0
RichEditClass     db "RichEdit20A",0
NoRichEdit        db "Cant find riched20.dll",0
OpenFileFail      db "Cant open file",0
WannaSave         db "File changed. Save?",0
FileOpened        dd FALSE
BackgroundColor   dd 0D7FFFFh       ; default to manila
TextColor         dd 0              ; default to black

.code

start:
	invoke GetModuleHandle, NULL
	mov    hInstance,eax

    invoke GetCommandLine
    mov CommandLine, eax

	invoke LoadLibrary,addr RichEditDLL
	.if eax!=0
		mov hRichEdit,eax
		invoke WinMain, hInstance,0,CommandLine, SW_SHOWDEFAULT
		invoke FreeLibrary,hRichEdit

	.else
		invoke MessageBox,0,addr NoRichEdit,addr AppName,MB_OK or MB_ICONERROR
	.endif

invoke ExitProcess,eax ;close start: logic

; Reusable 3/4 scaling

TopXY proc wDim:DWORD, sDim:DWORD

    shr sDim, 1      ; divide screen dimension by 2
    shr wDim, 1      ; divide window dimension by 2

    mov eax, wDim    ; copy window dimension into eax
    sub sDim, eax    ; sub half win dimension from half screen dimension

    mov eax, sDim
    ret

TopXY endp

; Common code for command line or Open file dialog filespec

openafile proc hWnd:DWORD,lpFn:DWORD

	invoke CreateFile,lpFn,GENERIC_READ,FILE_SHARE_READ,\
	  NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0

	.if eax!=INVALID_HANDLE_VALUE
		mov hFile,eax

		;================================================================
		; stream the text into the richedit control
		;================================================================						
		mov editstream.dwCookie,eax
		mov editstream.pfnCallback,offset StreamInProc
		invoke SendMessage,hwndRichEdit,EM_STREAMIN,SF_TEXT,addr editstream

		;==========================================================
		; Initialize the modify state to false
		;==========================================================
		invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
		invoke CloseHandle,hFile
		mov FileOpened,TRUE

	.else
		invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR
	.endif

	ret

openafile endp

; Microsofts crowning glory

WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD
	LOCAL wc:WNDCLASSEX
	LOCAL msg:MSG
	LOCAL hwnd:DWORD
	LOCAL Wwd  :DWORD
	LOCAL Wht  :DWORD
	LOCAL Wtx  :DWORD
	LOCAL Wty  :DWORD
	LOCAL sWid :DWORD
	LOCAL sHgt :DWORD

	mov wc.cbSize,          SIZEOF WNDCLASSEX

	mov wc.style,           CS_HREDRAW or CS_VREDRAW

	mov wc.lpfnWndProc,     OFFSET WndProc

	mov wc.cbClsExtra,      NULL
	mov wc.cbWndExtra,      NULL

	push hInst
	pop wc.hInstance

	mov wc.hbrBackground,   COLOR_WINDOW+1

	mov wc.lpszMenuName,    IDR_MAINMENU
	mov wc.lpszClassName,   OFFSET ClassName

	invoke LoadIcon,hInst,500
	mov wc.hIcon,           eax
	mov wc.hIconSm,         eax

	invoke LoadCursor,NULL,IDC_ARROW
	mov wc.hCursor,         eax

	invoke RegisterClassEx, addr wc

    ; -------------------------------------------------
    ; Size & centre window at 75% x 75% of screen size
    ; -------------------------------------------------
    invoke GetSystemMetrics,SM_CXSCREEN
    mov sWid, eax
    push sWid

    shr eax, 2
    sub sWid, eax
    m2m Wwd, sWid

    invoke GetSystemMetrics,SM_CYSCREEN
    mov sHgt, eax
    push sHgt

    shr eax, 2
    sub sHgt, eax
    m2m Wht, sHgt

    pop sHgt
    invoke TopXY,Wht,sHgt
    mov Wty, eax

    pop sWid
    invoke TopXY,Wwd,sWid
    mov Wtx, eax

	INVOKE CreateWindowEx,
			WS_EX_LEFT or WS_EX_ACCEPTFILES,
			ADDR ClassName,
			ADDR AppName,
			WS_OVERLAPPEDWINDOW,
			Wtx, 
			Wty, 
			Wwd, 
			Wht,
			NULL,
			NULL,
			hInst,
			NULL
	mov   hwnd,eax

	invoke ShowWindow, hwnd,SW_SHOWNORMAL
	invoke UpdateWindow, hwnd

    ; get any command line filename
    invoke PathGetArgs,CommandLine
    mov lpFileName, eax

    mov esi, eax
    lodsb
    .if al != 0
         cmp al, 34
         jne @F          ; jump if no quote

             invoke PathUnquoteSpaces,lpFileName
             mov lpFileName, eax

  @@:

         invoke exist,lpFileName
		.if eax == 1
			invoke openafile,hwnd,lpFileName
		.else
			invoke MessageBeep,0
		.endif
	.endif

	.while TRUE
  @@:
        invoke GetMessage, ADDR msg,0,0,0
        .break .if (!eax)

	    ; ----------------------------------------------------
	    ; process some keystrokes directly in the message loop
	    ; ----------------------------------------------------
        .if msg.message == WM_KEYDOWN
	        .if msg.wParam == VK_CONTROL
                mov CtrlFlag, 1                 ; flag set
	        .endif
        .endif

        .if msg.message == WM_KEYUP
            .if msg.wParam == VK_CONTROL
                mov CtrlFlag, 0                 ; flag clear

            .elseif msg.wParam == 'O'           ; Ctrl + O
                .if CtrlFlag == 1
                    invoke SendMessage,hwnd,WM_COMMAND,IDM_OPEN,0 ; Open File
                    jmp @B
                .endif

            .elseif msg.wParam == 'S'           ; Ctrl + S
                .if CtrlFlag == 1
                    invoke SendMessage,hwnd,WM_COMMAND,IDM_FIND,0 ; Find Text
                    jmp @B
                .endif

            .elseif msg.wParam == 'L'           ; Ctrl + L
                .if CtrlFlag == 1
                    invoke SendMessage,hwnd,WM_COMMAND,IDM_FINDNEXT,0 ; Find Next
                    jmp @B
                .endif

            .elseif msg.wParam == 'N'           ; Ctrl + N
                .if CtrlFlag == 1
                    invoke SendMessage,hwnd,WM_COMMAND,IDM_CLOSE,0 ; Empty Buffer
                    jmp @B
                .endif

            .elseif msg.wParam == VK_F2         ; F2
                .if CtrlFlag == 0
                    invoke SendMessage,hwnd,WM_COMMAND,IDM_SAVE,0 ; File Save
                    jmp @B
                .endif

            .elseif msg.wParam == VK_F3         ; F3
                .if CtrlFlag == 0
                    invoke SendMessage,hwnd,WM_COMMAND,IDM_OPEN,0 ; File Open
                    jmp @B
                .endif

            .endif
        .endif
	    ; ------------------------------------------------

		invoke TranslateMessage, ADDR msg
		invoke DispatchMessage, ADDR msg

	.endw

	mov   eax,msg.wParam
	ret

WinMain endp

; Large file capable read

StreamInProc proc hhFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesRead:DWORD

	invoke ReadFile,hhFile,pBuffer,NumBytes,pBytesRead,0
	xor eax,1

	ret

StreamInProc endp

; Large file capable write

StreamOutProc proc hhFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesWritten:DWORD

	invoke WriteFile,hhFile,pBuffer,NumBytes,pBytesWritten,0
	xor eax,1

	ret

StreamOutProc endp

; Are you sure you're sure??

CheckModifyState proc hWnd:DWORD

	;==========================================================
	; check whether the undo queue is empty
	;==========================================================
	invoke SendMessage,hwndRichEdit,EM_CANUNDO,0,0
	.if eax==0
		;if no undo then text not modified, make it so.
		invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
	.endif

	invoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0
	.if eax!=0

		invoke MessageBox,hWnd,addr WannaSave,addr AppName,MB_YESNOCANCEL

		.if eax==IDYES
			invoke SendMessage,hWnd,WM_COMMAND,IDM_SAVE,0

		.elseif eax==IDCANCEL
			mov eax,FALSE

			ret

		.endif

	.endif

	mov eax,TRUE

	ret

CheckModifyState endp

; Set background and text color

SetColor proc
	LOCAL cfm:CHARFORMAT

	invoke SendMessage,hwndRichEdit,EM_SETBKGNDCOLOR,0,BackgroundColor
	invoke RtlZeroMemory,addr cfm,sizeof cfm

	mov cfm.cbSize,sizeof cfm
	mov cfm.dwMask,CFM_COLOR

	push TextColor
	pop cfm.crTextColor
	invoke SendMessage,hwndRichEdit,EM_SETCHARFORMAT,SCF_ALL,addr cfm

	ret

SetColor endp

; #########################################################################
; Find search string or beep on not

TextFind proc lpBuffer:DWORD, len:DWORD

    LOCAL tp :DWORD
    LOCAL tl :DWORD
    LOCAL sch:DWORD
    LOCAL ft :FINDTEXT
    LOCAL Cr :CHARRANGE

    ; 0 = case insensitive
    ; 2 = FT_WHOLEWORD
    ; 4 = FT_MATCHCASE
    ; 6 = FT_WHOLEWORD or FT_MATCHCASE

    mov sch, 0
    .if CaseFlag == 1
        or sch, 4
    .endif
    .if WholeWord == 1
        or sch, 2
    .endif
    or sch, FR_DOWN

    invoke SendMessage,hwndRichEdit,WM_GETTEXTLENGTH,0,0
    mov tl, eax

    invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,ADDR Cr

    inc Cr.cpMin                 ; inc starting pos by 1 so not searching
                                 ; same position repeatedly
    m2m ft.chrg.cpMin, Cr.cpMin
    m2m ft.chrg.cpMax, -1        ;
    m2m ft.lpstrText, lpBuffer   ; string to search for

    invoke SendMessage,hwndRichEdit,EM_FINDTEXT,sch,ADDR ft
    mov tp, eax

    .if eax == -1
        invoke MessageBeep,0

        ret

    .endif

    m2m Cr.cpMin,tp     ; put start pos into structure
    dec len             ; dec length for zero terminator
    mov eax, len
    add tp,eax          ; add length to character pos
    m2m Cr.cpMax,tp     ; put end pos into structure

    ; ------------------------------------
    ; set the selection to the search word
    ; ------------------------------------
    invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,ADDR Cr
    invoke SetFocus,hwndRichEdit ;restore focus

    ret

TextFind endp

; #########################################################################
; Search Dialog: set controls and do dialog

SearchProc proc hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD

    LOCAL hEdit      :DWORD

    .if uMsg == WM_INITDIALOG
        invoke GetDlgItem,hWin,3093
        mov hCheck1, eax
        invoke GetDlgItem,hWin,3094
        mov hCheck2, eax
        .if CaseFlag == 1
            invoke SendMessage,hCheck1,BM_SETCHECK,BST_CHECKED,0
        .endif
        .if WholeWord == 1
            invoke SendMessage,hCheck2,BM_SETCHECK,BST_CHECKED,0
        .endif
    .elseif uMsg == WM_COMMAND
        .if wParam == IDCANCEL            ; cancel button
            jmp OutaHere
        .elseif wParam == IDOK            ; default enter key
            jmp FindMe
        .elseif wParam == 3090            ; find button
  FindMe:
            invoke GetDlgItem,hWin,3092
            mov hEdit, eax
            invoke SendMessage,hEdit,WM_GETTEXTLENGTH,0,0
            mov TextLen, eax
            .if eax == 0
                ret
            .else
                invoke SendMessage,hCheck1,BM_GETCHECK,0,0
                .if eax == BST_CHECKED
                    mov CaseFlag, 1
                .else
                    mov CaseFlag, 0
                .endif
                invoke SendMessage,hCheck2,BM_GETCHECK,0,0
                .if eax == BST_CHECKED
                    mov WholeWord, 1
                .else
                    mov WholeWord, 0
                .endif
                inc TextLen
                invoke SendMessage,hEdit,WM_GETTEXT,TextLen,ADDR SearchText
                invoke TextFind,ADDR SearchText,TextLen
                jmp OutaHere
            .endif
        .elseif wParam == IDCANCEL  ; default escape button
            jmp OutaHere
        .endif
    .elseif uMsg == WM_CLOSE
  OutaHere:
        invoke EndDialog,hWin,0
    .endif

    return 0

SearchProc endp

; #########################################################################

WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
	LOCAL chrg:CHARRANGE
	LOCAL ofn:OPENFILENAME

	.if uMsg==WM_CREATE
		invoke CreateWindowEx,
				0,
				addr RichEditClass,
				0,
                WS_VISIBLE or ES_SUNKEN or \
                WS_CHILDWINDOW or WS_CLIPSIBLINGS or \
                ES_MULTILINE or WS_VSCROLL or \
                ES_AUTOVSCROLL or ES_NOHIDESEL or \
                WS_HSCROLL or ES_AUTOHSCROLL,
				CW_USEDEFAULT,
				CW_USEDEFAULT, 
				CW_USEDEFAULT, 
				CW_USEDEFAULT,
				hWnd,
				RichEditID,
				hInstance,
				0
		mov hwndRichEdit,eax

		;=============================================================
		; Set the text limit. The default is 64K
		;=============================================================
		invoke SendMessage,hwndRichEdit,EM_EXLIMITTEXT,0,100000000
		;=============================================================
		; Set the default text/background color and font
		;=============================================================
		invoke SetColor
		invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
		invoke SendMessage,hwndRichEdit,EM_EMPTYUNDOBUFFER,0,0
		invoke GetStockObject,SYSTEM_FIXED_FONT
		invoke SendMessage,hwndRichEdit,WM_SETFONT,eax,0

	.elseif uMsg==WM_COMMAND
		.if lParam==0		; menu commands
			mov eax,wParam
			.if ax==IDM_OPEN
				invoke RtlZeroMemory,addr ofn,sizeof ofn
				mov ofn.lStructSize,sizeof ofn

				push hWnd
				pop ofn.hWndOwner

				push hInstance
				pop ofn.hInstance

				mov ofn.lpstrFilter,offset ASMFilterString
				mov ofn.lpstrFile,offset FileName

				mov byte ptr [FileName],0
				mov ofn.nMaxFile,sizeof FileName

				mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST
				invoke GetOpenFileName,addr ofn
				.if eax!=0
				    mov lpFileName,offset FileName
				    invoke openafile,hWnd,lpFileName
				.endif

			.elseif ax==IDM_CLOSE
				invoke CheckModifyState,hWnd
				.if eax==TRUE
					invoke SetWindowText,hwndRichEdit,0
					mov FileOpened,FALSE
				.endif

			.elseif ax==IDM_SAVEAS
				invoke RtlZeroMemory,addr ofn,sizeof ofn
				mov ofn.lStructSize,sizeof ofn

				push hWnd
				pop ofn.hWndOwner

				push hInstance
				pop ofn.hInstance

				mov ofn.lpstrFilter,offset ASMFilterString
				mov ofn.lpstrFile,offset AlternateFileName

				mov byte ptr [AlternateFileName],0
				mov ofn.nMaxFile,sizeof AlternateFileName

				mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
				invoke GetSaveFileName,addr ofn
				.if eax!=0
				    mov lpFileName,offset AlternateFileName
					jmp @F   ; use IDM_SAVE common code

				.endif

			.elseif ax==IDM_SAVE
  @@:				
				invoke CreateFile,lpFileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
				.if eax!=INVALID_HANDLE_VALUE
					mov hFile,eax

					;================================================================
					; stream the text to the file
					;================================================================						
					mov editstream.dwCookie,eax
					mov editstream.pfnCallback,offset StreamOutProc
					invoke SendMessage,hwndRichEdit,EM_STREAMOUT,SF_TEXT,addr editstream

					;==========================================================
					; Initialize the modify state to false
					;==========================================================
					invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
					invoke CloseHandle,hFile

				.else
					invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR
				.endif

			.elseif ax==IDM_COPY
				invoke SendMessage,hwndRichEdit,WM_COPY,0,0

			.elseif ax==IDM_CUT
				invoke SendMessage,hwndRichEdit,WM_CUT,0,0

			.elseif ax==IDM_PASTE
				invoke SendMessage,hwndRichEdit,WM_PASTE,0,0

			.elseif ax==IDM_DELETE
				invoke SendMessage,hwndRichEdit,EM_REPLACESEL,TRUE,0

	        .elseif ax==IDM_FIND
  mnu_FindText:
			    invoke DialogBoxParam,hInstance,IDD_FINDDLG,hWnd,ADDR SearchProc,0
	            invoke SetFocus, hwndRichEdit

	        .elseif ax==IDM_FINDNEXT
  mnu_FindNext:
	            invoke TextFind,ADDR SearchText,TextLen

			.elseif ax==IDM_SELECTALL
				mov chrg.cpMin,0
				mov chrg.cpMax,-1
				invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr chrg

			.elseif ax==IDM_UNDO
				invoke SendMessage,hwndRichEdit,EM_UNDO,0,0

			.elseif ax==IDM_REDO
				invoke SendMessage,hwndRichEdit,EM_REDO,0,0

			.elseif ax==IDM_EXIT
				invoke SendMessage,hWnd,WM_CLOSE,0,0

			.elseif ax==IDM_ABOUT
				szText RichEd,\
				"micro RichEdit",13,10,\
				"Copyright MASM32 2001"
				invoke MessageBox,hIcon,ADDR RichEd,ADDR AppName,MB_OK or MB_USERICON

			.endif

		.endif

	.elseif uMsg==WM_CLOSE
		invoke CheckModifyState,hWnd
		.if eax==TRUE
			invoke DestroyWindow,hWnd
		.endif

	.elseif uMsg == WM_SETFOCUS
		invoke SetFocus,hwndRichEdit

	.elseif uMsg==WM_SIZE
		mov eax,lParam
		mov edx,eax
		and eax,0FFFFh
		shr edx,16
		invoke MoveWindow,hwndRichEdit,0,0,eax,edx,TRUE		

	.elseif uMsg==WM_DESTROY
		invoke PostQuitMessage,NULL

	.else
		invoke DefWindowProc,hWnd,uMsg,wParam,lParam		
		ret

	.endif

	xor eax,eax
	ret

WndProc endp

end start
