Update the address of the Free Software Foundation.
[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: %f cf2test.dwEffects: %f\n",(double) cf2.dwEffects, (double) cf2test.dwEffects);
347   /*Test TM_RICHTEXT by: switching back to Rich Text mode
348                          printing "wine" in the current format(normal)
349                          pasting "wine" from the clipboard(italicized)
350                          comparing the two formats(should differ)*/
351
352   /*Attempt to switch with text in control*/
353   rc = SendMessage(hwndRichEdit, EM_SETTEXTMODE, (WPARAM) TM_RICHTEXT, 0);
354   ok(rc != 0, "EM_SETTEXTMODE: changed from plain text to rich text with text in control - returned: %d\n", rc);
355
356   /*Clear control*/
357   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "");
358
359   /*Switch into Rich Text mode*/
360   rc = SendMessage(hwndRichEdit, EM_SETTEXTMODE, (WPARAM) TM_RICHTEXT, 0);
361   ok(rc == 0, "EM_SETTEXTMODE: unable to change to rich text with empty control - returned: %d\n", rc);
362
363   /*Print "wine" in normal formatting into the control*/
364   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "wine");
365
366   /*Paste italicized "wine" into the control*/
367   SendMessage(hwndRichEdit, WM_PASTE, 0, 0);
368
369   /*Select text from the first "wine" string*/
370   cr.cpMin = 1;
371   cr.cpMax = 3;
372   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
373
374   /*Retrieve its formatting*/
375   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_SELECTION,
376                 (LPARAM) &cf2);
377
378   /*Select text from the second "wine" string*/
379   cr.cpMin = 6;
380   cr.cpMax = 7;
381   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
382
383   /*Retrieve its formatting*/
384   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_SELECTION,
385                 (LPARAM) &cf2test);
386
387   /*Test that the two formattings are not the same*/
388   ok((cf2.dwMask == cf2test.dwMask) && (cf2.dwEffects != cf2test.dwEffects),
389       "expected different formats - cf2.dwMask: %lx, cf2test.dwMask: %lx, cf2.dwEffects: %lx, cf2test.dwEffects: %lx\n",
390       cf2.dwMask, cf2test.dwMask, cf2.dwEffects, cf2test.dwEffects);
391
392   DestroyWindow(hwndRichEdit);
393 }
394
395 static void test_TM_PLAINTEXT()
396 {
397   /*Tests plain text properties*/
398
399   HWND hwndRichEdit = new_richedit(NULL);
400   CHARFORMAT2 cf2, cf2test;
401   CHARRANGE cr;
402
403   /*Switch to plain text mode*/
404
405   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "");
406   SendMessage(hwndRichEdit, EM_SETTEXTMODE, TM_PLAINTEXT, 0);
407
408   /*Fill control with text*/
409
410   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "Is Wine an emulator? No it's not");
411
412   /*Select some text and bold it*/
413
414   cr.cpMin = 10;
415   cr.cpMax = 20;
416   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
417   cf2.cbSize = sizeof(CHARFORMAT2);
418   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_DEFAULT,
419               (LPARAM) &cf2);
420
421   cf2.dwMask = CFM_BOLD | cf2.dwMask;
422   cf2.dwEffects = CFE_BOLD ^ cf2.dwEffects;
423
424   SendMessage(hwndRichEdit, EM_SETCHARFORMAT, (WPARAM) SCF_SELECTION, (LPARAM) &cf2);
425
426   /*Get the formatting of those characters*/
427
428   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_SELECTION, (LPARAM) &cf2);
429
430   /*Get the formatting of some other characters*/
431   cf2test.cbSize = sizeof(CHARFORMAT2);
432   cr.cpMin = 21;
433   cr.cpMax = 30;
434   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
435   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_SELECTION, (LPARAM) &cf2test);
436
437   /*Test that they are the same as plain text allows only one formatting*/
438
439   ok((cf2.dwMask == cf2test.dwMask) && (cf2.dwEffects == cf2test.dwEffects),
440      "two selections' formats differ - cf2.dwMask: %lx, cf2test.dwMask %lx, cf2.dwEffects: %lx, cf2test.dwEffects: %lx\n",
441      cf2.dwMask, cf2test.dwMask, cf2.dwEffects, cf2test.dwEffects);
442   
443   /*Fill the control with a "wine" string, which when inserted will be bold*/
444
445   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "wine");
446
447   /*Copy the bolded "wine" string*/
448
449   cr.cpMin = 0;
450   cr.cpMax = 4;
451   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
452   SendMessage(hwndRichEdit, WM_COPY, 0, 0);
453
454   /*Swap back to rich text*/
455
456   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "");
457   SendMessage(hwndRichEdit, EM_SETTEXTMODE, (WPARAM) TM_RICHTEXT, 0);
458
459   /*Set the default formatting to bold italics*/
460
461   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_DEFAULT, (LPARAM) &cf2);
462   cf2.dwMask |= CFM_ITALIC;
463   cf2.dwEffects ^= CFE_ITALIC;
464   SendMessage(hwndRichEdit, EM_SETCHARFORMAT, (WPARAM) SCF_ALL, (LPARAM) &cf2);
465
466   /*Set the text in the control to "wine", which will be bold and italicized*/
467
468   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "wine");
469
470   /*Paste the plain text "wine" string, which should take the insert
471    formatting, which at the moment is bold italics*/
472
473   SendMessage(hwndRichEdit, WM_PASTE, 0, 0);
474
475   /*Select the first "wine" string and retrieve its formatting*/
476
477   cr.cpMin = 1;
478   cr.cpMax = 3;
479   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
480   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_SELECTION, (LPARAM) &cf2);
481
482   /*Select the second "wine" string and retrieve its formatting*/
483
484   cr.cpMin = 5;
485   cr.cpMax = 7;
486   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
487   SendMessage(hwndRichEdit, EM_GETCHARFORMAT, (WPARAM) SCF_SELECTION, (LPARAM) &cf2test);
488
489   /*Compare the two formattings. They should be the same.*/
490
491   ok((cf2.dwMask == cf2test.dwMask) && (cf2.dwEffects == cf2test.dwEffects),
492      "Copied text retained formatting - cf2.dwMask: %lx, cf2test.dwMask: %lx, cf2.dwEffects: %lx, cf2test.dwEffects: %lx\n",
493      cf2.dwMask, cf2test.dwMask, cf2.dwEffects, cf2test.dwEffects);
494   DestroyWindow(hwndRichEdit);
495 }
496
497 static void test_WM_GETTEXT()
498 {
499     HWND hwndRichEdit = new_richedit(NULL);
500     static const char text[] = "Hello. My name is RichEdit!";
501     char buffer[1024] = {0};
502     int result;
503
504     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) text);
505     SendMessage(hwndRichEdit, WM_GETTEXT, 1024, (LPARAM) buffer);
506     result = strcmp(buffer,text);
507     ok(result == 0, 
508         "WM_GETTEXT: settext and gettext differ. strcmp: %d\n", result);
509 }
510
511 /* FIXME: need to test unimplemented options and robustly test wparam */
512 static void test_EM_SETOPTIONS()
513 {
514     HWND hwndRichEdit = new_richedit(NULL);
515     static const char text[] = "Hello. My name is RichEdit!";
516     char buffer[1024] = {0};
517
518     /* NEGATIVE TESTING - NO OPTIONS SET */
519     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) text);
520     SendMessage(hwndRichEdit, EM_SETOPTIONS, ECOOP_SET, 0);
521
522     /* testing no readonly by sending 'a' to the control*/
523     SetFocus(hwndRichEdit);
524     SendMessage(hwndRichEdit, WM_CHAR, 'a', 0x1E0001);
525     SendMessage(hwndRichEdit, WM_GETTEXT, 1024, (LPARAM) buffer);
526     ok(buffer[0]=='a', 
527        "EM_SETOPTIONS: Text not changed! s1:%s s2:%s\n", text, buffer);
528     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) text);
529
530     /* READONLY - sending 'a' to the control */
531     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) text);
532     SendMessage(hwndRichEdit, EM_SETOPTIONS, ECOOP_SET, ECO_READONLY);
533     SetFocus(hwndRichEdit);
534     SendMessage(hwndRichEdit, WM_CHAR, 'a', 0x1E0001);
535     SendMessage(hwndRichEdit, WM_GETTEXT, 1024, (LPARAM) buffer);
536     ok(buffer[0]==text[0], 
537        "EM_SETOPTIONS: Text changed! s1:%s s2:%s\n", text, buffer); 
538
539     DestroyWindow(hwndRichEdit);
540 }
541
542 static void check_CFE_LINK_rcvd(HWND hwnd, int is_url)
543 {
544   CHARFORMAT2W text_format;
545   int link_present = 0;
546   text_format.cbSize = sizeof(text_format);
547   SendMessage(hwnd, EM_SETSEL, 0, 0);
548   SendMessage(hwnd, EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM) &text_format);
549   link_present = text_format.dwEffects & CFE_LINK;
550   if (is_url) 
551   { /* control text is url; should get CFE_LINK */
552         ok(0 != link_present, "URL Case: CFE_LINK not set.\n");
553   }
554   else 
555   {
556     ok(0 == link_present, "Non-URL Case: CFE_LINK set.\n");
557   }
558 }
559
560 static HWND new_static_wnd(HWND parent) {
561   return new_window("Static", 0, parent);
562 }
563
564 static void test_EM_AUTOURLDETECT(void)
565 {
566   struct urls_s {
567     char *text;
568     int is_url;
569   } urls[12] = {
570     {"winehq.org", 0},
571     {"http://www.winehq.org", 1},
572     {"http//winehq.org", 0},
573     {"ww.winehq.org", 0},
574     {"www.winehq.org", 1},
575     {"ftp://192.168.1.1", 1},
576     {"ftp//192.168.1.1", 0},
577     {"mailto:your@email.com", 1},    
578     {"prospero:prosperoserver", 1},
579     {"telnet:test", 1},
580     {"news:newserver", 1},
581     {"wais:waisserver", 1}  
582   };
583
584   int i;
585   int urlRet=-1;
586   HWND hwndRichEdit, parent;
587
588   parent = new_static_wnd(NULL);
589   hwndRichEdit = new_richedit(parent);
590   /* Try and pass EM_AUTOURLDETECT some test wParam values */
591   urlRet=SendMessage(hwndRichEdit, EM_AUTOURLDETECT, FALSE, 0);
592   ok(urlRet==0, "Good wParam: urlRet is: %d\n", urlRet);
593   urlRet=SendMessage(hwndRichEdit, EM_AUTOURLDETECT, 1, 0);
594   ok(urlRet==0, "Good wParam2: urlRet is: %d\n", urlRet);
595   /* Windows returns -2147024809 (0x80070057) on bad wParam values */
596   urlRet=SendMessage(hwndRichEdit, EM_AUTOURLDETECT, 8, 0);
597   ok(urlRet==E_INVALIDARG, "Bad wParam: urlRet is: %d\n", urlRet);
598   urlRet=SendMessage(hwndRichEdit, EM_AUTOURLDETECT, (WPARAM)"h", (LPARAM)"h");
599   ok(urlRet==E_INVALIDARG, "Bad wParam2: urlRet is: %d\n", urlRet);
600   /* for each url, check the text to see if CFE_LINK effect is present */
601   for (i = 0; i < sizeof(urls)/sizeof(struct urls_s); i++) {
602     SendMessage(hwndRichEdit, EM_AUTOURLDETECT, FALSE, 0);
603     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) urls[i].text);
604     SendMessage(hwndRichEdit, WM_CHAR, 0, 0);
605     check_CFE_LINK_rcvd(hwndRichEdit, 0);
606     SendMessage(hwndRichEdit, EM_AUTOURLDETECT, TRUE, 0);
607     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) urls[i].text);
608     SendMessage(hwndRichEdit, WM_CHAR, 0, 0);
609     check_CFE_LINK_rcvd(hwndRichEdit, urls[i].is_url);
610   }
611   DestroyWindow(hwndRichEdit);
612   DestroyWindow(parent);
613 }
614
615 static void test_EM_SCROLL()
616 {
617   int i, j;
618   int r; /* return value */
619   int expr; /* expected return value */
620   HWND hwndRichEdit = new_richedit(NULL);
621   int y_before, y_after; /* units of lines of text */
622
623   /* test a richedit box containing a single line of text */
624   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "a");/* one line of text */
625   expr = 0x00010000;
626   for (i = 0; i < 4; i++) {
627     static const int cmd[4] = { SB_PAGEDOWN, SB_PAGEUP, SB_LINEDOWN, SB_LINEUP };
628
629     r = SendMessage(hwndRichEdit, EM_SCROLL, cmd[i], 0);
630     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
631     ok(expr == r, "EM_SCROLL improper return value returned (i == %d). "
632        "Got 0x%08x, expected 0x%08x\n", i, r, expr);
633     ok(y_after == 0, "EM_SCROLL improper scroll. scrolled to line %d, not 1 "
634        "(i == %d)\n", y_after, i);
635   }
636
637   /*
638    * test a richedit box that will scroll. There are two general
639    * cases: the case without any long lines and the case with a long
640    * line.
641    */
642   for (i = 0; i < 2; i++) { /* iterate through different bodies of text */
643     if (i == 0)
644       SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "a\nb\nc\nd\ne");
645     else
646       SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM)
647                   "a LONG LINE LONG LINE LONG LINE LONG LINE LONG LINE "
648                   "LONG LINE LONG LINE LONG LINE LONG LINE LONG LINE "
649                   "LONG LINE \nb\nc\nd\ne");
650     for (j = 0; j < 12; j++) /* reset scrol position to top */
651       SendMessage(hwndRichEdit, EM_SCROLL, SB_PAGEUP, 0);
652
653     /* get first visible line */
654     y_before = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
655     r = SendMessage(hwndRichEdit, EM_SCROLL, SB_PAGEDOWN, 0); /* page down */
656
657     /* get new current first visible line */
658     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
659
660     ok(((r & 0xffffff00) == 0x00010000) &&
661        ((r & 0x000000ff) != 0x00000000),
662        "EM_SCROLL page down didn't scroll by a small positive number of "
663        "lines (r == 0x%08x)\n", r);
664     ok(y_after > y_before, "EM_SCROLL page down not functioning "
665        "(line %d scrolled to line %d\n", y_before, y_after);
666
667     y_before = y_after;
668     
669     r = SendMessage(hwndRichEdit, EM_SCROLL, SB_PAGEUP, 0); /* page up */
670     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
671     ok(((r & 0xffffff00) == 0x0001ff00),
672        "EM_SCROLL page up didn't scroll by a small negative number of lines "
673        "(r == 0x%08x)\n", r);
674     ok(y_after < y_before, "EM_SCROLL page up not functioning (line "
675        "%d scrolled to line %d\n", y_before, y_after);
676     
677     y_before = y_after;
678
679     r = SendMessage(hwndRichEdit, EM_SCROLL, SB_LINEDOWN, 0); /* line down */
680
681     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
682
683     ok(r == 0x00010001, "EM_SCROLL line down didn't scroll by one line "
684        "(r == 0x%08x)\n", r);
685     ok(y_after -1 == y_before, "EM_SCROLL line down didn't go down by "
686        "1 line (%d scrolled to %d)\n", y_before, y_after);
687
688     y_before = y_after;
689
690     r = SendMessage(hwndRichEdit, EM_SCROLL, SB_LINEUP, 0); /* line up */
691
692     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
693
694     ok(r == 0x0001ffff, "EM_SCROLL line up didn't scroll by one line "
695        "(r == 0x%08x)\n", r);
696     ok(y_after +1 == y_before, "EM_SCROLL line up didn't go up by 1 "
697        "line (%d scrolled to %d)\n", y_before, y_after);
698
699     y_before = y_after;
700
701     r = SendMessage(hwndRichEdit, EM_SCROLL,
702                     SB_LINEUP, 0); /* lineup beyond top */
703
704     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
705
706     ok(r == 0x00010000,
707        "EM_SCROLL line up returned indicating movement (0x%08x)\n", r);
708     ok(y_before == y_after,
709        "EM_SCROLL line up beyond top worked (%d)\n", y_after);
710
711     y_before = y_after;
712
713     r = SendMessage(hwndRichEdit, EM_SCROLL,
714                     SB_PAGEUP, 0);/*page up beyond top */
715
716     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
717
718     ok(r == 0x00010000,
719        "EM_SCROLL page up returned indicating movement (0x%08x)\n", r);
720     ok(y_before == y_after,
721        "EM_SCROLL page up beyond top worked (%d)\n", y_after);
722
723     for (j = 0; j < 12; j++) /* page down all the way to the bottom */
724       SendMessage(hwndRichEdit, EM_SCROLL, SB_PAGEDOWN, 0);
725     y_before = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
726     r = SendMessage(hwndRichEdit, EM_SCROLL,
727                     SB_PAGEDOWN, 0); /* page down beyond bot */
728     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
729
730     ok(r == 0x00010000,
731        "EM_SCROLL page down returned indicating movement (0x%08x)\n", r);
732     ok(y_before == y_after,
733        "EM_SCROLL page down beyond bottom worked (%d -> %d)\n",
734        y_before, y_after);
735
736     y_before = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
737     SendMessage(hwndRichEdit, EM_SCROLL,
738                 SB_LINEDOWN, 0); /* line down beyond bot */
739     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
740     
741     ok(r == 0x00010000,
742        "EM_SCROLL line down returned indicating movement (0x%08x)\n", r);
743     ok(y_before == y_after,
744        "EM_SCROLL line down beyond bottom worked (%d -> %d)\n",
745        y_before, y_after);
746   }
747   DestroyWindow(hwndRichEdit);
748 }
749
750 static void test_EM_SETUNDOLIMIT()
751 {
752   /* cases we test for:
753    * default behaviour - limiting at 100 undo's 
754    * undo disabled - setting a limit of 0
755    * undo limited -  undo limit set to some to some number, like 2
756    * bad input - sending a negative number should default to 100 undo's */
757  
758   HWND hwndRichEdit = new_richedit(NULL);
759   CHARRANGE cr;
760   int i;
761   int result;
762   
763   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "x");
764   cr.cpMin = 0;
765   cr.cpMax = 1;
766   SendMessage(hwndRichEdit, WM_COPY, 0, 0);
767     /*Load "x" into the clipboard. Paste is an easy, undo'able operation.
768       also, multiple pastes don't combine like WM_CHAR would */
769   SendMessage(hwndRichEdit, EM_EXSETSEL, 0, (LPARAM) &cr);
770
771   /* first case - check the default */
772   SendMessage(hwndRichEdit,EM_EMPTYUNDOBUFFER, 0,0); 
773   for (i=0; i<101; i++) /* Put 101 undo's on the stack */
774     SendMessage(hwndRichEdit, WM_PASTE, 0, 0); 
775   for (i=0; i<100; i++) /* Undo 100 of them */
776     SendMessage(hwndRichEdit, WM_UNDO, 0, 0); 
777   ok(!SendMessage(hwndRichEdit, EM_CANUNDO, 0, 0),
778      "EM_SETUNDOLIMIT allowed more than a hundred undo's by default.\n");
779
780   /* second case - cannot undo */
781   SendMessage(hwndRichEdit,EM_EMPTYUNDOBUFFER, 0, 0); 
782   SendMessage(hwndRichEdit, EM_SETUNDOLIMIT, 0, 0); 
783   SendMessage(hwndRichEdit,
784               WM_PASTE, 0, 0); /* Try to put something in the undo stack */
785   ok(!SendMessage(hwndRichEdit, EM_CANUNDO, 0, 0),
786      "EM_SETUNDOLIMIT allowed undo with UNDOLIMIT set to 0\n");
787
788   /* third case - set it to an arbitrary number */
789   SendMessage(hwndRichEdit,EM_EMPTYUNDOBUFFER, 0, 0); 
790   SendMessage(hwndRichEdit, EM_SETUNDOLIMIT, 2, 0); 
791   SendMessage(hwndRichEdit, WM_PASTE, 0, 0);
792   SendMessage(hwndRichEdit, WM_PASTE, 0, 0);
793   SendMessage(hwndRichEdit, WM_PASTE, 0, 0); 
794   /* If SETUNDOLIMIT is working, there should only be two undo's after this */
795   ok(SendMessage(hwndRichEdit, EM_CANUNDO, 0,0),
796      "EM_SETUNDOLIMIT didn't allow the first undo with UNDOLIMIT set to 2\n");
797   SendMessage(hwndRichEdit, WM_UNDO, 0, 0);
798   ok(SendMessage(hwndRichEdit, EM_CANUNDO, 0, 0),
799      "EM_SETUNDOLIMIT didn't allow a second undo with UNDOLIMIT set to 2\n");
800   SendMessage(hwndRichEdit, WM_UNDO, 0, 0); 
801   ok(!SendMessage(hwndRichEdit, EM_CANUNDO, 0, 0),
802      "EM_SETUNDOLIMIT allowed a third undo with UNDOLIMIT set to 2\n");
803   
804   /* fourth case - setting negative numbers should default to 100 undos */
805   SendMessage(hwndRichEdit,EM_EMPTYUNDOBUFFER, 0,0); 
806   result = SendMessage(hwndRichEdit, EM_SETUNDOLIMIT, -1, 0);
807   ok (result == 100, 
808       "EM_SETUNDOLIMIT returned %d when set to -1, instead of 100",result);
809       
810   DestroyWindow(hwndRichEdit);
811 }
812
813
814 START_TEST( editor )
815 {
816   MSG msg;
817   time_t end;
818
819   /* Must explicitly LoadLibrary(). The test has no references to functions in
820    * RICHED20.DLL, so the linker doesn't actually link to it. */
821   hmoduleRichEdit = LoadLibrary("RICHED20.DLL");
822   ok(hmoduleRichEdit != NULL, "error: %d\n", (int) GetLastError());
823   test_EM_FINDTEXT();
824   test_EM_SCROLLCARET();
825   test_EM_SCROLL();
826   test_EM_SETTEXTMODE();
827   test_TM_PLAINTEXT();
828   test_EM_SETOPTIONS();
829   test_WM_GETTEXT();
830   test_EM_AUTOURLDETECT();
831   test_EM_SETUNDOLIMIT();
832
833   /* Set the environment variable WINETEST_RICHED20 to keep windows
834    * responsive and open for 30 seconds. This is useful for debugging.
835    *
836    * The message pump uses PeekMessage() to empty the queue and then sleeps for
837    * 50ms before retrying the queue. */
838   end = time(NULL) + 30;
839   if (getenv( "WINETEST_RICHED20" )) {
840     while (time(NULL) < end) {
841       if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
842         TranslateMessage(&msg);
843         DispatchMessage(&msg);
844       } else {
845         Sleep(50);
846       }
847     }
848   }
849
850   ok(FreeLibrary(hmoduleRichEdit) != 0, "error: %d\n", (int) GetLastError());
851 }