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 .ENDW
Po 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==0
Jeś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 popad
Po 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