- EM_STREAMIN can now deal with undo in a reasonable manner (no
[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 ME_TextBuffer *ME_MakeText() {
220   
221   ME_TextBuffer *buf = ALLOC_OBJ(ME_TextBuffer);
222
223   ME_DisplayItem *p1 = ME_MakeDI(diTextStart);
224   ME_DisplayItem *p2 = ME_MakeDI(diTextEnd);
225   
226   p1->prev = NULL;
227   p1->next = p2;
228   p2->prev = p1;
229   p2->next = NULL;
230   p1->member.para.next_para = p2;
231   p2->member.para.prev_para = p1;
232   p2->member.para.nCharOfs = 0;  
233   
234   buf->pFirst = p1;
235   buf->pLast = p2;
236   buf->pCharStyle = NULL;
237   
238   return buf;
239 }
240
241 #define STREAMIN_BUFFER_SIZE 4096 /* M$ compatibility */
242
243 static LRESULT ME_StreamInText(ME_TextEditor *editor, DWORD dwFormat, EDITSTREAM *stream, ME_Style *style)
244 {
245   BYTE buffer[STREAMIN_BUFFER_SIZE+1];
246   WCHAR wszText[STREAMIN_BUFFER_SIZE+1];
247   
248   TRACE("%08lx %p\n", dwFormat, stream);
249   stream->dwError = 0;
250   
251   do {
252     long nDataSize = 0, nWideChars = 0;
253     stream->dwError = stream->pfnCallback(stream->dwCookie, 
254       (dwFormat & SF_UNICODE ? (BYTE *)wszText : buffer), 
255       STREAMIN_BUFFER_SIZE, &nDataSize);
256     
257     if (stream->dwError)
258       break;
259     if (!nDataSize)
260       break;
261       
262     if (!(dwFormat & SF_UNICODE))
263     {
264       /* FIXME? this is doomed to fail on true MBCS like UTF-8, luckily they're unlikely to be used as CP_ACP */
265       nWideChars = MultiByteToWideChar(CP_ACP, 0, buffer, nDataSize, wszText, STREAMIN_BUFFER_SIZE);
266     }
267     else
268       nWideChars = nDataSize>>1;
269     ME_InsertTextFromCursor(editor, 0, wszText, nWideChars, style);
270     if (nDataSize<STREAMIN_BUFFER_SIZE)
271       break;
272   } while(1);
273   ME_CommitUndo(editor);    
274   return 0;
275 }
276
277 void ME_RTFCharAttrHook(RTF_Info *info)
278 {
279   CHARFORMAT2A fmt;
280   fmt.cbSize = sizeof(fmt);
281   fmt.dwMask = 0;
282   
283   switch(info->rtfMinor)
284   {
285     case rtfBold:
286       fmt.dwMask = CFM_BOLD;
287       fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
288       break;
289     case rtfItalic:
290       fmt.dwMask = CFM_ITALIC;
291       fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
292       break;
293     case rtfUnderline:
294       fmt.dwMask = CFM_UNDERLINE;
295       fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
296       break;
297     case rtfStrikeThru:
298       fmt.dwMask = CFM_STRIKEOUT;
299       fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
300       break;
301     case rtfBackColor:
302       fmt.dwMask = CFM_BACKCOLOR;
303       fmt.dwEffects = 0;
304       if (info->rtfParam == 0)
305         fmt.dwEffects = CFE_AUTOBACKCOLOR;
306       else if (info->rtfParam != rtfNoParam)
307       {
308         RTFColor *c = RTFGetColor(info, info->rtfParam);
309         fmt.crTextColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
310       }
311       break;
312     case rtfForeColor:
313       fmt.dwMask = CFM_COLOR;
314       fmt.dwEffects = 0;
315       if (info->rtfParam == 0)
316         fmt.dwEffects = CFE_AUTOCOLOR;
317       else if (info->rtfParam != rtfNoParam)
318       {
319         RTFColor *c = RTFGetColor(info, info->rtfParam);
320         fmt.crTextColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
321       }
322       break;
323     case rtfFontNum:
324       if (info->rtfParam != rtfNoParam)
325       {
326         RTFFont *f = RTFGetFont(info, info->rtfParam);
327         if (f)
328         {
329           strncpy(fmt.szFaceName, f->rtfFName, sizeof(fmt.szFaceName)-1);
330           fmt.szFaceName[sizeof(fmt.szFaceName)-1] = '\0';
331           fmt.dwMask = CFM_FACE;
332         }
333       }
334       break;
335     case rtfFontSize:
336       fmt.dwMask = CFM_SIZE;
337       if (info->rtfParam != rtfNoParam)
338         fmt.yHeight = info->rtfParam*10;
339       break;
340   }
341   if (fmt.dwMask) {
342     RTFFlushOutputBuffer(info);
343     SendMessageW(info->hwndEdit, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&fmt);
344   }
345 }
346
347 void ME_RTFParAttrHook(RTF_Info *info)
348 {
349   PARAFORMAT2 fmt;
350   fmt.cbSize = sizeof(fmt);
351   fmt.dwMask = 0;
352   
353   switch(info->rtfMinor)
354   {
355   case rtfParDef: /* I'm not 100% sure what does it do, but I guess it restores default paragraph attributes */
356     fmt.dwMask = PFM_ALIGNMENT;
357     fmt.wAlignment = PFA_LEFT;
358     break;
359   case rtfQuadLeft:
360   case rtfQuadJust:
361     fmt.dwMask = PFM_ALIGNMENT;
362     fmt.wAlignment = PFA_LEFT;
363     break;
364   case rtfQuadRight:
365     fmt.dwMask = PFM_ALIGNMENT;
366     fmt.wAlignment = PFA_RIGHT;
367     break;
368   case rtfQuadCenter:
369     fmt.dwMask = PFM_ALIGNMENT;
370     fmt.wAlignment = PFA_CENTER;
371     break;
372   }  
373   if (fmt.dwMask) {
374     RTFFlushOutputBuffer(info);
375     SendMessageW(info->hwndEdit, EM_SETPARAFORMAT, 0, (LPARAM)&fmt);
376   }
377 }
378
379 void ME_RTFReadHook(RTF_Info *info) {
380   switch(info->rtfClass)
381   {
382     case rtfControl:
383       switch(info->rtfMajor)
384       {
385         case rtfCharAttr:
386           ME_RTFCharAttrHook(info);
387           break;
388         case rtfParAttr:
389           ME_RTFParAttrHook(info);
390           break;
391       }
392       break;
393   }
394 }
395
396 static LRESULT ME_StreamIn(ME_TextEditor *editor, DWORD format, EDITSTREAM *stream)
397 {
398   RTF_Info parser;
399   ME_Style *style;
400   int from, to, to2, nUndoMode;
401   ME_UndoItem *pUI;
402
403   TRACE("%p %p\n", stream, editor->hWnd);
404   
405   ME_GetSelection(editor, &from, &to);
406   if (format & SFF_SELECTION) {
407     style = ME_GetSelectionInsertStyle(editor);
408
409     ME_InternalDeleteText(editor, from, to-from);
410   }
411   else {
412     style = editor->pBuffer->pDefaultStyle;
413     ME_AddRefStyle(style);
414     SendMessageA(editor->hWnd, EM_SETSEL, 0, 0);    
415     ME_InternalDeleteText(editor, 0, ME_GetTextLength(editor));
416     from = to = 0;
417     ME_ClearTempStyle(editor);
418   }
419   
420   nUndoMode = editor->nUndoMode;
421   editor->nUndoMode = umIgnore;
422   if (format & SF_RTF) {
423     /* setup the RTF parser */
424     memset(&parser, 0, sizeof parser);
425     RTFSetEditStream(&parser, stream);
426     parser.rtfFormat = format&(SF_TEXT|SF_RTF);
427     parser.hwndEdit = editor->hWnd;
428     WriterInit(&parser);
429     RTFInit(&parser);
430     RTFSetReadHook(&parser, ME_RTFReadHook);
431     BeginFile(&parser);
432   
433     /* do the parsing */
434     RTFRead(&parser);
435     RTFFlushOutputBuffer(&parser);
436   }
437   else if (format & SF_TEXT)
438     ME_StreamInText(editor, format, stream, style);
439   else
440     ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
441   ME_GetSelection(editor, &to, &to2);
442   /* put the cursor at the top */
443   if (!(format & SFF_SELECTION))
444     SendMessageA(editor->hWnd, EM_SETSEL, 0, 0);
445   else
446   {
447     /* FIXME where to put cursor now ? */
448   }
449   
450   editor->nUndoMode = nUndoMode;
451   pUI = ME_AddUndoItem(editor, diUndoDeleteRun, NULL);
452   FIXME("from %d to %d\n", from, to);
453   if (pUI && from < to)
454   {
455     pUI->nStart = from;
456     pUI->nLen = to-from;
457   }
458   ME_CommitUndo(editor);
459   ME_ReleaseStyle(style);
460
461   return 0;
462 }
463
464 ME_TextEditor *ME_MakeEditor(HWND hWnd) {
465   ME_TextEditor *ed = ALLOC_OBJ(ME_TextEditor);
466   HDC hDC;
467   int i;
468   ed->hWnd = hWnd;
469   ed->pBuffer = ME_MakeText();
470   hDC = GetDC(hWnd);
471   ME_MakeFirstParagraph(hDC, ed->pBuffer);
472   ReleaseDC(hWnd, hDC);
473   ed->bCaretShown = FALSE;
474   ed->nCursors = 3;
475   ed->pCursors = ALLOC_N_OBJ(ME_Cursor, ed->nCursors);
476   ed->pCursors[0].pRun = ME_FindItemFwd(ed->pBuffer->pFirst, diRun);
477   ed->pCursors[0].nOffset = 0;
478   ed->pCursors[1].pRun = ME_FindItemFwd(ed->pBuffer->pFirst, diRun);
479   ed->pCursors[1].nOffset = 0;
480   ed->nLastTotalLength = ed->nTotalLength = 0;
481   ed->nScrollPos = 0;
482   ed->nUDArrowX = -1;
483   ed->nSequence = 0;
484   ed->rgbBackColor = -1;
485   ed->bCaretAtEnd = FALSE;
486   ed->nEventMask = 0;
487   ed->nModifyStep = 0;
488   ed->pUndoStack = ed->pRedoStack = NULL;
489   ed->nUndoMode = umAddToUndo;
490   ed->nParagraphs = 1;
491   ed->nLastSelStart = ed->nLastSelEnd = 0;
492   for (i=0; i<HFONT_CACHE_SIZE; i++)
493   {
494     ed->pFontCache[i].nRefs = 0;
495     ed->pFontCache[i].nAge = 0;
496     ed->pFontCache[i].hFont = NULL;
497   }
498   ME_CheckCharOffsets(ed);
499   return ed;
500 }
501
502 void ME_DestroyEditor(ME_TextEditor *editor)
503 {
504   ME_DisplayItem *pFirst = editor->pBuffer->pFirst;
505   ME_DisplayItem *p = pFirst, *pNext = NULL;
506   int i;
507   
508   ME_ClearTempStyle(editor);
509   ME_EmptyUndoStack(editor);
510   while(p) {
511     pNext = p->next;
512     ME_DestroyDisplayItem(p);    
513     p = pNext;
514   }
515   ME_ReleaseStyle(editor->pBuffer->pDefaultStyle);
516   for (i=0; i<HFONT_CACHE_SIZE; i++)
517   {
518     if (editor->pFontCache[i].hFont)
519       DeleteObject(editor->pFontCache[i].hFont);
520   }
521     
522   FREE_OBJ(editor);
523 }
524
525 #define UNSUPPORTED_MSG(e) \
526   case e: \
527     FIXME(#e ": stub\n"); \
528     return DefWindowProcW(hWnd, msg, wParam, lParam);
529
530 /******************************************************************
531  *        RichEditANSIWndProc (RICHED20.10)
532  */
533 LRESULT WINAPI RichEditANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
534   HDC hDC;
535   PAINTSTRUCT ps;
536   SCROLLINFO si;
537   ME_TextEditor *editor = (ME_TextEditor *)GetWindowLongW(hWnd, 0);
538   TRACE("msg %d %08x %08lx\n", msg, wParam, lParam);
539   switch(msg) {
540   
541   UNSUPPORTED_MSG(EM_AUTOURLDETECT)
542   UNSUPPORTED_MSG(EM_CANPASTE)
543   UNSUPPORTED_MSG(EM_CHARFROMPOS)
544   UNSUPPORTED_MSG(EM_DISPLAYBAND)
545   UNSUPPORTED_MSG(EM_EXLIMITTEXT)
546   UNSUPPORTED_MSG(EM_EXLINEFROMCHAR)
547   UNSUPPORTED_MSG(EM_FINDTEXT)
548   UNSUPPORTED_MSG(EM_FINDTEXTEX)
549   UNSUPPORTED_MSG(EM_FINDWORDBREAK)
550   UNSUPPORTED_MSG(EM_FMTLINES)
551   UNSUPPORTED_MSG(EM_FORMATRANGE)
552   UNSUPPORTED_MSG(EM_GETFIRSTVISIBLELINE)
553   UNSUPPORTED_MSG(EM_GETIMECOMPMODE)
554   /* UNSUPPORTED_MSG(EM_GETIMESTATUS) missing in Wine headers */
555   UNSUPPORTED_MSG(EM_GETLANGOPTIONS)
556   UNSUPPORTED_MSG(EM_GETLIMITTEXT)
557   UNSUPPORTED_MSG(EM_GETLINE)
558   UNSUPPORTED_MSG(EM_GETLINECOUNT)
559   /* UNSUPPORTED_MSG(EM_GETOLEINTERFACE) separate stub */
560   UNSUPPORTED_MSG(EM_GETOPTIONS)
561   UNSUPPORTED_MSG(EM_GETRECT)
562   UNSUPPORTED_MSG(EM_GETREDONAME)
563   UNSUPPORTED_MSG(EM_GETTEXTMODE)
564   UNSUPPORTED_MSG(EM_GETUNDONAME)
565   UNSUPPORTED_MSG(EM_GETWORDBREAKPROC)
566   UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX)
567   UNSUPPORTED_MSG(EM_HIDESELECTION)
568   UNSUPPORTED_MSG(EM_LIMITTEXT) /* also known as EM_SETLIMITTEXT */
569   UNSUPPORTED_MSG(EM_LINEFROMCHAR)
570   UNSUPPORTED_MSG(EM_LINEINDEX)
571   UNSUPPORTED_MSG(EM_LINELENGTH)
572   UNSUPPORTED_MSG(EM_LINESCROLL)
573   UNSUPPORTED_MSG(EM_PASTESPECIAL)
574 /*  UNSUPPORTED_MSG(EM_POSFROMCHARS) missing in Wine headers */
575   UNSUPPORTED_MSG(EM_REQUESTRESIZE)
576   UNSUPPORTED_MSG(EM_SCROLL)
577   UNSUPPORTED_MSG(EM_SCROLLCARET)
578   UNSUPPORTED_MSG(EM_SELECTIONTYPE)
579   UNSUPPORTED_MSG(EM_SETLANGOPTIONS)
580   UNSUPPORTED_MSG(EM_SETOLECALLBACK)
581   UNSUPPORTED_MSG(EM_SETOPTIONS)
582   UNSUPPORTED_MSG(EM_SETRECT)
583   UNSUPPORTED_MSG(EM_SETRECTNP)
584   UNSUPPORTED_MSG(EM_SETTARGETDEVICE)
585   UNSUPPORTED_MSG(EM_SETTEXTMODE)
586   UNSUPPORTED_MSG(EM_SETUNDOLIMIT)
587   UNSUPPORTED_MSG(EM_SETWORDBREAKPROC)
588   UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX)
589   UNSUPPORTED_MSG(EM_STREAMOUT)
590   UNSUPPORTED_MSG(WM_SETFONT)
591   UNSUPPORTED_MSG(WM_PASTE)
592   UNSUPPORTED_MSG(WM_STYLECHANGING)
593   UNSUPPORTED_MSG(WM_STYLECHANGED)
594 /*  UNSUPPORTED_MSG(WM_UNICHAR) FIXME missing in Wine headers */
595     
596 /* Messages specific to Richedit controls */
597   
598   case EM_STREAMIN:
599    return ME_StreamIn(editor, wParam, (EDITSTREAM*)lParam);
600   case WM_GETDLGCODE:
601   {
602     UINT code = DLGC_WANTCHARS|DLGC_WANTARROWS;
603     if (GetWindowLongW(hWnd, GWL_STYLE)&ES_WANTRETURN)
604       code |= 0; /* FIXME what can we do here ? ask for messages and censor them ? */
605     return code;
606   }
607   case WM_NCCREATE:
608   {
609     CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
610     editor = ME_MakeEditor(hWnd);
611     SetWindowLongW(hWnd, 0, (long)editor);
612     pcs = 0; /* ignore */
613     return TRUE;
614   }
615   case EM_EMPTYUNDOBUFFER:
616     ME_EmptyUndoStack(editor);
617     return 0;
618   case EM_GETSEL:
619   {
620     ME_GetSelection(editor, (int *)wParam, (int *)lParam);
621     if (!((wParam|lParam) & 0xFFFF0000))
622       return (lParam<<16)|wParam;
623     return -1;
624   }
625   case EM_EXGETSEL:
626   {
627     CHARRANGE *pRange = (CHARRANGE *)lParam;
628     ME_GetSelection(editor, (int *)&pRange->cpMin, (int *)&pRange->cpMax);
629     return 0;
630   }
631   case EM_CANUNDO:
632     return editor->pUndoStack != NULL;
633   case EM_CANREDO:
634     return editor->pRedoStack != NULL;
635   case EM_UNDO:
636     ME_Undo(editor);
637     return 0;
638   case EM_REDO:
639     ME_Redo(editor);
640     return 0;
641   case EM_SETSEL:
642   {
643     ME_SetSelection(editor, wParam, lParam);
644     ME_Repaint(editor);
645     ME_SendSelChange(editor);
646     return 0;
647   }
648   case EM_EXSETSEL:
649   {
650     CHARRANGE *pRange = (CHARRANGE *)lParam;
651     ME_SetSelection(editor, pRange->cpMin, pRange->cpMax);
652     /* FIXME optimize */
653     ME_Repaint(editor);
654     ME_SendSelChange(editor);
655     return 0;
656   }
657   case EM_SETBKGNDCOLOR:
658   {
659     LRESULT lColor = ME_GetBackColor(editor);
660     if (wParam)
661       editor->rgbBackColor = -1;
662     else
663       editor->rgbBackColor = lParam; 
664     InvalidateRect(hWnd, NULL, TRUE);
665     UpdateWindow(hWnd);
666     return lColor;
667   }
668   case EM_GETMODIFY:
669     return editor->nModifyStep == 0 ? 0 : 1;
670   case EM_SETMODIFY:
671   {
672     if (wParam)
673       editor->nModifyStep = 0x80000000;
674     else
675       editor->nModifyStep = 0;
676     
677     return 0;
678   }
679   case EM_SETREADONLY:
680   {
681     long nStyle = GetWindowLongW(hWnd, GWL_STYLE);
682     if (wParam)
683       nStyle |= ES_READONLY;
684     else
685       nStyle &= ~ES_READONLY;
686     SetWindowLongW(hWnd, GWL_STYLE, nStyle);
687     ME_Repaint(editor);
688     return 0;
689   }
690   case EM_SETEVENTMASK:
691     editor->nEventMask = lParam;
692     return 0;
693   case EM_GETEVENTMASK:
694     return editor->nEventMask;
695   case EM_SETCHARFORMAT:
696   {
697     CHARFORMAT2W buf, *p;
698     p = ME_ToCF2W(&buf, (CHARFORMAT2W *)lParam);
699     if (!wParam)
700       ME_SetDefaultCharFormat(editor, p);
701     else if (wParam == (SCF_WORD | SCF_SELECTION))
702       FIXME("word selection not supported\n");
703     else if (wParam == SCF_ALL)
704       ME_SetCharFormat(editor, 0, ME_GetTextLength(editor), p);
705     else
706       ME_SetSelectionCharFormat(editor, p);
707     ME_CommitUndo(editor);
708     ME_UpdateRepaint(editor);
709     return 0;
710   }
711   case EM_GETCHARFORMAT:
712   {
713     CHARFORMAT2W tmp;
714     tmp.cbSize = sizeof(tmp);
715     if (!wParam)
716       ME_GetDefaultCharFormat(editor, &tmp);
717     else
718       ME_GetSelectionCharFormat(editor, &tmp);
719     ME_CopyToCFAny((CHARFORMAT2W *)lParam, &tmp);
720     return 0;
721   }
722   case EM_SETPARAFORMAT:
723     ME_SetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
724     ME_CommitUndo(editor);
725     return 0;
726   case EM_GETPARAFORMAT:
727     ME_GetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
728     return 0;
729   case WM_CLEAR:
730   {
731     int from, to;
732     ME_GetSelection(editor, &from, &to);
733     ME_InternalDeleteText(editor, from, to-from);
734     ME_CommitUndo(editor);
735     ME_UpdateRepaint(editor);
736     return 0;
737   }
738   case EM_REPLACESEL:
739   {
740     int from, to;
741     ME_Style *style;
742     LPWSTR wszText = ME_ToUnicode(hWnd, (void *)lParam);
743     size_t len = lstrlenW(wszText);
744     TRACE("EM_REPLACESEL - %s\n", debugstr_w(wszText));
745     
746     ME_GetSelection(editor, &from, &to);
747     style = ME_GetSelectionInsertStyle(editor);
748     ME_InternalDeleteText(editor, from, to-from);
749     ME_InsertTextFromCursor(editor, 0, wszText, len, style);
750     ME_ReleaseStyle(style);
751     ME_EndToUnicode(hWnd, wszText);
752     /* drop temporary style if line end */
753     /* FIXME question: does abc\n mean: put abc, clear temp style, put \n? (would require a change) */  
754     if (len>0 && wszText[len-1] == '\n')
755       ME_ClearTempStyle(editor);
756       
757     ME_CommitUndo(editor);
758     if (!wParam)
759       ME_EmptyUndoStack(editor);
760     ME_UpdateRepaint(editor);
761     return 0;
762   }
763   case WM_SETTEXT:
764   {
765     LPWSTR wszText = ME_ToUnicode(hWnd, (void *)lParam);
766     TRACE("WM_SETTEXT - %s\n", (char *)(wszText)); /* debugstr_w() */
767     ME_InternalDeleteText(editor, 0, ME_GetTextLength(editor));
768     /* uses default style! */
769     ME_InsertTextFromCursor(editor, 0, wszText, -1, editor->pBuffer->pDefaultStyle);
770     ME_EndToUnicode(hWnd, wszText);
771     ME_CommitUndo(editor);
772     ME_EmptyUndoStack(editor);
773     ME_UpdateRepaint(editor);
774     return 0;
775   }
776   case WM_CUT:
777   case WM_COPY:
778   {
779     int from, to, pars;
780     WCHAR *data;
781     HANDLE hData;
782     
783     if (!OpenClipboard(hWnd))
784       return 0;
785       
786     EmptyClipboard();
787     ME_GetSelection(editor, &from, &to);
788     pars = ME_CountParagraphsBetween(editor, from, to);
789     hData = GlobalAlloc(GMEM_MOVEABLE, sizeof(WCHAR)*(to-from+pars+1));
790     data = (WCHAR *)GlobalLock(hData);
791     ME_GetTextW(editor, data, from, to-from, TRUE);
792     GlobalUnlock(hData);
793     SetClipboardData(CF_UNICODETEXT, hData);
794     CloseClipboard();
795     if (msg == WM_CUT)
796     {
797       ME_InternalDeleteText(editor, from, to-from);
798       ME_CommitUndo(editor);
799       ME_UpdateRepaint(editor);
800     }
801     return 0;
802   }
803   case WM_GETTEXTLENGTH:
804     return ME_GetTextLength(editor);
805   case WM_GETTEXT:
806   {
807     TEXTRANGEW tr; /* W and A differ only by rng->lpstrText */
808     tr.chrg.cpMin = 0;
809     tr.chrg.cpMax = wParam-1;
810     tr.lpstrText = (WCHAR *)lParam;
811     return RichEditANSIWndProc(hWnd, EM_GETTEXTRANGE, 0, (LPARAM)&tr);
812   }
813   case EM_GETSELTEXT:
814   {
815     int from, to;
816     TEXTRANGEW tr; /* W and A differ only by rng->lpstrText */
817     ME_GetSelection(editor, &from, &to);
818     tr.chrg.cpMin = from;
819     tr.chrg.cpMax = to;
820     tr.lpstrText = (WCHAR *)lParam;
821     return RichEditANSIWndProc(hWnd, EM_GETTEXTRANGE, 0, (LPARAM)&tr);
822   }
823   case EM_GETTEXTRANGE:
824   {
825     TEXTRANGEW *rng = (TEXTRANGEW *)lParam;
826     if (IsWindowUnicode(hWnd))
827       return ME_GetTextW(editor, rng->lpstrText, rng->chrg.cpMin, rng->chrg.cpMax-rng->chrg.cpMin, FALSE);
828     else
829     {
830       int nLen = rng->chrg.cpMax-rng->chrg.cpMin;
831       WCHAR *p = ALLOC_N_OBJ(WCHAR, nLen+1);
832       int nChars = ME_GetTextW(editor, p, rng->chrg.cpMin, nLen, FALSE);
833       /* FIXME this is a potential security hole (buffer overrun) 
834          if you know more about wchar->mbyte conversion please explain
835       */
836       WideCharToMultiByte(CP_ACP, 0, p, nChars+1, (char *)rng->lpstrText, nLen+1, NULL, NULL);
837       FREE_OBJ(p);
838       return nChars;
839     }
840     return ME_GetTextW(editor, rng->lpstrText, rng->chrg.cpMin, rng->chrg.cpMax-rng->chrg.cpMin, FALSE);
841   }
842   case WM_CREATE:
843     ME_CommitUndo(editor);
844     ME_WrapMarkedParagraphs(editor);
845     ME_MoveCaret(editor);
846     return 0;
847   case WM_DESTROY:
848     ME_DestroyEditor(editor);
849     SetWindowLongW(hWnd, 0, 0);
850     return 0;
851   case WM_LBUTTONDOWN:
852     SetFocus(hWnd);
853     ME_LButtonDown(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
854     SetCapture(hWnd);
855     break;
856   case WM_MOUSEMOVE:
857     if (GetCapture() == hWnd)
858       ME_MouseMove(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
859     break;
860   case WM_LBUTTONUP:
861     if (GetCapture() == hWnd)
862       ReleaseCapture();
863     break;
864   case WM_PAINT:
865     hDC = BeginPaint(hWnd, &ps);
866     ME_PaintContent(editor, hDC, FALSE, &ps.rcPaint);
867     EndPaint(hWnd, &ps);
868     break;
869   case WM_SETFOCUS:
870     ME_ShowCaret(editor);
871     ME_SendOldNotify(editor, EN_SETFOCUS);
872     return 0;
873   case WM_KILLFOCUS:
874     ME_HideCaret(editor);
875     ME_SendOldNotify(editor, EN_KILLFOCUS);
876     return 0;
877   case WM_ERASEBKGND:
878   {
879     HDC hDC = (HDC)wParam;
880     RECT rc;
881     COLORREF rgbBG = ME_GetBackColor(editor);
882     if (GetUpdateRect(hWnd,&rc,TRUE))
883     {
884       HBRUSH hbr = CreateSolidBrush(rgbBG);
885       FillRect(hDC, &rc, hbr);
886       DeleteObject(hbr);
887     }
888     return 1;
889   }
890   case WM_COMMAND:
891     TRACE("editor wnd command = %d\n", LOWORD(wParam));
892     return 0;
893   case WM_KEYDOWN:
894     if (ME_ArrowKey(editor, LOWORD(wParam), GetKeyState(VK_CONTROL)<0)) {
895       ME_CommitUndo(editor);
896       ME_EnsureVisible(editor, editor->pCursors[0].pRun);
897       HideCaret(hWnd);
898       ME_MoveCaret(editor);
899       ShowCaret(hWnd);
900       return 0;
901     }
902     if (GetKeyState(VK_CONTROL)<0)
903     {
904       if (LOWORD(wParam)=='W')
905       {
906         CHARFORMAT2W chf;
907         char buf[2048];
908         ME_GetSelectionCharFormat(editor, &chf);
909         ME_DumpStyleToBuf(&chf, buf);
910         MessageBoxA(NULL, buf, "Style dump", MB_OK);
911       }
912       if (LOWORD(wParam)=='Q')
913       {
914         ME_CheckCharOffsets(editor);
915       }
916     }
917     goto do_default;
918   case WM_CHAR: 
919   {
920     WCHAR wstr;
921     if (GetWindowLongW(editor->hWnd, GWL_STYLE) & ES_READONLY) {
922       MessageBeep(MB_ICONERROR);
923       return 0; /* FIXME really 0 ? */
924     }
925     wstr = LOWORD(wParam);
926     if (((unsigned)wstr)>=' ' || wstr=='\r') {
927       /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
928       ME_Style *style = ME_GetInsertStyle(editor, 0);
929       ME_SaveTempStyle(editor);
930       ME_InsertTextFromCursor(editor, 0, &wstr, 1, style);
931       ME_ReleaseStyle(style);
932       ME_CommitUndo(editor);
933       ME_UpdateRepaint(editor);
934     }
935     return 0;
936   }
937   case WM_VSCROLL: 
938   {
939     si.cbSize = sizeof(SCROLLINFO);
940     si.fMask = SIF_PAGE|SIF_POS|SIF_RANGE|SIF_TRACKPOS;
941     GetScrollInfo(hWnd, SB_VERT, &si);
942     switch(LOWORD(wParam)) {
943     case SB_THUMBTRACK:
944       SetScrollPos(hWnd, SB_VERT, si.nTrackPos, FALSE);
945       ScrollWindow(hWnd, 0, si.nPos-si.nTrackPos, NULL, NULL);
946       /* InvalidateRect(hWnd, NULL, TRUE); */
947       UpdateWindow(hWnd);
948       break;
949     }
950     break;
951   }
952   case WM_SIZE:
953   {
954     ME_MarkAllForWrapping(editor);
955     ME_Repaint(editor);
956     return DefWindowProcW(hWnd, msg, wParam, lParam);
957   }
958   case EM_GETOLEINTERFACE:
959   {
960     LPVOID *ppvObj = (LPVOID*) lParam;
961     FIXME("EM_GETOLEINTERFACE %p: stub\n", ppvObj);
962     return CreateIRichEditOle(ppvObj);
963   }
964   default:
965   do_default:
966     return DefWindowProcW(hWnd, msg, wParam, lParam);
967   }
968   return 0L;
969 }
970
971 /******************************************************************
972  *        RichEdit10ANSIWndProc (RICHED20.9)
973  */
974 LRESULT WINAPI RichEdit10ANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
975 {
976   /* FIXME: this is NOT the same as 2.0 version */
977   return RichEditANSIWndProc(hWnd, msg, wParam, lParam);
978 }
979
980 void ME_SendOldNotify(ME_TextEditor *editor, int nCode)
981 {
982   HWND hWnd = editor->hWnd;
983   SendMessageA(GetParent(hWnd), WM_COMMAND, (nCode<<16)|GetWindowLongW(hWnd, GWLP_ID), (LPARAM)hWnd);
984 }
985
986 int ME_CountParagraphsBetween(ME_TextEditor *editor, int from, int to)
987 {
988   ME_DisplayItem *item = ME_FindItemFwd(editor->pBuffer->pFirst, diParagraph);
989   int i = 0;
990   
991   while(item && item->member.para.next_para->member.para.nCharOfs <= from)
992     item = item->member.para.next_para;
993   if (!item)
994     return 0;
995   while(item && item->member.para.next_para->member.para.nCharOfs <= to) {
996     item = item->member.para.next_para;
997     i++;
998   }
999   return i;
1000 }
1001
1002 int ME_GetTextW(ME_TextEditor *editor, WCHAR *buffer, int nStart, int nChars, int bCRLF)
1003 {
1004   ME_DisplayItem *item = ME_FindItemFwd(editor->pBuffer->pFirst, diParagraph);
1005   int nWritten = 0;
1006   
1007   while(item && item->member.para.next_para->member.para.nCharOfs <= nStart)
1008     item = ME_FindItemFwd(item, diParagraph);
1009   if (!item) {
1010     *buffer = L'\0';
1011     return 0;
1012   }
1013   nStart -= item->member.para.nCharOfs;
1014
1015   do {  
1016     item = ME_FindItemFwd(item, diRun);
1017   } while(item && (item->member.run.nCharOfs + ME_StrLen(item->member.run.strText) <= nStart));
1018   assert(item);    
1019   
1020   nStart -= item->member.run.nCharOfs;
1021   
1022   if (nStart)
1023   {
1024     int nLen = ME_StrLen(item->member.run.strText) - nStart;
1025     if (nLen > nChars)
1026       nLen = nChars;
1027     CopyMemory(buffer, item->member.run.strText->szData + nStart, sizeof(WCHAR)*nLen);
1028     nChars -= nLen;
1029     nWritten += nLen;
1030     if (!nChars)
1031       return nWritten;
1032     buffer += nLen;
1033     nStart = 0;
1034     item = ME_FindItemFwd(item, diRun);
1035   }
1036   
1037   while(nChars && item)
1038   {
1039     int nLen = ME_StrLen(item->member.run.strText);
1040     if (nLen > nChars)
1041       nLen = nChars;
1042       
1043     if (item->member.run.nFlags & MERF_ENDPARA)
1044     {
1045       if (bCRLF) {
1046         *buffer++ = '\r';
1047         nWritten++;
1048       }        
1049       *buffer = '\n';
1050       assert(nLen == 1);
1051     }
1052     else      
1053       CopyMemory(buffer, item->member.run.strText->szData, sizeof(WCHAR)*nLen);
1054     nChars -= nLen;
1055     nWritten += nLen;
1056     buffer += nLen;    
1057       
1058     if (!nChars)
1059     {
1060       *buffer = L'\0';
1061       return nWritten;
1062     }
1063     item = ME_FindItemFwd(item, diRun);
1064   }
1065   *buffer = L'\0';
1066   return nWritten;  
1067 }
1068
1069 static WCHAR wszClassName[] = {'R', 'i', 'c', 'h', 'E', 'd', 'i', 't', '2', '0', 'W', 0};
1070 static WCHAR wszClassName50[] = {'R', 'i', 'c', 'h', 'E', 'd', 'i', 't', '5', '0', 'W', 0};
1071
1072 void ME_RegisterEditorClass(HINSTANCE hInstance)
1073 {
1074   BOOL bResult;
1075   WNDCLASSW wcW;
1076   WNDCLASSA wcA;
1077   
1078   wcW.style = CS_HREDRAW | CS_VREDRAW;
1079   wcW.lpfnWndProc = RichEditANSIWndProc;
1080   wcW.cbClsExtra = 0;
1081   wcW.cbWndExtra = 4;
1082   wcW.hInstance = NULL; /* hInstance would register DLL-local class */
1083   wcW.hIcon = NULL;
1084   wcW.hCursor = LoadCursorW(NULL, MAKEINTRESOURCEW(IDC_IBEAM));
1085   wcW.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);
1086   wcW.lpszMenuName = NULL;
1087   wcW.lpszClassName = wszClassName;
1088   bResult = RegisterClassW(&wcW);  
1089   assert(bResult);
1090   wcW.lpszClassName = wszClassName50;
1091   bResult = RegisterClassW(&wcW);  
1092   assert(bResult);
1093
1094   wcA.style = CS_HREDRAW | CS_VREDRAW;
1095   wcA.lpfnWndProc = RichEditANSIWndProc;
1096   wcA.cbClsExtra = 0;
1097   wcA.cbWndExtra = 4;
1098   wcA.hInstance = NULL; /* hInstance would register DLL-local class */
1099   wcA.hIcon = NULL;
1100   wcA.hCursor = LoadCursorW(NULL, MAKEINTRESOURCEW(IDC_IBEAM));
1101   wcA.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);
1102   wcA.lpszMenuName = NULL;
1103   wcA.lpszClassName = "RichEdit20A";
1104   bResult = RegisterClassA(&wcA);  
1105   assert(bResult);
1106   wcA.lpszClassName = "RichEdit50A";
1107   bResult = RegisterClassA(&wcA);  
1108   assert(bResult);
1109 }
1110
1111 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
1112 {
1113     TRACE("\n");
1114     switch (fdwReason)
1115     {
1116     case DLL_PROCESS_ATTACH:
1117       DisableThreadLibraryCalls(hinstDLL);
1118       me_heap = HeapCreate (0, 0x10000, 0);
1119       ME_RegisterEditorClass(hinstDLL);
1120       break;
1121
1122     case DLL_PROCESS_DETACH:
1123       UnregisterClassW(wszClassName, 0);
1124       UnregisterClassW(wszClassName50, 0);
1125       UnregisterClassA("RichEdit20A", 0);
1126       UnregisterClassA("RichEdit50A", 0);
1127       HeapDestroy (me_heap);
1128       me_heap = NULL;
1129       break;
1130     }
1131     return TRUE;
1132 }
1133
1134 /******************************************************************
1135  *        CreateTextServices (RICHED20.4)
1136  *
1137  * FIXME should be ITextHost instead of void*
1138  */
1139 HRESULT WINAPI CreateTextServices(IUnknown *punkOuter, void *pITextHost,
1140     IUnknown **ppUnk)
1141 {
1142   FIXME("stub\n");
1143   /* FIXME should support aggregation */
1144   if (punkOuter)
1145     return CLASS_E_NOAGGREGATION;
1146     
1147   return E_FAIL; /* E_NOTIMPL isn't allowed by MSDN */
1148 }
1149
1150 /******************************************************************
1151  *        REExtendedRegisterClass (RICHED20.8)
1152  *
1153  * FIXME undocumented
1154  */
1155 void WINAPI REExtendedRegisterClass(void)
1156 {
1157   FIXME("stub\n");
1158 }