comctl32: A couple fixes for tab icon offsets.
[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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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: %f, cf2test.dwMask: %f, cf2.dwEffects: %f, cf2test.dwEffects: %f\n",
390       (double) cf2.dwMask, (double) cf2test.dwMask, (double) cf2.dwEffects, (double) 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: %f, cf2test.dwMask %f, cf2.dwEffects: %f, cf2test.dwEffects: %f\n",
441      (double) cf2.dwMask, (double) cf2test.dwMask, (double) cf2.dwEffects, (double) 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: %f, cf2test.dwMask: %f, cf2.dwEffects: %f, cf2test.dwEffects: %f\n",
493      (double) cf2.dwMask, (double) cf2test.dwMask, (double) cf2.dwEffects, (double) cf2test.dwEffects);
494   DestroyWindow(hwndRichEdit);
495 }
496
497 /* FIXME: Extra '\r' appended at end of gotten text*/
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     todo_wine{
509       ok(result == 0, 
510         "WM_GETTEXT: settext and gettext differ. strcmp: %d\n", result);
511     }
512 }
513
514 /* FIXME: need to test unimplemented options and robustly test wparam */
515 static void test_EM_SETOPTIONS()
516 {
517     HWND hwndRichEdit = new_richedit(NULL);
518     static const char text[] = "Hello. My name is RichEdit!";
519     char buffer[1024] = {0};
520
521     /* NEGATIVE TESTING - NO OPTIONS SET */
522     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) text);
523     SendMessage(hwndRichEdit, EM_SETOPTIONS, ECOOP_SET, 0);
524
525     /* testing no readonly by sending 'a' to the control*/
526     SetFocus(hwndRichEdit);
527     SendMessage(hwndRichEdit, WM_CHAR, 'a', 0x1E0001);
528     SendMessage(hwndRichEdit, WM_GETTEXT, 1024, (LPARAM) buffer);
529     ok(buffer[0]=='a', 
530        "EM_SETOPTIONS: Text not changed! s1:%s s2:%s\n", text, buffer);
531     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) text);
532
533     /* READONLY - sending 'a' to the control */
534     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) text);
535     SendMessage(hwndRichEdit, EM_SETOPTIONS, ECOOP_SET, ECO_READONLY);
536     SetFocus(hwndRichEdit);
537     SendMessage(hwndRichEdit, WM_CHAR, 'a', 0x1E0001);
538     SendMessage(hwndRichEdit, WM_GETTEXT, 1024, (LPARAM) buffer);
539     ok(buffer[0]==text[0], 
540        "EM_SETOPTIONS: Text changed! s1:%s s2:%s\n", text, buffer); 
541
542     DestroyWindow(hwndRichEdit);
543 }
544
545 static void check_CFE_LINK_rcvd(HWND hwnd, int is_url)
546 {
547   CHARFORMAT2W text_format;
548   int link_present = 0;
549   text_format.cbSize = sizeof(text_format);
550   SendMessage(hwnd, EM_SETSEL, 0, 0);
551   SendMessage(hwnd, EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM) &text_format);
552   link_present = text_format.dwEffects & CFE_LINK;
553   if (is_url) 
554   { /* control text is url; should get CFE_LINK */
555         ok(0 != link_present, "URL Case: CFE_LINK not set.\n");
556   }
557   else 
558   {
559     ok(0 == link_present, "Non-URL Case: CFE_LINK set.\n");
560   }
561 }
562
563 static HWND new_static_wnd(HWND parent) {
564   return new_window("Static", 0, parent);
565 }
566
567 static void test_EM_AUTOURLDETECT(void)
568 {
569   struct urls_s {
570     char *text;
571     int is_url;
572   } urls[12] = {
573     {"winehq.org", 0},
574     {"http://www.winehq.org", 1},
575     {"http//winehq.org", 0},
576     {"ww.winehq.org", 0},
577     {"www.winehq.org", 1},
578     {"ftp://192.168.1.1", 1},
579     {"ftp//192.168.1.1", 0},
580     {"mailto:your@email.com", 1},    
581     {"prospero:prosperoserver", 1},
582     {"telnet:test", 1},
583     {"news:newserver", 1},
584     {"wais:waisserver", 1}  
585   };
586
587   int i;
588   int urlRet=-1;
589   HWND hwndRichEdit, parent;
590
591   parent = new_static_wnd(NULL);
592   hwndRichEdit = new_richedit(parent);
593   /* Try and pass EM_AUTOURLDETECT some test wParam values */
594   urlRet=SendMessage(hwndRichEdit, EM_AUTOURLDETECT, FALSE, 0);
595   ok(urlRet==0, "Good wParam: urlRet is: %d\n", urlRet);
596   urlRet=SendMessage(hwndRichEdit, EM_AUTOURLDETECT, 1, 0);
597   ok(urlRet==0, "Good wParam2: urlRet is: %d\n", urlRet);
598   /* Windows returns -2147024809 (0x80070057) on bad wParam values */
599   urlRet=SendMessage(hwndRichEdit, EM_AUTOURLDETECT, 8, 0);
600   ok(urlRet==E_INVALIDARG, "Bad wParam: urlRet is: %d\n", urlRet);
601   urlRet=SendMessage(hwndRichEdit, EM_AUTOURLDETECT, (WPARAM)"h", (LPARAM)"h");
602   ok(urlRet==E_INVALIDARG, "Bad wParam2: urlRet is: %d\n", urlRet);
603   /* for each url, check the text to see if CFE_LINK effect is present */
604   for (i = 0; i < sizeof(urls)/sizeof(struct urls_s); i++) {
605     SendMessage(hwndRichEdit, EM_AUTOURLDETECT, FALSE, 0);
606     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) urls[i].text);
607     SendMessage(hwndRichEdit, WM_CHAR, 0, 0);
608     check_CFE_LINK_rcvd(hwndRichEdit, 0);
609     SendMessage(hwndRichEdit, EM_AUTOURLDETECT, TRUE, 0);
610     SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) urls[i].text);
611     SendMessage(hwndRichEdit, WM_CHAR, 0, 0);
612     check_CFE_LINK_rcvd(hwndRichEdit, urls[i].is_url);
613   }
614   DestroyWindow(hwndRichEdit);
615   DestroyWindow(parent);
616 }
617
618 static void test_EM_SCROLL()
619 {
620   int i, j;
621   int r; /* return value */
622   int expr; /* expected return value */
623   HWND hwndRichEdit = new_richedit(NULL);
624   int y_before, y_after; /* units of lines of text */
625
626   /* test a richedit box containing a single line of text */
627   SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "a");/* one line of text */
628   expr = 0x00010000;
629   for (i = 0; i < 4; i++) {
630     static const int cmd[4] = { SB_PAGEDOWN, SB_PAGEUP, SB_LINEDOWN, SB_LINEUP };
631
632     r = SendMessage(hwndRichEdit, EM_SCROLL, cmd[i], 0);
633     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
634     ok(expr == r, "EM_SCROLL improper return value returned (i == %d). "
635        "Got 0x%08x, expected 0x%08x\n", i, r, expr);
636     ok(y_after == 0, "EM_SCROLL improper scroll. scrolled to line %d, not 1 "
637        "(i == %d)\n", y_after, i);
638   }
639
640   /*
641    * test a richedit box that will scroll. There are two general
642    * cases: the case without any long lines and the case with a long
643    * line.
644    */
645   for (i = 0; i < 2; i++) { /* iterate through different bodies of text */
646     if (i == 0)
647       SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) "a\nb\nc\nd\ne");
648     else
649       SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM)
650                   "a LONG LINE LONG LINE LONG LINE LONG LINE LONG LINE "
651                   "LONG LINE LONG LINE LONG LINE LONG LINE LONG LINE "
652                   "LONG LINE \nb\nc\nd\ne");
653     for (j = 0; j < 12; j++) /* reset scrol position to top */
654       SendMessage(hwndRichEdit, EM_SCROLL, SB_PAGEUP, 0);
655
656     /* get first visible line */
657     y_before = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
658     r = SendMessage(hwndRichEdit, EM_SCROLL, SB_PAGEDOWN, 0); /* page down */
659
660     /* get new current first visible line */
661     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
662
663     ok(((r & 0xffffff00) == 0x00010000) &&
664        ((r & 0x000000ff) != 0x00000000),
665        "EM_SCROLL page down didn't scroll by a small positive number of "
666        "lines (r == 0x%08x)\n", r);
667     ok(y_after > y_before, "EM_SCROLL page down not functioning "
668        "(line %d scrolled to line %d\n", y_before, y_after);
669
670     y_before = y_after;
671     
672     r = SendMessage(hwndRichEdit, EM_SCROLL, SB_PAGEUP, 0); /* page up */
673     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
674     ok(((r & 0xffffff00) == 0x0001ff00),
675        "EM_SCROLL page up didn't scroll by a small negative number of lines "
676        "(r == 0x%08x)\n", r);
677     ok(y_after < y_before, "EM_SCROLL page up not functioning (line "
678        "%d scrolled to line %d\n", y_before, y_after);
679     
680     y_before = y_after;
681
682     r = SendMessage(hwndRichEdit, EM_SCROLL, SB_LINEDOWN, 0); /* line down */
683
684     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
685
686     ok(r == 0x00010001, "EM_SCROLL line down didn't scroll by one line "
687        "(r == 0x%08x)\n", r);
688     ok(y_after -1 == y_before, "EM_SCROLL line down didn't go down by "
689        "1 line (%d scrolled to %d)\n", y_before, y_after);
690
691     y_before = y_after;
692
693     r = SendMessage(hwndRichEdit, EM_SCROLL, SB_LINEUP, 0); /* line up */
694
695     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
696
697     ok(r == 0x0001ffff, "EM_SCROLL line up didn't scroll by one line "
698        "(r == 0x%08x)\n", r);
699     ok(y_after +1 == y_before, "EM_SCROLL line up didn't go up by 1 "
700        "line (%d scrolled to %d)\n", y_before, y_after);
701
702     y_before = y_after;
703
704     r = SendMessage(hwndRichEdit, EM_SCROLL,
705                     SB_LINEUP, 0); /* lineup beyond top */
706
707     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
708
709     ok(r == 0x00010000,
710        "EM_SCROLL line up returned indicating movement (0x%08x)\n", r);
711     ok(y_before == y_after,
712        "EM_SCROLL line up beyond top worked (%d)\n", y_after);
713
714     y_before = y_after;
715
716     r = SendMessage(hwndRichEdit, EM_SCROLL,
717                     SB_PAGEUP, 0);/*page up beyond top */
718
719     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
720
721     ok(r == 0x00010000,
722        "EM_SCROLL page up returned indicating movement (0x%08x)\n", r);
723     ok(y_before == y_after,
724        "EM_SCROLL page up beyond top worked (%d)\n", y_after);
725
726     for (j = 0; j < 12; j++) /* page down all the way to the bottom */
727       SendMessage(hwndRichEdit, EM_SCROLL, SB_PAGEDOWN, 0);
728     y_before = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
729     r = SendMessage(hwndRichEdit, EM_SCROLL,
730                     SB_PAGEDOWN, 0); /* page down beyond bot */
731     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
732
733     ok(r == 0x00010000,
734        "EM_SCROLL page down returned indicating movement (0x%08x)\n", r);
735     ok(y_before == y_after,
736        "EM_SCROLL page down beyond bottom worked (%d -> %d)\n",
737        y_before, y_after);
738
739     y_before = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
740     SendMessage(hwndRichEdit, EM_SCROLL,
741                 SB_LINEDOWN, 0); /* line down beyond bot */
742     y_after = SendMessage(hwndRichEdit, EM_GETFIRSTVISIBLELINE, 0, 0);
743     
744     ok(r == 0x00010000,
745        "EM_SCROLL line down returned indicating movement (0x%08x)\n", r);
746     ok(y_before == y_after,
747        "EM_SCROLL line down beyond bottom worked (%d -> %d)\n",
748        y_before, y_after);
749   }
750   DestroyWindow(hwndRichEdit);
751 }
752
753 START_TEST( editor )
754 {
755   MSG msg;
756   time_t end;
757
758   /* Must explicitly LoadLibrary(). The test has no references to functions in
759    * RICHED20.DLL, so the linker doesn't actually link to it. */
760   hmoduleRichEdit = LoadLibrary("RICHED20.DLL");
761   ok(hmoduleRichEdit != NULL, "error: %d\n", (int) GetLastError());
762   test_EM_FINDTEXT();
763   test_EM_SCROLLCARET();
764   test_EM_SCROLL();
765   test_EM_SETTEXTMODE();
766   test_TM_PLAINTEXT();
767   test_EM_SETOPTIONS();
768   test_WM_GETTEXT();
769   test_EM_AUTOURLDETECT();
770
771   /* Set the environment variable WINETEST_RICHED20 to keep windows
772    * responsive and open for 30 seconds. This is useful for debugging.
773    *
774    * The message pump uses PeekMessage() to empty the queue and then sleeps for
775    * 50ms before retrying the queue. */
776   end = time(NULL) + 30;
777   if (getenv( "WINETEST_RICHED20" )) {
778     while (time(NULL) < end) {
779       if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
780         TranslateMessage(&msg);
781         DispatchMessage(&msg);
782       } else {
783         Sleep(50);
784       }
785     }
786   }
787
788   ok(FreeLibrary(hmoduleRichEdit) != 0, "error: %d\n", (int) GetLastError());
789 }