rpcrt4: Spelling fixes in comments.
[wine.git] / dlls / riched20 / editor.c
blob1ce444e6047547a9b6b86fc8cc09e36e42f89ed3
1 /*
2 * RichEdit - functions dealing with editor object
4 * Copyright 2004 by Krzysztof Foltman
5 * Copyright 2005 by Cihan Altinay
6 * Copyright 2005 by Phil Krylov
7 * Copyright 2008 Eric Pouech
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 /*
25 API implementation status:
27 Messages (ANSI versions not done yet)
28 + EM_AUTOURLDETECT 2.0
29 + EM_CANPASTE
30 + EM_CANREDO 2.0
31 + EM_CANUNDO
32 + EM_CHARFROMPOS
33 - EM_DISPLAYBAND
34 + EM_EMPTYUNDOBUFFER
35 + EM_EXGETSEL
36 + EM_EXLIMITTEXT
37 + EM_EXLINEFROMCHAR
38 + EM_EXSETSEL
39 + EM_FINDTEXT (only FR_DOWN flag implemented)
40 + EM_FINDTEXTEX (only FR_DOWN flag implemented)
41 - EM_FINDWORDBREAK
42 - EM_FMTLINES
43 - EM_FORMATRANGE
44 + EM_GETAUTOURLDETECT 2.0
45 - EM_GETBIDIOPTIONS 3.0
46 - EM_GETCHARFORMAT (partly done)
47 - EM_GETEDITSTYLE
48 + EM_GETEVENTMASK
49 + EM_GETFIRSTVISIBLELINE (can be optimized if needed)
50 - EM_GETIMECOLOR 1.0asian
51 - EM_GETIMECOMPMODE 2.0
52 - EM_GETIMEOPTIONS 1.0asian
53 - EM_GETIMESTATUS
54 - EM_GETLANGOPTIONS 2.0
55 + EM_GETLIMITTEXT
56 + EM_GETLINE
57 + EM_GETLINECOUNT returns number of rows, not of paragraphs
58 + EM_GETMODIFY
59 + EM_GETOLEINTERFACE
60 + EM_GETOPTIONS
61 + EM_GETPARAFORMAT
62 + EM_GETPASSWORDCHAR 2.0
63 - EM_GETPUNCTUATION 1.0asian
64 + EM_GETRECT
65 - EM_GETREDONAME 2.0
66 + EM_GETSEL
67 + EM_GETSELTEXT (ANSI&Unicode)
68 + EM_GETSCROLLPOS 3.0
69 ! - EM_GETTHUMB
70 + EM_GETTEXTEX 2.0
71 + EM_GETTEXTLENGTHEX (GTL_PRECISE unimplemented)
72 + EM_GETTEXTMODE 2.0
73 ? + EM_GETTEXTRANGE (ANSI&Unicode)
74 - EM_GETTYPOGRAPHYOPTIONS 3.0
75 - EM_GETUNDONAME
76 + EM_GETWORDBREAKPROC
77 - EM_GETWORDBREAKPROCEX
78 - EM_GETWORDWRAPMODE 1.0asian
79 + EM_GETZOOM 3.0
80 + EM_HIDESELECTION
81 + EM_LIMITTEXT (Also called EM_SETLIMITTEXT)
82 + EM_LINEFROMCHAR
83 + EM_LINEINDEX
84 + EM_LINELENGTH
85 + EM_LINESCROLL
86 - EM_PASTESPECIAL
87 + EM_POSFROMCHAR
88 + EM_REDO 2.0
89 + EM_REQUESTRESIZE
90 + EM_REPLACESEL (proper style?) ANSI&Unicode
91 + EM_SCROLL
92 + EM_SCROLLCARET
93 - EM_SELECTIONTYPE
94 - EM_SETBIDIOPTIONS 3.0
95 + EM_SETBKGNDCOLOR
96 + EM_SETCHARFORMAT (partly done, no ANSI)
97 - EM_SETEDITSTYLE
98 + EM_SETEVENTMASK (few notifications supported)
99 + EM_SETFONTSIZE
100 - EM_SETIMECOLOR 1.0asian
101 - EM_SETIMEOPTIONS 1.0asian
102 - EM_SETIMESTATUS
103 - EM_SETLANGOPTIONS 2.0
104 - EM_SETLIMITTEXT
105 - EM_SETMARGINS
106 + EM_SETMODIFY (not sure if implementation is correct)
107 - EM_SETOLECALLBACK
108 + EM_SETOPTIONS (partially implemented)
109 - EM_SETPALETTE 2.0
110 + EM_SETPARAFORMAT
111 + EM_SETPASSWORDCHAR 2.0
112 - EM_SETPUNCTUATION 1.0asian
113 + EM_SETREADONLY no beep on modification attempt
114 + EM_SETRECT
115 + EM_SETRECTNP (EM_SETRECT without repainting)
116 + EM_SETSEL
117 + EM_SETSCROLLPOS 3.0
118 - EM_SETTABSTOPS 3.0
119 - EM_SETTARGETDEVICE (partial)
120 + EM_SETTEXTEX 3.0 (proper style?)
121 - EM_SETTEXTMODE 2.0
122 - EM_SETTYPOGRAPHYOPTIONS 3.0
123 + EM_SETUNDOLIMIT 2.0
124 + EM_SETWORDBREAKPROC (used only for word movement at the moment)
125 - EM_SETWORDBREAKPROCEX
126 - EM_SETWORDWRAPMODE 1.0asian
127 + EM_SETZOOM 3.0
128 + EM_SHOWSCROLLBAR 2.0
129 + EM_STOPGROUPTYPING 2.0
130 + EM_STREAMIN
131 + EM_STREAMOUT
132 + EM_UNDO
133 + WM_CHAR
134 + WM_CLEAR
135 + WM_COPY
136 + WM_CUT
137 + WM_GETDLGCODE (the current implementation is incomplete)
138 + WM_GETTEXT (ANSI&Unicode)
139 + WM_GETTEXTLENGTH (ANSI version sucks)
140 + WM_HSCROLL
141 + WM_PASTE
142 + WM_SETFONT
143 + WM_SETTEXT (resets undo stack !) (proper style?) ANSI&Unicode
144 + WM_STYLECHANGING (seems to do nothing)
145 + WM_STYLECHANGED (seems to do nothing)
146 + WM_UNICHAR
147 + WM_VSCROLL
149 Notifications
151 * EN_CHANGE (sent from the wrong place)
152 - EN_CORRECTTEXT
153 - EN_DROPFILES
154 - EN_ERRSPACE
155 - EN_HSCROLL
156 - EN_IMECHANGE
157 + EN_KILLFOCUS
158 - EN_LINK
159 - EN_MAXTEXT
160 - EN_MSGFILTER
161 - EN_OLEOPFAILED
162 - EN_PROTECTED
163 + EN_REQUESTRESIZE
164 - EN_SAVECLIPBOARD
165 + EN_SELCHANGE
166 + EN_SETFOCUS
167 - EN_STOPNOUNDO
168 * EN_UPDATE (sent from the wrong place)
169 - EN_VSCROLL
171 Styles
173 - ES_AUTOHSCROLL
174 - ES_AUTOVSCROLL
175 + ES_CENTER
176 + ES_DISABLENOSCROLL (scrollbar is always visible)
177 - ES_EX_NOCALLOLEINIT
178 + ES_LEFT
179 - ES_MULTILINE (currently single line controls aren't supported)
180 - ES_NOIME
181 - ES_READONLY (I'm not sure if beeping is the proper behaviour)
182 + ES_RIGHT
183 - ES_SAVESEL
184 - ES_SELFIME
185 - ES_SUNKEN
186 - ES_VERTICAL
187 - ES_WANTRETURN (don't know how to do WM_GETDLGCODE part)
188 - WS_SETFONT
189 + WS_HSCROLL
190 + WS_VSCROLL
194 * RICHED20 TODO (incomplete):
196 * - messages/styles/notifications listed above
197 * - add remaining CHARFORMAT/PARAFORMAT fields
198 * - right/center align should strip spaces from the beginning
199 * - pictures/OLE objects (not just smiling faces that lack API support ;-) )
200 * - COM interface (looks like a major pain in the TODO list)
201 * - calculate heights of pictures (half-done)
202 * - hysteresis during wrapping (related to scrollbars appearing/disappearing)
203 * - find/replace
204 * - how to implement EM_FORMATRANGE and EM_DISPLAYBAND ? (Mission Impossible)
205 * - italic caret with italic fonts
206 * - IME
207 * - most notifications aren't sent at all (the most important ones are)
208 * - when should EN_SELCHANGE be sent after text change ? (before/after EN_UPDATE?)
209 * - WM_SETTEXT may use wrong style (but I'm 80% sure it's OK)
210 * - EM_GETCHARFORMAT with SCF_SELECTION may not behave 100% like in original (but very close)
211 * - full justification
212 * - hyphenation
213 * - tables
214 * - ListBox & ComboBox not implemented
216 * Bugs that are probably fixed, but not so easy to verify:
217 * - EN_UPDATE/EN_CHANGE are handled very incorrectly (should be OK now)
218 * - undo for ME_JoinParagraphs doesn't store paragraph format ? (it does)
219 * - check/fix artificial EOL logic (bCursorAtEnd, hardly logical)
220 * - caret shouldn't be displayed when selection isn't empty
221 * - check refcounting in style management functions (looks perfect now, but no bugs is suspicious)
222 * - undo for setting default format (done, might be buggy)
223 * - styles might be not released properly (looks like they work like charm, but who knows?
227 #define NONAMELESSUNION
229 #include "editor.h"
230 #include "commdlg.h"
231 #include "winreg.h"
232 #define NO_SHLWAPI_STREAM
233 #include "shlwapi.h"
234 #include "rtf.h"
235 #include "imm.h"
236 #include "res.h"
238 #define STACK_SIZE_DEFAULT 100
239 #define STACK_SIZE_MAX 1000
241 #define TEXT_LIMIT_DEFAULT 32767
243 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
245 static BOOL ME_RegisterEditorClass(HINSTANCE);
246 static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars);
248 static const WCHAR REListBox20W[] = {'R','E','L','i','s','t','B','o','x','2','0','W', 0};
249 static const WCHAR REComboBox20W[] = {'R','E','C','o','m','b','o','B','o','x','2','0','W', 0};
250 static HCURSOR hLeft;
252 BOOL me_debug = FALSE;
253 HANDLE me_heap = NULL;
255 static BOOL ME_ListBoxRegistered = FALSE;
256 static BOOL ME_ComboBoxRegistered = FALSE;
258 static inline BOOL is_version_nt(void)
260 return !(GetVersion() & 0x80000000);
263 static ME_TextBuffer *ME_MakeText(void) {
265 ME_TextBuffer *buf = ALLOC_OBJ(ME_TextBuffer);
267 ME_DisplayItem *p1 = ME_MakeDI(diTextStart);
268 ME_DisplayItem *p2 = ME_MakeDI(diTextEnd);
270 p1->prev = NULL;
271 p1->next = p2;
272 p2->prev = p1;
273 p2->next = NULL;
274 p1->member.para.next_para = p2;
275 p2->member.para.prev_para = p1;
276 p2->member.para.nCharOfs = 0;
278 buf->pFirst = p1;
279 buf->pLast = p2;
280 buf->pCharStyle = NULL;
282 return buf;
286 static LRESULT ME_StreamInText(ME_TextEditor *editor, DWORD dwFormat, ME_InStream *stream, ME_Style *style)
288 WCHAR *pText;
289 LRESULT total_bytes_read = 0;
290 BOOL is_read = FALSE;
291 DWORD cp = CP_ACP, copy = 0;
292 char conv_buf[4 + STREAMIN_BUFFER_SIZE]; /* up to 4 additional UTF-8 bytes */
294 static const char bom_utf8[] = {0xEF, 0xBB, 0xBF};
296 TRACE("%08x %p\n", dwFormat, stream);
298 do {
299 LONG nWideChars = 0;
300 WCHAR wszText[STREAMIN_BUFFER_SIZE+1];
302 if (!stream->dwSize)
304 ME_StreamInFill(stream);
305 if (stream->editstream->dwError)
306 break;
307 if (!stream->dwSize)
308 break;
309 total_bytes_read += stream->dwSize;
312 if (!(dwFormat & SF_UNICODE))
314 char * buf = stream->buffer;
315 DWORD size = stream->dwSize, end;
317 if (!is_read)
319 is_read = TRUE;
320 if (stream->dwSize >= 3 && !memcmp(stream->buffer, bom_utf8, 3))
322 cp = CP_UTF8;
323 buf += 3;
324 size -= 3;
328 if (cp == CP_UTF8)
330 if (copy)
332 memcpy(conv_buf + copy, buf, size);
333 buf = conv_buf;
334 size += copy;
336 end = size;
337 while ((buf[end-1] & 0xC0) == 0x80)
339 --end;
340 --total_bytes_read; /* strange, but seems to match windows */
342 if (buf[end-1] & 0x80)
344 DWORD need = 0;
345 if ((buf[end-1] & 0xE0) == 0xC0)
346 need = 1;
347 if ((buf[end-1] & 0xF0) == 0xE0)
348 need = 2;
349 if ((buf[end-1] & 0xF8) == 0xF0)
350 need = 3;
352 if (size - end >= need)
354 /* we have enough bytes for this sequence */
355 end = size;
357 else
359 /* need more bytes, so don't transcode this sequence */
360 --end;
364 else
365 end = size;
367 nWideChars = MultiByteToWideChar(cp, 0, buf, end, wszText, STREAMIN_BUFFER_SIZE);
368 pText = wszText;
370 if (cp == CP_UTF8)
372 if (end != size)
374 memcpy(conv_buf, buf + end, size - end);
375 copy = size - end;
379 else
381 nWideChars = stream->dwSize >> 1;
382 pText = (WCHAR *)stream->buffer;
385 ME_InsertTextFromCursor(editor, 0, pText, nWideChars, style);
386 if (stream->dwSize == 0)
387 break;
388 stream->dwSize = 0;
389 } while(1);
390 return total_bytes_read;
393 static void ME_ApplyBorderProperties(RTF_Info *info,
394 ME_BorderRect *borderRect,
395 RTFBorder *borderDef)
397 int i, colorNum;
398 ME_Border *pBorders[] = {&borderRect->top,
399 &borderRect->left,
400 &borderRect->bottom,
401 &borderRect->right};
402 for (i = 0; i < 4; i++)
404 RTFColor *colorDef = info->colorList;
405 pBorders[i]->width = borderDef[i].width;
406 colorNum = borderDef[i].color;
407 while (colorDef && colorDef->rtfCNum != colorNum)
408 colorDef = colorDef->rtfNextColor;
409 if (colorDef)
410 pBorders[i]->colorRef = RGB(
411 colorDef->rtfCRed >= 0 ? colorDef->rtfCRed : 0,
412 colorDef->rtfCGreen >= 0 ? colorDef->rtfCGreen : 0,
413 colorDef->rtfCBlue >= 0 ? colorDef->rtfCBlue : 0);
414 else
415 pBorders[i]->colorRef = RGB(0, 0, 0);
419 void ME_RTFCharAttrHook(RTF_Info *info)
421 CHARFORMAT2W fmt;
422 fmt.cbSize = sizeof(fmt);
423 fmt.dwMask = 0;
424 fmt.dwEffects = 0;
426 switch(info->rtfMinor)
428 case rtfPlain:
429 /* FIXME add more flags once they're implemented */
430 fmt.dwMask = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE | CFM_UNDERLINETYPE | CFM_STRIKEOUT |
431 CFM_COLOR | CFM_BACKCOLOR | CFM_SIZE | CFM_WEIGHT;
432 fmt.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
433 fmt.yHeight = 12*20; /* 12pt */
434 fmt.wWeight = FW_NORMAL;
435 fmt.bUnderlineType = CFU_UNDERLINE;
436 break;
437 case rtfBold:
438 fmt.dwMask = CFM_BOLD | CFM_WEIGHT;
439 fmt.dwEffects = info->rtfParam ? CFE_BOLD : 0;
440 fmt.wWeight = info->rtfParam ? FW_BOLD : FW_NORMAL;
441 break;
442 case rtfItalic:
443 fmt.dwMask = CFM_ITALIC;
444 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
445 break;
446 case rtfUnderline:
447 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
448 fmt.bUnderlineType = CFU_UNDERLINE;
449 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
450 break;
451 case rtfDotUnderline:
452 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
453 fmt.bUnderlineType = CFU_UNDERLINEDOTTED;
454 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
455 break;
456 case rtfDbUnderline:
457 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
458 fmt.bUnderlineType = CFU_UNDERLINEDOUBLE;
459 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
460 break;
461 case rtfWordUnderline:
462 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
463 fmt.bUnderlineType = CFU_UNDERLINEWORD;
464 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
465 break;
466 case rtfNoUnderline:
467 fmt.dwMask = CFM_UNDERLINE;
468 fmt.dwEffects = 0;
469 break;
470 case rtfStrikeThru:
471 fmt.dwMask = CFM_STRIKEOUT;
472 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
473 break;
474 case rtfSubScript:
475 case rtfSuperScript:
476 case rtfSubScrShrink:
477 case rtfSuperScrShrink:
478 case rtfNoSuperSub:
479 fmt.dwMask = CFM_SUBSCRIPT|CFM_SUPERSCRIPT;
480 if (info->rtfMinor == rtfSubScrShrink) fmt.dwEffects = CFE_SUBSCRIPT;
481 if (info->rtfMinor == rtfSuperScrShrink) fmt.dwEffects = CFE_SUPERSCRIPT;
482 if (info->rtfMinor == rtfNoSuperSub) fmt.dwEffects = 0;
483 break;
484 case rtfInvisible:
485 fmt.dwMask = CFM_HIDDEN;
486 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
487 break;
488 case rtfBackColor:
489 fmt.dwMask = CFM_BACKCOLOR;
490 fmt.dwEffects = 0;
491 if (info->rtfParam == 0)
492 fmt.dwEffects = CFE_AUTOBACKCOLOR;
493 else if (info->rtfParam != rtfNoParam)
495 RTFColor *c = RTFGetColor(info, info->rtfParam);
496 if (c && c->rtfCBlue >= 0)
497 fmt.crBackColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
498 else
499 fmt.dwEffects = CFE_AUTOBACKCOLOR;
501 break;
502 case rtfForeColor:
503 fmt.dwMask = CFM_COLOR;
504 fmt.dwEffects = 0;
505 if (info->rtfParam == 0)
506 fmt.dwEffects = CFE_AUTOCOLOR;
507 else if (info->rtfParam != rtfNoParam)
509 RTFColor *c = RTFGetColor(info, info->rtfParam);
510 if (c && c->rtfCBlue >= 0)
511 fmt.crTextColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
512 else {
513 fmt.dwEffects = CFE_AUTOCOLOR;
516 break;
517 case rtfFontNum:
518 if (info->rtfParam != rtfNoParam)
520 RTFFont *f = RTFGetFont(info, info->rtfParam);
521 if (f)
523 MultiByteToWideChar(CP_ACP, 0, f->rtfFName, -1, fmt.szFaceName, sizeof(fmt.szFaceName)/sizeof(WCHAR));
524 fmt.szFaceName[sizeof(fmt.szFaceName)/sizeof(WCHAR)-1] = '\0';
525 fmt.bCharSet = f->rtfFCharSet;
526 fmt.dwMask = CFM_FACE | CFM_CHARSET;
527 fmt.bPitchAndFamily = f->rtfFPitch | (f->rtfFFamily << 4);
530 break;
531 case rtfFontSize:
532 fmt.dwMask = CFM_SIZE;
533 if (info->rtfParam != rtfNoParam)
534 fmt.yHeight = info->rtfParam*10;
535 break;
537 if (fmt.dwMask) {
538 ME_Style *style2;
539 RTFFlushOutputBuffer(info);
540 /* FIXME too slow ? how come ? */
541 style2 = ME_ApplyStyle(info->editor, info->style, &fmt);
542 ME_ReleaseStyle(info->style);
543 info->style = style2;
544 info->styleChanged = TRUE;
548 /* FIXME this function doesn't get any information about context of the RTF tag, which is very bad,
549 the same tags mean different things in different contexts */
550 void ME_RTFParAttrHook(RTF_Info *info)
552 switch(info->rtfMinor)
554 case rtfParDef: /* restores default paragraph attributes */
555 if (!info->editor->bEmulateVersion10) /* v4.1 */
556 info->borderType = RTFBorderParaLeft;
557 else /* v1.0 - 3.0 */
558 info->borderType = RTFBorderParaTop;
559 info->fmt.dwMask = PFM_ALIGNMENT | PFM_BORDER | PFM_LINESPACING | PFM_TABSTOPS |
560 PFM_OFFSET | PFM_RIGHTINDENT | PFM_SPACEAFTER | PFM_SPACEBEFORE |
561 PFM_STARTINDENT | PFM_RTLPARA | PFM_NUMBERING | PFM_NUMBERINGSTART |
562 PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB;
563 /* TODO: shading */
564 info->fmt.wAlignment = PFA_LEFT;
565 info->fmt.cTabCount = 0;
566 info->fmt.dxOffset = info->fmt.dxStartIndent = info->fmt.dxRightIndent = 0;
567 info->fmt.wBorderWidth = info->fmt.wBorders = 0;
568 info->fmt.wBorderSpace = 0;
569 info->fmt.bLineSpacingRule = 0;
570 info->fmt.dySpaceBefore = info->fmt.dySpaceAfter = 0;
571 info->fmt.dyLineSpacing = 0;
572 info->fmt.wEffects &= ~PFE_RTLPARA;
573 info->fmt.wNumbering = 0;
574 info->fmt.wNumberingStart = 0;
575 info->fmt.wNumberingStyle = 0;
576 info->fmt.wNumberingTab = 0;
578 if (!info->editor->bEmulateVersion10) /* v4.1 */
580 if (info->tableDef && info->tableDef->tableRowStart &&
581 info->tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
583 ME_Cursor cursor;
584 ME_DisplayItem *para;
585 /* We are just after a table row. */
586 RTFFlushOutputBuffer(info);
587 cursor = info->editor->pCursors[0];
588 para = cursor.pPara;
589 if (para == info->tableDef->tableRowStart->member.para.next_para
590 && !cursor.nOffset && !cursor.pRun->member.run.nCharOfs)
592 /* Since the table row end, no text has been inserted, and the \intbl
593 * control word has not be used. We can confirm that we are not in a
594 * table anymore.
596 info->tableDef->tableRowStart = NULL;
597 info->canInheritInTbl = FALSE;
600 } else { /* v1.0 - v3.0 */
601 info->fmt.dwMask |= PFM_TABLE;
602 info->fmt.wEffects &= ~PFE_TABLE;
604 break;
605 case rtfNestLevel:
606 if (!info->editor->bEmulateVersion10) /* v4.1 */
608 while (info->rtfParam > info->nestingLevel) {
609 RTFTable *tableDef = ALLOC_OBJ(RTFTable);
610 ZeroMemory(tableDef, sizeof(RTFTable));
611 tableDef->parent = info->tableDef;
612 info->tableDef = tableDef;
614 RTFFlushOutputBuffer(info);
615 if (tableDef->tableRowStart &&
616 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
618 ME_DisplayItem *para = tableDef->tableRowStart;
619 para = para->member.para.next_para;
620 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
621 tableDef->tableRowStart = para;
622 } else {
623 ME_Cursor cursor;
624 WCHAR endl = '\r';
625 cursor = info->editor->pCursors[0];
626 if (cursor.nOffset || cursor.pRun->member.run.nCharOfs)
627 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
628 tableDef->tableRowStart = ME_InsertTableRowStartFromCursor(info->editor);
631 info->nestingLevel++;
633 info->canInheritInTbl = FALSE;
635 break;
636 case rtfInTable:
638 if (!info->editor->bEmulateVersion10) /* v4.1 */
640 if (info->nestingLevel < 1)
642 RTFTable *tableDef;
643 if (!info->tableDef)
645 info->tableDef = ALLOC_OBJ(RTFTable);
646 ZeroMemory(info->tableDef, sizeof(RTFTable));
648 tableDef = info->tableDef;
649 RTFFlushOutputBuffer(info);
650 if (tableDef->tableRowStart &&
651 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
653 ME_DisplayItem *para = tableDef->tableRowStart;
654 para = para->member.para.next_para;
655 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
656 tableDef->tableRowStart = para;
657 } else {
658 ME_Cursor cursor;
659 WCHAR endl = '\r';
660 cursor = info->editor->pCursors[0];
661 if (cursor.nOffset || cursor.pRun->member.run.nCharOfs)
662 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
663 tableDef->tableRowStart = ME_InsertTableRowStartFromCursor(info->editor);
665 info->nestingLevel = 1;
666 info->canInheritInTbl = TRUE;
668 return;
669 } else { /* v1.0 - v3.0 */
670 info->fmt.dwMask |= PFM_TABLE;
671 info->fmt.wEffects |= PFE_TABLE;
673 break;
675 case rtfFirstIndent:
676 case rtfLeftIndent:
677 if ((info->fmt.dwMask & (PFM_STARTINDENT | PFM_OFFSET)) != (PFM_STARTINDENT | PFM_OFFSET))
679 PARAFORMAT2 fmt;
680 fmt.cbSize = sizeof(fmt);
681 ME_GetSelectionParaFormat(info->editor, &fmt);
682 info->fmt.dwMask |= PFM_STARTINDENT | PFM_OFFSET;
683 info->fmt.dxStartIndent = fmt.dxStartIndent;
684 info->fmt.dxOffset = fmt.dxOffset;
686 if (info->rtfMinor == rtfFirstIndent)
688 info->fmt.dxStartIndent += info->fmt.dxOffset + info->rtfParam;
689 info->fmt.dxOffset = -info->rtfParam;
691 else
692 info->fmt.dxStartIndent = info->rtfParam - info->fmt.dxOffset;
693 break;
694 case rtfRightIndent:
695 info->fmt.dwMask |= PFM_RIGHTINDENT;
696 info->fmt.dxRightIndent = info->rtfParam;
697 break;
698 case rtfQuadLeft:
699 case rtfQuadJust:
700 info->fmt.dwMask |= PFM_ALIGNMENT;
701 info->fmt.wAlignment = PFA_LEFT;
702 break;
703 case rtfQuadRight:
704 info->fmt.dwMask |= PFM_ALIGNMENT;
705 info->fmt.wAlignment = PFA_RIGHT;
706 break;
707 case rtfQuadCenter:
708 info->fmt.dwMask |= PFM_ALIGNMENT;
709 info->fmt.wAlignment = PFA_CENTER;
710 break;
711 case rtfTabPos:
712 if (!(info->fmt.dwMask & PFM_TABSTOPS))
714 PARAFORMAT2 fmt;
715 fmt.cbSize = sizeof(fmt);
716 ME_GetSelectionParaFormat(info->editor, &fmt);
717 memcpy(info->fmt.rgxTabs, fmt.rgxTabs,
718 fmt.cTabCount * sizeof(fmt.rgxTabs[0]));
719 info->fmt.cTabCount = fmt.cTabCount;
720 info->fmt.dwMask |= PFM_TABSTOPS;
722 if (info->fmt.cTabCount < MAX_TAB_STOPS && info->rtfParam < 0x1000000)
723 info->fmt.rgxTabs[info->fmt.cTabCount++] = info->rtfParam;
724 break;
725 case rtfKeep:
726 info->fmt.dwMask |= PFM_KEEP;
727 info->fmt.wEffects |= PFE_KEEP;
728 break;
729 case rtfNoWidowControl:
730 info->fmt.dwMask |= PFM_NOWIDOWCONTROL;
731 info->fmt.wEffects |= PFE_NOWIDOWCONTROL;
732 break;
733 case rtfKeepNext:
734 info->fmt.dwMask |= PFM_KEEPNEXT;
735 info->fmt.wEffects |= PFE_KEEPNEXT;
736 break;
737 case rtfSpaceAfter:
738 info->fmt.dwMask |= PFM_SPACEAFTER;
739 info->fmt.dySpaceAfter = info->rtfParam;
740 break;
741 case rtfSpaceBefore:
742 info->fmt.dwMask |= PFM_SPACEBEFORE;
743 info->fmt.dySpaceBefore = info->rtfParam;
744 break;
745 case rtfSpaceBetween:
746 info->fmt.dwMask |= PFM_LINESPACING;
747 if ((int)info->rtfParam > 0)
749 info->fmt.dyLineSpacing = info->rtfParam;
750 info->fmt.bLineSpacingRule = 3;
752 else
754 info->fmt.dyLineSpacing = info->rtfParam;
755 info->fmt.bLineSpacingRule = 4;
757 break;
758 case rtfSpaceMultiply:
759 info->fmt.dwMask |= PFM_LINESPACING;
760 info->fmt.dyLineSpacing = info->rtfParam * 20;
761 info->fmt.bLineSpacingRule = 5;
762 break;
763 case rtfParBullet:
764 info->fmt.dwMask |= PFM_NUMBERING;
765 info->fmt.wNumbering = PFN_BULLET;
766 break;
767 case rtfParSimple:
768 info->fmt.dwMask |= PFM_NUMBERING;
769 info->fmt.wNumbering = 2; /* FIXME: MSDN says it's not used ?? */
770 break;
771 case rtfBorderLeft:
772 info->borderType = RTFBorderParaLeft;
773 info->fmt.wBorders |= 1;
774 info->fmt.dwMask |= PFM_BORDER;
775 break;
776 case rtfBorderRight:
777 info->borderType = RTFBorderParaRight;
778 info->fmt.wBorders |= 2;
779 info->fmt.dwMask |= PFM_BORDER;
780 break;
781 case rtfBorderTop:
782 info->borderType = RTFBorderParaTop;
783 info->fmt.wBorders |= 4;
784 info->fmt.dwMask |= PFM_BORDER;
785 break;
786 case rtfBorderBottom:
787 info->borderType = RTFBorderParaBottom;
788 info->fmt.wBorders |= 8;
789 info->fmt.dwMask |= PFM_BORDER;
790 break;
791 case rtfBorderSingle:
792 info->fmt.wBorders &= ~0x700;
793 info->fmt.wBorders |= 1 << 8;
794 info->fmt.dwMask |= PFM_BORDER;
795 break;
796 case rtfBorderThick:
797 info->fmt.wBorders &= ~0x700;
798 info->fmt.wBorders |= 2 << 8;
799 info->fmt.dwMask |= PFM_BORDER;
800 break;
801 case rtfBorderShadow:
802 info->fmt.wBorders &= ~0x700;
803 info->fmt.wBorders |= 10 << 8;
804 info->fmt.dwMask |= PFM_BORDER;
805 break;
806 case rtfBorderDouble:
807 info->fmt.wBorders &= ~0x700;
808 info->fmt.wBorders |= 7 << 8;
809 info->fmt.dwMask |= PFM_BORDER;
810 break;
811 case rtfBorderDot:
812 info->fmt.wBorders &= ~0x700;
813 info->fmt.wBorders |= 11 << 8;
814 info->fmt.dwMask |= PFM_BORDER;
815 break;
816 case rtfBorderWidth:
818 int borderSide = info->borderType & RTFBorderSideMask;
819 RTFTable *tableDef = info->tableDef;
820 if ((info->borderType & RTFBorderTypeMask) == RTFBorderTypeCell)
822 RTFBorder *border;
823 if (!tableDef || tableDef->numCellsDefined >= MAX_TABLE_CELLS)
824 break;
825 border = &tableDef->cells[tableDef->numCellsDefined].border[borderSide];
826 border->width = info->rtfParam;
827 break;
829 info->fmt.wBorderWidth = info->rtfParam;
830 info->fmt.dwMask |= PFM_BORDER;
831 break;
833 case rtfBorderSpace:
834 info->fmt.wBorderSpace = info->rtfParam;
835 info->fmt.dwMask |= PFM_BORDER;
836 break;
837 case rtfBorderColor:
839 RTFTable *tableDef = info->tableDef;
840 int borderSide = info->borderType & RTFBorderSideMask;
841 int borderType = info->borderType & RTFBorderTypeMask;
842 switch(borderType)
844 case RTFBorderTypePara:
845 if (!info->editor->bEmulateVersion10) /* v4.1 */
846 break;
847 /* v1.0 - 3.0 treat paragraph and row borders the same. */
848 case RTFBorderTypeRow:
849 if (tableDef) {
850 tableDef->border[borderSide].color = info->rtfParam;
852 break;
853 case RTFBorderTypeCell:
854 if (tableDef && tableDef->numCellsDefined < MAX_TABLE_CELLS) {
855 tableDef->cells[tableDef->numCellsDefined].border[borderSide].color = info->rtfParam;
857 break;
859 break;
861 case rtfRTLPar:
862 info->fmt.dwMask |= PFM_RTLPARA;
863 info->fmt.wEffects |= PFE_RTLPARA;
864 break;
865 case rtfLTRPar:
866 info->fmt.dwMask |= PFM_RTLPARA;
867 info->fmt.wEffects &= ~PFE_RTLPARA;
868 break;
872 void ME_RTFTblAttrHook(RTF_Info *info)
874 switch (info->rtfMinor)
876 case rtfRowDef:
878 if (!info->editor->bEmulateVersion10) /* v4.1 */
879 info->borderType = 0; /* Not sure */
880 else /* v1.0 - 3.0 */
881 info->borderType = RTFBorderRowTop;
882 if (!info->tableDef) {
883 info->tableDef = ME_MakeTableDef(info->editor);
884 } else {
885 ME_InitTableDef(info->editor, info->tableDef);
887 break;
889 case rtfCellPos:
891 int cellNum;
892 if (!info->tableDef)
894 info->tableDef = ME_MakeTableDef(info->editor);
896 cellNum = info->tableDef->numCellsDefined;
897 if (cellNum >= MAX_TABLE_CELLS)
898 break;
899 info->tableDef->cells[cellNum].rightBoundary = info->rtfParam;
900 if (cellNum < MAX_TAB_STOPS) {
901 /* Tab stops were used to store cell positions before v4.1 but v4.1
902 * still seems to set the tabstops without using them. */
903 ME_DisplayItem *para = info->editor->pCursors[0].pPara;
904 PARAFORMAT2 *pFmt = &para->member.para.fmt;
905 pFmt->rgxTabs[cellNum] &= ~0x00FFFFFF;
906 pFmt->rgxTabs[cellNum] |= 0x00FFFFFF & info->rtfParam;
908 info->tableDef->numCellsDefined++;
909 break;
911 case rtfRowBordTop:
912 info->borderType = RTFBorderRowTop;
913 break;
914 case rtfRowBordLeft:
915 info->borderType = RTFBorderRowLeft;
916 break;
917 case rtfRowBordBottom:
918 info->borderType = RTFBorderRowBottom;
919 break;
920 case rtfRowBordRight:
921 info->borderType = RTFBorderRowRight;
922 break;
923 case rtfCellBordTop:
924 info->borderType = RTFBorderCellTop;
925 break;
926 case rtfCellBordLeft:
927 info->borderType = RTFBorderCellLeft;
928 break;
929 case rtfCellBordBottom:
930 info->borderType = RTFBorderCellBottom;
931 break;
932 case rtfCellBordRight:
933 info->borderType = RTFBorderCellRight;
934 break;
935 case rtfRowGapH:
936 if (info->tableDef)
937 info->tableDef->gapH = info->rtfParam;
938 break;
939 case rtfRowLeftEdge:
940 if (info->tableDef)
941 info->tableDef->leftEdge = info->rtfParam;
942 break;
946 void ME_RTFSpecialCharHook(RTF_Info *info)
948 RTFTable *tableDef = info->tableDef;
949 switch (info->rtfMinor)
951 case rtfNestCell:
952 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
953 break;
954 /* else fall through since v4.1 treats rtfNestCell and rtfCell the same */
955 case rtfCell:
956 if (!tableDef)
957 break;
958 RTFFlushOutputBuffer(info);
959 if (!info->editor->bEmulateVersion10) { /* v4.1 */
960 if (tableDef->tableRowStart)
962 if (!info->nestingLevel &&
963 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
965 ME_DisplayItem *para = tableDef->tableRowStart;
966 para = para->member.para.next_para;
967 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
968 tableDef->tableRowStart = para;
969 info->nestingLevel = 1;
971 ME_InsertTableCellFromCursor(info->editor);
973 } else { /* v1.0 - v3.0 */
974 ME_DisplayItem *para = info->editor->pCursors[0].pPara;
975 PARAFORMAT2 *pFmt = &para->member.para.fmt;
976 if (pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE &&
977 tableDef->numCellsInserted < tableDef->numCellsDefined)
979 WCHAR tab = '\t';
980 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
981 tableDef->numCellsInserted++;
984 break;
985 case rtfNestRow:
986 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
987 break;
988 /* else fall through since v4.1 treats rtfNestRow and rtfRow the same */
989 case rtfRow:
991 ME_DisplayItem *para, *cell, *run;
992 int i;
994 if (!tableDef)
995 break;
996 RTFFlushOutputBuffer(info);
997 if (!info->editor->bEmulateVersion10) { /* v4.1 */
998 if (!tableDef->tableRowStart)
999 break;
1000 if (!info->nestingLevel &&
1001 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
1003 para = tableDef->tableRowStart;
1004 para = para->member.para.next_para;
1005 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
1006 tableDef->tableRowStart = para;
1007 info->nestingLevel++;
1009 para = tableDef->tableRowStart;
1010 cell = ME_FindItemFwd(para, diCell);
1011 assert(cell && !cell->member.cell.prev_cell);
1012 if (tableDef->numCellsDefined < 1)
1014 /* 2000 twips appears to be the cell size that native richedit uses
1015 * when no cell sizes are specified. */
1016 const int defaultCellSize = 2000;
1017 int nRightBoundary = defaultCellSize;
1018 cell->member.cell.nRightBoundary = nRightBoundary;
1019 while (cell->member.cell.next_cell) {
1020 cell = cell->member.cell.next_cell;
1021 nRightBoundary += defaultCellSize;
1022 cell->member.cell.nRightBoundary = nRightBoundary;
1024 para = ME_InsertTableCellFromCursor(info->editor);
1025 cell = para->member.para.pCell;
1026 cell->member.cell.nRightBoundary = nRightBoundary;
1027 } else {
1028 for (i = 0; i < tableDef->numCellsDefined; i++)
1030 RTFCell *cellDef = &tableDef->cells[i];
1031 cell->member.cell.nRightBoundary = cellDef->rightBoundary;
1032 ME_ApplyBorderProperties(info, &cell->member.cell.border,
1033 cellDef->border);
1034 cell = cell->member.cell.next_cell;
1035 if (!cell)
1037 para = ME_InsertTableCellFromCursor(info->editor);
1038 cell = para->member.para.pCell;
1041 /* Cell for table row delimiter is empty */
1042 cell->member.cell.nRightBoundary = tableDef->cells[i-1].rightBoundary;
1045 run = ME_FindItemFwd(cell, diRun);
1046 if (info->editor->pCursors[0].pRun != run ||
1047 info->editor->pCursors[0].nOffset)
1049 int nOfs, nChars;
1050 /* Delete inserted cells that aren't defined. */
1051 info->editor->pCursors[1].pRun = run;
1052 info->editor->pCursors[1].pPara = ME_GetParagraph(run);
1053 info->editor->pCursors[1].nOffset = 0;
1054 nOfs = ME_GetCursorOfs(&info->editor->pCursors[1]);
1055 nChars = ME_GetCursorOfs(&info->editor->pCursors[0]) - nOfs;
1056 ME_InternalDeleteText(info->editor, &info->editor->pCursors[1],
1057 nChars, TRUE);
1060 para = ME_InsertTableRowEndFromCursor(info->editor);
1061 para->member.para.fmt.dxOffset = abs(info->tableDef->gapH);
1062 para->member.para.fmt.dxStartIndent = info->tableDef->leftEdge;
1063 ME_ApplyBorderProperties(info, &para->member.para.border,
1064 tableDef->border);
1065 info->nestingLevel--;
1066 if (!info->nestingLevel)
1068 if (info->canInheritInTbl) {
1069 tableDef->tableRowStart = para;
1070 } else {
1071 while (info->tableDef) {
1072 tableDef = info->tableDef;
1073 info->tableDef = tableDef->parent;
1074 heap_free(tableDef);
1077 } else {
1078 info->tableDef = tableDef->parent;
1079 heap_free(tableDef);
1081 } else { /* v1.0 - v3.0 */
1082 WCHAR endl = '\r';
1083 ME_DisplayItem *para = info->editor->pCursors[0].pPara;
1084 PARAFORMAT2 *pFmt = &para->member.para.fmt;
1085 pFmt->dxOffset = info->tableDef->gapH;
1086 pFmt->dxStartIndent = info->tableDef->leftEdge;
1088 ME_ApplyBorderProperties(info, &para->member.para.border,
1089 tableDef->border);
1090 while (tableDef->numCellsInserted < tableDef->numCellsDefined)
1092 WCHAR tab = '\t';
1093 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
1094 tableDef->numCellsInserted++;
1096 pFmt->cTabCount = min(tableDef->numCellsDefined, MAX_TAB_STOPS);
1097 if (!tableDef->numCellsDefined)
1098 pFmt->wEffects &= ~PFE_TABLE;
1099 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
1100 tableDef->numCellsInserted = 0;
1102 break;
1104 case rtfTab:
1105 case rtfPar:
1106 if (info->editor->bEmulateVersion10) { /* v1.0 - 3.0 */
1107 ME_DisplayItem *para;
1108 PARAFORMAT2 *pFmt;
1109 RTFFlushOutputBuffer(info);
1110 para = info->editor->pCursors[0].pPara;
1111 pFmt = &para->member.para.fmt;
1112 if (pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE)
1114 /* rtfPar is treated like a space within a table. */
1115 info->rtfClass = rtfText;
1116 info->rtfMajor = ' ';
1118 else if (info->rtfMinor == rtfPar && tableDef)
1119 tableDef->numCellsInserted = 0;
1121 break;
1125 static BOOL ME_RTFInsertOleObject(RTF_Info *info, HENHMETAFILE hemf, HBITMAP hbmp,
1126 const SIZEL* sz)
1128 LPOLEOBJECT lpObject = NULL;
1129 LPSTORAGE lpStorage = NULL;
1130 LPOLECLIENTSITE lpClientSite = NULL;
1131 LPDATAOBJECT lpDataObject = NULL;
1132 LPOLECACHE lpOleCache = NULL;
1133 STGMEDIUM stgm;
1134 FORMATETC fm;
1135 CLSID clsid;
1136 BOOL ret = FALSE;
1137 DWORD conn;
1139 if (hemf)
1141 stgm.tymed = TYMED_ENHMF;
1142 stgm.u.hEnhMetaFile = hemf;
1143 fm.cfFormat = CF_ENHMETAFILE;
1145 else if (hbmp)
1147 stgm.tymed = TYMED_GDI;
1148 stgm.u.hBitmap = hbmp;
1149 fm.cfFormat = CF_BITMAP;
1151 stgm.pUnkForRelease = NULL;
1153 fm.ptd = NULL;
1154 fm.dwAspect = DVASPECT_CONTENT;
1155 fm.lindex = -1;
1156 fm.tymed = stgm.tymed;
1158 if (!info->lpRichEditOle)
1160 CreateIRichEditOle(NULL, info->editor, (VOID**)&info->lpRichEditOle);
1163 if (OleCreateDefaultHandler(&CLSID_NULL, NULL, &IID_IOleObject, (void**)&lpObject) == S_OK &&
1164 IRichEditOle_GetClientSite(info->lpRichEditOle, &lpClientSite) == S_OK &&
1165 IOleObject_SetClientSite(lpObject, lpClientSite) == S_OK &&
1166 IOleObject_GetUserClassID(lpObject, &clsid) == S_OK &&
1167 IOleObject_QueryInterface(lpObject, &IID_IOleCache, (void**)&lpOleCache) == S_OK &&
1168 IOleCache_Cache(lpOleCache, &fm, 0, &conn) == S_OK &&
1169 IOleObject_QueryInterface(lpObject, &IID_IDataObject, (void**)&lpDataObject) == S_OK &&
1170 IDataObject_SetData(lpDataObject, &fm, &stgm, TRUE) == S_OK)
1172 REOBJECT reobject;
1174 reobject.cbStruct = sizeof(reobject);
1175 reobject.cp = REO_CP_SELECTION;
1176 reobject.clsid = clsid;
1177 reobject.poleobj = lpObject;
1178 reobject.pstg = lpStorage;
1179 reobject.polesite = lpClientSite;
1180 /* convert from twips to .01 mm */
1181 reobject.sizel.cx = MulDiv(sz->cx, 254, 144);
1182 reobject.sizel.cy = MulDiv(sz->cy, 254, 144);
1183 reobject.dvaspect = DVASPECT_CONTENT;
1184 reobject.dwFlags = 0; /* FIXME */
1185 reobject.dwUser = 0;
1187 ME_InsertOLEFromCursor(info->editor, &reobject, 0);
1188 ret = TRUE;
1191 if (lpObject) IOleObject_Release(lpObject);
1192 if (lpClientSite) IOleClientSite_Release(lpClientSite);
1193 if (lpStorage) IStorage_Release(lpStorage);
1194 if (lpDataObject) IDataObject_Release(lpDataObject);
1195 if (lpOleCache) IOleCache_Release(lpOleCache);
1197 return ret;
1200 static void ME_RTFReadShpPictGroup( RTF_Info *info )
1202 int level = 1;
1204 for (;;)
1206 RTFGetToken (info);
1208 if (info->rtfClass == rtfEOF) return;
1209 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1211 if (--level == 0) break;
1213 else if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1215 level++;
1217 else
1219 RTFRouteToken( info );
1220 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1221 level--;
1225 RTFRouteToken( info ); /* feed "}" back to router */
1226 return;
1229 static DWORD read_hex_data( RTF_Info *info, BYTE **out )
1231 DWORD read = 0, size = 1024;
1232 BYTE *buf, val;
1233 BOOL flip;
1235 *out = NULL;
1237 if (info->rtfClass != rtfText)
1239 ERR("Called with incorrect token\n");
1240 return 0;
1243 buf = HeapAlloc( GetProcessHeap(), 0, size );
1244 if (!buf) return 0;
1246 val = info->rtfMajor;
1247 for (flip = TRUE;; flip = !flip)
1249 RTFGetToken( info );
1250 if (info->rtfClass == rtfEOF)
1252 HeapFree( GetProcessHeap(), 0, buf );
1253 return 0;
1255 if (info->rtfClass != rtfText) break;
1256 if (flip)
1258 if (read >= size)
1260 size *= 2;
1261 buf = HeapReAlloc( GetProcessHeap(), 0, buf, size );
1262 if (!buf) return 0;
1264 buf[read++] = RTFCharToHex(val) * 16 + RTFCharToHex(info->rtfMajor);
1266 else
1267 val = info->rtfMajor;
1269 if (flip) FIXME("wrong hex string\n");
1271 *out = buf;
1272 return read;
1275 static void ME_RTFReadPictGroup(RTF_Info *info)
1277 SIZEL sz;
1278 BYTE *buffer = NULL;
1279 DWORD size = 0;
1280 METAFILEPICT mfp;
1281 HENHMETAFILE hemf;
1282 HBITMAP hbmp;
1283 enum gfxkind {gfx_unknown = 0, gfx_enhmetafile, gfx_metafile, gfx_dib} gfx = gfx_unknown;
1284 int level = 1;
1286 mfp.mm = MM_TEXT;
1287 sz.cx = sz.cy = 0;
1289 for (;;)
1291 RTFGetToken( info );
1293 if (info->rtfClass == rtfText)
1295 if (level == 1)
1297 if (!buffer)
1298 size = read_hex_data( info, &buffer );
1300 else
1302 RTFSkipGroup( info );
1304 } /* We potentially have a new token so fall through. */
1306 if (info->rtfClass == rtfEOF) return;
1308 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1310 if (--level == 0) break;
1311 continue;
1313 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1315 level++;
1316 continue;
1318 if (!RTFCheckCM( info, rtfControl, rtfPictAttr ))
1320 RTFRouteToken( info );
1321 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1322 level--;
1323 continue;
1326 if (RTFCheckMM( info, rtfPictAttr, rtfWinMetafile ))
1328 mfp.mm = info->rtfParam;
1329 gfx = gfx_metafile;
1331 else if (RTFCheckMM( info, rtfPictAttr, rtfDevIndBitmap ))
1333 if (info->rtfParam != 0) FIXME("dibitmap should be 0 (%d)\n", info->rtfParam);
1334 gfx = gfx_dib;
1336 else if (RTFCheckMM( info, rtfPictAttr, rtfEmfBlip ))
1337 gfx = gfx_enhmetafile;
1338 else if (RTFCheckMM( info, rtfPictAttr, rtfPicWid ))
1339 mfp.xExt = info->rtfParam;
1340 else if (RTFCheckMM( info, rtfPictAttr, rtfPicHt ))
1341 mfp.yExt = info->rtfParam;
1342 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalWid ))
1343 sz.cx = info->rtfParam;
1344 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalHt ))
1345 sz.cy = info->rtfParam;
1346 else
1347 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1350 if (buffer)
1352 switch (gfx)
1354 case gfx_enhmetafile:
1355 if ((hemf = SetEnhMetaFileBits( size, buffer )))
1356 ME_RTFInsertOleObject( info, hemf, NULL, &sz );
1357 break;
1358 case gfx_metafile:
1359 if ((hemf = SetWinMetaFileBits( size, buffer, NULL, &mfp )))
1360 ME_RTFInsertOleObject( info, hemf, NULL, &sz );
1361 break;
1362 case gfx_dib:
1364 BITMAPINFO *bi = (BITMAPINFO*)buffer;
1365 HDC hdc = GetDC(0);
1366 unsigned nc = bi->bmiHeader.biClrUsed;
1368 /* not quite right, especially for bitfields type of compression */
1369 if (!nc && bi->bmiHeader.biBitCount <= 8)
1370 nc = 1 << bi->bmiHeader.biBitCount;
1371 if ((hbmp = CreateDIBitmap( hdc, &bi->bmiHeader,
1372 CBM_INIT, (char*)(bi + 1) + nc * sizeof(RGBQUAD),
1373 bi, DIB_RGB_COLORS)) )
1374 ME_RTFInsertOleObject( info, NULL, hbmp, &sz );
1375 ReleaseDC( 0, hdc );
1376 break;
1378 default:
1379 break;
1382 HeapFree( GetProcessHeap(), 0, buffer );
1383 RTFRouteToken( info ); /* feed "}" back to router */
1384 return;
1387 /* for now, lookup the \result part and use it, whatever the object */
1388 static void ME_RTFReadObjectGroup(RTF_Info *info)
1390 for (;;)
1392 RTFGetToken (info);
1393 if (info->rtfClass == rtfEOF)
1394 return;
1395 if (RTFCheckCM(info, rtfGroup, rtfEndGroup))
1396 break;
1397 if (RTFCheckCM(info, rtfGroup, rtfBeginGroup))
1399 RTFGetToken (info);
1400 if (info->rtfClass == rtfEOF)
1401 return;
1402 if (RTFCheckCMM(info, rtfControl, rtfDestination, rtfObjResult))
1404 int level = 1;
1406 while (RTFGetToken (info) != rtfEOF)
1408 if (info->rtfClass == rtfGroup)
1410 if (info->rtfMajor == rtfBeginGroup) level++;
1411 else if (info->rtfMajor == rtfEndGroup && --level < 0) break;
1413 RTFRouteToken(info);
1416 else RTFSkipGroup(info);
1417 continue;
1419 if (!RTFCheckCM (info, rtfControl, rtfObjAttr))
1421 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1422 return;
1425 RTFRouteToken(info); /* feed "}" back to router */
1428 static void ME_RTFReadParnumGroup( RTF_Info *info )
1430 int level = 1, type = -1;
1431 WORD indent = 0, start = 1;
1432 WCHAR txt_before = 0, txt_after = 0;
1434 for (;;)
1436 RTFGetToken( info );
1438 if (RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextBefore ) ||
1439 RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextAfter ))
1441 int loc = info->rtfMinor;
1443 RTFGetToken( info );
1444 if (info->rtfClass == rtfText)
1446 if (loc == rtfParNumTextBefore)
1447 txt_before = info->rtfMajor;
1448 else
1449 txt_after = info->rtfMajor;
1450 continue;
1452 /* falling through to catch EOFs and group level changes */
1455 if (info->rtfClass == rtfEOF)
1456 return;
1458 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1460 if (--level == 0) break;
1461 continue;
1464 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1466 level++;
1467 continue;
1470 /* Ignore non para-attr */
1471 if (!RTFCheckCM( info, rtfControl, rtfParAttr ))
1472 continue;
1474 switch (info->rtfMinor)
1476 case rtfParLevel: /* Para level is ignored */
1477 case rtfParSimple:
1478 break;
1479 case rtfParBullet:
1480 type = PFN_BULLET;
1481 break;
1483 case rtfParNumDecimal:
1484 type = PFN_ARABIC;
1485 break;
1486 case rtfParNumULetter:
1487 type = PFN_UCLETTER;
1488 break;
1489 case rtfParNumURoman:
1490 type = PFN_UCROMAN;
1491 break;
1492 case rtfParNumLLetter:
1493 type = PFN_LCLETTER;
1494 break;
1495 case rtfParNumLRoman:
1496 type = PFN_LCROMAN;
1497 break;
1499 case rtfParNumIndent:
1500 indent = info->rtfParam;
1501 break;
1502 case rtfParNumStartAt:
1503 start = info->rtfParam;
1504 break;
1508 if (type != -1)
1510 info->fmt.dwMask |= (PFM_NUMBERING | PFM_NUMBERINGSTART | PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB);
1511 info->fmt.wNumbering = type;
1512 info->fmt.wNumberingStart = start;
1513 info->fmt.wNumberingStyle = PFNS_PAREN;
1514 if (type != PFN_BULLET)
1516 if (txt_before == 0 && txt_after == 0)
1517 info->fmt.wNumberingStyle = PFNS_PLAIN;
1518 else if (txt_after == '.')
1519 info->fmt.wNumberingStyle = PFNS_PERIOD;
1520 else if (txt_before == '(' && txt_after == ')')
1521 info->fmt.wNumberingStyle = PFNS_PARENS;
1523 info->fmt.wNumberingTab = indent;
1526 TRACE("type %d indent %d start %d txt before %04x txt after %04x\n",
1527 type, indent, start, txt_before, txt_after);
1529 RTFRouteToken( info ); /* feed "}" back to router */
1532 static void ME_RTFReadHook(RTF_Info *info)
1534 switch(info->rtfClass)
1536 case rtfGroup:
1537 switch(info->rtfMajor)
1539 case rtfBeginGroup:
1540 if (info->stackTop < maxStack) {
1541 info->stack[info->stackTop].style = info->style;
1542 ME_AddRefStyle(info->style);
1543 info->stack[info->stackTop].codePage = info->codePage;
1544 info->stack[info->stackTop].unicodeLength = info->unicodeLength;
1546 info->stackTop++;
1547 info->styleChanged = FALSE;
1548 break;
1549 case rtfEndGroup:
1551 RTFFlushOutputBuffer(info);
1552 info->stackTop--;
1553 if (info->stackTop <= 0)
1554 info->rtfClass = rtfEOF;
1555 if (info->stackTop < 0)
1556 return;
1558 ME_ReleaseStyle(info->style);
1559 info->style = info->stack[info->stackTop].style;
1560 info->codePage = info->stack[info->stackTop].codePage;
1561 info->unicodeLength = info->stack[info->stackTop].unicodeLength;
1562 break;
1565 break;
1569 void
1570 ME_StreamInFill(ME_InStream *stream)
1572 stream->editstream->dwError = stream->editstream->pfnCallback(stream->editstream->dwCookie,
1573 (BYTE *)stream->buffer,
1574 sizeof(stream->buffer),
1575 (LONG *)&stream->dwSize);
1576 stream->dwUsed = 0;
1579 static LRESULT ME_StreamIn(ME_TextEditor *editor, DWORD format, EDITSTREAM *stream, BOOL stripLastCR)
1581 RTF_Info parser;
1582 ME_Style *style;
1583 int from, to, nUndoMode;
1584 int nEventMask = editor->nEventMask;
1585 ME_InStream inStream;
1586 BOOL invalidRTF = FALSE;
1587 ME_Cursor *selStart, *selEnd;
1588 LRESULT num_read = 0; /* bytes read for SF_TEXT, non-control chars inserted for SF_RTF */
1590 TRACE("stream==%p editor==%p format==0x%X\n", stream, editor, format);
1591 editor->nEventMask = 0;
1593 ME_GetSelectionOfs(editor, &from, &to);
1594 if (format & SFF_SELECTION && editor->mode & TM_RICHTEXT)
1596 ME_GetSelection(editor, &selStart, &selEnd);
1597 style = ME_GetSelectionInsertStyle(editor);
1599 ME_InternalDeleteText(editor, selStart, to - from, FALSE);
1601 /* Don't insert text at the end of the table row */
1602 if (!editor->bEmulateVersion10) { /* v4.1 */
1603 ME_DisplayItem *para = editor->pCursors->pPara;
1604 if (para->member.para.nFlags & MEPF_ROWEND)
1606 para = para->member.para.next_para;
1607 editor->pCursors[0].pPara = para;
1608 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
1609 editor->pCursors[0].nOffset = 0;
1611 if (para->member.para.nFlags & MEPF_ROWSTART)
1613 para = para->member.para.next_para;
1614 editor->pCursors[0].pPara = para;
1615 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
1616 editor->pCursors[0].nOffset = 0;
1618 editor->pCursors[1] = editor->pCursors[0];
1619 } else { /* v1.0 - 3.0 */
1620 if (editor->pCursors[0].pRun->member.run.nFlags & MERF_ENDPARA &&
1621 ME_IsInTable(editor->pCursors[0].pRun))
1622 return 0;
1624 } else {
1625 style = editor->pBuffer->pDefaultStyle;
1626 ME_AddRefStyle(style);
1627 ME_SetSelection(editor, 0, 0);
1628 ME_InternalDeleteText(editor, &editor->pCursors[1],
1629 ME_GetTextLength(editor), FALSE);
1630 from = to = 0;
1631 ME_ClearTempStyle(editor);
1632 ME_SetDefaultParaFormat(editor, &editor->pCursors[0].pPara->member.para.fmt);
1636 /* Back up undo mode to a local variable */
1637 nUndoMode = editor->nUndoMode;
1639 /* Only create an undo if SFF_SELECTION is set */
1640 if (!(format & SFF_SELECTION))
1641 editor->nUndoMode = umIgnore;
1643 inStream.editstream = stream;
1644 inStream.editstream->dwError = 0;
1645 inStream.dwSize = 0;
1646 inStream.dwUsed = 0;
1648 if (format & SF_RTF)
1650 /* Check if it's really RTF, and if it is not, use plain text */
1651 ME_StreamInFill(&inStream);
1652 if (!inStream.editstream->dwError)
1654 if ((!editor->bEmulateVersion10 && strncmp(inStream.buffer, "{\\rtf", 5) && strncmp(inStream.buffer, "{\\urtf", 6))
1655 || (editor->bEmulateVersion10 && *inStream.buffer != '{'))
1657 invalidRTF = TRUE;
1658 inStream.editstream->dwError = -16;
1663 if (!invalidRTF && !inStream.editstream->dwError)
1665 ME_Cursor start;
1666 from = ME_GetCursorOfs(&editor->pCursors[0]);
1667 if (format & SF_RTF) {
1669 /* setup the RTF parser */
1670 memset(&parser, 0, sizeof parser);
1671 RTFSetEditStream(&parser, &inStream);
1672 parser.rtfFormat = format&(SF_TEXT|SF_RTF);
1673 parser.editor = editor;
1674 parser.style = style;
1675 WriterInit(&parser);
1676 RTFInit(&parser);
1677 RTFSetReadHook(&parser, ME_RTFReadHook);
1678 RTFSetDestinationCallback(&parser, rtfShpPict, ME_RTFReadShpPictGroup);
1679 RTFSetDestinationCallback(&parser, rtfPict, ME_RTFReadPictGroup);
1680 RTFSetDestinationCallback(&parser, rtfObject, ME_RTFReadObjectGroup);
1681 RTFSetDestinationCallback(&parser, rtfParNumbering, ME_RTFReadParnumGroup);
1682 if (!parser.editor->bEmulateVersion10) /* v4.1 */
1684 RTFSetDestinationCallback(&parser, rtfNoNestTables, RTFSkipGroup);
1685 RTFSetDestinationCallback(&parser, rtfNestTableProps, RTFReadGroup);
1687 BeginFile(&parser);
1689 /* do the parsing */
1690 RTFRead(&parser);
1691 RTFFlushOutputBuffer(&parser);
1692 if (!editor->bEmulateVersion10) { /* v4.1 */
1693 if (parser.tableDef && parser.tableDef->tableRowStart &&
1694 (parser.nestingLevel > 0 || parser.canInheritInTbl))
1696 /* Delete any incomplete table row at the end of the rich text. */
1697 int nOfs, nChars;
1698 ME_DisplayItem *para;
1700 parser.rtfMinor = rtfRow;
1701 /* Complete the table row before deleting it.
1702 * By doing it this way we will have the current paragraph format set
1703 * properly to reflect that is not in the complete table, and undo items
1704 * will be added for this change to the current paragraph format. */
1705 if (parser.nestingLevel > 0)
1707 while (parser.nestingLevel > 1)
1708 ME_RTFSpecialCharHook(&parser); /* Decrements nestingLevel */
1709 para = parser.tableDef->tableRowStart;
1710 ME_RTFSpecialCharHook(&parser);
1711 } else {
1712 para = parser.tableDef->tableRowStart;
1713 ME_RTFSpecialCharHook(&parser);
1714 assert(para->member.para.nFlags & MEPF_ROWEND);
1715 para = para->member.para.next_para;
1718 editor->pCursors[1].pPara = para;
1719 editor->pCursors[1].pRun = ME_FindItemFwd(para, diRun);
1720 editor->pCursors[1].nOffset = 0;
1721 nOfs = ME_GetCursorOfs(&editor->pCursors[1]);
1722 nChars = ME_GetCursorOfs(&editor->pCursors[0]) - nOfs;
1723 ME_InternalDeleteText(editor, &editor->pCursors[1], nChars, TRUE);
1724 if (parser.tableDef)
1725 parser.tableDef->tableRowStart = NULL;
1728 ME_CheckTablesForCorruption(editor);
1729 RTFDestroy(&parser);
1730 if (parser.lpRichEditOle)
1731 IRichEditOle_Release(parser.lpRichEditOle);
1733 if (parser.stackTop > 0)
1735 while (--parser.stackTop >= 0)
1737 ME_ReleaseStyle(parser.style);
1738 parser.style = parser.stack[parser.stackTop].style;
1740 if (!inStream.editstream->dwError)
1741 inStream.editstream->dwError = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
1744 /* Remove last line break, as mandated by tests. This is not affected by
1745 CR/LF counters, since RTF streaming presents only \para tokens, which
1746 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1748 if (stripLastCR && !(format & SFF_SELECTION)) {
1749 int newto;
1750 ME_GetSelection(editor, &selStart, &selEnd);
1751 newto = ME_GetCursorOfs(selEnd);
1752 if (newto > to + (editor->bEmulateVersion10 ? 1 : 0)) {
1753 WCHAR lastchar[3] = {'\0', '\0'};
1754 int linebreakSize = editor->bEmulateVersion10 ? 2 : 1;
1755 ME_Cursor linebreakCursor = *selEnd, lastcharCursor = *selEnd;
1756 CHARFORMAT2W cf;
1758 /* Set the final eop to the char fmt of the last char */
1759 cf.cbSize = sizeof(cf);
1760 cf.dwMask = CFM_ALL2;
1761 ME_MoveCursorChars(editor, &lastcharCursor, -1, FALSE);
1762 ME_GetCharFormat(editor, &lastcharCursor, &linebreakCursor, &cf);
1763 ME_SetSelection(editor, newto, -1);
1764 ME_SetSelectionCharFormat(editor, &cf);
1765 ME_SetSelection(editor, newto, newto);
1767 ME_MoveCursorChars(editor, &linebreakCursor, -linebreakSize, FALSE);
1768 ME_GetTextW(editor, lastchar, 2, &linebreakCursor, linebreakSize, FALSE, FALSE);
1769 if (lastchar[0] == '\r' && (lastchar[1] == '\n' || lastchar[1] == '\0')) {
1770 ME_InternalDeleteText(editor, &linebreakCursor, linebreakSize, FALSE);
1774 to = ME_GetCursorOfs(&editor->pCursors[0]);
1775 num_read = to - from;
1777 style = parser.style;
1779 else if (format & SF_TEXT)
1781 num_read = ME_StreamInText(editor, format, &inStream, style);
1782 to = ME_GetCursorOfs(&editor->pCursors[0]);
1784 else
1785 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1786 /* put the cursor at the top */
1787 if (!(format & SFF_SELECTION))
1788 ME_SetSelection(editor, 0, 0);
1789 ME_CursorFromCharOfs(editor, from, &start);
1790 ME_UpdateLinkAttribute(editor, &start, to - from);
1793 /* Restore saved undo mode */
1794 editor->nUndoMode = nUndoMode;
1796 /* even if we didn't add an undo, we need to commit anything on the stack */
1797 ME_CommitUndo(editor);
1799 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1800 if (!(format & SFF_SELECTION))
1801 ME_EmptyUndoStack(editor);
1803 ME_ReleaseStyle(style);
1804 editor->nEventMask = nEventMask;
1805 ME_UpdateRepaint(editor, FALSE);
1806 if (!(format & SFF_SELECTION)) {
1807 ME_ClearTempStyle(editor);
1809 ITextHost_TxShowCaret(editor->texthost, FALSE);
1810 ME_MoveCaret(editor);
1811 ITextHost_TxShowCaret(editor->texthost, TRUE);
1812 ME_SendSelChange(editor);
1813 ME_SendRequestResize(editor, FALSE);
1815 return num_read;
1819 typedef struct tagME_RTFStringStreamStruct
1821 char *string;
1822 int pos;
1823 int length;
1824 } ME_RTFStringStreamStruct;
1826 static DWORD CALLBACK ME_ReadFromRTFString(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
1828 ME_RTFStringStreamStruct *pStruct = (ME_RTFStringStreamStruct *)dwCookie;
1829 int count;
1831 count = min(cb, pStruct->length - pStruct->pos);
1832 memmove(lpBuff, pStruct->string + pStruct->pos, count);
1833 pStruct->pos += count;
1834 *pcb = count;
1835 return 0;
1838 static void
1839 ME_StreamInRTFString(ME_TextEditor *editor, BOOL selection, char *string)
1841 EDITSTREAM es;
1842 ME_RTFStringStreamStruct data;
1844 data.string = string;
1845 data.length = strlen(string);
1846 data.pos = 0;
1847 es.dwCookie = (DWORD_PTR)&data;
1848 es.pfnCallback = ME_ReadFromRTFString;
1849 ME_StreamIn(editor, SF_RTF | (selection ? SFF_SELECTION : 0), &es, TRUE);
1853 static int
1854 ME_FindText(ME_TextEditor *editor, DWORD flags, const CHARRANGE *chrg, const WCHAR *text, CHARRANGE *chrgText)
1856 const int nLen = lstrlenW(text);
1857 const int nTextLen = ME_GetTextLength(editor);
1858 int nMin, nMax;
1859 ME_Cursor cursor;
1860 WCHAR wLastChar = ' ';
1862 TRACE("flags==0x%08x, chrg->cpMin==%d, chrg->cpMax==%d text==%s\n",
1863 flags, chrg->cpMin, chrg->cpMax, debugstr_w(text));
1865 if (flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD))
1866 FIXME("Flags 0x%08x not implemented\n",
1867 flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD));
1869 nMin = chrg->cpMin;
1870 if (chrg->cpMax == -1)
1871 nMax = nTextLen;
1872 else
1873 nMax = chrg->cpMax > nTextLen ? nTextLen : chrg->cpMax;
1875 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1876 if (editor->bEmulateVersion10 && nMax == nTextLen)
1878 flags |= FR_DOWN;
1881 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1882 if (editor->bEmulateVersion10 && nMax < nMin)
1884 if (chrgText)
1886 chrgText->cpMin = -1;
1887 chrgText->cpMax = -1;
1889 return -1;
1892 /* when searching up, if cpMin < cpMax, then instead of searching
1893 * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on
1894 * [cpMax, cpMin]. The exception is when cpMax is -1, in which
1895 * case, it is always bigger than cpMin.
1897 if (!editor->bEmulateVersion10 && !(flags & FR_DOWN))
1899 int nSwap = nMax;
1901 nMax = nMin > nTextLen ? nTextLen : nMin;
1902 if (nMin < nSwap || chrg->cpMax == -1)
1903 nMin = 0;
1904 else
1905 nMin = nSwap;
1908 if (!nLen || nMin < 0 || nMax < 0 || nMax < nMin)
1910 if (chrgText)
1911 chrgText->cpMin = chrgText->cpMax = -1;
1912 return -1;
1915 if (flags & FR_DOWN) /* Forward search */
1917 /* If possible, find the character before where the search starts */
1918 if ((flags & FR_WHOLEWORD) && nMin)
1920 ME_CursorFromCharOfs(editor, nMin - 1, &cursor);
1921 wLastChar = *get_text( &cursor.pRun->member.run, cursor.nOffset );
1922 ME_MoveCursorChars(editor, &cursor, 1, FALSE);
1923 } else {
1924 ME_CursorFromCharOfs(editor, nMin, &cursor);
1927 while (cursor.pRun && ME_GetCursorOfs(&cursor) + nLen <= nMax)
1929 ME_DisplayItem *pCurItem = cursor.pRun;
1930 int nCurStart = cursor.nOffset;
1931 int nMatched = 0;
1933 while (pCurItem && ME_CharCompare( *get_text( &pCurItem->member.run, nCurStart + nMatched ), text[nMatched], (flags & FR_MATCHCASE)))
1935 if ((flags & FR_WHOLEWORD) && isalnumW(wLastChar))
1936 break;
1938 nMatched++;
1939 if (nMatched == nLen)
1941 ME_DisplayItem *pNextItem = pCurItem;
1942 int nNextStart = nCurStart;
1943 WCHAR wNextChar;
1945 /* Check to see if next character is a whitespace */
1946 if (flags & FR_WHOLEWORD)
1948 if (nCurStart + nMatched == pCurItem->member.run.len)
1950 pNextItem = ME_FindItemFwd(pCurItem, diRun);
1951 nNextStart = -nMatched;
1954 if (pNextItem)
1955 wNextChar = *get_text( &pNextItem->member.run, nNextStart + nMatched );
1956 else
1957 wNextChar = ' ';
1959 if (isalnumW(wNextChar))
1960 break;
1963 cursor.nOffset += cursor.pPara->member.para.nCharOfs + cursor.pRun->member.run.nCharOfs;
1964 if (chrgText)
1966 chrgText->cpMin = cursor.nOffset;
1967 chrgText->cpMax = cursor.nOffset + nLen;
1969 TRACE("found at %d-%d\n", cursor.nOffset, cursor.nOffset + nLen);
1970 return cursor.nOffset;
1972 if (nCurStart + nMatched == pCurItem->member.run.len)
1974 pCurItem = ME_FindItemFwd(pCurItem, diRun);
1975 nCurStart = -nMatched;
1978 if (pCurItem)
1979 wLastChar = *get_text( &pCurItem->member.run, nCurStart + nMatched );
1980 else
1981 wLastChar = ' ';
1983 cursor.nOffset++;
1984 if (cursor.nOffset == cursor.pRun->member.run.len)
1986 ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE);
1987 cursor.nOffset = 0;
1991 else /* Backward search */
1993 /* If possible, find the character after where the search ends */
1994 if ((flags & FR_WHOLEWORD) && nMax < nTextLen - 1)
1996 ME_CursorFromCharOfs(editor, nMax + 1, &cursor);
1997 wLastChar = *get_text( &cursor.pRun->member.run, cursor.nOffset );
1998 ME_MoveCursorChars(editor, &cursor, -1, FALSE);
1999 } else {
2000 ME_CursorFromCharOfs(editor, nMax, &cursor);
2003 while (cursor.pRun && ME_GetCursorOfs(&cursor) - nLen >= nMin)
2005 ME_DisplayItem *pCurItem = cursor.pRun;
2006 ME_DisplayItem *pCurPara = cursor.pPara;
2007 int nCurEnd = cursor.nOffset;
2008 int nMatched = 0;
2010 if (nCurEnd == 0)
2012 ME_PrevRun(&pCurPara, &pCurItem, TRUE);
2013 nCurEnd = pCurItem->member.run.len;
2016 while (pCurItem && ME_CharCompare( *get_text( &pCurItem->member.run, nCurEnd - nMatched - 1 ),
2017 text[nLen - nMatched - 1], (flags & FR_MATCHCASE) ))
2019 if ((flags & FR_WHOLEWORD) && isalnumW(wLastChar))
2020 break;
2022 nMatched++;
2023 if (nMatched == nLen)
2025 ME_DisplayItem *pPrevItem = pCurItem;
2026 int nPrevEnd = nCurEnd;
2027 WCHAR wPrevChar;
2028 int nStart;
2030 /* Check to see if previous character is a whitespace */
2031 if (flags & FR_WHOLEWORD)
2033 if (nPrevEnd - nMatched == 0)
2035 pPrevItem = ME_FindItemBack(pCurItem, diRun);
2036 if (pPrevItem)
2037 nPrevEnd = pPrevItem->member.run.len + nMatched;
2040 if (pPrevItem)
2041 wPrevChar = *get_text( &pPrevItem->member.run, nPrevEnd - nMatched - 1 );
2042 else
2043 wPrevChar = ' ';
2045 if (isalnumW(wPrevChar))
2046 break;
2049 nStart = pCurPara->member.para.nCharOfs
2050 + pCurItem->member.run.nCharOfs + nCurEnd - nMatched;
2051 if (chrgText)
2053 chrgText->cpMin = nStart;
2054 chrgText->cpMax = nStart + nLen;
2056 TRACE("found at %d-%d\n", nStart, nStart + nLen);
2057 return nStart;
2059 if (nCurEnd - nMatched == 0)
2061 ME_PrevRun(&pCurPara, &pCurItem, TRUE);
2062 /* Don't care about pCurItem becoming NULL here; it's already taken
2063 * care of in the exterior loop condition */
2064 nCurEnd = pCurItem->member.run.len + nMatched;
2067 if (pCurItem)
2068 wLastChar = *get_text( &pCurItem->member.run, nCurEnd - nMatched - 1 );
2069 else
2070 wLastChar = ' ';
2072 cursor.nOffset--;
2073 if (cursor.nOffset < 0)
2075 ME_PrevRun(&cursor.pPara, &cursor.pRun, TRUE);
2076 cursor.nOffset = cursor.pRun->member.run.len;
2080 TRACE("not found\n");
2081 if (chrgText)
2082 chrgText->cpMin = chrgText->cpMax = -1;
2083 return -1;
2086 static int ME_GetTextEx(ME_TextEditor *editor, GETTEXTEX *ex, LPARAM pText)
2088 int nChars;
2089 ME_Cursor start;
2091 if (!ex->cb || !pText) return 0;
2093 if (ex->flags & ~(GT_SELECTION | GT_USECRLF))
2094 FIXME("GETTEXTEX flags 0x%08x not supported\n", ex->flags & ~(GT_SELECTION | GT_USECRLF));
2096 if (ex->flags & GT_SELECTION)
2098 int from, to;
2099 int nStartCur = ME_GetSelectionOfs(editor, &from, &to);
2100 start = editor->pCursors[nStartCur];
2101 nChars = to - from;
2103 else
2105 ME_SetCursorToStart(editor, &start);
2106 nChars = INT_MAX;
2108 if (ex->codepage == CP_UNICODE)
2110 return ME_GetTextW(editor, (LPWSTR)pText, ex->cb / sizeof(WCHAR) - 1,
2111 &start, nChars, ex->flags & GT_USECRLF, FALSE);
2113 else
2115 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2116 we can just take a bigger buffer? :)
2117 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2118 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2120 int crlfmul = (ex->flags & GT_USECRLF) ? 2 : 1;
2121 DWORD buflen;
2122 LPWSTR buffer;
2123 LRESULT rc;
2125 buflen = min(crlfmul * nChars, ex->cb - 1);
2126 buffer = heap_alloc((buflen + 1) * sizeof(WCHAR));
2128 nChars = ME_GetTextW(editor, buffer, buflen, &start, nChars, ex->flags & GT_USECRLF, FALSE);
2129 rc = WideCharToMultiByte(ex->codepage, 0, buffer, nChars + 1,
2130 (LPSTR)pText, ex->cb, ex->lpDefaultChar, ex->lpUsedDefChar);
2131 if (rc) rc--; /* do not count 0 terminator */
2133 heap_free(buffer);
2134 return rc;
2138 static int ME_GetTextRange(ME_TextEditor *editor, WCHAR *strText,
2139 const ME_Cursor *start, int nLen, BOOL unicode)
2141 if (!strText) return 0;
2142 if (unicode) {
2143 return ME_GetTextW(editor, strText, INT_MAX, start, nLen, FALSE, FALSE);
2144 } else {
2145 int nChars;
2146 WCHAR *p = ALLOC_N_OBJ(WCHAR, nLen+1);
2147 if (!p) return 0;
2148 nChars = ME_GetTextW(editor, p, nLen, start, nLen, FALSE, FALSE);
2149 WideCharToMultiByte(CP_ACP, 0, p, nChars+1, (char *)strText,
2150 nLen+1, NULL, NULL);
2151 FREE_OBJ(p);
2152 return nChars;
2156 static int handle_EM_EXSETSEL( ME_TextEditor *editor, int to, int from )
2158 int end;
2160 TRACE("%d - %d\n", to, from );
2162 ME_InvalidateSelection( editor );
2163 end = ME_SetSelection( editor, to, from );
2164 ME_InvalidateSelection( editor );
2165 ITextHost_TxShowCaret( editor->texthost, FALSE );
2166 ME_ShowCaret( editor );
2167 ME_SendSelChange( editor );
2169 return end;
2172 typedef struct tagME_GlobalDestStruct
2174 HGLOBAL hData;
2175 int nLength;
2176 } ME_GlobalDestStruct;
2178 static DWORD CALLBACK ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2180 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2181 int i;
2182 WORD *pSrc, *pDest;
2184 cb = cb >> 1;
2185 pDest = (WORD *)lpBuff;
2186 pSrc = GlobalLock(pData->hData);
2187 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2188 pDest[i] = pSrc[pData->nLength+i];
2190 pData->nLength += i;
2191 *pcb = 2*i;
2192 GlobalUnlock(pData->hData);
2193 return 0;
2196 static DWORD CALLBACK ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2198 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2199 int i;
2200 BYTE *pSrc, *pDest;
2202 pDest = lpBuff;
2203 pSrc = GlobalLock(pData->hData);
2204 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2205 pDest[i] = pSrc[pData->nLength+i];
2207 pData->nLength += i;
2208 *pcb = i;
2209 GlobalUnlock(pData->hData);
2210 return 0;
2213 static BOOL ME_Paste(ME_TextEditor *editor)
2215 DWORD dwFormat = 0;
2216 EDITSTREAM es;
2217 ME_GlobalDestStruct gds;
2218 UINT nRTFFormat = RegisterClipboardFormatA("Rich Text Format");
2219 UINT cf = 0;
2221 if (IsClipboardFormatAvailable(nRTFFormat))
2222 cf = nRTFFormat, dwFormat = SF_RTF;
2223 else if (IsClipboardFormatAvailable(CF_UNICODETEXT))
2224 cf = CF_UNICODETEXT, dwFormat = SF_TEXT|SF_UNICODE;
2225 else
2226 return FALSE;
2228 if (!OpenClipboard(editor->hWnd))
2229 return FALSE;
2230 gds.hData = GetClipboardData(cf);
2231 gds.nLength = 0;
2232 es.dwCookie = (DWORD_PTR)&gds;
2233 es.pfnCallback = dwFormat == SF_RTF ? ME_ReadFromHGLOBALRTF : ME_ReadFromHGLOBALUnicode;
2234 ME_StreamIn(editor, dwFormat|SFF_SELECTION, &es, FALSE);
2236 CloseClipboard();
2237 return TRUE;
2240 static BOOL ME_Copy(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
2242 LPDATAOBJECT dataObj = NULL;
2243 HRESULT hr = S_OK;
2245 if (editor->cPasswordMask)
2246 return FALSE; /* Copying or Cutting masked text isn't allowed */
2248 if(editor->lpOleCallback)
2250 CHARRANGE range;
2251 range.cpMin = ME_GetCursorOfs(start);
2252 range.cpMax = range.cpMin + nChars;
2253 hr = IRichEditOleCallback_GetClipboardData(editor->lpOleCallback, &range, RECO_COPY, &dataObj);
2255 if(FAILED(hr) || !dataObj)
2256 hr = ME_GetDataObject(editor, start, nChars, &dataObj);
2257 if(SUCCEEDED(hr)) {
2258 hr = OleSetClipboard(dataObj);
2259 IDataObject_Release(dataObj);
2261 return SUCCEEDED(hr);
2264 /* helper to send a msg filter notification */
2265 static BOOL
2266 ME_FilterEvent(ME_TextEditor *editor, UINT msg, WPARAM* wParam, LPARAM* lParam)
2268 MSGFILTER msgf;
2270 if (!editor->hWnd || !editor->hwndParent) return FALSE;
2271 msgf.nmhdr.hwndFrom = editor->hWnd;
2272 msgf.nmhdr.idFrom = GetWindowLongW(editor->hWnd, GWLP_ID);
2273 msgf.nmhdr.code = EN_MSGFILTER;
2274 msgf.msg = msg;
2275 msgf.wParam = *wParam;
2276 msgf.lParam = *lParam;
2277 if (SendMessageW(editor->hwndParent, WM_NOTIFY, msgf.nmhdr.idFrom, (LPARAM)&msgf))
2278 return FALSE;
2279 *wParam = msgf.wParam;
2280 *lParam = msgf.lParam;
2281 msgf.wParam = *wParam;
2283 return TRUE;
2286 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor *editor)
2288 ME_DisplayItem *startPara, *endPara;
2289 ME_DisplayItem *prev_para;
2290 ME_Cursor *from, *to;
2291 ME_Cursor start;
2292 int nChars;
2294 if (!editor->AutoURLDetect_bEnable) return;
2296 ME_GetSelection(editor, &from, &to);
2298 /* Find paragraph previous to the one that contains start cursor */
2299 startPara = from->pPara;
2300 prev_para = startPara->member.para.prev_para;
2301 if (prev_para->type == diParagraph) startPara = prev_para;
2303 /* Find paragraph that contains end cursor */
2304 endPara = to->pPara->member.para.next_para;
2306 start.pPara = startPara;
2307 start.pRun = ME_FindItemFwd(startPara, diRun);
2308 start.nOffset = 0;
2309 nChars = endPara->member.para.nCharOfs - startPara->member.para.nCharOfs;
2311 ME_UpdateLinkAttribute(editor, &start, nChars);
2314 static BOOL
2315 ME_KeyDown(ME_TextEditor *editor, WORD nKey)
2317 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2318 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2320 if (editor->bMouseCaptured)
2321 return FALSE;
2322 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey != VK_MENU)
2323 editor->nSelectionType = stPosition;
2325 switch (nKey)
2327 case VK_LEFT:
2328 case VK_RIGHT:
2329 case VK_HOME:
2330 case VK_END:
2331 editor->nUDArrowX = -1;
2332 /* fall through */
2333 case VK_UP:
2334 case VK_DOWN:
2335 case VK_PRIOR:
2336 case VK_NEXT:
2337 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
2338 ME_ArrowKey(editor, nKey, shift_is_down, ctrl_is_down);
2339 return TRUE;
2340 case VK_BACK:
2341 case VK_DELETE:
2342 editor->nUDArrowX = -1;
2343 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2344 if (editor->styleFlags & ES_READONLY)
2345 return FALSE;
2346 if (ME_IsSelection(editor))
2348 ME_DeleteSelection(editor);
2349 ME_CommitUndo(editor);
2351 else if (nKey == VK_DELETE)
2353 /* Delete stops group typing.
2354 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2355 ME_DeleteTextAtCursor(editor, 1, 1);
2356 ME_CommitUndo(editor);
2358 else if (ME_ArrowKey(editor, VK_LEFT, FALSE, FALSE))
2360 BOOL bDeletionSucceeded;
2361 /* Backspace can be grouped for a single undo */
2362 ME_ContinueCoalescingTransaction(editor);
2363 bDeletionSucceeded = ME_DeleteTextAtCursor(editor, 1, 1);
2364 if (!bDeletionSucceeded && !editor->bEmulateVersion10) { /* v4.1 */
2365 /* Deletion was prevented so the cursor is moved back to where it was.
2366 * (e.g. this happens when trying to delete cell boundaries)
2368 ME_ArrowKey(editor, VK_RIGHT, FALSE, FALSE);
2370 ME_CommitCoalescingUndo(editor);
2372 else
2373 return TRUE;
2374 ME_MoveCursorFromTableRowStartParagraph(editor);
2375 ME_UpdateSelectionLinkAttribute(editor);
2376 ME_UpdateRepaint(editor, FALSE);
2377 ME_SendRequestResize(editor, FALSE);
2378 return TRUE;
2379 case VK_RETURN:
2380 if (editor->bDialogMode)
2382 if (ctrl_is_down)
2383 return TRUE;
2385 if (!(editor->styleFlags & ES_WANTRETURN))
2387 if (editor->hwndParent)
2389 DWORD dw;
2390 dw = SendMessageW(editor->hwndParent, DM_GETDEFID, 0, 0);
2391 if (HIWORD(dw) == DC_HASDEFID)
2393 HWND hwDefCtrl = GetDlgItem(editor->hwndParent, LOWORD(dw));
2394 if (hwDefCtrl)
2396 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, (WPARAM)hwDefCtrl, TRUE);
2397 PostMessageW(hwDefCtrl, WM_KEYDOWN, VK_RETURN, 0);
2401 return TRUE;
2405 if (editor->styleFlags & ES_MULTILINE)
2407 ME_Cursor cursor = editor->pCursors[0];
2408 ME_DisplayItem *para = cursor.pPara;
2409 int from, to;
2410 const WCHAR endl = '\r';
2411 const WCHAR endlv10[] = {'\r','\n'};
2412 ME_Style *style, *eop_style;
2414 if (editor->styleFlags & ES_READONLY) {
2415 MessageBeep(MB_ICONERROR);
2416 return TRUE;
2419 ME_GetSelectionOfs(editor, &from, &to);
2420 if (editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2422 if (!editor->bEmulateVersion10) { /* v4.1 */
2423 if (para->member.para.nFlags & MEPF_ROWEND) {
2424 /* Add a new table row after this row. */
2425 para = ME_AppendTableRow(editor, para);
2426 para = para->member.para.next_para;
2427 editor->pCursors[0].pPara = para;
2428 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2429 editor->pCursors[0].nOffset = 0;
2430 editor->pCursors[1] = editor->pCursors[0];
2431 ME_CommitUndo(editor);
2432 ME_CheckTablesForCorruption(editor);
2433 ME_UpdateRepaint(editor, FALSE);
2434 return TRUE;
2436 else if (para == editor->pCursors[1].pPara &&
2437 cursor.nOffset + cursor.pRun->member.run.nCharOfs == 0 &&
2438 para->member.para.prev_para->member.para.nFlags & MEPF_ROWSTART &&
2439 !para->member.para.prev_para->member.para.nCharOfs)
2441 /* Insert a newline before the table. */
2442 para = para->member.para.prev_para;
2443 para->member.para.nFlags &= ~MEPF_ROWSTART;
2444 editor->pCursors[0].pPara = para;
2445 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2446 editor->pCursors[1] = editor->pCursors[0];
2447 ME_InsertTextFromCursor(editor, 0, &endl, 1,
2448 editor->pCursors[0].pRun->member.run.style);
2449 para = editor->pBuffer->pFirst->member.para.next_para;
2450 ME_SetDefaultParaFormat(editor, &para->member.para.fmt);
2451 para->member.para.nFlags = MEPF_REWRAP;
2452 editor->pCursors[0].pPara = para;
2453 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2454 editor->pCursors[1] = editor->pCursors[0];
2455 para->member.para.next_para->member.para.nFlags |= MEPF_ROWSTART;
2456 ME_CommitCoalescingUndo(editor);
2457 ME_CheckTablesForCorruption(editor);
2458 ME_UpdateRepaint(editor, FALSE);
2459 return TRUE;
2461 } else { /* v1.0 - 3.0 */
2462 ME_DisplayItem *para = cursor.pPara;
2463 if (ME_IsInTable(para))
2465 if (cursor.pRun->member.run.nFlags & MERF_ENDPARA)
2467 if (from == to) {
2468 ME_ContinueCoalescingTransaction(editor);
2469 para = ME_AppendTableRow(editor, para);
2470 editor->pCursors[0].pPara = para;
2471 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2472 editor->pCursors[0].nOffset = 0;
2473 editor->pCursors[1] = editor->pCursors[0];
2474 ME_CommitCoalescingUndo(editor);
2475 ME_UpdateRepaint(editor, FALSE);
2476 return TRUE;
2478 } else {
2479 ME_ContinueCoalescingTransaction(editor);
2480 if (cursor.pRun->member.run.nCharOfs + cursor.nOffset == 0 &&
2481 !ME_IsInTable(para->member.para.prev_para))
2483 /* Insert newline before table */
2484 cursor.pRun = ME_FindItemBack(para, diRun);
2485 if (cursor.pRun) {
2486 editor->pCursors[0].pRun = cursor.pRun;
2487 editor->pCursors[0].pPara = para->member.para.prev_para;
2489 editor->pCursors[0].nOffset = 0;
2490 editor->pCursors[1] = editor->pCursors[0];
2491 ME_InsertTextFromCursor(editor, 0, &endl, 1,
2492 editor->pCursors[0].pRun->member.run.style);
2493 } else {
2494 editor->pCursors[1] = editor->pCursors[0];
2495 para = ME_AppendTableRow(editor, para);
2496 editor->pCursors[0].pPara = para;
2497 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2498 editor->pCursors[0].nOffset = 0;
2499 editor->pCursors[1] = editor->pCursors[0];
2501 ME_CommitCoalescingUndo(editor);
2502 ME_UpdateRepaint(editor, FALSE);
2503 return TRUE;
2508 style = ME_GetInsertStyle(editor, 0);
2510 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2511 eop style (this prevents the list label style changing when the new eop is inserted).
2512 No extra ref is taken here on eop_style. */
2513 if (para->member.para.fmt.wNumbering)
2514 eop_style = para->member.para.eop_run->style;
2515 else
2516 eop_style = style;
2517 ME_ContinueCoalescingTransaction(editor);
2518 if (shift_is_down)
2519 ME_InsertEndRowFromCursor(editor, 0);
2520 else
2521 if (!editor->bEmulateVersion10)
2522 ME_InsertTextFromCursor(editor, 0, &endl, 1, eop_style);
2523 else
2524 ME_InsertTextFromCursor(editor, 0, endlv10, 2, eop_style);
2525 ME_CommitCoalescingUndo(editor);
2526 SetCursor(NULL);
2528 ME_UpdateSelectionLinkAttribute(editor);
2529 ME_UpdateRepaint(editor, FALSE);
2530 ME_SaveTempStyle(editor, style); /* set the temp insert style for the new para */
2531 ME_ReleaseStyle(style);
2533 return TRUE;
2535 break;
2536 case VK_ESCAPE:
2537 if (editor->bDialogMode && editor->hwndParent)
2538 PostMessageW(editor->hwndParent, WM_CLOSE, 0, 0);
2539 return TRUE;
2540 case VK_TAB:
2541 if (editor->bDialogMode && editor->hwndParent)
2542 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, shift_is_down, 0);
2543 return TRUE;
2544 case 'A':
2545 if (ctrl_is_down)
2547 handle_EM_EXSETSEL( editor, 0, -1 );
2548 return TRUE;
2550 break;
2551 case 'V':
2552 if (ctrl_is_down)
2553 return ME_Paste(editor);
2554 break;
2555 case 'C':
2556 case 'X':
2557 if (ctrl_is_down)
2559 BOOL result;
2560 int nOfs, nChars;
2561 int nStartCur = ME_GetSelectionOfs(editor, &nOfs, &nChars);
2562 ME_Cursor *selStart = &editor->pCursors[nStartCur];
2564 nChars -= nOfs;
2565 result = ME_Copy(editor, selStart, nChars);
2566 if (result && nKey == 'X')
2568 ME_InternalDeleteText(editor, selStart, nChars, FALSE);
2569 ME_CommitUndo(editor);
2570 ME_UpdateRepaint(editor, TRUE);
2572 return result;
2574 break;
2575 case 'Z':
2576 if (ctrl_is_down)
2578 ME_Undo(editor);
2579 return TRUE;
2581 break;
2582 case 'Y':
2583 if (ctrl_is_down)
2585 ME_Redo(editor);
2586 return TRUE;
2588 break;
2590 default:
2591 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey && nKey != VK_MENU)
2592 editor->nUDArrowX = -1;
2593 if (ctrl_is_down)
2595 if (nKey == 'W')
2597 CHARFORMAT2W chf;
2598 char buf[2048];
2599 chf.cbSize = sizeof(chf);
2601 ME_GetSelectionCharFormat(editor, &chf);
2602 ME_DumpStyleToBuf(&chf, buf);
2603 MessageBoxA(NULL, buf, "Style dump", MB_OK);
2605 if (nKey == 'Q')
2607 ME_CheckCharOffsets(editor);
2611 return FALSE;
2614 static LRESULT ME_Char(ME_TextEditor *editor, WPARAM charCode,
2615 LPARAM flags, BOOL unicode)
2617 WCHAR wstr;
2619 if (editor->bMouseCaptured)
2620 return 0;
2622 if (unicode)
2623 wstr = (WCHAR)charCode;
2624 else
2626 CHAR charA = charCode;
2627 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &wstr, 1);
2630 if (editor->styleFlags & ES_READONLY) {
2631 MessageBeep(MB_ICONERROR);
2632 return 0; /* FIXME really 0 ? */
2635 if ((unsigned)wstr >= ' ' || wstr == '\t')
2637 ME_Cursor cursor = editor->pCursors[0];
2638 ME_DisplayItem *para = cursor.pPara;
2639 int from, to;
2640 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2641 ME_GetSelectionOfs(editor, &from, &to);
2642 if (wstr == '\t' &&
2643 /* v4.1 allows tabs to be inserted with ctrl key down */
2644 !(ctrl_is_down && !editor->bEmulateVersion10))
2646 ME_DisplayItem *para;
2647 BOOL bSelectedRow = FALSE;
2649 para = cursor.pPara;
2650 if (ME_IsSelection(editor) &&
2651 cursor.pRun->member.run.nCharOfs + cursor.nOffset == 0 &&
2652 to == ME_GetCursorOfs(&editor->pCursors[0]) &&
2653 para->member.para.prev_para->type == diParagraph)
2655 para = para->member.para.prev_para;
2656 bSelectedRow = TRUE;
2658 if (ME_IsInTable(para))
2660 ME_TabPressedInTable(editor, bSelectedRow);
2661 ME_CommitUndo(editor);
2662 return 0;
2664 } else if (!editor->bEmulateVersion10) { /* v4.1 */
2665 if (para->member.para.nFlags & MEPF_ROWEND) {
2666 if (from == to) {
2667 para = para->member.para.next_para;
2668 if (para->member.para.nFlags & MEPF_ROWSTART)
2669 para = para->member.para.next_para;
2670 editor->pCursors[0].pPara = para;
2671 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2672 editor->pCursors[0].nOffset = 0;
2673 editor->pCursors[1] = editor->pCursors[0];
2676 } else { /* v1.0 - 3.0 */
2677 if (ME_IsInTable(cursor.pRun) &&
2678 cursor.pRun->member.run.nFlags & MERF_ENDPARA &&
2679 from == to)
2681 /* Text should not be inserted at the end of the table. */
2682 MessageBeep(-1);
2683 return 0;
2686 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2687 /* WM_CHAR is restricted to nTextLimit */
2688 if(editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2690 ME_Style *style = ME_GetInsertStyle(editor, 0);
2691 ME_ContinueCoalescingTransaction(editor);
2692 ME_InsertTextFromCursor(editor, 0, &wstr, 1, style);
2693 ME_ReleaseStyle(style);
2694 ME_CommitCoalescingUndo(editor);
2695 ITextHost_TxSetCursor(editor->texthost, NULL, FALSE);
2698 ME_UpdateSelectionLinkAttribute(editor);
2699 ME_UpdateRepaint(editor, FALSE);
2701 return 0;
2704 /* Process the message and calculate the new click count.
2706 * returns: The click count if it is mouse down event, else returns 0. */
2707 static int ME_CalculateClickCount(ME_TextEditor *editor, UINT msg, WPARAM wParam,
2708 LPARAM lParam)
2710 static int clickNum = 0;
2711 if (msg < WM_MOUSEFIRST || msg > WM_MOUSELAST)
2712 return 0;
2714 if ((msg == WM_LBUTTONDBLCLK) ||
2715 (msg == WM_RBUTTONDBLCLK) ||
2716 (msg == WM_MBUTTONDBLCLK) ||
2717 (msg == WM_XBUTTONDBLCLK))
2719 msg -= (WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
2722 if ((msg == WM_LBUTTONDOWN) ||
2723 (msg == WM_RBUTTONDOWN) ||
2724 (msg == WM_MBUTTONDOWN) ||
2725 (msg == WM_XBUTTONDOWN))
2727 static MSG prevClickMsg;
2728 MSG clickMsg;
2729 /* Compare the editor instead of the hwnd so that the this
2730 * can still be done for windowless richedit controls. */
2731 clickMsg.hwnd = (HWND)editor;
2732 clickMsg.message = msg;
2733 clickMsg.wParam = wParam;
2734 clickMsg.lParam = lParam;
2735 clickMsg.time = GetMessageTime();
2736 clickMsg.pt.x = (short)LOWORD(lParam);
2737 clickMsg.pt.y = (short)HIWORD(lParam);
2738 if ((clickNum != 0) &&
2739 (clickMsg.message == prevClickMsg.message) &&
2740 (clickMsg.hwnd == prevClickMsg.hwnd) &&
2741 (clickMsg.wParam == prevClickMsg.wParam) &&
2742 (clickMsg.time - prevClickMsg.time < GetDoubleClickTime()) &&
2743 (abs(clickMsg.pt.x - prevClickMsg.pt.x) < GetSystemMetrics(SM_CXDOUBLECLK)/2) &&
2744 (abs(clickMsg.pt.y - prevClickMsg.pt.y) < GetSystemMetrics(SM_CYDOUBLECLK)/2))
2746 clickNum++;
2747 } else {
2748 clickNum = 1;
2750 prevClickMsg = clickMsg;
2751 } else {
2752 return 0;
2754 return clickNum;
2757 static BOOL is_link( ME_Run *run )
2759 return (run->style->fmt.dwMask & CFM_LINK) && (run->style->fmt.dwEffects & CFE_LINK);
2762 static BOOL ME_SetCursor(ME_TextEditor *editor)
2764 ME_Cursor cursor;
2765 POINT pt;
2766 BOOL isExact;
2767 SCROLLBARINFO sbi;
2768 DWORD messagePos = GetMessagePos();
2769 pt.x = (short)LOWORD(messagePos);
2770 pt.y = (short)HIWORD(messagePos);
2772 if (editor->hWnd)
2774 sbi.cbSize = sizeof(sbi);
2775 GetScrollBarInfo(editor->hWnd, OBJID_HSCROLL, &sbi);
2776 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2777 PtInRect(&sbi.rcScrollBar, pt))
2779 ITextHost_TxSetCursor(editor->texthost,
2780 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2781 return TRUE;
2783 sbi.cbSize = sizeof(sbi);
2784 GetScrollBarInfo(editor->hWnd, OBJID_VSCROLL, &sbi);
2785 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2786 PtInRect(&sbi.rcScrollBar, pt))
2788 ITextHost_TxSetCursor(editor->texthost,
2789 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2790 return TRUE;
2793 ITextHost_TxScreenToClient(editor->texthost, &pt);
2795 if (editor->nSelectionType == stLine && editor->bMouseCaptured) {
2796 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2797 return TRUE;
2799 if (!editor->bEmulateVersion10 /* v4.1 */ &&
2800 pt.y < editor->rcFormat.top &&
2801 pt.x < editor->rcFormat.left)
2803 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2804 return TRUE;
2806 if (pt.y < editor->rcFormat.top || pt.y > editor->rcFormat.bottom)
2808 if (editor->bEmulateVersion10) /* v1.0 - 3.0 */
2809 ITextHost_TxSetCursor(editor->texthost,
2810 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2811 else /* v4.1 */
2812 ITextHost_TxSetCursor(editor->texthost,
2813 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
2814 return TRUE;
2816 if (pt.x < editor->rcFormat.left)
2818 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2819 return TRUE;
2821 ME_CharFromPos(editor, pt.x, pt.y, &cursor, &isExact);
2822 if (isExact)
2824 ME_Run *run;
2826 run = &cursor.pRun->member.run;
2827 if (is_link( run ))
2829 ITextHost_TxSetCursor(editor->texthost,
2830 LoadCursorW(NULL, (WCHAR*)IDC_HAND),
2831 FALSE);
2832 return TRUE;
2835 if (ME_IsSelection(editor))
2837 int selStart, selEnd;
2838 int offset = ME_GetCursorOfs(&cursor);
2840 ME_GetSelectionOfs(editor, &selStart, &selEnd);
2841 if (selStart <= offset && selEnd >= offset) {
2842 ITextHost_TxSetCursor(editor->texthost,
2843 LoadCursorW(NULL, (WCHAR*)IDC_ARROW),
2844 FALSE);
2845 return TRUE;
2849 ITextHost_TxSetCursor(editor->texthost,
2850 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
2851 return TRUE;
2854 static void ME_SetDefaultFormatRect(ME_TextEditor *editor)
2856 ITextHost_TxGetClientRect(editor->texthost, &editor->rcFormat);
2857 editor->rcFormat.top += editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
2858 editor->rcFormat.left += 1 + editor->selofs;
2859 editor->rcFormat.right -= 1;
2862 static BOOL ME_ShowContextMenu(ME_TextEditor *editor, int x, int y)
2864 CHARRANGE selrange;
2865 HMENU menu;
2866 int seltype = 0;
2867 if(!editor->lpOleCallback || !editor->hWnd)
2868 return FALSE;
2869 ME_GetSelectionOfs(editor, &selrange.cpMin, &selrange.cpMax);
2870 if(selrange.cpMin == selrange.cpMax)
2871 seltype |= SEL_EMPTY;
2872 else
2874 /* FIXME: Handle objects */
2875 seltype |= SEL_TEXT;
2876 if(selrange.cpMax-selrange.cpMin > 1)
2877 seltype |= SEL_MULTICHAR;
2879 if(SUCCEEDED(IRichEditOleCallback_GetContextMenu(editor->lpOleCallback, seltype, NULL, &selrange, &menu)))
2881 TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, 0, editor->hwndParent, NULL);
2882 DestroyMenu(menu);
2884 return TRUE;
2887 ME_TextEditor *ME_MakeEditor(ITextHost *texthost, BOOL bEmulateVersion10, DWORD csStyle)
2889 ME_TextEditor *ed = ALLOC_OBJ(ME_TextEditor);
2890 int i;
2891 DWORD props;
2892 LONG selbarwidth;
2894 ed->hWnd = NULL;
2895 ed->hwndParent = NULL;
2896 ed->sizeWindow.cx = ed->sizeWindow.cy = 0;
2897 ed->texthost = texthost;
2898 ed->reOle = NULL;
2899 ed->bEmulateVersion10 = bEmulateVersion10;
2900 ed->styleFlags = 0;
2901 ed->alignStyle = PFA_LEFT;
2902 if (csStyle & ES_RIGHT)
2903 ed->alignStyle = PFA_RIGHT;
2904 if (csStyle & ES_CENTER)
2905 ed->alignStyle = PFA_CENTER;
2906 ITextHost_TxGetPropertyBits(texthost,
2907 (TXTBIT_RICHTEXT|TXTBIT_MULTILINE|
2908 TXTBIT_READONLY|TXTBIT_USEPASSWORD|
2909 TXTBIT_HIDESELECTION|TXTBIT_SAVESELECTION|
2910 TXTBIT_AUTOWORDSEL|TXTBIT_VERTICAL|
2911 TXTBIT_WORDWRAP|TXTBIT_DISABLEDRAG),
2912 &props);
2913 ITextHost_TxGetScrollBars(texthost, &ed->styleFlags);
2914 ed->styleFlags &= (WS_VSCROLL|WS_HSCROLL|ES_AUTOVSCROLL|
2915 ES_AUTOHSCROLL|ES_DISABLENOSCROLL);
2916 ed->pBuffer = ME_MakeText();
2917 ed->nZoomNumerator = ed->nZoomDenominator = 0;
2918 ed->nAvailWidth = 0; /* wrap to client area */
2919 ME_MakeFirstParagraph(ed);
2920 /* The four cursors are for:
2921 * 0 - The position where the caret is shown
2922 * 1 - The anchored end of the selection (for normal selection)
2923 * 2 & 3 - The anchored start and end respectively for word, line,
2924 * or paragraph selection.
2926 ed->nCursors = 4;
2927 ed->pCursors = ALLOC_N_OBJ(ME_Cursor, ed->nCursors);
2928 ME_SetCursorToStart(ed, &ed->pCursors[0]);
2929 ed->pCursors[1] = ed->pCursors[0];
2930 ed->pCursors[2] = ed->pCursors[0];
2931 ed->pCursors[3] = ed->pCursors[1];
2932 ed->nLastTotalLength = ed->nTotalLength = 0;
2933 ed->nLastTotalWidth = ed->nTotalWidth = 0;
2934 ed->nUDArrowX = -1;
2935 ed->rgbBackColor = -1;
2936 ed->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
2937 ed->bCaretAtEnd = FALSE;
2938 ed->nEventMask = 0;
2939 ed->nModifyStep = 0;
2940 ed->nTextLimit = TEXT_LIMIT_DEFAULT;
2941 list_init( &ed->undo_stack );
2942 list_init( &ed->redo_stack );
2943 ed->nUndoStackSize = 0;
2944 ed->nUndoLimit = STACK_SIZE_DEFAULT;
2945 ed->nUndoMode = umAddToUndo;
2946 ed->nParagraphs = 1;
2947 ed->nLastSelStart = ed->nLastSelEnd = 0;
2948 ed->pLastSelStartPara = ed->pLastSelEndPara = ed->pCursors[0].pPara;
2949 ed->bHideSelection = FALSE;
2950 ed->pfnWordBreak = NULL;
2951 ed->lpOleCallback = NULL;
2952 ed->mode = TM_MULTILEVELUNDO | TM_MULTICODEPAGE;
2953 ed->mode |= (props & TXTBIT_RICHTEXT) ? TM_RICHTEXT : TM_PLAINTEXT;
2954 ed->AutoURLDetect_bEnable = FALSE;
2955 ed->bHaveFocus = FALSE;
2956 ed->bDialogMode = FALSE;
2957 ed->bMouseCaptured = FALSE;
2958 for (i=0; i<HFONT_CACHE_SIZE; i++)
2960 ed->pFontCache[i].nRefs = 0;
2961 ed->pFontCache[i].nAge = 0;
2962 ed->pFontCache[i].hFont = NULL;
2965 ME_CheckCharOffsets(ed);
2966 ed->bDefaultFormatRect = TRUE;
2967 ITextHost_TxGetSelectionBarWidth(ed->texthost, &selbarwidth);
2968 if (selbarwidth) {
2969 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
2970 ed->selofs = SELECTIONBAR_WIDTH;
2971 ed->styleFlags |= ES_SELECTIONBAR;
2972 } else {
2973 ed->selofs = 0;
2975 ed->nSelectionType = stPosition;
2977 ed->cPasswordMask = 0;
2978 if (props & TXTBIT_USEPASSWORD)
2979 ITextHost_TxGetPasswordChar(texthost, &ed->cPasswordMask);
2981 if (props & TXTBIT_AUTOWORDSEL)
2982 ed->styleFlags |= ECO_AUTOWORDSELECTION;
2983 if (props & TXTBIT_MULTILINE) {
2984 ed->styleFlags |= ES_MULTILINE;
2985 ed->bWordWrap = (props & TXTBIT_WORDWRAP) != 0;
2986 } else {
2987 ed->bWordWrap = FALSE;
2989 if (props & TXTBIT_READONLY)
2990 ed->styleFlags |= ES_READONLY;
2991 if (!(props & TXTBIT_HIDESELECTION))
2992 ed->styleFlags |= ES_NOHIDESEL;
2993 if (props & TXTBIT_SAVESELECTION)
2994 ed->styleFlags |= ES_SAVESEL;
2995 if (props & TXTBIT_VERTICAL)
2996 ed->styleFlags |= ES_VERTICAL;
2997 if (props & TXTBIT_DISABLEDRAG)
2998 ed->styleFlags |= ES_NOOLEDRAGDROP;
3000 ed->notified_cr.cpMin = ed->notified_cr.cpMax = 0;
3002 /* Default scrollbar information */
3003 ed->vert_si.cbSize = sizeof(SCROLLINFO);
3004 ed->vert_si.nMin = 0;
3005 ed->vert_si.nMax = 0;
3006 ed->vert_si.nPage = 0;
3007 ed->vert_si.nPos = 0;
3009 ed->horz_si.cbSize = sizeof(SCROLLINFO);
3010 ed->horz_si.nMin = 0;
3011 ed->horz_si.nMax = 0;
3012 ed->horz_si.nPage = 0;
3013 ed->horz_si.nPos = 0;
3015 ed->wheel_remain = 0;
3017 list_init( &ed->style_list );
3018 OleInitialize(NULL);
3020 return ed;
3023 void ME_DestroyEditor(ME_TextEditor *editor)
3025 ME_DisplayItem *pFirst = editor->pBuffer->pFirst;
3026 ME_DisplayItem *p = pFirst, *pNext = NULL;
3027 ME_Style *s, *cursor2;
3028 int i;
3030 ME_ClearTempStyle(editor);
3031 ME_EmptyUndoStack(editor);
3032 while(p) {
3033 pNext = p->next;
3034 ME_DestroyDisplayItem(p);
3035 p = pNext;
3038 LIST_FOR_EACH_ENTRY_SAFE( s, cursor2, &editor->style_list, ME_Style, entry )
3039 ME_DestroyStyle( s );
3041 ME_ReleaseStyle(editor->pBuffer->pDefaultStyle);
3042 for (i=0; i<HFONT_CACHE_SIZE; i++)
3044 if (editor->pFontCache[i].hFont)
3045 DeleteObject(editor->pFontCache[i].hFont);
3047 if (editor->rgbBackColor != -1)
3048 DeleteObject(editor->hbrBackground);
3049 if(editor->lpOleCallback)
3050 IRichEditOleCallback_Release(editor->lpOleCallback);
3051 ITextHost_Release(editor->texthost);
3052 if (editor->reOle)
3054 IRichEditOle_Release(editor->reOle);
3055 editor->reOle = NULL;
3057 OleUninitialize();
3059 FREE_OBJ(editor->pBuffer);
3060 FREE_OBJ(editor->pCursors);
3062 FREE_OBJ(editor);
3065 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
3067 TRACE("\n");
3068 switch (fdwReason)
3070 case DLL_PROCESS_ATTACH:
3071 DisableThreadLibraryCalls(hinstDLL);
3072 me_heap = HeapCreate (0, 0x10000, 0);
3073 if (!ME_RegisterEditorClass(hinstDLL)) return FALSE;
3074 hLeft = LoadCursorW(hinstDLL, MAKEINTRESOURCEW(OCR_REVERSE));
3075 LookupInit();
3076 break;
3078 case DLL_PROCESS_DETACH:
3079 if (lpvReserved) break;
3080 UnregisterClassW(RICHEDIT_CLASS20W, 0);
3081 UnregisterClassW(MSFTEDIT_CLASS, 0);
3082 UnregisterClassA(RICHEDIT_CLASS20A, 0);
3083 UnregisterClassA("RichEdit50A", 0);
3084 if (ME_ListBoxRegistered)
3085 UnregisterClassW(REListBox20W, 0);
3086 if (ME_ComboBoxRegistered)
3087 UnregisterClassW(REComboBox20W, 0);
3088 LookupCleanup();
3089 HeapDestroy (me_heap);
3090 release_typelib();
3091 break;
3093 return TRUE;
3096 static inline int get_default_line_height( ME_TextEditor *editor )
3098 int height = 0;
3100 if (editor->pBuffer && editor->pBuffer->pDefaultStyle)
3101 height = editor->pBuffer->pDefaultStyle->tm.tmHeight;
3102 if (height <= 0) height = 24;
3104 return height;
3107 static inline int calc_wheel_change( int *remain, int amount_per_click )
3109 int change = amount_per_click * (float)*remain / WHEEL_DELTA;
3110 *remain -= WHEEL_DELTA * change / amount_per_click;
3111 return change;
3114 static const char * const edit_messages[] = {
3115 "EM_GETSEL",
3116 "EM_SETSEL",
3117 "EM_GETRECT",
3118 "EM_SETRECT",
3119 "EM_SETRECTNP",
3120 "EM_SCROLL",
3121 "EM_LINESCROLL",
3122 "EM_SCROLLCARET",
3123 "EM_GETMODIFY",
3124 "EM_SETMODIFY",
3125 "EM_GETLINECOUNT",
3126 "EM_LINEINDEX",
3127 "EM_SETHANDLE",
3128 "EM_GETHANDLE",
3129 "EM_GETTHUMB",
3130 "EM_UNKNOWN_BF",
3131 "EM_UNKNOWN_C0",
3132 "EM_LINELENGTH",
3133 "EM_REPLACESEL",
3134 "EM_UNKNOWN_C3",
3135 "EM_GETLINE",
3136 "EM_LIMITTEXT",
3137 "EM_CANUNDO",
3138 "EM_UNDO",
3139 "EM_FMTLINES",
3140 "EM_LINEFROMCHAR",
3141 "EM_UNKNOWN_CA",
3142 "EM_SETTABSTOPS",
3143 "EM_SETPASSWORDCHAR",
3144 "EM_EMPTYUNDOBUFFER",
3145 "EM_GETFIRSTVISIBLELINE",
3146 "EM_SETREADONLY",
3147 "EM_SETWORDBREAKPROC",
3148 "EM_GETWORDBREAKPROC",
3149 "EM_GETPASSWORDCHAR",
3150 "EM_SETMARGINS",
3151 "EM_GETMARGINS",
3152 "EM_GETLIMITTEXT",
3153 "EM_POSFROMCHAR",
3154 "EM_CHARFROMPOS",
3155 "EM_SETIMESTATUS",
3156 "EM_GETIMESTATUS"
3159 static const char * const richedit_messages[] = {
3160 "EM_CANPASTE",
3161 "EM_DISPLAYBAND",
3162 "EM_EXGETSEL",
3163 "EM_EXLIMITTEXT",
3164 "EM_EXLINEFROMCHAR",
3165 "EM_EXSETSEL",
3166 "EM_FINDTEXT",
3167 "EM_FORMATRANGE",
3168 "EM_GETCHARFORMAT",
3169 "EM_GETEVENTMASK",
3170 "EM_GETOLEINTERFACE",
3171 "EM_GETPARAFORMAT",
3172 "EM_GETSELTEXT",
3173 "EM_HIDESELECTION",
3174 "EM_PASTESPECIAL",
3175 "EM_REQUESTRESIZE",
3176 "EM_SELECTIONTYPE",
3177 "EM_SETBKGNDCOLOR",
3178 "EM_SETCHARFORMAT",
3179 "EM_SETEVENTMASK",
3180 "EM_SETOLECALLBACK",
3181 "EM_SETPARAFORMAT",
3182 "EM_SETTARGETDEVICE",
3183 "EM_STREAMIN",
3184 "EM_STREAMOUT",
3185 "EM_GETTEXTRANGE",
3186 "EM_FINDWORDBREAK",
3187 "EM_SETOPTIONS",
3188 "EM_GETOPTIONS",
3189 "EM_FINDTEXTEX",
3190 "EM_GETWORDBREAKPROCEX",
3191 "EM_SETWORDBREAKPROCEX",
3192 "EM_SETUNDOLIMIT",
3193 "EM_UNKNOWN_USER_83",
3194 "EM_REDO",
3195 "EM_CANREDO",
3196 "EM_GETUNDONAME",
3197 "EM_GETREDONAME",
3198 "EM_STOPGROUPTYPING",
3199 "EM_SETTEXTMODE",
3200 "EM_GETTEXTMODE",
3201 "EM_AUTOURLDETECT",
3202 "EM_GETAUTOURLDETECT",
3203 "EM_SETPALETTE",
3204 "EM_GETTEXTEX",
3205 "EM_GETTEXTLENGTHEX",
3206 "EM_SHOWSCROLLBAR",
3207 "EM_SETTEXTEX",
3208 "EM_UNKNOWN_USER_98",
3209 "EM_UNKNOWN_USER_99",
3210 "EM_SETPUNCTUATION",
3211 "EM_GETPUNCTUATION",
3212 "EM_SETWORDWRAPMODE",
3213 "EM_GETWORDWRAPMODE",
3214 "EM_SETIMECOLOR",
3215 "EM_GETIMECOLOR",
3216 "EM_SETIMEOPTIONS",
3217 "EM_GETIMEOPTIONS",
3218 "EM_CONVPOSITION",
3219 "EM_UNKNOWN_USER_109",
3220 "EM_UNKNOWN_USER_110",
3221 "EM_UNKNOWN_USER_111",
3222 "EM_UNKNOWN_USER_112",
3223 "EM_UNKNOWN_USER_113",
3224 "EM_UNKNOWN_USER_114",
3225 "EM_UNKNOWN_USER_115",
3226 "EM_UNKNOWN_USER_116",
3227 "EM_UNKNOWN_USER_117",
3228 "EM_UNKNOWN_USER_118",
3229 "EM_UNKNOWN_USER_119",
3230 "EM_SETLANGOPTIONS",
3231 "EM_GETLANGOPTIONS",
3232 "EM_GETIMECOMPMODE",
3233 "EM_FINDTEXTW",
3234 "EM_FINDTEXTEXW",
3235 "EM_RECONVERSION",
3236 "EM_SETIMEMODEBIAS",
3237 "EM_GETIMEMODEBIAS"
3240 static const char *
3241 get_msg_name(UINT msg)
3243 if (msg >= EM_GETSEL && msg <= EM_CHARFROMPOS)
3244 return edit_messages[msg - EM_GETSEL];
3245 if (msg >= EM_CANPASTE && msg <= EM_GETIMEMODEBIAS)
3246 return richedit_messages[msg - EM_CANPASTE];
3247 return "";
3250 static void ME_LinkNotify(ME_TextEditor *editor, UINT msg, WPARAM wParam, LPARAM lParam)
3252 int x,y;
3253 BOOL isExact;
3254 ME_Cursor cursor; /* The start of the clicked text. */
3256 ENLINK info;
3257 x = (short)LOWORD(lParam);
3258 y = (short)HIWORD(lParam);
3259 ME_CharFromPos(editor, x, y, &cursor, &isExact);
3260 if (!isExact) return;
3262 if (is_link( &cursor.pRun->member.run ))
3263 { /* The clicked run has CFE_LINK set */
3264 ME_DisplayItem *di;
3266 info.nmhdr.hwndFrom = NULL;
3267 info.nmhdr.idFrom = 0;
3268 info.nmhdr.code = EN_LINK;
3269 info.msg = msg;
3270 info.wParam = wParam;
3271 info.lParam = lParam;
3272 cursor.nOffset = 0;
3274 /* find the first contiguous run with CFE_LINK set */
3275 info.chrg.cpMin = ME_GetCursorOfs(&cursor);
3276 di = cursor.pRun;
3277 while (ME_PrevRun( NULL, &di, FALSE ) && is_link( &di->member.run ))
3278 info.chrg.cpMin -= di->member.run.len;
3280 /* find the last contiguous run with CFE_LINK set */
3281 info.chrg.cpMax = ME_GetCursorOfs(&cursor) + cursor.pRun->member.run.len;
3282 di = cursor.pRun;
3283 while (ME_NextRun( NULL, &di, FALSE ) && is_link( &di->member.run ))
3284 info.chrg.cpMax += di->member.run.len;
3286 ITextHost_TxNotify(editor->texthost, info.nmhdr.code, &info);
3290 void ME_ReplaceSel(ME_TextEditor *editor, BOOL can_undo, const WCHAR *str, int len)
3292 int from, to, nStartCursor;
3293 ME_Style *style;
3295 nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3296 style = ME_GetSelectionInsertStyle(editor);
3297 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3298 ME_InsertTextFromCursor(editor, 0, str, len, style);
3299 ME_ReleaseStyle(style);
3300 /* drop temporary style if line end */
3302 * FIXME question: does abc\n mean: put abc,
3303 * clear temp style, put \n? (would require a change)
3305 if (len>0 && str[len-1] == '\n')
3306 ME_ClearTempStyle(editor);
3307 ME_CommitUndo(editor);
3308 ME_UpdateSelectionLinkAttribute(editor);
3309 if (!can_undo)
3310 ME_EmptyUndoStack(editor);
3311 ME_UpdateRepaint(editor, FALSE);
3314 static void ME_SetText(ME_TextEditor *editor, void *text, BOOL unicode)
3316 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
3317 int textLen;
3319 LPWSTR wszText = ME_ToUnicode(codepage, text, &textLen);
3321 if (textLen > 0)
3323 int len = -1;
3325 /* uses default style! */
3326 if (!(editor->styleFlags & ES_MULTILINE))
3328 WCHAR *p = wszText;
3330 while (*p != '\0' && *p != '\r' && *p != '\n') p++;
3331 len = p - wszText;
3333 ME_InsertTextFromCursor(editor, 0, wszText, len, editor->pBuffer->pDefaultStyle);
3335 ME_EndToUnicode(codepage, wszText);
3338 static LRESULT ME_WmCreate(ME_TextEditor *editor, LPARAM lParam, BOOL unicode)
3340 CREATESTRUCTW *createW = (CREATESTRUCTW*)lParam;
3341 CREATESTRUCTA *createA = (CREATESTRUCTA*)lParam;
3342 void *text = NULL;
3343 INT max;
3345 if (lParam)
3346 text = unicode ? (void*)createW->lpszName : (void*)createA->lpszName;
3348 ME_SetDefaultFormatRect(editor);
3350 max = (editor->styleFlags & ES_DISABLENOSCROLL) ? 1 : 0;
3351 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_VSCROLL)
3352 ITextHost_TxSetScrollRange(editor->texthost, SB_VERT, 0, max, TRUE);
3354 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_HSCROLL)
3355 ITextHost_TxSetScrollRange(editor->texthost, SB_HORZ, 0, max, TRUE);
3357 if (editor->styleFlags & ES_DISABLENOSCROLL)
3359 if (editor->styleFlags & WS_VSCROLL)
3361 ITextHost_TxEnableScrollBar(editor->texthost, SB_VERT, ESB_DISABLE_BOTH);
3362 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT, TRUE);
3364 if (editor->styleFlags & WS_HSCROLL)
3366 ITextHost_TxEnableScrollBar(editor->texthost, SB_HORZ, ESB_DISABLE_BOTH);
3367 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ, TRUE);
3371 if (text)
3373 ME_SetText(editor, text, unicode);
3374 ME_SetCursorToStart(editor, &editor->pCursors[0]);
3375 ME_SetCursorToStart(editor, &editor->pCursors[1]);
3378 ME_CommitUndo(editor);
3379 ME_WrapMarkedParagraphs(editor);
3380 ME_MoveCaret(editor);
3381 return 0;
3385 #define UNSUPPORTED_MSG(e) \
3386 case e: \
3387 FIXME(#e ": stub\n"); \
3388 *phresult = S_FALSE; \
3389 return 0;
3391 /* Handle messages for windowless and windowed richedit controls.
3393 * The LRESULT that is returned is a return value for window procs,
3394 * and the phresult parameter is the COM return code needed by the
3395 * text services interface. */
3396 LRESULT ME_HandleMessage(ME_TextEditor *editor, UINT msg, WPARAM wParam,
3397 LPARAM lParam, BOOL unicode, HRESULT* phresult)
3399 *phresult = S_OK;
3401 switch(msg) {
3403 UNSUPPORTED_MSG(EM_DISPLAYBAND)
3404 UNSUPPORTED_MSG(EM_FINDWORDBREAK)
3405 UNSUPPORTED_MSG(EM_FMTLINES)
3406 UNSUPPORTED_MSG(EM_FORMATRANGE)
3407 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS)
3408 UNSUPPORTED_MSG(EM_GETEDITSTYLE)
3409 UNSUPPORTED_MSG(EM_GETIMECOMPMODE)
3410 UNSUPPORTED_MSG(EM_GETIMESTATUS)
3411 UNSUPPORTED_MSG(EM_SETIMESTATUS)
3412 UNSUPPORTED_MSG(EM_GETLANGOPTIONS)
3413 UNSUPPORTED_MSG(EM_GETREDONAME)
3414 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS)
3415 UNSUPPORTED_MSG(EM_GETUNDONAME)
3416 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX)
3417 UNSUPPORTED_MSG(EM_PASTESPECIAL)
3418 UNSUPPORTED_MSG(EM_SELECTIONTYPE)
3419 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS)
3420 UNSUPPORTED_MSG(EM_SETEDITSTYLE)
3421 UNSUPPORTED_MSG(EM_SETLANGOPTIONS)
3422 UNSUPPORTED_MSG(EM_SETMARGINS)
3423 UNSUPPORTED_MSG(EM_SETPALETTE)
3424 UNSUPPORTED_MSG(EM_SETTABSTOPS)
3425 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS)
3426 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX)
3428 /* Messages specific to Richedit controls */
3430 case EM_STREAMIN:
3431 return ME_StreamIn(editor, wParam, (EDITSTREAM*)lParam, TRUE);
3432 case EM_STREAMOUT:
3433 return ME_StreamOut(editor, wParam, (EDITSTREAM *)lParam);
3434 case WM_GETDLGCODE:
3436 UINT code = DLGC_WANTCHARS|DLGC_WANTTAB|DLGC_WANTARROWS;
3438 if (lParam)
3439 editor->bDialogMode = TRUE;
3440 if (editor->styleFlags & ES_MULTILINE)
3441 code |= DLGC_WANTMESSAGE;
3442 if (!(editor->styleFlags & ES_SAVESEL))
3443 code |= DLGC_HASSETSEL;
3444 return code;
3446 case EM_EMPTYUNDOBUFFER:
3447 ME_EmptyUndoStack(editor);
3448 return 0;
3449 case EM_GETSEL:
3451 /* Note: wParam/lParam can be NULL */
3452 UINT from, to;
3453 PUINT pfrom = wParam ? (PUINT)wParam : &from;
3454 PUINT pto = lParam ? (PUINT)lParam : &to;
3455 ME_GetSelectionOfs(editor, (int *)pfrom, (int *)pto);
3456 if ((*pfrom|*pto) & 0xFFFF0000)
3457 return -1;
3458 return MAKELONG(*pfrom,*pto);
3460 case EM_EXGETSEL:
3462 CHARRANGE *pRange = (CHARRANGE *)lParam;
3463 ME_GetSelectionOfs(editor, &pRange->cpMin, &pRange->cpMax);
3464 TRACE("EM_EXGETSEL = (%d,%d)\n", pRange->cpMin, pRange->cpMax);
3465 return 0;
3467 case EM_SETUNDOLIMIT:
3469 if ((int)wParam < 0)
3470 editor->nUndoLimit = STACK_SIZE_DEFAULT;
3471 else
3472 editor->nUndoLimit = min(wParam, STACK_SIZE_MAX);
3473 /* Setting a max stack size keeps wine from getting killed
3474 for hogging memory. Windows allocates all this memory at once, so
3475 no program would realistically set a value above our maximum. */
3476 return editor->nUndoLimit;
3478 case EM_CANUNDO:
3479 return !list_empty( &editor->undo_stack );
3480 case EM_CANREDO:
3481 return !list_empty( &editor->redo_stack );
3482 case WM_UNDO: /* FIXME: actually not the same */
3483 case EM_UNDO:
3484 return ME_Undo(editor);
3485 case EM_REDO:
3486 return ME_Redo(editor);
3487 case EM_GETOPTIONS:
3489 /* these flags are equivalent to the ES_* counterparts */
3490 DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3491 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN | ECO_SELECTIONBAR;
3492 DWORD settings = editor->styleFlags & mask;
3494 return settings;
3496 case EM_SETFONTSIZE:
3498 CHARFORMAT2W cf;
3499 LONG tmp_size, size;
3500 BOOL is_increase = ((LONG)wParam > 0);
3502 if (editor->mode & TM_PLAINTEXT)
3503 return FALSE;
3505 cf.cbSize = sizeof(cf);
3506 cf.dwMask = CFM_SIZE;
3507 ME_GetSelectionCharFormat(editor, &cf);
3508 tmp_size = (cf.yHeight / 20) + wParam;
3510 if (tmp_size <= 1)
3511 size = 1;
3512 else if (tmp_size > 12 && tmp_size < 28 && tmp_size % 2)
3513 size = tmp_size + (is_increase ? 1 : -1);
3514 else if (tmp_size > 28 && tmp_size < 36)
3515 size = is_increase ? 36 : 28;
3516 else if (tmp_size > 36 && tmp_size < 48)
3517 size = is_increase ? 48 : 36;
3518 else if (tmp_size > 48 && tmp_size < 72)
3519 size = is_increase ? 72 : 48;
3520 else if (tmp_size > 72 && tmp_size < 80)
3521 size = is_increase ? 80 : 72;
3522 else if (tmp_size > 80 && tmp_size < 1638)
3523 size = 10 * (is_increase ? (tmp_size / 10 + 1) : (tmp_size / 10));
3524 else if (tmp_size >= 1638)
3525 size = 1638;
3526 else
3527 size = tmp_size;
3529 cf.yHeight = size * 20; /* convert twips to points */
3530 ME_SetSelectionCharFormat(editor, &cf);
3531 ME_CommitUndo(editor);
3532 ME_WrapMarkedParagraphs(editor);
3533 ME_UpdateScrollBar(editor);
3534 ME_Repaint(editor);
3536 return TRUE;
3538 case EM_SETOPTIONS:
3540 /* these flags are equivalent to ES_* counterparts, except for
3541 * ECO_AUTOWORDSELECTION that doesn't have an ES_* counterpart,
3542 * but is still stored in editor->styleFlags. */
3543 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3544 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
3545 ECO_SELECTIONBAR | ECO_AUTOWORDSELECTION;
3546 DWORD settings = mask & editor->styleFlags;
3547 DWORD oldSettings = settings;
3548 DWORD changedSettings;
3550 switch(wParam)
3552 case ECOOP_SET:
3553 settings = lParam;
3554 break;
3555 case ECOOP_OR:
3556 settings |= lParam;
3557 break;
3558 case ECOOP_AND:
3559 settings &= lParam;
3560 break;
3561 case ECOOP_XOR:
3562 settings ^= lParam;
3564 changedSettings = oldSettings ^ settings;
3566 if (changedSettings) {
3567 editor->styleFlags = (editor->styleFlags & ~mask) | (settings & mask);
3569 if (changedSettings & ECO_SELECTIONBAR)
3571 ITextHost_TxInvalidateRect(editor->texthost, &editor->rcFormat, TRUE);
3572 if (settings & ECO_SELECTIONBAR) {
3573 assert(!editor->selofs);
3574 editor->selofs = SELECTIONBAR_WIDTH;
3575 editor->rcFormat.left += editor->selofs;
3576 } else {
3577 editor->rcFormat.left -= editor->selofs;
3578 editor->selofs = 0;
3580 ME_RewrapRepaint(editor);
3583 if ((changedSettings & settings & ES_NOHIDESEL) && !editor->bHaveFocus)
3584 ME_InvalidateSelection( editor );
3586 if (changedSettings & settings & ECO_VERTICAL)
3587 FIXME("ECO_VERTICAL not implemented yet!\n");
3588 if (changedSettings & settings & ECO_AUTOHSCROLL)
3589 FIXME("ECO_AUTOHSCROLL not implemented yet!\n");
3590 if (changedSettings & settings & ECO_AUTOVSCROLL)
3591 FIXME("ECO_AUTOVSCROLL not implemented yet!\n");
3592 if (changedSettings & settings & ECO_WANTRETURN)
3593 FIXME("ECO_WANTRETURN not implemented yet!\n");
3594 if (changedSettings & settings & ECO_AUTOWORDSELECTION)
3595 FIXME("ECO_AUTOWORDSELECTION not implemented yet!\n");
3598 return settings;
3600 case EM_SETSEL:
3602 return handle_EM_EXSETSEL( editor, wParam, lParam );
3604 case EM_SETSCROLLPOS:
3606 POINT *point = (POINT *)lParam;
3607 ME_ScrollAbs(editor, point->x, point->y);
3608 return 0;
3610 case EM_AUTOURLDETECT:
3612 if (wParam==1 || wParam ==0)
3614 editor->AutoURLDetect_bEnable = (BOOL)wParam;
3615 return 0;
3617 return E_INVALIDARG;
3619 case EM_GETAUTOURLDETECT:
3621 return editor->AutoURLDetect_bEnable;
3623 case EM_EXSETSEL:
3625 CHARRANGE range = *(CHARRANGE *)lParam;
3627 return handle_EM_EXSETSEL( editor, range.cpMin, range.cpMax );
3629 case EM_SHOWSCROLLBAR:
3631 DWORD flags;
3633 switch (wParam)
3635 case SB_HORZ:
3636 flags = WS_HSCROLL;
3637 break;
3638 case SB_VERT:
3639 flags = WS_VSCROLL;
3640 break;
3641 case SB_BOTH:
3642 flags = WS_HSCROLL|WS_VSCROLL;
3643 break;
3644 default:
3645 return 0;
3648 if (lParam) {
3649 editor->styleFlags |= flags;
3650 if (flags & WS_HSCROLL)
3651 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ,
3652 editor->nTotalWidth > editor->sizeWindow.cx);
3653 if (flags & WS_VSCROLL)
3654 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT,
3655 editor->nTotalLength > editor->sizeWindow.cy);
3656 } else {
3657 editor->styleFlags &= ~flags;
3658 ITextHost_TxShowScrollBar(editor->texthost, wParam, FALSE);
3660 return 0;
3662 case EM_SETTEXTEX:
3664 LPWSTR wszText;
3665 SETTEXTEX *pStruct = (SETTEXTEX *)wParam;
3666 int from, to, len;
3667 ME_Style *style;
3668 BOOL bRtf, bUnicode, bSelection, bUTF8;
3669 int oldModify = editor->nModifyStep;
3670 static const char utf8_bom[] = {0xef, 0xbb, 0xbf};
3672 if (!pStruct) return 0;
3674 /* If we detect ascii rtf at the start of the string,
3675 * we know it isn't unicode. */
3676 bRtf = (lParam && (!strncmp((char *)lParam, "{\\rtf", 5) ||
3677 !strncmp((char *)lParam, "{\\urtf", 6)));
3678 bUnicode = !bRtf && pStruct->codepage == CP_UNICODE;
3679 bUTF8 = (lParam && (!strncmp((char *)lParam, utf8_bom, 3)));
3681 TRACE("EM_SETTEXTEX - %s, flags %d, cp %d\n",
3682 bUnicode ? debugstr_w((LPCWSTR)lParam) : debugstr_a((LPCSTR)lParam),
3683 pStruct->flags, pStruct->codepage);
3685 bSelection = (pStruct->flags & ST_SELECTION) != 0;
3686 if (bSelection) {
3687 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3688 style = ME_GetSelectionInsertStyle(editor);
3689 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to - from, FALSE);
3690 } else {
3691 ME_Cursor start;
3692 ME_SetCursorToStart(editor, &start);
3693 ME_InternalDeleteText(editor, &start, ME_GetTextLength(editor), FALSE);
3694 style = editor->pBuffer->pDefaultStyle;
3697 if (bRtf) {
3698 ME_StreamInRTFString(editor, bSelection, (char *)lParam);
3699 if (bSelection) {
3700 /* FIXME: The length returned doesn't include the rtf control
3701 * characters, only the actual text. */
3702 len = lParam ? strlen((char *)lParam) : 0;
3704 } else {
3705 if (bUTF8 && !bUnicode) {
3706 wszText = ME_ToUnicode(CP_UTF8, (void *)(lParam+3), &len);
3707 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3708 ME_EndToUnicode(CP_UTF8, wszText);
3709 } else {
3710 wszText = ME_ToUnicode(pStruct->codepage, (void *)lParam, &len);
3711 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3712 ME_EndToUnicode(pStruct->codepage, wszText);
3716 if (bSelection) {
3717 ME_ReleaseStyle(style);
3718 ME_UpdateSelectionLinkAttribute(editor);
3719 } else {
3720 ME_Cursor cursor;
3721 len = 1;
3722 ME_SetCursorToStart(editor, &cursor);
3723 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3725 ME_CommitUndo(editor);
3726 if (!(pStruct->flags & ST_KEEPUNDO))
3728 editor->nModifyStep = oldModify;
3729 ME_EmptyUndoStack(editor);
3731 ME_UpdateRepaint(editor, FALSE);
3732 return len;
3734 case EM_SETBKGNDCOLOR:
3736 LRESULT lColor;
3737 if (editor->rgbBackColor != -1) {
3738 DeleteObject(editor->hbrBackground);
3739 lColor = editor->rgbBackColor;
3741 else lColor = ITextHost_TxGetSysColor(editor->texthost, COLOR_WINDOW);
3743 if (wParam)
3745 editor->rgbBackColor = -1;
3746 editor->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3748 else
3750 editor->rgbBackColor = lParam;
3751 editor->hbrBackground = CreateSolidBrush(editor->rgbBackColor);
3753 ITextHost_TxInvalidateRect(editor->texthost, NULL, TRUE);
3754 return lColor;
3756 case EM_GETMODIFY:
3757 return editor->nModifyStep == 0 ? 0 : -1;
3758 case EM_SETMODIFY:
3760 if (wParam)
3761 editor->nModifyStep = 1;
3762 else
3763 editor->nModifyStep = 0;
3765 return 0;
3767 case EM_SETREADONLY:
3769 if (wParam)
3770 editor->styleFlags |= ES_READONLY;
3771 else
3772 editor->styleFlags &= ~ES_READONLY;
3773 return 1;
3775 case EM_SETEVENTMASK:
3777 DWORD nOldMask = editor->nEventMask;
3779 editor->nEventMask = lParam;
3780 return nOldMask;
3782 case EM_GETEVENTMASK:
3783 return editor->nEventMask;
3784 case EM_SETCHARFORMAT:
3786 CHARFORMAT2W buf, *p;
3787 BOOL bRepaint = TRUE;
3788 p = ME_ToCF2W(&buf, (CHARFORMAT2W *)lParam);
3789 if (p == NULL) return 0;
3790 if (wParam & SCF_ALL) {
3791 if (editor->mode & TM_PLAINTEXT) {
3792 ME_SetDefaultCharFormat(editor, p);
3793 } else {
3794 ME_Cursor start;
3795 ME_SetCursorToStart(editor, &start);
3796 ME_SetCharFormat(editor, &start, NULL, p);
3797 editor->nModifyStep = 1;
3799 } else if (wParam & SCF_SELECTION) {
3800 if (editor->mode & TM_PLAINTEXT)
3801 return 0;
3802 if (wParam & SCF_WORD) {
3803 ME_Cursor start;
3804 ME_Cursor end = editor->pCursors[0];
3805 ME_MoveCursorWords(editor, &end, +1);
3806 start = end;
3807 ME_MoveCursorWords(editor, &start, -1);
3808 ME_SetCharFormat(editor, &start, &end, p);
3810 bRepaint = ME_IsSelection(editor);
3811 ME_SetSelectionCharFormat(editor, p);
3812 if (bRepaint) editor->nModifyStep = 1;
3813 } else { /* SCF_DEFAULT */
3814 ME_SetDefaultCharFormat(editor, p);
3816 ME_CommitUndo(editor);
3817 if (bRepaint)
3819 ME_WrapMarkedParagraphs(editor);
3820 ME_UpdateScrollBar(editor);
3821 ME_Repaint(editor);
3823 return 1;
3825 case EM_GETCHARFORMAT:
3827 CHARFORMAT2W tmp, *dst = (CHARFORMAT2W *)lParam;
3828 if (dst->cbSize != sizeof(CHARFORMATA) &&
3829 dst->cbSize != sizeof(CHARFORMATW) &&
3830 dst->cbSize != sizeof(CHARFORMAT2A) &&
3831 dst->cbSize != sizeof(CHARFORMAT2W))
3832 return 0;
3833 tmp.cbSize = sizeof(tmp);
3834 if (!wParam)
3835 ME_GetDefaultCharFormat(editor, &tmp);
3836 else
3837 ME_GetSelectionCharFormat(editor, &tmp);
3838 ME_CopyToCFAny(dst, &tmp);
3839 return tmp.dwMask;
3841 case EM_SETPARAFORMAT:
3843 BOOL result = ME_SetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
3844 ME_WrapMarkedParagraphs(editor);
3845 ME_UpdateScrollBar(editor);
3846 ME_Repaint(editor);
3847 ME_CommitUndo(editor);
3848 return result;
3850 case EM_GETPARAFORMAT:
3851 ME_GetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
3852 return ((PARAFORMAT2 *)lParam)->dwMask;
3853 case EM_GETFIRSTVISIBLELINE:
3855 ME_DisplayItem *p = editor->pBuffer->pFirst;
3856 int y = editor->vert_si.nPos;
3857 int ypara = 0;
3858 int count = 0;
3859 int ystart, yend;
3860 while(p) {
3861 p = ME_FindItemFwd(p, diStartRowOrParagraphOrEnd);
3862 if (p->type == diTextEnd)
3863 break;
3864 if (p->type == diParagraph) {
3865 ypara = p->member.para.pt.y;
3866 continue;
3868 ystart = ypara + p->member.row.pt.y;
3869 yend = ystart + p->member.row.nHeight;
3870 if (y < yend) {
3871 break;
3873 count++;
3875 return count;
3877 case EM_HIDESELECTION:
3879 editor->bHideSelection = (wParam != 0);
3880 ME_InvalidateSelection(editor);
3881 return 0;
3883 case EM_LINESCROLL:
3885 if (!(editor->styleFlags & ES_MULTILINE))
3886 return FALSE;
3887 ME_ScrollDown( editor, lParam * get_default_line_height( editor ) );
3888 return TRUE;
3890 case WM_CLEAR:
3892 int from, to;
3893 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3894 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3895 ME_CommitUndo(editor);
3896 ME_UpdateRepaint(editor, TRUE);
3897 return 0;
3899 case EM_REPLACESEL:
3901 int len = 0;
3902 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
3903 LPWSTR wszText = ME_ToUnicode(codepage, (void *)lParam, &len);
3905 TRACE("EM_REPLACESEL - %s\n", debugstr_w(wszText));
3907 ME_ReplaceSel(editor, !!wParam, wszText, len);
3908 ME_EndToUnicode(codepage, wszText);
3909 return len;
3911 case EM_SCROLLCARET:
3912 ME_EnsureVisible(editor, &editor->pCursors[0]);
3913 return 0;
3914 case WM_SETFONT:
3916 LOGFONTW lf;
3917 CHARFORMAT2W fmt;
3918 HDC hDC;
3919 BOOL bRepaint = LOWORD(lParam);
3921 if (!wParam)
3922 wParam = (WPARAM)GetStockObject(SYSTEM_FONT);
3924 if (!GetObjectW((HGDIOBJ)wParam, sizeof(LOGFONTW), &lf))
3925 return 0;
3927 hDC = ITextHost_TxGetDC(editor->texthost);
3928 ME_CharFormatFromLogFont(hDC, &lf, &fmt);
3929 ITextHost_TxReleaseDC(editor->texthost, hDC);
3930 if (editor->mode & TM_RICHTEXT) {
3931 ME_Cursor start;
3932 ME_SetCursorToStart(editor, &start);
3933 ME_SetCharFormat(editor, &start, NULL, &fmt);
3935 ME_SetDefaultCharFormat(editor, &fmt);
3937 ME_CommitUndo(editor);
3938 ME_MarkAllForWrapping(editor);
3939 ME_WrapMarkedParagraphs(editor);
3940 ME_UpdateScrollBar(editor);
3941 if (bRepaint)
3942 ME_Repaint(editor);
3943 return 0;
3945 case WM_SETTEXT:
3947 ME_Cursor cursor;
3948 ME_SetCursorToStart(editor, &cursor);
3949 ME_InternalDeleteText(editor, &cursor, ME_GetTextLength(editor), FALSE);
3950 if (lParam)
3952 TRACE("WM_SETTEXT lParam==%lx\n",lParam);
3953 if (!strncmp((char *)lParam, "{\\rtf", 5) ||
3954 !strncmp((char *)lParam, "{\\urtf", 6))
3956 /* Undocumented: WM_SETTEXT supports RTF text */
3957 ME_StreamInRTFString(editor, 0, (char *)lParam);
3959 else
3960 ME_SetText(editor, (void*)lParam, unicode);
3962 else
3963 TRACE("WM_SETTEXT - NULL\n");
3964 ME_SetCursorToStart(editor, &cursor);
3965 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3966 ME_SetSelection(editor, 0, 0);
3967 editor->nModifyStep = 0;
3968 ME_CommitUndo(editor);
3969 ME_EmptyUndoStack(editor);
3970 ME_UpdateRepaint(editor, FALSE);
3971 return 1;
3973 case EM_CANPASTE:
3975 UINT nRTFFormat = RegisterClipboardFormatA("Rich Text Format");
3976 if (IsClipboardFormatAvailable(nRTFFormat))
3977 return TRUE;
3978 if (IsClipboardFormatAvailable(CF_UNICODETEXT))
3979 return TRUE;
3980 return FALSE;
3982 case WM_PASTE:
3983 case WM_MBUTTONDOWN:
3984 ME_Paste(editor);
3985 return 0;
3986 case WM_CUT:
3987 case WM_COPY:
3989 int nFrom, nTo, nStartCur = ME_GetSelectionOfs(editor, &nFrom, &nTo);
3990 int nChars = nTo - nFrom;
3991 ME_Cursor *selStart = &editor->pCursors[nStartCur];
3993 if (ME_Copy(editor, selStart, nChars) && msg == WM_CUT)
3995 ME_InternalDeleteText(editor, selStart, nChars, FALSE);
3996 ME_CommitUndo(editor);
3997 ME_UpdateRepaint(editor, TRUE);
3999 return 0;
4001 case WM_GETTEXTLENGTH:
4003 GETTEXTLENGTHEX how;
4005 /* CR/LF conversion required in 2.0 mode, verbatim in 1.0 mode */
4006 how.flags = GTL_CLOSE | (editor->bEmulateVersion10 ? 0 : GTL_USECRLF) | GTL_NUMCHARS;
4007 how.codepage = unicode ? CP_UNICODE : CP_ACP;
4008 return ME_GetTextLengthEx(editor, &how);
4010 case EM_GETTEXTLENGTHEX:
4011 return ME_GetTextLengthEx(editor, (GETTEXTLENGTHEX *)wParam);
4012 case WM_GETTEXT:
4014 GETTEXTEX ex;
4015 ex.cb = wParam * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
4016 ex.flags = GT_USECRLF;
4017 ex.codepage = unicode ? CP_UNICODE : CP_ACP;
4018 ex.lpDefaultChar = NULL;
4019 ex.lpUsedDefChar = NULL;
4020 return ME_GetTextEx(editor, &ex, lParam);
4022 case EM_GETTEXTEX:
4023 return ME_GetTextEx(editor, (GETTEXTEX*)wParam, lParam);
4024 case EM_GETSELTEXT:
4026 int nFrom, nTo, nStartCur = ME_GetSelectionOfs(editor, &nFrom, &nTo);
4027 ME_Cursor *from = &editor->pCursors[nStartCur];
4028 return ME_GetTextRange(editor, (WCHAR *)lParam, from,
4029 nTo - nFrom, unicode);
4031 case EM_GETSCROLLPOS:
4033 POINT *point = (POINT *)lParam;
4034 point->x = editor->horz_si.nPos;
4035 point->y = editor->vert_si.nPos;
4036 /* 16-bit scaled value is returned as stored in scrollinfo */
4037 if (editor->horz_si.nMax > 0xffff)
4038 point->x = MulDiv(point->x, 0xffff, editor->horz_si.nMax);
4039 if (editor->vert_si.nMax > 0xffff)
4040 point->y = MulDiv(point->y, 0xffff, editor->vert_si.nMax);
4041 return 1;
4043 case EM_GETTEXTRANGE:
4045 TEXTRANGEW *rng = (TEXTRANGEW *)lParam;
4046 ME_Cursor start;
4047 int nStart = rng->chrg.cpMin;
4048 int nEnd = rng->chrg.cpMax;
4049 int textlength = ME_GetTextLength(editor);
4051 TRACE("EM_GETTEXTRANGE min=%d max=%d unicode=%d textlength=%d\n",
4052 rng->chrg.cpMin, rng->chrg.cpMax, unicode, textlength);
4053 if (nStart < 0) return 0;
4054 if ((nStart == 0 && nEnd == -1) || nEnd > textlength)
4055 nEnd = textlength;
4056 if (nStart >= nEnd) return 0;
4058 ME_CursorFromCharOfs(editor, nStart, &start);
4059 return ME_GetTextRange(editor, rng->lpstrText, &start, nEnd - nStart, unicode);
4061 case EM_GETLINE:
4063 ME_DisplayItem *run;
4064 const unsigned int nMaxChars = *(WORD *) lParam;
4065 unsigned int nCharsLeft = nMaxChars;
4066 char *dest = (char *) lParam;
4067 BOOL wroteNull = FALSE;
4069 TRACE("EM_GETLINE: row=%d, nMaxChars=%d (%s)\n", (int) wParam, nMaxChars,
4070 unicode ? "Unicode" : "Ansi");
4072 run = ME_FindRowWithNumber(editor, wParam);
4073 if (run == NULL)
4074 return 0;
4076 while (nCharsLeft && (run = ME_FindItemFwd(run, diRunOrStartRow))
4077 && run->type == diRun)
4079 WCHAR *str = get_text( &run->member.run, 0 );
4080 unsigned int nCopy;
4082 nCopy = min(nCharsLeft, run->member.run.len);
4084 if (unicode)
4085 memcpy(dest, str, nCopy * sizeof(WCHAR));
4086 else
4087 nCopy = WideCharToMultiByte(CP_ACP, 0, str, nCopy, dest,
4088 nCharsLeft, NULL, NULL);
4089 dest += nCopy * (unicode ? sizeof(WCHAR) : 1);
4090 nCharsLeft -= nCopy;
4093 /* append line termination, space allowing */
4094 if (nCharsLeft > 0)
4096 if (unicode)
4097 *((WCHAR *)dest) = '\0';
4098 else
4099 *dest = '\0';
4100 nCharsLeft--;
4101 wroteNull = TRUE;
4104 TRACE("EM_GETLINE: got %u characters\n", nMaxChars - nCharsLeft);
4105 return nMaxChars - nCharsLeft - (wroteNull ? 1 : 0);
4107 case EM_GETLINECOUNT:
4109 ME_DisplayItem *item = editor->pBuffer->pFirst->next;
4110 int nRows = 0;
4112 ME_DisplayItem *prev_para = NULL, *last_para = NULL;
4114 while (item != editor->pBuffer->pLast)
4116 assert(item->type == diParagraph);
4117 prev_para = ME_FindItemBack(item, diRun);
4118 if (prev_para) {
4119 assert(prev_para->member.run.nFlags & MERF_ENDPARA);
4121 nRows += item->member.para.nRows;
4122 item = item->member.para.next_para;
4124 last_para = ME_FindItemBack(item, diRun);
4125 assert(last_para);
4126 assert(last_para->member.run.nFlags & MERF_ENDPARA);
4127 if (editor->bEmulateVersion10 && prev_para &&
4128 last_para->member.run.nCharOfs == 0 &&
4129 prev_para->member.run.len == 1 &&
4130 *get_text( &prev_para->member.run, 0 ) == '\r')
4132 /* In 1.0 emulation, the last solitary \r at the very end of the text
4133 (if one exists) is NOT a line break.
4134 FIXME: this is an ugly hack. This should have a more regular model. */
4135 nRows--;
4138 TRACE("EM_GETLINECOUNT: nRows==%d\n", nRows);
4139 return max(1, nRows);
4141 case EM_LINEFROMCHAR:
4143 if (wParam == -1)
4144 return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(&editor->pCursors[1]));
4145 else
4146 return ME_RowNumberFromCharOfs(editor, wParam);
4148 case EM_EXLINEFROMCHAR:
4150 if (lParam == -1)
4151 return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(&editor->pCursors[1]));
4152 else
4153 return ME_RowNumberFromCharOfs(editor, lParam);
4155 case EM_LINEINDEX:
4157 ME_DisplayItem *item, *para;
4158 int nCharOfs;
4160 if (wParam == -1)
4161 item = ME_FindItemBack(editor->pCursors[0].pRun, diStartRow);
4162 else
4163 item = ME_FindRowWithNumber(editor, wParam);
4164 if (!item)
4165 return -1;
4166 para = ME_GetParagraph(item);
4167 item = ME_FindItemFwd(item, diRun);
4168 nCharOfs = para->member.para.nCharOfs + item->member.run.nCharOfs;
4169 TRACE("EM_LINEINDEX: nCharOfs==%d\n", nCharOfs);
4170 return nCharOfs;
4172 case EM_LINELENGTH:
4174 ME_DisplayItem *item, *item_end;
4175 int nChars = 0, nThisLineOfs = 0, nNextLineOfs = 0;
4176 ME_DisplayItem *para, *run;
4178 if (wParam > ME_GetTextLength(editor))
4179 return 0;
4180 if (wParam == -1)
4182 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
4183 return 0;
4185 ME_RunOfsFromCharOfs(editor, wParam, &para, &run, NULL);
4186 item = ME_RowStart(run);
4187 nThisLineOfs = ME_CharOfsFromRunOfs(editor, para, ME_FindItemFwd(item, diRun), 0);
4188 item_end = ME_FindItemFwd(item, diStartRowOrParagraphOrEnd);
4189 if (item_end->type == diStartRow) {
4190 nNextLineOfs = ME_CharOfsFromRunOfs(editor, para, ME_FindItemFwd(item_end, diRun), 0);
4191 } else {
4192 ME_DisplayItem *endRun = ME_FindItemBack(item_end, diRun);
4193 assert(endRun && endRun->member.run.nFlags & MERF_ENDPARA);
4194 nNextLineOfs = item_end->member.para.nCharOfs - endRun->member.run.len;
4196 nChars = nNextLineOfs - nThisLineOfs;
4197 TRACE("EM_LINELENGTH(%ld)==%d\n",wParam, nChars);
4198 return nChars;
4200 case EM_EXLIMITTEXT:
4202 if ((int)lParam < 0)
4203 return 0;
4204 if (lParam == 0)
4205 editor->nTextLimit = 65536;
4206 else
4207 editor->nTextLimit = (int) lParam;
4208 return 0;
4210 case EM_LIMITTEXT:
4212 if (wParam == 0)
4213 editor->nTextLimit = 65536;
4214 else
4215 editor->nTextLimit = (int) wParam;
4216 return 0;
4218 case EM_GETLIMITTEXT:
4220 return editor->nTextLimit;
4222 case EM_FINDTEXT:
4224 LRESULT r;
4225 if(!unicode){
4226 FINDTEXTA *ft = (FINDTEXTA *)lParam;
4227 int nChars = MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, NULL, 0);
4228 WCHAR *tmp;
4230 if ((tmp = ALLOC_N_OBJ(WCHAR, nChars)) != NULL)
4231 MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, tmp, nChars);
4232 r = ME_FindText(editor, wParam, &ft->chrg, tmp, NULL);
4233 FREE_OBJ( tmp );
4234 }else{
4235 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4236 r = ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4238 return r;
4240 case EM_FINDTEXTEX:
4242 LRESULT r;
4243 if(!unicode){
4244 FINDTEXTEXA *ex = (FINDTEXTEXA *)lParam;
4245 int nChars = MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, NULL, 0);
4246 WCHAR *tmp;
4248 if ((tmp = ALLOC_N_OBJ(WCHAR, nChars)) != NULL)
4249 MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, tmp, nChars);
4250 r = ME_FindText(editor, wParam, &ex->chrg, tmp, &ex->chrgText);
4251 FREE_OBJ( tmp );
4252 }else{
4253 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4254 r = ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4256 return r;
4258 case EM_FINDTEXTW:
4260 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4261 return ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4263 case EM_FINDTEXTEXW:
4265 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4266 return ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4268 case EM_GETZOOM:
4269 if (!wParam || !lParam)
4270 return FALSE;
4271 *(int *)wParam = editor->nZoomNumerator;
4272 *(int *)lParam = editor->nZoomDenominator;
4273 return TRUE;
4274 case EM_SETZOOM:
4275 return ME_SetZoom(editor, wParam, lParam);
4276 case EM_CHARFROMPOS:
4278 ME_Cursor cursor;
4279 if (ME_CharFromPos(editor, ((POINTL *)lParam)->x, ((POINTL *)lParam)->y,
4280 &cursor, NULL))
4281 return ME_GetCursorOfs(&cursor);
4282 else
4283 return -1;
4285 case EM_POSFROMCHAR:
4287 ME_DisplayItem *pPara, *pRun;
4288 int nCharOfs, nOffset, nLength;
4289 POINTL pt = {0,0};
4291 nCharOfs = wParam;
4292 /* detect which API version we're dealing with */
4293 if (wParam >= 0x40000)
4294 nCharOfs = lParam;
4295 nLength = ME_GetTextLength(editor);
4296 nCharOfs = min(nCharOfs, nLength);
4297 nCharOfs = max(nCharOfs, 0);
4299 ME_RunOfsFromCharOfs(editor, nCharOfs, &pPara, &pRun, &nOffset);
4300 assert(pRun->type == diRun);
4301 pt.y = pRun->member.run.pt.y;
4302 pt.x = pRun->member.run.pt.x + ME_PointFromChar(editor, &pRun->member.run, nOffset, TRUE);
4303 pt.y += pPara->member.para.pt.y + editor->rcFormat.top;
4304 pt.x += editor->rcFormat.left;
4306 pt.x -= editor->horz_si.nPos;
4307 pt.y -= editor->vert_si.nPos;
4309 if (wParam >= 0x40000) {
4310 *(POINTL *)wParam = pt;
4312 return (wParam >= 0x40000) ? 0 : MAKELONG( pt.x, pt.y );
4314 case WM_CREATE:
4315 return ME_WmCreate(editor, lParam, unicode);
4316 case WM_DESTROY:
4317 ME_DestroyEditor(editor);
4318 return 0;
4319 case WM_SETCURSOR:
4321 POINT cursor_pos;
4322 if (wParam == (WPARAM)editor->hWnd && GetCursorPos(&cursor_pos) &&
4323 ScreenToClient(editor->hWnd, &cursor_pos))
4324 ME_LinkNotify(editor, msg, 0, MAKELPARAM(cursor_pos.x, cursor_pos.y));
4325 return ME_SetCursor(editor);
4327 case WM_LBUTTONDBLCLK:
4328 case WM_LBUTTONDOWN:
4330 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4331 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4332 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4333 return 0;
4334 ITextHost_TxSetFocus(editor->texthost);
4335 ME_LButtonDown(editor, (short)LOWORD(lParam), (short)HIWORD(lParam),
4336 ME_CalculateClickCount(editor, msg, wParam, lParam));
4337 ITextHost_TxSetCapture(editor->texthost, TRUE);
4338 editor->bMouseCaptured = TRUE;
4339 ME_LinkNotify(editor, msg, wParam, lParam);
4340 if (!ME_SetCursor(editor)) goto do_default;
4341 break;
4343 case WM_MOUSEMOVE:
4344 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4345 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4346 return 0;
4347 if (editor->bMouseCaptured)
4348 ME_MouseMove(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
4349 else
4350 ME_LinkNotify(editor, msg, wParam, lParam);
4351 /* Set cursor if mouse is captured, since WM_SETCURSOR won't be received. */
4352 if (editor->bMouseCaptured)
4353 ME_SetCursor(editor);
4354 break;
4355 case WM_LBUTTONUP:
4356 if (editor->bMouseCaptured) {
4357 ITextHost_TxSetCapture(editor->texthost, FALSE);
4358 editor->bMouseCaptured = FALSE;
4360 if (editor->nSelectionType == stDocument)
4361 editor->nSelectionType = stPosition;
4362 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4363 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4364 return 0;
4365 else
4367 ME_SetCursor(editor);
4368 ME_LinkNotify(editor, msg, wParam, lParam);
4370 break;
4371 case WM_RBUTTONUP:
4372 case WM_RBUTTONDOWN:
4373 case WM_RBUTTONDBLCLK:
4374 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4375 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4376 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4377 return 0;
4378 ME_LinkNotify(editor, msg, wParam, lParam);
4379 goto do_default;
4380 case WM_CONTEXTMENU:
4381 if (!ME_ShowContextMenu(editor, (short)LOWORD(lParam), (short)HIWORD(lParam)))
4382 goto do_default;
4383 break;
4384 case WM_SETFOCUS:
4385 editor->bHaveFocus = TRUE;
4386 ME_ShowCaret(editor);
4387 ME_SendOldNotify(editor, EN_SETFOCUS);
4388 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4389 ME_InvalidateSelection( editor );
4390 return 0;
4391 case WM_KILLFOCUS:
4392 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4393 editor->bHaveFocus = FALSE;
4394 editor->wheel_remain = 0;
4395 ME_HideCaret(editor);
4396 ME_SendOldNotify(editor, EN_KILLFOCUS);
4397 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4398 ME_InvalidateSelection( editor );
4399 return 0;
4400 case WM_COMMAND:
4401 TRACE("editor wnd command = %d\n", LOWORD(wParam));
4402 return 0;
4403 case WM_KEYUP:
4404 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4405 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4406 return 0;
4407 goto do_default;
4408 case WM_KEYDOWN:
4409 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4410 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4411 return 0;
4412 if (ME_KeyDown(editor, LOWORD(wParam)))
4413 return 0;
4414 goto do_default;
4415 case WM_CHAR:
4416 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4417 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4418 return 0;
4419 return ME_Char(editor, wParam, lParam, unicode);
4420 case WM_UNICHAR:
4421 if (unicode)
4423 if(wParam == UNICODE_NOCHAR) return TRUE;
4424 if(wParam <= 0x000fffff)
4426 if(wParam > 0xffff) /* convert to surrogates */
4428 wParam -= 0x10000;
4429 ME_Char(editor, (wParam >> 10) + 0xd800, 0, TRUE);
4430 ME_Char(editor, (wParam & 0x03ff) + 0xdc00, 0, TRUE);
4431 } else {
4432 ME_Char(editor, wParam, 0, TRUE);
4435 return 0;
4437 break;
4438 case EM_STOPGROUPTYPING:
4439 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4440 return 0;
4441 case WM_HSCROLL:
4443 const int scrollUnit = 7;
4445 switch(LOWORD(wParam))
4447 case SB_LEFT:
4448 ME_ScrollAbs(editor, 0, 0);
4449 break;
4450 case SB_RIGHT:
4451 ME_ScrollAbs(editor,
4452 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4453 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4454 break;
4455 case SB_LINELEFT:
4456 ME_ScrollLeft(editor, scrollUnit);
4457 break;
4458 case SB_LINERIGHT:
4459 ME_ScrollRight(editor, scrollUnit);
4460 break;
4461 case SB_PAGELEFT:
4462 ME_ScrollLeft(editor, editor->sizeWindow.cx);
4463 break;
4464 case SB_PAGERIGHT:
4465 ME_ScrollRight(editor, editor->sizeWindow.cx);
4466 break;
4467 case SB_THUMBTRACK:
4468 case SB_THUMBPOSITION:
4470 int pos = HIWORD(wParam);
4471 if (editor->horz_si.nMax > 0xffff)
4472 pos = MulDiv(pos, editor->horz_si.nMax, 0xffff);
4473 ME_HScrollAbs(editor, pos);
4474 break;
4477 break;
4479 case EM_SCROLL: /* fall through */
4480 case WM_VSCROLL:
4482 int origNPos;
4483 int lineHeight = get_default_line_height( editor );
4485 origNPos = editor->vert_si.nPos;
4487 switch(LOWORD(wParam))
4489 case SB_TOP:
4490 ME_ScrollAbs(editor, 0, 0);
4491 break;
4492 case SB_BOTTOM:
4493 ME_ScrollAbs(editor,
4494 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4495 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4496 break;
4497 case SB_LINEUP:
4498 ME_ScrollUp(editor,lineHeight);
4499 break;
4500 case SB_LINEDOWN:
4501 ME_ScrollDown(editor,lineHeight);
4502 break;
4503 case SB_PAGEUP:
4504 ME_ScrollUp(editor,editor->sizeWindow.cy);
4505 break;
4506 case SB_PAGEDOWN:
4507 ME_ScrollDown(editor,editor->sizeWindow.cy);
4508 break;
4509 case SB_THUMBTRACK:
4510 case SB_THUMBPOSITION:
4512 int pos = HIWORD(wParam);
4513 if (editor->vert_si.nMax > 0xffff)
4514 pos = MulDiv(pos, editor->vert_si.nMax, 0xffff);
4515 ME_VScrollAbs(editor, pos);
4516 break;
4519 if (msg == EM_SCROLL)
4520 return 0x00010000 | (((editor->vert_si.nPos - origNPos)/lineHeight) & 0xffff);
4521 break;
4523 case WM_MOUSEWHEEL:
4525 int delta;
4526 BOOL ctrl_is_down;
4528 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4529 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4530 return 0;
4532 ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
4534 delta = GET_WHEEL_DELTA_WPARAM(wParam);
4536 /* if scrolling changes direction, ignore left overs */
4537 if ((delta < 0 && editor->wheel_remain < 0) ||
4538 (delta > 0 && editor->wheel_remain > 0))
4539 editor->wheel_remain += delta;
4540 else
4541 editor->wheel_remain = delta;
4543 if (editor->wheel_remain)
4545 if (ctrl_is_down) {
4546 int numerator;
4547 if (!editor->nZoomNumerator || !editor->nZoomDenominator)
4549 numerator = 100;
4550 } else {
4551 numerator = editor->nZoomNumerator * 100 / editor->nZoomDenominator;
4553 numerator += calc_wheel_change( &editor->wheel_remain, 10 );
4554 if (numerator >= 10 && numerator <= 500)
4555 ME_SetZoom(editor, numerator, 100);
4556 } else {
4557 UINT max_lines = 3;
4558 int lines = 0;
4560 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES, 0, &max_lines, 0 );
4561 if (max_lines)
4562 lines = calc_wheel_change( &editor->wheel_remain, (int)max_lines );
4563 if (lines)
4564 ME_ScrollDown( editor, -lines * get_default_line_height( editor ) );
4567 break;
4569 case EM_GETRECT:
4571 *((RECT *)lParam) = editor->rcFormat;
4572 if (editor->bDefaultFormatRect)
4573 ((RECT *)lParam)->left -= editor->selofs;
4574 return 0;
4576 case EM_SETRECT:
4577 case EM_SETRECTNP:
4579 if (lParam)
4581 int border = 0;
4582 RECT clientRect;
4583 RECT *rc = (RECT *)lParam;
4585 border = editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
4586 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4587 if (wParam == 0)
4589 editor->rcFormat.top = max(0, rc->top - border);
4590 editor->rcFormat.left = max(0, rc->left - border);
4591 editor->rcFormat.bottom = min(clientRect.bottom, rc->bottom);
4592 editor->rcFormat.right = min(clientRect.right, rc->right + border);
4593 } else if (wParam == 1) {
4594 /* MSDN incorrectly says a wParam value of 1 causes the
4595 * lParam rect to be used as a relative offset,
4596 * however, the tests show it just prevents min/max bound
4597 * checking. */
4598 editor->rcFormat.top = rc->top - border;
4599 editor->rcFormat.left = rc->left - border;
4600 editor->rcFormat.bottom = rc->bottom;
4601 editor->rcFormat.right = rc->right + border;
4602 } else {
4603 return 0;
4605 editor->bDefaultFormatRect = FALSE;
4607 else
4609 ME_SetDefaultFormatRect(editor);
4610 editor->bDefaultFormatRect = TRUE;
4612 ME_MarkAllForWrapping(editor);
4613 ME_WrapMarkedParagraphs(editor);
4614 ME_UpdateScrollBar(editor);
4615 if (msg != EM_SETRECTNP)
4616 ME_Repaint(editor);
4617 return 0;
4619 case EM_REQUESTRESIZE:
4620 ME_SendRequestResize(editor, TRUE);
4621 return 0;
4622 case WM_SETREDRAW:
4623 goto do_default;
4624 case WM_WINDOWPOSCHANGED:
4626 RECT clientRect;
4627 WINDOWPOS *winpos = (WINDOWPOS *)lParam;
4629 if (winpos->flags & SWP_NOCLIENTSIZE) goto do_default;
4630 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4631 if (editor->bDefaultFormatRect) {
4632 ME_SetDefaultFormatRect(editor);
4633 } else {
4634 editor->rcFormat.right += clientRect.right - editor->prevClientRect.right;
4635 editor->rcFormat.bottom += clientRect.bottom - editor->prevClientRect.bottom;
4637 editor->prevClientRect = clientRect;
4638 ME_RewrapRepaint(editor);
4639 goto do_default;
4641 /* IME messages to make richedit controls IME aware */
4642 case WM_IME_SETCONTEXT:
4643 case WM_IME_CONTROL:
4644 case WM_IME_SELECT:
4645 case WM_IME_COMPOSITIONFULL:
4646 return 0;
4647 case WM_IME_STARTCOMPOSITION:
4649 editor->imeStartIndex=ME_GetCursorOfs(&editor->pCursors[0]);
4650 ME_DeleteSelection(editor);
4651 ME_CommitUndo(editor);
4652 ME_UpdateRepaint(editor, FALSE);
4653 return 0;
4655 case WM_IME_COMPOSITION:
4657 HIMC hIMC;
4659 ME_Style *style = ME_GetInsertStyle(editor, 0);
4660 hIMC = ITextHost_TxImmGetContext(editor->texthost);
4661 ME_DeleteSelection(editor);
4662 ME_SaveTempStyle(editor, style);
4663 if (lParam & (GCS_RESULTSTR|GCS_COMPSTR))
4665 LPWSTR lpCompStr = NULL;
4666 DWORD dwBufLen;
4667 DWORD dwIndex = lParam & GCS_RESULTSTR;
4668 if (!dwIndex)
4669 dwIndex = GCS_COMPSTR;
4671 dwBufLen = ImmGetCompositionStringW(hIMC, dwIndex, NULL, 0);
4672 lpCompStr = HeapAlloc(GetProcessHeap(),0,dwBufLen + sizeof(WCHAR));
4673 ImmGetCompositionStringW(hIMC, dwIndex, lpCompStr, dwBufLen);
4674 lpCompStr[dwBufLen/sizeof(WCHAR)] = 0;
4675 ME_InsertTextFromCursor(editor,0,lpCompStr,dwBufLen/sizeof(WCHAR),style);
4676 HeapFree(GetProcessHeap(), 0, lpCompStr);
4678 if (dwIndex == GCS_COMPSTR)
4679 ME_SetSelection(editor,editor->imeStartIndex,
4680 editor->imeStartIndex + dwBufLen/sizeof(WCHAR));
4682 ME_ReleaseStyle(style);
4683 ME_CommitUndo(editor);
4684 ME_UpdateRepaint(editor, FALSE);
4685 return 0;
4687 case WM_IME_ENDCOMPOSITION:
4689 ME_DeleteSelection(editor);
4690 editor->imeStartIndex=-1;
4691 return 0;
4693 case EM_GETOLEINTERFACE:
4695 if (!editor->reOle)
4696 if (!CreateIRichEditOle(NULL, editor, (LPVOID *)&editor->reOle))
4697 return 0;
4698 *(LPVOID *)lParam = editor->reOle;
4699 IRichEditOle_AddRef(editor->reOle);
4700 return 1;
4702 case EM_GETPASSWORDCHAR:
4704 return editor->cPasswordMask;
4706 case EM_SETOLECALLBACK:
4707 if(editor->lpOleCallback)
4708 IRichEditOleCallback_Release(editor->lpOleCallback);
4709 editor->lpOleCallback = (IRichEditOleCallback*)lParam;
4710 if(editor->lpOleCallback)
4711 IRichEditOleCallback_AddRef(editor->lpOleCallback);
4712 return TRUE;
4713 case EM_GETWORDBREAKPROC:
4714 return (LRESULT)editor->pfnWordBreak;
4715 case EM_SETWORDBREAKPROC:
4717 EDITWORDBREAKPROCW pfnOld = editor->pfnWordBreak;
4719 editor->pfnWordBreak = (EDITWORDBREAKPROCW)lParam;
4720 return (LRESULT)pfnOld;
4722 case EM_GETTEXTMODE:
4723 return editor->mode;
4724 case EM_SETTEXTMODE:
4726 int mask = 0;
4727 int changes = 0;
4729 if (ME_GetTextLength(editor) ||
4730 !list_empty( &editor->undo_stack ) || !list_empty( &editor->redo_stack ))
4731 return E_UNEXPECTED;
4733 /* Check for mutually exclusive flags in adjacent bits of wParam */
4734 if ((wParam & (TM_RICHTEXT | TM_MULTILEVELUNDO | TM_MULTICODEPAGE)) &
4735 (wParam & (TM_PLAINTEXT | TM_SINGLELEVELUNDO | TM_SINGLECODEPAGE)) << 1)
4736 return E_INVALIDARG;
4738 if (wParam & (TM_RICHTEXT | TM_PLAINTEXT))
4740 mask |= TM_RICHTEXT | TM_PLAINTEXT;
4741 changes |= wParam & (TM_RICHTEXT | TM_PLAINTEXT);
4742 if (wParam & TM_PLAINTEXT) {
4743 /* Clear selection since it should be possible to select the
4744 * end of text run for rich text */
4745 ME_InvalidateSelection(editor);
4746 ME_SetCursorToStart(editor, &editor->pCursors[0]);
4747 editor->pCursors[1] = editor->pCursors[0];
4748 /* plain text can only have the default style. */
4749 ME_ClearTempStyle(editor);
4750 ME_AddRefStyle(editor->pBuffer->pDefaultStyle);
4751 ME_ReleaseStyle(editor->pCursors[0].pRun->member.run.style);
4752 editor->pCursors[0].pRun->member.run.style = editor->pBuffer->pDefaultStyle;
4755 /* FIXME: Currently no support for undo level and code page options */
4756 editor->mode = (editor->mode & ~mask) | changes;
4757 return 0;
4759 case EM_SETPASSWORDCHAR:
4761 editor->cPasswordMask = wParam;
4762 ME_RewrapRepaint(editor);
4763 return 0;
4765 case EM_SETTARGETDEVICE:
4766 if (wParam == 0)
4768 BOOL new = (lParam == 0 && (editor->styleFlags & ES_MULTILINE));
4769 if (editor->nAvailWidth || editor->bWordWrap != new)
4771 editor->bWordWrap = new;
4772 editor->nAvailWidth = 0; /* wrap to client area */
4773 ME_RewrapRepaint(editor);
4775 } else {
4776 int width = max(0, lParam);
4777 if ((editor->styleFlags & ES_MULTILINE) &&
4778 (!editor->bWordWrap || editor->nAvailWidth != width))
4780 editor->nAvailWidth = width;
4781 editor->bWordWrap = TRUE;
4782 ME_RewrapRepaint(editor);
4784 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4786 return TRUE;
4787 default:
4788 do_default:
4789 *phresult = S_FALSE;
4790 break;
4792 return 0L;
4795 static LRESULT RichEditWndProc_common(HWND hWnd, UINT msg, WPARAM wParam,
4796 LPARAM lParam, BOOL unicode)
4798 ME_TextEditor *editor;
4799 HRESULT hresult;
4800 LRESULT lresult = 0;
4802 TRACE("enter hwnd %p msg %04x (%s) %lx %lx, unicode %d\n",
4803 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode);
4805 editor = (ME_TextEditor *)GetWindowLongPtrW(hWnd, 0);
4806 if (!editor)
4808 if (msg == WM_NCCREATE)
4810 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
4811 ITextHost *texthost;
4813 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
4814 texthost = ME_CreateTextHost(hWnd, pcs, FALSE);
4815 return texthost != NULL;
4817 else
4819 return DefWindowProcW(hWnd, msg, wParam, lParam);
4823 switch (msg)
4825 case WM_PAINT:
4827 HDC hDC;
4828 RECT rc;
4829 PAINTSTRUCT ps;
4831 hDC = BeginPaint(editor->hWnd, &ps);
4832 if (!editor->bEmulateVersion10 || (editor->nEventMask & ENM_UPDATE))
4833 ME_SendOldNotify(editor, EN_UPDATE);
4834 /* Erase area outside of the formatting rectangle */
4835 if (ps.rcPaint.top < editor->rcFormat.top)
4837 rc = ps.rcPaint;
4838 rc.bottom = editor->rcFormat.top;
4839 FillRect(hDC, &rc, editor->hbrBackground);
4840 ps.rcPaint.top = editor->rcFormat.top;
4842 if (ps.rcPaint.bottom > editor->rcFormat.bottom) {
4843 rc = ps.rcPaint;
4844 rc.top = editor->rcFormat.bottom;
4845 FillRect(hDC, &rc, editor->hbrBackground);
4846 ps.rcPaint.bottom = editor->rcFormat.bottom;
4848 if (ps.rcPaint.left < editor->rcFormat.left) {
4849 rc = ps.rcPaint;
4850 rc.right = editor->rcFormat.left;
4851 FillRect(hDC, &rc, editor->hbrBackground);
4852 ps.rcPaint.left = editor->rcFormat.left;
4854 if (ps.rcPaint.right > editor->rcFormat.right) {
4855 rc = ps.rcPaint;
4856 rc.left = editor->rcFormat.right;
4857 FillRect(hDC, &rc, editor->hbrBackground);
4858 ps.rcPaint.right = editor->rcFormat.right;
4861 ME_PaintContent(editor, hDC, &ps.rcPaint);
4862 EndPaint(editor->hWnd, &ps);
4863 return 0;
4865 case WM_ERASEBKGND:
4867 HDC hDC = (HDC)wParam;
4868 RECT rc;
4870 if (GetUpdateRect(editor->hWnd, &rc, TRUE))
4871 FillRect(hDC, &rc, editor->hbrBackground);
4872 return 1;
4874 case EM_SETOPTIONS:
4876 DWORD dwStyle;
4877 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
4878 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
4879 ECO_SELECTIONBAR;
4880 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
4881 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
4882 dwStyle = (dwStyle & ~mask) | (lresult & mask);
4883 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
4884 return lresult;
4886 case EM_SETREADONLY:
4888 DWORD dwStyle;
4889 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
4890 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
4891 dwStyle &= ~ES_READONLY;
4892 if (wParam)
4893 dwStyle |= ES_READONLY;
4894 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
4895 return lresult;
4897 default:
4898 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
4901 if (hresult == S_FALSE)
4902 lresult = DefWindowProcW(hWnd, msg, wParam, lParam);
4904 TRACE("exit hwnd %p msg %04x (%s) %lx %lx, unicode %d -> %lu\n",
4905 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode, lresult);
4907 return lresult;
4910 static LRESULT WINAPI RichEditWndProcW(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
4912 BOOL unicode = TRUE;
4914 /* Under Win9x RichEdit20W returns ANSI strings, see the tests. */
4915 if (msg == WM_GETTEXT && (GetVersion() & 0x80000000))
4916 unicode = FALSE;
4918 return RichEditWndProc_common(hWnd, msg, wParam, lParam, unicode);
4921 static LRESULT WINAPI RichEditWndProcA(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
4923 return RichEditWndProc_common(hWnd, msg, wParam, lParam, FALSE);
4926 /******************************************************************
4927 * RichEditANSIWndProc (RICHED20.10)
4929 LRESULT WINAPI RichEditANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
4931 return RichEditWndProcA(hWnd, msg, wParam, lParam);
4934 /******************************************************************
4935 * RichEdit10ANSIWndProc (RICHED20.9)
4937 LRESULT WINAPI RichEdit10ANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
4939 if (msg == WM_NCCREATE && !GetWindowLongPtrW(hWnd, 0))
4941 ITextHost *texthost;
4942 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
4944 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
4945 texthost = ME_CreateTextHost(hWnd, pcs, TRUE);
4946 return texthost != NULL;
4948 return RichEditANSIWndProc(hWnd, msg, wParam, lParam);
4951 void ME_SendOldNotify(ME_TextEditor *editor, int nCode)
4953 ITextHost_TxNotify(editor->texthost, nCode, NULL);
4956 /* Fill buffer with srcChars unicode characters from the start cursor.
4958 * buffer: destination buffer
4959 * buflen: length of buffer in characters excluding the NULL terminator.
4960 * start: start of editor text to copy into buffer.
4961 * srcChars: Number of characters to use from the editor text.
4962 * bCRLF: if true, replaces all end of lines with \r\n pairs.
4964 * returns the number of characters written excluding the NULL terminator.
4966 * The written text is always NULL terminated.
4968 int ME_GetTextW(ME_TextEditor *editor, WCHAR *buffer, int buflen,
4969 const ME_Cursor *start, int srcChars, BOOL bCRLF,
4970 BOOL bEOP)
4972 ME_DisplayItem *pRun, *pNextRun;
4973 const WCHAR *pStart = buffer;
4974 const WCHAR cr_lf[] = {'\r', '\n', 0};
4975 const WCHAR *str;
4976 int nLen;
4978 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
4979 if (editor->bEmulateVersion10) bCRLF = FALSE;
4981 pRun = start->pRun;
4982 assert(pRun);
4983 pNextRun = ME_FindItemFwd(pRun, diRun);
4985 nLen = pRun->member.run.len - start->nOffset;
4986 str = get_text( &pRun->member.run, start->nOffset );
4988 while (srcChars && buflen && pNextRun)
4990 int nFlags = pRun->member.run.nFlags;
4992 if (bCRLF && nFlags & MERF_ENDPARA && ~nFlags & MERF_ENDCELL)
4994 if (buflen == 1) break;
4995 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
4996 * EM_GETTEXTEX, however, this is done for copying text which
4997 * also uses this function. */
4998 srcChars -= min(nLen, srcChars);
4999 nLen = 2;
5000 str = cr_lf;
5001 } else {
5002 nLen = min(nLen, srcChars);
5003 srcChars -= nLen;
5006 nLen = min(nLen, buflen);
5007 buflen -= nLen;
5009 CopyMemory(buffer, str, sizeof(WCHAR) * nLen);
5011 buffer += nLen;
5013 pRun = pNextRun;
5014 pNextRun = ME_FindItemFwd(pRun, diRun);
5016 nLen = pRun->member.run.len;
5017 str = get_text( &pRun->member.run, 0 );
5019 /* append '\r' to the last paragraph. */
5020 if (pRun->next->type == diTextEnd && bEOP)
5022 *buffer = '\r';
5023 buffer ++;
5025 *buffer = 0;
5026 return buffer - pStart;
5029 static BOOL ME_RegisterEditorClass(HINSTANCE hInstance)
5031 WNDCLASSW wcW;
5032 WNDCLASSA wcA;
5034 wcW.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5035 wcW.lpfnWndProc = RichEditWndProcW;
5036 wcW.cbClsExtra = 0;
5037 wcW.cbWndExtra = sizeof(ME_TextEditor *);
5038 wcW.hInstance = NULL; /* hInstance would register DLL-local class */
5039 wcW.hIcon = NULL;
5040 wcW.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5041 wcW.hbrBackground = GetStockObject(NULL_BRUSH);
5042 wcW.lpszMenuName = NULL;
5044 if (is_version_nt())
5046 wcW.lpszClassName = RICHEDIT_CLASS20W;
5047 if (!RegisterClassW(&wcW)) return FALSE;
5048 wcW.lpszClassName = MSFTEDIT_CLASS;
5049 if (!RegisterClassW(&wcW)) return FALSE;
5051 else
5053 /* WNDCLASSA/W have the same layout */
5054 wcW.lpszClassName = (LPCWSTR)"RichEdit20W";
5055 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5056 wcW.lpszClassName = (LPCWSTR)"RichEdit50W";
5057 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5060 wcA.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5061 wcA.lpfnWndProc = RichEditWndProcA;
5062 wcA.cbClsExtra = 0;
5063 wcA.cbWndExtra = sizeof(ME_TextEditor *);
5064 wcA.hInstance = NULL; /* hInstance would register DLL-local class */
5065 wcA.hIcon = NULL;
5066 wcA.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5067 wcA.hbrBackground = GetStockObject(NULL_BRUSH);
5068 wcA.lpszMenuName = NULL;
5069 wcA.lpszClassName = RICHEDIT_CLASS20A;
5070 if (!RegisterClassA(&wcA)) return FALSE;
5071 wcA.lpszClassName = "RichEdit50A";
5072 if (!RegisterClassA(&wcA)) return FALSE;
5074 return TRUE;
5077 static LRESULT WINAPI REComboWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5078 /* FIXME: Not implemented */
5079 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5080 hWnd, msg, get_msg_name(msg), wParam, lParam);
5081 return DefWindowProcW(hWnd, msg, wParam, lParam);
5084 static LRESULT WINAPI REListWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5085 /* FIXME: Not implemented */
5086 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5087 hWnd, msg, get_msg_name(msg), wParam, lParam);
5088 return DefWindowProcW(hWnd, msg, wParam, lParam);
5091 /******************************************************************
5092 * REExtendedRegisterClass (RICHED20.8)
5094 * FIXME undocumented
5095 * Need to check for errors and implement controls and callbacks
5097 LRESULT WINAPI REExtendedRegisterClass(void)
5099 WNDCLASSW wcW;
5100 UINT result;
5102 FIXME("semi stub\n");
5104 wcW.cbClsExtra = 0;
5105 wcW.cbWndExtra = 4;
5106 wcW.hInstance = NULL;
5107 wcW.hIcon = NULL;
5108 wcW.hCursor = NULL;
5109 wcW.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
5110 wcW.lpszMenuName = NULL;
5112 if (!ME_ListBoxRegistered)
5114 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS;
5115 wcW.lpfnWndProc = REListWndProc;
5116 wcW.lpszClassName = REListBox20W;
5117 if (RegisterClassW(&wcW)) ME_ListBoxRegistered = TRUE;
5120 if (!ME_ComboBoxRegistered)
5122 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS | CS_VREDRAW | CS_HREDRAW;
5123 wcW.lpfnWndProc = REComboWndProc;
5124 wcW.lpszClassName = REComboBox20W;
5125 if (RegisterClassW(&wcW)) ME_ComboBoxRegistered = TRUE;
5128 result = 0;
5129 if (ME_ListBoxRegistered)
5130 result += 1;
5131 if (ME_ComboBoxRegistered)
5132 result += 2;
5134 return result;
5137 static int wchar_comp( const void *key, const void *elem )
5139 return *(const WCHAR *)key - *(const WCHAR *)elem;
5142 /* neutral characters end the url if the next non-neutral character is a space character,
5143 otherwise they are included in the url. */
5144 static BOOL isurlneutral( WCHAR c )
5146 /* NB this list is sorted */
5147 static const WCHAR neutral_chars[] = {'!','\"','\'','(',')',',','-','.',':',';','<','>','?','[',']','{','}'};
5149 /* Some shortcuts */
5150 if (isalnum( c )) return FALSE;
5151 if (c > neutral_chars[sizeof(neutral_chars) / sizeof(neutral_chars[0]) - 1]) return FALSE;
5153 return !!bsearch( &c, neutral_chars, sizeof(neutral_chars) / sizeof(neutral_chars[0]),
5154 sizeof(c), wchar_comp );
5158 * This proc takes a selection, and scans it forward in order to select the span
5159 * of a possible URL candidate. A possible URL candidate must start with isalnum
5160 * or one of the following special characters: *|/\+%#@ and must consist entirely
5161 * of the characters allowed to start the URL, plus : (colon) which may occur
5162 * at most once, and not at either end.
5164 static BOOL ME_FindNextURLCandidate(ME_TextEditor *editor,
5165 const ME_Cursor *start,
5166 int nChars,
5167 ME_Cursor *candidate_min,
5168 ME_Cursor *candidate_max)
5170 ME_Cursor cursor = *start, neutral_end, space_end;
5171 BOOL candidateStarted = FALSE, quoted = FALSE;
5172 WCHAR c;
5174 while (nChars > 0)
5176 WCHAR *str = get_text( &cursor.pRun->member.run, 0 );
5177 int run_len = cursor.pRun->member.run.len;
5179 nChars -= run_len - cursor.nOffset;
5181 /* Find start of candidate */
5182 if (!candidateStarted)
5184 while (cursor.nOffset < run_len)
5186 c = str[cursor.nOffset];
5187 if (!isspaceW( c ) && !isurlneutral( c ))
5189 *candidate_min = cursor;
5190 candidateStarted = TRUE;
5191 neutral_end.pPara = NULL;
5192 space_end.pPara = NULL;
5193 cursor.nOffset++;
5194 break;
5196 quoted = (c == '<');
5197 cursor.nOffset++;
5201 /* Find end of candidate */
5202 if (candidateStarted)
5204 while (cursor.nOffset < run_len)
5206 c = str[cursor.nOffset];
5207 if (isspaceW( c ))
5209 if (quoted && c != '\r')
5211 if (!space_end.pPara)
5213 if (neutral_end.pPara)
5214 space_end = neutral_end;
5215 else
5216 space_end = cursor;
5219 else
5220 goto done;
5222 else if (isurlneutral( c ))
5224 if (quoted && c == '>')
5226 neutral_end.pPara = NULL;
5227 space_end.pPara = NULL;
5228 goto done;
5230 if (!neutral_end.pPara)
5231 neutral_end = cursor;
5233 else
5234 neutral_end.pPara = NULL;
5236 cursor.nOffset++;
5240 cursor.nOffset = 0;
5241 if (!ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE))
5242 goto done;
5245 done:
5246 if (candidateStarted)
5248 if (space_end.pPara)
5249 *candidate_max = space_end;
5250 else if (neutral_end.pPara)
5251 *candidate_max = neutral_end;
5252 else
5253 *candidate_max = cursor;
5254 return TRUE;
5256 *candidate_max = *candidate_min = cursor;
5257 return FALSE;
5261 * This proc evaluates the selection and returns TRUE if it can be considered an URL
5263 static BOOL ME_IsCandidateAnURL(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
5265 #define MAX_PREFIX_LEN 9
5266 struct prefix_s {
5267 const WCHAR text[MAX_PREFIX_LEN];
5268 int length;
5269 }prefixes[] = {
5270 {{'p','r','o','s','p','e','r','o',':'}, 9},
5271 {{'t','e','l','n','e','t',':'}, 7},
5272 {{'g','o','p','h','e','r',':'}, 7},
5273 {{'m','a','i','l','t','o',':'}, 7},
5274 {{'h','t','t','p','s',':'}, 6},
5275 {{'f','i','l','e',':'}, 5},
5276 {{'n','e','w','s',':'}, 5},
5277 {{'w','a','i','s',':'}, 5},
5278 {{'n','n','t','p',':'}, 5},
5279 {{'h','t','t','p',':'}, 5},
5280 {{'w','w','w','.'}, 4},
5281 {{'f','t','p',':'}, 4},
5283 WCHAR bufferW[MAX_PREFIX_LEN + 1];
5284 unsigned int i;
5286 ME_GetTextW(editor, bufferW, MAX_PREFIX_LEN, start, nChars, FALSE, FALSE);
5287 for (i = 0; i < sizeof(prefixes) / sizeof(*prefixes); i++)
5289 if (nChars < prefixes[i].length) continue;
5290 if (!memcmp(prefixes[i].text, bufferW, prefixes[i].length * sizeof(WCHAR)))
5291 return TRUE;
5293 return FALSE;
5294 #undef MAX_PREFIX_LEN
5298 * This proc walks through the indicated selection and evaluates whether each
5299 * section identified by ME_FindNextURLCandidate and in-between sections have
5300 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
5301 * not what it is supposed to be, this proc sets or unsets it as appropriate.
5303 * Since this function can cause runs to be split, do not depend on the value
5304 * of the start cursor at the end of the function.
5306 * nChars may be set to INT_MAX to update to the end of the text.
5308 * Returns TRUE if at least one section was modified.
5310 static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars)
5312 BOOL modified = FALSE;
5313 ME_Cursor startCur = *start;
5315 if (!editor->AutoURLDetect_bEnable) return FALSE;
5319 CHARFORMAT2W link;
5320 ME_Cursor candidateStart, candidateEnd;
5322 if (ME_FindNextURLCandidate(editor, &startCur, nChars,
5323 &candidateStart, &candidateEnd))
5325 /* Section before candidate is not an URL */
5326 int cMin = ME_GetCursorOfs(&candidateStart);
5327 int cMax = ME_GetCursorOfs(&candidateEnd);
5329 if (!ME_IsCandidateAnURL(editor, &candidateStart, cMax - cMin))
5330 candidateStart = candidateEnd;
5331 nChars -= cMax - ME_GetCursorOfs(&startCur);
5333 else
5335 /* No more candidates until end of selection */
5336 nChars = 0;
5339 if (startCur.pRun != candidateStart.pRun ||
5340 startCur.nOffset != candidateStart.nOffset)
5342 /* CFE_LINK effect should be consistently unset */
5343 link.cbSize = sizeof(link);
5344 ME_GetCharFormat(editor, &startCur, &candidateStart, &link);
5345 if (!(link.dwMask & CFM_LINK) || (link.dwEffects & CFE_LINK))
5347 /* CFE_LINK must be unset from this range */
5348 memset(&link, 0, sizeof(CHARFORMAT2W));
5349 link.cbSize = sizeof(link);
5350 link.dwMask = CFM_LINK;
5351 link.dwEffects = 0;
5352 ME_SetCharFormat(editor, &startCur, &candidateStart, &link);
5353 /* Update candidateEnd since setting character formats may split
5354 * runs, which can cause a cursor to be at an invalid offset within
5355 * a split run. */
5356 while (candidateEnd.nOffset >= candidateEnd.pRun->member.run.len)
5358 candidateEnd.nOffset -= candidateEnd.pRun->member.run.len;
5359 candidateEnd.pRun = ME_FindItemFwd(candidateEnd.pRun, diRun);
5361 modified = TRUE;
5364 if (candidateStart.pRun != candidateEnd.pRun ||
5365 candidateStart.nOffset != candidateEnd.nOffset)
5367 /* CFE_LINK effect should be consistently set */
5368 link.cbSize = sizeof(link);
5369 ME_GetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5370 if (!(link.dwMask & CFM_LINK) || !(link.dwEffects & CFE_LINK))
5372 /* CFE_LINK must be set on this range */
5373 memset(&link, 0, sizeof(CHARFORMAT2W));
5374 link.cbSize = sizeof(link);
5375 link.dwMask = CFM_LINK;
5376 link.dwEffects = CFE_LINK;
5377 ME_SetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5378 modified = TRUE;
5381 startCur = candidateEnd;
5382 } while (nChars > 0);
5383 return modified;