/* * Unit test suite for rich edit control * * Copyright 2006 Google (Thomas Kho) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include static HMODULE hmoduleRichEdit; static int EN_LINK_rcvd = 0; static const char haystack[] = "Think of Wine as a compatibility layer for \ running Windows programs. Wine does not require Microsoft Windows, as it is a \ completely free alternative implementation of the Windows API consisting of \ 100% non-Microsoft code, however Wine can optionally use native Windows DLLs \ if they are available. Wine provides both a development toolkit for porting \ Windows source code to Unix as well as a program loader, allowing many \ unmodified Windows programs to run on x86-based Unixes, including Linux, \ FreeBSD, and Solaris."; typedef struct SCREENBITMAP_S { HBITMAP hBitmap; BYTE *data; } SCREENBITMAP; /* caller must call DeleteObject() on the returned SCREENBITMAP.hBitmap */ static SCREENBITMAP get_hwnd_bitmap(HWND hWnd) { BYTE *pBits; BITMAPINFO *bmi; HBITMAP hBitmap, hPrevBitmap; HDC hDCWnd, hDCMem; SCREENBITMAP sb; UpdateWindow(hWnd); SetLastError(0xdeadbeef); hDCWnd = GetWindowDC(hWnd); ok(hDCWnd != NULL, "error: %d\n", (int) GetLastError()); bmi = calloc(1, sizeof(BITMAPINFO)); bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi->bmiHeader.biWidth = GetDeviceCaps(hDCWnd, HORZRES); bmi->bmiHeader.biHeight = GetDeviceCaps(hDCWnd, VERTRES); bmi->bmiHeader.biPlanes = 1; bmi->bmiHeader.biBitCount = 32; bmi->bmiHeader.biCompression = BI_RGB; SetLastError(0xdeadbeef); hDCMem = CreateCompatibleDC(hDCWnd); ok(hDCMem != NULL, "error: %d\n", (int) GetLastError()); SetLastError(0xdeadbeef); hBitmap = CreateDIBSection(hDCWnd, bmi, DIB_RGB_COLORS, (void **) &pBits, NULL, 0); ok(hBitmap != NULL && pBits != NULL, "error: %d\n", (int) GetLastError()); /* must restore hDCMem to hPrevBitmap */ hPrevBitmap = SelectObject(hDCMem, hBitmap); ok(hPrevBitmap != HGDI_ERROR && hPrevBitmap != NULL, "hBitmap=%d\n", (int) hPrevBitmap); SetLastError(0xdeadbeef); ok(BitBlt(hDCMem, 0, 0, bmi->bmiHeader.biWidth, bmi->bmiHeader.biHeight, hDCWnd, 0, 0, SRCCOPY) != 0, "error: %d\n", (int) GetLastError()); hPrevBitmap = SelectObject(hDCMem, hPrevBitmap); ok(hBitmap == hPrevBitmap, "Expected %d, got %d.\n", (int) hBitmap, (int) hPrevBitmap); DeleteDC(hDCMem); free(bmi); ReleaseDC(hWnd, hDCWnd); sb.hBitmap = hBitmap; sb.data = pBits; return sb; } /* returns non-zero if the rectangular area defined by (x, y, width, height) * in the two screens differ */ static int screens_differ(SCREENBITMAP one, SCREENBITMAP two, int x, int y, int width, int height) { DIBSECTION ds1, ds2; int linesize1, linesize2; int i, j; SetLastError(0xdeadbeef); ok(GetObject(one.hBitmap, sizeof(DIBSECTION), &ds1) != 0, "error: %d\n", (int) GetLastError()); SetLastError(0xdeadbeef); ok(GetObject(two.hBitmap, sizeof(DIBSECTION), &ds2) != 0, "error: %d\n", (int) GetLastError()); /* require 32 bits per pixel */ ok((ds1.dsBmih.biBitCount == 32 && ds2.dsBmih.biBitCount == 32), "%d != %d != 32\n", ds1.dsBmih.biBitCount, ds2.dsBmih.biBitCount); linesize1 = ((ds1.dsBmih.biWidth * 4) + 3) & ~3; /* word aligned */ linesize2 = ((ds2.dsBmih.biWidth * 4) + 3) & ~3; for (i = y; i < y + height; i++) { BYTE *pb1 = one.data + x*4 + (ds1.dsBmih.biHeight - i - 1) * linesize1; BYTE *pb2 = two.data + x*4 + (ds2.dsBmih.biHeight - i - 1) * linesize2; for (j = 0; j < width; j++, pb1+=4, pb2+=4) { if (pb1[2] != pb2[2] || pb1[1] != pb2[1] || pb1[0] != pb2[0]) return 1; } } return 0; } static HWND new_richedit(HWND parent) { HWND hwndRichEdit; SetLastError(0xdeadbeef); hwndRichEdit = CreateWindow(RICHEDIT_CLASS, NULL, ES_MULTILINE|WS_POPUP |WS_HSCROLL|WS_VSCROLL|WS_VISIBLE, 0, 0, 200, 50, parent, NULL, hmoduleRichEdit, NULL); ok(hwndRichEdit != NULL, "Cannot create window, error: %d\n", (int) GetLastError()); return hwndRichEdit; } LRESULT CALLBACK proc_EM_AUTOURLDETECT(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { if ((Msg == WM_NOTIFY) && ((ENLINK *) lParam)->nmhdr.code == EN_LINK) EN_LINK_rcvd = 1; return DefWindowProc(hWnd, Msg, wParam, lParam); } static void check_EN_LINK_rcvd(HWND hwnd, int is_url) { /* simulate mouse movement at (2,2) relative to control */ SendMessage(hwnd, WM_MOUSEMOVE, 0 , 2 | 2 << 16); if (is_url) { /* control text is url; should get EN_LINK */ todo_wine { ok(0 != EN_LINK_rcvd, "en_link_rcvd=%d\n", EN_LINK_rcvd); } } else { ok(0 == EN_LINK_rcvd, "en_link_rcvd=%d\n", EN_LINK_rcvd); } EN_LINK_rcvd = 0; } static void test_EM_AUTOURLDETECT() { struct urls_s { char *text; int is_url; } urls[3] = { {"winehq.org\n", 0}, {"www.winehq.org\n", 1}, {"http://www.winehq.org\n", 1} }; int i, screen_diff; SCREENBITMAP before, after; HWND hwndRichEdit, parent; parent = new_richedit(NULL); hwndRichEdit = new_richedit(parent); SendMessage(hwndRichEdit, EM_SETEVENTMASK, 0, ENM_LINK); /* overwrite the parent WndProc to get EN_LINK notifications */ SetLastError(0xdeadbeef); ok(0 != SetWindowLongPtr(parent, GWLP_WNDPROC, (LONG_PTR) proc_EM_AUTOURLDETECT), "error: %d\n", (int) GetLastError()); /* for each url, check that the screen changes with/without ENM_LINK if * expected and that the EN_LINK message is received if expected */ for (i = 0; i < sizeof(urls)/sizeof(struct urls_s); i++) { SendMessage(hwndRichEdit, EM_AUTOURLDETECT, FALSE, 0); SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) urls[i].text); check_EN_LINK_rcvd(hwndRichEdit, 0); before = get_hwnd_bitmap(hwndRichEdit); SendMessage(hwndRichEdit, EM_AUTOURLDETECT, TRUE, 0); SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) urls[i].text); check_EN_LINK_rcvd(hwndRichEdit, urls[i].is_url); after = get_hwnd_bitmap(hwndRichEdit); screen_diff = screens_differ(before, after, 0, 0, 200, 50); if (urls[i].is_url) { todo_wine { ok(screen_diff, "no change\n"); } } else { ok(!screen_diff, "change\n"); } DeleteObject(before.hBitmap); DeleteObject(after.hBitmap); } DestroyWindow(hwndRichEdit); DestroyWindow(parent); } static void test_EM_EXGETSEL() { int screen_diff; char text[] = "test selection of text"; SCREENBITMAP before, after; CHARRANGE cr; HWND hwndRichEdit = new_richedit(NULL); SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) text); /* initially there should be no text selection */ memset(&cr, 0, sizeof(CHARRANGE)); SendMessage(hwndRichEdit, EM_EXGETSEL, 0, (LPARAM) &cr); ok(cr.cpMin == cr.cpMax, "(%ld,%ld)\n", cr.cpMin, cr.cpMax); /* select characters 3 to 10 */ cr.cpMin = 2; cr.cpMax = 10; before = get_hwnd_bitmap(hwndRichEdit); SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr); after = get_hwnd_bitmap(hwndRichEdit); screen_diff = screens_differ(before, after, 0, 0, 200, 50); todo_wine { ok(screen_diff, "selection not updated to the screen\n"); } DeleteObject(before.hBitmap); DeleteObject(after.hBitmap); /* verify selection change */ memset(&cr, 0, sizeof(CHARRANGE)); SendMessage(hwndRichEdit, EM_EXGETSEL, 0, (LPARAM) &cr); ok(cr.cpMin == 2 && cr.cpMax == 10, "(%ld,%ld)\n", cr.cpMin, cr.cpMax); /* check that range (0, -1) means everything */ cr.cpMin = 0; cr.cpMax = -1; SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr); memset(&cr, 0, sizeof(CHARRANGE)); SendMessage(hwndRichEdit, EM_EXGETSEL, 0, (LPARAM) &cr); /* TODO: bug in Wine, CHARRANGE marks positions, not character counts*/ todo_wine { ok(cr.cpMin == 0 && cr.cpMax == sizeof(text), "(%ld,%ld)\n", cr.cpMin, cr.cpMax); } /* TODO: simulate drag-selection */ /* TODO: color of text under the cursor is not inverted in Wine, but it is in * Windows */ DestroyWindow(hwndRichEdit); } static void test_EM_EXLIMITTEXT() { int i, screen_diff; char *text; int textlimit = 0; /* multiple of 100 */ HWND hwndRichEdit = new_richedit(NULL); SCREENBITMAP sb[4]; todo_wine { i = SendMessage(hwndRichEdit, EM_GETLIMITTEXT, 0, 0); ok(32767 == i, "expected: %d, actual: %d\n", 32767, i); /* default */ textlimit = 40000; SendMessage(hwndRichEdit, EM_LIMITTEXT, textlimit, 0); i = SendMessage(hwndRichEdit, EM_GETLIMITTEXT, 0, 0); ok(textlimit == i, "expected: %d, actual: %d\n", textlimit, i); textlimit = 80000; SendMessage(hwndRichEdit, EM_EXLIMITTEXT, 0, 0); i = SendMessage(hwndRichEdit, EM_GETLIMITTEXT, 0, 0); /* default for WParam = 0 */ ok(65536 == i, "expected: %d, actual: %d\n", 65536, i); SendMessage(hwndRichEdit, EM_EXLIMITTEXT, 0, textlimit); i = SendMessage(hwndRichEdit, EM_GETLIMITTEXT, 0, 0); /* our textlimit */ ok(textlimit == i, "expected: %d, actual: %d\n", textlimit, i); } text = malloc(textlimit+1); memset(text, 'W', textlimit); text[textlimit] = 0; /* maxed out text */ SendMessage(hwndRichEdit, EM_REPLACESEL, TRUE, (LPARAM) text); sb[0] = get_hwnd_bitmap(hwndRichEdit); /* maxed text */ SendMessage(hwndRichEdit, WM_KEYDOWN, VK_BACK, 1); SendMessage(hwndRichEdit, WM_CHAR, VK_BACK, 1); SendMessage(hwndRichEdit, WM_KEYUP, VK_BACK, 1); sb[1] = get_hwnd_bitmap(hwndRichEdit); /* hit backspace */ screen_diff = screens_differ(sb[0], sb[1], 0, 0, 200, 50); ok(screen_diff, "change expected"); SendMessage(hwndRichEdit, WM_KEYDOWN, 0x41, 1); SendMessage(hwndRichEdit, WM_CHAR, 0x41, 1); SendMessage(hwndRichEdit, WM_KEYUP, 0x41, 1); sb[2] = get_hwnd_bitmap(hwndRichEdit); /* hit 'A' */ screen_diff = screens_differ(sb[1], sb[2], 0, 0, 200, 50); todo_wine { ok(screen_diff, "change expected"); } /* this one should have no effect */ SendMessage(hwndRichEdit, WM_KEYDOWN, 0x41, 1); SendMessage(hwndRichEdit, WM_CHAR, 0x41, 1); SendMessage(hwndRichEdit, WM_KEYUP, 0x41, 1); sb[3] = get_hwnd_bitmap(hwndRichEdit); /* full; should be no effect */ screen_diff = screens_differ(sb[2], sb[3], 0, 0, 200, 50); ok(!screen_diff, "no change expected"); for (i = 0; i < sizeof(sb)/sizeof(SCREENBITMAP); i++) DeleteObject(sb[i].hBitmap); DestroyWindow(hwndRichEdit); } static void check_EM_FINDTEXT(HWND hwnd, int start, int end, char needle[], int flags, int expected_start) { int findloc; FINDTEXT ft; memset(&ft, 0, sizeof(ft)); ft.chrg.cpMin = start; ft.chrg.cpMax = end; ft.lpstrText = needle; findloc = SendMessage(hwnd, EM_FINDTEXT, flags, (LPARAM) &ft); ok(findloc == expected_start, "finding %s in range (%d,%d), got start at %d\n", needle, start, end, findloc); } static void test_EM_FINDTEXT() { CHARRANGE cr; GETTEXTLENGTHEX gtl; int size; HWND hwndRichEdit = new_richedit(NULL); SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) haystack); SendMessage(hwndRichEdit, EM_EXGETSEL, 0, (LPARAM)&cr); ok(cr.cpMin == cr.cpMax, "(%ld,%ld)\n", cr.cpMin, cr.cpMax); gtl.flags = GTL_NUMCHARS; gtl.codepage = CP_ACP; size = SendMessage(hwndRichEdit, EM_GETTEXTLENGTHEX, (WPARAM) >l, 0); ok(size == sizeof(haystack) - 1, "size=%d, sizeof haystack=%d\n", size, sizeof(haystack)); /* sizeof counts '\0' */ check_EM_FINDTEXT(hwndRichEdit, 0, size, "Wine", FR_DOWN | FR_MATCHCASE, 9); check_EM_FINDTEXT(hwndRichEdit, 10, size, "Wine", FR_DOWN | FR_MATCHCASE, 69); check_EM_FINDTEXT(hwndRichEdit, 298, size, "Wine", FR_DOWN | FR_MATCHCASE, -1); check_EM_FINDTEXT(hwndRichEdit, 0, size, "wine", FR_DOWN | FR_MATCHCASE, -1); todo_wine { check_EM_FINDTEXT(hwndRichEdit, 0, size, "wine", FR_DOWN | FR_WHOLEWORD, 9); check_EM_FINDTEXT(hwndRichEdit, 0, size, "win", FR_DOWN | FR_WHOLEWORD, -1); } DestroyWindow(hwndRichEdit); } static void check_EM_FINDTEXTEX(HWND hwnd, int start, int end, char needle[], int flags, int expected_start, int expected_end) { int findloc; FINDTEXTEX ft; memset(&ft, 0, sizeof(ft)); ft.chrg.cpMin = start; ft.chrg.cpMax = end; ft.lpstrText = needle; findloc = SendMessage(hwnd, EM_FINDTEXTEX, flags, (LPARAM) &ft); ok(findloc == expected_start, "finding %s in range (%d,%d), got start at %d\n", needle, start, end, findloc); if(findloc != -1) { ok(ft.chrgText.cpMin == expected_start, "finding %s in range (%d,%d), got start at %ld\n", needle, start, end, ft.chrgText.cpMin); ok(ft.chrgText.cpMax == expected_end, "finding %s in range (%d,%d), got end at %ld\n", needle, start, end, ft.chrgText.cpMax); } } static void test_EM_FINDTEXTEX() { CHARRANGE cr; GETTEXTLENGTHEX gtl; int size; HWND hwndRichEdit = new_richedit(NULL); SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) haystack); SendMessage(hwndRichEdit, EM_EXGETSEL, 0, (LPARAM) &cr); ok(cr.cpMin == cr.cpMax, "(%ld,%ld)\n", cr.cpMin, cr.cpMax); gtl.flags = GTL_NUMCHARS; gtl.codepage = CP_ACP; size = SendMessage(hwndRichEdit, EM_GETTEXTLENGTHEX, (WPARAM) >l, 0); ok(size == sizeof(haystack) - 1, "size=%d, sizeof haystack=%d\n", size, sizeof(haystack)); /* sizeof counts '\0' */ check_EM_FINDTEXTEX(hwndRichEdit, 0, size, "Wine", FR_DOWN | FR_MATCHCASE, 9, 13); check_EM_FINDTEXTEX(hwndRichEdit, 10, size, "Wine", FR_DOWN | FR_MATCHCASE, 69, 73); check_EM_FINDTEXTEX(hwndRichEdit, 298, size, "Wine", FR_DOWN | FR_MATCHCASE, -1, -1); check_EM_FINDTEXTEX(hwndRichEdit, 0, size, "wine", FR_DOWN | FR_MATCHCASE, -1, -1); todo_wine { check_EM_FINDTEXTEX(hwndRichEdit, 0, size, "wine", FR_DOWN | FR_WHOLEWORD, 9, 13); check_EM_FINDTEXTEX(hwndRichEdit, 0, size, "win", FR_DOWN | FR_WHOLEWORD, -1, -1); } DestroyWindow(hwndRichEdit); } static void test_EM_SCROLL() { int i, screen_diff; SCREENBITMAP sb[5]; HWND hwndRichEdit = new_richedit(NULL); /* initially scroll is at the top */ SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "a\nb\nc\nd\ne"); sb[0] = get_hwnd_bitmap(hwndRichEdit); SendMessage(hwndRichEdit, EM_SCROLL, SB_PAGEDOWN, 0); sb[1] = get_hwnd_bitmap(hwndRichEdit); screen_diff = screens_differ(sb[0], sb[1], 0, 0, 200, 50); todo_wine { ok(screen_diff, "change expected\n"); } SendMessage(hwndRichEdit, EM_SCROLL, SB_LINEUP, 0); sb[2] = get_hwnd_bitmap(hwndRichEdit); screen_diff = screens_differ(sb[1], sb[2], 0, 0, 200, 50); todo_wine { ok(screen_diff, "change expected\n"); } SendMessage(hwndRichEdit, EM_SCROLL, SB_PAGEUP, 0); sb[3] = get_hwnd_bitmap(hwndRichEdit); screen_diff = screens_differ(sb[2], sb[3], 0, 0, 200, 50); todo_wine { ok(screen_diff, "change expected\n"); } SendMessage(hwndRichEdit, EM_SCROLL, SB_LINEDOWN, 0); sb[4] = get_hwnd_bitmap(hwndRichEdit); screen_diff = screens_differ(sb[3], sb[4], 0, 0, 200, 50); todo_wine { ok(screen_diff, "change expected\n"); } for (i = 0; i < sizeof(sb)/sizeof(SCREENBITMAP); i++) DeleteObject(sb[i].hBitmap); DestroyWindow(hwndRichEdit); } static void test_EM_SCROLLCARET() { int screen_diff[3]; SCREENBITMAP sb[3]; HWND hwndRichEdit = new_richedit(NULL); SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "a\nb\nc\nd\ne"); sb[0] = get_hwnd_bitmap(hwndRichEdit); SendMessage(hwndRichEdit, EM_SCROLL, SB_PAGEDOWN, 0); sb[1] = get_hwnd_bitmap(hwndRichEdit); SendMessage(hwndRichEdit, EM_SCROLLCARET, 0, 0); sb[2] = get_hwnd_bitmap(hwndRichEdit); screen_diff[0] = screens_differ(sb[0], sb[1], 0, 0, 200, 50); screen_diff[1] = screens_differ(sb[1], sb[2], 0, 0, 200, 50); screen_diff[2] = screens_differ(sb[0], sb[2], 0, 0, 200, 50); todo_wine { ok(screen_diff[0] && screen_diff[1] && !screen_diff[2], "screen_diff = {%d, %d, %d}\n", screen_diff[0], screen_diff[1], screen_diff[2]); } DestroyWindow(hwndRichEdit); } START_TEST( editor ) { MSG msg; SetLastError(0xdeadbeef); hmoduleRichEdit = LoadLibrary("RICHED20.DLL"); ok(hmoduleRichEdit != NULL, "Cannot load RICHED20.DLL, error: %d\n", (int) GetLastError()); test_EM_AUTOURLDETECT(); test_EM_EXGETSEL(); /*test_EM_EXLIMITTEXT();*/ /* too slow */ test_EM_FINDTEXT(); test_EM_FINDTEXTEX(); test_EM_SCROLL(); test_EM_SCROLLCARET(); /* set the environment variable WINETEST_INTERACTIVE to keep open windows * responsive when debugging */ if (winetest_interactive) { while (GetMessage(&msg, NULL, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessage(&msg); } } }