riched20: Extract handling of VK_RETURN into a method.
[wine.git] / dlls / riched20 / editor.c
blob79c9636a7f2c154c6279b05ca292297168cbac9b
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
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) {
264 ME_TextBuffer *buf = heap_alloc(sizeof(*buf));
265 ME_DisplayItem *p1 = ME_MakeDI(diTextStart);
266 ME_DisplayItem *p2 = ME_MakeDI(diTextEnd);
268 p1->prev = NULL;
269 p1->next = p2;
270 p2->prev = p1;
271 p2->next = NULL;
272 p1->member.para.next_para = p2;
273 p2->member.para.prev_para = p1;
274 p2->member.para.nCharOfs = 0;
276 buf->pFirst = p1;
277 buf->pLast = p2;
278 buf->pCharStyle = NULL;
280 return buf;
284 static LRESULT ME_StreamInText(ME_TextEditor *editor, DWORD dwFormat, ME_InStream *stream, ME_Style *style)
286 WCHAR *pText;
287 LRESULT total_bytes_read = 0;
288 BOOL is_read = FALSE;
289 DWORD cp = CP_ACP, copy = 0;
290 char conv_buf[4 + STREAMIN_BUFFER_SIZE]; /* up to 4 additional UTF-8 bytes */
292 static const char bom_utf8[] = {0xEF, 0xBB, 0xBF};
294 TRACE("%08x %p\n", dwFormat, stream);
296 do {
297 LONG nWideChars = 0;
298 WCHAR wszText[STREAMIN_BUFFER_SIZE+1];
300 if (!stream->dwSize)
302 ME_StreamInFill(stream);
303 if (stream->editstream->dwError)
304 break;
305 if (!stream->dwSize)
306 break;
307 total_bytes_read += stream->dwSize;
310 if (!(dwFormat & SF_UNICODE))
312 char * buf = stream->buffer;
313 DWORD size = stream->dwSize, end;
315 if (!is_read)
317 is_read = TRUE;
318 if (stream->dwSize >= 3 && !memcmp(stream->buffer, bom_utf8, 3))
320 cp = CP_UTF8;
321 buf += 3;
322 size -= 3;
326 if (cp == CP_UTF8)
328 if (copy)
330 memcpy(conv_buf + copy, buf, size);
331 buf = conv_buf;
332 size += copy;
334 end = size;
335 while ((buf[end-1] & 0xC0) == 0x80)
337 --end;
338 --total_bytes_read; /* strange, but seems to match windows */
340 if (buf[end-1] & 0x80)
342 DWORD need = 0;
343 if ((buf[end-1] & 0xE0) == 0xC0)
344 need = 1;
345 if ((buf[end-1] & 0xF0) == 0xE0)
346 need = 2;
347 if ((buf[end-1] & 0xF8) == 0xF0)
348 need = 3;
350 if (size - end >= need)
352 /* we have enough bytes for this sequence */
353 end = size;
355 else
357 /* need more bytes, so don't transcode this sequence */
358 --end;
362 else
363 end = size;
365 nWideChars = MultiByteToWideChar(cp, 0, buf, end, wszText, STREAMIN_BUFFER_SIZE);
366 pText = wszText;
368 if (cp == CP_UTF8)
370 if (end != size)
372 memcpy(conv_buf, buf + end, size - end);
373 copy = size - end;
377 else
379 nWideChars = stream->dwSize >> 1;
380 pText = (WCHAR *)stream->buffer;
383 ME_InsertTextFromCursor(editor, 0, pText, nWideChars, style);
384 if (stream->dwSize == 0)
385 break;
386 stream->dwSize = 0;
387 } while(1);
388 return total_bytes_read;
391 static void ME_ApplyBorderProperties(RTF_Info *info,
392 ME_BorderRect *borderRect,
393 RTFBorder *borderDef)
395 int i, colorNum;
396 ME_Border *pBorders[] = {&borderRect->top,
397 &borderRect->left,
398 &borderRect->bottom,
399 &borderRect->right};
400 for (i = 0; i < 4; i++)
402 RTFColor *colorDef = info->colorList;
403 pBorders[i]->width = borderDef[i].width;
404 colorNum = borderDef[i].color;
405 while (colorDef && colorDef->rtfCNum != colorNum)
406 colorDef = colorDef->rtfNextColor;
407 if (colorDef)
408 pBorders[i]->colorRef = RGB(
409 colorDef->rtfCRed >= 0 ? colorDef->rtfCRed : 0,
410 colorDef->rtfCGreen >= 0 ? colorDef->rtfCGreen : 0,
411 colorDef->rtfCBlue >= 0 ? colorDef->rtfCBlue : 0);
412 else
413 pBorders[i]->colorRef = RGB(0, 0, 0);
417 void ME_RTFCharAttrHook(RTF_Info *info)
419 CHARFORMAT2W fmt;
420 fmt.cbSize = sizeof(fmt);
421 fmt.dwMask = 0;
422 fmt.dwEffects = 0;
424 switch(info->rtfMinor)
426 case rtfPlain:
427 /* FIXME add more flags once they're implemented */
428 fmt.dwMask = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE | CFM_UNDERLINETYPE | CFM_STRIKEOUT |
429 CFM_COLOR | CFM_BACKCOLOR | CFM_SIZE | CFM_WEIGHT;
430 fmt.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
431 fmt.yHeight = 12*20; /* 12pt */
432 fmt.wWeight = FW_NORMAL;
433 fmt.bUnderlineType = CFU_UNDERLINE;
434 break;
435 case rtfBold:
436 fmt.dwMask = CFM_BOLD | CFM_WEIGHT;
437 fmt.dwEffects = info->rtfParam ? CFE_BOLD : 0;
438 fmt.wWeight = info->rtfParam ? FW_BOLD : FW_NORMAL;
439 break;
440 case rtfItalic:
441 fmt.dwMask = CFM_ITALIC;
442 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
443 break;
444 case rtfUnderline:
445 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
446 fmt.bUnderlineType = CFU_UNDERLINE;
447 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
448 break;
449 case rtfDotUnderline:
450 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
451 fmt.bUnderlineType = CFU_UNDERLINEDOTTED;
452 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
453 break;
454 case rtfDbUnderline:
455 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
456 fmt.bUnderlineType = CFU_UNDERLINEDOUBLE;
457 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
458 break;
459 case rtfWordUnderline:
460 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
461 fmt.bUnderlineType = CFU_UNDERLINEWORD;
462 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
463 break;
464 case rtfNoUnderline:
465 fmt.dwMask = CFM_UNDERLINE;
466 fmt.dwEffects = 0;
467 break;
468 case rtfStrikeThru:
469 fmt.dwMask = CFM_STRIKEOUT;
470 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
471 break;
472 case rtfSubScript:
473 case rtfSuperScript:
474 case rtfSubScrShrink:
475 case rtfSuperScrShrink:
476 case rtfNoSuperSub:
477 fmt.dwMask = CFM_SUBSCRIPT|CFM_SUPERSCRIPT;
478 if (info->rtfMinor == rtfSubScrShrink) fmt.dwEffects = CFE_SUBSCRIPT;
479 if (info->rtfMinor == rtfSuperScrShrink) fmt.dwEffects = CFE_SUPERSCRIPT;
480 if (info->rtfMinor == rtfNoSuperSub) fmt.dwEffects = 0;
481 break;
482 case rtfInvisible:
483 fmt.dwMask = CFM_HIDDEN;
484 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
485 break;
486 case rtfBackColor:
487 fmt.dwMask = CFM_BACKCOLOR;
488 fmt.dwEffects = 0;
489 if (info->rtfParam == 0)
490 fmt.dwEffects = CFE_AUTOBACKCOLOR;
491 else if (info->rtfParam != rtfNoParam)
493 RTFColor *c = RTFGetColor(info, info->rtfParam);
494 if (c && c->rtfCBlue >= 0)
495 fmt.crBackColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
496 else
497 fmt.dwEffects = CFE_AUTOBACKCOLOR;
499 break;
500 case rtfForeColor:
501 fmt.dwMask = CFM_COLOR;
502 fmt.dwEffects = 0;
503 if (info->rtfParam == 0)
504 fmt.dwEffects = CFE_AUTOCOLOR;
505 else if (info->rtfParam != rtfNoParam)
507 RTFColor *c = RTFGetColor(info, info->rtfParam);
508 if (c && c->rtfCBlue >= 0)
509 fmt.crTextColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
510 else {
511 fmt.dwEffects = CFE_AUTOCOLOR;
514 break;
515 case rtfFontNum:
516 if (info->rtfParam != rtfNoParam)
518 RTFFont *f = RTFGetFont(info, info->rtfParam);
519 if (f)
521 MultiByteToWideChar(CP_ACP, 0, f->rtfFName, -1, fmt.szFaceName, ARRAY_SIZE(fmt.szFaceName));
522 fmt.szFaceName[ARRAY_SIZE(fmt.szFaceName)-1] = '\0';
523 fmt.bCharSet = f->rtfFCharSet;
524 fmt.dwMask = CFM_FACE | CFM_CHARSET;
525 fmt.bPitchAndFamily = f->rtfFPitch | (f->rtfFFamily << 4);
528 break;
529 case rtfFontSize:
530 fmt.dwMask = CFM_SIZE;
531 if (info->rtfParam != rtfNoParam)
532 fmt.yHeight = info->rtfParam*10;
533 break;
535 if (fmt.dwMask) {
536 ME_Style *style2;
537 RTFFlushOutputBuffer(info);
538 /* FIXME too slow ? how come ? */
539 style2 = ME_ApplyStyle(info->editor, info->style, &fmt);
540 ME_ReleaseStyle(info->style);
541 info->style = style2;
542 info->styleChanged = TRUE;
546 /* FIXME this function doesn't get any information about context of the RTF tag, which is very bad,
547 the same tags mean different things in different contexts */
548 void ME_RTFParAttrHook(RTF_Info *info)
550 switch(info->rtfMinor)
552 case rtfParDef: /* restores default paragraph attributes */
553 if (!info->editor->bEmulateVersion10) /* v4.1 */
554 info->borderType = RTFBorderParaLeft;
555 else /* v1.0 - 3.0 */
556 info->borderType = RTFBorderParaTop;
557 info->fmt.dwMask = PFM_ALIGNMENT | PFM_BORDER | PFM_LINESPACING | PFM_TABSTOPS |
558 PFM_OFFSET | PFM_RIGHTINDENT | PFM_SPACEAFTER | PFM_SPACEBEFORE |
559 PFM_STARTINDENT | PFM_RTLPARA | PFM_NUMBERING | PFM_NUMBERINGSTART |
560 PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB;
561 /* TODO: shading */
562 info->fmt.wAlignment = PFA_LEFT;
563 info->fmt.cTabCount = 0;
564 info->fmt.dxOffset = info->fmt.dxStartIndent = info->fmt.dxRightIndent = 0;
565 info->fmt.wBorderWidth = info->fmt.wBorders = 0;
566 info->fmt.wBorderSpace = 0;
567 info->fmt.bLineSpacingRule = 0;
568 info->fmt.dySpaceBefore = info->fmt.dySpaceAfter = 0;
569 info->fmt.dyLineSpacing = 0;
570 info->fmt.wEffects &= ~PFE_RTLPARA;
571 info->fmt.wNumbering = 0;
572 info->fmt.wNumberingStart = 0;
573 info->fmt.wNumberingStyle = 0;
574 info->fmt.wNumberingTab = 0;
576 if (!info->editor->bEmulateVersion10) /* v4.1 */
578 if (info->tableDef && info->tableDef->tableRowStart &&
579 info->tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
581 ME_Cursor cursor;
582 ME_DisplayItem *para;
583 /* We are just after a table row. */
584 RTFFlushOutputBuffer(info);
585 cursor = info->editor->pCursors[0];
586 para = cursor.pPara;
587 if (para == info->tableDef->tableRowStart->member.para.next_para
588 && !cursor.nOffset && !cursor.pRun->member.run.nCharOfs)
590 /* Since the table row end, no text has been inserted, and the \intbl
591 * control word has not be used. We can confirm that we are not in a
592 * table anymore.
594 info->tableDef->tableRowStart = NULL;
595 info->canInheritInTbl = FALSE;
598 } else { /* v1.0 - v3.0 */
599 info->fmt.dwMask |= PFM_TABLE;
600 info->fmt.wEffects &= ~PFE_TABLE;
602 break;
603 case rtfNestLevel:
604 if (!info->editor->bEmulateVersion10) /* v4.1 */
606 while (info->rtfParam > info->nestingLevel) {
607 RTFTable *tableDef = heap_alloc_zero(sizeof(*tableDef));
608 tableDef->parent = info->tableDef;
609 info->tableDef = tableDef;
611 RTFFlushOutputBuffer(info);
612 if (tableDef->tableRowStart &&
613 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
615 ME_DisplayItem *para = tableDef->tableRowStart;
616 para = para->member.para.next_para;
617 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
618 tableDef->tableRowStart = para;
619 } else {
620 ME_Cursor cursor;
621 WCHAR endl = '\r';
622 cursor = info->editor->pCursors[0];
623 if (cursor.nOffset || cursor.pRun->member.run.nCharOfs)
624 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
625 tableDef->tableRowStart = ME_InsertTableRowStartFromCursor(info->editor);
628 info->nestingLevel++;
630 info->canInheritInTbl = FALSE;
632 break;
633 case rtfInTable:
635 if (!info->editor->bEmulateVersion10) /* v4.1 */
637 if (info->nestingLevel < 1)
639 RTFTable *tableDef;
640 if (!info->tableDef)
641 info->tableDef = heap_alloc_zero(sizeof(*info->tableDef));
642 tableDef = info->tableDef;
643 RTFFlushOutputBuffer(info);
644 if (tableDef->tableRowStart &&
645 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
647 ME_DisplayItem *para = tableDef->tableRowStart;
648 para = para->member.para.next_para;
649 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
650 tableDef->tableRowStart = para;
651 } else {
652 ME_Cursor cursor;
653 WCHAR endl = '\r';
654 cursor = info->editor->pCursors[0];
655 if (cursor.nOffset || cursor.pRun->member.run.nCharOfs)
656 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
657 tableDef->tableRowStart = ME_InsertTableRowStartFromCursor(info->editor);
659 info->nestingLevel = 1;
660 info->canInheritInTbl = TRUE;
662 return;
663 } else { /* v1.0 - v3.0 */
664 info->fmt.dwMask |= PFM_TABLE;
665 info->fmt.wEffects |= PFE_TABLE;
667 break;
669 case rtfFirstIndent:
670 case rtfLeftIndent:
671 if ((info->fmt.dwMask & (PFM_STARTINDENT | PFM_OFFSET)) != (PFM_STARTINDENT | PFM_OFFSET))
673 PARAFORMAT2 fmt;
674 fmt.cbSize = sizeof(fmt);
675 ME_GetSelectionParaFormat(info->editor, &fmt);
676 info->fmt.dwMask |= PFM_STARTINDENT | PFM_OFFSET;
677 info->fmt.dxStartIndent = fmt.dxStartIndent;
678 info->fmt.dxOffset = fmt.dxOffset;
680 if (info->rtfMinor == rtfFirstIndent)
682 info->fmt.dxStartIndent += info->fmt.dxOffset + info->rtfParam;
683 info->fmt.dxOffset = -info->rtfParam;
685 else
686 info->fmt.dxStartIndent = info->rtfParam - info->fmt.dxOffset;
687 break;
688 case rtfRightIndent:
689 info->fmt.dwMask |= PFM_RIGHTINDENT;
690 info->fmt.dxRightIndent = info->rtfParam;
691 break;
692 case rtfQuadLeft:
693 case rtfQuadJust:
694 info->fmt.dwMask |= PFM_ALIGNMENT;
695 info->fmt.wAlignment = PFA_LEFT;
696 break;
697 case rtfQuadRight:
698 info->fmt.dwMask |= PFM_ALIGNMENT;
699 info->fmt.wAlignment = PFA_RIGHT;
700 break;
701 case rtfQuadCenter:
702 info->fmt.dwMask |= PFM_ALIGNMENT;
703 info->fmt.wAlignment = PFA_CENTER;
704 break;
705 case rtfTabPos:
706 if (!(info->fmt.dwMask & PFM_TABSTOPS))
708 PARAFORMAT2 fmt;
709 fmt.cbSize = sizeof(fmt);
710 ME_GetSelectionParaFormat(info->editor, &fmt);
711 memcpy(info->fmt.rgxTabs, fmt.rgxTabs,
712 fmt.cTabCount * sizeof(fmt.rgxTabs[0]));
713 info->fmt.cTabCount = fmt.cTabCount;
714 info->fmt.dwMask |= PFM_TABSTOPS;
716 if (info->fmt.cTabCount < MAX_TAB_STOPS && info->rtfParam < 0x1000000)
717 info->fmt.rgxTabs[info->fmt.cTabCount++] = info->rtfParam;
718 break;
719 case rtfKeep:
720 info->fmt.dwMask |= PFM_KEEP;
721 info->fmt.wEffects |= PFE_KEEP;
722 break;
723 case rtfNoWidowControl:
724 info->fmt.dwMask |= PFM_NOWIDOWCONTROL;
725 info->fmt.wEffects |= PFE_NOWIDOWCONTROL;
726 break;
727 case rtfKeepNext:
728 info->fmt.dwMask |= PFM_KEEPNEXT;
729 info->fmt.wEffects |= PFE_KEEPNEXT;
730 break;
731 case rtfSpaceAfter:
732 info->fmt.dwMask |= PFM_SPACEAFTER;
733 info->fmt.dySpaceAfter = info->rtfParam;
734 break;
735 case rtfSpaceBefore:
736 info->fmt.dwMask |= PFM_SPACEBEFORE;
737 info->fmt.dySpaceBefore = info->rtfParam;
738 break;
739 case rtfSpaceBetween:
740 info->fmt.dwMask |= PFM_LINESPACING;
741 if ((int)info->rtfParam > 0)
743 info->fmt.dyLineSpacing = info->rtfParam;
744 info->fmt.bLineSpacingRule = 3;
746 else
748 info->fmt.dyLineSpacing = info->rtfParam;
749 info->fmt.bLineSpacingRule = 4;
751 break;
752 case rtfSpaceMultiply:
753 info->fmt.dwMask |= PFM_LINESPACING;
754 info->fmt.dyLineSpacing = info->rtfParam * 20;
755 info->fmt.bLineSpacingRule = 5;
756 break;
757 case rtfParBullet:
758 info->fmt.dwMask |= PFM_NUMBERING;
759 info->fmt.wNumbering = PFN_BULLET;
760 break;
761 case rtfParSimple:
762 info->fmt.dwMask |= PFM_NUMBERING;
763 info->fmt.wNumbering = 2; /* FIXME: MSDN says it's not used ?? */
764 break;
765 case rtfBorderLeft:
766 info->borderType = RTFBorderParaLeft;
767 info->fmt.wBorders |= 1;
768 info->fmt.dwMask |= PFM_BORDER;
769 break;
770 case rtfBorderRight:
771 info->borderType = RTFBorderParaRight;
772 info->fmt.wBorders |= 2;
773 info->fmt.dwMask |= PFM_BORDER;
774 break;
775 case rtfBorderTop:
776 info->borderType = RTFBorderParaTop;
777 info->fmt.wBorders |= 4;
778 info->fmt.dwMask |= PFM_BORDER;
779 break;
780 case rtfBorderBottom:
781 info->borderType = RTFBorderParaBottom;
782 info->fmt.wBorders |= 8;
783 info->fmt.dwMask |= PFM_BORDER;
784 break;
785 case rtfBorderSingle:
786 info->fmt.wBorders &= ~0x700;
787 info->fmt.wBorders |= 1 << 8;
788 info->fmt.dwMask |= PFM_BORDER;
789 break;
790 case rtfBorderThick:
791 info->fmt.wBorders &= ~0x700;
792 info->fmt.wBorders |= 2 << 8;
793 info->fmt.dwMask |= PFM_BORDER;
794 break;
795 case rtfBorderShadow:
796 info->fmt.wBorders &= ~0x700;
797 info->fmt.wBorders |= 10 << 8;
798 info->fmt.dwMask |= PFM_BORDER;
799 break;
800 case rtfBorderDouble:
801 info->fmt.wBorders &= ~0x700;
802 info->fmt.wBorders |= 7 << 8;
803 info->fmt.dwMask |= PFM_BORDER;
804 break;
805 case rtfBorderDot:
806 info->fmt.wBorders &= ~0x700;
807 info->fmt.wBorders |= 11 << 8;
808 info->fmt.dwMask |= PFM_BORDER;
809 break;
810 case rtfBorderWidth:
812 int borderSide = info->borderType & RTFBorderSideMask;
813 RTFTable *tableDef = info->tableDef;
814 if ((info->borderType & RTFBorderTypeMask) == RTFBorderTypeCell)
816 RTFBorder *border;
817 if (!tableDef || tableDef->numCellsDefined >= MAX_TABLE_CELLS)
818 break;
819 border = &tableDef->cells[tableDef->numCellsDefined].border[borderSide];
820 border->width = info->rtfParam;
821 break;
823 info->fmt.wBorderWidth = info->rtfParam;
824 info->fmt.dwMask |= PFM_BORDER;
825 break;
827 case rtfBorderSpace:
828 info->fmt.wBorderSpace = info->rtfParam;
829 info->fmt.dwMask |= PFM_BORDER;
830 break;
831 case rtfBorderColor:
833 RTFTable *tableDef = info->tableDef;
834 int borderSide = info->borderType & RTFBorderSideMask;
835 int borderType = info->borderType & RTFBorderTypeMask;
836 switch(borderType)
838 case RTFBorderTypePara:
839 if (!info->editor->bEmulateVersion10) /* v4.1 */
840 break;
841 /* v1.0 - 3.0 treat paragraph and row borders the same. */
842 case RTFBorderTypeRow:
843 if (tableDef) {
844 tableDef->border[borderSide].color = info->rtfParam;
846 break;
847 case RTFBorderTypeCell:
848 if (tableDef && tableDef->numCellsDefined < MAX_TABLE_CELLS) {
849 tableDef->cells[tableDef->numCellsDefined].border[borderSide].color = info->rtfParam;
851 break;
853 break;
855 case rtfRTLPar:
856 info->fmt.dwMask |= PFM_RTLPARA;
857 info->fmt.wEffects |= PFE_RTLPARA;
858 break;
859 case rtfLTRPar:
860 info->fmt.dwMask |= PFM_RTLPARA;
861 info->fmt.wEffects &= ~PFE_RTLPARA;
862 break;
866 void ME_RTFTblAttrHook(RTF_Info *info)
868 switch (info->rtfMinor)
870 case rtfRowDef:
872 if (!info->editor->bEmulateVersion10) /* v4.1 */
873 info->borderType = 0; /* Not sure */
874 else /* v1.0 - 3.0 */
875 info->borderType = RTFBorderRowTop;
876 if (!info->tableDef) {
877 info->tableDef = ME_MakeTableDef(info->editor);
878 } else {
879 ME_InitTableDef(info->editor, info->tableDef);
881 break;
883 case rtfCellPos:
885 int cellNum;
886 if (!info->tableDef)
888 info->tableDef = ME_MakeTableDef(info->editor);
890 cellNum = info->tableDef->numCellsDefined;
891 if (cellNum >= MAX_TABLE_CELLS)
892 break;
893 info->tableDef->cells[cellNum].rightBoundary = info->rtfParam;
894 if (cellNum < MAX_TAB_STOPS) {
895 /* Tab stops were used to store cell positions before v4.1 but v4.1
896 * still seems to set the tabstops without using them. */
897 ME_DisplayItem *para = info->editor->pCursors[0].pPara;
898 PARAFORMAT2 *pFmt = &para->member.para.fmt;
899 pFmt->rgxTabs[cellNum] &= ~0x00FFFFFF;
900 pFmt->rgxTabs[cellNum] |= 0x00FFFFFF & info->rtfParam;
902 info->tableDef->numCellsDefined++;
903 break;
905 case rtfRowBordTop:
906 info->borderType = RTFBorderRowTop;
907 break;
908 case rtfRowBordLeft:
909 info->borderType = RTFBorderRowLeft;
910 break;
911 case rtfRowBordBottom:
912 info->borderType = RTFBorderRowBottom;
913 break;
914 case rtfRowBordRight:
915 info->borderType = RTFBorderRowRight;
916 break;
917 case rtfCellBordTop:
918 info->borderType = RTFBorderCellTop;
919 break;
920 case rtfCellBordLeft:
921 info->borderType = RTFBorderCellLeft;
922 break;
923 case rtfCellBordBottom:
924 info->borderType = RTFBorderCellBottom;
925 break;
926 case rtfCellBordRight:
927 info->borderType = RTFBorderCellRight;
928 break;
929 case rtfRowGapH:
930 if (info->tableDef)
931 info->tableDef->gapH = info->rtfParam;
932 break;
933 case rtfRowLeftEdge:
934 if (info->tableDef)
935 info->tableDef->leftEdge = info->rtfParam;
936 break;
940 void ME_RTFSpecialCharHook(RTF_Info *info)
942 RTFTable *tableDef = info->tableDef;
943 switch (info->rtfMinor)
945 case rtfNestCell:
946 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
947 break;
948 /* else fall through since v4.1 treats rtfNestCell and rtfCell the same */
949 case rtfCell:
950 if (!tableDef)
951 break;
952 RTFFlushOutputBuffer(info);
953 if (!info->editor->bEmulateVersion10) { /* v4.1 */
954 if (tableDef->tableRowStart)
956 if (!info->nestingLevel &&
957 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
959 ME_DisplayItem *para = tableDef->tableRowStart;
960 para = para->member.para.next_para;
961 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
962 tableDef->tableRowStart = para;
963 info->nestingLevel = 1;
965 ME_InsertTableCellFromCursor(info->editor);
967 } else { /* v1.0 - v3.0 */
968 ME_DisplayItem *para = info->editor->pCursors[0].pPara;
969 PARAFORMAT2 *pFmt = &para->member.para.fmt;
970 if (pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE &&
971 tableDef->numCellsInserted < tableDef->numCellsDefined)
973 WCHAR tab = '\t';
974 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
975 tableDef->numCellsInserted++;
978 break;
979 case rtfNestRow:
980 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
981 break;
982 /* else fall through since v4.1 treats rtfNestRow and rtfRow the same */
983 case rtfRow:
985 ME_DisplayItem *para, *cell, *run;
986 int i;
988 if (!tableDef)
989 break;
990 RTFFlushOutputBuffer(info);
991 if (!info->editor->bEmulateVersion10) { /* v4.1 */
992 if (!tableDef->tableRowStart)
993 break;
994 if (!info->nestingLevel &&
995 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
997 para = tableDef->tableRowStart;
998 para = para->member.para.next_para;
999 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
1000 tableDef->tableRowStart = para;
1001 info->nestingLevel++;
1003 para = tableDef->tableRowStart;
1004 cell = ME_FindItemFwd(para, diCell);
1005 assert(cell && !cell->member.cell.prev_cell);
1006 if (tableDef->numCellsDefined < 1)
1008 /* 2000 twips appears to be the cell size that native richedit uses
1009 * when no cell sizes are specified. */
1010 const int defaultCellSize = 2000;
1011 int nRightBoundary = defaultCellSize;
1012 cell->member.cell.nRightBoundary = nRightBoundary;
1013 while (cell->member.cell.next_cell) {
1014 cell = cell->member.cell.next_cell;
1015 nRightBoundary += defaultCellSize;
1016 cell->member.cell.nRightBoundary = nRightBoundary;
1018 para = ME_InsertTableCellFromCursor(info->editor);
1019 cell = para->member.para.pCell;
1020 cell->member.cell.nRightBoundary = nRightBoundary;
1021 } else {
1022 for (i = 0; i < tableDef->numCellsDefined; i++)
1024 RTFCell *cellDef = &tableDef->cells[i];
1025 cell->member.cell.nRightBoundary = cellDef->rightBoundary;
1026 ME_ApplyBorderProperties(info, &cell->member.cell.border,
1027 cellDef->border);
1028 cell = cell->member.cell.next_cell;
1029 if (!cell)
1031 para = ME_InsertTableCellFromCursor(info->editor);
1032 cell = para->member.para.pCell;
1035 /* Cell for table row delimiter is empty */
1036 cell->member.cell.nRightBoundary = tableDef->cells[i-1].rightBoundary;
1039 run = ME_FindItemFwd(cell, diRun);
1040 if (info->editor->pCursors[0].pRun != run ||
1041 info->editor->pCursors[0].nOffset)
1043 int nOfs, nChars;
1044 /* Delete inserted cells that aren't defined. */
1045 info->editor->pCursors[1].pRun = run;
1046 info->editor->pCursors[1].pPara = ME_GetParagraph(run);
1047 info->editor->pCursors[1].nOffset = 0;
1048 nOfs = ME_GetCursorOfs(&info->editor->pCursors[1]);
1049 nChars = ME_GetCursorOfs(&info->editor->pCursors[0]) - nOfs;
1050 ME_InternalDeleteText(info->editor, &info->editor->pCursors[1],
1051 nChars, TRUE);
1054 para = ME_InsertTableRowEndFromCursor(info->editor);
1055 para->member.para.fmt.dxOffset = abs(info->tableDef->gapH);
1056 para->member.para.fmt.dxStartIndent = info->tableDef->leftEdge;
1057 ME_ApplyBorderProperties(info, &para->member.para.border,
1058 tableDef->border);
1059 info->nestingLevel--;
1060 if (!info->nestingLevel)
1062 if (info->canInheritInTbl) {
1063 tableDef->tableRowStart = para;
1064 } else {
1065 while (info->tableDef) {
1066 tableDef = info->tableDef;
1067 info->tableDef = tableDef->parent;
1068 heap_free(tableDef);
1071 } else {
1072 info->tableDef = tableDef->parent;
1073 heap_free(tableDef);
1075 } else { /* v1.0 - v3.0 */
1076 WCHAR endl = '\r';
1077 ME_DisplayItem *para = info->editor->pCursors[0].pPara;
1078 PARAFORMAT2 *pFmt = &para->member.para.fmt;
1079 pFmt->dxOffset = info->tableDef->gapH;
1080 pFmt->dxStartIndent = info->tableDef->leftEdge;
1082 ME_ApplyBorderProperties(info, &para->member.para.border,
1083 tableDef->border);
1084 while (tableDef->numCellsInserted < tableDef->numCellsDefined)
1086 WCHAR tab = '\t';
1087 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
1088 tableDef->numCellsInserted++;
1090 pFmt->cTabCount = min(tableDef->numCellsDefined, MAX_TAB_STOPS);
1091 if (!tableDef->numCellsDefined)
1092 pFmt->wEffects &= ~PFE_TABLE;
1093 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
1094 tableDef->numCellsInserted = 0;
1096 break;
1098 case rtfTab:
1099 case rtfPar:
1100 if (info->editor->bEmulateVersion10) { /* v1.0 - 3.0 */
1101 ME_DisplayItem *para;
1102 PARAFORMAT2 *pFmt;
1103 RTFFlushOutputBuffer(info);
1104 para = info->editor->pCursors[0].pPara;
1105 pFmt = &para->member.para.fmt;
1106 if (pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE)
1108 /* rtfPar is treated like a space within a table. */
1109 info->rtfClass = rtfText;
1110 info->rtfMajor = ' ';
1112 else if (info->rtfMinor == rtfPar && tableDef)
1113 tableDef->numCellsInserted = 0;
1115 break;
1119 static HRESULT insert_static_object(ME_TextEditor *editor, HENHMETAFILE hemf, HBITMAP hbmp,
1120 const SIZEL* sz)
1122 LPOLEOBJECT lpObject = NULL;
1123 LPSTORAGE lpStorage = NULL;
1124 LPOLECLIENTSITE lpClientSite = NULL;
1125 LPDATAOBJECT lpDataObject = NULL;
1126 LPOLECACHE lpOleCache = NULL;
1127 STGMEDIUM stgm;
1128 FORMATETC fm;
1129 CLSID clsid;
1130 HRESULT hr = E_FAIL;
1131 DWORD conn;
1133 if (hemf)
1135 stgm.tymed = TYMED_ENHMF;
1136 stgm.u.hEnhMetaFile = hemf;
1137 fm.cfFormat = CF_ENHMETAFILE;
1139 else if (hbmp)
1141 stgm.tymed = TYMED_GDI;
1142 stgm.u.hBitmap = hbmp;
1143 fm.cfFormat = CF_BITMAP;
1145 stgm.pUnkForRelease = NULL;
1147 fm.ptd = NULL;
1148 fm.dwAspect = DVASPECT_CONTENT;
1149 fm.lindex = -1;
1150 fm.tymed = stgm.tymed;
1152 if (!editor->reOle)
1154 if (!CreateIRichEditOle(NULL, editor, (LPVOID *)&editor->reOle))
1155 return hr;
1158 if (OleCreateDefaultHandler(&CLSID_NULL, NULL, &IID_IOleObject, (void**)&lpObject) == S_OK &&
1159 IRichEditOle_GetClientSite(editor->reOle, &lpClientSite) == S_OK &&
1160 IOleObject_SetClientSite(lpObject, lpClientSite) == S_OK &&
1161 IOleObject_GetUserClassID(lpObject, &clsid) == S_OK &&
1162 IOleObject_QueryInterface(lpObject, &IID_IOleCache, (void**)&lpOleCache) == S_OK &&
1163 IOleCache_Cache(lpOleCache, &fm, 0, &conn) == S_OK &&
1164 IOleObject_QueryInterface(lpObject, &IID_IDataObject, (void**)&lpDataObject) == S_OK &&
1165 IDataObject_SetData(lpDataObject, &fm, &stgm, TRUE) == S_OK)
1167 REOBJECT reobject;
1169 reobject.cbStruct = sizeof(reobject);
1170 reobject.cp = REO_CP_SELECTION;
1171 reobject.clsid = clsid;
1172 reobject.poleobj = lpObject;
1173 reobject.pstg = lpStorage;
1174 reobject.polesite = lpClientSite;
1175 /* convert from twips to .01 mm */
1176 reobject.sizel.cx = MulDiv(sz->cx, 254, 144);
1177 reobject.sizel.cy = MulDiv(sz->cy, 254, 144);
1178 reobject.dvaspect = DVASPECT_CONTENT;
1179 reobject.dwFlags = 0; /* FIXME */
1180 reobject.dwUser = 0;
1182 ME_InsertOLEFromCursor(editor, &reobject, 0);
1183 hr = S_OK;
1186 if (lpObject) IOleObject_Release(lpObject);
1187 if (lpClientSite) IOleClientSite_Release(lpClientSite);
1188 if (lpStorage) IStorage_Release(lpStorage);
1189 if (lpDataObject) IDataObject_Release(lpDataObject);
1190 if (lpOleCache) IOleCache_Release(lpOleCache);
1192 return hr;
1195 static void ME_RTFReadShpPictGroup( RTF_Info *info )
1197 int level = 1;
1199 for (;;)
1201 RTFGetToken (info);
1203 if (info->rtfClass == rtfEOF) return;
1204 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1206 if (--level == 0) break;
1208 else if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1210 level++;
1212 else
1214 RTFRouteToken( info );
1215 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1216 level--;
1220 RTFRouteToken( info ); /* feed "}" back to router */
1221 return;
1224 static DWORD read_hex_data( RTF_Info *info, BYTE **out )
1226 DWORD read = 0, size = 1024;
1227 BYTE *buf, val;
1228 BOOL flip;
1230 *out = NULL;
1232 if (info->rtfClass != rtfText)
1234 ERR("Called with incorrect token\n");
1235 return 0;
1238 buf = HeapAlloc( GetProcessHeap(), 0, size );
1239 if (!buf) return 0;
1241 val = info->rtfMajor;
1242 for (flip = TRUE;; flip = !flip)
1244 RTFGetToken( info );
1245 if (info->rtfClass == rtfEOF)
1247 HeapFree( GetProcessHeap(), 0, buf );
1248 return 0;
1250 if (info->rtfClass != rtfText) break;
1251 if (flip)
1253 if (read >= size)
1255 size *= 2;
1256 buf = HeapReAlloc( GetProcessHeap(), 0, buf, size );
1257 if (!buf) return 0;
1259 buf[read++] = RTFCharToHex(val) * 16 + RTFCharToHex(info->rtfMajor);
1261 else
1262 val = info->rtfMajor;
1264 if (flip) FIXME("wrong hex string\n");
1266 *out = buf;
1267 return read;
1270 static void ME_RTFReadPictGroup(RTF_Info *info)
1272 SIZEL sz;
1273 BYTE *buffer = NULL;
1274 DWORD size = 0;
1275 METAFILEPICT mfp;
1276 HENHMETAFILE hemf;
1277 HBITMAP hbmp;
1278 enum gfxkind {gfx_unknown = 0, gfx_enhmetafile, gfx_metafile, gfx_dib} gfx = gfx_unknown;
1279 int level = 1;
1281 mfp.mm = MM_TEXT;
1282 sz.cx = sz.cy = 0;
1284 for (;;)
1286 RTFGetToken( info );
1288 if (info->rtfClass == rtfText)
1290 if (level == 1)
1292 if (!buffer)
1293 size = read_hex_data( info, &buffer );
1295 else
1297 RTFSkipGroup( info );
1299 } /* We potentially have a new token so fall through. */
1301 if (info->rtfClass == rtfEOF) return;
1303 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1305 if (--level == 0) break;
1306 continue;
1308 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1310 level++;
1311 continue;
1313 if (!RTFCheckCM( info, rtfControl, rtfPictAttr ))
1315 RTFRouteToken( info );
1316 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1317 level--;
1318 continue;
1321 if (RTFCheckMM( info, rtfPictAttr, rtfWinMetafile ))
1323 mfp.mm = info->rtfParam;
1324 gfx = gfx_metafile;
1326 else if (RTFCheckMM( info, rtfPictAttr, rtfDevIndBitmap ))
1328 if (info->rtfParam != 0) FIXME("dibitmap should be 0 (%d)\n", info->rtfParam);
1329 gfx = gfx_dib;
1331 else if (RTFCheckMM( info, rtfPictAttr, rtfEmfBlip ))
1332 gfx = gfx_enhmetafile;
1333 else if (RTFCheckMM( info, rtfPictAttr, rtfPicWid ))
1334 mfp.xExt = info->rtfParam;
1335 else if (RTFCheckMM( info, rtfPictAttr, rtfPicHt ))
1336 mfp.yExt = info->rtfParam;
1337 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalWid ))
1338 sz.cx = info->rtfParam;
1339 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalHt ))
1340 sz.cy = info->rtfParam;
1341 else
1342 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1345 if (buffer)
1347 switch (gfx)
1349 case gfx_enhmetafile:
1350 if ((hemf = SetEnhMetaFileBits( size, buffer )))
1351 insert_static_object( info->editor, hemf, NULL, &sz );
1352 break;
1353 case gfx_metafile:
1354 if ((hemf = SetWinMetaFileBits( size, buffer, NULL, &mfp )))
1355 insert_static_object( info->editor, hemf, NULL, &sz );
1356 break;
1357 case gfx_dib:
1359 BITMAPINFO *bi = (BITMAPINFO*)buffer;
1360 HDC hdc = GetDC(0);
1361 unsigned nc = bi->bmiHeader.biClrUsed;
1363 /* not quite right, especially for bitfields type of compression */
1364 if (!nc && bi->bmiHeader.biBitCount <= 8)
1365 nc = 1 << bi->bmiHeader.biBitCount;
1366 if ((hbmp = CreateDIBitmap( hdc, &bi->bmiHeader,
1367 CBM_INIT, (char*)(bi + 1) + nc * sizeof(RGBQUAD),
1368 bi, DIB_RGB_COLORS)) )
1369 insert_static_object( info->editor, NULL, hbmp, &sz );
1370 ReleaseDC( 0, hdc );
1371 break;
1373 default:
1374 break;
1377 HeapFree( GetProcessHeap(), 0, buffer );
1378 RTFRouteToken( info ); /* feed "}" back to router */
1379 return;
1382 /* for now, lookup the \result part and use it, whatever the object */
1383 static void ME_RTFReadObjectGroup(RTF_Info *info)
1385 for (;;)
1387 RTFGetToken (info);
1388 if (info->rtfClass == rtfEOF)
1389 return;
1390 if (RTFCheckCM(info, rtfGroup, rtfEndGroup))
1391 break;
1392 if (RTFCheckCM(info, rtfGroup, rtfBeginGroup))
1394 RTFGetToken (info);
1395 if (info->rtfClass == rtfEOF)
1396 return;
1397 if (RTFCheckCMM(info, rtfControl, rtfDestination, rtfObjResult))
1399 int level = 1;
1401 while (RTFGetToken (info) != rtfEOF)
1403 if (info->rtfClass == rtfGroup)
1405 if (info->rtfMajor == rtfBeginGroup) level++;
1406 else if (info->rtfMajor == rtfEndGroup && --level < 0) break;
1408 RTFRouteToken(info);
1411 else RTFSkipGroup(info);
1412 continue;
1414 if (!RTFCheckCM (info, rtfControl, rtfObjAttr))
1416 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1417 return;
1420 RTFRouteToken(info); /* feed "}" back to router */
1423 static void ME_RTFReadParnumGroup( RTF_Info *info )
1425 int level = 1, type = -1;
1426 WORD indent = 0, start = 1;
1427 WCHAR txt_before = 0, txt_after = 0;
1429 for (;;)
1431 RTFGetToken( info );
1433 if (RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextBefore ) ||
1434 RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextAfter ))
1436 int loc = info->rtfMinor;
1438 RTFGetToken( info );
1439 if (info->rtfClass == rtfText)
1441 if (loc == rtfParNumTextBefore)
1442 txt_before = info->rtfMajor;
1443 else
1444 txt_after = info->rtfMajor;
1445 continue;
1447 /* falling through to catch EOFs and group level changes */
1450 if (info->rtfClass == rtfEOF)
1451 return;
1453 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1455 if (--level == 0) break;
1456 continue;
1459 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1461 level++;
1462 continue;
1465 /* Ignore non para-attr */
1466 if (!RTFCheckCM( info, rtfControl, rtfParAttr ))
1467 continue;
1469 switch (info->rtfMinor)
1471 case rtfParLevel: /* Para level is ignored */
1472 case rtfParSimple:
1473 break;
1474 case rtfParBullet:
1475 type = PFN_BULLET;
1476 break;
1478 case rtfParNumDecimal:
1479 type = PFN_ARABIC;
1480 break;
1481 case rtfParNumULetter:
1482 type = PFN_UCLETTER;
1483 break;
1484 case rtfParNumURoman:
1485 type = PFN_UCROMAN;
1486 break;
1487 case rtfParNumLLetter:
1488 type = PFN_LCLETTER;
1489 break;
1490 case rtfParNumLRoman:
1491 type = PFN_LCROMAN;
1492 break;
1494 case rtfParNumIndent:
1495 indent = info->rtfParam;
1496 break;
1497 case rtfParNumStartAt:
1498 start = info->rtfParam;
1499 break;
1503 if (type != -1)
1505 info->fmt.dwMask |= (PFM_NUMBERING | PFM_NUMBERINGSTART | PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB);
1506 info->fmt.wNumbering = type;
1507 info->fmt.wNumberingStart = start;
1508 info->fmt.wNumberingStyle = PFNS_PAREN;
1509 if (type != PFN_BULLET)
1511 if (txt_before == 0 && txt_after == 0)
1512 info->fmt.wNumberingStyle = PFNS_PLAIN;
1513 else if (txt_after == '.')
1514 info->fmt.wNumberingStyle = PFNS_PERIOD;
1515 else if (txt_before == '(' && txt_after == ')')
1516 info->fmt.wNumberingStyle = PFNS_PARENS;
1518 info->fmt.wNumberingTab = indent;
1521 TRACE("type %d indent %d start %d txt before %04x txt after %04x\n",
1522 type, indent, start, txt_before, txt_after);
1524 RTFRouteToken( info ); /* feed "}" back to router */
1527 static void ME_RTFReadHook(RTF_Info *info)
1529 switch(info->rtfClass)
1531 case rtfGroup:
1532 switch(info->rtfMajor)
1534 case rtfBeginGroup:
1535 if (info->stackTop < maxStack) {
1536 info->stack[info->stackTop].style = info->style;
1537 ME_AddRefStyle(info->style);
1538 info->stack[info->stackTop].codePage = info->codePage;
1539 info->stack[info->stackTop].unicodeLength = info->unicodeLength;
1541 info->stackTop++;
1542 info->styleChanged = FALSE;
1543 break;
1544 case rtfEndGroup:
1546 RTFFlushOutputBuffer(info);
1547 info->stackTop--;
1548 if (info->stackTop <= 0)
1549 info->rtfClass = rtfEOF;
1550 if (info->stackTop < 0)
1551 return;
1553 ME_ReleaseStyle(info->style);
1554 info->style = info->stack[info->stackTop].style;
1555 info->codePage = info->stack[info->stackTop].codePage;
1556 info->unicodeLength = info->stack[info->stackTop].unicodeLength;
1557 break;
1560 break;
1564 void
1565 ME_StreamInFill(ME_InStream *stream)
1567 stream->editstream->dwError = stream->editstream->pfnCallback(stream->editstream->dwCookie,
1568 (BYTE *)stream->buffer,
1569 sizeof(stream->buffer),
1570 (LONG *)&stream->dwSize);
1571 stream->dwUsed = 0;
1574 static LRESULT ME_StreamIn(ME_TextEditor *editor, DWORD format, EDITSTREAM *stream, BOOL stripLastCR)
1576 RTF_Info parser;
1577 ME_Style *style;
1578 int from, to, nUndoMode;
1579 int nEventMask = editor->nEventMask;
1580 ME_InStream inStream;
1581 BOOL invalidRTF = FALSE;
1582 ME_Cursor *selStart, *selEnd;
1583 LRESULT num_read = 0; /* bytes read for SF_TEXT, non-control chars inserted for SF_RTF */
1585 TRACE("stream==%p editor==%p format==0x%X\n", stream, editor, format);
1586 editor->nEventMask = 0;
1588 ME_GetSelectionOfs(editor, &from, &to);
1589 if (format & SFF_SELECTION && editor->mode & TM_RICHTEXT)
1591 ME_GetSelection(editor, &selStart, &selEnd);
1592 style = ME_GetSelectionInsertStyle(editor);
1594 ME_InternalDeleteText(editor, selStart, to - from, FALSE);
1596 /* Don't insert text at the end of the table row */
1597 if (!editor->bEmulateVersion10) { /* v4.1 */
1598 ME_DisplayItem *para = editor->pCursors->pPara;
1599 if (para->member.para.nFlags & MEPF_ROWEND)
1601 para = para->member.para.next_para;
1602 editor->pCursors[0].pPara = para;
1603 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
1604 editor->pCursors[0].nOffset = 0;
1606 if (para->member.para.nFlags & MEPF_ROWSTART)
1608 para = para->member.para.next_para;
1609 editor->pCursors[0].pPara = para;
1610 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
1611 editor->pCursors[0].nOffset = 0;
1613 editor->pCursors[1] = editor->pCursors[0];
1614 } else { /* v1.0 - 3.0 */
1615 if (editor->pCursors[0].pRun->member.run.nFlags & MERF_ENDPARA &&
1616 ME_IsInTable(editor->pCursors[0].pRun))
1617 return 0;
1619 } else {
1620 style = editor->pBuffer->pDefaultStyle;
1621 ME_AddRefStyle(style);
1622 ME_SetSelection(editor, 0, 0);
1623 ME_InternalDeleteText(editor, &editor->pCursors[1],
1624 ME_GetTextLength(editor), FALSE);
1625 from = to = 0;
1626 ME_ClearTempStyle(editor);
1627 ME_SetDefaultParaFormat(editor, &editor->pCursors[0].pPara->member.para.fmt);
1631 /* Back up undo mode to a local variable */
1632 nUndoMode = editor->nUndoMode;
1634 /* Only create an undo if SFF_SELECTION is set */
1635 if (!(format & SFF_SELECTION))
1636 editor->nUndoMode = umIgnore;
1638 inStream.editstream = stream;
1639 inStream.editstream->dwError = 0;
1640 inStream.dwSize = 0;
1641 inStream.dwUsed = 0;
1643 if (format & SF_RTF)
1645 /* Check if it's really RTF, and if it is not, use plain text */
1646 ME_StreamInFill(&inStream);
1647 if (!inStream.editstream->dwError)
1649 if ((!editor->bEmulateVersion10 && strncmp(inStream.buffer, "{\\rtf", 5) && strncmp(inStream.buffer, "{\\urtf", 6))
1650 || (editor->bEmulateVersion10 && *inStream.buffer != '{'))
1652 invalidRTF = TRUE;
1653 inStream.editstream->dwError = -16;
1658 if (!invalidRTF && !inStream.editstream->dwError)
1660 ME_Cursor start;
1661 from = ME_GetCursorOfs(&editor->pCursors[0]);
1662 if (format & SF_RTF) {
1664 /* setup the RTF parser */
1665 memset(&parser, 0, sizeof parser);
1666 RTFSetEditStream(&parser, &inStream);
1667 parser.rtfFormat = format&(SF_TEXT|SF_RTF);
1668 parser.editor = editor;
1669 parser.style = style;
1670 WriterInit(&parser);
1671 RTFInit(&parser);
1672 RTFSetReadHook(&parser, ME_RTFReadHook);
1673 RTFSetDestinationCallback(&parser, rtfShpPict, ME_RTFReadShpPictGroup);
1674 RTFSetDestinationCallback(&parser, rtfPict, ME_RTFReadPictGroup);
1675 RTFSetDestinationCallback(&parser, rtfObject, ME_RTFReadObjectGroup);
1676 RTFSetDestinationCallback(&parser, rtfParNumbering, ME_RTFReadParnumGroup);
1677 if (!parser.editor->bEmulateVersion10) /* v4.1 */
1679 RTFSetDestinationCallback(&parser, rtfNoNestTables, RTFSkipGroup);
1680 RTFSetDestinationCallback(&parser, rtfNestTableProps, RTFReadGroup);
1682 BeginFile(&parser);
1684 /* do the parsing */
1685 RTFRead(&parser);
1686 RTFFlushOutputBuffer(&parser);
1687 if (!editor->bEmulateVersion10) { /* v4.1 */
1688 if (parser.tableDef && parser.tableDef->tableRowStart &&
1689 (parser.nestingLevel > 0 || parser.canInheritInTbl))
1691 /* Delete any incomplete table row at the end of the rich text. */
1692 int nOfs, nChars;
1693 ME_DisplayItem *para;
1695 parser.rtfMinor = rtfRow;
1696 /* Complete the table row before deleting it.
1697 * By doing it this way we will have the current paragraph format set
1698 * properly to reflect that is not in the complete table, and undo items
1699 * will be added for this change to the current paragraph format. */
1700 if (parser.nestingLevel > 0)
1702 while (parser.nestingLevel > 1)
1703 ME_RTFSpecialCharHook(&parser); /* Decrements nestingLevel */
1704 para = parser.tableDef->tableRowStart;
1705 ME_RTFSpecialCharHook(&parser);
1706 } else {
1707 para = parser.tableDef->tableRowStart;
1708 ME_RTFSpecialCharHook(&parser);
1709 assert(para->member.para.nFlags & MEPF_ROWEND);
1710 para = para->member.para.next_para;
1713 editor->pCursors[1].pPara = para;
1714 editor->pCursors[1].pRun = ME_FindItemFwd(para, diRun);
1715 editor->pCursors[1].nOffset = 0;
1716 nOfs = ME_GetCursorOfs(&editor->pCursors[1]);
1717 nChars = ME_GetCursorOfs(&editor->pCursors[0]) - nOfs;
1718 ME_InternalDeleteText(editor, &editor->pCursors[1], nChars, TRUE);
1719 if (parser.tableDef)
1720 parser.tableDef->tableRowStart = NULL;
1723 ME_CheckTablesForCorruption(editor);
1724 RTFDestroy(&parser);
1726 if (parser.stackTop > 0)
1728 while (--parser.stackTop >= 0)
1730 ME_ReleaseStyle(parser.style);
1731 parser.style = parser.stack[parser.stackTop].style;
1733 if (!inStream.editstream->dwError)
1734 inStream.editstream->dwError = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
1737 /* Remove last line break, as mandated by tests. This is not affected by
1738 CR/LF counters, since RTF streaming presents only \para tokens, which
1739 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1741 if (stripLastCR && !(format & SFF_SELECTION)) {
1742 int newto;
1743 ME_GetSelection(editor, &selStart, &selEnd);
1744 newto = ME_GetCursorOfs(selEnd);
1745 if (newto > to + (editor->bEmulateVersion10 ? 1 : 0)) {
1746 WCHAR lastchar[3] = {'\0', '\0'};
1747 int linebreakSize = editor->bEmulateVersion10 ? 2 : 1;
1748 ME_Cursor linebreakCursor = *selEnd, lastcharCursor = *selEnd;
1749 CHARFORMAT2W cf;
1751 /* Set the final eop to the char fmt of the last char */
1752 cf.cbSize = sizeof(cf);
1753 cf.dwMask = CFM_ALL2;
1754 ME_MoveCursorChars(editor, &lastcharCursor, -1, FALSE);
1755 ME_GetCharFormat(editor, &lastcharCursor, &linebreakCursor, &cf);
1756 ME_SetSelection(editor, newto, -1);
1757 ME_SetSelectionCharFormat(editor, &cf);
1758 ME_SetSelection(editor, newto, newto);
1760 ME_MoveCursorChars(editor, &linebreakCursor, -linebreakSize, FALSE);
1761 ME_GetTextW(editor, lastchar, 2, &linebreakCursor, linebreakSize, FALSE, FALSE);
1762 if (lastchar[0] == '\r' && (lastchar[1] == '\n' || lastchar[1] == '\0')) {
1763 ME_InternalDeleteText(editor, &linebreakCursor, linebreakSize, FALSE);
1767 to = ME_GetCursorOfs(&editor->pCursors[0]);
1768 num_read = to - from;
1770 style = parser.style;
1772 else if (format & SF_TEXT)
1774 num_read = ME_StreamInText(editor, format, &inStream, style);
1775 to = ME_GetCursorOfs(&editor->pCursors[0]);
1777 else
1778 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1779 /* put the cursor at the top */
1780 if (!(format & SFF_SELECTION))
1781 ME_SetSelection(editor, 0, 0);
1782 ME_CursorFromCharOfs(editor, from, &start);
1783 ME_UpdateLinkAttribute(editor, &start, to - from);
1786 /* Restore saved undo mode */
1787 editor->nUndoMode = nUndoMode;
1789 /* even if we didn't add an undo, we need to commit anything on the stack */
1790 ME_CommitUndo(editor);
1792 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1793 if (!(format & SFF_SELECTION))
1794 ME_EmptyUndoStack(editor);
1796 ME_ReleaseStyle(style);
1797 editor->nEventMask = nEventMask;
1798 ME_UpdateRepaint(editor, FALSE);
1799 if (!(format & SFF_SELECTION)) {
1800 ME_ClearTempStyle(editor);
1802 ITextHost_TxShowCaret(editor->texthost, FALSE);
1803 ME_MoveCaret(editor);
1804 ITextHost_TxShowCaret(editor->texthost, TRUE);
1805 ME_SendSelChange(editor);
1806 ME_SendRequestResize(editor, FALSE);
1808 return num_read;
1812 typedef struct tagME_RTFStringStreamStruct
1814 char *string;
1815 int pos;
1816 int length;
1817 } ME_RTFStringStreamStruct;
1819 static DWORD CALLBACK ME_ReadFromRTFString(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
1821 ME_RTFStringStreamStruct *pStruct = (ME_RTFStringStreamStruct *)dwCookie;
1822 int count;
1824 count = min(cb, pStruct->length - pStruct->pos);
1825 memmove(lpBuff, pStruct->string + pStruct->pos, count);
1826 pStruct->pos += count;
1827 *pcb = count;
1828 return 0;
1831 static void
1832 ME_StreamInRTFString(ME_TextEditor *editor, BOOL selection, char *string)
1834 EDITSTREAM es;
1835 ME_RTFStringStreamStruct data;
1837 data.string = string;
1838 data.length = strlen(string);
1839 data.pos = 0;
1840 es.dwCookie = (DWORD_PTR)&data;
1841 es.pfnCallback = ME_ReadFromRTFString;
1842 ME_StreamIn(editor, SF_RTF | (selection ? SFF_SELECTION : 0), &es, TRUE);
1846 static int
1847 ME_FindText(ME_TextEditor *editor, DWORD flags, const CHARRANGE *chrg, const WCHAR *text, CHARRANGE *chrgText)
1849 const int nLen = lstrlenW(text);
1850 const int nTextLen = ME_GetTextLength(editor);
1851 int nMin, nMax;
1852 ME_Cursor cursor;
1853 WCHAR wLastChar = ' ';
1855 TRACE("flags==0x%08x, chrg->cpMin==%d, chrg->cpMax==%d text==%s\n",
1856 flags, chrg->cpMin, chrg->cpMax, debugstr_w(text));
1858 if (flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD))
1859 FIXME("Flags 0x%08x not implemented\n",
1860 flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD));
1862 nMin = chrg->cpMin;
1863 if (chrg->cpMax == -1)
1864 nMax = nTextLen;
1865 else
1866 nMax = chrg->cpMax > nTextLen ? nTextLen : chrg->cpMax;
1868 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1869 if (editor->bEmulateVersion10 && nMax == nTextLen)
1871 flags |= FR_DOWN;
1874 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1875 if (editor->bEmulateVersion10 && nMax < nMin)
1877 if (chrgText)
1879 chrgText->cpMin = -1;
1880 chrgText->cpMax = -1;
1882 return -1;
1885 /* when searching up, if cpMin < cpMax, then instead of searching
1886 * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on
1887 * [cpMax, cpMin]. The exception is when cpMax is -1, in which
1888 * case, it is always bigger than cpMin.
1890 if (!editor->bEmulateVersion10 && !(flags & FR_DOWN))
1892 int nSwap = nMax;
1894 nMax = nMin > nTextLen ? nTextLen : nMin;
1895 if (nMin < nSwap || chrg->cpMax == -1)
1896 nMin = 0;
1897 else
1898 nMin = nSwap;
1901 if (!nLen || nMin < 0 || nMax < 0 || nMax < nMin)
1903 if (chrgText)
1904 chrgText->cpMin = chrgText->cpMax = -1;
1905 return -1;
1908 if (flags & FR_DOWN) /* Forward search */
1910 /* If possible, find the character before where the search starts */
1911 if ((flags & FR_WHOLEWORD) && nMin)
1913 ME_CursorFromCharOfs(editor, nMin - 1, &cursor);
1914 wLastChar = *get_text( &cursor.pRun->member.run, cursor.nOffset );
1915 ME_MoveCursorChars(editor, &cursor, 1, FALSE);
1916 } else {
1917 ME_CursorFromCharOfs(editor, nMin, &cursor);
1920 while (cursor.pRun && ME_GetCursorOfs(&cursor) + nLen <= nMax)
1922 ME_DisplayItem *pCurItem = cursor.pRun;
1923 int nCurStart = cursor.nOffset;
1924 int nMatched = 0;
1926 while (pCurItem && ME_CharCompare( *get_text( &pCurItem->member.run, nCurStart + nMatched ), text[nMatched], (flags & FR_MATCHCASE)))
1928 if ((flags & FR_WHOLEWORD) && isalnumW(wLastChar))
1929 break;
1931 nMatched++;
1932 if (nMatched == nLen)
1934 ME_DisplayItem *pNextItem = pCurItem;
1935 int nNextStart = nCurStart;
1936 WCHAR wNextChar;
1938 /* Check to see if next character is a whitespace */
1939 if (flags & FR_WHOLEWORD)
1941 if (nCurStart + nMatched == pCurItem->member.run.len)
1943 pNextItem = ME_FindItemFwd(pCurItem, diRun);
1944 nNextStart = -nMatched;
1947 if (pNextItem)
1948 wNextChar = *get_text( &pNextItem->member.run, nNextStart + nMatched );
1949 else
1950 wNextChar = ' ';
1952 if (isalnumW(wNextChar))
1953 break;
1956 cursor.nOffset += cursor.pPara->member.para.nCharOfs + cursor.pRun->member.run.nCharOfs;
1957 if (chrgText)
1959 chrgText->cpMin = cursor.nOffset;
1960 chrgText->cpMax = cursor.nOffset + nLen;
1962 TRACE("found at %d-%d\n", cursor.nOffset, cursor.nOffset + nLen);
1963 return cursor.nOffset;
1965 if (nCurStart + nMatched == pCurItem->member.run.len)
1967 pCurItem = ME_FindItemFwd(pCurItem, diRun);
1968 nCurStart = -nMatched;
1971 if (pCurItem)
1972 wLastChar = *get_text( &pCurItem->member.run, nCurStart + nMatched );
1973 else
1974 wLastChar = ' ';
1976 cursor.nOffset++;
1977 if (cursor.nOffset == cursor.pRun->member.run.len)
1979 ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE);
1980 cursor.nOffset = 0;
1984 else /* Backward search */
1986 /* If possible, find the character after where the search ends */
1987 if ((flags & FR_WHOLEWORD) && nMax < nTextLen - 1)
1989 ME_CursorFromCharOfs(editor, nMax + 1, &cursor);
1990 wLastChar = *get_text( &cursor.pRun->member.run, cursor.nOffset );
1991 ME_MoveCursorChars(editor, &cursor, -1, FALSE);
1992 } else {
1993 ME_CursorFromCharOfs(editor, nMax, &cursor);
1996 while (cursor.pRun && ME_GetCursorOfs(&cursor) - nLen >= nMin)
1998 ME_DisplayItem *pCurItem = cursor.pRun;
1999 ME_DisplayItem *pCurPara = cursor.pPara;
2000 int nCurEnd = cursor.nOffset;
2001 int nMatched = 0;
2003 if (nCurEnd == 0)
2005 ME_PrevRun(&pCurPara, &pCurItem, TRUE);
2006 nCurEnd = pCurItem->member.run.len;
2009 while (pCurItem && ME_CharCompare( *get_text( &pCurItem->member.run, nCurEnd - nMatched - 1 ),
2010 text[nLen - nMatched - 1], (flags & FR_MATCHCASE) ))
2012 if ((flags & FR_WHOLEWORD) && isalnumW(wLastChar))
2013 break;
2015 nMatched++;
2016 if (nMatched == nLen)
2018 ME_DisplayItem *pPrevItem = pCurItem;
2019 int nPrevEnd = nCurEnd;
2020 WCHAR wPrevChar;
2021 int nStart;
2023 /* Check to see if previous character is a whitespace */
2024 if (flags & FR_WHOLEWORD)
2026 if (nPrevEnd - nMatched == 0)
2028 pPrevItem = ME_FindItemBack(pCurItem, diRun);
2029 if (pPrevItem)
2030 nPrevEnd = pPrevItem->member.run.len + nMatched;
2033 if (pPrevItem)
2034 wPrevChar = *get_text( &pPrevItem->member.run, nPrevEnd - nMatched - 1 );
2035 else
2036 wPrevChar = ' ';
2038 if (isalnumW(wPrevChar))
2039 break;
2042 nStart = pCurPara->member.para.nCharOfs
2043 + pCurItem->member.run.nCharOfs + nCurEnd - nMatched;
2044 if (chrgText)
2046 chrgText->cpMin = nStart;
2047 chrgText->cpMax = nStart + nLen;
2049 TRACE("found at %d-%d\n", nStart, nStart + nLen);
2050 return nStart;
2052 if (nCurEnd - nMatched == 0)
2054 ME_PrevRun(&pCurPara, &pCurItem, TRUE);
2055 /* Don't care about pCurItem becoming NULL here; it's already taken
2056 * care of in the exterior loop condition */
2057 nCurEnd = pCurItem->member.run.len + nMatched;
2060 if (pCurItem)
2061 wLastChar = *get_text( &pCurItem->member.run, nCurEnd - nMatched - 1 );
2062 else
2063 wLastChar = ' ';
2065 cursor.nOffset--;
2066 if (cursor.nOffset < 0)
2068 ME_PrevRun(&cursor.pPara, &cursor.pRun, TRUE);
2069 cursor.nOffset = cursor.pRun->member.run.len;
2073 TRACE("not found\n");
2074 if (chrgText)
2075 chrgText->cpMin = chrgText->cpMax = -1;
2076 return -1;
2079 static int ME_GetTextEx(ME_TextEditor *editor, GETTEXTEX *ex, LPARAM pText)
2081 int nChars;
2082 ME_Cursor start;
2084 if (!ex->cb || !pText) return 0;
2086 if (ex->flags & ~(GT_SELECTION | GT_USECRLF))
2087 FIXME("GETTEXTEX flags 0x%08x not supported\n", ex->flags & ~(GT_SELECTION | GT_USECRLF));
2089 if (ex->flags & GT_SELECTION)
2091 int from, to;
2092 int nStartCur = ME_GetSelectionOfs(editor, &from, &to);
2093 start = editor->pCursors[nStartCur];
2094 nChars = to - from;
2096 else
2098 ME_SetCursorToStart(editor, &start);
2099 nChars = INT_MAX;
2101 if (ex->codepage == CP_UNICODE)
2103 return ME_GetTextW(editor, (LPWSTR)pText, ex->cb / sizeof(WCHAR) - 1,
2104 &start, nChars, ex->flags & GT_USECRLF, FALSE);
2106 else
2108 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2109 we can just take a bigger buffer? :)
2110 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2111 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2113 int crlfmul = (ex->flags & GT_USECRLF) ? 2 : 1;
2114 DWORD buflen;
2115 LPWSTR buffer;
2116 LRESULT rc;
2118 buflen = min(crlfmul * nChars, ex->cb - 1);
2119 buffer = heap_alloc((buflen + 1) * sizeof(WCHAR));
2121 nChars = ME_GetTextW(editor, buffer, buflen, &start, nChars, ex->flags & GT_USECRLF, FALSE);
2122 rc = WideCharToMultiByte(ex->codepage, 0, buffer, nChars + 1,
2123 (LPSTR)pText, ex->cb, ex->lpDefaultChar, ex->lpUsedDefChar);
2124 if (rc) rc--; /* do not count 0 terminator */
2126 heap_free(buffer);
2127 return rc;
2131 static int ME_GetTextRange(ME_TextEditor *editor, WCHAR *strText,
2132 const ME_Cursor *start, int nLen, BOOL unicode)
2134 if (!strText) return 0;
2135 if (unicode) {
2136 return ME_GetTextW(editor, strText, INT_MAX, start, nLen, FALSE, FALSE);
2137 } else {
2138 int nChars;
2139 WCHAR *p = heap_alloc((nLen+1) * sizeof(*p));
2140 if (!p) return 0;
2141 nChars = ME_GetTextW(editor, p, nLen, start, nLen, FALSE, FALSE);
2142 WideCharToMultiByte(CP_ACP, 0, p, nChars+1, (char *)strText,
2143 nLen+1, NULL, NULL);
2144 heap_free(p);
2145 return nChars;
2149 static int handle_EM_EXSETSEL( ME_TextEditor *editor, int to, int from )
2151 int end;
2153 TRACE("%d - %d\n", to, from );
2155 ME_InvalidateSelection( editor );
2156 end = ME_SetSelection( editor, to, from );
2157 ME_InvalidateSelection( editor );
2158 ITextHost_TxShowCaret( editor->texthost, FALSE );
2159 ME_ShowCaret( editor );
2160 ME_SendSelChange( editor );
2162 return end;
2165 typedef struct tagME_GlobalDestStruct
2167 HGLOBAL hData;
2168 int nLength;
2169 } ME_GlobalDestStruct;
2171 static DWORD CALLBACK ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2173 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2174 int i;
2175 WORD *pSrc, *pDest;
2177 cb = cb >> 1;
2178 pDest = (WORD *)lpBuff;
2179 pSrc = GlobalLock(pData->hData);
2180 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2181 pDest[i] = pSrc[pData->nLength+i];
2183 pData->nLength += i;
2184 *pcb = 2*i;
2185 GlobalUnlock(pData->hData);
2186 return 0;
2189 static DWORD CALLBACK ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2191 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2192 int i;
2193 BYTE *pSrc, *pDest;
2195 pDest = lpBuff;
2196 pSrc = GlobalLock(pData->hData);
2197 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2198 pDest[i] = pSrc[pData->nLength+i];
2200 pData->nLength += i;
2201 *pcb = i;
2202 GlobalUnlock(pData->hData);
2203 return 0;
2206 static const WCHAR rtfW[] = {'R','i','c','h',' ','T','e','x','t',' ','F','o','r','m','a','t',0};
2208 static HRESULT paste_rtf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2210 EDITSTREAM es;
2211 ME_GlobalDestStruct gds;
2212 HRESULT hr;
2214 gds.hData = med->u.hGlobal;
2215 gds.nLength = 0;
2216 es.dwCookie = (DWORD_PTR)&gds;
2217 es.pfnCallback = ME_ReadFromHGLOBALRTF;
2218 hr = ME_StreamIn( editor, SF_RTF | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2219 ReleaseStgMedium( med );
2220 return hr;
2223 static HRESULT paste_text(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2225 EDITSTREAM es;
2226 ME_GlobalDestStruct gds;
2227 HRESULT hr;
2229 gds.hData = med->u.hGlobal;
2230 gds.nLength = 0;
2231 es.dwCookie = (DWORD_PTR)&gds;
2232 es.pfnCallback = ME_ReadFromHGLOBALUnicode;
2233 hr = ME_StreamIn( editor, SF_TEXT | SF_UNICODE | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2234 ReleaseStgMedium( med );
2235 return hr;
2238 static HRESULT paste_emf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2240 HRESULT hr;
2241 SIZEL sz = {0, 0};
2243 hr = insert_static_object( editor, med->u.hEnhMetaFile, NULL, &sz );
2244 if (SUCCEEDED(hr))
2246 ME_CommitUndo( editor );
2247 ME_UpdateRepaint( editor, FALSE );
2249 else
2250 ReleaseStgMedium( med );
2252 return hr;
2255 static struct paste_format
2257 FORMATETC fmt;
2258 HRESULT (*paste)(ME_TextEditor *, FORMATETC *, STGMEDIUM *);
2259 const WCHAR *name;
2260 } paste_formats[] =
2262 {{ -1, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_rtf, rtfW },
2263 {{ CF_UNICODETEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_text },
2264 {{ CF_ENHMETAFILE, NULL, DVASPECT_CONTENT, -1, TYMED_ENHMF }, paste_emf },
2265 {{ 0 }}
2268 static void init_paste_formats(void)
2270 struct paste_format *format;
2271 static int done;
2273 if (!done)
2275 for (format = paste_formats; format->fmt.cfFormat; format++)
2277 if (format->name)
2278 format->fmt.cfFormat = RegisterClipboardFormatW( format->name );
2280 done = 1;
2284 static BOOL paste_special(ME_TextEditor *editor, UINT cf, REPASTESPECIAL *ps, BOOL check_only)
2286 HRESULT hr;
2287 STGMEDIUM med;
2288 struct paste_format *format;
2289 IDataObject *data;
2291 /* Protect read-only edit control from modification */
2292 if (editor->styleFlags & ES_READONLY)
2294 if (!check_only)
2295 MessageBeep(MB_ICONERROR);
2296 return FALSE;
2299 init_paste_formats();
2301 if (ps && ps->dwAspect != DVASPECT_CONTENT)
2302 FIXME("Ignoring aspect %x\n", ps->dwAspect);
2304 hr = OleGetClipboard( &data );
2305 if (hr != S_OK) return FALSE;
2307 if (cf == CF_TEXT) cf = CF_UNICODETEXT;
2309 hr = S_FALSE;
2310 for (format = paste_formats; format->fmt.cfFormat; format++)
2312 if (cf && cf != format->fmt.cfFormat) continue;
2313 hr = IDataObject_QueryGetData( data, &format->fmt );
2314 if (hr == S_OK)
2316 if (!check_only)
2318 hr = IDataObject_GetData( data, &format->fmt, &med );
2319 if (hr != S_OK) goto done;
2320 hr = format->paste( editor, &format->fmt, &med );
2322 break;
2326 done:
2327 IDataObject_Release( data );
2329 return hr == S_OK;
2332 static BOOL ME_Copy(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
2334 LPDATAOBJECT dataObj = NULL;
2335 HRESULT hr = S_OK;
2337 if (editor->cPasswordMask)
2338 return FALSE; /* Copying or Cutting masked text isn't allowed */
2340 if(editor->lpOleCallback)
2342 CHARRANGE range;
2343 range.cpMin = ME_GetCursorOfs(start);
2344 range.cpMax = range.cpMin + nChars;
2345 hr = IRichEditOleCallback_GetClipboardData(editor->lpOleCallback, &range, RECO_COPY, &dataObj);
2347 if(FAILED(hr) || !dataObj)
2348 hr = ME_GetDataObject(editor, start, nChars, &dataObj);
2349 if(SUCCEEDED(hr)) {
2350 hr = OleSetClipboard(dataObj);
2351 IDataObject_Release(dataObj);
2353 return SUCCEEDED(hr);
2356 static BOOL copy_or_cut(ME_TextEditor *editor, BOOL cut)
2358 BOOL result;
2359 int offs, num_chars;
2360 int start_cursor = ME_GetSelectionOfs(editor, &offs, &num_chars);
2361 ME_Cursor *sel_start = &editor->pCursors[start_cursor];
2363 if (cut && (editor->styleFlags & ES_READONLY))
2365 MessageBeep(MB_ICONERROR);
2366 return FALSE;
2369 num_chars -= offs;
2370 result = ME_Copy(editor, sel_start, num_chars);
2371 if (result && cut)
2373 ME_InternalDeleteText(editor, sel_start, num_chars, FALSE);
2374 ME_CommitUndo(editor);
2375 ME_UpdateRepaint(editor, TRUE);
2377 return result;
2380 /* helper to send a msg filter notification */
2381 static BOOL
2382 ME_FilterEvent(ME_TextEditor *editor, UINT msg, WPARAM* wParam, LPARAM* lParam)
2384 MSGFILTER msgf;
2386 if (!editor->hWnd || !editor->hwndParent) return FALSE;
2387 msgf.nmhdr.hwndFrom = editor->hWnd;
2388 msgf.nmhdr.idFrom = GetWindowLongW(editor->hWnd, GWLP_ID);
2389 msgf.nmhdr.code = EN_MSGFILTER;
2390 msgf.msg = msg;
2391 msgf.wParam = *wParam;
2392 msgf.lParam = *lParam;
2393 if (SendMessageW(editor->hwndParent, WM_NOTIFY, msgf.nmhdr.idFrom, (LPARAM)&msgf))
2394 return FALSE;
2395 *wParam = msgf.wParam;
2396 *lParam = msgf.lParam;
2397 msgf.wParam = *wParam;
2399 return TRUE;
2402 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor *editor)
2404 ME_DisplayItem *startPara, *endPara;
2405 ME_DisplayItem *prev_para;
2406 ME_Cursor *from, *to;
2407 ME_Cursor start;
2408 int nChars;
2410 if (!editor->AutoURLDetect_bEnable) return;
2412 ME_GetSelection(editor, &from, &to);
2414 /* Find paragraph previous to the one that contains start cursor */
2415 startPara = from->pPara;
2416 prev_para = startPara->member.para.prev_para;
2417 if (prev_para->type == diParagraph) startPara = prev_para;
2419 /* Find paragraph that contains end cursor */
2420 endPara = to->pPara->member.para.next_para;
2422 start.pPara = startPara;
2423 start.pRun = ME_FindItemFwd(startPara, diRun);
2424 start.nOffset = 0;
2425 nChars = endPara->member.para.nCharOfs - startPara->member.para.nCharOfs;
2427 ME_UpdateLinkAttribute(editor, &start, nChars);
2430 static BOOL handle_enter(ME_TextEditor *editor)
2432 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2433 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2435 if (editor->bDialogMode)
2437 if (ctrl_is_down)
2438 return TRUE;
2440 if (!(editor->styleFlags & ES_WANTRETURN))
2442 if (editor->hwndParent)
2444 DWORD dw;
2445 dw = SendMessageW(editor->hwndParent, DM_GETDEFID, 0, 0);
2446 if (HIWORD(dw) == DC_HASDEFID)
2448 HWND hwDefCtrl = GetDlgItem(editor->hwndParent, LOWORD(dw));
2449 if (hwDefCtrl)
2451 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, (WPARAM)hwDefCtrl, TRUE);
2452 PostMessageW(hwDefCtrl, WM_KEYDOWN, VK_RETURN, 0);
2456 return TRUE;
2460 if (editor->styleFlags & ES_MULTILINE)
2462 static const WCHAR endl = '\r';
2463 static const WCHAR endlv10[] = {'\r','\n'};
2464 ME_Cursor cursor = editor->pCursors[0];
2465 ME_DisplayItem *para = cursor.pPara;
2466 int from, to;
2467 ME_Style *style, *eop_style;
2469 if (editor->styleFlags & ES_READONLY)
2471 MessageBeep(MB_ICONERROR);
2472 return TRUE;
2475 ME_GetSelectionOfs(editor, &from, &to);
2476 if (editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2478 if (!editor->bEmulateVersion10) /* v4.1 */
2480 if (para->member.para.nFlags & MEPF_ROWEND)
2482 /* Add a new table row after this row. */
2483 para = ME_AppendTableRow(editor, para);
2484 para = para->member.para.next_para;
2485 editor->pCursors[0].pPara = para;
2486 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2487 editor->pCursors[0].nOffset = 0;
2488 editor->pCursors[1] = editor->pCursors[0];
2489 ME_CommitUndo(editor);
2490 ME_CheckTablesForCorruption(editor);
2491 ME_UpdateRepaint(editor, FALSE);
2492 return TRUE;
2494 else if (para == editor->pCursors[1].pPara &&
2495 cursor.nOffset + cursor.pRun->member.run.nCharOfs == 0 &&
2496 para->member.para.prev_para->member.para.nFlags & MEPF_ROWSTART &&
2497 !para->member.para.prev_para->member.para.nCharOfs)
2499 /* Insert a newline before the table. */
2500 para = para->member.para.prev_para;
2501 para->member.para.nFlags &= ~MEPF_ROWSTART;
2502 editor->pCursors[0].pPara = para;
2503 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2504 editor->pCursors[1] = editor->pCursors[0];
2505 ME_InsertTextFromCursor(editor, 0, &endl, 1,
2506 editor->pCursors[0].pRun->member.run.style);
2507 para = editor->pBuffer->pFirst->member.para.next_para;
2508 ME_SetDefaultParaFormat(editor, &para->member.para.fmt);
2509 para->member.para.nFlags = 0;
2510 mark_para_rewrap(editor, para);
2511 editor->pCursors[0].pPara = para;
2512 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2513 editor->pCursors[1] = editor->pCursors[0];
2514 para->member.para.next_para->member.para.nFlags |= MEPF_ROWSTART;
2515 ME_CommitCoalescingUndo(editor);
2516 ME_CheckTablesForCorruption(editor);
2517 ME_UpdateRepaint(editor, FALSE);
2518 return TRUE;
2521 else /* v1.0 - 3.0 */
2523 ME_DisplayItem *para = cursor.pPara;
2524 if (ME_IsInTable(para))
2526 if (cursor.pRun->member.run.nFlags & MERF_ENDPARA)
2528 if (from == to)
2530 ME_ContinueCoalescingTransaction(editor);
2531 para = ME_AppendTableRow(editor, para);
2532 editor->pCursors[0].pPara = para;
2533 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2534 editor->pCursors[0].nOffset = 0;
2535 editor->pCursors[1] = editor->pCursors[0];
2536 ME_CommitCoalescingUndo(editor);
2537 ME_UpdateRepaint(editor, FALSE);
2538 return TRUE;
2541 else
2543 ME_ContinueCoalescingTransaction(editor);
2544 if (cursor.pRun->member.run.nCharOfs + cursor.nOffset == 0 &&
2545 !ME_IsInTable(para->member.para.prev_para))
2547 /* Insert newline before table */
2548 cursor.pRun = ME_FindItemBack(para, diRun);
2549 if (cursor.pRun)
2551 editor->pCursors[0].pRun = cursor.pRun;
2552 editor->pCursors[0].pPara = para->member.para.prev_para;
2554 editor->pCursors[0].nOffset = 0;
2555 editor->pCursors[1] = editor->pCursors[0];
2556 ME_InsertTextFromCursor(editor, 0, &endl, 1,
2557 editor->pCursors[0].pRun->member.run.style);
2559 else
2561 editor->pCursors[1] = editor->pCursors[0];
2562 para = ME_AppendTableRow(editor, para);
2563 editor->pCursors[0].pPara = para;
2564 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2565 editor->pCursors[0].nOffset = 0;
2566 editor->pCursors[1] = editor->pCursors[0];
2568 ME_CommitCoalescingUndo(editor);
2569 ME_UpdateRepaint(editor, FALSE);
2570 return TRUE;
2575 style = ME_GetInsertStyle(editor, 0);
2577 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2578 eop style (this prevents the list label style changing when the new eop is inserted).
2579 No extra ref is taken here on eop_style. */
2580 if (para->member.para.fmt.wNumbering)
2581 eop_style = para->member.para.eop_run->style;
2582 else
2583 eop_style = style;
2584 ME_ContinueCoalescingTransaction(editor);
2585 if (shift_is_down)
2586 ME_InsertEndRowFromCursor(editor, 0);
2587 else
2588 if (!editor->bEmulateVersion10)
2589 ME_InsertTextFromCursor(editor, 0, &endl, 1, eop_style);
2590 else
2591 ME_InsertTextFromCursor(editor, 0, endlv10, 2, eop_style);
2592 ME_CommitCoalescingUndo(editor);
2593 SetCursor(NULL);
2595 ME_UpdateSelectionLinkAttribute(editor);
2596 ME_UpdateRepaint(editor, FALSE);
2597 ME_SaveTempStyle(editor, style); /* set the temp insert style for the new para */
2598 ME_ReleaseStyle(style);
2600 return TRUE;
2602 return FALSE;
2605 static BOOL
2606 ME_KeyDown(ME_TextEditor *editor, WORD nKey)
2608 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2609 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2611 if (editor->bMouseCaptured)
2612 return FALSE;
2613 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey != VK_MENU)
2614 editor->nSelectionType = stPosition;
2616 switch (nKey)
2618 case VK_LEFT:
2619 case VK_RIGHT:
2620 case VK_HOME:
2621 case VK_END:
2622 editor->nUDArrowX = -1;
2623 /* fall through */
2624 case VK_UP:
2625 case VK_DOWN:
2626 case VK_PRIOR:
2627 case VK_NEXT:
2628 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
2629 ME_ArrowKey(editor, nKey, shift_is_down, ctrl_is_down);
2630 return TRUE;
2631 case VK_BACK:
2632 case VK_DELETE:
2633 editor->nUDArrowX = -1;
2634 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2635 if (editor->styleFlags & ES_READONLY)
2636 return FALSE;
2637 if (ME_IsSelection(editor))
2639 ME_DeleteSelection(editor);
2640 ME_CommitUndo(editor);
2642 else if (nKey == VK_DELETE)
2644 /* Delete stops group typing.
2645 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2646 ME_DeleteTextAtCursor(editor, 1, 1);
2647 ME_CommitUndo(editor);
2649 else if (ME_ArrowKey(editor, VK_LEFT, FALSE, FALSE))
2651 BOOL bDeletionSucceeded;
2652 /* Backspace can be grouped for a single undo */
2653 ME_ContinueCoalescingTransaction(editor);
2654 bDeletionSucceeded = ME_DeleteTextAtCursor(editor, 1, 1);
2655 if (!bDeletionSucceeded && !editor->bEmulateVersion10) { /* v4.1 */
2656 /* Deletion was prevented so the cursor is moved back to where it was.
2657 * (e.g. this happens when trying to delete cell boundaries)
2659 ME_ArrowKey(editor, VK_RIGHT, FALSE, FALSE);
2661 ME_CommitCoalescingUndo(editor);
2663 else
2664 return TRUE;
2665 ME_MoveCursorFromTableRowStartParagraph(editor);
2666 ME_UpdateSelectionLinkAttribute(editor);
2667 ME_UpdateRepaint(editor, FALSE);
2668 ME_SendRequestResize(editor, FALSE);
2669 return TRUE;
2670 case VK_RETURN:
2671 return handle_enter(editor);
2672 case VK_ESCAPE:
2673 if (editor->bDialogMode && editor->hwndParent)
2674 PostMessageW(editor->hwndParent, WM_CLOSE, 0, 0);
2675 return TRUE;
2676 case VK_TAB:
2677 if (editor->bDialogMode && editor->hwndParent)
2678 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, shift_is_down, 0);
2679 return TRUE;
2680 case 'A':
2681 if (ctrl_is_down)
2683 handle_EM_EXSETSEL( editor, 0, -1 );
2684 return TRUE;
2686 break;
2687 case 'V':
2688 if (ctrl_is_down)
2689 return paste_special( editor, 0, NULL, FALSE );
2690 break;
2691 case 'C':
2692 case 'X':
2693 if (ctrl_is_down)
2694 return copy_or_cut(editor, nKey == 'X');
2695 break;
2696 case 'Z':
2697 if (ctrl_is_down)
2699 ME_Undo(editor);
2700 return TRUE;
2702 break;
2703 case 'Y':
2704 if (ctrl_is_down)
2706 ME_Redo(editor);
2707 return TRUE;
2709 break;
2711 default:
2712 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey && nKey != VK_MENU)
2713 editor->nUDArrowX = -1;
2714 if (ctrl_is_down)
2716 if (nKey == 'W')
2718 CHARFORMAT2W chf;
2719 char buf[2048];
2720 chf.cbSize = sizeof(chf);
2722 ME_GetSelectionCharFormat(editor, &chf);
2723 ME_DumpStyleToBuf(&chf, buf);
2724 MessageBoxA(NULL, buf, "Style dump", MB_OK);
2726 if (nKey == 'Q')
2728 ME_CheckCharOffsets(editor);
2732 return FALSE;
2735 static LRESULT ME_Char(ME_TextEditor *editor, WPARAM charCode,
2736 LPARAM flags, BOOL unicode)
2738 WCHAR wstr;
2740 if (editor->bMouseCaptured)
2741 return 0;
2743 if (unicode)
2744 wstr = (WCHAR)charCode;
2745 else
2747 CHAR charA = charCode;
2748 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &wstr, 1);
2751 if (editor->styleFlags & ES_READONLY) {
2752 MessageBeep(MB_ICONERROR);
2753 return 0; /* FIXME really 0 ? */
2756 if ((unsigned)wstr >= ' ' || wstr == '\t')
2758 ME_Cursor cursor = editor->pCursors[0];
2759 ME_DisplayItem *para = cursor.pPara;
2760 int from, to;
2761 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2762 ME_GetSelectionOfs(editor, &from, &to);
2763 if (wstr == '\t' &&
2764 /* v4.1 allows tabs to be inserted with ctrl key down */
2765 !(ctrl_is_down && !editor->bEmulateVersion10))
2767 ME_DisplayItem *para;
2768 BOOL bSelectedRow = FALSE;
2770 para = cursor.pPara;
2771 if (ME_IsSelection(editor) &&
2772 cursor.pRun->member.run.nCharOfs + cursor.nOffset == 0 &&
2773 to == ME_GetCursorOfs(&editor->pCursors[0]) &&
2774 para->member.para.prev_para->type == diParagraph)
2776 para = para->member.para.prev_para;
2777 bSelectedRow = TRUE;
2779 if (ME_IsInTable(para))
2781 ME_TabPressedInTable(editor, bSelectedRow);
2782 ME_CommitUndo(editor);
2783 return 0;
2785 } else if (!editor->bEmulateVersion10) { /* v4.1 */
2786 if (para->member.para.nFlags & MEPF_ROWEND) {
2787 if (from == to) {
2788 para = para->member.para.next_para;
2789 if (para->member.para.nFlags & MEPF_ROWSTART)
2790 para = para->member.para.next_para;
2791 editor->pCursors[0].pPara = para;
2792 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2793 editor->pCursors[0].nOffset = 0;
2794 editor->pCursors[1] = editor->pCursors[0];
2797 } else { /* v1.0 - 3.0 */
2798 if (ME_IsInTable(cursor.pRun) &&
2799 cursor.pRun->member.run.nFlags & MERF_ENDPARA &&
2800 from == to)
2802 /* Text should not be inserted at the end of the table. */
2803 MessageBeep(-1);
2804 return 0;
2807 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2808 /* WM_CHAR is restricted to nTextLimit */
2809 if(editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2811 ME_Style *style = ME_GetInsertStyle(editor, 0);
2812 ME_ContinueCoalescingTransaction(editor);
2813 ME_InsertTextFromCursor(editor, 0, &wstr, 1, style);
2814 ME_ReleaseStyle(style);
2815 ME_CommitCoalescingUndo(editor);
2816 ITextHost_TxSetCursor(editor->texthost, NULL, FALSE);
2819 ME_UpdateSelectionLinkAttribute(editor);
2820 ME_UpdateRepaint(editor, FALSE);
2822 return 0;
2825 /* Process the message and calculate the new click count.
2827 * returns: The click count if it is mouse down event, else returns 0. */
2828 static int ME_CalculateClickCount(ME_TextEditor *editor, UINT msg, WPARAM wParam,
2829 LPARAM lParam)
2831 static int clickNum = 0;
2832 if (msg < WM_MOUSEFIRST || msg > WM_MOUSELAST)
2833 return 0;
2835 if ((msg == WM_LBUTTONDBLCLK) ||
2836 (msg == WM_RBUTTONDBLCLK) ||
2837 (msg == WM_MBUTTONDBLCLK) ||
2838 (msg == WM_XBUTTONDBLCLK))
2840 msg -= (WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
2843 if ((msg == WM_LBUTTONDOWN) ||
2844 (msg == WM_RBUTTONDOWN) ||
2845 (msg == WM_MBUTTONDOWN) ||
2846 (msg == WM_XBUTTONDOWN))
2848 static MSG prevClickMsg;
2849 MSG clickMsg;
2850 /* Compare the editor instead of the hwnd so that the this
2851 * can still be done for windowless richedit controls. */
2852 clickMsg.hwnd = (HWND)editor;
2853 clickMsg.message = msg;
2854 clickMsg.wParam = wParam;
2855 clickMsg.lParam = lParam;
2856 clickMsg.time = GetMessageTime();
2857 clickMsg.pt.x = (short)LOWORD(lParam);
2858 clickMsg.pt.y = (short)HIWORD(lParam);
2859 if ((clickNum != 0) &&
2860 (clickMsg.message == prevClickMsg.message) &&
2861 (clickMsg.hwnd == prevClickMsg.hwnd) &&
2862 (clickMsg.wParam == prevClickMsg.wParam) &&
2863 (clickMsg.time - prevClickMsg.time < GetDoubleClickTime()) &&
2864 (abs(clickMsg.pt.x - prevClickMsg.pt.x) < GetSystemMetrics(SM_CXDOUBLECLK)/2) &&
2865 (abs(clickMsg.pt.y - prevClickMsg.pt.y) < GetSystemMetrics(SM_CYDOUBLECLK)/2))
2867 clickNum++;
2868 } else {
2869 clickNum = 1;
2871 prevClickMsg = clickMsg;
2872 } else {
2873 return 0;
2875 return clickNum;
2878 static BOOL is_link( ME_Run *run )
2880 return (run->style->fmt.dwMask & CFM_LINK) && (run->style->fmt.dwEffects & CFE_LINK);
2883 static BOOL ME_SetCursor(ME_TextEditor *editor)
2885 ME_Cursor cursor;
2886 POINT pt;
2887 BOOL isExact;
2888 SCROLLBARINFO sbi;
2889 DWORD messagePos = GetMessagePos();
2890 pt.x = (short)LOWORD(messagePos);
2891 pt.y = (short)HIWORD(messagePos);
2893 if (editor->hWnd)
2895 sbi.cbSize = sizeof(sbi);
2896 GetScrollBarInfo(editor->hWnd, OBJID_HSCROLL, &sbi);
2897 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2898 PtInRect(&sbi.rcScrollBar, pt))
2900 ITextHost_TxSetCursor(editor->texthost,
2901 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2902 return TRUE;
2904 sbi.cbSize = sizeof(sbi);
2905 GetScrollBarInfo(editor->hWnd, OBJID_VSCROLL, &sbi);
2906 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2907 PtInRect(&sbi.rcScrollBar, pt))
2909 ITextHost_TxSetCursor(editor->texthost,
2910 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2911 return TRUE;
2914 ITextHost_TxScreenToClient(editor->texthost, &pt);
2916 if (editor->nSelectionType == stLine && editor->bMouseCaptured) {
2917 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2918 return TRUE;
2920 if (!editor->bEmulateVersion10 /* v4.1 */ &&
2921 pt.y < editor->rcFormat.top &&
2922 pt.x < editor->rcFormat.left)
2924 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2925 return TRUE;
2927 if (pt.y < editor->rcFormat.top || pt.y > editor->rcFormat.bottom)
2929 if (editor->bEmulateVersion10) /* v1.0 - 3.0 */
2930 ITextHost_TxSetCursor(editor->texthost,
2931 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2932 else /* v4.1 */
2933 ITextHost_TxSetCursor(editor->texthost,
2934 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
2935 return TRUE;
2937 if (pt.x < editor->rcFormat.left)
2939 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2940 return TRUE;
2942 ME_CharFromPos(editor, pt.x, pt.y, &cursor, &isExact);
2943 if (isExact)
2945 ME_Run *run;
2947 run = &cursor.pRun->member.run;
2948 if (is_link( run ))
2950 ITextHost_TxSetCursor(editor->texthost,
2951 LoadCursorW(NULL, (WCHAR*)IDC_HAND),
2952 FALSE);
2953 return TRUE;
2956 if (ME_IsSelection(editor))
2958 int selStart, selEnd;
2959 int offset = ME_GetCursorOfs(&cursor);
2961 ME_GetSelectionOfs(editor, &selStart, &selEnd);
2962 if (selStart <= offset && selEnd >= offset) {
2963 ITextHost_TxSetCursor(editor->texthost,
2964 LoadCursorW(NULL, (WCHAR*)IDC_ARROW),
2965 FALSE);
2966 return TRUE;
2970 ITextHost_TxSetCursor(editor->texthost,
2971 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
2972 return TRUE;
2975 static void ME_SetDefaultFormatRect(ME_TextEditor *editor)
2977 ITextHost_TxGetClientRect(editor->texthost, &editor->rcFormat);
2978 editor->rcFormat.top += editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
2979 editor->rcFormat.left += 1 + editor->selofs;
2980 editor->rcFormat.right -= 1;
2983 static LONG ME_GetSelectionType(ME_TextEditor *editor)
2985 LONG sel_type = SEL_EMPTY;
2986 LONG start, end;
2988 ME_GetSelectionOfs(editor, &start, &end);
2989 if (start == end)
2990 sel_type = SEL_EMPTY;
2991 else
2993 LONG object_count = 0, character_count = 0;
2994 int i;
2996 for (i = 0; i < end - start; i++)
2998 ME_Cursor cursor;
3000 ME_CursorFromCharOfs(editor, start + i, &cursor);
3001 if (cursor.pRun->member.run.reobj)
3002 object_count++;
3003 else
3004 character_count++;
3005 if (character_count >= 2 && object_count >= 2)
3006 return (SEL_TEXT | SEL_MULTICHAR | SEL_OBJECT | SEL_MULTIOBJECT);
3008 if (character_count)
3010 sel_type |= SEL_TEXT;
3011 if (character_count >= 2)
3012 sel_type |= SEL_MULTICHAR;
3014 if (object_count)
3016 sel_type |= SEL_OBJECT;
3017 if (object_count >= 2)
3018 sel_type |= SEL_MULTIOBJECT;
3021 return sel_type;
3024 static BOOL ME_ShowContextMenu(ME_TextEditor *editor, int x, int y)
3026 CHARRANGE selrange;
3027 HMENU menu;
3028 int seltype;
3030 if(!editor->lpOleCallback || !editor->hWnd)
3031 return FALSE;
3032 ME_GetSelectionOfs(editor, &selrange.cpMin, &selrange.cpMax);
3033 seltype = ME_GetSelectionType(editor);
3034 if(SUCCEEDED(IRichEditOleCallback_GetContextMenu(editor->lpOleCallback, seltype, NULL, &selrange, &menu)))
3036 TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, 0, editor->hwndParent, NULL);
3037 DestroyMenu(menu);
3039 return TRUE;
3042 ME_TextEditor *ME_MakeEditor(ITextHost *texthost, BOOL bEmulateVersion10)
3044 ME_TextEditor *ed = heap_alloc(sizeof(*ed));
3045 int i;
3046 DWORD props;
3047 LONG selbarwidth;
3049 ed->hWnd = NULL;
3050 ed->hwndParent = NULL;
3051 ed->sizeWindow.cx = ed->sizeWindow.cy = 0;
3052 ed->texthost = texthost;
3053 ed->reOle = NULL;
3054 ed->bEmulateVersion10 = bEmulateVersion10;
3055 ed->styleFlags = 0;
3056 ed->exStyleFlags = 0;
3057 ed->first_marked_para = NULL;
3058 ed->total_rows = 0;
3059 ITextHost_TxGetPropertyBits(texthost,
3060 (TXTBIT_RICHTEXT|TXTBIT_MULTILINE|
3061 TXTBIT_READONLY|TXTBIT_USEPASSWORD|
3062 TXTBIT_HIDESELECTION|TXTBIT_SAVESELECTION|
3063 TXTBIT_AUTOWORDSEL|TXTBIT_VERTICAL|
3064 TXTBIT_WORDWRAP|TXTBIT_DISABLEDRAG),
3065 &props);
3066 ITextHost_TxGetScrollBars(texthost, &ed->styleFlags);
3067 ed->styleFlags &= (WS_VSCROLL|WS_HSCROLL|ES_AUTOVSCROLL|
3068 ES_AUTOHSCROLL|ES_DISABLENOSCROLL);
3069 ed->pBuffer = ME_MakeText();
3070 ed->nZoomNumerator = ed->nZoomDenominator = 0;
3071 ed->nAvailWidth = 0; /* wrap to client area */
3072 list_init( &ed->style_list );
3073 ME_MakeFirstParagraph(ed);
3074 /* The four cursors are for:
3075 * 0 - The position where the caret is shown
3076 * 1 - The anchored end of the selection (for normal selection)
3077 * 2 & 3 - The anchored start and end respectively for word, line,
3078 * or paragraph selection.
3080 ed->nCursors = 4;
3081 ed->pCursors = heap_alloc(ed->nCursors * sizeof(*ed->pCursors));
3082 ME_SetCursorToStart(ed, &ed->pCursors[0]);
3083 ed->pCursors[1] = ed->pCursors[0];
3084 ed->pCursors[2] = ed->pCursors[0];
3085 ed->pCursors[3] = ed->pCursors[1];
3086 ed->nLastTotalLength = ed->nTotalLength = 0;
3087 ed->nLastTotalWidth = ed->nTotalWidth = 0;
3088 ed->nUDArrowX = -1;
3089 ed->rgbBackColor = -1;
3090 ed->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3091 ed->bCaretAtEnd = FALSE;
3092 ed->nEventMask = 0;
3093 ed->nModifyStep = 0;
3094 ed->nTextLimit = TEXT_LIMIT_DEFAULT;
3095 list_init( &ed->undo_stack );
3096 list_init( &ed->redo_stack );
3097 ed->nUndoStackSize = 0;
3098 ed->nUndoLimit = STACK_SIZE_DEFAULT;
3099 ed->nUndoMode = umAddToUndo;
3100 ed->nParagraphs = 1;
3101 ed->nLastSelStart = ed->nLastSelEnd = 0;
3102 ed->pLastSelStartPara = ed->pLastSelEndPara = ed->pCursors[0].pPara;
3103 ed->bHideSelection = FALSE;
3104 ed->pfnWordBreak = NULL;
3105 ed->lpOleCallback = NULL;
3106 ed->mode = TM_MULTILEVELUNDO | TM_MULTICODEPAGE;
3107 ed->mode |= (props & TXTBIT_RICHTEXT) ? TM_RICHTEXT : TM_PLAINTEXT;
3108 ed->AutoURLDetect_bEnable = FALSE;
3109 ed->bHaveFocus = FALSE;
3110 ed->bDialogMode = FALSE;
3111 ed->bMouseCaptured = FALSE;
3112 for (i=0; i<HFONT_CACHE_SIZE; i++)
3114 ed->pFontCache[i].nRefs = 0;
3115 ed->pFontCache[i].nAge = 0;
3116 ed->pFontCache[i].hFont = NULL;
3119 ME_CheckCharOffsets(ed);
3120 SetRectEmpty(&ed->rcFormat);
3121 ed->bDefaultFormatRect = TRUE;
3122 ITextHost_TxGetSelectionBarWidth(ed->texthost, &selbarwidth);
3123 if (selbarwidth) {
3124 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3125 ed->selofs = SELECTIONBAR_WIDTH;
3126 ed->styleFlags |= ES_SELECTIONBAR;
3127 } else {
3128 ed->selofs = 0;
3130 ed->nSelectionType = stPosition;
3132 ed->cPasswordMask = 0;
3133 if (props & TXTBIT_USEPASSWORD)
3134 ITextHost_TxGetPasswordChar(texthost, &ed->cPasswordMask);
3136 if (props & TXTBIT_AUTOWORDSEL)
3137 ed->styleFlags |= ECO_AUTOWORDSELECTION;
3138 if (props & TXTBIT_MULTILINE) {
3139 ed->styleFlags |= ES_MULTILINE;
3140 ed->bWordWrap = (props & TXTBIT_WORDWRAP) != 0;
3141 } else {
3142 ed->bWordWrap = FALSE;
3144 if (props & TXTBIT_READONLY)
3145 ed->styleFlags |= ES_READONLY;
3146 if (!(props & TXTBIT_HIDESELECTION))
3147 ed->styleFlags |= ES_NOHIDESEL;
3148 if (props & TXTBIT_SAVESELECTION)
3149 ed->styleFlags |= ES_SAVESEL;
3150 if (props & TXTBIT_VERTICAL)
3151 ed->styleFlags |= ES_VERTICAL;
3152 if (props & TXTBIT_DISABLEDRAG)
3153 ed->styleFlags |= ES_NOOLEDRAGDROP;
3155 ed->notified_cr.cpMin = ed->notified_cr.cpMax = 0;
3157 /* Default scrollbar information */
3158 ed->vert_si.cbSize = sizeof(SCROLLINFO);
3159 ed->vert_si.nMin = 0;
3160 ed->vert_si.nMax = 0;
3161 ed->vert_si.nPage = 0;
3162 ed->vert_si.nPos = 0;
3164 ed->horz_si.cbSize = sizeof(SCROLLINFO);
3165 ed->horz_si.nMin = 0;
3166 ed->horz_si.nMax = 0;
3167 ed->horz_si.nPage = 0;
3168 ed->horz_si.nPos = 0;
3170 ed->wheel_remain = 0;
3172 list_init( &ed->reobj_list );
3173 OleInitialize(NULL);
3175 return ed;
3178 void ME_DestroyEditor(ME_TextEditor *editor)
3180 ME_DisplayItem *pFirst = editor->pBuffer->pFirst;
3181 ME_DisplayItem *p = pFirst, *pNext = NULL;
3182 ME_Style *s, *cursor2;
3183 int i;
3185 ME_ClearTempStyle(editor);
3186 ME_EmptyUndoStack(editor);
3187 while(p) {
3188 pNext = p->next;
3189 if (p->type == diParagraph)
3190 destroy_para(editor, p);
3191 else
3192 ME_DestroyDisplayItem(p);
3193 p = pNext;
3196 LIST_FOR_EACH_ENTRY_SAFE( s, cursor2, &editor->style_list, ME_Style, entry )
3197 ME_DestroyStyle( s );
3199 ME_ReleaseStyle(editor->pBuffer->pDefaultStyle);
3200 for (i=0; i<HFONT_CACHE_SIZE; i++)
3202 if (editor->pFontCache[i].hFont)
3203 DeleteObject(editor->pFontCache[i].hFont);
3205 if (editor->rgbBackColor != -1)
3206 DeleteObject(editor->hbrBackground);
3207 if(editor->lpOleCallback)
3208 IRichEditOleCallback_Release(editor->lpOleCallback);
3209 ITextHost_Release(editor->texthost);
3210 if (editor->reOle)
3212 IRichEditOle_Release(editor->reOle);
3213 editor->reOle = NULL;
3215 OleUninitialize();
3217 heap_free(editor->pBuffer);
3218 heap_free(editor->pCursors);
3219 heap_free(editor);
3222 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
3224 TRACE("\n");
3225 switch (fdwReason)
3227 case DLL_PROCESS_ATTACH:
3228 DisableThreadLibraryCalls(hinstDLL);
3229 me_heap = HeapCreate (0, 0x10000, 0);
3230 if (!ME_RegisterEditorClass(hinstDLL)) return FALSE;
3231 hLeft = LoadCursorW(hinstDLL, MAKEINTRESOURCEW(OCR_REVERSE));
3232 LookupInit();
3233 break;
3235 case DLL_PROCESS_DETACH:
3236 if (lpvReserved) break;
3237 UnregisterClassW(RICHEDIT_CLASS20W, 0);
3238 UnregisterClassW(MSFTEDIT_CLASS, 0);
3239 UnregisterClassA(RICHEDIT_CLASS20A, 0);
3240 UnregisterClassA("RichEdit50A", 0);
3241 if (ME_ListBoxRegistered)
3242 UnregisterClassW(REListBox20W, 0);
3243 if (ME_ComboBoxRegistered)
3244 UnregisterClassW(REComboBox20W, 0);
3245 LookupCleanup();
3246 HeapDestroy (me_heap);
3247 release_typelib();
3248 break;
3250 return TRUE;
3253 static inline int get_default_line_height( ME_TextEditor *editor )
3255 int height = 0;
3257 if (editor->pBuffer && editor->pBuffer->pDefaultStyle)
3258 height = editor->pBuffer->pDefaultStyle->tm.tmHeight;
3259 if (height <= 0) height = 24;
3261 return height;
3264 static inline int calc_wheel_change( int *remain, int amount_per_click )
3266 int change = amount_per_click * (float)*remain / WHEEL_DELTA;
3267 *remain -= WHEEL_DELTA * change / amount_per_click;
3268 return change;
3271 static const char * const edit_messages[] = {
3272 "EM_GETSEL",
3273 "EM_SETSEL",
3274 "EM_GETRECT",
3275 "EM_SETRECT",
3276 "EM_SETRECTNP",
3277 "EM_SCROLL",
3278 "EM_LINESCROLL",
3279 "EM_SCROLLCARET",
3280 "EM_GETMODIFY",
3281 "EM_SETMODIFY",
3282 "EM_GETLINECOUNT",
3283 "EM_LINEINDEX",
3284 "EM_SETHANDLE",
3285 "EM_GETHANDLE",
3286 "EM_GETTHUMB",
3287 "EM_UNKNOWN_BF",
3288 "EM_UNKNOWN_C0",
3289 "EM_LINELENGTH",
3290 "EM_REPLACESEL",
3291 "EM_UNKNOWN_C3",
3292 "EM_GETLINE",
3293 "EM_LIMITTEXT",
3294 "EM_CANUNDO",
3295 "EM_UNDO",
3296 "EM_FMTLINES",
3297 "EM_LINEFROMCHAR",
3298 "EM_UNKNOWN_CA",
3299 "EM_SETTABSTOPS",
3300 "EM_SETPASSWORDCHAR",
3301 "EM_EMPTYUNDOBUFFER",
3302 "EM_GETFIRSTVISIBLELINE",
3303 "EM_SETREADONLY",
3304 "EM_SETWORDBREAKPROC",
3305 "EM_GETWORDBREAKPROC",
3306 "EM_GETPASSWORDCHAR",
3307 "EM_SETMARGINS",
3308 "EM_GETMARGINS",
3309 "EM_GETLIMITTEXT",
3310 "EM_POSFROMCHAR",
3311 "EM_CHARFROMPOS",
3312 "EM_SETIMESTATUS",
3313 "EM_GETIMESTATUS"
3316 static const char * const richedit_messages[] = {
3317 "EM_CANPASTE",
3318 "EM_DISPLAYBAND",
3319 "EM_EXGETSEL",
3320 "EM_EXLIMITTEXT",
3321 "EM_EXLINEFROMCHAR",
3322 "EM_EXSETSEL",
3323 "EM_FINDTEXT",
3324 "EM_FORMATRANGE",
3325 "EM_GETCHARFORMAT",
3326 "EM_GETEVENTMASK",
3327 "EM_GETOLEINTERFACE",
3328 "EM_GETPARAFORMAT",
3329 "EM_GETSELTEXT",
3330 "EM_HIDESELECTION",
3331 "EM_PASTESPECIAL",
3332 "EM_REQUESTRESIZE",
3333 "EM_SELECTIONTYPE",
3334 "EM_SETBKGNDCOLOR",
3335 "EM_SETCHARFORMAT",
3336 "EM_SETEVENTMASK",
3337 "EM_SETOLECALLBACK",
3338 "EM_SETPARAFORMAT",
3339 "EM_SETTARGETDEVICE",
3340 "EM_STREAMIN",
3341 "EM_STREAMOUT",
3342 "EM_GETTEXTRANGE",
3343 "EM_FINDWORDBREAK",
3344 "EM_SETOPTIONS",
3345 "EM_GETOPTIONS",
3346 "EM_FINDTEXTEX",
3347 "EM_GETWORDBREAKPROCEX",
3348 "EM_SETWORDBREAKPROCEX",
3349 "EM_SETUNDOLIMIT",
3350 "EM_UNKNOWN_USER_83",
3351 "EM_REDO",
3352 "EM_CANREDO",
3353 "EM_GETUNDONAME",
3354 "EM_GETREDONAME",
3355 "EM_STOPGROUPTYPING",
3356 "EM_SETTEXTMODE",
3357 "EM_GETTEXTMODE",
3358 "EM_AUTOURLDETECT",
3359 "EM_GETAUTOURLDETECT",
3360 "EM_SETPALETTE",
3361 "EM_GETTEXTEX",
3362 "EM_GETTEXTLENGTHEX",
3363 "EM_SHOWSCROLLBAR",
3364 "EM_SETTEXTEX",
3365 "EM_UNKNOWN_USER_98",
3366 "EM_UNKNOWN_USER_99",
3367 "EM_SETPUNCTUATION",
3368 "EM_GETPUNCTUATION",
3369 "EM_SETWORDWRAPMODE",
3370 "EM_GETWORDWRAPMODE",
3371 "EM_SETIMECOLOR",
3372 "EM_GETIMECOLOR",
3373 "EM_SETIMEOPTIONS",
3374 "EM_GETIMEOPTIONS",
3375 "EM_CONVPOSITION",
3376 "EM_UNKNOWN_USER_109",
3377 "EM_UNKNOWN_USER_110",
3378 "EM_UNKNOWN_USER_111",
3379 "EM_UNKNOWN_USER_112",
3380 "EM_UNKNOWN_USER_113",
3381 "EM_UNKNOWN_USER_114",
3382 "EM_UNKNOWN_USER_115",
3383 "EM_UNKNOWN_USER_116",
3384 "EM_UNKNOWN_USER_117",
3385 "EM_UNKNOWN_USER_118",
3386 "EM_UNKNOWN_USER_119",
3387 "EM_SETLANGOPTIONS",
3388 "EM_GETLANGOPTIONS",
3389 "EM_GETIMECOMPMODE",
3390 "EM_FINDTEXTW",
3391 "EM_FINDTEXTEXW",
3392 "EM_RECONVERSION",
3393 "EM_SETIMEMODEBIAS",
3394 "EM_GETIMEMODEBIAS"
3397 static const char *
3398 get_msg_name(UINT msg)
3400 if (msg >= EM_GETSEL && msg <= EM_CHARFROMPOS)
3401 return edit_messages[msg - EM_GETSEL];
3402 if (msg >= EM_CANPASTE && msg <= EM_GETIMEMODEBIAS)
3403 return richedit_messages[msg - EM_CANPASTE];
3404 return "";
3407 static void ME_LinkNotify(ME_TextEditor *editor, UINT msg, WPARAM wParam, LPARAM lParam)
3409 int x,y;
3410 BOOL isExact;
3411 ME_Cursor cursor; /* The start of the clicked text. */
3413 ENLINK info;
3414 x = (short)LOWORD(lParam);
3415 y = (short)HIWORD(lParam);
3416 ME_CharFromPos(editor, x, y, &cursor, &isExact);
3417 if (!isExact) return;
3419 if (is_link( &cursor.pRun->member.run ))
3420 { /* The clicked run has CFE_LINK set */
3421 ME_DisplayItem *di;
3423 info.nmhdr.hwndFrom = NULL;
3424 info.nmhdr.idFrom = 0;
3425 info.nmhdr.code = EN_LINK;
3426 info.msg = msg;
3427 info.wParam = wParam;
3428 info.lParam = lParam;
3429 cursor.nOffset = 0;
3431 /* find the first contiguous run with CFE_LINK set */
3432 info.chrg.cpMin = ME_GetCursorOfs(&cursor);
3433 di = cursor.pRun;
3434 while (ME_PrevRun( NULL, &di, FALSE ) && is_link( &di->member.run ))
3435 info.chrg.cpMin -= di->member.run.len;
3437 /* find the last contiguous run with CFE_LINK set */
3438 info.chrg.cpMax = ME_GetCursorOfs(&cursor) + cursor.pRun->member.run.len;
3439 di = cursor.pRun;
3440 while (ME_NextRun( NULL, &di, FALSE ) && is_link( &di->member.run ))
3441 info.chrg.cpMax += di->member.run.len;
3443 ITextHost_TxNotify(editor->texthost, info.nmhdr.code, &info);
3447 void ME_ReplaceSel(ME_TextEditor *editor, BOOL can_undo, const WCHAR *str, int len)
3449 int from, to, nStartCursor;
3450 ME_Style *style;
3452 nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3453 style = ME_GetSelectionInsertStyle(editor);
3454 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3455 ME_InsertTextFromCursor(editor, 0, str, len, style);
3456 ME_ReleaseStyle(style);
3457 /* drop temporary style if line end */
3459 * FIXME question: does abc\n mean: put abc,
3460 * clear temp style, put \n? (would require a change)
3462 if (len>0 && str[len-1] == '\n')
3463 ME_ClearTempStyle(editor);
3464 ME_CommitUndo(editor);
3465 ME_UpdateSelectionLinkAttribute(editor);
3466 if (!can_undo)
3467 ME_EmptyUndoStack(editor);
3468 ME_UpdateRepaint(editor, FALSE);
3471 static void ME_SetText(ME_TextEditor *editor, void *text, BOOL unicode)
3473 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
3474 int textLen;
3476 LPWSTR wszText = ME_ToUnicode(codepage, text, &textLen);
3477 ME_InsertTextFromCursor(editor, 0, wszText, textLen, editor->pBuffer->pDefaultStyle);
3478 ME_EndToUnicode(codepage, wszText);
3481 static LRESULT ME_WmCreate(ME_TextEditor *editor, LPARAM lParam, BOOL unicode)
3483 CREATESTRUCTW *createW = (CREATESTRUCTW*)lParam;
3484 CREATESTRUCTA *createA = (CREATESTRUCTA*)lParam;
3485 void *text = NULL;
3486 INT max;
3488 if (lParam)
3489 text = unicode ? (void*)createW->lpszName : (void*)createA->lpszName;
3491 ME_SetDefaultFormatRect(editor);
3493 max = (editor->styleFlags & ES_DISABLENOSCROLL) ? 1 : 0;
3494 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_VSCROLL)
3495 ITextHost_TxSetScrollRange(editor->texthost, SB_VERT, 0, max, TRUE);
3497 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_HSCROLL)
3498 ITextHost_TxSetScrollRange(editor->texthost, SB_HORZ, 0, max, TRUE);
3500 if (editor->styleFlags & ES_DISABLENOSCROLL)
3502 if (editor->styleFlags & WS_VSCROLL)
3504 ITextHost_TxEnableScrollBar(editor->texthost, SB_VERT, ESB_DISABLE_BOTH);
3505 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT, TRUE);
3507 if (editor->styleFlags & WS_HSCROLL)
3509 ITextHost_TxEnableScrollBar(editor->texthost, SB_HORZ, ESB_DISABLE_BOTH);
3510 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ, TRUE);
3514 if (text)
3516 ME_SetText(editor, text, unicode);
3517 ME_SetCursorToStart(editor, &editor->pCursors[0]);
3518 ME_SetCursorToStart(editor, &editor->pCursors[1]);
3521 ME_CommitUndo(editor);
3522 ME_WrapMarkedParagraphs(editor);
3523 ME_MoveCaret(editor);
3524 return 0;
3528 #define UNSUPPORTED_MSG(e) \
3529 case e: \
3530 FIXME(#e ": stub\n"); \
3531 *phresult = S_FALSE; \
3532 return 0;
3534 /* Handle messages for windowless and windowed richedit controls.
3536 * The LRESULT that is returned is a return value for window procs,
3537 * and the phresult parameter is the COM return code needed by the
3538 * text services interface. */
3539 LRESULT ME_HandleMessage(ME_TextEditor *editor, UINT msg, WPARAM wParam,
3540 LPARAM lParam, BOOL unicode, HRESULT* phresult)
3542 *phresult = S_OK;
3544 switch(msg) {
3546 UNSUPPORTED_MSG(EM_DISPLAYBAND)
3547 UNSUPPORTED_MSG(EM_FINDWORDBREAK)
3548 UNSUPPORTED_MSG(EM_FMTLINES)
3549 UNSUPPORTED_MSG(EM_FORMATRANGE)
3550 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS)
3551 UNSUPPORTED_MSG(EM_GETEDITSTYLE)
3552 UNSUPPORTED_MSG(EM_GETIMECOMPMODE)
3553 UNSUPPORTED_MSG(EM_GETIMESTATUS)
3554 UNSUPPORTED_MSG(EM_SETIMESTATUS)
3555 UNSUPPORTED_MSG(EM_GETLANGOPTIONS)
3556 UNSUPPORTED_MSG(EM_GETREDONAME)
3557 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS)
3558 UNSUPPORTED_MSG(EM_GETUNDONAME)
3559 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX)
3560 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS)
3561 UNSUPPORTED_MSG(EM_SETEDITSTYLE)
3562 UNSUPPORTED_MSG(EM_SETLANGOPTIONS)
3563 UNSUPPORTED_MSG(EM_SETMARGINS)
3564 UNSUPPORTED_MSG(EM_SETPALETTE)
3565 UNSUPPORTED_MSG(EM_SETTABSTOPS)
3566 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS)
3567 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX)
3569 /* Messages specific to Richedit controls */
3571 case EM_STREAMIN:
3572 return ME_StreamIn(editor, wParam, (EDITSTREAM*)lParam, TRUE);
3573 case EM_STREAMOUT:
3574 return ME_StreamOut(editor, wParam, (EDITSTREAM *)lParam);
3575 case WM_GETDLGCODE:
3577 UINT code = DLGC_WANTCHARS|DLGC_WANTTAB|DLGC_WANTARROWS;
3579 if (lParam)
3580 editor->bDialogMode = TRUE;
3581 if (editor->styleFlags & ES_MULTILINE)
3582 code |= DLGC_WANTMESSAGE;
3583 if (!(editor->styleFlags & ES_SAVESEL))
3584 code |= DLGC_HASSETSEL;
3585 return code;
3587 case EM_EMPTYUNDOBUFFER:
3588 ME_EmptyUndoStack(editor);
3589 return 0;
3590 case EM_GETSEL:
3592 /* Note: wParam/lParam can be NULL */
3593 UINT from, to;
3594 PUINT pfrom = wParam ? (PUINT)wParam : &from;
3595 PUINT pto = lParam ? (PUINT)lParam : &to;
3596 ME_GetSelectionOfs(editor, (int *)pfrom, (int *)pto);
3597 if ((*pfrom|*pto) & 0xFFFF0000)
3598 return -1;
3599 return MAKELONG(*pfrom,*pto);
3601 case EM_EXGETSEL:
3603 CHARRANGE *pRange = (CHARRANGE *)lParam;
3604 ME_GetSelectionOfs(editor, &pRange->cpMin, &pRange->cpMax);
3605 TRACE("EM_EXGETSEL = (%d,%d)\n", pRange->cpMin, pRange->cpMax);
3606 return 0;
3608 case EM_SETUNDOLIMIT:
3610 if ((int)wParam < 0)
3611 editor->nUndoLimit = STACK_SIZE_DEFAULT;
3612 else
3613 editor->nUndoLimit = min(wParam, STACK_SIZE_MAX);
3614 /* Setting a max stack size keeps wine from getting killed
3615 for hogging memory. Windows allocates all this memory at once, so
3616 no program would realistically set a value above our maximum. */
3617 return editor->nUndoLimit;
3619 case EM_CANUNDO:
3620 return !list_empty( &editor->undo_stack );
3621 case EM_CANREDO:
3622 return !list_empty( &editor->redo_stack );
3623 case WM_UNDO: /* FIXME: actually not the same */
3624 case EM_UNDO:
3625 return ME_Undo(editor);
3626 case EM_REDO:
3627 return ME_Redo(editor);
3628 case EM_GETOPTIONS:
3630 /* these flags are equivalent to the ES_* counterparts */
3631 DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3632 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN | ECO_SELECTIONBAR;
3633 DWORD settings = editor->styleFlags & mask;
3635 return settings;
3637 case EM_SETFONTSIZE:
3639 CHARFORMAT2W cf;
3640 LONG tmp_size, size;
3641 BOOL is_increase = ((LONG)wParam > 0);
3643 if (editor->mode & TM_PLAINTEXT)
3644 return FALSE;
3646 cf.cbSize = sizeof(cf);
3647 cf.dwMask = CFM_SIZE;
3648 ME_GetSelectionCharFormat(editor, &cf);
3649 tmp_size = (cf.yHeight / 20) + wParam;
3651 if (tmp_size <= 1)
3652 size = 1;
3653 else if (tmp_size > 12 && tmp_size < 28 && tmp_size % 2)
3654 size = tmp_size + (is_increase ? 1 : -1);
3655 else if (tmp_size > 28 && tmp_size < 36)
3656 size = is_increase ? 36 : 28;
3657 else if (tmp_size > 36 && tmp_size < 48)
3658 size = is_increase ? 48 : 36;
3659 else if (tmp_size > 48 && tmp_size < 72)
3660 size = is_increase ? 72 : 48;
3661 else if (tmp_size > 72 && tmp_size < 80)
3662 size = is_increase ? 80 : 72;
3663 else if (tmp_size > 80 && tmp_size < 1638)
3664 size = 10 * (is_increase ? (tmp_size / 10 + 1) : (tmp_size / 10));
3665 else if (tmp_size >= 1638)
3666 size = 1638;
3667 else
3668 size = tmp_size;
3670 cf.yHeight = size * 20; /* convert twips to points */
3671 ME_SetSelectionCharFormat(editor, &cf);
3672 ME_CommitUndo(editor);
3673 ME_WrapMarkedParagraphs(editor);
3674 ME_UpdateScrollBar(editor);
3675 ME_Repaint(editor);
3677 return TRUE;
3679 case EM_SETOPTIONS:
3681 /* these flags are equivalent to ES_* counterparts, except for
3682 * ECO_AUTOWORDSELECTION that doesn't have an ES_* counterpart,
3683 * but is still stored in editor->styleFlags. */
3684 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3685 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
3686 ECO_SELECTIONBAR | ECO_AUTOWORDSELECTION;
3687 DWORD settings = mask & editor->styleFlags;
3688 DWORD oldSettings = settings;
3689 DWORD changedSettings;
3691 switch(wParam)
3693 case ECOOP_SET:
3694 settings = lParam;
3695 break;
3696 case ECOOP_OR:
3697 settings |= lParam;
3698 break;
3699 case ECOOP_AND:
3700 settings &= lParam;
3701 break;
3702 case ECOOP_XOR:
3703 settings ^= lParam;
3705 changedSettings = oldSettings ^ settings;
3707 if (changedSettings) {
3708 editor->styleFlags = (editor->styleFlags & ~mask) | (settings & mask);
3710 if (changedSettings & ECO_SELECTIONBAR)
3712 ITextHost_TxInvalidateRect(editor->texthost, &editor->rcFormat, TRUE);
3713 if (settings & ECO_SELECTIONBAR) {
3714 assert(!editor->selofs);
3715 editor->selofs = SELECTIONBAR_WIDTH;
3716 editor->rcFormat.left += editor->selofs;
3717 } else {
3718 editor->rcFormat.left -= editor->selofs;
3719 editor->selofs = 0;
3721 ME_RewrapRepaint(editor);
3724 if ((changedSettings & settings & ES_NOHIDESEL) && !editor->bHaveFocus)
3725 ME_InvalidateSelection( editor );
3727 if (changedSettings & settings & ECO_VERTICAL)
3728 FIXME("ECO_VERTICAL not implemented yet!\n");
3729 if (changedSettings & settings & ECO_AUTOHSCROLL)
3730 FIXME("ECO_AUTOHSCROLL not implemented yet!\n");
3731 if (changedSettings & settings & ECO_AUTOVSCROLL)
3732 FIXME("ECO_AUTOVSCROLL not implemented yet!\n");
3733 if (changedSettings & settings & ECO_WANTRETURN)
3734 FIXME("ECO_WANTRETURN not implemented yet!\n");
3735 if (changedSettings & settings & ECO_AUTOWORDSELECTION)
3736 FIXME("ECO_AUTOWORDSELECTION not implemented yet!\n");
3739 return settings;
3741 case EM_SETSEL:
3743 return handle_EM_EXSETSEL( editor, wParam, lParam );
3745 case EM_SETSCROLLPOS:
3747 POINT *point = (POINT *)lParam;
3748 ME_ScrollAbs(editor, point->x, point->y);
3749 return 0;
3751 case EM_AUTOURLDETECT:
3753 if (wParam==1 || wParam ==0)
3755 editor->AutoURLDetect_bEnable = (BOOL)wParam;
3756 return 0;
3758 return E_INVALIDARG;
3760 case EM_GETAUTOURLDETECT:
3762 return editor->AutoURLDetect_bEnable;
3764 case EM_EXSETSEL:
3766 CHARRANGE range = *(CHARRANGE *)lParam;
3768 return handle_EM_EXSETSEL( editor, range.cpMin, range.cpMax );
3770 case EM_SHOWSCROLLBAR:
3772 DWORD flags;
3774 switch (wParam)
3776 case SB_HORZ:
3777 flags = WS_HSCROLL;
3778 break;
3779 case SB_VERT:
3780 flags = WS_VSCROLL;
3781 break;
3782 case SB_BOTH:
3783 flags = WS_HSCROLL|WS_VSCROLL;
3784 break;
3785 default:
3786 return 0;
3789 if (lParam) {
3790 editor->styleFlags |= flags;
3791 if (flags & WS_HSCROLL)
3792 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ,
3793 editor->nTotalWidth > editor->sizeWindow.cx);
3794 if (flags & WS_VSCROLL)
3795 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT,
3796 editor->nTotalLength > editor->sizeWindow.cy);
3797 } else {
3798 editor->styleFlags &= ~flags;
3799 ITextHost_TxShowScrollBar(editor->texthost, wParam, FALSE);
3801 return 0;
3803 case EM_SETTEXTEX:
3805 LPWSTR wszText;
3806 SETTEXTEX *pStruct = (SETTEXTEX *)wParam;
3807 int from, to, len;
3808 ME_Style *style;
3809 BOOL bRtf, bUnicode, bSelection, bUTF8;
3810 int oldModify = editor->nModifyStep;
3811 static const char utf8_bom[] = {0xef, 0xbb, 0xbf};
3813 if (!pStruct) return 0;
3815 /* If we detect ascii rtf at the start of the string,
3816 * we know it isn't unicode. */
3817 bRtf = (lParam && (!strncmp((char *)lParam, "{\\rtf", 5) ||
3818 !strncmp((char *)lParam, "{\\urtf", 6)));
3819 bUnicode = !bRtf && pStruct->codepage == CP_UNICODE;
3820 bUTF8 = (lParam && (!strncmp((char *)lParam, utf8_bom, 3)));
3822 TRACE("EM_SETTEXTEX - %s, flags %d, cp %d\n",
3823 bUnicode ? debugstr_w((LPCWSTR)lParam) : debugstr_a((LPCSTR)lParam),
3824 pStruct->flags, pStruct->codepage);
3826 bSelection = (pStruct->flags & ST_SELECTION) != 0;
3827 if (bSelection) {
3828 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3829 style = ME_GetSelectionInsertStyle(editor);
3830 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to - from, FALSE);
3831 } else {
3832 ME_Cursor start;
3833 ME_SetCursorToStart(editor, &start);
3834 ME_InternalDeleteText(editor, &start, ME_GetTextLength(editor), FALSE);
3835 style = editor->pBuffer->pDefaultStyle;
3838 if (bRtf) {
3839 ME_StreamInRTFString(editor, bSelection, (char *)lParam);
3840 if (bSelection) {
3841 /* FIXME: The length returned doesn't include the rtf control
3842 * characters, only the actual text. */
3843 len = lParam ? strlen((char *)lParam) : 0;
3845 } else {
3846 if (bUTF8 && !bUnicode) {
3847 wszText = ME_ToUnicode(CP_UTF8, (void *)(lParam+3), &len);
3848 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3849 ME_EndToUnicode(CP_UTF8, wszText);
3850 } else {
3851 wszText = ME_ToUnicode(pStruct->codepage, (void *)lParam, &len);
3852 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3853 ME_EndToUnicode(pStruct->codepage, wszText);
3857 if (bSelection) {
3858 ME_ReleaseStyle(style);
3859 ME_UpdateSelectionLinkAttribute(editor);
3860 } else {
3861 ME_Cursor cursor;
3862 len = 1;
3863 ME_SetCursorToStart(editor, &cursor);
3864 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3866 ME_CommitUndo(editor);
3867 if (!(pStruct->flags & ST_KEEPUNDO))
3869 editor->nModifyStep = oldModify;
3870 ME_EmptyUndoStack(editor);
3872 ME_UpdateRepaint(editor, FALSE);
3873 return len;
3875 case EM_SELECTIONTYPE:
3876 return ME_GetSelectionType(editor);
3877 case EM_SETBKGNDCOLOR:
3879 LRESULT lColor;
3880 if (editor->rgbBackColor != -1) {
3881 DeleteObject(editor->hbrBackground);
3882 lColor = editor->rgbBackColor;
3884 else lColor = ITextHost_TxGetSysColor(editor->texthost, COLOR_WINDOW);
3886 if (wParam)
3888 editor->rgbBackColor = -1;
3889 editor->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3891 else
3893 editor->rgbBackColor = lParam;
3894 editor->hbrBackground = CreateSolidBrush(editor->rgbBackColor);
3896 ITextHost_TxInvalidateRect(editor->texthost, NULL, TRUE);
3897 return lColor;
3899 case EM_GETMODIFY:
3900 return editor->nModifyStep == 0 ? 0 : -1;
3901 case EM_SETMODIFY:
3903 if (wParam)
3904 editor->nModifyStep = 1;
3905 else
3906 editor->nModifyStep = 0;
3908 return 0;
3910 case EM_SETREADONLY:
3912 if (wParam)
3913 editor->styleFlags |= ES_READONLY;
3914 else
3915 editor->styleFlags &= ~ES_READONLY;
3916 return 1;
3918 case EM_SETEVENTMASK:
3920 DWORD nOldMask = editor->nEventMask;
3922 editor->nEventMask = lParam;
3923 return nOldMask;
3925 case EM_GETEVENTMASK:
3926 return editor->nEventMask;
3927 case EM_SETCHARFORMAT:
3929 CHARFORMAT2W p;
3930 BOOL bRepaint = TRUE;
3931 if (!cfany_to_cf2w(&p, (CHARFORMAT2W *)lParam))
3932 return 0;
3933 if (wParam & SCF_ALL) {
3934 if (editor->mode & TM_PLAINTEXT) {
3935 ME_SetDefaultCharFormat(editor, &p);
3936 } else {
3937 ME_Cursor start;
3938 ME_SetCursorToStart(editor, &start);
3939 ME_SetCharFormat(editor, &start, NULL, &p);
3940 editor->nModifyStep = 1;
3942 } else if (wParam & SCF_SELECTION) {
3943 if (editor->mode & TM_PLAINTEXT)
3944 return 0;
3945 if (wParam & SCF_WORD) {
3946 ME_Cursor start;
3947 ME_Cursor end = editor->pCursors[0];
3948 ME_MoveCursorWords(editor, &end, +1);
3949 start = end;
3950 ME_MoveCursorWords(editor, &start, -1);
3951 ME_SetCharFormat(editor, &start, &end, &p);
3953 bRepaint = ME_IsSelection(editor);
3954 ME_SetSelectionCharFormat(editor, &p);
3955 if (bRepaint) editor->nModifyStep = 1;
3956 } else { /* SCF_DEFAULT */
3957 ME_SetDefaultCharFormat(editor, &p);
3959 ME_CommitUndo(editor);
3960 if (bRepaint)
3962 ME_WrapMarkedParagraphs(editor);
3963 ME_UpdateScrollBar(editor);
3964 ME_Repaint(editor);
3966 return 1;
3968 case EM_GETCHARFORMAT:
3970 CHARFORMAT2W tmp, *dst = (CHARFORMAT2W *)lParam;
3971 if (dst->cbSize != sizeof(CHARFORMATA) &&
3972 dst->cbSize != sizeof(CHARFORMATW) &&
3973 dst->cbSize != sizeof(CHARFORMAT2A) &&
3974 dst->cbSize != sizeof(CHARFORMAT2W))
3975 return 0;
3976 tmp.cbSize = sizeof(tmp);
3977 if (!wParam)
3978 ME_GetDefaultCharFormat(editor, &tmp);
3979 else
3980 ME_GetSelectionCharFormat(editor, &tmp);
3981 cf2w_to_cfany(dst, &tmp);
3982 return tmp.dwMask;
3984 case EM_SETPARAFORMAT:
3986 BOOL result = ME_SetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
3987 ME_WrapMarkedParagraphs(editor);
3988 ME_UpdateScrollBar(editor);
3989 ME_Repaint(editor);
3990 ME_CommitUndo(editor);
3991 return result;
3993 case EM_GETPARAFORMAT:
3994 ME_GetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
3995 return ((PARAFORMAT2 *)lParam)->dwMask;
3996 case EM_GETFIRSTVISIBLELINE:
3998 ME_DisplayItem *p = editor->pBuffer->pFirst;
3999 int y = editor->vert_si.nPos;
4000 int ypara = 0;
4001 int count = 0;
4002 int ystart, yend;
4003 while(p) {
4004 p = ME_FindItemFwd(p, diStartRowOrParagraphOrEnd);
4005 if (p->type == diTextEnd)
4006 break;
4007 if (p->type == diParagraph) {
4008 ypara = p->member.para.pt.y;
4009 continue;
4011 ystart = ypara + p->member.row.pt.y;
4012 yend = ystart + p->member.row.nHeight;
4013 if (y < yend) {
4014 break;
4016 count++;
4018 return count;
4020 case EM_HIDESELECTION:
4022 editor->bHideSelection = (wParam != 0);
4023 ME_InvalidateSelection(editor);
4024 return 0;
4026 case EM_LINESCROLL:
4028 if (!(editor->styleFlags & ES_MULTILINE))
4029 return FALSE;
4030 ME_ScrollDown( editor, lParam * get_default_line_height( editor ) );
4031 return TRUE;
4033 case WM_CLEAR:
4035 int from, to;
4036 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
4037 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
4038 ME_CommitUndo(editor);
4039 ME_UpdateRepaint(editor, TRUE);
4040 return 0;
4042 case EM_REPLACESEL:
4044 int len = 0;
4045 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
4046 LPWSTR wszText = ME_ToUnicode(codepage, (void *)lParam, &len);
4048 TRACE("EM_REPLACESEL - %s\n", debugstr_w(wszText));
4050 ME_ReplaceSel(editor, !!wParam, wszText, len);
4051 ME_EndToUnicode(codepage, wszText);
4052 return len;
4054 case EM_SCROLLCARET:
4055 ME_EnsureVisible(editor, &editor->pCursors[0]);
4056 return 0;
4057 case WM_SETFONT:
4059 LOGFONTW lf;
4060 CHARFORMAT2W fmt;
4061 HDC hDC;
4062 BOOL bRepaint = LOWORD(lParam);
4064 if (!wParam)
4065 wParam = (WPARAM)GetStockObject(SYSTEM_FONT);
4067 if (!GetObjectW((HGDIOBJ)wParam, sizeof(LOGFONTW), &lf))
4068 return 0;
4070 hDC = ITextHost_TxGetDC(editor->texthost);
4071 ME_CharFormatFromLogFont(hDC, &lf, &fmt);
4072 ITextHost_TxReleaseDC(editor->texthost, hDC);
4073 if (editor->mode & TM_RICHTEXT) {
4074 ME_Cursor start;
4075 ME_SetCursorToStart(editor, &start);
4076 ME_SetCharFormat(editor, &start, NULL, &fmt);
4078 ME_SetDefaultCharFormat(editor, &fmt);
4080 ME_CommitUndo(editor);
4081 ME_MarkAllForWrapping(editor);
4082 ME_WrapMarkedParagraphs(editor);
4083 ME_UpdateScrollBar(editor);
4084 if (bRepaint)
4085 ME_Repaint(editor);
4086 return 0;
4088 case WM_SETTEXT:
4090 ME_Cursor cursor;
4091 ME_SetCursorToStart(editor, &cursor);
4092 ME_InternalDeleteText(editor, &cursor, ME_GetTextLength(editor), FALSE);
4093 if (lParam)
4095 TRACE("WM_SETTEXT lParam==%lx\n",lParam);
4096 if (!strncmp((char *)lParam, "{\\rtf", 5) ||
4097 !strncmp((char *)lParam, "{\\urtf", 6))
4099 /* Undocumented: WM_SETTEXT supports RTF text */
4100 ME_StreamInRTFString(editor, 0, (char *)lParam);
4102 else
4103 ME_SetText(editor, (void*)lParam, unicode);
4105 else
4106 TRACE("WM_SETTEXT - NULL\n");
4107 ME_SetCursorToStart(editor, &cursor);
4108 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
4109 ME_SetSelection(editor, 0, 0);
4110 editor->nModifyStep = 0;
4111 ME_CommitUndo(editor);
4112 ME_EmptyUndoStack(editor);
4113 ME_UpdateRepaint(editor, FALSE);
4114 return 1;
4116 case EM_CANPASTE:
4117 return paste_special( editor, 0, NULL, TRUE );
4118 case WM_PASTE:
4119 case WM_MBUTTONDOWN:
4120 wParam = 0;
4121 lParam = 0;
4122 /* fall through */
4123 case EM_PASTESPECIAL:
4124 paste_special( editor, wParam, (REPASTESPECIAL *)lParam, FALSE );
4125 return 0;
4126 case WM_CUT:
4127 case WM_COPY:
4128 copy_or_cut(editor, msg == WM_CUT);
4129 return 0;
4130 case WM_GETTEXTLENGTH:
4132 GETTEXTLENGTHEX how;
4134 /* CR/LF conversion required in 2.0 mode, verbatim in 1.0 mode */
4135 how.flags = GTL_CLOSE | (editor->bEmulateVersion10 ? 0 : GTL_USECRLF) | GTL_NUMCHARS;
4136 how.codepage = unicode ? CP_UNICODE : CP_ACP;
4137 return ME_GetTextLengthEx(editor, &how);
4139 case EM_GETTEXTLENGTHEX:
4140 return ME_GetTextLengthEx(editor, (GETTEXTLENGTHEX *)wParam);
4141 case WM_GETTEXT:
4143 GETTEXTEX ex;
4144 ex.cb = wParam * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
4145 ex.flags = GT_USECRLF;
4146 ex.codepage = unicode ? CP_UNICODE : CP_ACP;
4147 ex.lpDefaultChar = NULL;
4148 ex.lpUsedDefChar = NULL;
4149 return ME_GetTextEx(editor, &ex, lParam);
4151 case EM_GETTEXTEX:
4152 return ME_GetTextEx(editor, (GETTEXTEX*)wParam, lParam);
4153 case EM_GETSELTEXT:
4155 int nFrom, nTo, nStartCur = ME_GetSelectionOfs(editor, &nFrom, &nTo);
4156 ME_Cursor *from = &editor->pCursors[nStartCur];
4157 return ME_GetTextRange(editor, (WCHAR *)lParam, from,
4158 nTo - nFrom, unicode);
4160 case EM_GETSCROLLPOS:
4162 POINT *point = (POINT *)lParam;
4163 point->x = editor->horz_si.nPos;
4164 point->y = editor->vert_si.nPos;
4165 /* 16-bit scaled value is returned as stored in scrollinfo */
4166 if (editor->horz_si.nMax > 0xffff)
4167 point->x = MulDiv(point->x, 0xffff, editor->horz_si.nMax);
4168 if (editor->vert_si.nMax > 0xffff)
4169 point->y = MulDiv(point->y, 0xffff, editor->vert_si.nMax);
4170 return 1;
4172 case EM_GETTEXTRANGE:
4174 TEXTRANGEW *rng = (TEXTRANGEW *)lParam;
4175 ME_Cursor start;
4176 int nStart = rng->chrg.cpMin;
4177 int nEnd = rng->chrg.cpMax;
4178 int textlength = ME_GetTextLength(editor);
4180 TRACE("EM_GETTEXTRANGE min=%d max=%d unicode=%d textlength=%d\n",
4181 rng->chrg.cpMin, rng->chrg.cpMax, unicode, textlength);
4182 if (nStart < 0) return 0;
4183 if ((nStart == 0 && nEnd == -1) || nEnd > textlength)
4184 nEnd = textlength;
4185 if (nStart >= nEnd) return 0;
4187 ME_CursorFromCharOfs(editor, nStart, &start);
4188 return ME_GetTextRange(editor, rng->lpstrText, &start, nEnd - nStart, unicode);
4190 case EM_GETLINE:
4192 ME_DisplayItem *run;
4193 const unsigned int nMaxChars = *(WORD *) lParam;
4194 unsigned int nCharsLeft = nMaxChars;
4195 char *dest = (char *) lParam;
4196 BOOL wroteNull = FALSE;
4198 TRACE("EM_GETLINE: row=%d, nMaxChars=%d (%s)\n", (int) wParam, nMaxChars,
4199 unicode ? "Unicode" : "Ansi");
4201 run = ME_FindRowWithNumber(editor, wParam);
4202 if (run == NULL)
4203 return 0;
4205 while (nCharsLeft && (run = ME_FindItemFwd(run, diRunOrStartRow))
4206 && run->type == diRun)
4208 WCHAR *str = get_text( &run->member.run, 0 );
4209 unsigned int nCopy;
4211 nCopy = min(nCharsLeft, run->member.run.len);
4213 if (unicode)
4214 memcpy(dest, str, nCopy * sizeof(WCHAR));
4215 else
4216 nCopy = WideCharToMultiByte(CP_ACP, 0, str, nCopy, dest,
4217 nCharsLeft, NULL, NULL);
4218 dest += nCopy * (unicode ? sizeof(WCHAR) : 1);
4219 nCharsLeft -= nCopy;
4222 /* append line termination, space allowing */
4223 if (nCharsLeft > 0)
4225 if (unicode)
4226 *((WCHAR *)dest) = '\0';
4227 else
4228 *dest = '\0';
4229 nCharsLeft--;
4230 wroteNull = TRUE;
4233 TRACE("EM_GETLINE: got %u characters\n", nMaxChars - nCharsLeft);
4234 return nMaxChars - nCharsLeft - (wroteNull ? 1 : 0);
4236 case EM_GETLINECOUNT:
4238 ME_DisplayItem *item = editor->pBuffer->pLast;
4239 int nRows = editor->total_rows;
4240 ME_DisplayItem *prev_para = NULL, *last_para = NULL;
4242 last_para = ME_FindItemBack(item, diRun);
4243 prev_para = ME_FindItemBack(last_para, diRun);
4244 assert(last_para);
4245 assert(last_para->member.run.nFlags & MERF_ENDPARA);
4246 if (editor->bEmulateVersion10 && prev_para &&
4247 last_para->member.run.nCharOfs == 0 &&
4248 prev_para->member.run.len == 1 &&
4249 *get_text( &prev_para->member.run, 0 ) == '\r')
4251 /* In 1.0 emulation, the last solitary \r at the very end of the text
4252 (if one exists) is NOT a line break.
4253 FIXME: this is an ugly hack. This should have a more regular model. */
4254 nRows--;
4257 TRACE("EM_GETLINECOUNT: nRows==%d\n", nRows);
4258 return max(1, nRows);
4260 case EM_LINEFROMCHAR:
4262 if (wParam == -1)
4263 return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(&editor->pCursors[1]));
4264 else
4265 return ME_RowNumberFromCharOfs(editor, wParam);
4267 case EM_EXLINEFROMCHAR:
4269 if (lParam == -1)
4270 return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(&editor->pCursors[1]));
4271 else
4272 return ME_RowNumberFromCharOfs(editor, lParam);
4274 case EM_LINEINDEX:
4276 ME_DisplayItem *item, *para;
4277 int nCharOfs;
4279 if (wParam == -1)
4280 item = ME_FindItemBack(editor->pCursors[0].pRun, diStartRow);
4281 else
4282 item = ME_FindRowWithNumber(editor, wParam);
4283 if (!item)
4284 return -1;
4285 para = ME_GetParagraph(item);
4286 item = ME_FindItemFwd(item, diRun);
4287 nCharOfs = para->member.para.nCharOfs + item->member.run.nCharOfs;
4288 TRACE("EM_LINEINDEX: nCharOfs==%d\n", nCharOfs);
4289 return nCharOfs;
4291 case EM_LINELENGTH:
4293 ME_DisplayItem *item, *item_end;
4294 int nChars = 0, nThisLineOfs = 0, nNextLineOfs = 0;
4295 ME_DisplayItem *para, *run;
4297 if (wParam > ME_GetTextLength(editor))
4298 return 0;
4299 if (wParam == -1)
4301 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
4302 return 0;
4304 ME_RunOfsFromCharOfs(editor, wParam, &para, &run, NULL);
4305 item = ME_RowStart(run);
4306 nThisLineOfs = ME_CharOfsFromRunOfs(editor, para, ME_FindItemFwd(item, diRun), 0);
4307 item_end = ME_FindItemFwd(item, diStartRowOrParagraphOrEnd);
4308 if (item_end->type == diStartRow) {
4309 nNextLineOfs = ME_CharOfsFromRunOfs(editor, para, ME_FindItemFwd(item_end, diRun), 0);
4310 } else {
4311 ME_DisplayItem *endRun = ME_FindItemBack(item_end, diRun);
4312 assert(endRun && endRun->member.run.nFlags & MERF_ENDPARA);
4313 nNextLineOfs = item_end->member.para.nCharOfs - endRun->member.run.len;
4315 nChars = nNextLineOfs - nThisLineOfs;
4316 TRACE("EM_LINELENGTH(%ld)==%d\n",wParam, nChars);
4317 return nChars;
4319 case EM_EXLIMITTEXT:
4321 if ((int)lParam < 0)
4322 return 0;
4323 if (lParam == 0)
4324 editor->nTextLimit = 65536;
4325 else
4326 editor->nTextLimit = (int) lParam;
4327 return 0;
4329 case EM_LIMITTEXT:
4331 if (wParam == 0)
4332 editor->nTextLimit = 65536;
4333 else
4334 editor->nTextLimit = (int) wParam;
4335 return 0;
4337 case EM_GETLIMITTEXT:
4339 return editor->nTextLimit;
4341 case EM_FINDTEXT:
4343 LRESULT r;
4344 if(!unicode){
4345 FINDTEXTA *ft = (FINDTEXTA *)lParam;
4346 int nChars = MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, NULL, 0);
4347 WCHAR *tmp;
4349 if ((tmp = heap_alloc(nChars * sizeof(*tmp))) != NULL)
4350 MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, tmp, nChars);
4351 r = ME_FindText(editor, wParam, &ft->chrg, tmp, NULL);
4352 heap_free(tmp);
4353 }else{
4354 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4355 r = ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4357 return r;
4359 case EM_FINDTEXTEX:
4361 LRESULT r;
4362 if(!unicode){
4363 FINDTEXTEXA *ex = (FINDTEXTEXA *)lParam;
4364 int nChars = MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, NULL, 0);
4365 WCHAR *tmp;
4367 if ((tmp = heap_alloc(nChars * sizeof(*tmp))) != NULL)
4368 MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, tmp, nChars);
4369 r = ME_FindText(editor, wParam, &ex->chrg, tmp, &ex->chrgText);
4370 heap_free(tmp);
4371 }else{
4372 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4373 r = ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4375 return r;
4377 case EM_FINDTEXTW:
4379 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4380 return ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4382 case EM_FINDTEXTEXW:
4384 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4385 return ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4387 case EM_GETZOOM:
4388 if (!wParam || !lParam)
4389 return FALSE;
4390 *(int *)wParam = editor->nZoomNumerator;
4391 *(int *)lParam = editor->nZoomDenominator;
4392 return TRUE;
4393 case EM_SETZOOM:
4394 return ME_SetZoom(editor, wParam, lParam);
4395 case EM_CHARFROMPOS:
4397 ME_Cursor cursor;
4398 if (ME_CharFromPos(editor, ((POINTL *)lParam)->x, ((POINTL *)lParam)->y,
4399 &cursor, NULL))
4400 return ME_GetCursorOfs(&cursor);
4401 else
4402 return -1;
4404 case EM_POSFROMCHAR:
4406 ME_DisplayItem *pPara, *pRun;
4407 int nCharOfs, nOffset, nLength;
4408 POINTL pt = {0,0};
4410 nCharOfs = wParam;
4411 /* detect which API version we're dealing with */
4412 if (wParam >= 0x40000)
4413 nCharOfs = lParam;
4414 nLength = ME_GetTextLength(editor);
4415 nCharOfs = min(nCharOfs, nLength);
4416 nCharOfs = max(nCharOfs, 0);
4418 ME_RunOfsFromCharOfs(editor, nCharOfs, &pPara, &pRun, &nOffset);
4419 assert(pRun->type == diRun);
4420 pt.y = pRun->member.run.pt.y;
4421 pt.x = pRun->member.run.pt.x + ME_PointFromChar(editor, &pRun->member.run, nOffset, TRUE);
4422 pt.y += pPara->member.para.pt.y + editor->rcFormat.top;
4423 pt.x += editor->rcFormat.left;
4425 pt.x -= editor->horz_si.nPos;
4426 pt.y -= editor->vert_si.nPos;
4428 if (wParam >= 0x40000) {
4429 *(POINTL *)wParam = pt;
4431 return (wParam >= 0x40000) ? 0 : MAKELONG( pt.x, pt.y );
4433 case WM_CREATE:
4434 return ME_WmCreate(editor, lParam, unicode);
4435 case WM_DESTROY:
4436 ME_DestroyEditor(editor);
4437 return 0;
4438 case WM_SETCURSOR:
4440 POINT cursor_pos;
4441 if (wParam == (WPARAM)editor->hWnd && GetCursorPos(&cursor_pos) &&
4442 ScreenToClient(editor->hWnd, &cursor_pos))
4443 ME_LinkNotify(editor, msg, 0, MAKELPARAM(cursor_pos.x, cursor_pos.y));
4444 return ME_SetCursor(editor);
4446 case WM_LBUTTONDBLCLK:
4447 case WM_LBUTTONDOWN:
4449 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4450 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4451 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4452 return 0;
4453 ITextHost_TxSetFocus(editor->texthost);
4454 ME_LButtonDown(editor, (short)LOWORD(lParam), (short)HIWORD(lParam),
4455 ME_CalculateClickCount(editor, msg, wParam, lParam));
4456 ITextHost_TxSetCapture(editor->texthost, TRUE);
4457 editor->bMouseCaptured = TRUE;
4458 ME_LinkNotify(editor, msg, wParam, lParam);
4459 if (!ME_SetCursor(editor)) goto do_default;
4460 break;
4462 case WM_MOUSEMOVE:
4463 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4464 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4465 return 0;
4466 if (editor->bMouseCaptured)
4467 ME_MouseMove(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
4468 else
4469 ME_LinkNotify(editor, msg, wParam, lParam);
4470 /* Set cursor if mouse is captured, since WM_SETCURSOR won't be received. */
4471 if (editor->bMouseCaptured)
4472 ME_SetCursor(editor);
4473 break;
4474 case WM_LBUTTONUP:
4475 if (editor->bMouseCaptured) {
4476 ITextHost_TxSetCapture(editor->texthost, FALSE);
4477 editor->bMouseCaptured = FALSE;
4479 if (editor->nSelectionType == stDocument)
4480 editor->nSelectionType = stPosition;
4481 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4482 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4483 return 0;
4484 else
4486 ME_SetCursor(editor);
4487 ME_LinkNotify(editor, msg, wParam, lParam);
4489 break;
4490 case WM_RBUTTONUP:
4491 case WM_RBUTTONDOWN:
4492 case WM_RBUTTONDBLCLK:
4493 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4494 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4495 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4496 return 0;
4497 ME_LinkNotify(editor, msg, wParam, lParam);
4498 goto do_default;
4499 case WM_CONTEXTMENU:
4500 if (!ME_ShowContextMenu(editor, (short)LOWORD(lParam), (short)HIWORD(lParam)))
4501 goto do_default;
4502 break;
4503 case WM_SETFOCUS:
4504 editor->bHaveFocus = TRUE;
4505 ME_ShowCaret(editor);
4506 ME_SendOldNotify(editor, EN_SETFOCUS);
4507 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4508 ME_InvalidateSelection( editor );
4509 return 0;
4510 case WM_KILLFOCUS:
4511 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4512 editor->bHaveFocus = FALSE;
4513 editor->wheel_remain = 0;
4514 ME_HideCaret(editor);
4515 ME_SendOldNotify(editor, EN_KILLFOCUS);
4516 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4517 ME_InvalidateSelection( editor );
4518 return 0;
4519 case WM_COMMAND:
4520 TRACE("editor wnd command = %d\n", LOWORD(wParam));
4521 return 0;
4522 case WM_KEYUP:
4523 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4524 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4525 return 0;
4526 goto do_default;
4527 case WM_KEYDOWN:
4528 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4529 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4530 return 0;
4531 if (ME_KeyDown(editor, LOWORD(wParam)))
4532 return 0;
4533 goto do_default;
4534 case WM_CHAR:
4535 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4536 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4537 return 0;
4538 return ME_Char(editor, wParam, lParam, unicode);
4539 case WM_UNICHAR:
4540 if (unicode)
4542 if(wParam == UNICODE_NOCHAR) return TRUE;
4543 if(wParam <= 0x000fffff)
4545 if(wParam > 0xffff) /* convert to surrogates */
4547 wParam -= 0x10000;
4548 ME_Char(editor, (wParam >> 10) + 0xd800, 0, TRUE);
4549 ME_Char(editor, (wParam & 0x03ff) + 0xdc00, 0, TRUE);
4550 } else {
4551 ME_Char(editor, wParam, 0, TRUE);
4554 return 0;
4556 break;
4557 case EM_STOPGROUPTYPING:
4558 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4559 return 0;
4560 case WM_HSCROLL:
4562 const int scrollUnit = 7;
4564 switch(LOWORD(wParam))
4566 case SB_LEFT:
4567 ME_ScrollAbs(editor, 0, 0);
4568 break;
4569 case SB_RIGHT:
4570 ME_ScrollAbs(editor,
4571 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4572 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4573 break;
4574 case SB_LINELEFT:
4575 ME_ScrollLeft(editor, scrollUnit);
4576 break;
4577 case SB_LINERIGHT:
4578 ME_ScrollRight(editor, scrollUnit);
4579 break;
4580 case SB_PAGELEFT:
4581 ME_ScrollLeft(editor, editor->sizeWindow.cx);
4582 break;
4583 case SB_PAGERIGHT:
4584 ME_ScrollRight(editor, editor->sizeWindow.cx);
4585 break;
4586 case SB_THUMBTRACK:
4587 case SB_THUMBPOSITION:
4589 int pos = HIWORD(wParam);
4590 if (editor->horz_si.nMax > 0xffff)
4591 pos = MulDiv(pos, editor->horz_si.nMax, 0xffff);
4592 ME_HScrollAbs(editor, pos);
4593 break;
4596 break;
4598 case EM_SCROLL: /* fall through */
4599 case WM_VSCROLL:
4601 int origNPos;
4602 int lineHeight = get_default_line_height( editor );
4604 origNPos = editor->vert_si.nPos;
4606 switch(LOWORD(wParam))
4608 case SB_TOP:
4609 ME_ScrollAbs(editor, 0, 0);
4610 break;
4611 case SB_BOTTOM:
4612 ME_ScrollAbs(editor,
4613 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4614 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4615 break;
4616 case SB_LINEUP:
4617 ME_ScrollUp(editor,lineHeight);
4618 break;
4619 case SB_LINEDOWN:
4620 ME_ScrollDown(editor,lineHeight);
4621 break;
4622 case SB_PAGEUP:
4623 ME_ScrollUp(editor,editor->sizeWindow.cy);
4624 break;
4625 case SB_PAGEDOWN:
4626 ME_ScrollDown(editor,editor->sizeWindow.cy);
4627 break;
4628 case SB_THUMBTRACK:
4629 case SB_THUMBPOSITION:
4631 int pos = HIWORD(wParam);
4632 if (editor->vert_si.nMax > 0xffff)
4633 pos = MulDiv(pos, editor->vert_si.nMax, 0xffff);
4634 ME_VScrollAbs(editor, pos);
4635 break;
4638 if (msg == EM_SCROLL)
4639 return 0x00010000 | (((editor->vert_si.nPos - origNPos)/lineHeight) & 0xffff);
4640 break;
4642 case WM_MOUSEWHEEL:
4644 int delta;
4645 BOOL ctrl_is_down;
4647 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4648 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4649 return 0;
4651 ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
4653 delta = GET_WHEEL_DELTA_WPARAM(wParam);
4655 /* if scrolling changes direction, ignore left overs */
4656 if ((delta < 0 && editor->wheel_remain < 0) ||
4657 (delta > 0 && editor->wheel_remain > 0))
4658 editor->wheel_remain += delta;
4659 else
4660 editor->wheel_remain = delta;
4662 if (editor->wheel_remain)
4664 if (ctrl_is_down) {
4665 int numerator;
4666 if (!editor->nZoomNumerator || !editor->nZoomDenominator)
4668 numerator = 100;
4669 } else {
4670 numerator = editor->nZoomNumerator * 100 / editor->nZoomDenominator;
4672 numerator += calc_wheel_change( &editor->wheel_remain, 10 );
4673 if (numerator >= 10 && numerator <= 500)
4674 ME_SetZoom(editor, numerator, 100);
4675 } else {
4676 UINT max_lines = 3;
4677 int lines = 0;
4679 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES, 0, &max_lines, 0 );
4680 if (max_lines)
4681 lines = calc_wheel_change( &editor->wheel_remain, (int)max_lines );
4682 if (lines)
4683 ME_ScrollDown( editor, -lines * get_default_line_height( editor ) );
4686 break;
4688 case EM_GETRECT:
4690 *((RECT *)lParam) = editor->rcFormat;
4691 if (editor->bDefaultFormatRect)
4692 ((RECT *)lParam)->left -= editor->selofs;
4693 return 0;
4695 case EM_SETRECT:
4696 case EM_SETRECTNP:
4698 if (lParam)
4700 int border = 0;
4701 RECT clientRect;
4702 RECT *rc = (RECT *)lParam;
4704 border = editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
4705 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4706 if (wParam == 0)
4708 editor->rcFormat.top = max(0, rc->top - border);
4709 editor->rcFormat.left = max(0, rc->left - border);
4710 editor->rcFormat.bottom = min(clientRect.bottom, rc->bottom);
4711 editor->rcFormat.right = min(clientRect.right, rc->right + border);
4712 } else if (wParam == 1) {
4713 /* MSDN incorrectly says a wParam value of 1 causes the
4714 * lParam rect to be used as a relative offset,
4715 * however, the tests show it just prevents min/max bound
4716 * checking. */
4717 editor->rcFormat.top = rc->top - border;
4718 editor->rcFormat.left = rc->left - border;
4719 editor->rcFormat.bottom = rc->bottom;
4720 editor->rcFormat.right = rc->right + border;
4721 } else {
4722 return 0;
4724 editor->bDefaultFormatRect = FALSE;
4726 else
4728 ME_SetDefaultFormatRect(editor);
4729 editor->bDefaultFormatRect = TRUE;
4731 ME_MarkAllForWrapping(editor);
4732 ME_WrapMarkedParagraphs(editor);
4733 ME_UpdateScrollBar(editor);
4734 if (msg != EM_SETRECTNP)
4735 ME_Repaint(editor);
4736 return 0;
4738 case EM_REQUESTRESIZE:
4739 ME_SendRequestResize(editor, TRUE);
4740 return 0;
4741 case WM_SETREDRAW:
4742 goto do_default;
4743 case WM_WINDOWPOSCHANGED:
4745 RECT clientRect;
4746 WINDOWPOS *winpos = (WINDOWPOS *)lParam;
4748 if (winpos->flags & SWP_NOCLIENTSIZE) goto do_default;
4749 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4750 if (editor->bDefaultFormatRect) {
4751 ME_SetDefaultFormatRect(editor);
4752 } else {
4753 editor->rcFormat.right += clientRect.right - editor->prevClientRect.right;
4754 editor->rcFormat.bottom += clientRect.bottom - editor->prevClientRect.bottom;
4756 editor->prevClientRect = clientRect;
4757 ME_RewrapRepaint(editor);
4758 goto do_default;
4760 /* IME messages to make richedit controls IME aware */
4761 case WM_IME_SETCONTEXT:
4762 case WM_IME_CONTROL:
4763 case WM_IME_SELECT:
4764 case WM_IME_COMPOSITIONFULL:
4765 return 0;
4766 case WM_IME_STARTCOMPOSITION:
4768 editor->imeStartIndex=ME_GetCursorOfs(&editor->pCursors[0]);
4769 ME_DeleteSelection(editor);
4770 ME_CommitUndo(editor);
4771 ME_UpdateRepaint(editor, FALSE);
4772 return 0;
4774 case WM_IME_COMPOSITION:
4776 HIMC hIMC;
4778 ME_Style *style = ME_GetInsertStyle(editor, 0);
4779 hIMC = ITextHost_TxImmGetContext(editor->texthost);
4780 ME_DeleteSelection(editor);
4781 ME_SaveTempStyle(editor, style);
4782 if (lParam & (GCS_RESULTSTR|GCS_COMPSTR))
4784 LPWSTR lpCompStr = NULL;
4785 DWORD dwBufLen;
4786 DWORD dwIndex = lParam & GCS_RESULTSTR;
4787 if (!dwIndex)
4788 dwIndex = GCS_COMPSTR;
4790 dwBufLen = ImmGetCompositionStringW(hIMC, dwIndex, NULL, 0);
4791 lpCompStr = HeapAlloc(GetProcessHeap(),0,dwBufLen + sizeof(WCHAR));
4792 ImmGetCompositionStringW(hIMC, dwIndex, lpCompStr, dwBufLen);
4793 lpCompStr[dwBufLen/sizeof(WCHAR)] = 0;
4794 ME_InsertTextFromCursor(editor,0,lpCompStr,dwBufLen/sizeof(WCHAR),style);
4795 HeapFree(GetProcessHeap(), 0, lpCompStr);
4797 if (dwIndex == GCS_COMPSTR)
4798 ME_SetSelection(editor,editor->imeStartIndex,
4799 editor->imeStartIndex + dwBufLen/sizeof(WCHAR));
4801 ME_ReleaseStyle(style);
4802 ME_CommitUndo(editor);
4803 ME_UpdateRepaint(editor, FALSE);
4804 return 0;
4806 case WM_IME_ENDCOMPOSITION:
4808 ME_DeleteSelection(editor);
4809 editor->imeStartIndex=-1;
4810 return 0;
4812 case EM_GETOLEINTERFACE:
4814 if (!editor->reOle)
4815 if (!CreateIRichEditOle(NULL, editor, (LPVOID *)&editor->reOle))
4816 return 0;
4817 *(LPVOID *)lParam = editor->reOle;
4818 IRichEditOle_AddRef(editor->reOle);
4819 return 1;
4821 case EM_GETPASSWORDCHAR:
4823 return editor->cPasswordMask;
4825 case EM_SETOLECALLBACK:
4826 if(editor->lpOleCallback)
4827 IRichEditOleCallback_Release(editor->lpOleCallback);
4828 editor->lpOleCallback = (IRichEditOleCallback*)lParam;
4829 if(editor->lpOleCallback)
4830 IRichEditOleCallback_AddRef(editor->lpOleCallback);
4831 return TRUE;
4832 case EM_GETWORDBREAKPROC:
4833 return (LRESULT)editor->pfnWordBreak;
4834 case EM_SETWORDBREAKPROC:
4836 EDITWORDBREAKPROCW pfnOld = editor->pfnWordBreak;
4838 editor->pfnWordBreak = (EDITWORDBREAKPROCW)lParam;
4839 return (LRESULT)pfnOld;
4841 case EM_GETTEXTMODE:
4842 return editor->mode;
4843 case EM_SETTEXTMODE:
4845 int mask = 0;
4846 int changes = 0;
4848 if (ME_GetTextLength(editor) ||
4849 !list_empty( &editor->undo_stack ) || !list_empty( &editor->redo_stack ))
4850 return E_UNEXPECTED;
4852 /* Check for mutually exclusive flags in adjacent bits of wParam */
4853 if ((wParam & (TM_RICHTEXT | TM_MULTILEVELUNDO | TM_MULTICODEPAGE)) &
4854 (wParam & (TM_PLAINTEXT | TM_SINGLELEVELUNDO | TM_SINGLECODEPAGE)) << 1)
4855 return E_INVALIDARG;
4857 if (wParam & (TM_RICHTEXT | TM_PLAINTEXT))
4859 mask |= TM_RICHTEXT | TM_PLAINTEXT;
4860 changes |= wParam & (TM_RICHTEXT | TM_PLAINTEXT);
4861 if (wParam & TM_PLAINTEXT) {
4862 /* Clear selection since it should be possible to select the
4863 * end of text run for rich text */
4864 ME_InvalidateSelection(editor);
4865 ME_SetCursorToStart(editor, &editor->pCursors[0]);
4866 editor->pCursors[1] = editor->pCursors[0];
4867 /* plain text can only have the default style. */
4868 ME_ClearTempStyle(editor);
4869 ME_AddRefStyle(editor->pBuffer->pDefaultStyle);
4870 ME_ReleaseStyle(editor->pCursors[0].pRun->member.run.style);
4871 editor->pCursors[0].pRun->member.run.style = editor->pBuffer->pDefaultStyle;
4874 /* FIXME: Currently no support for undo level and code page options */
4875 editor->mode = (editor->mode & ~mask) | changes;
4876 return 0;
4878 case EM_SETPASSWORDCHAR:
4880 editor->cPasswordMask = wParam;
4881 ME_RewrapRepaint(editor);
4882 return 0;
4884 case EM_SETTARGETDEVICE:
4885 if (wParam == 0)
4887 BOOL new = (lParam == 0 && (editor->styleFlags & ES_MULTILINE));
4888 if (editor->nAvailWidth || editor->bWordWrap != new)
4890 editor->bWordWrap = new;
4891 editor->nAvailWidth = 0; /* wrap to client area */
4892 ME_RewrapRepaint(editor);
4894 } else {
4895 int width = max(0, lParam);
4896 if ((editor->styleFlags & ES_MULTILINE) &&
4897 (!editor->bWordWrap || editor->nAvailWidth != width))
4899 editor->nAvailWidth = width;
4900 editor->bWordWrap = TRUE;
4901 ME_RewrapRepaint(editor);
4903 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4905 return TRUE;
4906 default:
4907 do_default:
4908 *phresult = S_FALSE;
4909 break;
4911 return 0L;
4914 static BOOL create_windowed_editor(HWND hwnd, CREATESTRUCTW *create, BOOL emulate_10)
4916 ITextHost *host = ME_CreateTextHost( hwnd, create, emulate_10 );
4917 ME_TextEditor *editor;
4919 if (!host) return FALSE;
4921 editor = ME_MakeEditor( host, emulate_10 );
4922 if (!editor)
4924 ITextHost_Release( host );
4925 return FALSE;
4928 editor->exStyleFlags = GetWindowLongW( hwnd, GWL_EXSTYLE );
4929 editor->styleFlags |= GetWindowLongW( hwnd, GWL_STYLE ) & ES_WANTRETURN;
4930 editor->hWnd = hwnd; /* FIXME: Remove editor's dependence on hWnd */
4931 editor->hwndParent = create->hwndParent;
4933 SetWindowLongPtrW( hwnd, 0, (LONG_PTR)editor );
4935 return TRUE;
4938 static LRESULT RichEditWndProc_common(HWND hWnd, UINT msg, WPARAM wParam,
4939 LPARAM lParam, BOOL unicode)
4941 ME_TextEditor *editor;
4942 HRESULT hresult;
4943 LRESULT lresult = 0;
4945 TRACE("enter hwnd %p msg %04x (%s) %lx %lx, unicode %d\n",
4946 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode);
4948 editor = (ME_TextEditor *)GetWindowLongPtrW(hWnd, 0);
4949 if (!editor)
4951 if (msg == WM_NCCREATE)
4953 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
4955 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
4956 return create_windowed_editor( hWnd, pcs, FALSE );
4958 else
4960 return DefWindowProcW(hWnd, msg, wParam, lParam);
4964 switch (msg)
4966 case WM_PAINT:
4968 HDC hDC;
4969 RECT rc;
4970 PAINTSTRUCT ps;
4972 hDC = BeginPaint(editor->hWnd, &ps);
4973 if (!editor->bEmulateVersion10 || (editor->nEventMask & ENM_UPDATE))
4974 ME_SendOldNotify(editor, EN_UPDATE);
4975 /* Erase area outside of the formatting rectangle */
4976 if (ps.rcPaint.top < editor->rcFormat.top)
4978 rc = ps.rcPaint;
4979 rc.bottom = editor->rcFormat.top;
4980 FillRect(hDC, &rc, editor->hbrBackground);
4981 ps.rcPaint.top = editor->rcFormat.top;
4983 if (ps.rcPaint.bottom > editor->rcFormat.bottom) {
4984 rc = ps.rcPaint;
4985 rc.top = editor->rcFormat.bottom;
4986 FillRect(hDC, &rc, editor->hbrBackground);
4987 ps.rcPaint.bottom = editor->rcFormat.bottom;
4989 if (ps.rcPaint.left < editor->rcFormat.left) {
4990 rc = ps.rcPaint;
4991 rc.right = editor->rcFormat.left;
4992 FillRect(hDC, &rc, editor->hbrBackground);
4993 ps.rcPaint.left = editor->rcFormat.left;
4995 if (ps.rcPaint.right > editor->rcFormat.right) {
4996 rc = ps.rcPaint;
4997 rc.left = editor->rcFormat.right;
4998 FillRect(hDC, &rc, editor->hbrBackground);
4999 ps.rcPaint.right = editor->rcFormat.right;
5002 ME_PaintContent(editor, hDC, &ps.rcPaint);
5003 EndPaint(editor->hWnd, &ps);
5004 return 0;
5006 case WM_ERASEBKGND:
5008 HDC hDC = (HDC)wParam;
5009 RECT rc;
5011 if (GetUpdateRect(editor->hWnd, &rc, TRUE))
5012 FillRect(hDC, &rc, editor->hbrBackground);
5013 return 1;
5015 case EM_SETOPTIONS:
5017 DWORD dwStyle;
5018 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
5019 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
5020 ECO_SELECTIONBAR;
5021 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5022 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
5023 dwStyle = (dwStyle & ~mask) | (lresult & mask);
5024 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
5025 return lresult;
5027 case EM_SETREADONLY:
5029 DWORD dwStyle;
5030 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5031 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
5032 dwStyle &= ~ES_READONLY;
5033 if (wParam)
5034 dwStyle |= ES_READONLY;
5035 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
5036 return lresult;
5038 default:
5039 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5042 if (hresult == S_FALSE)
5043 lresult = DefWindowProcW(hWnd, msg, wParam, lParam);
5045 TRACE("exit hwnd %p msg %04x (%s) %lx %lx, unicode %d -> %lu\n",
5046 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode, lresult);
5048 return lresult;
5051 static LRESULT WINAPI RichEditWndProcW(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5053 BOOL unicode = TRUE;
5055 /* Under Win9x RichEdit20W returns ANSI strings, see the tests. */
5056 if (msg == WM_GETTEXT && (GetVersion() & 0x80000000))
5057 unicode = FALSE;
5059 return RichEditWndProc_common(hWnd, msg, wParam, lParam, unicode);
5062 static LRESULT WINAPI RichEditWndProcA(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5064 return RichEditWndProc_common(hWnd, msg, wParam, lParam, FALSE);
5067 /******************************************************************
5068 * RichEditANSIWndProc (RICHED20.10)
5070 LRESULT WINAPI RichEditANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5072 return RichEditWndProcA(hWnd, msg, wParam, lParam);
5075 /******************************************************************
5076 * RichEdit10ANSIWndProc (RICHED20.9)
5078 LRESULT WINAPI RichEdit10ANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5080 if (msg == WM_NCCREATE && !GetWindowLongPtrW(hWnd, 0))
5082 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
5084 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
5085 return create_windowed_editor( hWnd, pcs, TRUE );
5087 return RichEditANSIWndProc(hWnd, msg, wParam, lParam);
5090 void ME_SendOldNotify(ME_TextEditor *editor, int nCode)
5092 ITextHost_TxNotify(editor->texthost, nCode, NULL);
5095 /* Fill buffer with srcChars unicode characters from the start cursor.
5097 * buffer: destination buffer
5098 * buflen: length of buffer in characters excluding the NULL terminator.
5099 * start: start of editor text to copy into buffer.
5100 * srcChars: Number of characters to use from the editor text.
5101 * bCRLF: if true, replaces all end of lines with \r\n pairs.
5103 * returns the number of characters written excluding the NULL terminator.
5105 * The written text is always NULL terminated.
5107 int ME_GetTextW(ME_TextEditor *editor, WCHAR *buffer, int buflen,
5108 const ME_Cursor *start, int srcChars, BOOL bCRLF,
5109 BOOL bEOP)
5111 ME_DisplayItem *pRun, *pNextRun;
5112 const WCHAR *pStart = buffer;
5113 const WCHAR cr_lf[] = {'\r', '\n', 0};
5114 const WCHAR *str;
5115 int nLen;
5117 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
5118 if (editor->bEmulateVersion10) bCRLF = FALSE;
5120 pRun = start->pRun;
5121 assert(pRun);
5122 pNextRun = ME_FindItemFwd(pRun, diRun);
5124 nLen = pRun->member.run.len - start->nOffset;
5125 str = get_text( &pRun->member.run, start->nOffset );
5127 while (srcChars && buflen && pNextRun)
5129 int nFlags = pRun->member.run.nFlags;
5131 if (bCRLF && nFlags & MERF_ENDPARA && ~nFlags & MERF_ENDCELL)
5133 if (buflen == 1) break;
5134 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
5135 * EM_GETTEXTEX, however, this is done for copying text which
5136 * also uses this function. */
5137 srcChars -= min(nLen, srcChars);
5138 nLen = 2;
5139 str = cr_lf;
5140 } else {
5141 nLen = min(nLen, srcChars);
5142 srcChars -= nLen;
5145 nLen = min(nLen, buflen);
5146 buflen -= nLen;
5148 CopyMemory(buffer, str, sizeof(WCHAR) * nLen);
5150 buffer += nLen;
5152 pRun = pNextRun;
5153 pNextRun = ME_FindItemFwd(pRun, diRun);
5155 nLen = pRun->member.run.len;
5156 str = get_text( &pRun->member.run, 0 );
5158 /* append '\r' to the last paragraph. */
5159 if (pRun->next->type == diTextEnd && bEOP)
5161 *buffer = '\r';
5162 buffer ++;
5164 *buffer = 0;
5165 return buffer - pStart;
5168 static BOOL ME_RegisterEditorClass(HINSTANCE hInstance)
5170 WNDCLASSW wcW;
5171 WNDCLASSA wcA;
5173 wcW.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5174 wcW.lpfnWndProc = RichEditWndProcW;
5175 wcW.cbClsExtra = 0;
5176 wcW.cbWndExtra = sizeof(ME_TextEditor *);
5177 wcW.hInstance = NULL; /* hInstance would register DLL-local class */
5178 wcW.hIcon = NULL;
5179 wcW.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5180 wcW.hbrBackground = GetStockObject(NULL_BRUSH);
5181 wcW.lpszMenuName = NULL;
5183 if (is_version_nt())
5185 wcW.lpszClassName = RICHEDIT_CLASS20W;
5186 if (!RegisterClassW(&wcW)) return FALSE;
5187 wcW.lpszClassName = MSFTEDIT_CLASS;
5188 if (!RegisterClassW(&wcW)) return FALSE;
5190 else
5192 /* WNDCLASSA/W have the same layout */
5193 wcW.lpszClassName = (LPCWSTR)"RichEdit20W";
5194 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5195 wcW.lpszClassName = (LPCWSTR)"RichEdit50W";
5196 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5199 wcA.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5200 wcA.lpfnWndProc = RichEditWndProcA;
5201 wcA.cbClsExtra = 0;
5202 wcA.cbWndExtra = sizeof(ME_TextEditor *);
5203 wcA.hInstance = NULL; /* hInstance would register DLL-local class */
5204 wcA.hIcon = NULL;
5205 wcA.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5206 wcA.hbrBackground = GetStockObject(NULL_BRUSH);
5207 wcA.lpszMenuName = NULL;
5208 wcA.lpszClassName = RICHEDIT_CLASS20A;
5209 if (!RegisterClassA(&wcA)) return FALSE;
5210 wcA.lpszClassName = "RichEdit50A";
5211 if (!RegisterClassA(&wcA)) return FALSE;
5213 return TRUE;
5216 static LRESULT WINAPI REComboWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5217 /* FIXME: Not implemented */
5218 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5219 hWnd, msg, get_msg_name(msg), wParam, lParam);
5220 return DefWindowProcW(hWnd, msg, wParam, lParam);
5223 static LRESULT WINAPI REListWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5224 /* FIXME: Not implemented */
5225 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5226 hWnd, msg, get_msg_name(msg), wParam, lParam);
5227 return DefWindowProcW(hWnd, msg, wParam, lParam);
5230 /******************************************************************
5231 * REExtendedRegisterClass (RICHED20.8)
5233 * FIXME undocumented
5234 * Need to check for errors and implement controls and callbacks
5236 LRESULT WINAPI REExtendedRegisterClass(void)
5238 WNDCLASSW wcW;
5239 UINT result;
5241 FIXME("semi stub\n");
5243 wcW.cbClsExtra = 0;
5244 wcW.cbWndExtra = 4;
5245 wcW.hInstance = NULL;
5246 wcW.hIcon = NULL;
5247 wcW.hCursor = NULL;
5248 wcW.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
5249 wcW.lpszMenuName = NULL;
5251 if (!ME_ListBoxRegistered)
5253 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS;
5254 wcW.lpfnWndProc = REListWndProc;
5255 wcW.lpszClassName = REListBox20W;
5256 if (RegisterClassW(&wcW)) ME_ListBoxRegistered = TRUE;
5259 if (!ME_ComboBoxRegistered)
5261 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS | CS_VREDRAW | CS_HREDRAW;
5262 wcW.lpfnWndProc = REComboWndProc;
5263 wcW.lpszClassName = REComboBox20W;
5264 if (RegisterClassW(&wcW)) ME_ComboBoxRegistered = TRUE;
5267 result = 0;
5268 if (ME_ListBoxRegistered)
5269 result += 1;
5270 if (ME_ComboBoxRegistered)
5271 result += 2;
5273 return result;
5276 static int wchar_comp( const void *key, const void *elem )
5278 return *(const WCHAR *)key - *(const WCHAR *)elem;
5281 /* neutral characters end the url if the next non-neutral character is a space character,
5282 otherwise they are included in the url. */
5283 static BOOL isurlneutral( WCHAR c )
5285 /* NB this list is sorted */
5286 static const WCHAR neutral_chars[] = {'!','\"','\'','(',')',',','-','.',':',';','<','>','?','[',']','{','}'};
5288 /* Some shortcuts */
5289 if (isalnum( c )) return FALSE;
5290 if (c > neutral_chars[ARRAY_SIZE( neutral_chars ) - 1]) return FALSE;
5292 return !!bsearch( &c, neutral_chars, ARRAY_SIZE( neutral_chars ), sizeof(c), wchar_comp );
5296 * This proc takes a selection, and scans it forward in order to select the span
5297 * of a possible URL candidate. A possible URL candidate must start with isalnum
5298 * or one of the following special characters: *|/\+%#@ and must consist entirely
5299 * of the characters allowed to start the URL, plus : (colon) which may occur
5300 * at most once, and not at either end.
5302 static BOOL ME_FindNextURLCandidate(ME_TextEditor *editor,
5303 const ME_Cursor *start,
5304 int nChars,
5305 ME_Cursor *candidate_min,
5306 ME_Cursor *candidate_max)
5308 ME_Cursor cursor = *start, neutral_end, space_end;
5309 BOOL candidateStarted = FALSE, quoted = FALSE;
5310 WCHAR c;
5312 while (nChars > 0)
5314 WCHAR *str = get_text( &cursor.pRun->member.run, 0 );
5315 int run_len = cursor.pRun->member.run.len;
5317 nChars -= run_len - cursor.nOffset;
5319 /* Find start of candidate */
5320 if (!candidateStarted)
5322 while (cursor.nOffset < run_len)
5324 c = str[cursor.nOffset];
5325 if (!isspaceW( c ) && !isurlneutral( c ))
5327 *candidate_min = cursor;
5328 candidateStarted = TRUE;
5329 neutral_end.pPara = NULL;
5330 space_end.pPara = NULL;
5331 cursor.nOffset++;
5332 break;
5334 quoted = (c == '<');
5335 cursor.nOffset++;
5339 /* Find end of candidate */
5340 if (candidateStarted)
5342 while (cursor.nOffset < run_len)
5344 c = str[cursor.nOffset];
5345 if (isspaceW( c ))
5347 if (quoted && c != '\r')
5349 if (!space_end.pPara)
5351 if (neutral_end.pPara)
5352 space_end = neutral_end;
5353 else
5354 space_end = cursor;
5357 else
5358 goto done;
5360 else if (isurlneutral( c ))
5362 if (quoted && c == '>')
5364 neutral_end.pPara = NULL;
5365 space_end.pPara = NULL;
5366 goto done;
5368 if (!neutral_end.pPara)
5369 neutral_end = cursor;
5371 else
5372 neutral_end.pPara = NULL;
5374 cursor.nOffset++;
5378 cursor.nOffset = 0;
5379 if (!ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE))
5380 goto done;
5383 done:
5384 if (candidateStarted)
5386 if (space_end.pPara)
5387 *candidate_max = space_end;
5388 else if (neutral_end.pPara)
5389 *candidate_max = neutral_end;
5390 else
5391 *candidate_max = cursor;
5392 return TRUE;
5394 *candidate_max = *candidate_min = cursor;
5395 return FALSE;
5399 * This proc evaluates the selection and returns TRUE if it can be considered an URL
5401 static BOOL ME_IsCandidateAnURL(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
5403 #define MAX_PREFIX_LEN 9
5404 struct prefix_s {
5405 const WCHAR text[MAX_PREFIX_LEN];
5406 int length;
5407 }prefixes[] = {
5408 {{'p','r','o','s','p','e','r','o',':'}, 9},
5409 {{'t','e','l','n','e','t',':'}, 7},
5410 {{'g','o','p','h','e','r',':'}, 7},
5411 {{'m','a','i','l','t','o',':'}, 7},
5412 {{'h','t','t','p','s',':'}, 6},
5413 {{'f','i','l','e',':'}, 5},
5414 {{'n','e','w','s',':'}, 5},
5415 {{'w','a','i','s',':'}, 5},
5416 {{'n','n','t','p',':'}, 5},
5417 {{'h','t','t','p',':'}, 5},
5418 {{'w','w','w','.'}, 4},
5419 {{'f','t','p',':'}, 4},
5421 WCHAR bufferW[MAX_PREFIX_LEN + 1];
5422 unsigned int i;
5424 ME_GetTextW(editor, bufferW, MAX_PREFIX_LEN, start, nChars, FALSE, FALSE);
5425 for (i = 0; i < ARRAY_SIZE(prefixes); i++)
5427 if (nChars < prefixes[i].length) continue;
5428 if (!memcmp(prefixes[i].text, bufferW, prefixes[i].length * sizeof(WCHAR)))
5429 return TRUE;
5431 return FALSE;
5432 #undef MAX_PREFIX_LEN
5436 * This proc walks through the indicated selection and evaluates whether each
5437 * section identified by ME_FindNextURLCandidate and in-between sections have
5438 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
5439 * not what it is supposed to be, this proc sets or unsets it as appropriate.
5441 * Since this function can cause runs to be split, do not depend on the value
5442 * of the start cursor at the end of the function.
5444 * nChars may be set to INT_MAX to update to the end of the text.
5446 * Returns TRUE if at least one section was modified.
5448 static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars)
5450 BOOL modified = FALSE;
5451 ME_Cursor startCur = *start;
5453 if (!editor->AutoURLDetect_bEnable) return FALSE;
5457 CHARFORMAT2W link;
5458 ME_Cursor candidateStart, candidateEnd;
5460 if (ME_FindNextURLCandidate(editor, &startCur, nChars,
5461 &candidateStart, &candidateEnd))
5463 /* Section before candidate is not an URL */
5464 int cMin = ME_GetCursorOfs(&candidateStart);
5465 int cMax = ME_GetCursorOfs(&candidateEnd);
5467 if (!ME_IsCandidateAnURL(editor, &candidateStart, cMax - cMin))
5468 candidateStart = candidateEnd;
5469 nChars -= cMax - ME_GetCursorOfs(&startCur);
5471 else
5473 /* No more candidates until end of selection */
5474 nChars = 0;
5477 if (startCur.pRun != candidateStart.pRun ||
5478 startCur.nOffset != candidateStart.nOffset)
5480 /* CFE_LINK effect should be consistently unset */
5481 link.cbSize = sizeof(link);
5482 ME_GetCharFormat(editor, &startCur, &candidateStart, &link);
5483 if (!(link.dwMask & CFM_LINK) || (link.dwEffects & CFE_LINK))
5485 /* CFE_LINK must be unset from this range */
5486 memset(&link, 0, sizeof(CHARFORMAT2W));
5487 link.cbSize = sizeof(link);
5488 link.dwMask = CFM_LINK;
5489 link.dwEffects = 0;
5490 ME_SetCharFormat(editor, &startCur, &candidateStart, &link);
5491 /* Update candidateEnd since setting character formats may split
5492 * runs, which can cause a cursor to be at an invalid offset within
5493 * a split run. */
5494 while (candidateEnd.nOffset >= candidateEnd.pRun->member.run.len)
5496 candidateEnd.nOffset -= candidateEnd.pRun->member.run.len;
5497 candidateEnd.pRun = ME_FindItemFwd(candidateEnd.pRun, diRun);
5499 modified = TRUE;
5502 if (candidateStart.pRun != candidateEnd.pRun ||
5503 candidateStart.nOffset != candidateEnd.nOffset)
5505 /* CFE_LINK effect should be consistently set */
5506 link.cbSize = sizeof(link);
5507 ME_GetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5508 if (!(link.dwMask & CFM_LINK) || !(link.dwEffects & CFE_LINK))
5510 /* CFE_LINK must be set on this range */
5511 memset(&link, 0, sizeof(CHARFORMAT2W));
5512 link.cbSize = sizeof(link);
5513 link.dwMask = CFM_LINK;
5514 link.dwEffects = CFE_LINK;
5515 ME_SetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5516 modified = TRUE;
5519 startCur = candidateEnd;
5520 } while (nChars > 0);
5521 return modified;