![]() |
Wyjście Spis treści Poprzedni
Autor:
©Iczelion |
©2008 mgr Jerzy Wałaszek I LO w Tarnowie
|
Przyjmij moje ostrzeżenie, zanim zaczniesz czytać ten rozdział. Poruszane tutaj tematy należą do trudnych i nie nadają się na materiał dla początkujących. Jest to ostatnia lekcja dotycząca kontrolki RichEdit.

Załaduj {ten przykład}
Podświetlanie składni (syntax hilighting) jest gorąco komentowane wśród twórców różnych edytorów tekstowych. Najlepszym rozwiązaniem (według mnie) jest zaprojektowanie własnej kontrolki edycyjnej i droga ta jest wybierana przez wiele komercyjnych firm programistycznych. Jednakże dla tych z nas, którym brakuje czasu na zaprogramowanie takiej kontrolki, następnym najlepszym rozwiązaniem jest takie zaadoptowanie istniejącej kontrolki, aby spełniała nasze potrzeby.
Spójrzmy na to, co udostępnia nam kontrolka RichEdit, aby nas wspomóż przy implementacji podświetlania składni. Od razu zastrzegam sobie, iż prezentowana metoda nie jest właściwa: po prostu chcę pokazać pułapki, w które wielu wpada. Kontrolka RichEdit udostępnia wiadomość EM_SETCHARFORMAT, którą można wykorzystać do zmiany koloru tekstu. Na pierwszy rzut oka wiadomość ta wydaje się idealnym rozwiązaniem (wiem to, bo sam kiedyś dałem się nabrać). Jednakże uważniejsze zbadanie jej ujawni kilka niepożądanych rzeczy:
Mając powyższe na względzie widzisz, że wykorzystanie EM_SETCHARFORMAT nie jest dobrym wyborem. Pokażę ci "względnie poprawny" wybór.
Metoda, z której obecnie korzystam, może być nazwana "podświetlanie składni na bieżąco". Podświetlam jedynie widoczną część tekstu. Stąd prędkość podświetlania w ogóle nie zależy od rozmiaru pliku. Bez względu na wielkość pliku tylko jego mała część jest w danym momencie widoczna.
Jak tego dokonać? Odpowiedź jest prosta:
Oczywiście nie jest to aż tak proste: wciąż istnieje kilka ważnych elementów do rozwiązania, lecz powyższa metoda pracuje całkiem ładnie. Szybkość wyświetlania jest bardzo zadowalająca.
Teraz skoncentrujmy się na szczegółach. Proces przejmowania procedury okna jest prosty i nie wymaga wiele uwagi. Jedynym skomplikowanym elementem tej układanki jest znalezienie szybkiego sposobu wyszukiwania słów, które mają być podświetlane. Sprawa dalej się komplikuje, jeśli przyjmiemy, iż w obrębie komentarzy słowa te nie będą podświetlane (to samo powinno dotyczyć łańcuchów tekstu).
Metoda, z której korzystam, może nie być najlepsza, ale działa poprawnie. Na pewno znajdziesz szybszy sposób. W każdym razie oto ona:
WORDINFO STRUCT WordLen DD ? ; długość słowa do szybkiego porównania pszWord DD ? ; wskazanie słowa pColor DD ? ; wskazanie koloru podświetlenia NextLink DD ? ; wskazanie następnej struktury WORDINFO WORDINFO ENDS
Jak widać do drugiego szybkiego porównania stosuję długość słowa. Jeśli pierwszy znak wyrazu zgadza się z pierwszym znakiem podświetlanego słowa, porównujemy jego długość z długościami dostępnych słów. Każdy element tablicy ASMSyntaxArray zawiera wskazanie nagłówka związanej z nim struktury WORDINFO. Na przykład element reprezentujący znak "i" będzie zawierał wskazanie połączonej listy słów, które rozpoczynają się na literę "i". Pole pColor wskazuje zmienną DWORD zawierającą wartość koloru, który ma zostać użyty przy podświetlaniu słowa. Dlaczego nie umieszczamy tutaj po prostu koloru? Odpowiedź jest prosta. Pola te wskazują na elementy palety definiującej kolory dla kilku grup podświetlanych wyrazów. Jeśli będziemy chcieli zmienić w przyszłości kolory podświetleń, to wystarczy zmienić kolor w palecie dla danej grupy, a będzie on już automatycznie obowiązywał dla wszystkich wyrazów w tej grupie. Inaczej musielibyśmy wyszukiwać wszystkie wyrazy danej grupy i indywidualnie zmieniać im kolory - co najmniej kłopotliwe. Moje podejście jest zatem dużo lepsze i rokuje możliwość implementacji wyboru podświetleń dokonywanego przez użytkownika. Pole pszWord wskazuje słowo, które należy podświetlać, zapisane małymi literkami.
Lista słów jest umieszczona w pliku o nazwie "wordfile.txt" i uzyskuję do niej dostęp poprzez funkcję API GetPrivateProfileString. Udostępniam 10 różnych kolorów podświetleń, poczynając od C1 do C10. Tablica kolorów nosi nazwę ASMColorArray. Pole pColor każdej struktury WORDINFO wskazuje jeden z elementów DWORD tej tablicy. Zatem łatwo jest zmieniać kolory podświetlenia w locie, zmieniasz element DWORD w tablicy ASMColorArray, a wszystkie słowo używające tego koloru będą natychmiast stosowały nowy.
Plik ICZEDIT.ASM
.386
.MODEL FLAT, STDCALL
OPTION CASEMAP:NONE
INCLUDE \masm32\include\windows.inc
INCLUDE \masm32\include\user32.inc
INCLUDE \masm32\include\comdlg32.inc
INCLUDE \masm32\include\gdi32.inc
INCLUDE \masm32\include\kernel32.inc
INCLUDELIB \masm32\lib\gdi32.lib
INCLUDELIB \masm32\lib\comdlg32.lib
INCLUDELIB \masm32\lib\user32.lib
INCLUDELIB \masm32\lib\kernel32.lib
WinMain PROTO :DWORD, :DWORD, :DWORD, :DWORD
WORDINFO STRUCT
WordLen DD ? ; długość słowa: używane do szybkiego porównywania
pszWord DD ? ; wskazanie słowa
pColor DD ? ; wskazanie koloru podświetlenia
NextLink DD ? ; wskazanie następnej struktury WORDINFO
WORDINFO ENDS
.CONST
IDR_MAINMENU EQU 101
IDD_OPTIONDLG EQU 101
IDD_FINDDLG EQU 102
IDD_GOTODLG EQU 103
IDD_REPLACEDLG EQU 104
IDR_MAINACCEL EQU 105
RichEditID EQU 300
IDC_BACKCOLORBOX EQU 1000
IDC_TEXTCOLORBOX EQU 1001
IDC_FINDEDIT EQU 1000
IDC_MATCHCASE EQU 1001
IDC_REPLACEEDIT EQU 1001
IDC_WHOLEWORD EQU 1002
IDC_DOWN EQU 1003
IDC_UP EQU 1004
IDC_LINENO EQU 1005
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_OPTION EQU 40011
IDM_UNDO EQU 40012
IDM_REDO EQU 40013
IDM_FIND EQU 40014
IDM_FINDNEXT EQU 40015
IDM_REPLACE EQU 40016
IDM_GOTOLINE EQU 40017
IDM_FINDPREV EQU 40018
.DATA
ClassName DB "IczEditClass", 0
AppName DB "IczEdit wersja 3.0", 0
RichEditDLL DB "riched20.dll", 0
RichEditClass DB "RichEdit20A", 0
NoRichEdit DB "Nie można odnaleźć biblioteki riched20.dll", 0
ASMFilterString DB "Kod źródłowy ASM (*.asm)", 0, "*.asm", 0,
"Wszystkie pliki (*.*)", 0, "*.*", 0, 0
OpenFileFail DB "Nie można otworzyć pliku", 0
WannaSave DB "Dane w edytorze zostały zmodyfikowane. Zapisać je?", 0
FileOpened DD FALSE
BackgroundColor DD 0FFFFFFh ; standardowy kolor tła - biały
TextColor DD 0 ; standardowy kolor tekstu - czarny
WordFileName DB "\wordfile.txt", 0
ASMSection DB "ASSEMBLY", 0
C1Key DB "C1", 0, 0
C2Key DB "C2", 0, 0
C3Key DB "C3", 0, 0
C4Key DB "C4", 0, 0
C5Key DB "C5", 0, 0
C6Key DB "C6", 0, 0
C7Key DB "C7", 0, 0
C8Key DB "C8", 0, 0
C9Key DB "C9", 0, 0
C10Key DB "C10", 0
ZeroString DB 0
ASMColorArray DD 0FF0000h, 0805F50h, 0FFh, 666F00h, 44F0h,
5F8754h, 4 DUP(0FF0000h)
CmntColor DD 808000h
.DATA?
hInstance DD ?
hRichEdit DD ?
hwndRichEdit DD ?
FileName DB 256 DUP(?)
AlternateFileName DB 256 DUP(?)
CustomColors DD 16 DUP(?)
FindBuffer DB 256 DUP(?)
ReplaceBuffer DB 256 DUP(?)
uFlags DD ?
findtext FINDTEXTEX <>
ASMSyntaxArray DD 256 DUP(?)
hSearch DD ? ; uchwyt okna dialogowego szukania/zamiany
hAccel DD ?
hMainHeap DD ? ; uchwyt stosu
OldWndProc DD ?
RichEditVersion DD ?
.CODE
start:
mov BYTE PTR [FindBuffer], 0
mov BYTE PTR [ReplaceBuffer], 0
INVOKE GetModuleHandle, NULL
mov hInstance, eax
INVOKE LoadLibrary, ADDR RichEditDLL
.IF eax!=0
mov hRichEdit, eax
INVOKE GetProcessHeap
mov hMainHeap, eax
; Ładujemy słowa, które będą podświetlane
call FillHiliteInfo
INVOKE WinMain, hInstance, 0, 0, SW_SHOWDEFAULT
INVOKE FreeLibrary, hRichEdit
.ELSE
INVOKE MessageBox, 0, ADDR NoRichEdit, ADDR AppName,
MB_OK OR MB_ICONERROR
.ENDIF
INVOKE ExitProcess, eax
WinMain PROC hInst:DWORD, hPrevInst:DWORD, CmdLine:DWORD, CmdShow:DWORD
LOCAL wc : WNDCLASSEX
LOCAL msg : MSG
LOCAL hwnd : 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, NULL, IDI_APPLICATION
mov wc.hIcon, eax
mov wc.hIconSm, eax
INVOKE LoadCursor, NULL, IDC_ARROW
mov wc.hCursor, eax
INVOKE RegisterClassEx, ADDR wc
INVOKE CreateWindowEx, NULL, ADDR ClassName, ADDR AppName,
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInst, NULL
mov hwnd, eax
INVOKE ShowWindow, hwnd, SW_SHOWNORMAL
INVOKE UpdateWindow, hwnd
INVOKE LoadAccelerators, hInstance, IDR_MAINACCEL
mov hAccel, eax
.WHILE TRUE
INVOKE GetMessage, ADDR msg, 0, 0, 0
.BREAK .IF (!eax)
INVOKE IsDialogMessage, hSearch, ADDR msg
.IF eax==FALSE
INVOKE TranslateAccelerator, hwnd, hAccel, ADDR msg
.IF eax==0
INVOKE TranslateMessage, ADDR msg
INVOKE DispatchMessage, ADDR msg
.ENDIF
.ENDIF
.ENDW
mov eax, msg.wParam
ret
WinMain ENDP
StreamInProc PROC hFile: DWORD,
pBuffer: DWORD,
NumBytes: DWORD,
pBytesRead: DWORD
INVOKE ReadFile, hFile, pBuffer, NumBytes, pBytesRead, 0
xor eax, 1
ret
StreamInProc ENDP
StreamOutProc PROC hFile: DWORD,
pBuffer: DWORD,
NumBytes: DWORD,
pBytesWritten: DWORD
INVOKE WriteFile, hFile, pBuffer, NumBytes, pBytesWritten, 0
xor eax, 1
ret
StreamOutProc ENDP
CheckModifyState PROC hWnd:DWORD
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
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
OptionProc PROC hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
LOCAL clr: CHOOSECOLOR
.IF uMsg==WM_INITDIALOG
.ELSEIF uMsg==WM_COMMAND
mov ax, WORD PTR wParam + 2
.IF ax==BN_CLICKED
mov eax, wParam
.IF ax==IDCANCEL
INVOKE SendMessage, hWnd, WM_CLOSE, 0, 0
.ELSEIF ax==IDC_BACKCOLORBOX
INVOKE RtlZeroMemory, ADDR clr, SIZEOF clr
mov clr.lStructSize, SIZEOF clr
push hWnd
pop clr.hwndOwner
push hInstance
pop clr.hInstance
push BackgroundColor
pop clr.rgbResult
mov clr.lpCustColors, OFFSET CustomColors
mov clr.Flags, CC_ANYCOLOR OR CC_RGBINIT
INVOKE ChooseColor, ADDR clr
.IF eax!=0
push clr.rgbResult
pop BackgroundColor
INVOKE GetDlgItem, hWnd, IDC_BACKCOLORBOX
INVOKE InvalidateRect, eax, 0, TRUE
.ENDIF
.ELSEIF ax==IDC_TEXTCOLORBOX
INVOKE RtlZeroMemory, ADDR clr, SIZEOF clr
mov clr.lStructSize, SIZEOF clr
push hWnd
pop clr.hwndOwner
push hInstance
pop clr.hInstance
push TextColor
pop clr.rgbResult
mov clr.lpCustColors, OFFSET CustomColors
mov clr.Flags, CC_ANYCOLOR OR CC_RGBINIT
INVOKE ChooseColor, ADDR clr
.IF eax!=0
push clr.rgbResult
pop TextColor
INVOKE GetDlgItem, hWnd, IDC_TEXTCOLORBOX
INVOKE InvalidateRect, eax, 0, TRUE
.ENDIF
.ELSEIF ax==IDOK
; Zachowujemy stan modyfikacji kontrolki RichEdit, ponieważ zmiana
; koloru zmienia również stan modyfikacji
INVOKE SendMessage, hwndRichEdit, EM_GETMODIFY, 0, 0
push eax
INVOKE SetColor
pop eax
INVOKE SendMessage, hwndRichEdit, EM_SETMODIFY, eax, 0
INVOKE EndDialog, hWnd, 0
.ENDIF
.ENDIF
.ELSEIF uMsg==WM_CTLCOLORSTATIC
INVOKE GetDlgItem, hWnd, IDC_BACKCOLORBOX
.IF eax==lParam
INVOKE CreateSolidBrush, BackgroundColor
ret
.ELSE
INVOKE GetDlgItem, hWnd, IDC_TEXTCOLORBOX
.IF eax==lParam
INVOKE CreateSolidBrush, TextColor
ret
.ENDIF
.ENDIF
mov eax, FALSE
ret
.ELSEIF uMsg==WM_CLOSE
INVOKE EndDialog, hWnd, 0
.ELSE
mov eax, FALSE
ret
.ENDIF
mov eax, TRUE
ret
OptionProc ENDP
SearchProc PROC hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.IF uMsg==WM_INITDIALOG
push hWnd
pop hSearch
; Standardowo wybieramy opcję poszukiwań w dół
INVOKE CheckRadioButton, hWnd, IDC_DOWN, IDC_UP, IDC_DOWN
INVOKE SendDlgItemMessage, hWnd, IDC_FINDEDIT, WM_SETTEXT, 0,
ADDR FindBuffer
.ELSEIF uMsg==WM_COMMAND
mov ax, WORD PTR wParam + 2
.IF ax==BN_CLICKED
mov eax, wParam
.IF ax==IDOK
mov uFlags, 0
INVOKE SendMessage, hwndRichEdit, EM_EXGETSEL, 0,
ADDR findtext.chrg
INVOKE GetDlgItemText, hWnd, IDC_FINDEDIT,
ADDR FindBuffer, SIZEOF FindBuffer
.IF eax!=0
INVOKE IsDlgButtonChecked, hWnd, IDC_DOWN
.IF eax==BST_CHECKED
or uFlags, FR_DOWN
mov eax, findtext.chrg.cpMin
.IF eax!=findtext.chrg.cpMax
push findtext.chrg.cpMax
pop findtext.chrg.cpMin
.ENDIF
mov findtext.chrg.cpMax, -1
.ELSE
mov findtext.chrg.cpMax, 0
.ENDIF
INVOKE IsDlgButtonChecked, hWnd, IDC_MATCHCASE
.IF eax==BST_CHECKED
or uFlags, FR_MATCHCASE
.ENDIF
INVOKE IsDlgButtonChecked, hWnd, IDC_WHOLEWORD
.IF eax==BST_CHECKED
or uFlags, FR_WHOLEWORD
.ENDIF
mov findtext.lpstrText, OFFSET FindBuffer
INVOKE SendMessage, hwndRichEdit, EM_FINDTEXTEX,
uFlags, ADDR findtext
.IF eax!=-1
INVOKE SendMessage, hwndRichEdit, EM_EXSETSEL,
0, ADDR findtext.chrgText
.ENDIF
.ENDIF
.ELSEIF ax==IDCANCEL
INVOKE SendMessage, hWnd, WM_CLOSE, 0, 0
.ELSE
mov eax, FALSE
ret
.ENDIF
.ENDIF
.ELSEIF uMsg==WM_CLOSE
mov hSearch, 0
INVOKE EndDialog, hWnd, 0
.ELSE
mov eax, FALSE
ret
.ENDIF
mov eax, TRUE
ret
SearchProc ENDP
ReplaceProc PROC hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
LOCAL settext: SETTEXTEX
.IF uMsg==WM_INITDIALOG
push hWnd
pop hSearch
INVOKE SetDlgItemText, hWnd, IDC_FINDEDIT, ADDR FindBuffer
INVOKE SetDlgItemText, hWnd, IDC_REPLACEEDIT, ADDR ReplaceBuffer
.ELSEIF uMsg==WM_COMMAND
mov ax, WORD PTR wParam + 2
.IF ax==BN_CLICKED
mov eax, wParam
.IF ax==IDCANCEL
INVOKE SendMessage, hWnd, WM_CLOSE, 0, 0
.ELSEIF ax==IDOK
INVOKE GetDlgItemText, hWnd, IDC_FINDEDIT,
ADDR FindBuffer, SIZEOF FindBuffer
INVOKE GetDlgItemText, hWnd, IDC_REPLACEEDIT,
ADDR ReplaceBuffer, SIZEOF ReplaceBuffer
; Sprawdzamy, czy teksty do znalezienia i zastąpienia są różne. Jeśli
; tak, to wykonujemy operację zastąpienia. W przeciwnym wypadku nic
; nie robimy. Sprawdzenie to jest konieczne, ponieważ może dojść do
; zablokowania aplikacji z uwagi na przyjęty sposób pracy tej procedury.
INVOKE lstrcmp, ADDR FindBuffer, ADDR ReplaceBuffer
.IF eax!=0
mov findtext.chrg.cpMin, 0
mov findtext.chrg.cpMax, -1
mov findtext.lpstrText, OFFSET FindBuffer
mov settext.flags, ST_SELECTION
mov settext.codepage, CP_ACP
.WHILE TRUE
INVOKE SendMessage, hwndRichEdit, EM_FINDTEXTEX,
FR_DOWN OR FR_MATCHCASE,
ADDR findtext
.BREAK .IF eax==-1
INVOKE SendMessage, hwndRichEdit, EM_EXSETSEL, 0,
ADDR findtext.chrgText
INVOKE SendMessage, hwndRichEdit, EM_SETTEXTEX,
ADDR settext, ADDR ReplaceBuffer
.ENDW
.ENDIF
.ENDIF
.ENDIF
.ELSEIF uMsg==WM_CLOSE
mov hSearch, 0
INVOKE EndDialog, hWnd, 0
.ELSE
mov eax, FALSE
ret
.ENDIF
mov eax, TRUE
ret
ReplaceProc ENDP
GoToProc PROC hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
LOCAL LineNo: DWORD
LOCAL chrg: CHARRANGE
.IF uMsg==WM_INITDIALOG
push hWnd
pop hSearch
.ELSEIF uMsg==WM_COMMAND
mov ax, WORD PTR wParam + 2
.IF ax==BN_CLICKED
mov eax, wParam
.IF ax==IDCANCEL
INVOKE SendMessage, hWnd, WM_CLOSE, 0, 0
.ELSEIF ax==IDOK
INVOKE GetDlgItemInt, hWnd, IDC_LINENO, NULL, FALSE
mov LineNo, eax
INVOKE SendMessage, hwndRichEdit, EM_GETLINECOUNT, 0, 0
.IF eax>LineNo
INVOKE SendMessage, hwndRichEdit, EM_LINEINDEX, LineNo, 0
INVOKE SendMessage, hwndRichEdit, EM_SETSEL, eax, eax
INVOKE SetFocus, hwndRichEdit
.ENDIF
.ENDIF
.ENDIF
.ELSEIF uMsg==WM_CLOSE
mov hSearch, 0
INVOKE EndDialog, hWnd, 0
.ELSE
mov eax, FALSE
ret
.ENDIF
mov eax, TRUE
ret
GoToProc ENDP
PrepareEditMenu PROC hSubMenu:DWORD
LOCAL chrg: CHARRANGE
; Sprawdzamy, czy w schowku jest jakiś tekst.
; Jeśli tak, to uaktywniamy element menu Wklej
INVOKE SendMessage, hwndRichEdit, EM_CANPASTE, CF_TEXT, 0
.IF eax==0 ; w schowku brak tekstu
INVOKE EnableMenuItem, hSubMenu, IDM_PASTE, MF_GRAYED
.ELSE
INVOKE EnableMenuItem, hSubMenu, IDM_PASTE, MF_ENABLED
.ENDIF
; Sprawdzamy, czy kolejka cofnięć jest pusta
INVOKE SendMessage, hwndRichEdit, EM_CANUNDO, 0, 0
.IF eax==0
INVOKE EnableMenuItem, hSubMenu, IDM_UNDO, MF_GRAYED
.ELSE
INVOKE EnableMenuItem, hSubMenu, IDM_UNDO, MF_ENABLED
.ENDIF
; Sprawdzamy, czy kolejka ponowień jest pusta
INVOKE SendMessage, hwndRichEdit, EM_CANREDO, 0, 0
.IF eax==0
INVOKE EnableMenuItem, hSubMenu, IDM_REDO, MF_GRAYED
.ELSE
INVOKE EnableMenuItem, hSubMenu, IDM_REDO, MF_ENABLED
.ENDIF
; Sprawdzamy, czy w kontrolce RichEdit istnieje bieżące zaznaczenie
; Jeśli tak, włączamy elementy menu Wytnij/Kopiuj/Usuń
INVOKE SendMessage, hwndRichEdit, EM_EXGETSEL, 0, ADDR chrg
mov eax, chrg.cpMin
.IF eax==chrg.cpMax ; brak bieżącego wyboru
INVOKE EnableMenuItem, hSubMenu, IDM_COPY, MF_GRAYED
INVOKE EnableMenuItem, hSubMenu, IDM_CUT, MF_GRAYED
INVOKE EnableMenuItem, hSubMenu, IDM_DELETE, MF_GRAYED
.ELSE
INVOKE EnableMenuItem, hSubMenu, IDM_COPY, MF_ENABLED
INVOKE EnableMenuItem, hSubMenu, IDM_CUT, MF_ENABLED
INVOKE EnableMenuItem, hSubMenu, IDM_DELETE, MF_ENABLED
.ENDIF
ret
PrepareEditMenu ENDP
ParseBuffer PROC USES edi esi hHeap: DWORD,
pBuffer: DWORD,
nSize: DWORD,
ArrayOffset: DWORD,
pArray: DWORD
LOCAL buffer[128]: BYTE
LOCAL InProgress: DWORD
mov InProgress, FALSE
lea esi, buffer
mov edi, pBuffer
INVOKE CharLower, edi
mov ecx, nSize
SearchLoop:
or ecx, ecx
jz Finished
cmp BYTE PTR [edi], " "
je EndOfWord
cmp BYTE PTR [edi], 9 ; tab
je EndOfWord
mov InProgress, TRUE
mov al, BYTE PTR [edi]
mov BYTE PTR [esi], al
inc esi
SkipIt:
inc edi
dec ecx
jmp SearchLoop
EndOfWord:
cmp InProgress, TRUE
je WordFound
jmp SkipIt
WordFound:
mov BYTE PTR [esi], 0
push ecx
; umieszczamy słowo w strukturze WORDINFO
INVOKE HeapAlloc, hHeap, HEAP_ZERO_MEMORY, SIZEOF WORDINFO
push esi
mov esi, eax
ASSUME esi:PTR WORDINFO
INVOKE lstrlen, ADDR buffer
mov [esi].WordLen, eax
push ArrayOffset
pop [esi].pColor
inc eax
INVOKE HeapAlloc, hHeap, HEAP_ZERO_MEMORY, eax
mov [esi].pszWord, eax
mov edx, eax
INVOKE lstrcpy, edx, ADDR buffer
mov eax, pArray
movzx edx, BYTE PTR [buffer]
shl edx, 2 ; mnożenie przez 4
add eax, edx
.IF DWORD PTR [eax]==0
mov DWORD PTR [eax], esi
.ELSE
push DWORD PTR [eax]
pop [esi].NextLink
mov DWORD PTR [eax], esi
.ENDIF
pop esi
pop ecx
lea esi, buffer
mov InProgress, FALSE
jmp SkipIt
Finished:
.IF InProgress==TRUE
; Umieszczamy słowo w strukturze WORDINFO
INVOKE HeapAlloc, hHeap, HEAP_ZERO_MEMORY, SIZEOF WORDINFO
push esi
mov esi, eax
ASSUME esi:PTR WORDINFO
INVOKE lstrlen, ADDR buffer
mov [esi].WordLen, eax
push ArrayOffset
pop [esi].pColor
inc eax
INVOKE HeapAlloc, hHeap, HEAP_ZERO_MEMORY, eax
mov [esi].pszWord, eax
mov edx, eax
INVOKE lstrcpy, edx, ADDR buffer
mov eax, pArray
movzx edx, BYTE PTR [buffer]
shl edx, 2 ; mnożenie przez 4
add eax, edx
.IF DWORD PTR [eax]==0
mov DWORD PTR [eax], esi
.ELSE
push DWORD PTR [eax]
pop [esi].NextLink
mov DWORD PTR [eax], esi
.ENDIF
pop esi
.ENDIF
ret
ParseBuffer ENDP
FillHiliteInfo PROC USES edi
LOCAL buffer[1024]: BYTE
LOCAL pTemp: DWORD
LOCAL BlockSize: DWORD
; Zerujemy tablicę
INVOKE RtlZeroMemory, ADDR ASMSyntaxArray, SIZEOF ASMSyntaxArray
; Odczytujemy ścieżkę dyskową tego egzemplarza aplikacji
INVOKE GetModuleFileName, hInstance, ADDR buffer, SIZEOF buffer
INVOKE lstrlen, ADDR buffer
mov ecx, eax
dec ecx
lea edi, buffer
add edi, ecx
std
mov al, "\"
repne scasb
cld
inc edi
mov BYTE PTR [edi], 0
INVOKE lstrcat, ADDR buffer, ADDR WordFileName
; Sprawdzamy istnienie pliku
INVOKE GetFileAttributes, ADDR buffer
.IF eax!=-1
; Przydzielamy ze stosu blok pamięci na łańcuchy znakowe
mov BlockSize, 1024*10
INVOKE HeapAlloc, hMainHeap, 0, BlockSize
mov pTemp, eax
mov ecx, OFFSET C1Key
@@:
push ecx
INVOKE GetPrivateProfileString, ADDR ASMSection, ecx,
ADDR ZeroString, pTemp,
BlockSize, ADDR buffer
pop ecx
.IF eax!=0
inc eax
.IF eax==BlockSize ; bufor jest za mały
add BlockSize, 1024*10
push ecx
INVOKE HeapReAlloc, hMainHeap, 0, pTemp, BlockSize
pop ecx
mov pTemp, eax
jmp @B
.ENDIF
mov edx, ecx
sub edx, OFFSET C1Key
add edx, OFFSET ASMColorArray
push ecx
INVOKE ParseBuffer, hMainHeap, pTemp, eax, edx, ADDR ASMSyntaxArray
pop ecx
.ENDIF
add ecx, 4
cmp ecx, 4 + OFFSET C10Key
jne @B
INVOKE HeapFree, hMainHeap, 0, pTemp
.ENDIF
ret
FillHiliteInfo ENDP
NewRichEditProc PROC hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
LOCAL hdc: DWORD
LOCAL hOldFont: DWORD
LOCAL FirstChar: DWORD
LOCAL rect: RECT
LOCAL txtrange: TEXTRANGE
LOCAL buffer[1024*10]: BYTE
LOCAL hRgn: DWORD
LOCAL hOldRgn: DWORD
LOCAL RealRect: RECT
LOCAL pString: DWORD
LOCAL BufferSize: DWORD
LOCAL pt: POINT
.IF uMsg==WM_PAINT
push edi
push esi
INVOKE HideCaret, hWnd
INVOKE CallWindowProc, OldWndProc, hWnd, uMsg, wParam, lParam
push eax
mov edi, OFFSET ASMSyntaxArray
INVOKE GetDC, hWnd
mov hdc, eax
INVOKE SetBkMode, hdc, TRANSPARENT
; Tutaj podświetlamy składnię
INVOKE SendMessage, hWnd, EM_GETRECT, 0, ADDR rect
INVOKE SendMessage, hWnd, EM_CHARFROMPOS, 0, ADDR rect
; Odczytujemy numer wiersza
INVOKE SendMessage, hWnd, EM_LINEFROMCHAR, eax, 0
INVOKE SendMessage, hWnd, EM_LINEINDEX, eax, 0
mov txtrange.chrg.cpMin, eax
mov FirstChar, eax
INVOKE SendMessage, hWnd, EM_CHARFROMPOS, 0, ADDR rect.right
mov txtrange.chrg.cpMax, eax
push rect.left
pop RealRect.left
push rect.top
pop RealRect.top
push rect.right
pop RealRect.right
push rect.bottom
pop RealRect.bottom
INVOKE CreateRectRgn, RealRect.left, RealRect.top,
RealRect.right, RealRect.bottom
mov hRgn, eax
INVOKE SelectObject, hdc, hRgn
mov hOldRgn, eax
INVOKE SetTextColor, hdc, CmntColor
; Pobieramy widoczny tekst do bufora
lea eax, buffer
mov txtrange.lpstrText, eax
INVOKE SendMessage, hWnd, EM_GETTEXTRANGE, 0, ADDR txtrange
mov esi, eax ; esi == rozmiar tekstu
.IF esi>0
mov BufferSize, eax
; Najpierw szukamy komentarza
push edi
push ebx
lea edi, buffer
mov edx, edi ; używane jako punkt odniesienia
mov ecx, esi
mov al, ";"
ScanMore:
repne scasb
je NextSkip
jmp NoMoreHit
NextSkip:
dec edi
inc ecx
mov pString, edi
mov ebx, edi
sub ebx, edx
add ebx, FirstChar
mov txtrange.chrg.cpMin, ebx
; Szukamy końca wiersza lub końca bufora
push eax
mov al, 0Dh
repne scasb
pop eax
HiliteTheComment:
.IF ecx>0
mov BYTE PTR [edi-1], 0
.ENDIF
mov ebx, edi
sub ebx, edx
add ebx, FirstChar
mov txtrange.chrg.cpMax, ebx
pushad
; Teraz wyszukujemy w zakresie tabulacje
mov edi, pString
mov esi, txtrange.chrg.cpMax
sub esi, txtrange.chrg.cpMin ; esi zawiera długość bufora
mov eax, esi
push edi
.WHILE eax>0
.IF BYTE PTR [edi]==9
mov BYTE PTR [edi], 0
.ENDIF
inc edi
dec eax
.ENDW
pop edi
.WHILE esi>0
.IF BYTE PTR [edi]!=0
INVOKE lstrlen, edi
push eax
mov ecx, edi
lea edx, buffer
sub ecx, edx
add ecx, FirstChar
.IF RichEditVersion==3
INVOKE SendMessage, hWnd, EM_POSFROMCHAR,
ADDR rect, ecx
.ELSE
INVOKE SendMessage, hWnd, EM_POSFROMCHAR, ecx, 0
mov ecx, eax
and ecx, 0FFFFh
mov rect.left, ecx
shr eax, 16
mov rect.top, eax
.ENDIF
INVOKE DrawText, hdc, edi, -1, ADDR rect, 0
pop eax
add edi, eax
sub esi, eax
.ELSE
inc edi
dec esi
.ENDIF
.ENDW
mov ecx, txtrange.chrg.cpMax
sub ecx, txtrange.chrg.cpMin
INVOKE RtlZeroMemory, pString, ecx
popad
.IF ecx>0
jmp ScanMore
.ENDIF
NoMoreHit:
pop ebx
pop edi
; Gdy mamy za sobą komentarze, pozbądźmy się separatorów
mov ecx, BufferSize
lea esi, buffer
.WHILE ecx>0
mov al, BYTE PTR [esi]
.IF al==" " || al==0Dh || al=="/" || al=="," ||\
al=="|" || al=="+" || al=="-" || al=="*" ||\
al=="&" || al=="<" || al==">" || al=="=" ||\
al=="(" || al==")" || al=="{" || al=="}" ||\
al=="[" || al=="]" || al=="^" || al==":" || al==9
mov BYTE PTR [esi], 0
.ENDIF
dec ecx
inc esi
.ENDW
; Rozpoczynamy wyszukiwanie
lea esi, buffer
mov ecx, BufferSize
.WHILE ecx>0
mov al, BYTE PTR [esi]
.IF al!=0
push ecx
INVOKE lstrlen, esi
push eax
mov edx, eax ; edx zawiera długość łańcucha
movzx eax, BYTE PTR [esi]
.IF al>="A" && al<="Z"
sub al, "A"
add al, "a"
.ENDIF
shl eax, 2
add eax, edi ; edi wskazanie tablicy wskazań WORDINFO
.IF DWORD PTR [eax]!=0
mov eax, DWORD PTR [eax]
ASSUME eax:PTR WORDINFO
.WHILE eax!=0
.IF edx==[eax].WordLen
pushad
INVOKE lstrcmpi, [eax].pszWord, esi
.IF eax==0
popad
; Podświetlamy słowo
mov ecx, esi
lea edx, buffer
sub ecx, edx
add ecx, FirstChar
pushad
.IF RichEditVersion==3
INVOKE SendMessage, hWnd, EM_POSFROMCHAR,
ADDR rect, ecx
.ELSE
INVOKE SendMessage, hWnd, EM_POSFROMCHAR, ecx, 0
mov ecx, eax
and ecx, 0FFFFh
mov rect.left, ecx
shr eax, 16
mov rect.top, eax
.ENDIF
popad
mov edx, [eax].pColor
INVOKE SetTextColor, hdc, DWORD PTR [edx]
INVOKE DrawText, hdc, esi, -1, ADDR rect, 0
.BREAK
.ENDIF
popad
.ENDIF
push [eax].NextLink
pop eax
.ENDW
.ENDIF
pop eax
pop ecx
add esi, eax
sub ecx, eax
.ELSE
inc esi
dec ecx
.ENDIF
.ENDW
.ENDIF
INVOKE SelectObject, hdc, hOldRgn
INVOKE DeleteObject, hRgn
INVOKE SelectObject, hdc, hOldFont
INVOKE ReleaseDC, hWnd, hdc
INVOKE ShowCaret, hWnd
pop eax
pop esi
pop edi
ret
.ELSEIF uMsg==WM_CLOSE
INVOKE SetWindowLong, hWnd, GWL_WNDPROC, OldWndProc
.ELSE
INVOKE CallWindowProc, OldWndProc, hWnd, uMsg, wParam, lParam
ret
.ENDIF
NewRichEditProc ENDP
WndProc PROC hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
LOCAL ofn: OPENFILENAME
LOCAL buffer[256]: BYTE
LOCAL editstream: EDITSTREAM
LOCAL hFile: DWORD
LOCAL hPopup: DWORD
LOCAL pt: POINT
LOCAL chrg: CHARRANGE
.IF uMsg==WM_CREATE
INVOKE CreateWindowEx, WS_EX_CLIENTEDGE, ADDR RichEditClass,
0, WS_CHILD OR WS_VISIBLE OR \
ES_MULTILINE OR WS_VSCROLL OR \
WS_HSCROLL OR ES_NOHIDESEL, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
hWnd, RichEditID, hInstance, 0
mov hwndRichEdit, eax
; Sprawdzamy wersję kontrolki RichEdit
INVOKE SendMessage, hwndRichEdit, EM_SETTYPOGRAPHYOPTIONS,
TO_SIMPLELINEBREAK, TO_SIMPLELINEBREAK
INVOKE SendMessage, hwndRichEdit, EM_GETTYPOGRAPHYOPTIONS, 1, 1
.IF eax==0 ; oznacza, iż ta wiadomość jest nieobecna
mov RichEditVersion, 2
.ELSE
mov RichEditVersion, 3
; Włączamy emulację systemowej kontrolki edycyjnej, aby
; uaktualnienie kolorów nie zajmowało zbyt wiele czasu
INVOKE SendMessage, hwndRichEdit, EM_SETEDITSTYLE,
SES_EMULATESYSEDIT, SES_EMULATESYSEDIT
.ENDIF
; Przechwytujemy procedurę okna kontrolki RichEdit
INVOKE SetWindowLong, hwndRichEdit, GWL_WNDPROC, ADDR NewRichEditProc
mov OldWndProc, eax
; Zmieniamy pojemność tekstu. Standardowo jest 64KB
INVOKE SendMessage, hwndRichEdit, EM_LIMITTEXT, -1, 0
; Ustawiamy standardowe kolory tekstu i tła
INVOKE SetColor
INVOKE SendMessage, hwndRichEdit, EM_SETMODIFY, FALSE, 0
; Ustawiamy maskę zdarzeń
INVOKE SendMessage, hwndRichEdit, EM_SETEVENTMASK, 0, ENM_MOUSEEVENTS
INVOKE SendMessage, hwndRichEdit, EM_EMPTYUNDOBUFFER, 0, 0
.ELSEIF uMsg==WM_NOTIFY
push esi
mov esi, lParam
ASSUME esi:PTR NMHDR
.IF [esi].code==EN_MSGFILTER
ASSUME esi:PTR MSGFILTER
.IF [esi].msg==WM_RBUTTONDOWN
INVOKE GetMenu, hWnd
INVOKE GetSubMenu, eax, 1
mov hPopup, eax
INVOKE PrepareEditMenu, hPopup
mov edx, [esi].lParam
mov ecx, edx
and edx, 0FFFFh
shr ecx, 16
mov pt.x, edx
mov pt.y, ecx
INVOKE ClientToScreen, hWnd, ADDR pt
INVOKE TrackPopupMenu, hPopup, TPM_LEFTALIGN OR \
TPM_BOTTOMALIGN, pt.x, pt.y,
NULL, hWnd, NULL
.ENDIF
.ENDIF
pop esi
.ELSEIF uMsg==WM_INITMENUPOPUP
mov eax, lParam
.IF ax==0 ; menu Plik
.IF FileOpened==TRUE ; plik jest już otwarty
INVOKE EnableMenuItem, wParam, IDM_OPEN, MF_GRAYED
INVOKE EnableMenuItem, wParam, IDM_CLOSE, MF_ENABLED
INVOKE EnableMenuItem, wParam, IDM_SAVE, MF_ENABLED
INVOKE EnableMenuItem, wParam, IDM_SAVEAS, MF_ENABLED
.ELSE
INVOKE EnableMenuItem, wParam, IDM_OPEN, MF_ENABLED
INVOKE EnableMenuItem, wParam, IDM_CLOSE, MF_GRAYED
INVOKE EnableMenuItem, wParam, IDM_SAVE, MF_GRAYED
INVOKE EnableMenuItem, wParam, IDM_SAVEAS, MF_GRAYED
.ENDIF
.ELSEIF ax==1 ; menu Edycja
INVOKE PrepareEditMenu, wParam
.ELSEIF ax==2 ; menu Szukaj
.IF FileOpened==TRUE
INVOKE EnableMenuItem, wParam, IDM_FIND, MF_ENABLED
INVOKE EnableMenuItem, wParam, IDM_FINDNEXT, MF_ENABLED
INVOKE EnableMenuItem, wParam, IDM_FINDPREV, MF_ENABLED
INVOKE EnableMenuItem, wParam, IDM_REPLACE, MF_ENABLED
INVOKE EnableMenuItem, wParam, IDM_GOTOLINE, MF_ENABLED
.ELSE
INVOKE EnableMenuItem, wParam, IDM_FIND, MF_GRAYED
INVOKE EnableMenuItem, wParam, IDM_FINDNEXT, MF_GRAYED
INVOKE EnableMenuItem, wParam, IDM_FINDPREV, MF_GRAYED
INVOKE EnableMenuItem, wParam, IDM_REPLACE, MF_GRAYED
INVOKE EnableMenuItem, wParam, IDM_GOTOLINE, MF_GRAYED
.ENDIF
.ENDIF
.ELSEIF uMsg==WM_COMMAND
.IF lParam==0 ; polecenia menu
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_HIDEREADONLY OR OFN_PATHMUSTEXIST
INVOKE GetOpenFileName, ADDR ofn
.IF eax!=0
INVOKE CreateFile, ADDR FileName, GENERIC_READ,
FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, 0
.IF eax!=INVALID_HANDLE_VALUE
mov hFile, eax
; Strumień tekstu do kontrolki RichEdit
mov editstream.dwCookie, eax
mov editstream.pfnCallback, OFFSET StreamInProc
INVOKE SendMessage, hwndRichEdit, EM_STREAMIN,
SF_TEXT, ADDR editstream
; Inicjujemy stan modyfikacji na 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
.ENDIF
.ELSEIF ax==IDM_CLOSE
INVOKE CheckModifyState, hWnd
.IF eax==TRUE
INVOKE SetWindowText, hwndRichEdit, 0
mov FileOpened, FALSE
.ENDIF
.ELSEIF ax==IDM_SAVE
INVOKE CreateFile, ADDR FileName, GENERIC_WRITE,
FILE_SHARE_READ, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, 0
.IF eax!=INVALID_HANDLE_VALUE
@@:
mov hFile, eax
; Strumień tekstu do pliku
mov editstream.dwCookie, eax
mov editstream.pfnCallback, OFFSET StreamOutProc
INVOKE SendMessage, hwndRichEdit, EM_STREAMOUT,
SF_TEXT, ADDR editstream
; Inicjujemy stan modyfikacji na 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_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_OPTION
INVOKE DialogBoxParam, hInstance, IDD_OPTIONDLG, hWnd,
ADDR OptionProc, 0
.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
INVOKE CreateFile, ADDR AlternateFileName, GENERIC_WRITE,
FILE_SHARE_READ, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, 0
.IF eax!=INVALID_HANDLE_VALUE
jmp @B
.ENDIF
.ENDIF
.ELSEIF ax==IDM_FIND
.IF hSearch==0
INVOKE CreateDialogParam, hInstance, IDD_FINDDLG, hWnd,
ADDR SearchProc, 0
.ENDIF
.ELSEIF ax==IDM_REPLACE
.IF hSearch==0
INVOKE CreateDialogParam, hInstance, IDD_REPLACEDLG, hWnd,
ADDR ReplaceProc, 0
.ENDIF
.ELSEIF ax==IDM_GOTOLINE
.IF hSearch==0
INVOKE CreateDialogParam, hInstance, IDD_GOTODLG, hWnd,
ADDR GoToProc, 0
.ENDIF
.ELSEIF ax==IDM_FINDNEXT
INVOKE lstrlen, ADDR FindBuffer
.IF eax!=0
INVOKE SendMessage, hwndRichEdit, EM_EXGETSEL, 0,
ADDR findtext.chrg
mov eax, findtext.chrg.cpMin
.IF eax!=findtext.chrg.cpMax
push findtext.chrg.cpMax
pop findtext.chrg.cpMin
.ENDIF
mov findtext.chrg.cpMax, -1
mov findtext.lpstrText, OFFSET FindBuffer
INVOKE SendMessage, hwndRichEdit, EM_FINDTEXTEX, FR_DOWN, ADDR findtext
.IF eax!=-1
INVOKE SendMessage, hwndRichEdit, EM_EXSETSEL, 0, ADDR findtext.chrgText
.ENDIF
.ENDIF
.ELSEIF ax==IDM_FINDPREV
INVOKE lstrlen, ADDR FindBuffer
.IF eax!=0
INVOKE SendMessage, hwndRichEdit, EM_EXGETSEL, 0, ADDR findtext.chrg
mov findtext.chrg.cpMax, 0
mov findtext.lpstrText, OFFSET FindBuffer
INVOKE SendMessage, hwndRichEdit, EM_FINDTEXTEX, 0, ADDR findtext
.IF eax!=-1
INVOKE SendMessage, hwndRichEdit, EM_EXSETSEL,
0, ADDR findtext.chrgText
.ENDIF
.ENDIF
.ELSEIF ax==IDM_EXIT
INVOKE SendMessage, hWnd, WM_CLOSE, 0, 0
.ENDIF
.ENDIF
.ELSEIF uMsg==WM_CLOSE
INVOKE CheckModifyState, hWnd
.IF eax==TRUE
INVOKE DestroyWindow, hWnd
.ENDIF
.ELSEIF uMsg==WM_SIZE
movzx eax, WORD PTR lParam
movzx edx, WORD PTR lParam + 2
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
Plik zasobów ICZEDIT.RC jest identyczny jak w poprzedniej lekcji.
Przed wywołaniem procedury WinMain wywołujemy FillHiliteInfo. Funkcja ta odczytuje zawartość pliku wordfile.txt i przegląda ją.
FillHiliteInfo PROC USES edi
LOCAL buffer[1024]: BYTE
LOCAL pTemp: DWORD
LOCAL BlockSize: DWORD
INVOKE RtlZeroMemory, ADDR ASMSyntaxArray, SIZEOF ASMSyntaxArray
INVOKE GetModuleFileName, hInstance, ADDR buffer, SIZEOF buffer
INVOKE lstrlen, ADDR buffer
mov ecx, eax
dec ecx
lea edi, buffer
add edi, ecx
std
mov al, "\"
repne scasb
cld
inc edi
mov BYTE PTR [edi], 0
INVOKE lstrcat, ADDR buffer, ADDR WordFileName
Tworzymy pełną ścieżkę do pliku wordfile.txt: zakładam, iż zawsze będzie on w tym samym katalogu co program.
INVOKE GetFileAttributes, ADDR buffer
.IF eax!=-1
Stosuję tę metodę do szybkiego sprawdzenia istnienia pliku.
mov BlockSize, 1024*10
INVOKE HeapAlloc, hMainHeap, 0, BlockSize
mov pTemp, eax
Przydzielamy pamięć do przechowywania słów. Standardowo będzie to 10KB. Pamięć przydzielana jest ze standardowej sterty aplikacji.
mov ecx, OFFSET C1Key
@@:
push ecx
INVOKE GetPrivateProfileString, ADDR ASMSection, ecx,
ADDR ZeroString, pTemp,
BlockSize, ADDR buffer
pop ecx
.IF eax!=0
inc eax
.IF eax==BlockSize ; bufor jest za mały
add BlockSize, 1024*10
push ecx
INVOKE HeapReAlloc, hMainHeap, 0, pTemp, BlockSize
pop ecx
mov pTemp, eax
jmp @B
.ENDIF
Sprawdzamy, czy blok jest wystarczająco duży. Jeśli nie, to zwiększamy jego wielkość o 10KB, aż będzie wystarczający do przechowania wszystkich podświetlanych słów.
mov edx, ecx
sub edx, OFFSET C1Key
add edx, OFFSET ASMColorArray
push ecx
INVOKE ParseBuffer, hMainHeap, pTemp, eax, edx,
ADDR ASMSyntaxArray
pop ecx
.ENDIF
add ecx, 4
cmp ecx, 4 + OFFSET C10Key
jne @B
INVOKE HeapFree, hMainHeap, 0, pTemp
.ENDIF
ret
FillHiliteInfo ENDP
Do funkcji ParseBuffer przekazujemy uchwyt bloku pamięci, odczytane słowa, rozmiar danych odczytanych z pliku wordfile.txt, adres definicji koloru używanego do podświetlania tych wyrazów oraz adres tablicy ASMSyntaxArray.
Teraz zbadajmy, co robi ParseBuffer. Akceptuje ona jako parametr adres bufora zawierającego słowa, które mają być podświetlane, wyodrębnia pojedyncze wyrazy i umieszcza każdy z nich w tablicy struktur WORDINFO, do której jest szybki dostęp z tablicy wskazań ASMSyntaxArray.
ParseBuffer PROC USES edi esi hHeap: DWORD,
pBuffer: DWORD,
nSize: DWORD,
ArrayOffset: DWORD,
pArray: DWORD
LOCAL buffer[128]: BYTE
LOCAL InProgress: DWORD
mov InProgress, FALSE
Zmienna InProgress jest znacznikiem stosowanym do zaznaczenia rozpoczęcia procesu przeglądania. Jeśli ma wartość FALSE, to nie napotkaliśmy jeszcze znaku, który nie jest separatorem.
lea esi, buffer
mov edi, pBuffer
INVOKE CharLower, edi
Rejestr esi wskazuje na lokalny bufor zawierający wyszukane słowo z listy słów. Rejestr edi wskazuje na łańcuch listy słów. Aby uprościć sobie późniejsze wyszukiwanie, zamieniamy wszystkie literki w słowach na małe.
mov ecx, nSize
SearchLoop:
or ecx, ecx
jz Finished
cmp BYTE PTR [edi], " "
je EndOfWord
cmp BYTE PTR [edi], 9 ; tab
je EndOfWord
Przeglądamy całą listę słów w buforze w poszukiwaniu separatorów. Jeśli znajdziemy separator, to musimy określić, czy oznacza on początek, czy koniec słowa.
mov InProgress, TRUE
mov al, BYTE PTR [edi]
mov BYTE PTR [esi], al
inc esi
SkipIt:
inc edi
dec ecx
jmp SearchLoop
Jeśli przeglądany znak nie jest separatorem, kopiujemy go do bufora, aby utworzyć kompletne słowo i kontynuujemy przeszukiwanie.
EndOfWord:
cmp InProgress, TRUE
je WordFound
jmp SkipIt
Jeśli natrafimy na separator, sprawdzamy wartość znacznika InProgress. Jeśli zawiera on TRUE, możemy przyjąć, iż oznacza on koniec słowa i możemy przejść do wstawienia słowa bieżąco w buforze (wskazywanym przez rejestr esi) do struktury WORDINFO. Jeśli wartość znacznika wynosi FALSE, kontynuujemy przeszukiwanie, aż zostanie znaleziony znak nie będący separatorem.
WordFound:
mov BYTE PTR [esi], 0
push ecx
INVOKE HeapAlloc, hHeap, HEAP_ZERO_MEMORY, SIZEOF WORDINFO
Gdy zostanie odszukany koniec słowa, dołączamy do bufora znak o kodzie 0, aby tekst był łańcuchem ASCIIZ. Następnie przydzielamy dla tego słowa ze sterty blok pamięci o rozmiarze struktury WORDINFO.
push esi
mov esi, eax
ASSUME esi:PTR WORDINFO
INVOKE lstrlen, ADDR buffer
mov [esi].WordLen, eax
Pobieramy długość słowa w lokalnym buforze i umieszczamy ją w polu WordLen struktury WORDINFO w celu późniejszego wykorzystania przy porównaniach.
push ArrayOffset
pop [esi].pColor
W polu pColor umieszczamy wskazanie koloru, który ma być używany do podświetlania tego słowa.
inc eax
INVOKE HeapAlloc, hHeap, HEAP_ZERO_MEMORY, eax
mov [esi].pszWord, eax
mov edx, eax
INVOKE lstrcpy, edx, ADDR buffer
Przydzielamy ze sterty pamięć do przechowywania samego słowa. W tym momencie struktura WORDINFO jest gotowa do umieszczenia jej na odpowiedniej, połączonej liście.
mov eax, pArray
movzx edx, BYTE PTR [buffer]
shl edx, 2 ; mnożenie przez 4
add eax, edx
Parametr pArray zawiera adres tablicy ASMSyntaxArray. Przesuwamy się do elementu, którego indeks jest równy kodowi pierwszego znaku słowa. Zatem umieszczamy kod pierwszego znaku w rejestrze edx, a następnie mnożymy go przez 4 (ponieważ każdy element tablicy ASMSyntaxArray zajmuje w pamięci 4 bajty). Do wyniku dodajemy adres początku tablicy ASMSyntaxArray. W rejestrze eax otrzymujemy adres odpowiedniego elementu.
.IF DWORD PTR [eax]==0
mov DWORD PTR [eax], esi
.ELSE
push DWORD PTR [eax]
pop [esi].NextLink
mov DWORD PTR [eax], esi
.ENDIF
Sprawdzamy wartość elementu tablicy. Jeśli wynosi 0, to oznacza to, iż bieżąco na liście żadne słowo nie rozpoczyna się od tej literki. Zatem umieszczamy tam adres bieżącej struktury WORDINFO.
Jeśli element tablicy ma wartość różną od 0, to na liście jest przynajmniej jedno słowo rozpoczynające się od tej literki. Zatem wstawiamy naszą strukturę WORDINFO na początek listy i uaktualniamy pole NextLink, aby wskazywało następną strukturę WORDINFO, która poprzednio była na początku listy.
pop esi
pop ecx
lea esi, buffer
mov InProgress, FALSE
jmp SkipIt
Po zakończeniu tej operacji rozpoczynamy następny cykl przeglądania, aż dotrzemy do końca bufora.
INVOKE SendMessage, hwndRichEdit, EM_SETTYPOGRAPHYOPTIONS,
TO_SIMPLELINEBREAK, TO_SIMPLELINEBREAK
INVOKE SendMessage, hwndRichEdit, EM_GETTYPOGRAPHYOPTIONS, 1, 1
.IF eax==0 ; oznacza, iż ta wiadomość jest nieobecna
mov RichEditVersion, 2
.ELSE
mov RichEditVersion, 3
INVOKE SendMessage, hwndRichEdit, EM_SETEDITSTYLE,
SES_EMULATESYSEDIT, SES_EMULATESYSEDIT
.ENDIF
Po utworzeniu kontrolki RichEdit musimy określić jej wersję. Krok ten jest konieczny, ponieważ wiadomość EM_POSFROMCHAR zachowuje się różnie dla wersji 2.0 i 3.0, a wiadomość EM_POSFROMCHAR jest krytyczna dla naszej procedury podświetlania. Nigdy nie spotkałem się z udokumentowanym sposobem sprawdzania wersji kontrolki RichEdit, zatem musiałem to zrobić po swojemu. W tym przypadku ustawiam opcję specyficzną dla wersji 3.0 i natychmiast odczytuję jej wartość. Jeśli ją dostanę, to zakładam, iż mam do czynienia z kontrolką RichEdit w wersji 3.0.
Przy kontrolce RichEdit 3.0 zmiana koloru czcionki dla dużego pliku zajmuje całkiem sporo czasu. Problem ten wydaje się dotyczyć jedynie wersji 3.0. Znalazłem jego obejście: zmuszamy kontrolkę do emulacji zachowań systemowej kontrolki edycyjnej wysyłając do niej wiadomość EM_SETEDITSTYLE.
Po otrzymaniu informacji o numerze wersji przechodzimy dalej do przejęcia procedury okna kontrolki RichEdit. Teraz przeanalizujemy procedurę okna dla tej kontrolki.
NewRichEditProc PROC hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
...
.IF uMsg==WM_PAINT
push edi
push esi
INVOKE HideCaret, hWnd
INVOKE CallWindowProc, OldWndProc, hWnd, uMsg, wParam, lParam
push eax
Obsługujemy wiadomość WM_PAINT. Najpierw ukrywamy kursor tekstowy, aby pozbyć się śmieci graficznych po wykonaniu podświetlenia. Po tej operacji przekazujemy wiadomość do oryginalnej procedury okna kontrolki RichEdit, aby odświeżyła sobie okno. Gdy funkcja CallWindowProc powróci, tekst jest odświeżony w swoich standardowych kolorach liter i tła.. Teraz mamy sposobność dokonania podświetlenia składni.
mov edi, OFFSET ASMSyntaxArray
INVOKE GetDC, hWnd
mov hdc, eax
INVOKE SetBkMode, hdc, TRANSPARENT
Umieszczamy adres tablicy ASMSyntaxArray w rejestrze edi. Następnie pobieramy uchwyt kontekstu urządzenia i ustawiamy tryb przezroczystego tła tekstu, aby zapisywany w oknie tekst nie zmieniał koloru tła.
INVOKE SendMessage, hWnd, EM_GETRECT, 0, ADDR rect
INVOKE SendMessage, hWnd, EM_CHARFROMPOS, 0, ADDR rect
INVOKE SendMessage, hWnd, EM_LINEFROMCHAR, eax, 0
INVOKE SendMessage, hWnd, EM_LINEINDEX, eax, 0
Chcemy otrzymać widoczny tekst, zatem najpierw musimy pobrać wymiary formatującego prostokąta wysyłając wiadomość EM_GETRECT do kontrolki RichEdit. Gdy mamy ograniczający prostokąt w następnej kolejności znajdujemy indeks najbliższego znaku w lewym górnym narożniku prostokąta za pomocą wiadomości EM_CHARFROMPOS. Gdy już będziemy posiadać ten indeks znaku (pierwszy widoczny znak w kontrolce), możemy rozpocząć podświetlanie składni od tej pozycji. Jednakże efekt końcowy może nie być tak dobry jak w przypadku rozpoczęcia od pierwszego znaku wiersza. Z tego powodu muszę odczytać numer wiersza, w którym znajduje się pierwszy widoczny w kontrolce znak. Dokonuję tego wysyłając do niej wiadomość EM_LINEFROMCHAR. Aby otrzymać pierwszy znak w tym wierszu, wysyłam wiadomość EM_LINEINDEX.
mov txtrange.chrg.cpMin, eax
mov FirstChar, eax
INVOKE SendMessage, hWnd, EM_CHARFROMPOS, 0, ADDR rect.right
mov txtrange.chrg.cpMax, eax
Po otrzymaniu indeksu pierwszego znaku, zapamiętujemy go w zmiennej FirstChar dla użytku w przyszłości. Następnie odczytujemy indeks ostatniego widocznego znaku wysyłając wiadomość EM_CHARFROMPOS wraz ze współrzędnymi w lParam dolnego prawego narożnika formatującego prostokąta.
push rect.left
pop RealRect.left
push rect.top
pop RealRect.top
push rect.right
pop RealRect.right
push rect.bottom
pop RealRect.bottom
INVOKE CreateRectRgn, RealRect.left, RealRect.top,
RealRect.right, RealRect.bottom
mov hRgn, eax
INVOKE SelectObject, hdc, hRgn
mov hOldRgn, eax
Podczas projektowania podświetlania składni zauważyłem występowanie efektu ubocznego tej metody. Jeśli kontrolka RichEdit posiada margines (możesz określić margines w kontrolce RichEdit za pomocą wiadomości EM_SETMARGINS), funkcja DrawText pisze poza jego granice. Stąd muszę utworzyć obszar obcinania o rozmiarze formatującego prostokąta wywołując funkcję CreateRectRgn. Wyjście funkcji GDI będzie ograniczone do tego obszaru.
Następnie musimy najpierw podświetlić komentarze i usunąć je sobie z drogi. Moja metoda polega na wyszukiwaniu znaku ";" i podświetlaniu tekstu kolorem komentarza aż do napotkania znaku końca wiersza. Nie będę tutaj analizował tej procedury, gdyż jest dosyć długa i skomplikowana. Wystarczy powiedzieć, iż po podświetleniu wszystkich komentarzy są one zastępowane w buforze zerami, co pozwoli pominąć słowa zawarte w komentarzach.
mov ecx, BufferSize
lea esi, buffer
.WHILE ecx>0
mov al, BYTE PTR [esi]
.IF al==" " || al==0Dh || al=="/" || al=="," ||\
al=="|" || al=="+" || al=="-" || al=="*" ||\
al=="&" || al=="<" || al==">" || al=="=" ||\
al=="(" || al==")" || al=="{" || al=="}" ||\
al=="[" || al=="]" || al=="^" || al==":" || al==9
mov BYTE PTR [esi], 0
.ENDIF
dec ecx
inc esi
.ENDWPo pozbyciu się komentarzy oddzielamy słowa w buforze zastępując znaki separatorów znakiem o kodzie 0. W efekcie nie musimy się martwić znakami separatorów przy przetwarzaniu słów w buforze - będzie tylko jeden rodzaj separatora - NULL.
lea esi, buffer
mov ecx, BufferSize
.WHILE ecx>0
mov al, BYTE PTR [esi]
.IF al!=0
Szukamy w buforze pierwszego znaku, który nie jest znakiem o kodzie 0 - czyli jest pierwszym znakiem wyrazu.
push ecx
INVOKE lstrlen, esi
push eax
mov edx, eax
Odczytujemy długość tego słowa i umieszczamy ją w rejestrze edx
movzx eax, BYTE PTR [esi]
.IF al>="A" && al<="Z"
add al, 32
.ENDIF
Zmieniamy znak na małą literkę (o ile jest to duża literka)
shl eax, 2
add eax, edi
.IF DWORD PTR [eax]!=0
Następnie przechodzimy do odpowiedniego dla tego znaku elementu tablicy ASMSyntaxArray i sprawdzamy, czy element ten nie ma wartości 0. Jeśli ma, to możemy przejść do kolejnego słowa.
mov eax, DWORD PTR [eax]
ASSUME eax:PTR WORDINFO
.WHILE eax!=0
.IF edx==[eax].WordLen
Jeśli wartość elementu nie wynosi zero, to wskazuje on na połączoną listę struktur WORDINFO. Przetwarzamy tę listę porównując długość słowa w lokalnym buforze z długością słowa w strukturze WORDINFO. To szybki test przed porównaniem słów. Powinien on zaoszczędzić nieco cykli procesora.
pushad
INVOKE lstrcmpi, [eax].pszWord, esi
.IF eax==0Jeśli długość obu słów jest równa, to porównujemy je przy pomocy funkcji lstrcmpi.
popad
mov ecx, esi
lea edx, buffer
sub ecx, edx
add ecx, FirstChar
Konstruujemy indeks znaku z adresu pierwszego znaku słowa do podświetlenia w buforze. Najpierw obliczamy jego względną pozycję od początku bufora, a następnie dodajemy indeks znakowy pierwszego widocznego znaku.
pushad
.IF RichEditVersion==3
INVOKE SendMessage, hWnd, EM_POSFROMCHAR, ADDR rect, ecx
.ELSE
INVOKE SendMessage, hWnd, EM_POSFROMCHAR, ecx, 0
mov ecx, eax
and ecx, 0FFFFh
mov rect.left, ecx
shr eax, 16
mov rect.top, eax
.ENDIF
popadPo obliczeniu indeksu znakowego pierwszego znaku w podświetlanym słowie przechodzimy do otrzymania jego współrzędnych wysyłając wiadomość EM_POSFROMCHAR. Jednakże wiadomość ta jest różnie interpretowania przez kontrolki RichEdit w wersji 2.0 i 3.0. Dla kontrolki RichEdit 2.0 wParam jest wskazaniem do struktury POINT, która będzie wypełniona współrzędnymi, a lParam zawiera indeks znaku.
Jak widać przesłanie złych argumentów do EM_POSFROMCHAR może wywołać nieprzewidywalne skutki w twoim systemie. Dlatego zdecydowałem się rozróżniać wersję kontrolek RichEdit.
mov edx, [eax].pColor
INVOKE SetTextColor, hdc, DWORD PTR [edx]
INVOKE DrawText, hdc, esi, -1, ADDR rect, 0
.BREAK
.ENDIF
popad
Po otrzymaniu współrzędnych początku ustawiamy kolor tekstu zgodny z definicją w strukturze WORDINFO. A następnie zapisujemy słowo na ekranie nowym słowem o innym kolorze.
I ostatnie uwagi - prezentowana metoda może zostać ulepszona na kilka różnych sposobów. Na przykład, pobieram tekst rozpoczynający się od pierwszego do ostatniego widocznego wiersza. Jeśli wiersze są bardzo długie, to może spaść wydajność z uwagi na przetwarzanie słów, które nie pojawią się na ekranie. Możesz to zoptymalizować odczytując jedynie widoczny na ekranie fragment tekstu wiersz po wierszu. Również algorytm wyszukiwania można ulepszyć po zastosowaniu bardziej efektywnej metody. Zrozum mnie dobrze - prezentowana na tej lekcji metoda podświetlania składni jest SZYBKA, lecz może być jeszcze SZYBSZA.
|
Autorem
kursu jest
Iczelion. Kurs
programowania Windows znalazł się na serwerze I LO w Tarnowie za pisemną
zgodą autora. |
![]() | I Liceum Ogólnokształcące |
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe