make_dlls: Recursively ignore .ok files in all tests directories.
[wine] / dlls / riched20 / tests / editor.c
1 /*
2 * Unit test suite for rich edit control
3 *
4 * Copyright 2006 Google (Thomas Kho)
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 */
20
21 #include <wine/test.h>
22 #include <windows.h>
23 #include <richedit.h>
24 #include <time.h>
25
26 static HMODULE hmoduleRichEdit;
27
28 static HWND new_window(LPCTSTR lpClassName, DWORD dwStyle, HWND parent) {
29   HWND hwnd;
30   hwnd = CreateWindow(lpClassName, NULL, dwStyle|WS_POPUP|WS_HSCROLL|WS_VSCROLL
31                       |WS_VISIBLE, 0, 0, 200, 60, parent, NULL,
32                       hmoduleRichEdit, NULL);
33   ok(hwnd != NULL, "class: %s, error: %d\n", lpClassName, (int) GetLastError());
34   return hwnd;
35 }
36
37 static HWND new_richedit(HWND parent) {
38   return new_window(RICHEDIT_CLASS, ES_MULTILINE, parent);
39 }
40
41 static const char haystack[] = "WINEWine wineWine wine WineWine";
42                              /* ^0        ^10       ^20       ^30 */
43
44 struct find_s {
45   int start;
46   int end;
47   char *needle;
48   int flags;
49   int expected_loc;
50   int _todo_wine;
51 };
52
53 struct find_s find_tests[] = {
54   /* Find in empty text */
55   {0, -1, "foo", FR_DOWN, -1, 0},
56   {0, -1, "foo", 0, -1, 0},
57   {0, -1, "", FR_DOWN, -1, 0},
58   {20, 5, "foo", FR_DOWN, -1, 0},
59   {5, 20, "foo", FR_DOWN, -1, 0}
60 };
61
62 struct find_s find_tests2[] = {
63   /* No-result find */
64   {0, -1, "foo", FR_DOWN | FR_MATCHCASE, -1, 0},
65   {5, 20, "WINE", FR_DOWN | FR_MATCHCASE, -1, 0},
66
67   /* Subsequent finds */
68   {0, -1, "Wine", FR_DOWN | FR_MATCHCASE, 4, 0},
69   {5, 31, "Wine", FR_DOWN | FR_MATCHCASE, 13, 0},
70   {14, 31, "Wine", FR_DOWN | FR_MATCHCASE, 23, 0},
71   {24, 31, "Wine", FR_DOWN | FR_MATCHCASE, 27, 0},
72
73   /* Find backwards */
74   {19, 20, "Wine", FR_MATCHCASE, 13, 0},
75   {10, 20, "Wine", FR_MATCHCASE, 4, 0},
76   {20, 10, "Wine", FR_MATCHCASE, 13, 0},
77
78   /* Case-insensitive */
79   {1, 31, "wInE", FR_DOWN, 4, 0},
80   {1, 31, "Wine", FR_DOWN, 4, 0},
81
82   /* High-to-low ranges */
83   {20, 5, "Wine", FR_DOWN, -1, 0},
84   {2, 1, "Wine", FR_DOWN, -1, 0},
85   {30, 29, "Wine", FR_DOWN, -1, 0},
86   {20, 5, "Wine", 0, 13, 0},
87
88   /* Find nothing */
89   {5, 10, "", FR_DOWN, -1, 0},
90   {10, 5, "", FR_DOWN, -1, 0},
91   {0, -1, "", FR_DOWN, -1, 0},
92   {10, 5, "", 0, -1, 0},
93
94   /* Whole-word search */
95   {0, -1, "wine", FR_DOWN | FR_WHOLEWORD, 18, 0},
96   {0, -1, "win", FR_DOWN | FR_WHOLEWORD, -1, 0},
97   {13, -1, "wine", FR_DOWN | FR_WHOLEWORD, 18, 0},
98   {0, -1, "winewine", FR_DOWN | FR_WHOLEWORD, 0, 0},
99   {10, -1, "winewine", FR_DOWN | FR_WHOLEWORD, 23, 0},
100   {11, -1, "winewine", FR_WHOLEWORD, 0, 0},
101   {31, -1, "winewine", FR_WHOLEWORD, 23, 0},
102   
103   /* Bad ranges */
104   {5, 200, "XXX", FR_DOWN, -1, 0},
105   {-20, 20, "Wine", FR_DOWN, -1, 0},
106   {-20, 20, "Wine", FR_DOWN, -1, 0},
107   {-15, -20, "Wine", FR_DOWN, -1, 0},
108   {1<<12, 1<<13, "Wine", FR_DOWN, -1, 0},
109
110   /* Check the case noted in bug 4479 where matches at end aren't recognized */
111   {23, 31, "Wine", FR_DOWN | FR_MATCHCASE, 23, 0},
112   {27, 31, "Wine", FR_DOWN | FR_MATCHCASE, 27, 0},
113   {27, 32, "Wine", FR_DOWN | FR_MATCHCASE, 27, 0},
114   {13, 31, "WineWine", FR_DOWN | FR_MATCHCASE, 23, 0},
115   {13, 32, "WineWine", FR_DOWN | FR_MATCHCASE, 23, 0},
116
117   /* The backwards case of bug 4479; bounds look right
118    * Fails because backward find is wrong */
119   {19, 20, "WINE", FR_MATCHCASE, 0, 0},
120   {0, 20, "WINE", FR_MATCHCASE, -1, 0}
121 };
122
123 static void check_EM_FINDTEXT(HWND hwnd, char *name, struct find_s *f, int id) {
124   int findloc;
125   FINDTEXT ft;
126   memset(&ft, 0, sizeof(ft));
127   ft.chrg.cpMin = f->start;
128   ft.chrg.cpMax = f->end;
129   ft.lpstrText = f->needle;
130   findloc = SendMessage(hwnd, EM_FINDTEXT, f->flags, (LPARAM) &ft);
131   ok(findloc == f->expected_loc,
132      "EM_FINDTEXT(%s,%d) '%s' in range(%d,%d), flags %08x, got start at %d\n",
133      name, id, f->needle, f->start, f->end, f->flags, findloc);
134 }
135
136 static void check_EM_FINDTEXTEX(HWND hwnd, char *name, struct find_s *f,
137     int id) {
138   int findloc;
139   FINDTEXTEX ft;
140   memset(&ft, 0, sizeof(ft));
141   ft.chrg.cpMin = f->start;
142   ft.chrg.cpMax = f->end;
143   ft.lpstrText = f->needle;
144   findloc = SendMessage(hwnd, EM_FINDTEXTEX, f->flags, (LPARAM) &ft);
145   ok(findloc == f->expected_loc,
146       "EM_FINDTEXTEX(%s,%d) '%s' in range(%d,%d), flags %08x, start at %d\n",
147       name, id, f->needle, f->start, f->end, f->flags, findloc);
148   ok(ft.chrgText.cpMin == f->expected_loc,
149       "EM_FINDTEXTEX(%s,%d) '%s' in range(%d,%d), flags %08x, start at %ld\n",
150       name, id, f->needle, f->start, f->end, f->flags, ft.chrgText.cpMin);
151   ok(ft.chrgText.cpMax == ((f->expected_loc == -1) ? -1
152         : f->expected_loc + strlen(f->needle)),
153       "EM_FINDTEXTEX(%s,%d) '%s' in range(%d,%d), flags %08x, end at %ld\n",
154       name, id, f->needle, f->start, f->end, f->flags, ft.chrgText.cpMax);
155 }
156
157 static void run_tests_EM_FINDTEXT(HWND hwnd, char *name, struct find_s *find,
158     int num_tests)
159 {
160   int i;
161
162   for (i = 0; i < num_tests; i++) {
163     if (find[i]._todo_wine) {
164       todo_wine {
165         check_EM_FINDTEXT(hwnd, name, &find[i], i);
166         check_EM_FINDTEXTEX(hwnd, name, &find[i], i);
167       }
168     } else {
169         check_EM_FINDTEXT(hwnd, name, &find[i], i);
170         check_EM_FINDTEXTEX(hwnd, name, &find[i], i);
171     }
172   }
173 }
174
175 static void test_EM_FINDTEXT(void)
176 {
177   HWND hwndRichEdit = new_richedit(NULL);
178
179   /* Empty rich edit control */
180   run_tests_EM_FINDTEXT(hwndRichEdit, "1", find_tests,
181       sizeof(find_tests)/sizeof(struct find_s));
182
183   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) haystack);
184
185   /* Haystack text */
186   run_tests_EM_FINDTEXT(hwndRichEdit, "2", find_tests2,
187       sizeof(find_tests2)/sizeof(struct find_s));
188
189   DestroyWindow(hwndRichEdit);
190 }
191
192 static int get_scroll_pos_y(HWND hwnd)
193 {
194   POINT p = {-1, -1};
195   SendMessage(hwnd, EM_GETSCROLLPOS, 0, (LPARAM) &p);
196   ok(p.x != -1 && p.y != -1, "p.x:%ld p.y:%ld\n", p.x, p.y);
197   return p.y;
198 }
199
200 static void move_cursor(HWND hwnd, long charindex)
201 {
202   CHARRANGE cr;
203   cr.cpMax = charindex;
204   cr.cpMin = charindex;
205   SendMessage(hwnd, EM_EXSETSEL, 0, (LPARAM) &cr);
206 }
207
208 static void line_scroll(HWND hwnd, int amount)
209 {
210   SendMessage(hwnd, EM_LINESCROLL, 0, amount);
211 }
212
213 static void test_EM_SCROLLCARET(void)
214 {
215   int prevY, curY;
216   HWND hwndRichEdit = new_richedit(NULL);
217   const char text[] = "aa\n"
218       "this is a long line of text that should be longer than the "
219       "control's width\n"
220       "cc\n"
221       "dd\n"
222       "ee\n"
223       "ff\n"
224       "gg\n"
225       "hh\n";
226
227   /* Can't verify this */
228   SendMessage(hwndRichEdit, EM_SCROLLCARET, 0, 0);
229
230   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) text);
231
232   /* Caret above visible window */
233   line_scroll(hwndRichEdit, 3);
234   prevY = get_scroll_pos_y(hwndRichEdit);
235   SendMessage(hwndRichEdit, EM_SCROLLCARET, 0, 0);
236   curY = get_scroll_pos_y(hwndRichEdit);
237   ok(prevY != curY, "%d == %d\n", prevY, curY);
238
239   /* Caret below visible window */
240   move_cursor(hwndRichEdit, sizeof(text) - 1);
241   line_scroll(hwndRichEdit, -3);
242   prevY = get_scroll_pos_y(hwndRichEdit);
243   SendMessage(hwndRichEdit, EM_SCROLLCARET, 0, 0);
244   curY = get_scroll_pos_y(hwndRichEdit);
245   ok(prevY != curY, "%d == %d\n", prevY, curY);
246
247   /* Caret in visible window */
248   move_cursor(hwndRichEdit, sizeof(text) - 2);
249   prevY = get_scroll_pos_y(hwndRichEdit);
250   SendMessage(hwndRichEdit, EM_SCROLLCARET, 0, 0);
251   curY = get_scroll_pos_y(hwndRichEdit);
252   ok(prevY == curY, "%d != %d\n", prevY, curY);
253
254   /* Caret still in visible window */
255   line_scroll(hwndRichEdit, -1);
256   prevY = get_scroll_pos_y(hwndRichEdit);
257   SendMessage(hwndRichEdit, EM_SCROLLCARET, 0, 0);
258   curY = get_scroll_pos_y(hwndRichEdit);
259   ok(prevY == curY, "%d != %d\n", prevY, curY);
260
261   DestroyWindow(hwndRichEdit);
262 }
263
264 static void test_EM_SETTEXTMODE(void)
265 {
266   HWND hwndRichEdit = new_richedit(NULL);
267   CHARFORMAT2 cf2, cf2test;
268   CHARRANGE cr;
269   int rc = 0;
270
271   /*Test that EM_SETTEXTMODE fails if text exists within the control*/
272   /*Insert text into the control*/
273
274   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "wine");
275
276   /*Attempt to change the control to plain text mode*/
277   rc = SendMessage(hwndRichEdit, EM_SETTEXTMODE, (WPARAM) TM_PLAINTEXT, 0);
278   ok(rc != 0, "EM_SETTEXTMODE: changed text mode in control containing text - returned: %d\n", rc);
279
280   /*Test that EM_SETTEXTMODE does not allow rich edit text to be pasted.
281   If rich text is pasted, it should have the same formatting as the rest
282   of the text in the control*/
283
284   /*Italicize the text
285   *NOTE: If the default text was already italicized, the test will simply
286   reverse; in other words, it will copy a regular "wine" into a plain
287   text window that uses an italicized format*/
288   cf2.cbSize = sizeof(CHARFORMAT2);
289   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_DEFAULT,
290              (LPARAM) &cf2);
291
292   cf2.dwMask = CFM_ITALIC | cf2.dwMask;
293   cf2.dwEffects = CFE_ITALIC ^ cf2.dwEffects;
294
295   /*EM_SETCHARFORMAT is not yet fully implemented for all WPARAMs in wine;
296   however, SCF_ALL has been implemented*/
297   SendMessage(hwndRichEdit, EM_SETCHARFORMAT, (WPARAM) SCF_ALL, (LPARAM) &cf2);
298   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "wine");
299
300   /*Select the string "wine"*/
301   cr.cpMin = 0;
302   cr.cpMax = 4;
303   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
304
305   /*Copy the italicized "wine" to the clipboard*/
306   SendMessage(hwndRichEdit, WM_COPY, 0, 0);
307
308   /*Reset the formatting to default*/
309   cf2.dwEffects = CFE_ITALIC^cf2.dwEffects;
310   SendMessage(hwndRichEdit, EM_SETCHARFORMAT, (WPARAM) SCF_ALL, (LPARAM) &cf2);
311
312   /*Clear the text in the control*/
313   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "");
314
315   /*Switch to Plain Text Mode*/
316   rc = SendMessage(hwndRichEdit, EM_SETTEXTMODE, (WPARAM) TM_PLAINTEXT, 0);
317   ok(rc == 0, "EM_SETTEXTMODE: unable to switch to plain text mode with empty control:  returned: %d\n", rc);
318
319   /*Input "wine" again in normal format*/
320   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "wine");
321
322   /*Paste the italicized "wine" into the control*/
323   SendMessage(hwndRichEdit, WM_PASTE, 0, 0);
324
325   /*Select a character from the first "wine" string*/
326   cr.cpMin = 2;
327   cr.cpMax = 3;
328   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
329
330   /*Retrieve its formatting*/
331   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_SELECTION,
332               (LPARAM) &cf2);
333
334   /*Select a character from the second "wine" string*/
335   cr.cpMin = 5;
336   cr.cpMax = 6;
337   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
338
339   /*Retrieve its formatting*/
340   cf2test.cbSize = sizeof(CHARFORMAT2);
341   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_SELECTION,
342                (LPARAM) &cf2test);
343
344   /*Compare the two formattings*/
345     ok((cf2.dwMask == cf2test.dwMask) && (cf2.dwEffects == cf2test.dwEffects),
346       "two formats found in plain text mode - cf2.dwEffects: %lx cf2test.dwEffects: %lx\n",
347        cf2.dwEffects, cf2test.dwEffects);
348   /*Test TM_RICHTEXT by: switching back to Rich Text mode
349                          printing "wine" in the current format(normal)
350                          pasting "wine" from the clipboard(italicized)
351                          comparing the two formats(should differ)*/
352
353   /*Attempt to switch with text in control*/
354   rc = SendMessage(hwndRichEdit, EM_SETTEXTMODE, (WPARAM) TM_RICHTEXT, 0);
355   ok(rc != 0, "EM_SETTEXTMODE: changed from plain text to rich text with text in control - returned: %d\n", rc);
356
357   /*Clear control*/
358   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "");
359
360   /*Switch into Rich Text mode*/
361   rc = SendMessage(hwndRichEdit, EM_SETTEXTMODE, (WPARAM) TM_RICHTEXT, 0);
362   ok(rc == 0, "EM_SETTEXTMODE: unable to change to rich text with empty control - returned: %d\n", rc);
363
364   /*Print "wine" in normal formatting into the control*/
365   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "wine");
366
367   /*Paste italicized "wine" into the control*/
368   SendMessage(hwndRichEdit, WM_PASTE, 0, 0);
369
370   /*Select text from the first "wine" string*/
371   cr.cpMin = 1;
372   cr.cpMax = 3;
373   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
374
375   /*Retrieve its formatting*/
376   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_SELECTION,
377                 (LPARAM) &cf2);
378
379   /*Select text from the second "wine" string*/
380   cr.cpMin = 6;
381   cr.cpMax = 7;
382   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
383
384   /*Retrieve its formatting*/
385   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_SELECTION,
386                 (LPARAM) &cf2test);
387
388   /*Test that the two formattings are not the same*/
389   todo_wine ok((cf2.dwMask == cf2test.dwMask) && (cf2.dwEffects != cf2test.dwEffects),
390       "expected different formats - cf2.dwMask: %lx, cf2test.dwMask: %lx, cf2.dwEffects: %lx, cf2test.dwEffects: %lx\n",
391       cf2.dwMask, cf2test.dwMask, cf2.dwEffects, cf2test.dwEffects);
392
393   DestroyWindow(hwndRichEdit);
394 }
395
396 static void test_TM_PLAINTEXT()
397 {
398   /*Tests plain text properties*/
399
400   HWND hwndRichEdit = new_richedit(NULL);
401   CHARFORMAT2 cf2, cf2test;
402   CHARRANGE cr;
403
404   /*Switch to plain text mode*/
405
406   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "");
407   SendMessage(hwndRichEdit, EM_SETTEXTMODE, TM_PLAINTEXT, 0);
408
409   /*Fill control with text*/
410
411   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "Is Wine an emulator? No it's not");
412
413   /*Select some text and bold it*/
414
415   cr.cpMin = 10;
416   cr.cpMax = 20;
417   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
418   cf2.cbSize = sizeof(CHARFORMAT2);
419   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_DEFAULT,
420               (LPARAM) &cf2);
421
422   cf2.dwMask = CFM_BOLD | cf2.dwMask;
423   cf2.dwEffects = CFE_BOLD ^ cf2.dwEffects;
424
425   SendMessage(hwndRichEdit, EM_SETCHARFORMAT, (WPARAM) SCF_SELECTION, (LPARAM) &cf2);
426
427   /*Get the formatting of those characters*/
428
429   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_SELECTION, (LPARAM) &cf2);
430
431   /*Get the formatting of some other characters*/
432   cf2test.cbSize = sizeof(CHARFORMAT2);
433   cr.cpMin = 21;
434   cr.cpMax = 30;
435   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
436   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_SELECTION, (LPARAM) &cf2test);
437
438   /*Test that they are the same as plain text allows only one formatting*/
439
440   ok((cf2.dwMask == cf2test.dwMask) && (cf2.dwEffects == cf2test.dwEffects),
441      "two selections' formats differ - cf2.dwMask: %lx, cf2test.dwMask %lx, cf2.dwEffects: %lx, cf2test.dwEffects: %lx\n",
442      cf2.dwMask, cf2test.dwMask, cf2.dwEffects, cf2test.dwEffects);
443   
444   /*Fill the control with a "wine" string, which when inserted will be bold*/
445
446   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "wine");
447
448   /*Copy the bolded "wine" string*/
449
450   cr.cpMin = 0;
451   cr.cpMax = 4;
452   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
453   SendMessage(hwndRichEdit, WM_COPY, 0, 0);
454
455   /*Swap back to rich text*/
456
457   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "");
458   SendMessage(hwndRichEdit, EM_SETTEXTMODE, (WPARAM) TM_RICHTEXT, 0);
459
460   /*Set the default formatting to bold italics*/
461
462   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_DEFAULT, (LPARAM) &cf2);
463   cf2.dwMask |= CFM_ITALIC;
464   cf2.dwEffects ^= CFE_ITALIC;
465   SendMessage(hwndRichEdit, EM_SETCHARFORMAT, (WPARAM) SCF_ALL, (LPARAM) &cf2);
466
467   /*Set the text in the control to "wine", which will be bold and italicized*/
468
469   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "wine");
470
471   /*Paste the plain text "wine" string, which should take the insert
472    formatting, which at the moment is bold italics*/
473
474   SendMessage(hwndRichEdit, WM_PASTE, 0, 0);
475
476   /*Select the first "wine" string and retrieve its formatting*/
477
478   cr.cpMin = 1;
479   cr.cpMax = 3;
480   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
481   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_SELECTION, (LPARAM) &cf2);
482
483   /*Select the second "wine" string and retrieve its formatting*/
484
485   cr.cpMin = 5;
486   cr.cpMax = 7;
487   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
488   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_SELECTION, (LPARAM) &cf2test);
489
490   /*Compare the two formattings. They should be the same.*/
491
492   ok((cf2.dwMask == cf2test.dwMask) && (cf2.dwEffects == cf2test.dwEffects),
493      "Copied text retained formatting - cf2.dwMask: %lx, cf2test.dwMask: %lx, cf2.dwEffects: %lx, cf2test.dwEffects: %lx\n",
494      cf2.dwMask, cf2test.dwMask, cf2.dwEffects, cf2test.dwEffects);
495   DestroyWindow(hwndRichEdit);
496 }
497
498 static void test_WM_GETTEXT()
499 {
500     HWND hwndRichEdit = new_richedit(NULL);
501     static const char text[] = "Hello. My name is RichEdit!";
502     char buffer[1024] = {0};
503     int result;
504
505     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) text);
506     SendMessage(hwndRichEdit, WM_GETTEXT, 1024, (LPARAM) buffer);
507     result = strcmp(buffer,text);
508     ok(result == 0, 
509         "WM_GETTEXT: settext and gettext differ. strcmp: %d\n", result);
510 }
511
512 /* FIXME: need to test unimplemented options and robustly test wparam */
513 static void test_EM_SETOPTIONS()
514 {
515     HWND hwndRichEdit = new_richedit(NULL);
516     static const char text[] = "Hello. My name is RichEdit!";
517     char buffer[1024] = {0};
518
519     /* NEGATIVE TESTING - NO OPTIONS SET */
520     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) text);
521     SendMessage(hwndRichEdit, EM_SETOPTIONS, ECOOP_SET, 0);
522
523     /* testing no readonly by sending 'a' to the control*/
524     SetFocus(hwndRichEdit);
525     SendMessage(hwndRichEdit, WM_CHAR, 'a', 0x1E0001);
526     SendMessage(hwndRichEdit, WM_GETTEXT, 1024, (LPARAM) buffer);
527     ok(buffer[0]=='a', 
528        "EM_SETOPTIONS: Text not changed! s1:%s s2:%s\n", text, buffer);
529     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) text);
530
531     /* READONLY - sending 'a' to the control */
532     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) text);
533     SendMessage(hwndRichEdit, EM_SETOPTIONS, ECOOP_SET, ECO_READONLY);
534     SetFocus(hwndRichEdit);
535     SendMessage(hwndRichEdit, WM_CHAR, 'a', 0x1E0001);
536     SendMessage(hwndRichEdit, WM_GETTEXT, 1024, (LPARAM) buffer);
537     ok(buffer[0]==text[0], 
538        "EM_SETOPTIONS: Text changed! s1:%s s2:%s\n", text, buffer); 
539
540     DestroyWindow(hwndRichEdit);
541 }
542
543 static void check_CFE_LINK_rcvd(HWND hwnd, int is_url)
544 {
545   CHARFORMAT2W text_format;
546   int link_present = 0;
547   text_format.cbSize = sizeof(text_format);
548   SendMessage(hwnd, EM_SETSEL, 0, 0);
549   SendMessage(hwnd, EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM) &text_format);
550   link_present = text_format.dwEffects & CFE_LINK;
551   if (is_url) 
552   { /* control text is url; should get CFE_LINK */
553         ok(0 != link_present, "URL Case: CFE_LINK not set.\n");
554   }
555   else 
556   {
557     ok(0 == link_present, "Non-URL Case: CFE_LINK set.\n");
558   }
559 }
560
561 static HWND new_static_wnd(HWND parent) {
562   return new_window("Static", 0, parent);
563 }
564
565 static void test_EM_AUTOURLDETECT(void)
566 {
567   struct urls_s {
568     char *text;
569     int is_url;
570   } urls[12] = {
571     {"winehq.org", 0},
572     {"http://www.winehq.org", 1},
573     {"http//winehq.org", 0},
574     {"ww.winehq.org", 0},
575     {"www.winehq.org", 1},
576     {"ftp://192.168.1.1", 1},
577     {"ftp//192.168.1.1", 0},
578     {"mailto:your@email.com", 1},    
579     {"prospero:prosperoserver", 1},
580     {"telnet:test", 1},
581     {"news:newserver", 1},
582     {"wais:waisserver", 1}  
583   };
584
585   int i;
586   int urlRet=-1;
587   HWND hwndRichEdit, parent;
588
589   parent = new_static_wnd(NULL);
590   hwndRichEdit = new_richedit(parent);
591   /* Try and pass EM_AUTOURLDETECT some test wParam values */
592   urlRet=SendMessage(hwndRichEdit, EM_AUTOURLDETECT, FALSE, 0);
593   ok(urlRet==0, "Good wParam: urlRet is: %d\n", urlRet);
594   urlRet=SendMessage(hwndRichEdit, EM_AUTOURLDETECT, 1, 0);
595   ok(urlRet==0, "Good wParam2: urlRet is: %d\n", urlRet);
596   /* Windows returns -2147024809 (0x80070057) on bad wParam values */
597   urlRet=SendMessage(hwndRichEdit, EM_AUTOURLDETECT, 8, 0);
598   ok(urlRet==E_INVALIDARG, "Bad wParam: urlRet is: %d\n", urlRet);
599   urlRet=SendMessage(hwndRichEdit, EM_AUTOURLDETECT, (WPARAM)"h", (LPARAM)"h");
600   ok(urlRet==E_INVALIDARG, "Bad wParam2: urlRet is: %d\n", urlRet);
601   /* for each url, check the text to see if CFE_LINK effect is present */
602   for (i = 0; i < sizeof(urls)/sizeof(struct urls_s); i++) {
603     SendMessage(hwndRichEdit, EM_AUTOURLDETECT, FALSE, 0);
604     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) urls[i].text);
605     SendMessage(hwndRichEdit, WM_CHAR, 0, 0);
606     check_CFE_LINK_rcvd(hwndRichEdit, 0);
607     SendMessage(hwndRichEdit, EM_AUTOURLDETECT, TRUE, 0);
608     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) urls[i].text);
609     SendMessage(hwndRichEdit, WM_CHAR, 0, 0);
610     check_CFE_LINK_rcvd(hwndRichEdit, urls[i].is_url);
611   }
612   DestroyWindow(hwndRichEdit);
613   DestroyWindow(parent);
614 }
615
616 static void test_EM_SCROLL()
617 {
618   int i, j;
619   int r; /* return value */
620   int expr; /* expected return value */
621   HWND hwndRichEdit = new_richedit(NULL);
622   int y_before, y_after; /* units of lines of text */
623
624   /* test a richedit box containing a single line of text */
625   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "a");/* one line of text */
626   expr = 0x00010000;
627   for (i = 0; i < 4; i++) {
628     static const int cmd[4] = { SB_PAGEDOWN, SB_PAGEUP, SB_LINEDOWN, SB_LINEUP };
629
630     r = SendMessage(hwndRichEdit, EM_SCROLL, cmd[i], 0);
631     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
632     ok(expr == r, "EM_SCROLL improper return value returned (i == %d). "
633        "Got 0x%08x, expected 0x%08x\n", i, r, expr);
634     ok(y_after == 0, "EM_SCROLL improper scroll. scrolled to line %d, not 1 "
635        "(i == %d)\n", y_after, i);
636   }
637
638   /*
639    * test a richedit box that will scroll. There are two general
640    * cases: the case without any long lines and the case with a long
641    * line.
642    */
643   for (i = 0; i < 2; i++) { /* iterate through different bodies of text */
644     if (i == 0)
645       SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "a\nb\nc\nd\ne");
646     else
647       SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM)
648                   "a LONG LINE LONG LINE LONG LINE LONG LINE LONG LINE "
649                   "LONG LINE LONG LINE LONG LINE LONG LINE LONG LINE "
650                   "LONG LINE \nb\nc\nd\ne");
651     for (j = 0; j < 12; j++) /* reset scrol position to top */
652       SendMessage(hwndRichEdit, EM_SCROLL, SB_PAGEUP, 0);
653
654     /* get first visible line */
655     y_before = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
656     r = SendMessage(hwndRichEdit, EM_SCROLL, SB_PAGEDOWN, 0); /* page down */
657
658     /* get new current first visible line */
659     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
660
661     ok(((r & 0xffffff00) == 0x00010000) &&
662        ((r & 0x000000ff) != 0x00000000),
663        "EM_SCROLL page down didn't scroll by a small positive number of "
664        "lines (r == 0x%08x)\n", r);
665     ok(y_after > y_before, "EM_SCROLL page down not functioning "
666        "(line %d scrolled to line %d\n", y_before, y_after);
667
668     y_before = y_after;
669     
670     r = SendMessage(hwndRichEdit, EM_SCROLL, SB_PAGEUP, 0); /* page up */
671     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
672     ok(((r & 0xffffff00) == 0x0001ff00),
673        "EM_SCROLL page up didn't scroll by a small negative number of lines "
674        "(r == 0x%08x)\n", r);
675     ok(y_after < y_before, "EM_SCROLL page up not functioning (line "
676        "%d scrolled to line %d\n", y_before, y_after);
677     
678     y_before = y_after;
679
680     r = SendMessage(hwndRichEdit, EM_SCROLL, SB_LINEDOWN, 0); /* line down */
681
682     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
683
684     ok(r == 0x00010001, "EM_SCROLL line down didn't scroll by one line "
685        "(r == 0x%08x)\n", r);
686     ok(y_after -1 == y_before, "EM_SCROLL line down didn't go down by "
687        "1 line (%d scrolled to %d)\n", y_before, y_after);
688
689     y_before = y_after;
690
691     r = SendMessage(hwndRichEdit, EM_SCROLL, SB_LINEUP, 0); /* line up */
692
693     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
694
695     ok(r == 0x0001ffff, "EM_SCROLL line up didn't scroll by one line "
696        "(r == 0x%08x)\n", r);
697     ok(y_after +1 == y_before, "EM_SCROLL line up didn't go up by 1 "
698        "line (%d scrolled to %d)\n", y_before, y_after);
699
700     y_before = y_after;
701
702     r = SendMessage(hwndRichEdit, EM_SCROLL,
703                     SB_LINEUP, 0); /* lineup beyond top */
704
705     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
706
707     ok(r == 0x00010000,
708        "EM_SCROLL line up returned indicating movement (0x%08x)\n", r);
709     ok(y_before == y_after,
710        "EM_SCROLL line up beyond top worked (%d)\n", y_after);
711
712     y_before = y_after;
713
714     r = SendMessage(hwndRichEdit, EM_SCROLL,
715                     SB_PAGEUP, 0);/*page up beyond top */
716
717     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
718
719     ok(r == 0x00010000,
720        "EM_SCROLL page up returned indicating movement (0x%08x)\n", r);
721     ok(y_before == y_after,
722        "EM_SCROLL page up beyond top worked (%d)\n", y_after);
723
724     for (j = 0; j < 12; j++) /* page down all the way to the bottom */
725       SendMessage(hwndRichEdit, EM_SCROLL, SB_PAGEDOWN, 0);
726     y_before = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
727     r = SendMessage(hwndRichEdit, EM_SCROLL,
728                     SB_PAGEDOWN, 0); /* page down beyond bot */
729     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
730
731     ok(r == 0x00010000,
732        "EM_SCROLL page down returned indicating movement (0x%08x)\n", r);
733     ok(y_before == y_after,
734        "EM_SCROLL page down beyond bottom worked (%d -> %d)\n",
735        y_before, y_after);
736
737     y_before = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
738     SendMessage(hwndRichEdit, EM_SCROLL,
739                 SB_LINEDOWN, 0); /* line down beyond bot */
740     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
741     
742     ok(r == 0x00010000,
743        "EM_SCROLL line down returned indicating movement (0x%08x)\n", r);
744     ok(y_before == y_after,
745        "EM_SCROLL line down beyond bottom worked (%d -> %d)\n",
746        y_before, y_after);
747   }
748   DestroyWindow(hwndRichEdit);
749 }
750
751 static void test_EM_SETUNDOLIMIT()
752 {
753   /* cases we test for:
754    * default behaviour - limiting at 100 undo's 
755    * undo disabled - setting a limit of 0
756    * undo limited -  undo limit set to some to some number, like 2
757    * bad input - sending a negative number should default to 100 undo's */
758  
759   HWND hwndRichEdit = new_richedit(NULL);
760   CHARRANGE cr;
761   int i;
762   int result;
763   
764   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "x");
765   cr.cpMin = 0;
766   cr.cpMax = 1;
767   SendMessage(hwndRichEdit, WM_COPY, 0, 0);
768     /*Load "x" into the clipboard. Paste is an easy, undo'able operation.
769       also, multiple pastes don't combine like WM_CHAR would */
770   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
771
772   /* first case - check the default */
773   SendMessage(hwndRichEdit,EM_EMPTYUNDOBUFFER, 0,0); 
774   for (i=0; i<101; i++) /* Put 101 undo's on the stack */
775     SendMessage(hwndRichEdit, WM_PASTE, 0, 0); 
776   for (i=0; i<100; i++) /* Undo 100 of them */
777     SendMessage(hwndRichEdit, WM_UNDO, 0, 0); 
778   ok(!SendMessage(hwndRichEdit, EM_CANUNDO, 0, 0),
779      "EM_SETUNDOLIMIT allowed more than a hundred undo's by default.\n");
780
781   /* second case - cannot undo */
782   SendMessage(hwndRichEdit,EM_EMPTYUNDOBUFFER, 0, 0); 
783   SendMessage(hwndRichEdit, EM_SETUNDOLIMIT, 0, 0); 
784   SendMessage(hwndRichEdit,
785               WM_PASTE, 0, 0); /* Try to put something in the undo stack */
786   ok(!SendMessage(hwndRichEdit, EM_CANUNDO, 0, 0),
787      "EM_SETUNDOLIMIT allowed undo with UNDOLIMIT set to 0\n");
788
789   /* third case - set it to an arbitrary number */
790   SendMessage(hwndRichEdit,EM_EMPTYUNDOBUFFER, 0, 0); 
791   SendMessage(hwndRichEdit, EM_SETUNDOLIMIT, 2, 0); 
792   SendMessage(hwndRichEdit, WM_PASTE, 0, 0);
793   SendMessage(hwndRichEdit, WM_PASTE, 0, 0);
794   SendMessage(hwndRichEdit, WM_PASTE, 0, 0); 
795   /* If SETUNDOLIMIT is working, there should only be two undo's after this */
796   ok(SendMessage(hwndRichEdit, EM_CANUNDO, 0,0),
797      "EM_SETUNDOLIMIT didn't allow the first undo with UNDOLIMIT set to 2\n");
798   SendMessage(hwndRichEdit, WM_UNDO, 0, 0);
799   ok(SendMessage(hwndRichEdit, EM_CANUNDO, 0, 0),
800      "EM_SETUNDOLIMIT didn't allow a second undo with UNDOLIMIT set to 2\n");
801   SendMessage(hwndRichEdit, WM_UNDO, 0, 0); 
802   ok(!SendMessage(hwndRichEdit, EM_CANUNDO, 0, 0),
803      "EM_SETUNDOLIMIT allowed a third undo with UNDOLIMIT set to 2\n");
804   
805   /* fourth case - setting negative numbers should default to 100 undos */
806   SendMessage(hwndRichEdit,EM_EMPTYUNDOBUFFER, 0,0); 
807   result = SendMessage(hwndRichEdit, EM_SETUNDOLIMIT, -1, 0);
808   ok (result == 100, 
809       "EM_SETUNDOLIMIT returned %d when set to -1, instead of 100\n",result);
810       
811   DestroyWindow(hwndRichEdit);
812 }
813
814
815 START_TEST( editor )
816 {
817   MSG msg;
818   time_t end;
819
820   /* Must explicitly LoadLibrary(). The test has no references to functions in
821    * RICHED20.DLL, so the linker doesn't actually link to it. */
822   hmoduleRichEdit = LoadLibrary("RICHED20.DLL");
823   ok(hmoduleRichEdit != NULL, "error: %d\n", (int) GetLastError());
824   test_EM_FINDTEXT();
825   test_EM_SCROLLCARET();
826   test_EM_SCROLL();
827   test_EM_SETTEXTMODE();
828   test_TM_PLAINTEXT();
829   test_EM_SETOPTIONS();
830   test_WM_GETTEXT();
831   test_EM_AUTOURLDETECT();
832   test_EM_SETUNDOLIMIT();
833
834   /* Set the environment variable WINETEST_RICHED20 to keep windows
835    * responsive and open for 30 seconds. This is useful for debugging.
836    *
837    * The message pump uses PeekMessage() to empty the queue and then sleeps for
838    * 50ms before retrying the queue. */
839   end = time(NULL) + 30;
840   if (getenv( "WINETEST_RICHED20" )) {
841     while (time(NULL) < end) {
842       if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
843         TranslateMessage(&msg);
844         DispatchMessage(&msg);
845       } else {
846         Sleep(50);
847       }
848     }
849   }
850
851   ok(FreeLibrary(hmoduleRichEdit) != 0, "error: %d\n", (int) GetLastError());
852 }