- specialized handler for SF_TEXT (ANSI or Unicode), simple and fast
[wine] / dlls / riched20 / editor.c
1 /*
2  * RichEdit - functions dealing with editor object
3  *
4  * Copyright 2004 by Krzysztof Foltman
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 /* 
22   API implementation status:
23   
24   Messages (ANSI versions not done yet)
25   - EM_AUTOURLDETECT 2.0
26   - EM_CANPASTE
27   + EM_CANREDO 2.0
28   + EM_CANUNDO
29   - EM_CHARFROMPOS
30   - EM_DISPLAYBAND
31   + EM_EMPTYUNDOBUFFER
32   + EM_EXGETSEL
33   - EM_EXLIMITTEXT
34   - EM_EXLINEFROMCHAR
35   + EM_EXSETSEL
36   - EM_FINDTEXT
37   - EM_FINDTEXTEX
38   - EM_FINDWORDBREAK
39   - EM_FMTLINES
40   - EM_FORMATRANGE
41   - EM_GETCHARFORMAT (partly done)
42   + EM_GETEVENTMASK
43   - EM_GETFIRSTVISIBLELINE
44   - EM_GETIMECOLOR 1.0asian
45   - EM_GETIMECOMPMODE 2.0
46   - EM_GETIMEOPTIONS 1.0asian
47   - EM_GETIMESTATUS
48   - EM_GETLANGOPTIONS 2.0
49   - EM_GETLIMITTEXT
50   - EM_GETLINE        
51   - EM_GETLINECOUNT   returns number of rows, not of paragraphs
52   + EM_GETMODIFY
53   - EM_GETOLEINTERFACE
54   - EM_GETOPTIONS
55   + EM_GETPARAFORMAT
56   - EM_GETPUNCTUATION 1.0asian
57   - EM_GETRECT
58   - EM_GETREDONAME 2.0
59   + EM_GETSEL
60   + EM_GETSELTEXT (ANSI&Unicode)
61 ! - EM_GETTHUMB
62   - EM_GETTEXTMODE 2.0
63 ? + EM_GETTEXTRANGE (ANSI&Unicode)
64   - EM_GETUNDONAME
65   - EM_GETWORDBREAKPROC
66   - EM_GETWORDBREAKPROCEX
67   - EM_GETWORDWRAPMODE 1.0asian
68   - EM_HIDESELECTION
69   - EM_LIMITTEXT
70   - EM_LINEFROMCHAR
71   - EM_LINEINDEX
72   - EM_LINELENGTH
73   - EM_LINESCROLL
74   - EM_PASTESPECIAL
75   - EM_POSFROMCHARS
76   - EM_REDO 2.0
77   - EM_REQUESTRESIZE
78   + EM_REPLACESEL (proper style?) ANSI&Unicode
79   - EM_SCROLL
80   - EM_SCROLLCARET
81   - EM_SELECTIONTYPE
82   + EM_SETBKGNDCOLOR
83   - EM_SETCHARFORMAT (partly done, no ANSI)
84   + EM_SETEVENTMASK (few notifications supported)
85   - EM_SETIMECOLOR 1.0asian
86   - EM_SETIMEOPTIONS 1.0asian
87   - EM_SETLANGOPTIONS 2.0
88   - EM_SETLIMITTEXT
89   + EM_SETMODIFY (not sure if implementation is correct)
90   - EM_SETOLECALLBACK
91   - EM_SETOPTIONS
92   + EM_SETPARAFORMAT
93   - EM_SETPUNCTUATION 1.0asian
94   + EM_SETREADONLY no beep on modification attempt
95   - EM_SETRECT
96   - EM_SETRECTNP (EM_SETRECT without repainting) - not supported in RICHEDIT
97   + EM_SETSEL
98   - EM_SETTARGETDEVICE
99   - EM_SETTEXTMODE 2.0
100   - EM_SETUNDOLIMIT 2.0
101   - EM_SETWORDBREAKPROC
102   - EM_SETWORDBREAKPROCEX
103   - EM_SETWORDWRAPMODE 1.0asian
104   - EM_STOPGROUPTYPING 2.0
105   - EM_STREAMIN
106   - EM_STREAMOUT
107   - EM_UNDO
108   + WM_CHAR
109   + WM_CLEAR
110   - WM_COPY (lame implementation, no RTF support)
111   - WM_CUT (lame implementation, no RTF support)
112   + WM_GETDLGCODE (the current implementation is incomplete)
113   + WM_GETTEXT (ANSI&Unicode)
114   + WM_GETTEXTLENGTH (ANSI version sucks)
115   - WM_PASTE
116   - WM_SETFONT
117   + WM_SETTEXT (resets undo stack !) (proper style?) ANSI&Unicode
118   - WM_STYLECHANGING
119   - WM_STYLECHANGED (things like read-only flag)
120   - WM_UNICHAR
121   
122   Notifications
123   
124   * EN_CHANGE (sent from the wrong place)
125   - EN_CORRECTTEXT
126   - EN_DROPFILES
127   - EN_ERRSPACE
128   - EN_HSCROLL
129   - EN_IMECHANGE
130   + EN_KILLFOCUS
131   - EN_LINK
132   - EN_MAXTEXT
133   - EN_MSGFILTER
134   - EN_OLEOPFAILED
135   - EN_PROTECTED
136   - EN_REQUESTRESIZE
137   - EN_SAVECLIPBOARD
138   + EN_SELCHANGE 
139   + EN_SETFOCUS
140   - EN_STOPNOUNDO
141   * EN_UPDATE (sent from the wrong place)
142   - EN_VSCROLL
143   
144   Styles
145   
146   - ES_AUTOHSCROLL
147   - ES_AUTOVSCROLL
148   - ES_CENTER
149   - ES_DISABLENOSCROLL (scrollbar is always visible)
150   - ES_EX_NOCALLOLEINIT
151   - ES_LEFT
152   - ES_MULTILINE (currently single line controls aren't supported)
153   - ES_NOIME
154   - ES_READONLY (I'm not sure if beeping is the proper behaviour)
155   - ES_RIGHT
156   - ES_SAVESEL
157   - ES_SELFIME
158   - ES_SUNKEN
159   - ES_VERTICAL
160   - ES_WANTRETURN (don't know how to do WM_GETDLGCODE part)
161   - WS_SETFONT
162   - WS_HSCROLL
163   - WS_VSCROLL
164 */
165
166 /*
167  * RICHED20 TODO (incomplete):
168  *
169  * - font caching
170  * - add remaining CHARFORMAT/PARAFORMAT fields
171  * - right/center align should strip spaces from the beginning
172  * - more advanced navigation (Ctrl-arrows, PageUp/PageDn)
173  * - tabs
174  * - pictures (not just smiling faces that lack API support ;-) )
175  * - OLE objects
176  * - calculate heights of pictures (half-done)
177  * - EM_STREAMIN/EM_STREAMOUT
178  * - horizontal scrolling (not even started)
179  * - fix scrollbars and refresh (it sucks bigtime)
180  * - hysteresis during wrapping (related to scrollbars appearing/disappearing)
181  * - should remember maximum row width for wrap hysteresis
182  * - find/replace
183  * - how to implement EM_FORMATRANGE and EM_DISPLAYBAND ? (Mission Impossible)
184  * - italic cursor with italic fonts
185  * - IME
186  * - most notifications aren't sent at all (the most important ones are)
187  * - when should EN_SELCHANGE be sent after text change ? (before/after EN_UPDATE?)
188  * - WM_SETTEXT may use wrong style (but I'm 80% sure it's OK)
189  * - EM_GETCHARFORMAT with SCF_SELECTION may not behave 100% like in original (but very close)
190  * - bugs in end-of-text handling (the gray bar) could get me in jail ;-)
191  * - determination of row size
192  * - end-of-paragraph marks should be of reasonable size
193  *
194  * Bugs that are probably fixed, but not so easy to verify:
195  * - EN_UPDATE/EN_CHANGE are handled very incorrectly (should be OK now)
196  * - undo for ME_JoinParagraphs doesn't store paragraph format ? (it does)
197  * - check/fix artificial EOL logic (bCursorAtEnd, hardly logical)
198  * - caret shouldn't be displayed when selection isn't empty
199  * - check refcounting in style management functions (looks perfect now, but no bugs is suspicious)
200  * - undo for setting default format (done, might be buggy)
201  * - styles might be not released properly (looks like they work like charm, but who knows?
202  *
203  */
204
205 #include "editor.h"
206 #include <ole2.h>
207 #include <richole.h>
208 #include <winreg.h>
209 #define NO_SHLWAPI_STREAM 
210 #include <shlwapi.h>
211
212 #include "rtf.h"
213  
214 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
215
216 int me_debug = 0;
217 HANDLE me_heap = NULL;
218
219 void DoWrap(ME_TextEditor *editor) {
220   HDC hDC = GetDC(editor->hWnd);
221   ME_DisplayItem *item;
222   ME_Context c;
223   HWND hWnd = editor->hWnd;
224   int yLength = editor->nTotalLength;
225   int nSelFrom, nSelTo;
226   int nMinSel, nMaxSel;
227   
228   ME_GetSelection(editor, &nSelFrom, &nSelTo);
229   
230   nMinSel = nSelFrom < editor->nOldSelFrom ? nSelFrom : editor->nOldSelFrom;
231   nMaxSel = nSelTo > editor->nOldSelTo ? nSelTo : editor->nOldSelTo;
232   
233   ME_InitContext(&c, editor, hDC);
234   c.pt.x = 0;
235   c.pt.y = 0;
236   item = editor->pBuffer->pFirst->next;
237   while(item != editor->pBuffer->pLast) {
238     int para_from, para_to;
239     BOOL bRedraw = FALSE;
240     
241     para_from = item->member.para.nCharOfs;
242     para_to = item->member.para.next_para->member.para.nCharOfs;
243     
244     if (para_from <= nMaxSel && para_to >= nMinSel && nMinSel != nMaxSel)
245       bRedraw = TRUE;
246     
247     assert(item->type == diParagraph);
248     if (!(item->member.para.nFlags & MEPF_WRAPPED)
249      || (item->member.para.nYPos != c.pt.y))
250       bRedraw = TRUE;
251     item->member.para.nYPos = c.pt.y;
252     
253     ME_WrapTextParagraph(&c, item);
254
255     if (bRedraw) {
256       item->member.para.nFlags |= MEPF_REDRAW;
257     }
258     c.pt.y = item->member.para.nYPos + item->member.para.nHeight;
259     item = item->member.para.next_para;
260   }
261   editor->sizeWindow.cx = c.rcView.right-c.rcView.left;
262   editor->sizeWindow.cy = c.rcView.bottom-c.rcView.top;
263   editor->nTotalLength = c.pt.y-c.rcView.top;
264   
265   ME_UpdateScrollBar(editor, -1);
266   ME_EnsureVisible(editor, editor->pCursors[0].pRun);
267   
268   /* FIXME this should be marked for update too somehow, so that painting happens in ME_PaintContent */
269   if (yLength != c.pt.y-c.rcView.top) {
270     RECT rc;
271     rc.left = c.rcView.left;
272     rc.right = c.rcView.right;
273     rc.top = c.pt.y;
274     rc.bottom = c.rcView.bottom;
275     InvalidateRect(editor->hWnd, &rc, FALSE);
276     UpdateWindow(editor->hWnd);
277   }
278   
279   editor->nOldSelFrom = nSelFrom;
280   editor->nOldSelTo = nSelTo;
281   /* PatBlt(hDC, 0, c.pt.y, c.rcView.right, c.rcView.bottom, BLACKNESS);*/
282
283   ME_DestroyContext(&c);
284   ReleaseDC(hWnd, hDC);
285 }
286
287 ME_TextBuffer *ME_MakeText() {
288   
289   ME_TextBuffer *buf = ALLOC_OBJ(ME_TextBuffer);
290
291   ME_DisplayItem *p1 = ME_MakeDI(diTextStart);
292   ME_DisplayItem *p2 = ME_MakeDI(diTextEnd);
293   
294   p1->prev = NULL;
295   p1->next = p2;
296   p2->prev = p1;
297   p2->next = NULL;
298   p1->member.para.next_para = p2;
299   p2->member.para.prev_para = p1;
300   p2->member.para.nCharOfs = 0;  
301   
302   buf->pFirst = p1;
303   buf->pLast = p2;
304   buf->pCharStyle = NULL;
305   
306   return buf;
307 }
308
309 #define STREAMIN_BUFFER_SIZE 1024
310
311 static LRESULT ME_StreamInText(ME_TextEditor *editor, DWORD dwFormat, EDITSTREAM *stream, ME_Style *style)
312 {
313   BYTE buffer[STREAMIN_BUFFER_SIZE+1];
314   WCHAR wszText[STREAMIN_BUFFER_SIZE+1];
315   
316   TRACE("%08lx %p\n", dwFormat, stream);
317   stream->dwError = 0;
318   
319   do {
320     long nDataSize = 0, nWideChars = 0;
321     stream->dwError = stream->pfnCallback(stream->dwCookie, 
322       (dwFormat & SF_UNICODE ? (BYTE *)wszText : buffer), 
323       STREAMIN_BUFFER_SIZE, &nDataSize);
324     
325     if (stream->dwError)
326       break;
327     if (!nDataSize)
328       break;
329       
330     if (!(dwFormat & SF_UNICODE))
331     {
332       /* FIXME? this is doomed to fail on true MBCS like UTF-8, luckily they're unlikely to be used as CP_ACP */
333       nWideChars = MultiByteToWideChar(CP_ACP, 0, buffer, nDataSize, wszText, STREAMIN_BUFFER_SIZE);
334     }
335     else
336       nWideChars = nDataSize>>1;
337     ME_InsertTextFromCursor(editor, 0, wszText, nWideChars, style);
338     if (nDataSize<STREAMIN_BUFFER_SIZE)
339       break;
340   } while(1);
341   ME_CommitUndo(editor);    
342   return 0;
343 }
344
345 static LRESULT ME_StreamIn(ME_TextEditor *editor, DWORD format, EDITSTREAM *stream)
346 {
347   RTF_Info parser;
348   ME_Style *style;
349
350   FIXME("%08lx %p\n", format, stream);
351   TRACE("%p %p\n", stream, editor->hWnd);
352   
353   if (format & SFF_SELECTION) {
354     style = ME_GetSelectionInsertStyle(editor);
355     SendMessageW(editor->hWnd, WM_CLEAR, 0, 0);
356   }
357   else {
358     style = editor->pBuffer->pDefaultStyle;
359     ME_AddRefStyle(style);
360     SendMessageA(editor->hWnd, EM_SETSEL, 0, 0);    
361     SetWindowTextA(editor->hWnd, "");
362     ME_ClearTempStyle(editor);
363   }
364
365   if (format & SF_RTF) {
366     /* setup the RTF parser */
367     memset(&parser, 0, sizeof parser);
368     RTFSetEditStream(&parser, stream);
369     parser.rtfFormat = format&(SF_TEXT|SF_RTF);
370     parser.hwndEdit = editor->hWnd;
371     WriterInit(&parser);
372     RTFInit(&parser);
373     BeginFile(&parser);
374   
375     /* do the parsing */
376     RTFRead(&parser);
377     RTFFlushOutputBuffer(&parser);
378   }
379   else if (format & SF_TEXT)
380     ME_StreamInText(editor, format, stream, style);
381   else
382     ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
383   /* put the cursor at the top */
384   if (!(format & SFF_SELECTION))
385     SendMessageA(editor->hWnd, EM_SETSEL, 0, 0);
386   else
387   {
388     /* FIXME where to put cursor now ? */
389   }
390   ME_ReleaseStyle(style);
391
392   return 0;
393 }
394
395 ME_TextEditor *ME_MakeEditor(HWND hWnd) {
396   ME_TextEditor *ed = ALLOC_OBJ(ME_TextEditor);
397   HDC hDC;
398   int i;
399   ed->hWnd = hWnd;
400   ed->pBuffer = ME_MakeText();
401   hDC = GetDC(hWnd);
402   ME_MakeFirstParagraph(hDC, ed->pBuffer);
403   ReleaseDC(hWnd, hDC);
404   ed->bCaretShown = FALSE;
405   ed->nCursors = 3;
406   ed->pCursors = ALLOC_N_OBJ(ME_Cursor, ed->nCursors);
407   ed->pCursors[0].pRun = ME_FindItemFwd(ed->pBuffer->pFirst, diRun);
408   ed->pCursors[0].nOffset = 0;
409   ed->pCursors[1].pRun = ME_FindItemFwd(ed->pBuffer->pFirst, diRun);
410   ed->pCursors[1].nOffset = 0;
411   ed->nTotalLength = 0;
412   ed->nScrollPos = 0;
413   ed->nUDArrowX = -1;
414   ed->nSequence = 0;
415   ed->rgbBackColor = -1;
416   ed->bCaretAtEnd = FALSE;
417   ed->nEventMask = 0;
418   ed->nModifyStep = 0;
419   ed->pUndoStack = ed->pRedoStack = NULL;
420   ed->nUndoMode = umAddToUndo;
421   ed->nParagraphs = 1;
422   for (i=0; i<HFONT_CACHE_SIZE; i++)
423   {
424     ed->pFontCache[i].nRefs = 0;
425     ed->pFontCache[i].nAge = 0;
426     ed->pFontCache[i].hFont = NULL;
427   }
428   ME_CheckCharOffsets(ed);
429   return ed;
430 }
431
432 void ME_DestroyEditor(ME_TextEditor *editor)
433 {
434   ME_DisplayItem *pFirst = editor->pBuffer->pFirst;
435   ME_DisplayItem *p = pFirst, *pNext = NULL;
436   int i;
437   
438   ME_ClearTempStyle(editor);
439   ME_EmptyUndoStack(editor);
440   while(p) {
441     pNext = p->next;
442     ME_DestroyDisplayItem(p);    
443     p = pNext;
444   }
445   ME_ReleaseStyle(editor->pBuffer->pDefaultStyle);
446   for (i=0; i<HFONT_CACHE_SIZE; i++)
447   {
448     if (editor->pFontCache[i].hFont)
449       DeleteObject(editor->pFontCache[i].hFont);
450   }
451     
452   FREE_OBJ(editor);
453 }
454
455 #define UNSUPPORTED_MSG(e) \
456   case e: \
457     FIXME(#e ": stub\n"); \
458     return DefWindowProcW(hWnd, msg, wParam, lParam);
459
460 /******************************************************************
461  *        RichEditANSIWndProc (RICHED20.10)
462  */
463 LRESULT WINAPI RichEditANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
464   HDC hDC;
465   PAINTSTRUCT ps;
466   SCROLLINFO si;
467   ME_TextEditor *editor = (ME_TextEditor *)GetWindowLongW(hWnd, 0);
468   switch(msg) {
469   
470   UNSUPPORTED_MSG(EM_AUTOURLDETECT)
471   UNSUPPORTED_MSG(EM_CANPASTE)
472   UNSUPPORTED_MSG(EM_CHARFROMPOS)
473   UNSUPPORTED_MSG(EM_DISPLAYBAND)
474   UNSUPPORTED_MSG(EM_EXLIMITTEXT)
475   UNSUPPORTED_MSG(EM_EXLINEFROMCHAR)
476   UNSUPPORTED_MSG(EM_FINDTEXT)
477   UNSUPPORTED_MSG(EM_FINDTEXTEX)
478   UNSUPPORTED_MSG(EM_FINDWORDBREAK)
479   UNSUPPORTED_MSG(EM_FMTLINES)
480   UNSUPPORTED_MSG(EM_FORMATRANGE)
481   UNSUPPORTED_MSG(EM_GETFIRSTVISIBLELINE)
482   UNSUPPORTED_MSG(EM_GETIMECOMPMODE)
483   /* UNSUPPORTED_MSG(EM_GETIMESTATUS) missing in Wine headers */
484   UNSUPPORTED_MSG(EM_GETLANGOPTIONS)
485   UNSUPPORTED_MSG(EM_GETLIMITTEXT)
486   UNSUPPORTED_MSG(EM_GETLINE)
487   UNSUPPORTED_MSG(EM_GETLINECOUNT)
488   /* UNSUPPORTED_MSG(EM_GETOLEINTERFACE) separate stub */
489   UNSUPPORTED_MSG(EM_GETOPTIONS)
490   UNSUPPORTED_MSG(EM_GETRECT)
491   UNSUPPORTED_MSG(EM_GETREDONAME)
492   UNSUPPORTED_MSG(EM_GETTEXTMODE)
493   UNSUPPORTED_MSG(EM_GETUNDONAME)
494   UNSUPPORTED_MSG(EM_GETWORDBREAKPROC)
495   UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX)
496   UNSUPPORTED_MSG(EM_HIDESELECTION)
497   UNSUPPORTED_MSG(EM_LIMITTEXT) /* also known as EM_SETLIMITTEXT */
498   UNSUPPORTED_MSG(EM_LINEFROMCHAR)
499   UNSUPPORTED_MSG(EM_LINEINDEX)
500   UNSUPPORTED_MSG(EM_LINELENGTH)
501   UNSUPPORTED_MSG(EM_LINESCROLL)
502   UNSUPPORTED_MSG(EM_PASTESPECIAL)
503 /*  UNSUPPORTED_MSG(EM_POSFROMCHARS) missing in Wine headers */
504   UNSUPPORTED_MSG(EM_REQUESTRESIZE)
505   UNSUPPORTED_MSG(EM_SCROLL)
506   UNSUPPORTED_MSG(EM_SCROLLCARET)
507   UNSUPPORTED_MSG(EM_SELECTIONTYPE)
508   UNSUPPORTED_MSG(EM_SETLANGOPTIONS)
509   UNSUPPORTED_MSG(EM_SETOLECALLBACK)
510   UNSUPPORTED_MSG(EM_SETOPTIONS)
511   UNSUPPORTED_MSG(EM_SETRECT)
512   UNSUPPORTED_MSG(EM_SETRECTNP)
513   UNSUPPORTED_MSG(EM_SETTARGETDEVICE)
514   UNSUPPORTED_MSG(EM_SETTEXTMODE)
515   UNSUPPORTED_MSG(EM_SETUNDOLIMIT)
516   UNSUPPORTED_MSG(EM_SETWORDBREAKPROC)
517   UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX)
518   UNSUPPORTED_MSG(EM_STREAMOUT)
519   UNSUPPORTED_MSG(WM_SETFONT)
520   UNSUPPORTED_MSG(WM_PASTE)
521   UNSUPPORTED_MSG(WM_STYLECHANGING)
522   UNSUPPORTED_MSG(WM_STYLECHANGED)
523 /*  UNSUPPORTED_MSG(WM_UNICHAR) FIXME missing in Wine headers */
524     
525 /* Messages specific to Richedit controls */
526   
527   case EM_STREAMIN:
528    return ME_StreamIn(editor, wParam, (EDITSTREAM*)lParam);
529   case WM_GETDLGCODE:
530   {
531     UINT code = DLGC_WANTCHARS|DLGC_WANTARROWS;
532     if (GetWindowLongW(hWnd, GWL_STYLE)&ES_WANTRETURN)
533       code |= 0; /* FIXME what can we do here ? ask for messages and censor them ? */
534     return code;
535   }
536   case WM_NCCREATE:
537   {
538     CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
539     editor = ME_MakeEditor(hWnd);
540     SetWindowLongW(hWnd, 0, (long)editor);
541     pcs = 0; /* ignore */
542     return TRUE;
543   }
544   case EM_EMPTYUNDOBUFFER:
545     ME_EmptyUndoStack(editor);
546     return 0;
547   case EM_GETSEL:
548   {
549     ME_GetSelection(editor, (int *)wParam, (int *)lParam);
550     if (!((wParam|lParam) & 0xFFFF0000))
551       return (lParam<<16)|wParam;
552     return -1;
553   }
554   case EM_EXGETSEL:
555   {
556     CHARRANGE *pRange = (CHARRANGE *)lParam;
557     ME_GetSelection(editor, (int *)&pRange->cpMin, (int *)&pRange->cpMax);
558     return 0;
559   }
560   case EM_CANUNDO:
561     return editor->pUndoStack != NULL;
562   case EM_CANREDO:
563     return editor->pRedoStack != NULL;
564   case EM_UNDO:
565     ME_Undo(editor);
566     return 0;
567   case EM_REDO:
568     ME_Redo(editor);
569     return 0;
570   case EM_SETSEL:
571   {
572     ME_SetSelection(editor, wParam, lParam);
573     ME_Repaint(editor);
574     ME_SendSelChange(editor);
575     return 0;
576   }
577   case EM_EXSETSEL:
578   {
579     CHARRANGE *pRange = (CHARRANGE *)lParam;
580     ME_SetSelection(editor, pRange->cpMin, pRange->cpMax);
581     /* FIXME optimize */
582     ME_Repaint(editor);
583     ME_SendSelChange(editor);
584     return 0;
585   }
586   case EM_SETBKGNDCOLOR:
587   {
588     LRESULT lColor = ME_GetBackColor(editor);
589     if (wParam)
590       editor->rgbBackColor = -1;
591     else
592       editor->rgbBackColor = lParam; 
593     InvalidateRect(hWnd, NULL, TRUE);
594     UpdateWindow(hWnd);
595     return lColor;
596   }
597   case EM_GETMODIFY:
598     return editor->nModifyStep == 0 ? 0 : 1;
599   case EM_SETMODIFY:
600   {
601     if (wParam)
602       editor->nModifyStep = 0x80000000;
603     else
604       editor->nModifyStep = 0;
605     
606     return 0;
607   }
608   case EM_SETREADONLY:
609   {
610     long nStyle = GetWindowLongW(hWnd, GWL_STYLE);
611     if (wParam)
612       nStyle |= ES_READONLY;
613     else
614       nStyle &= ~ES_READONLY;
615     SetWindowLongW(hWnd, GWL_STYLE, nStyle);
616     ME_Repaint(editor);
617     return 0;
618   }
619   case EM_SETEVENTMASK:
620     editor->nEventMask = lParam;
621     return 0;
622   case EM_GETEVENTMASK:
623     return editor->nEventMask;
624   case EM_SETCHARFORMAT:
625   {
626     CHARFORMAT2W buf, *p;
627     p = ME_ToCF2W(&buf, (CHARFORMAT2W *)lParam);
628     if (!wParam)
629       ME_SetDefaultCharFormat(editor, p);
630     else if (wParam == (SCF_WORD | SCF_SELECTION))
631       FIXME("word selection not supported\n");
632     else if (wParam == SCF_ALL)
633       ME_SetCharFormat(editor, 0, ME_GetTextLength(editor), p);
634     else
635       ME_SetSelectionCharFormat(editor, p);
636     ME_CommitUndo(editor);
637     ME_UpdateRepaint(editor);
638     return 0;
639   }
640   case EM_GETCHARFORMAT:
641   {
642     CHARFORMAT2W tmp;
643     tmp.cbSize = sizeof(tmp);
644     if (!wParam)
645       ME_GetDefaultCharFormat(editor, &tmp);
646     else
647       ME_GetSelectionCharFormat(editor, &tmp);
648     ME_CopyToCFAny((CHARFORMAT2W *)lParam, &tmp);
649     return 0;
650   }
651   case EM_SETPARAFORMAT:
652     ME_SetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
653     ME_CommitUndo(editor);
654     return 0;
655   case EM_GETPARAFORMAT:
656     ME_GetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
657     return 0;
658   case WM_CLEAR:
659   {
660     int from, to;
661     ME_GetSelection(editor, &from, &to);
662     ME_InternalDeleteText(editor, from, to-from);
663     ME_CommitUndo(editor);
664     ME_UpdateRepaint(editor);
665     return 0;
666   }
667   case EM_REPLACESEL:
668   {
669     int from, to;
670     ME_Style *style;
671     LPWSTR wszText = ME_ToUnicode(hWnd, (void *)lParam);
672     size_t len = lstrlenW(wszText);
673     TRACE("EM_REPLACESEL - %s\n", debugstr_w(wszText));
674     
675     ME_GetSelection(editor, &from, &to);
676     style = ME_GetSelectionInsertStyle(editor);
677     ME_InternalDeleteText(editor, from, to-from);
678     ME_InsertTextFromCursor(editor, 0, wszText, len, style);
679     ME_ReleaseStyle(style);
680     ME_EndToUnicode(hWnd, wszText);
681     /* drop temporary style if line end */
682     /* FIXME question: does abc\n mean: put abc, clear temp style, put \n? (would require a change) */  
683     if (len>0 && wszText[len-1] == '\n')
684       ME_ClearTempStyle(editor);
685       
686     ME_CommitUndo(editor);
687     if (!wParam)
688       ME_EmptyUndoStack(editor);
689     ME_UpdateRepaint(editor);
690     return 0;
691   }
692   case WM_SETTEXT:
693   {
694     LPWSTR wszText = ME_ToUnicode(hWnd, (void *)lParam);
695     TRACE("WM_SETTEXT - %s\n", (char *)(wszText)); /* debugstr_w() */
696     ME_InternalDeleteText(editor, 0, ME_GetTextLength(editor));
697     /* uses default style! */
698     ME_InsertTextFromCursor(editor, 0, wszText, -1, editor->pBuffer->pDefaultStyle);
699     ME_EndToUnicode(hWnd, wszText);
700     ME_CommitUndo(editor);
701     ME_EmptyUndoStack(editor);
702     ME_UpdateRepaint(editor);
703     return 0;
704   }
705   case WM_CUT:
706   case WM_COPY:
707   {
708     int from, to, pars;
709     WCHAR *data;
710     HANDLE hData;
711     
712     if (!OpenClipboard(hWnd))
713       return 0;
714       
715     EmptyClipboard();
716     ME_GetSelection(editor, &from, &to);
717     pars = ME_CountParagraphsBetween(editor, from, to);
718     hData = GlobalAlloc(GMEM_MOVEABLE, sizeof(WCHAR)*(to-from+pars+1));
719     data = (WCHAR *)GlobalLock(hData);
720     ME_GetTextW(editor, data, from, to-from, TRUE);
721     GlobalUnlock(hData);
722     SetClipboardData(CF_UNICODETEXT, hData);
723     CloseClipboard();
724     if (msg == WM_CUT)
725     {
726       ME_InternalDeleteText(editor, from, to-from);
727       ME_CommitUndo(editor);
728       ME_UpdateRepaint(editor);
729     }
730     return 0;
731   }
732   case WM_GETTEXTLENGTH:
733     return ME_GetTextLength(editor);
734   case WM_GETTEXT:
735   {
736     TEXTRANGEW tr; /* W and A differ only by rng->lpstrText */
737     tr.chrg.cpMin = 0;
738     tr.chrg.cpMax = wParam-1;
739     tr.lpstrText = (WCHAR *)lParam;
740     return RichEditANSIWndProc(hWnd, EM_GETTEXTRANGE, 0, (LPARAM)&tr);
741   }
742   case EM_GETSELTEXT:
743   {
744     int from, to;
745     TEXTRANGEW tr; /* W and A differ only by rng->lpstrText */
746     ME_GetSelection(editor, &from, &to);
747     tr.chrg.cpMin = from;
748     tr.chrg.cpMax = to;
749     tr.lpstrText = (WCHAR *)lParam;
750     return RichEditANSIWndProc(hWnd, EM_GETTEXTRANGE, 0, (LPARAM)&tr);
751   }
752   case EM_GETTEXTRANGE:
753   {
754     TEXTRANGEW *rng = (TEXTRANGEW *)lParam;
755     if (IsWindowUnicode(hWnd))
756       return ME_GetTextW(editor, rng->lpstrText, rng->chrg.cpMin, rng->chrg.cpMax-rng->chrg.cpMin, FALSE);
757     else
758     {
759       int nLen = rng->chrg.cpMax-rng->chrg.cpMin;
760       WCHAR *p = ALLOC_N_OBJ(WCHAR, nLen+1);
761       int nChars = ME_GetTextW(editor, p, rng->chrg.cpMin, nLen, FALSE);
762       /* FIXME this is a potential security hole (buffer overrun) 
763          if you know more about wchar->mbyte conversion please explain
764       */
765       WideCharToMultiByte(CP_ACP, 0, p, nChars+1, (char *)rng->lpstrText, nLen+1, NULL, NULL);
766       FREE_OBJ(p);
767       return nChars;
768     }
769     return ME_GetTextW(editor, rng->lpstrText, rng->chrg.cpMin, rng->chrg.cpMax-rng->chrg.cpMin, FALSE);
770   }
771   case WM_CREATE:
772     ME_CommitUndo(editor);
773 /*    ME_InsertTextFromCursor(editor, 0, (WCHAR *)L"x", 1, editor->pBuffer->pDefaultStyle); */
774     DoWrap(editor);
775     ME_MoveCaret(editor);
776     return 0;
777   case WM_DESTROY:
778     ME_DestroyEditor(editor);
779     SetWindowLongW(hWnd, 0, 0);
780     PostQuitMessage(0);
781     break;
782   case WM_LBUTTONDOWN:
783     SetFocus(hWnd);
784     ME_LButtonDown(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
785     SetCapture(hWnd);
786     break;
787   case WM_MOUSEMOVE:
788     if (GetCapture() == hWnd)
789       ME_MouseMove(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
790     break;
791   case WM_LBUTTONUP:
792     if (GetCapture() == hWnd)
793       ReleaseCapture();
794     break;
795   case WM_PAINT:
796     hDC = BeginPaint(hWnd, &ps);
797     ME_PaintContent(editor, hDC, FALSE);
798     EndPaint(hWnd, &ps);
799     break;
800   case WM_SETFOCUS:
801     ME_ShowCaret(editor);
802     ME_SendOldNotify(editor, EN_SETFOCUS);
803     return 0;
804   case WM_KILLFOCUS:
805     ME_HideCaret(editor);
806     ME_SendOldNotify(editor, EN_KILLFOCUS);
807     return 0;
808   case WM_ERASEBKGND:
809   {
810     HDC hDC = (HDC)wParam;
811     RECT rc;
812     COLORREF rgbBG = ME_GetBackColor(editor);
813     if (GetUpdateRect(hWnd,&rc,TRUE))
814     {
815       HBRUSH hbr = CreateSolidBrush(rgbBG);
816       FillRect(hDC, &rc, hbr);
817       DeleteObject(hbr);
818     }
819     return 1;
820   }
821   case WM_COMMAND:
822     TRACE("editor wnd command = %d\n", LOWORD(wParam));
823     return 0;
824   case WM_KEYDOWN:
825     if (ME_ArrowKey(editor, LOWORD(wParam), GetKeyState(VK_CONTROL)<0)) {
826       ME_CommitUndo(editor);
827       ME_EnsureVisible(editor, editor->pCursors[0].pRun);
828       HideCaret(hWnd);
829       ME_MoveCaret(editor);
830       ShowCaret(hWnd);
831       return 0;
832     }
833     if (GetKeyState(VK_CONTROL)<0)
834     {
835       if (LOWORD(wParam)=='W')
836       {
837         CHARFORMAT2W chf;
838         char buf[2048];
839         ME_GetSelectionCharFormat(editor, &chf);
840         ME_DumpStyleToBuf(&chf, buf);
841         MessageBoxA(NULL, buf, "Style dump", MB_OK);
842       }
843       if (LOWORD(wParam)=='Q')
844       {
845         ME_CheckCharOffsets(editor);
846       }
847     }
848     goto do_default;
849   case WM_CHAR: 
850   {
851     WCHAR wstr;
852     if (GetWindowLongW(editor->hWnd, GWL_STYLE) & ES_READONLY) {
853       MessageBeep(MB_ICONERROR);
854       return 0; /* FIXME really 0 ? */
855     }
856     wstr = LOWORD(wParam);
857     if (((unsigned)wstr)>=' ' || wstr=='\r') {
858       /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
859       ME_Style *style = ME_GetInsertStyle(editor, 0);
860       ME_SaveTempStyle(editor);
861       ME_InsertTextFromCursor(editor, 0, &wstr, 1, style);
862       ME_ReleaseStyle(style);
863       ME_CommitUndo(editor);
864       ME_UpdateRepaint(editor);
865     }
866     return 0;
867   }
868   case WM_VSCROLL: 
869   {
870     si.cbSize = sizeof(SCROLLINFO);
871     si.fMask = SIF_PAGE|SIF_POS|SIF_RANGE|SIF_TRACKPOS;
872     GetScrollInfo(hWnd, SB_VERT, &si);
873     switch(LOWORD(wParam)) {
874     case SB_THUMBTRACK:
875       SetScrollPos(hWnd, SB_VERT, si.nTrackPos, FALSE);
876       ScrollWindow(hWnd, 0, si.nPos-si.nTrackPos, NULL, NULL);
877       /* InvalidateRect(hWnd, NULL, TRUE); */
878       UpdateWindow(hWnd);
879       break;
880     }
881     break;
882   }
883   case WM_SIZE:
884   {
885     ME_DisplayItem *tp = editor->pBuffer->pFirst;
886     while(tp)
887     {
888       if (tp->type == diParagraph)
889       {
890         tp->member.para.nFlags &= ~MEPF_WRAPPED;
891         tp = tp->member.para.next_para;
892       }
893       else
894         tp = tp->next;
895     }
896     ME_Repaint(editor);
897     return DefWindowProcW(hWnd, msg, wParam, lParam);
898   }
899   case EM_GETOLEINTERFACE:
900   {
901     LPVOID *ppvObj = (LPVOID*) lParam;
902     TRACE("EM_GETOLEINTERFACE %p\n", ppvObj);
903     return CreateIRichEditOle(ppvObj);
904   }
905   default:
906   do_default:
907     return DefWindowProcW(hWnd, msg, wParam, lParam);
908   }
909   return 0L;
910 }
911
912 /******************************************************************
913  *        RichEdit10ANSIWndProc (RICHED20.9)
914  */
915 LRESULT WINAPI RichEdit10ANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
916 {
917   /* FIXME: this is NOT the same as 2.0 version */
918   return RichEditANSIWndProc(hWnd, msg, wParam, lParam);
919 }
920
921 void ME_SendOldNotify(ME_TextEditor *editor, int nCode)
922 {
923   HWND hWnd = editor->hWnd;
924   SendMessageA(GetParent(hWnd), WM_COMMAND, (nCode<<16)|GetWindowLongW(hWnd, GWLP_ID), (LPARAM)hWnd);
925 }
926
927 int ME_CountParagraphsBetween(ME_TextEditor *editor, int from, int to)
928 {
929   ME_DisplayItem *item = ME_FindItemFwd(editor->pBuffer->pFirst, diParagraph);
930   int i = 0;
931   
932   while(item && item->member.para.next_para->member.para.nCharOfs <= from)
933     item = item->member.para.next_para;
934   if (!item)
935     return 0;
936   while(item && item->member.para.next_para->member.para.nCharOfs <= to) {
937     item = item->member.para.next_para;
938     i++;
939   }
940   return i;
941 }
942
943 int ME_GetTextW(ME_TextEditor *editor, WCHAR *buffer, int nStart, int nChars, int bCRLF)
944 {
945   ME_DisplayItem *item = ME_FindItemFwd(editor->pBuffer->pFirst, diParagraph);
946   int nWritten = 0;
947   
948   while(item && item->member.para.next_para->member.para.nCharOfs <= nStart)
949     item = ME_FindItemFwd(item, diParagraph);
950   if (!item) {
951     *buffer = L'\0';
952     return 0;
953   }
954   nStart -= item->member.para.nCharOfs;
955
956   do {  
957     item = ME_FindItemFwd(item, diRun);
958   } while(item && (item->member.run.nCharOfs + ME_StrLen(item->member.run.strText) <= nStart));
959   assert(item);    
960   
961   nStart -= item->member.run.nCharOfs;
962   
963   if (nStart)
964   {
965     int nLen = ME_StrLen(item->member.run.strText) - nStart;
966     if (nLen > nChars)
967       nLen = nChars;
968     CopyMemory(buffer, item->member.run.strText->szData + nStart, sizeof(WCHAR)*nLen);
969     nChars -= nLen;
970     nWritten += nLen;
971     if (!nChars)
972       return nWritten;
973     buffer += nLen;
974     nStart = 0;
975     item = ME_FindItemFwd(item, diRun);
976   }
977   
978   while(nChars && item)
979   {
980     int nLen = ME_StrLen(item->member.run.strText);
981     if (nLen > nChars)
982       nLen = nChars;
983       
984     if (item->member.run.nFlags & MERF_ENDPARA)
985     {
986       if (bCRLF) {
987         *buffer++ = '\r';
988         nWritten++;
989       }        
990       *buffer = '\n';
991       assert(nLen == 1);
992     }
993     else      
994       CopyMemory(buffer, item->member.run.strText->szData, sizeof(WCHAR)*nLen);
995     nChars -= nLen;
996     nWritten += nLen;
997     buffer += nLen;    
998       
999     if (!nChars)
1000     {
1001       *buffer = L'\0';
1002       return nWritten;
1003     }
1004     item = ME_FindItemFwd(item, diRun);
1005   }
1006   *buffer = L'\0';
1007   return nWritten;  
1008 }
1009
1010 static WCHAR wszClassName[] = {'R', 'i', 'c', 'h', 'E', 'd', 'i', 't', '2', '0', 'W', 0};
1011 static WCHAR wszClassName50[] = {'R', 'i', 'c', 'h', 'E', 'd', 'i', 't', '5', '0', 'W', 0};
1012
1013 void ME_RegisterEditorClass(HINSTANCE hInstance)
1014 {
1015   BOOL bResult;
1016   WNDCLASSW wcW;
1017   WNDCLASSA wcA;
1018   
1019   wcW.style = CS_HREDRAW | CS_VREDRAW;
1020   wcW.lpfnWndProc = RichEditANSIWndProc;
1021   wcW.cbClsExtra = 0;
1022   wcW.cbWndExtra = 4;
1023   wcW.hInstance = NULL; /* hInstance would register DLL-local class */
1024   wcW.hIcon = NULL;
1025   wcW.hCursor = LoadCursorW(NULL, MAKEINTRESOURCEW(IDC_IBEAM));
1026   wcW.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);
1027   wcW.lpszMenuName = NULL;
1028   wcW.lpszClassName = wszClassName;
1029   bResult = RegisterClassW(&wcW);  
1030   assert(bResult);
1031   wcW.lpszClassName = wszClassName50;
1032   bResult = RegisterClassW(&wcW);  
1033   assert(bResult);
1034
1035   wcA.style = CS_HREDRAW | CS_VREDRAW;
1036   wcA.lpfnWndProc = RichEditANSIWndProc;
1037   wcA.cbClsExtra = 0;
1038   wcA.cbWndExtra = 4;
1039   wcA.hInstance = NULL; /* hInstance would register DLL-local class */
1040   wcA.hIcon = NULL;
1041   wcA.hCursor = LoadCursorW(NULL, MAKEINTRESOURCEW(IDC_IBEAM));
1042   wcA.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);
1043   wcA.lpszMenuName = NULL;
1044   wcA.lpszClassName = "RichEdit20A";
1045   bResult = RegisterClassA(&wcA);  
1046   assert(bResult);
1047   wcA.lpszClassName = "RichEdit50A";
1048   bResult = RegisterClassA(&wcA);  
1049   assert(bResult);
1050 }
1051
1052 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
1053 {
1054     TRACE("\n");
1055     switch (fdwReason)
1056     {
1057     case DLL_PROCESS_ATTACH:
1058       DisableThreadLibraryCalls(hinstDLL);
1059       me_heap = HeapCreate (0, 0x10000, 0);
1060       ME_RegisterEditorClass(hinstDLL);
1061       break;
1062
1063     case DLL_PROCESS_DETACH:
1064       UnregisterClassW(wszClassName, 0);
1065       UnregisterClassW(wszClassName50, 0);
1066       UnregisterClassA("RichEdit20A", 0);
1067       UnregisterClassA("RichEdit50A", 0);
1068       HeapDestroy (me_heap);
1069       me_heap = NULL;
1070       break;
1071     }
1072     return TRUE;
1073 }
1074
1075 /******************************************************************
1076  *        CreateTextServices (RICHED20.4)
1077  *
1078  * FIXME should be ITextHost instead of void*
1079  */
1080 HRESULT WINAPI CreateTextServices(IUnknown *punkOuter, void *pITextHost,
1081     IUnknown **ppUnk)
1082 {
1083   FIXME("stub\n");
1084   /* FIXME should support aggregation */
1085   if (punkOuter)
1086     return CLASS_E_NOAGGREGATION;
1087     
1088   return E_FAIL; /* E_NOTIMPL isn't allowed by MSDN */
1089 }
1090
1091 /******************************************************************
1092  *        REExtendedRegisterClass (RICHED20.8)
1093  *
1094  * FIXME undocumented
1095  */
1096 void WINAPI REExtendedRegisterClass(void)
1097 {
1098   FIXME("stub\n");
1099 }