wevtapi: Fix typo in spec file.
[wine/zf.git] / dlls / riched20 / editor.c
blob2571ce97bcf76f04d8e0db87ddf2d2858f47c47e
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
2431 ME_KeyDown(ME_TextEditor *editor, WORD nKey)
2433 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2434 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2436 if (editor->bMouseCaptured)
2437 return FALSE;
2438 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey != VK_MENU)
2439 editor->nSelectionType = stPosition;
2441 switch (nKey)
2443 case VK_LEFT:
2444 case VK_RIGHT:
2445 case VK_HOME:
2446 case VK_END:
2447 editor->nUDArrowX = -1;
2448 /* fall through */
2449 case VK_UP:
2450 case VK_DOWN:
2451 case VK_PRIOR:
2452 case VK_NEXT:
2453 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
2454 ME_ArrowKey(editor, nKey, shift_is_down, ctrl_is_down);
2455 return TRUE;
2456 case VK_BACK:
2457 case VK_DELETE:
2458 editor->nUDArrowX = -1;
2459 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2460 if (editor->styleFlags & ES_READONLY)
2461 return FALSE;
2462 if (ME_IsSelection(editor))
2464 ME_DeleteSelection(editor);
2465 ME_CommitUndo(editor);
2467 else if (nKey == VK_DELETE)
2469 /* Delete stops group typing.
2470 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2471 ME_DeleteTextAtCursor(editor, 1, 1);
2472 ME_CommitUndo(editor);
2474 else if (ME_ArrowKey(editor, VK_LEFT, FALSE, FALSE))
2476 BOOL bDeletionSucceeded;
2477 /* Backspace can be grouped for a single undo */
2478 ME_ContinueCoalescingTransaction(editor);
2479 bDeletionSucceeded = ME_DeleteTextAtCursor(editor, 1, 1);
2480 if (!bDeletionSucceeded && !editor->bEmulateVersion10) { /* v4.1 */
2481 /* Deletion was prevented so the cursor is moved back to where it was.
2482 * (e.g. this happens when trying to delete cell boundaries)
2484 ME_ArrowKey(editor, VK_RIGHT, FALSE, FALSE);
2486 ME_CommitCoalescingUndo(editor);
2488 else
2489 return TRUE;
2490 ME_MoveCursorFromTableRowStartParagraph(editor);
2491 ME_UpdateSelectionLinkAttribute(editor);
2492 ME_UpdateRepaint(editor, FALSE);
2493 ME_SendRequestResize(editor, FALSE);
2494 return TRUE;
2495 case VK_RETURN:
2496 if (editor->bDialogMode)
2498 if (ctrl_is_down)
2499 return TRUE;
2501 if (!(editor->styleFlags & ES_WANTRETURN))
2503 if (editor->hwndParent)
2505 DWORD dw;
2506 dw = SendMessageW(editor->hwndParent, DM_GETDEFID, 0, 0);
2507 if (HIWORD(dw) == DC_HASDEFID)
2509 HWND hwDefCtrl = GetDlgItem(editor->hwndParent, LOWORD(dw));
2510 if (hwDefCtrl)
2512 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, (WPARAM)hwDefCtrl, TRUE);
2513 PostMessageW(hwDefCtrl, WM_KEYDOWN, VK_RETURN, 0);
2517 return TRUE;
2521 if (editor->styleFlags & ES_MULTILINE)
2523 ME_Cursor cursor = editor->pCursors[0];
2524 ME_DisplayItem *para = cursor.pPara;
2525 int from, to;
2526 const WCHAR endl = '\r';
2527 const WCHAR endlv10[] = {'\r','\n'};
2528 ME_Style *style, *eop_style;
2530 if (editor->styleFlags & ES_READONLY) {
2531 MessageBeep(MB_ICONERROR);
2532 return TRUE;
2535 ME_GetSelectionOfs(editor, &from, &to);
2536 if (editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2538 if (!editor->bEmulateVersion10) { /* v4.1 */
2539 if (para->member.para.nFlags & MEPF_ROWEND) {
2540 /* Add a new table row after this row. */
2541 para = ME_AppendTableRow(editor, para);
2542 para = para->member.para.next_para;
2543 editor->pCursors[0].pPara = para;
2544 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2545 editor->pCursors[0].nOffset = 0;
2546 editor->pCursors[1] = editor->pCursors[0];
2547 ME_CommitUndo(editor);
2548 ME_CheckTablesForCorruption(editor);
2549 ME_UpdateRepaint(editor, FALSE);
2550 return TRUE;
2552 else if (para == editor->pCursors[1].pPara &&
2553 cursor.nOffset + cursor.pRun->member.run.nCharOfs == 0 &&
2554 para->member.para.prev_para->member.para.nFlags & MEPF_ROWSTART &&
2555 !para->member.para.prev_para->member.para.nCharOfs)
2557 /* Insert a newline before the table. */
2558 para = para->member.para.prev_para;
2559 para->member.para.nFlags &= ~MEPF_ROWSTART;
2560 editor->pCursors[0].pPara = para;
2561 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2562 editor->pCursors[1] = editor->pCursors[0];
2563 ME_InsertTextFromCursor(editor, 0, &endl, 1,
2564 editor->pCursors[0].pRun->member.run.style);
2565 para = editor->pBuffer->pFirst->member.para.next_para;
2566 ME_SetDefaultParaFormat(editor, &para->member.para.fmt);
2567 para->member.para.nFlags = MEPF_REWRAP;
2568 editor->pCursors[0].pPara = para;
2569 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2570 editor->pCursors[1] = editor->pCursors[0];
2571 para->member.para.next_para->member.para.nFlags |= MEPF_ROWSTART;
2572 ME_CommitCoalescingUndo(editor);
2573 ME_CheckTablesForCorruption(editor);
2574 ME_UpdateRepaint(editor, FALSE);
2575 return TRUE;
2577 } else { /* v1.0 - 3.0 */
2578 ME_DisplayItem *para = cursor.pPara;
2579 if (ME_IsInTable(para))
2581 if (cursor.pRun->member.run.nFlags & MERF_ENDPARA)
2583 if (from == to) {
2584 ME_ContinueCoalescingTransaction(editor);
2585 para = ME_AppendTableRow(editor, para);
2586 editor->pCursors[0].pPara = para;
2587 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2588 editor->pCursors[0].nOffset = 0;
2589 editor->pCursors[1] = editor->pCursors[0];
2590 ME_CommitCoalescingUndo(editor);
2591 ME_UpdateRepaint(editor, FALSE);
2592 return TRUE;
2594 } else {
2595 ME_ContinueCoalescingTransaction(editor);
2596 if (cursor.pRun->member.run.nCharOfs + cursor.nOffset == 0 &&
2597 !ME_IsInTable(para->member.para.prev_para))
2599 /* Insert newline before table */
2600 cursor.pRun = ME_FindItemBack(para, diRun);
2601 if (cursor.pRun) {
2602 editor->pCursors[0].pRun = cursor.pRun;
2603 editor->pCursors[0].pPara = para->member.para.prev_para;
2605 editor->pCursors[0].nOffset = 0;
2606 editor->pCursors[1] = editor->pCursors[0];
2607 ME_InsertTextFromCursor(editor, 0, &endl, 1,
2608 editor->pCursors[0].pRun->member.run.style);
2609 } else {
2610 editor->pCursors[1] = editor->pCursors[0];
2611 para = ME_AppendTableRow(editor, para);
2612 editor->pCursors[0].pPara = para;
2613 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2614 editor->pCursors[0].nOffset = 0;
2615 editor->pCursors[1] = editor->pCursors[0];
2617 ME_CommitCoalescingUndo(editor);
2618 ME_UpdateRepaint(editor, FALSE);
2619 return TRUE;
2624 style = ME_GetInsertStyle(editor, 0);
2626 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2627 eop style (this prevents the list label style changing when the new eop is inserted).
2628 No extra ref is taken here on eop_style. */
2629 if (para->member.para.fmt.wNumbering)
2630 eop_style = para->member.para.eop_run->style;
2631 else
2632 eop_style = style;
2633 ME_ContinueCoalescingTransaction(editor);
2634 if (shift_is_down)
2635 ME_InsertEndRowFromCursor(editor, 0);
2636 else
2637 if (!editor->bEmulateVersion10)
2638 ME_InsertTextFromCursor(editor, 0, &endl, 1, eop_style);
2639 else
2640 ME_InsertTextFromCursor(editor, 0, endlv10, 2, eop_style);
2641 ME_CommitCoalescingUndo(editor);
2642 SetCursor(NULL);
2644 ME_UpdateSelectionLinkAttribute(editor);
2645 ME_UpdateRepaint(editor, FALSE);
2646 ME_SaveTempStyle(editor, style); /* set the temp insert style for the new para */
2647 ME_ReleaseStyle(style);
2649 return TRUE;
2651 break;
2652 case VK_ESCAPE:
2653 if (editor->bDialogMode && editor->hwndParent)
2654 PostMessageW(editor->hwndParent, WM_CLOSE, 0, 0);
2655 return TRUE;
2656 case VK_TAB:
2657 if (editor->bDialogMode && editor->hwndParent)
2658 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, shift_is_down, 0);
2659 return TRUE;
2660 case 'A':
2661 if (ctrl_is_down)
2663 handle_EM_EXSETSEL( editor, 0, -1 );
2664 return TRUE;
2666 break;
2667 case 'V':
2668 if (ctrl_is_down)
2669 return paste_special( editor, 0, NULL, FALSE );
2670 break;
2671 case 'C':
2672 case 'X':
2673 if (ctrl_is_down)
2674 return copy_or_cut(editor, nKey == 'X');
2675 break;
2676 case 'Z':
2677 if (ctrl_is_down)
2679 ME_Undo(editor);
2680 return TRUE;
2682 break;
2683 case 'Y':
2684 if (ctrl_is_down)
2686 ME_Redo(editor);
2687 return TRUE;
2689 break;
2691 default:
2692 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey && nKey != VK_MENU)
2693 editor->nUDArrowX = -1;
2694 if (ctrl_is_down)
2696 if (nKey == 'W')
2698 CHARFORMAT2W chf;
2699 char buf[2048];
2700 chf.cbSize = sizeof(chf);
2702 ME_GetSelectionCharFormat(editor, &chf);
2703 ME_DumpStyleToBuf(&chf, buf);
2704 MessageBoxA(NULL, buf, "Style dump", MB_OK);
2706 if (nKey == 'Q')
2708 ME_CheckCharOffsets(editor);
2712 return FALSE;
2715 static LRESULT ME_Char(ME_TextEditor *editor, WPARAM charCode,
2716 LPARAM flags, BOOL unicode)
2718 WCHAR wstr;
2720 if (editor->bMouseCaptured)
2721 return 0;
2723 if (unicode)
2724 wstr = (WCHAR)charCode;
2725 else
2727 CHAR charA = charCode;
2728 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &wstr, 1);
2731 if (editor->styleFlags & ES_READONLY) {
2732 MessageBeep(MB_ICONERROR);
2733 return 0; /* FIXME really 0 ? */
2736 if ((unsigned)wstr >= ' ' || wstr == '\t')
2738 ME_Cursor cursor = editor->pCursors[0];
2739 ME_DisplayItem *para = cursor.pPara;
2740 int from, to;
2741 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2742 ME_GetSelectionOfs(editor, &from, &to);
2743 if (wstr == '\t' &&
2744 /* v4.1 allows tabs to be inserted with ctrl key down */
2745 !(ctrl_is_down && !editor->bEmulateVersion10))
2747 ME_DisplayItem *para;
2748 BOOL bSelectedRow = FALSE;
2750 para = cursor.pPara;
2751 if (ME_IsSelection(editor) &&
2752 cursor.pRun->member.run.nCharOfs + cursor.nOffset == 0 &&
2753 to == ME_GetCursorOfs(&editor->pCursors[0]) &&
2754 para->member.para.prev_para->type == diParagraph)
2756 para = para->member.para.prev_para;
2757 bSelectedRow = TRUE;
2759 if (ME_IsInTable(para))
2761 ME_TabPressedInTable(editor, bSelectedRow);
2762 ME_CommitUndo(editor);
2763 return 0;
2765 } else if (!editor->bEmulateVersion10) { /* v4.1 */
2766 if (para->member.para.nFlags & MEPF_ROWEND) {
2767 if (from == to) {
2768 para = para->member.para.next_para;
2769 if (para->member.para.nFlags & MEPF_ROWSTART)
2770 para = para->member.para.next_para;
2771 editor->pCursors[0].pPara = para;
2772 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2773 editor->pCursors[0].nOffset = 0;
2774 editor->pCursors[1] = editor->pCursors[0];
2777 } else { /* v1.0 - 3.0 */
2778 if (ME_IsInTable(cursor.pRun) &&
2779 cursor.pRun->member.run.nFlags & MERF_ENDPARA &&
2780 from == to)
2782 /* Text should not be inserted at the end of the table. */
2783 MessageBeep(-1);
2784 return 0;
2787 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2788 /* WM_CHAR is restricted to nTextLimit */
2789 if(editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2791 ME_Style *style = ME_GetInsertStyle(editor, 0);
2792 ME_ContinueCoalescingTransaction(editor);
2793 ME_InsertTextFromCursor(editor, 0, &wstr, 1, style);
2794 ME_ReleaseStyle(style);
2795 ME_CommitCoalescingUndo(editor);
2796 ITextHost_TxSetCursor(editor->texthost, NULL, FALSE);
2799 ME_UpdateSelectionLinkAttribute(editor);
2800 ME_UpdateRepaint(editor, FALSE);
2802 return 0;
2805 /* Process the message and calculate the new click count.
2807 * returns: The click count if it is mouse down event, else returns 0. */
2808 static int ME_CalculateClickCount(ME_TextEditor *editor, UINT msg, WPARAM wParam,
2809 LPARAM lParam)
2811 static int clickNum = 0;
2812 if (msg < WM_MOUSEFIRST || msg > WM_MOUSELAST)
2813 return 0;
2815 if ((msg == WM_LBUTTONDBLCLK) ||
2816 (msg == WM_RBUTTONDBLCLK) ||
2817 (msg == WM_MBUTTONDBLCLK) ||
2818 (msg == WM_XBUTTONDBLCLK))
2820 msg -= (WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
2823 if ((msg == WM_LBUTTONDOWN) ||
2824 (msg == WM_RBUTTONDOWN) ||
2825 (msg == WM_MBUTTONDOWN) ||
2826 (msg == WM_XBUTTONDOWN))
2828 static MSG prevClickMsg;
2829 MSG clickMsg;
2830 /* Compare the editor instead of the hwnd so that the this
2831 * can still be done for windowless richedit controls. */
2832 clickMsg.hwnd = (HWND)editor;
2833 clickMsg.message = msg;
2834 clickMsg.wParam = wParam;
2835 clickMsg.lParam = lParam;
2836 clickMsg.time = GetMessageTime();
2837 clickMsg.pt.x = (short)LOWORD(lParam);
2838 clickMsg.pt.y = (short)HIWORD(lParam);
2839 if ((clickNum != 0) &&
2840 (clickMsg.message == prevClickMsg.message) &&
2841 (clickMsg.hwnd == prevClickMsg.hwnd) &&
2842 (clickMsg.wParam == prevClickMsg.wParam) &&
2843 (clickMsg.time - prevClickMsg.time < GetDoubleClickTime()) &&
2844 (abs(clickMsg.pt.x - prevClickMsg.pt.x) < GetSystemMetrics(SM_CXDOUBLECLK)/2) &&
2845 (abs(clickMsg.pt.y - prevClickMsg.pt.y) < GetSystemMetrics(SM_CYDOUBLECLK)/2))
2847 clickNum++;
2848 } else {
2849 clickNum = 1;
2851 prevClickMsg = clickMsg;
2852 } else {
2853 return 0;
2855 return clickNum;
2858 static BOOL is_link( ME_Run *run )
2860 return (run->style->fmt.dwMask & CFM_LINK) && (run->style->fmt.dwEffects & CFE_LINK);
2863 static BOOL ME_SetCursor(ME_TextEditor *editor)
2865 ME_Cursor cursor;
2866 POINT pt;
2867 BOOL isExact;
2868 SCROLLBARINFO sbi;
2869 DWORD messagePos = GetMessagePos();
2870 pt.x = (short)LOWORD(messagePos);
2871 pt.y = (short)HIWORD(messagePos);
2873 if (editor->hWnd)
2875 sbi.cbSize = sizeof(sbi);
2876 GetScrollBarInfo(editor->hWnd, OBJID_HSCROLL, &sbi);
2877 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2878 PtInRect(&sbi.rcScrollBar, pt))
2880 ITextHost_TxSetCursor(editor->texthost,
2881 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2882 return TRUE;
2884 sbi.cbSize = sizeof(sbi);
2885 GetScrollBarInfo(editor->hWnd, OBJID_VSCROLL, &sbi);
2886 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2887 PtInRect(&sbi.rcScrollBar, pt))
2889 ITextHost_TxSetCursor(editor->texthost,
2890 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2891 return TRUE;
2894 ITextHost_TxScreenToClient(editor->texthost, &pt);
2896 if (editor->nSelectionType == stLine && editor->bMouseCaptured) {
2897 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2898 return TRUE;
2900 if (!editor->bEmulateVersion10 /* v4.1 */ &&
2901 pt.y < editor->rcFormat.top &&
2902 pt.x < editor->rcFormat.left)
2904 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2905 return TRUE;
2907 if (pt.y < editor->rcFormat.top || pt.y > editor->rcFormat.bottom)
2909 if (editor->bEmulateVersion10) /* v1.0 - 3.0 */
2910 ITextHost_TxSetCursor(editor->texthost,
2911 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2912 else /* v4.1 */
2913 ITextHost_TxSetCursor(editor->texthost,
2914 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
2915 return TRUE;
2917 if (pt.x < editor->rcFormat.left)
2919 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2920 return TRUE;
2922 ME_CharFromPos(editor, pt.x, pt.y, &cursor, &isExact);
2923 if (isExact)
2925 ME_Run *run;
2927 run = &cursor.pRun->member.run;
2928 if (is_link( run ))
2930 ITextHost_TxSetCursor(editor->texthost,
2931 LoadCursorW(NULL, (WCHAR*)IDC_HAND),
2932 FALSE);
2933 return TRUE;
2936 if (ME_IsSelection(editor))
2938 int selStart, selEnd;
2939 int offset = ME_GetCursorOfs(&cursor);
2941 ME_GetSelectionOfs(editor, &selStart, &selEnd);
2942 if (selStart <= offset && selEnd >= offset) {
2943 ITextHost_TxSetCursor(editor->texthost,
2944 LoadCursorW(NULL, (WCHAR*)IDC_ARROW),
2945 FALSE);
2946 return TRUE;
2950 ITextHost_TxSetCursor(editor->texthost,
2951 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
2952 return TRUE;
2955 static void ME_SetDefaultFormatRect(ME_TextEditor *editor)
2957 ITextHost_TxGetClientRect(editor->texthost, &editor->rcFormat);
2958 editor->rcFormat.top += editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
2959 editor->rcFormat.left += 1 + editor->selofs;
2960 editor->rcFormat.right -= 1;
2963 static LONG ME_GetSelectionType(ME_TextEditor *editor)
2965 LONG sel_type = SEL_EMPTY;
2966 LONG start, end;
2968 ME_GetSelectionOfs(editor, &start, &end);
2969 if (start == end)
2970 sel_type = SEL_EMPTY;
2971 else
2973 LONG object_count = 0, character_count = 0;
2974 int i;
2976 for (i = 0; i < end - start; i++)
2978 ME_Cursor cursor;
2980 ME_CursorFromCharOfs(editor, start + i, &cursor);
2981 if (cursor.pRun->member.run.reobj)
2982 object_count++;
2983 else
2984 character_count++;
2985 if (character_count >= 2 && object_count >= 2)
2986 return (SEL_TEXT | SEL_MULTICHAR | SEL_OBJECT | SEL_MULTIOBJECT);
2988 if (character_count)
2990 sel_type |= SEL_TEXT;
2991 if (character_count >= 2)
2992 sel_type |= SEL_MULTICHAR;
2994 if (object_count)
2996 sel_type |= SEL_OBJECT;
2997 if (object_count >= 2)
2998 sel_type |= SEL_MULTIOBJECT;
3001 return sel_type;
3004 static BOOL ME_ShowContextMenu(ME_TextEditor *editor, int x, int y)
3006 CHARRANGE selrange;
3007 HMENU menu;
3008 int seltype;
3010 if(!editor->lpOleCallback || !editor->hWnd)
3011 return FALSE;
3012 ME_GetSelectionOfs(editor, &selrange.cpMin, &selrange.cpMax);
3013 seltype = ME_GetSelectionType(editor);
3014 if(SUCCEEDED(IRichEditOleCallback_GetContextMenu(editor->lpOleCallback, seltype, NULL, &selrange, &menu)))
3016 TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, 0, editor->hwndParent, NULL);
3017 DestroyMenu(menu);
3019 return TRUE;
3022 ME_TextEditor *ME_MakeEditor(ITextHost *texthost, BOOL bEmulateVersion10)
3024 ME_TextEditor *ed = heap_alloc(sizeof(*ed));
3025 int i;
3026 DWORD props;
3027 LONG selbarwidth;
3029 ed->hWnd = NULL;
3030 ed->hwndParent = NULL;
3031 ed->sizeWindow.cx = ed->sizeWindow.cy = 0;
3032 ed->texthost = texthost;
3033 ed->reOle = NULL;
3034 ed->bEmulateVersion10 = bEmulateVersion10;
3035 ed->styleFlags = 0;
3036 ed->exStyleFlags = 0;
3037 ITextHost_TxGetPropertyBits(texthost,
3038 (TXTBIT_RICHTEXT|TXTBIT_MULTILINE|
3039 TXTBIT_READONLY|TXTBIT_USEPASSWORD|
3040 TXTBIT_HIDESELECTION|TXTBIT_SAVESELECTION|
3041 TXTBIT_AUTOWORDSEL|TXTBIT_VERTICAL|
3042 TXTBIT_WORDWRAP|TXTBIT_DISABLEDRAG),
3043 &props);
3044 ITextHost_TxGetScrollBars(texthost, &ed->styleFlags);
3045 ed->styleFlags &= (WS_VSCROLL|WS_HSCROLL|ES_AUTOVSCROLL|
3046 ES_AUTOHSCROLL|ES_DISABLENOSCROLL);
3047 ed->pBuffer = ME_MakeText();
3048 ed->nZoomNumerator = ed->nZoomDenominator = 0;
3049 ed->nAvailWidth = 0; /* wrap to client area */
3050 list_init( &ed->style_list );
3051 ME_MakeFirstParagraph(ed);
3052 /* The four cursors are for:
3053 * 0 - The position where the caret is shown
3054 * 1 - The anchored end of the selection (for normal selection)
3055 * 2 & 3 - The anchored start and end respectively for word, line,
3056 * or paragraph selection.
3058 ed->nCursors = 4;
3059 ed->pCursors = heap_alloc(ed->nCursors * sizeof(*ed->pCursors));
3060 ME_SetCursorToStart(ed, &ed->pCursors[0]);
3061 ed->pCursors[1] = ed->pCursors[0];
3062 ed->pCursors[2] = ed->pCursors[0];
3063 ed->pCursors[3] = ed->pCursors[1];
3064 ed->nLastTotalLength = ed->nTotalLength = 0;
3065 ed->nLastTotalWidth = ed->nTotalWidth = 0;
3066 ed->nUDArrowX = -1;
3067 ed->rgbBackColor = -1;
3068 ed->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3069 ed->bCaretAtEnd = FALSE;
3070 ed->nEventMask = 0;
3071 ed->nModifyStep = 0;
3072 ed->nTextLimit = TEXT_LIMIT_DEFAULT;
3073 list_init( &ed->undo_stack );
3074 list_init( &ed->redo_stack );
3075 ed->nUndoStackSize = 0;
3076 ed->nUndoLimit = STACK_SIZE_DEFAULT;
3077 ed->nUndoMode = umAddToUndo;
3078 ed->nParagraphs = 1;
3079 ed->nLastSelStart = ed->nLastSelEnd = 0;
3080 ed->pLastSelStartPara = ed->pLastSelEndPara = ed->pCursors[0].pPara;
3081 ed->bHideSelection = FALSE;
3082 ed->pfnWordBreak = NULL;
3083 ed->lpOleCallback = NULL;
3084 ed->mode = TM_MULTILEVELUNDO | TM_MULTICODEPAGE;
3085 ed->mode |= (props & TXTBIT_RICHTEXT) ? TM_RICHTEXT : TM_PLAINTEXT;
3086 ed->AutoURLDetect_bEnable = FALSE;
3087 ed->bHaveFocus = FALSE;
3088 ed->bDialogMode = FALSE;
3089 ed->bMouseCaptured = FALSE;
3090 for (i=0; i<HFONT_CACHE_SIZE; i++)
3092 ed->pFontCache[i].nRefs = 0;
3093 ed->pFontCache[i].nAge = 0;
3094 ed->pFontCache[i].hFont = NULL;
3097 ME_CheckCharOffsets(ed);
3098 SetRectEmpty(&ed->rcFormat);
3099 ed->bDefaultFormatRect = TRUE;
3100 ITextHost_TxGetSelectionBarWidth(ed->texthost, &selbarwidth);
3101 if (selbarwidth) {
3102 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3103 ed->selofs = SELECTIONBAR_WIDTH;
3104 ed->styleFlags |= ES_SELECTIONBAR;
3105 } else {
3106 ed->selofs = 0;
3108 ed->nSelectionType = stPosition;
3110 ed->cPasswordMask = 0;
3111 if (props & TXTBIT_USEPASSWORD)
3112 ITextHost_TxGetPasswordChar(texthost, &ed->cPasswordMask);
3114 if (props & TXTBIT_AUTOWORDSEL)
3115 ed->styleFlags |= ECO_AUTOWORDSELECTION;
3116 if (props & TXTBIT_MULTILINE) {
3117 ed->styleFlags |= ES_MULTILINE;
3118 ed->bWordWrap = (props & TXTBIT_WORDWRAP) != 0;
3119 } else {
3120 ed->bWordWrap = FALSE;
3122 if (props & TXTBIT_READONLY)
3123 ed->styleFlags |= ES_READONLY;
3124 if (!(props & TXTBIT_HIDESELECTION))
3125 ed->styleFlags |= ES_NOHIDESEL;
3126 if (props & TXTBIT_SAVESELECTION)
3127 ed->styleFlags |= ES_SAVESEL;
3128 if (props & TXTBIT_VERTICAL)
3129 ed->styleFlags |= ES_VERTICAL;
3130 if (props & TXTBIT_DISABLEDRAG)
3131 ed->styleFlags |= ES_NOOLEDRAGDROP;
3133 ed->notified_cr.cpMin = ed->notified_cr.cpMax = 0;
3135 /* Default scrollbar information */
3136 ed->vert_si.cbSize = sizeof(SCROLLINFO);
3137 ed->vert_si.nMin = 0;
3138 ed->vert_si.nMax = 0;
3139 ed->vert_si.nPage = 0;
3140 ed->vert_si.nPos = 0;
3142 ed->horz_si.cbSize = sizeof(SCROLLINFO);
3143 ed->horz_si.nMin = 0;
3144 ed->horz_si.nMax = 0;
3145 ed->horz_si.nPage = 0;
3146 ed->horz_si.nPos = 0;
3148 ed->wheel_remain = 0;
3150 list_init( &ed->reobj_list );
3151 OleInitialize(NULL);
3153 return ed;
3156 void ME_DestroyEditor(ME_TextEditor *editor)
3158 ME_DisplayItem *pFirst = editor->pBuffer->pFirst;
3159 ME_DisplayItem *p = pFirst, *pNext = NULL;
3160 ME_Style *s, *cursor2;
3161 int i;
3163 ME_ClearTempStyle(editor);
3164 ME_EmptyUndoStack(editor);
3165 while(p) {
3166 pNext = p->next;
3167 ME_DestroyDisplayItem(p);
3168 p = pNext;
3171 LIST_FOR_EACH_ENTRY_SAFE( s, cursor2, &editor->style_list, ME_Style, entry )
3172 ME_DestroyStyle( s );
3174 ME_ReleaseStyle(editor->pBuffer->pDefaultStyle);
3175 for (i=0; i<HFONT_CACHE_SIZE; i++)
3177 if (editor->pFontCache[i].hFont)
3178 DeleteObject(editor->pFontCache[i].hFont);
3180 if (editor->rgbBackColor != -1)
3181 DeleteObject(editor->hbrBackground);
3182 if(editor->lpOleCallback)
3183 IRichEditOleCallback_Release(editor->lpOleCallback);
3184 ITextHost_Release(editor->texthost);
3185 if (editor->reOle)
3187 IRichEditOle_Release(editor->reOle);
3188 editor->reOle = NULL;
3190 OleUninitialize();
3192 heap_free(editor->pBuffer);
3193 heap_free(editor->pCursors);
3194 heap_free(editor);
3197 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
3199 TRACE("\n");
3200 switch (fdwReason)
3202 case DLL_PROCESS_ATTACH:
3203 DisableThreadLibraryCalls(hinstDLL);
3204 me_heap = HeapCreate (0, 0x10000, 0);
3205 if (!ME_RegisterEditorClass(hinstDLL)) return FALSE;
3206 hLeft = LoadCursorW(hinstDLL, MAKEINTRESOURCEW(OCR_REVERSE));
3207 LookupInit();
3208 break;
3210 case DLL_PROCESS_DETACH:
3211 if (lpvReserved) break;
3212 UnregisterClassW(RICHEDIT_CLASS20W, 0);
3213 UnregisterClassW(MSFTEDIT_CLASS, 0);
3214 UnregisterClassA(RICHEDIT_CLASS20A, 0);
3215 UnregisterClassA("RichEdit50A", 0);
3216 if (ME_ListBoxRegistered)
3217 UnregisterClassW(REListBox20W, 0);
3218 if (ME_ComboBoxRegistered)
3219 UnregisterClassW(REComboBox20W, 0);
3220 LookupCleanup();
3221 HeapDestroy (me_heap);
3222 release_typelib();
3223 break;
3225 return TRUE;
3228 static inline int get_default_line_height( ME_TextEditor *editor )
3230 int height = 0;
3232 if (editor->pBuffer && editor->pBuffer->pDefaultStyle)
3233 height = editor->pBuffer->pDefaultStyle->tm.tmHeight;
3234 if (height <= 0) height = 24;
3236 return height;
3239 static inline int calc_wheel_change( int *remain, int amount_per_click )
3241 int change = amount_per_click * (float)*remain / WHEEL_DELTA;
3242 *remain -= WHEEL_DELTA * change / amount_per_click;
3243 return change;
3246 static const char * const edit_messages[] = {
3247 "EM_GETSEL",
3248 "EM_SETSEL",
3249 "EM_GETRECT",
3250 "EM_SETRECT",
3251 "EM_SETRECTNP",
3252 "EM_SCROLL",
3253 "EM_LINESCROLL",
3254 "EM_SCROLLCARET",
3255 "EM_GETMODIFY",
3256 "EM_SETMODIFY",
3257 "EM_GETLINECOUNT",
3258 "EM_LINEINDEX",
3259 "EM_SETHANDLE",
3260 "EM_GETHANDLE",
3261 "EM_GETTHUMB",
3262 "EM_UNKNOWN_BF",
3263 "EM_UNKNOWN_C0",
3264 "EM_LINELENGTH",
3265 "EM_REPLACESEL",
3266 "EM_UNKNOWN_C3",
3267 "EM_GETLINE",
3268 "EM_LIMITTEXT",
3269 "EM_CANUNDO",
3270 "EM_UNDO",
3271 "EM_FMTLINES",
3272 "EM_LINEFROMCHAR",
3273 "EM_UNKNOWN_CA",
3274 "EM_SETTABSTOPS",
3275 "EM_SETPASSWORDCHAR",
3276 "EM_EMPTYUNDOBUFFER",
3277 "EM_GETFIRSTVISIBLELINE",
3278 "EM_SETREADONLY",
3279 "EM_SETWORDBREAKPROC",
3280 "EM_GETWORDBREAKPROC",
3281 "EM_GETPASSWORDCHAR",
3282 "EM_SETMARGINS",
3283 "EM_GETMARGINS",
3284 "EM_GETLIMITTEXT",
3285 "EM_POSFROMCHAR",
3286 "EM_CHARFROMPOS",
3287 "EM_SETIMESTATUS",
3288 "EM_GETIMESTATUS"
3291 static const char * const richedit_messages[] = {
3292 "EM_CANPASTE",
3293 "EM_DISPLAYBAND",
3294 "EM_EXGETSEL",
3295 "EM_EXLIMITTEXT",
3296 "EM_EXLINEFROMCHAR",
3297 "EM_EXSETSEL",
3298 "EM_FINDTEXT",
3299 "EM_FORMATRANGE",
3300 "EM_GETCHARFORMAT",
3301 "EM_GETEVENTMASK",
3302 "EM_GETOLEINTERFACE",
3303 "EM_GETPARAFORMAT",
3304 "EM_GETSELTEXT",
3305 "EM_HIDESELECTION",
3306 "EM_PASTESPECIAL",
3307 "EM_REQUESTRESIZE",
3308 "EM_SELECTIONTYPE",
3309 "EM_SETBKGNDCOLOR",
3310 "EM_SETCHARFORMAT",
3311 "EM_SETEVENTMASK",
3312 "EM_SETOLECALLBACK",
3313 "EM_SETPARAFORMAT",
3314 "EM_SETTARGETDEVICE",
3315 "EM_STREAMIN",
3316 "EM_STREAMOUT",
3317 "EM_GETTEXTRANGE",
3318 "EM_FINDWORDBREAK",
3319 "EM_SETOPTIONS",
3320 "EM_GETOPTIONS",
3321 "EM_FINDTEXTEX",
3322 "EM_GETWORDBREAKPROCEX",
3323 "EM_SETWORDBREAKPROCEX",
3324 "EM_SETUNDOLIMIT",
3325 "EM_UNKNOWN_USER_83",
3326 "EM_REDO",
3327 "EM_CANREDO",
3328 "EM_GETUNDONAME",
3329 "EM_GETREDONAME",
3330 "EM_STOPGROUPTYPING",
3331 "EM_SETTEXTMODE",
3332 "EM_GETTEXTMODE",
3333 "EM_AUTOURLDETECT",
3334 "EM_GETAUTOURLDETECT",
3335 "EM_SETPALETTE",
3336 "EM_GETTEXTEX",
3337 "EM_GETTEXTLENGTHEX",
3338 "EM_SHOWSCROLLBAR",
3339 "EM_SETTEXTEX",
3340 "EM_UNKNOWN_USER_98",
3341 "EM_UNKNOWN_USER_99",
3342 "EM_SETPUNCTUATION",
3343 "EM_GETPUNCTUATION",
3344 "EM_SETWORDWRAPMODE",
3345 "EM_GETWORDWRAPMODE",
3346 "EM_SETIMECOLOR",
3347 "EM_GETIMECOLOR",
3348 "EM_SETIMEOPTIONS",
3349 "EM_GETIMEOPTIONS",
3350 "EM_CONVPOSITION",
3351 "EM_UNKNOWN_USER_109",
3352 "EM_UNKNOWN_USER_110",
3353 "EM_UNKNOWN_USER_111",
3354 "EM_UNKNOWN_USER_112",
3355 "EM_UNKNOWN_USER_113",
3356 "EM_UNKNOWN_USER_114",
3357 "EM_UNKNOWN_USER_115",
3358 "EM_UNKNOWN_USER_116",
3359 "EM_UNKNOWN_USER_117",
3360 "EM_UNKNOWN_USER_118",
3361 "EM_UNKNOWN_USER_119",
3362 "EM_SETLANGOPTIONS",
3363 "EM_GETLANGOPTIONS",
3364 "EM_GETIMECOMPMODE",
3365 "EM_FINDTEXTW",
3366 "EM_FINDTEXTEXW",
3367 "EM_RECONVERSION",
3368 "EM_SETIMEMODEBIAS",
3369 "EM_GETIMEMODEBIAS"
3372 static const char *
3373 get_msg_name(UINT msg)
3375 if (msg >= EM_GETSEL && msg <= EM_CHARFROMPOS)
3376 return edit_messages[msg - EM_GETSEL];
3377 if (msg >= EM_CANPASTE && msg <= EM_GETIMEMODEBIAS)
3378 return richedit_messages[msg - EM_CANPASTE];
3379 return "";
3382 static void ME_LinkNotify(ME_TextEditor *editor, UINT msg, WPARAM wParam, LPARAM lParam)
3384 int x,y;
3385 BOOL isExact;
3386 ME_Cursor cursor; /* The start of the clicked text. */
3388 ENLINK info;
3389 x = (short)LOWORD(lParam);
3390 y = (short)HIWORD(lParam);
3391 ME_CharFromPos(editor, x, y, &cursor, &isExact);
3392 if (!isExact) return;
3394 if (is_link( &cursor.pRun->member.run ))
3395 { /* The clicked run has CFE_LINK set */
3396 ME_DisplayItem *di;
3398 info.nmhdr.hwndFrom = NULL;
3399 info.nmhdr.idFrom = 0;
3400 info.nmhdr.code = EN_LINK;
3401 info.msg = msg;
3402 info.wParam = wParam;
3403 info.lParam = lParam;
3404 cursor.nOffset = 0;
3406 /* find the first contiguous run with CFE_LINK set */
3407 info.chrg.cpMin = ME_GetCursorOfs(&cursor);
3408 di = cursor.pRun;
3409 while (ME_PrevRun( NULL, &di, FALSE ) && is_link( &di->member.run ))
3410 info.chrg.cpMin -= di->member.run.len;
3412 /* find the last contiguous run with CFE_LINK set */
3413 info.chrg.cpMax = ME_GetCursorOfs(&cursor) + cursor.pRun->member.run.len;
3414 di = cursor.pRun;
3415 while (ME_NextRun( NULL, &di, FALSE ) && is_link( &di->member.run ))
3416 info.chrg.cpMax += di->member.run.len;
3418 ITextHost_TxNotify(editor->texthost, info.nmhdr.code, &info);
3422 void ME_ReplaceSel(ME_TextEditor *editor, BOOL can_undo, const WCHAR *str, int len)
3424 int from, to, nStartCursor;
3425 ME_Style *style;
3427 nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3428 style = ME_GetSelectionInsertStyle(editor);
3429 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3430 ME_InsertTextFromCursor(editor, 0, str, len, style);
3431 ME_ReleaseStyle(style);
3432 /* drop temporary style if line end */
3434 * FIXME question: does abc\n mean: put abc,
3435 * clear temp style, put \n? (would require a change)
3437 if (len>0 && str[len-1] == '\n')
3438 ME_ClearTempStyle(editor);
3439 ME_CommitUndo(editor);
3440 ME_UpdateSelectionLinkAttribute(editor);
3441 if (!can_undo)
3442 ME_EmptyUndoStack(editor);
3443 ME_UpdateRepaint(editor, FALSE);
3446 static void ME_SetText(ME_TextEditor *editor, void *text, BOOL unicode)
3448 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
3449 int textLen;
3451 LPWSTR wszText = ME_ToUnicode(codepage, text, &textLen);
3452 ME_InsertTextFromCursor(editor, 0, wszText, textLen, editor->pBuffer->pDefaultStyle);
3453 ME_EndToUnicode(codepage, wszText);
3456 static LRESULT ME_WmCreate(ME_TextEditor *editor, LPARAM lParam, BOOL unicode)
3458 CREATESTRUCTW *createW = (CREATESTRUCTW*)lParam;
3459 CREATESTRUCTA *createA = (CREATESTRUCTA*)lParam;
3460 void *text = NULL;
3461 INT max;
3463 if (lParam)
3464 text = unicode ? (void*)createW->lpszName : (void*)createA->lpszName;
3466 ME_SetDefaultFormatRect(editor);
3468 max = (editor->styleFlags & ES_DISABLENOSCROLL) ? 1 : 0;
3469 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_VSCROLL)
3470 ITextHost_TxSetScrollRange(editor->texthost, SB_VERT, 0, max, TRUE);
3472 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_HSCROLL)
3473 ITextHost_TxSetScrollRange(editor->texthost, SB_HORZ, 0, max, TRUE);
3475 if (editor->styleFlags & ES_DISABLENOSCROLL)
3477 if (editor->styleFlags & WS_VSCROLL)
3479 ITextHost_TxEnableScrollBar(editor->texthost, SB_VERT, ESB_DISABLE_BOTH);
3480 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT, TRUE);
3482 if (editor->styleFlags & WS_HSCROLL)
3484 ITextHost_TxEnableScrollBar(editor->texthost, SB_HORZ, ESB_DISABLE_BOTH);
3485 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ, TRUE);
3489 if (text)
3491 ME_SetText(editor, text, unicode);
3492 ME_SetCursorToStart(editor, &editor->pCursors[0]);
3493 ME_SetCursorToStart(editor, &editor->pCursors[1]);
3496 ME_CommitUndo(editor);
3497 ME_WrapMarkedParagraphs(editor);
3498 ME_MoveCaret(editor);
3499 return 0;
3503 #define UNSUPPORTED_MSG(e) \
3504 case e: \
3505 FIXME(#e ": stub\n"); \
3506 *phresult = S_FALSE; \
3507 return 0;
3509 /* Handle messages for windowless and windowed richedit controls.
3511 * The LRESULT that is returned is a return value for window procs,
3512 * and the phresult parameter is the COM return code needed by the
3513 * text services interface. */
3514 LRESULT ME_HandleMessage(ME_TextEditor *editor, UINT msg, WPARAM wParam,
3515 LPARAM lParam, BOOL unicode, HRESULT* phresult)
3517 *phresult = S_OK;
3519 switch(msg) {
3521 UNSUPPORTED_MSG(EM_DISPLAYBAND)
3522 UNSUPPORTED_MSG(EM_FINDWORDBREAK)
3523 UNSUPPORTED_MSG(EM_FMTLINES)
3524 UNSUPPORTED_MSG(EM_FORMATRANGE)
3525 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS)
3526 UNSUPPORTED_MSG(EM_GETEDITSTYLE)
3527 UNSUPPORTED_MSG(EM_GETIMECOMPMODE)
3528 UNSUPPORTED_MSG(EM_GETIMESTATUS)
3529 UNSUPPORTED_MSG(EM_SETIMESTATUS)
3530 UNSUPPORTED_MSG(EM_GETLANGOPTIONS)
3531 UNSUPPORTED_MSG(EM_GETREDONAME)
3532 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS)
3533 UNSUPPORTED_MSG(EM_GETUNDONAME)
3534 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX)
3535 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS)
3536 UNSUPPORTED_MSG(EM_SETEDITSTYLE)
3537 UNSUPPORTED_MSG(EM_SETLANGOPTIONS)
3538 UNSUPPORTED_MSG(EM_SETMARGINS)
3539 UNSUPPORTED_MSG(EM_SETPALETTE)
3540 UNSUPPORTED_MSG(EM_SETTABSTOPS)
3541 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS)
3542 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX)
3544 /* Messages specific to Richedit controls */
3546 case EM_STREAMIN:
3547 return ME_StreamIn(editor, wParam, (EDITSTREAM*)lParam, TRUE);
3548 case EM_STREAMOUT:
3549 return ME_StreamOut(editor, wParam, (EDITSTREAM *)lParam);
3550 case WM_GETDLGCODE:
3552 UINT code = DLGC_WANTCHARS|DLGC_WANTTAB|DLGC_WANTARROWS;
3554 if (lParam)
3555 editor->bDialogMode = TRUE;
3556 if (editor->styleFlags & ES_MULTILINE)
3557 code |= DLGC_WANTMESSAGE;
3558 if (!(editor->styleFlags & ES_SAVESEL))
3559 code |= DLGC_HASSETSEL;
3560 return code;
3562 case EM_EMPTYUNDOBUFFER:
3563 ME_EmptyUndoStack(editor);
3564 return 0;
3565 case EM_GETSEL:
3567 /* Note: wParam/lParam can be NULL */
3568 UINT from, to;
3569 PUINT pfrom = wParam ? (PUINT)wParam : &from;
3570 PUINT pto = lParam ? (PUINT)lParam : &to;
3571 ME_GetSelectionOfs(editor, (int *)pfrom, (int *)pto);
3572 if ((*pfrom|*pto) & 0xFFFF0000)
3573 return -1;
3574 return MAKELONG(*pfrom,*pto);
3576 case EM_EXGETSEL:
3578 CHARRANGE *pRange = (CHARRANGE *)lParam;
3579 ME_GetSelectionOfs(editor, &pRange->cpMin, &pRange->cpMax);
3580 TRACE("EM_EXGETSEL = (%d,%d)\n", pRange->cpMin, pRange->cpMax);
3581 return 0;
3583 case EM_SETUNDOLIMIT:
3585 if ((int)wParam < 0)
3586 editor->nUndoLimit = STACK_SIZE_DEFAULT;
3587 else
3588 editor->nUndoLimit = min(wParam, STACK_SIZE_MAX);
3589 /* Setting a max stack size keeps wine from getting killed
3590 for hogging memory. Windows allocates all this memory at once, so
3591 no program would realistically set a value above our maximum. */
3592 return editor->nUndoLimit;
3594 case EM_CANUNDO:
3595 return !list_empty( &editor->undo_stack );
3596 case EM_CANREDO:
3597 return !list_empty( &editor->redo_stack );
3598 case WM_UNDO: /* FIXME: actually not the same */
3599 case EM_UNDO:
3600 return ME_Undo(editor);
3601 case EM_REDO:
3602 return ME_Redo(editor);
3603 case EM_GETOPTIONS:
3605 /* these flags are equivalent to the ES_* counterparts */
3606 DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3607 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN | ECO_SELECTIONBAR;
3608 DWORD settings = editor->styleFlags & mask;
3610 return settings;
3612 case EM_SETFONTSIZE:
3614 CHARFORMAT2W cf;
3615 LONG tmp_size, size;
3616 BOOL is_increase = ((LONG)wParam > 0);
3618 if (editor->mode & TM_PLAINTEXT)
3619 return FALSE;
3621 cf.cbSize = sizeof(cf);
3622 cf.dwMask = CFM_SIZE;
3623 ME_GetSelectionCharFormat(editor, &cf);
3624 tmp_size = (cf.yHeight / 20) + wParam;
3626 if (tmp_size <= 1)
3627 size = 1;
3628 else if (tmp_size > 12 && tmp_size < 28 && tmp_size % 2)
3629 size = tmp_size + (is_increase ? 1 : -1);
3630 else if (tmp_size > 28 && tmp_size < 36)
3631 size = is_increase ? 36 : 28;
3632 else if (tmp_size > 36 && tmp_size < 48)
3633 size = is_increase ? 48 : 36;
3634 else if (tmp_size > 48 && tmp_size < 72)
3635 size = is_increase ? 72 : 48;
3636 else if (tmp_size > 72 && tmp_size < 80)
3637 size = is_increase ? 80 : 72;
3638 else if (tmp_size > 80 && tmp_size < 1638)
3639 size = 10 * (is_increase ? (tmp_size / 10 + 1) : (tmp_size / 10));
3640 else if (tmp_size >= 1638)
3641 size = 1638;
3642 else
3643 size = tmp_size;
3645 cf.yHeight = size * 20; /* convert twips to points */
3646 ME_SetSelectionCharFormat(editor, &cf);
3647 ME_CommitUndo(editor);
3648 ME_WrapMarkedParagraphs(editor);
3649 ME_UpdateScrollBar(editor);
3650 ME_Repaint(editor);
3652 return TRUE;
3654 case EM_SETOPTIONS:
3656 /* these flags are equivalent to ES_* counterparts, except for
3657 * ECO_AUTOWORDSELECTION that doesn't have an ES_* counterpart,
3658 * but is still stored in editor->styleFlags. */
3659 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3660 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
3661 ECO_SELECTIONBAR | ECO_AUTOWORDSELECTION;
3662 DWORD settings = mask & editor->styleFlags;
3663 DWORD oldSettings = settings;
3664 DWORD changedSettings;
3666 switch(wParam)
3668 case ECOOP_SET:
3669 settings = lParam;
3670 break;
3671 case ECOOP_OR:
3672 settings |= lParam;
3673 break;
3674 case ECOOP_AND:
3675 settings &= lParam;
3676 break;
3677 case ECOOP_XOR:
3678 settings ^= lParam;
3680 changedSettings = oldSettings ^ settings;
3682 if (changedSettings) {
3683 editor->styleFlags = (editor->styleFlags & ~mask) | (settings & mask);
3685 if (changedSettings & ECO_SELECTIONBAR)
3687 ITextHost_TxInvalidateRect(editor->texthost, &editor->rcFormat, TRUE);
3688 if (settings & ECO_SELECTIONBAR) {
3689 assert(!editor->selofs);
3690 editor->selofs = SELECTIONBAR_WIDTH;
3691 editor->rcFormat.left += editor->selofs;
3692 } else {
3693 editor->rcFormat.left -= editor->selofs;
3694 editor->selofs = 0;
3696 ME_RewrapRepaint(editor);
3699 if ((changedSettings & settings & ES_NOHIDESEL) && !editor->bHaveFocus)
3700 ME_InvalidateSelection( editor );
3702 if (changedSettings & settings & ECO_VERTICAL)
3703 FIXME("ECO_VERTICAL not implemented yet!\n");
3704 if (changedSettings & settings & ECO_AUTOHSCROLL)
3705 FIXME("ECO_AUTOHSCROLL not implemented yet!\n");
3706 if (changedSettings & settings & ECO_AUTOVSCROLL)
3707 FIXME("ECO_AUTOVSCROLL not implemented yet!\n");
3708 if (changedSettings & settings & ECO_WANTRETURN)
3709 FIXME("ECO_WANTRETURN not implemented yet!\n");
3710 if (changedSettings & settings & ECO_AUTOWORDSELECTION)
3711 FIXME("ECO_AUTOWORDSELECTION not implemented yet!\n");
3714 return settings;
3716 case EM_SETSEL:
3718 return handle_EM_EXSETSEL( editor, wParam, lParam );
3720 case EM_SETSCROLLPOS:
3722 POINT *point = (POINT *)lParam;
3723 ME_ScrollAbs(editor, point->x, point->y);
3724 return 0;
3726 case EM_AUTOURLDETECT:
3728 if (wParam==1 || wParam ==0)
3730 editor->AutoURLDetect_bEnable = (BOOL)wParam;
3731 return 0;
3733 return E_INVALIDARG;
3735 case EM_GETAUTOURLDETECT:
3737 return editor->AutoURLDetect_bEnable;
3739 case EM_EXSETSEL:
3741 CHARRANGE range = *(CHARRANGE *)lParam;
3743 return handle_EM_EXSETSEL( editor, range.cpMin, range.cpMax );
3745 case EM_SHOWSCROLLBAR:
3747 DWORD flags;
3749 switch (wParam)
3751 case SB_HORZ:
3752 flags = WS_HSCROLL;
3753 break;
3754 case SB_VERT:
3755 flags = WS_VSCROLL;
3756 break;
3757 case SB_BOTH:
3758 flags = WS_HSCROLL|WS_VSCROLL;
3759 break;
3760 default:
3761 return 0;
3764 if (lParam) {
3765 editor->styleFlags |= flags;
3766 if (flags & WS_HSCROLL)
3767 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ,
3768 editor->nTotalWidth > editor->sizeWindow.cx);
3769 if (flags & WS_VSCROLL)
3770 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT,
3771 editor->nTotalLength > editor->sizeWindow.cy);
3772 } else {
3773 editor->styleFlags &= ~flags;
3774 ITextHost_TxShowScrollBar(editor->texthost, wParam, FALSE);
3776 return 0;
3778 case EM_SETTEXTEX:
3780 LPWSTR wszText;
3781 SETTEXTEX *pStruct = (SETTEXTEX *)wParam;
3782 int from, to, len;
3783 ME_Style *style;
3784 BOOL bRtf, bUnicode, bSelection, bUTF8;
3785 int oldModify = editor->nModifyStep;
3786 static const char utf8_bom[] = {0xef, 0xbb, 0xbf};
3788 if (!pStruct) return 0;
3790 /* If we detect ascii rtf at the start of the string,
3791 * we know it isn't unicode. */
3792 bRtf = (lParam && (!strncmp((char *)lParam, "{\\rtf", 5) ||
3793 !strncmp((char *)lParam, "{\\urtf", 6)));
3794 bUnicode = !bRtf && pStruct->codepage == CP_UNICODE;
3795 bUTF8 = (lParam && (!strncmp((char *)lParam, utf8_bom, 3)));
3797 TRACE("EM_SETTEXTEX - %s, flags %d, cp %d\n",
3798 bUnicode ? debugstr_w((LPCWSTR)lParam) : debugstr_a((LPCSTR)lParam),
3799 pStruct->flags, pStruct->codepage);
3801 bSelection = (pStruct->flags & ST_SELECTION) != 0;
3802 if (bSelection) {
3803 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3804 style = ME_GetSelectionInsertStyle(editor);
3805 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to - from, FALSE);
3806 } else {
3807 ME_Cursor start;
3808 ME_SetCursorToStart(editor, &start);
3809 ME_InternalDeleteText(editor, &start, ME_GetTextLength(editor), FALSE);
3810 style = editor->pBuffer->pDefaultStyle;
3813 if (bRtf) {
3814 ME_StreamInRTFString(editor, bSelection, (char *)lParam);
3815 if (bSelection) {
3816 /* FIXME: The length returned doesn't include the rtf control
3817 * characters, only the actual text. */
3818 len = lParam ? strlen((char *)lParam) : 0;
3820 } else {
3821 if (bUTF8 && !bUnicode) {
3822 wszText = ME_ToUnicode(CP_UTF8, (void *)(lParam+3), &len);
3823 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3824 ME_EndToUnicode(CP_UTF8, wszText);
3825 } else {
3826 wszText = ME_ToUnicode(pStruct->codepage, (void *)lParam, &len);
3827 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3828 ME_EndToUnicode(pStruct->codepage, wszText);
3832 if (bSelection) {
3833 ME_ReleaseStyle(style);
3834 ME_UpdateSelectionLinkAttribute(editor);
3835 } else {
3836 ME_Cursor cursor;
3837 len = 1;
3838 ME_SetCursorToStart(editor, &cursor);
3839 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3841 ME_CommitUndo(editor);
3842 if (!(pStruct->flags & ST_KEEPUNDO))
3844 editor->nModifyStep = oldModify;
3845 ME_EmptyUndoStack(editor);
3847 ME_UpdateRepaint(editor, FALSE);
3848 return len;
3850 case EM_SELECTIONTYPE:
3851 return ME_GetSelectionType(editor);
3852 case EM_SETBKGNDCOLOR:
3854 LRESULT lColor;
3855 if (editor->rgbBackColor != -1) {
3856 DeleteObject(editor->hbrBackground);
3857 lColor = editor->rgbBackColor;
3859 else lColor = ITextHost_TxGetSysColor(editor->texthost, COLOR_WINDOW);
3861 if (wParam)
3863 editor->rgbBackColor = -1;
3864 editor->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3866 else
3868 editor->rgbBackColor = lParam;
3869 editor->hbrBackground = CreateSolidBrush(editor->rgbBackColor);
3871 ITextHost_TxInvalidateRect(editor->texthost, NULL, TRUE);
3872 return lColor;
3874 case EM_GETMODIFY:
3875 return editor->nModifyStep == 0 ? 0 : -1;
3876 case EM_SETMODIFY:
3878 if (wParam)
3879 editor->nModifyStep = 1;
3880 else
3881 editor->nModifyStep = 0;
3883 return 0;
3885 case EM_SETREADONLY:
3887 if (wParam)
3888 editor->styleFlags |= ES_READONLY;
3889 else
3890 editor->styleFlags &= ~ES_READONLY;
3891 return 1;
3893 case EM_SETEVENTMASK:
3895 DWORD nOldMask = editor->nEventMask;
3897 editor->nEventMask = lParam;
3898 return nOldMask;
3900 case EM_GETEVENTMASK:
3901 return editor->nEventMask;
3902 case EM_SETCHARFORMAT:
3904 CHARFORMAT2W p;
3905 BOOL bRepaint = TRUE;
3906 if (!cfany_to_cf2w(&p, (CHARFORMAT2W *)lParam))
3907 return 0;
3908 if (wParam & SCF_ALL) {
3909 if (editor->mode & TM_PLAINTEXT) {
3910 ME_SetDefaultCharFormat(editor, &p);
3911 } else {
3912 ME_Cursor start;
3913 ME_SetCursorToStart(editor, &start);
3914 ME_SetCharFormat(editor, &start, NULL, &p);
3915 editor->nModifyStep = 1;
3917 } else if (wParam & SCF_SELECTION) {
3918 if (editor->mode & TM_PLAINTEXT)
3919 return 0;
3920 if (wParam & SCF_WORD) {
3921 ME_Cursor start;
3922 ME_Cursor end = editor->pCursors[0];
3923 ME_MoveCursorWords(editor, &end, +1);
3924 start = end;
3925 ME_MoveCursorWords(editor, &start, -1);
3926 ME_SetCharFormat(editor, &start, &end, &p);
3928 bRepaint = ME_IsSelection(editor);
3929 ME_SetSelectionCharFormat(editor, &p);
3930 if (bRepaint) editor->nModifyStep = 1;
3931 } else { /* SCF_DEFAULT */
3932 ME_SetDefaultCharFormat(editor, &p);
3934 ME_CommitUndo(editor);
3935 if (bRepaint)
3937 ME_WrapMarkedParagraphs(editor);
3938 ME_UpdateScrollBar(editor);
3939 ME_Repaint(editor);
3941 return 1;
3943 case EM_GETCHARFORMAT:
3945 CHARFORMAT2W tmp, *dst = (CHARFORMAT2W *)lParam;
3946 if (dst->cbSize != sizeof(CHARFORMATA) &&
3947 dst->cbSize != sizeof(CHARFORMATW) &&
3948 dst->cbSize != sizeof(CHARFORMAT2A) &&
3949 dst->cbSize != sizeof(CHARFORMAT2W))
3950 return 0;
3951 tmp.cbSize = sizeof(tmp);
3952 if (!wParam)
3953 ME_GetDefaultCharFormat(editor, &tmp);
3954 else
3955 ME_GetSelectionCharFormat(editor, &tmp);
3956 cf2w_to_cfany(dst, &tmp);
3957 return tmp.dwMask;
3959 case EM_SETPARAFORMAT:
3961 BOOL result = ME_SetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
3962 ME_WrapMarkedParagraphs(editor);
3963 ME_UpdateScrollBar(editor);
3964 ME_Repaint(editor);
3965 ME_CommitUndo(editor);
3966 return result;
3968 case EM_GETPARAFORMAT:
3969 ME_GetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
3970 return ((PARAFORMAT2 *)lParam)->dwMask;
3971 case EM_GETFIRSTVISIBLELINE:
3973 ME_DisplayItem *p = editor->pBuffer->pFirst;
3974 int y = editor->vert_si.nPos;
3975 int ypara = 0;
3976 int count = 0;
3977 int ystart, yend;
3978 while(p) {
3979 p = ME_FindItemFwd(p, diStartRowOrParagraphOrEnd);
3980 if (p->type == diTextEnd)
3981 break;
3982 if (p->type == diParagraph) {
3983 ypara = p->member.para.pt.y;
3984 continue;
3986 ystart = ypara + p->member.row.pt.y;
3987 yend = ystart + p->member.row.nHeight;
3988 if (y < yend) {
3989 break;
3991 count++;
3993 return count;
3995 case EM_HIDESELECTION:
3997 editor->bHideSelection = (wParam != 0);
3998 ME_InvalidateSelection(editor);
3999 return 0;
4001 case EM_LINESCROLL:
4003 if (!(editor->styleFlags & ES_MULTILINE))
4004 return FALSE;
4005 ME_ScrollDown( editor, lParam * get_default_line_height( editor ) );
4006 return TRUE;
4008 case WM_CLEAR:
4010 int from, to;
4011 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
4012 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
4013 ME_CommitUndo(editor);
4014 ME_UpdateRepaint(editor, TRUE);
4015 return 0;
4017 case EM_REPLACESEL:
4019 int len = 0;
4020 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
4021 LPWSTR wszText = ME_ToUnicode(codepage, (void *)lParam, &len);
4023 TRACE("EM_REPLACESEL - %s\n", debugstr_w(wszText));
4025 ME_ReplaceSel(editor, !!wParam, wszText, len);
4026 ME_EndToUnicode(codepage, wszText);
4027 return len;
4029 case EM_SCROLLCARET:
4030 ME_EnsureVisible(editor, &editor->pCursors[0]);
4031 return 0;
4032 case WM_SETFONT:
4034 LOGFONTW lf;
4035 CHARFORMAT2W fmt;
4036 HDC hDC;
4037 BOOL bRepaint = LOWORD(lParam);
4039 if (!wParam)
4040 wParam = (WPARAM)GetStockObject(SYSTEM_FONT);
4042 if (!GetObjectW((HGDIOBJ)wParam, sizeof(LOGFONTW), &lf))
4043 return 0;
4045 hDC = ITextHost_TxGetDC(editor->texthost);
4046 ME_CharFormatFromLogFont(hDC, &lf, &fmt);
4047 ITextHost_TxReleaseDC(editor->texthost, hDC);
4048 if (editor->mode & TM_RICHTEXT) {
4049 ME_Cursor start;
4050 ME_SetCursorToStart(editor, &start);
4051 ME_SetCharFormat(editor, &start, NULL, &fmt);
4053 ME_SetDefaultCharFormat(editor, &fmt);
4055 ME_CommitUndo(editor);
4056 ME_MarkAllForWrapping(editor);
4057 ME_WrapMarkedParagraphs(editor);
4058 ME_UpdateScrollBar(editor);
4059 if (bRepaint)
4060 ME_Repaint(editor);
4061 return 0;
4063 case WM_SETTEXT:
4065 ME_Cursor cursor;
4066 ME_SetCursorToStart(editor, &cursor);
4067 ME_InternalDeleteText(editor, &cursor, ME_GetTextLength(editor), FALSE);
4068 if (lParam)
4070 TRACE("WM_SETTEXT lParam==%lx\n",lParam);
4071 if (!strncmp((char *)lParam, "{\\rtf", 5) ||
4072 !strncmp((char *)lParam, "{\\urtf", 6))
4074 /* Undocumented: WM_SETTEXT supports RTF text */
4075 ME_StreamInRTFString(editor, 0, (char *)lParam);
4077 else
4078 ME_SetText(editor, (void*)lParam, unicode);
4080 else
4081 TRACE("WM_SETTEXT - NULL\n");
4082 ME_SetCursorToStart(editor, &cursor);
4083 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
4084 ME_SetSelection(editor, 0, 0);
4085 editor->nModifyStep = 0;
4086 ME_CommitUndo(editor);
4087 ME_EmptyUndoStack(editor);
4088 ME_UpdateRepaint(editor, FALSE);
4089 return 1;
4091 case EM_CANPASTE:
4092 return paste_special( editor, 0, NULL, TRUE );
4093 case WM_PASTE:
4094 case WM_MBUTTONDOWN:
4095 wParam = 0;
4096 lParam = 0;
4097 /* fall through */
4098 case EM_PASTESPECIAL:
4099 paste_special( editor, wParam, (REPASTESPECIAL *)lParam, FALSE );
4100 return 0;
4101 case WM_CUT:
4102 case WM_COPY:
4103 copy_or_cut(editor, msg == WM_CUT);
4104 return 0;
4105 case WM_GETTEXTLENGTH:
4107 GETTEXTLENGTHEX how;
4109 /* CR/LF conversion required in 2.0 mode, verbatim in 1.0 mode */
4110 how.flags = GTL_CLOSE | (editor->bEmulateVersion10 ? 0 : GTL_USECRLF) | GTL_NUMCHARS;
4111 how.codepage = unicode ? CP_UNICODE : CP_ACP;
4112 return ME_GetTextLengthEx(editor, &how);
4114 case EM_GETTEXTLENGTHEX:
4115 return ME_GetTextLengthEx(editor, (GETTEXTLENGTHEX *)wParam);
4116 case WM_GETTEXT:
4118 GETTEXTEX ex;
4119 ex.cb = wParam * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
4120 ex.flags = GT_USECRLF;
4121 ex.codepage = unicode ? CP_UNICODE : CP_ACP;
4122 ex.lpDefaultChar = NULL;
4123 ex.lpUsedDefChar = NULL;
4124 return ME_GetTextEx(editor, &ex, lParam);
4126 case EM_GETTEXTEX:
4127 return ME_GetTextEx(editor, (GETTEXTEX*)wParam, lParam);
4128 case EM_GETSELTEXT:
4130 int nFrom, nTo, nStartCur = ME_GetSelectionOfs(editor, &nFrom, &nTo);
4131 ME_Cursor *from = &editor->pCursors[nStartCur];
4132 return ME_GetTextRange(editor, (WCHAR *)lParam, from,
4133 nTo - nFrom, unicode);
4135 case EM_GETSCROLLPOS:
4137 POINT *point = (POINT *)lParam;
4138 point->x = editor->horz_si.nPos;
4139 point->y = editor->vert_si.nPos;
4140 /* 16-bit scaled value is returned as stored in scrollinfo */
4141 if (editor->horz_si.nMax > 0xffff)
4142 point->x = MulDiv(point->x, 0xffff, editor->horz_si.nMax);
4143 if (editor->vert_si.nMax > 0xffff)
4144 point->y = MulDiv(point->y, 0xffff, editor->vert_si.nMax);
4145 return 1;
4147 case EM_GETTEXTRANGE:
4149 TEXTRANGEW *rng = (TEXTRANGEW *)lParam;
4150 ME_Cursor start;
4151 int nStart = rng->chrg.cpMin;
4152 int nEnd = rng->chrg.cpMax;
4153 int textlength = ME_GetTextLength(editor);
4155 TRACE("EM_GETTEXTRANGE min=%d max=%d unicode=%d textlength=%d\n",
4156 rng->chrg.cpMin, rng->chrg.cpMax, unicode, textlength);
4157 if (nStart < 0) return 0;
4158 if ((nStart == 0 && nEnd == -1) || nEnd > textlength)
4159 nEnd = textlength;
4160 if (nStart >= nEnd) return 0;
4162 ME_CursorFromCharOfs(editor, nStart, &start);
4163 return ME_GetTextRange(editor, rng->lpstrText, &start, nEnd - nStart, unicode);
4165 case EM_GETLINE:
4167 ME_DisplayItem *run;
4168 const unsigned int nMaxChars = *(WORD *) lParam;
4169 unsigned int nCharsLeft = nMaxChars;
4170 char *dest = (char *) lParam;
4171 BOOL wroteNull = FALSE;
4173 TRACE("EM_GETLINE: row=%d, nMaxChars=%d (%s)\n", (int) wParam, nMaxChars,
4174 unicode ? "Unicode" : "Ansi");
4176 run = ME_FindRowWithNumber(editor, wParam);
4177 if (run == NULL)
4178 return 0;
4180 while (nCharsLeft && (run = ME_FindItemFwd(run, diRunOrStartRow))
4181 && run->type == diRun)
4183 WCHAR *str = get_text( &run->member.run, 0 );
4184 unsigned int nCopy;
4186 nCopy = min(nCharsLeft, run->member.run.len);
4188 if (unicode)
4189 memcpy(dest, str, nCopy * sizeof(WCHAR));
4190 else
4191 nCopy = WideCharToMultiByte(CP_ACP, 0, str, nCopy, dest,
4192 nCharsLeft, NULL, NULL);
4193 dest += nCopy * (unicode ? sizeof(WCHAR) : 1);
4194 nCharsLeft -= nCopy;
4197 /* append line termination, space allowing */
4198 if (nCharsLeft > 0)
4200 if (unicode)
4201 *((WCHAR *)dest) = '\0';
4202 else
4203 *dest = '\0';
4204 nCharsLeft--;
4205 wroteNull = TRUE;
4208 TRACE("EM_GETLINE: got %u characters\n", nMaxChars - nCharsLeft);
4209 return nMaxChars - nCharsLeft - (wroteNull ? 1 : 0);
4211 case EM_GETLINECOUNT:
4213 ME_DisplayItem *item = editor->pBuffer->pFirst->next;
4214 int nRows = 0;
4216 ME_DisplayItem *prev_para = NULL, *last_para = NULL;
4218 while (item != editor->pBuffer->pLast)
4220 assert(item->type == diParagraph);
4221 prev_para = ME_FindItemBack(item, diRun);
4222 if (prev_para) {
4223 assert(prev_para->member.run.nFlags & MERF_ENDPARA);
4225 nRows += item->member.para.nRows;
4226 item = item->member.para.next_para;
4228 last_para = ME_FindItemBack(item, diRun);
4229 assert(last_para);
4230 assert(last_para->member.run.nFlags & MERF_ENDPARA);
4231 if (editor->bEmulateVersion10 && prev_para &&
4232 last_para->member.run.nCharOfs == 0 &&
4233 prev_para->member.run.len == 1 &&
4234 *get_text( &prev_para->member.run, 0 ) == '\r')
4236 /* In 1.0 emulation, the last solitary \r at the very end of the text
4237 (if one exists) is NOT a line break.
4238 FIXME: this is an ugly hack. This should have a more regular model. */
4239 nRows--;
4242 TRACE("EM_GETLINECOUNT: nRows==%d\n", nRows);
4243 return max(1, nRows);
4245 case EM_LINEFROMCHAR:
4247 if (wParam == -1)
4248 return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(&editor->pCursors[1]));
4249 else
4250 return ME_RowNumberFromCharOfs(editor, wParam);
4252 case EM_EXLINEFROMCHAR:
4254 if (lParam == -1)
4255 return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(&editor->pCursors[1]));
4256 else
4257 return ME_RowNumberFromCharOfs(editor, lParam);
4259 case EM_LINEINDEX:
4261 ME_DisplayItem *item, *para;
4262 int nCharOfs;
4264 if (wParam == -1)
4265 item = ME_FindItemBack(editor->pCursors[0].pRun, diStartRow);
4266 else
4267 item = ME_FindRowWithNumber(editor, wParam);
4268 if (!item)
4269 return -1;
4270 para = ME_GetParagraph(item);
4271 item = ME_FindItemFwd(item, diRun);
4272 nCharOfs = para->member.para.nCharOfs + item->member.run.nCharOfs;
4273 TRACE("EM_LINEINDEX: nCharOfs==%d\n", nCharOfs);
4274 return nCharOfs;
4276 case EM_LINELENGTH:
4278 ME_DisplayItem *item, *item_end;
4279 int nChars = 0, nThisLineOfs = 0, nNextLineOfs = 0;
4280 ME_DisplayItem *para, *run;
4282 if (wParam > ME_GetTextLength(editor))
4283 return 0;
4284 if (wParam == -1)
4286 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
4287 return 0;
4289 ME_RunOfsFromCharOfs(editor, wParam, &para, &run, NULL);
4290 item = ME_RowStart(run);
4291 nThisLineOfs = ME_CharOfsFromRunOfs(editor, para, ME_FindItemFwd(item, diRun), 0);
4292 item_end = ME_FindItemFwd(item, diStartRowOrParagraphOrEnd);
4293 if (item_end->type == diStartRow) {
4294 nNextLineOfs = ME_CharOfsFromRunOfs(editor, para, ME_FindItemFwd(item_end, diRun), 0);
4295 } else {
4296 ME_DisplayItem *endRun = ME_FindItemBack(item_end, diRun);
4297 assert(endRun && endRun->member.run.nFlags & MERF_ENDPARA);
4298 nNextLineOfs = item_end->member.para.nCharOfs - endRun->member.run.len;
4300 nChars = nNextLineOfs - nThisLineOfs;
4301 TRACE("EM_LINELENGTH(%ld)==%d\n",wParam, nChars);
4302 return nChars;
4304 case EM_EXLIMITTEXT:
4306 if ((int)lParam < 0)
4307 return 0;
4308 if (lParam == 0)
4309 editor->nTextLimit = 65536;
4310 else
4311 editor->nTextLimit = (int) lParam;
4312 return 0;
4314 case EM_LIMITTEXT:
4316 if (wParam == 0)
4317 editor->nTextLimit = 65536;
4318 else
4319 editor->nTextLimit = (int) wParam;
4320 return 0;
4322 case EM_GETLIMITTEXT:
4324 return editor->nTextLimit;
4326 case EM_FINDTEXT:
4328 LRESULT r;
4329 if(!unicode){
4330 FINDTEXTA *ft = (FINDTEXTA *)lParam;
4331 int nChars = MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, NULL, 0);
4332 WCHAR *tmp;
4334 if ((tmp = heap_alloc(nChars * sizeof(*tmp))) != NULL)
4335 MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, tmp, nChars);
4336 r = ME_FindText(editor, wParam, &ft->chrg, tmp, NULL);
4337 heap_free(tmp);
4338 }else{
4339 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4340 r = ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4342 return r;
4344 case EM_FINDTEXTEX:
4346 LRESULT r;
4347 if(!unicode){
4348 FINDTEXTEXA *ex = (FINDTEXTEXA *)lParam;
4349 int nChars = MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, NULL, 0);
4350 WCHAR *tmp;
4352 if ((tmp = heap_alloc(nChars * sizeof(*tmp))) != NULL)
4353 MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, tmp, nChars);
4354 r = ME_FindText(editor, wParam, &ex->chrg, tmp, &ex->chrgText);
4355 heap_free(tmp);
4356 }else{
4357 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4358 r = ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4360 return r;
4362 case EM_FINDTEXTW:
4364 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4365 return ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4367 case EM_FINDTEXTEXW:
4369 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4370 return ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4372 case EM_GETZOOM:
4373 if (!wParam || !lParam)
4374 return FALSE;
4375 *(int *)wParam = editor->nZoomNumerator;
4376 *(int *)lParam = editor->nZoomDenominator;
4377 return TRUE;
4378 case EM_SETZOOM:
4379 return ME_SetZoom(editor, wParam, lParam);
4380 case EM_CHARFROMPOS:
4382 ME_Cursor cursor;
4383 if (ME_CharFromPos(editor, ((POINTL *)lParam)->x, ((POINTL *)lParam)->y,
4384 &cursor, NULL))
4385 return ME_GetCursorOfs(&cursor);
4386 else
4387 return -1;
4389 case EM_POSFROMCHAR:
4391 ME_DisplayItem *pPara, *pRun;
4392 int nCharOfs, nOffset, nLength;
4393 POINTL pt = {0,0};
4395 nCharOfs = wParam;
4396 /* detect which API version we're dealing with */
4397 if (wParam >= 0x40000)
4398 nCharOfs = lParam;
4399 nLength = ME_GetTextLength(editor);
4400 nCharOfs = min(nCharOfs, nLength);
4401 nCharOfs = max(nCharOfs, 0);
4403 ME_RunOfsFromCharOfs(editor, nCharOfs, &pPara, &pRun, &nOffset);
4404 assert(pRun->type == diRun);
4405 pt.y = pRun->member.run.pt.y;
4406 pt.x = pRun->member.run.pt.x + ME_PointFromChar(editor, &pRun->member.run, nOffset, TRUE);
4407 pt.y += pPara->member.para.pt.y + editor->rcFormat.top;
4408 pt.x += editor->rcFormat.left;
4410 pt.x -= editor->horz_si.nPos;
4411 pt.y -= editor->vert_si.nPos;
4413 if (wParam >= 0x40000) {
4414 *(POINTL *)wParam = pt;
4416 return (wParam >= 0x40000) ? 0 : MAKELONG( pt.x, pt.y );
4418 case WM_CREATE:
4419 return ME_WmCreate(editor, lParam, unicode);
4420 case WM_DESTROY:
4421 ME_DestroyEditor(editor);
4422 return 0;
4423 case WM_SETCURSOR:
4425 POINT cursor_pos;
4426 if (wParam == (WPARAM)editor->hWnd && GetCursorPos(&cursor_pos) &&
4427 ScreenToClient(editor->hWnd, &cursor_pos))
4428 ME_LinkNotify(editor, msg, 0, MAKELPARAM(cursor_pos.x, cursor_pos.y));
4429 return ME_SetCursor(editor);
4431 case WM_LBUTTONDBLCLK:
4432 case WM_LBUTTONDOWN:
4434 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4435 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4436 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4437 return 0;
4438 ITextHost_TxSetFocus(editor->texthost);
4439 ME_LButtonDown(editor, (short)LOWORD(lParam), (short)HIWORD(lParam),
4440 ME_CalculateClickCount(editor, msg, wParam, lParam));
4441 ITextHost_TxSetCapture(editor->texthost, TRUE);
4442 editor->bMouseCaptured = TRUE;
4443 ME_LinkNotify(editor, msg, wParam, lParam);
4444 if (!ME_SetCursor(editor)) goto do_default;
4445 break;
4447 case WM_MOUSEMOVE:
4448 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4449 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4450 return 0;
4451 if (editor->bMouseCaptured)
4452 ME_MouseMove(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
4453 else
4454 ME_LinkNotify(editor, msg, wParam, lParam);
4455 /* Set cursor if mouse is captured, since WM_SETCURSOR won't be received. */
4456 if (editor->bMouseCaptured)
4457 ME_SetCursor(editor);
4458 break;
4459 case WM_LBUTTONUP:
4460 if (editor->bMouseCaptured) {
4461 ITextHost_TxSetCapture(editor->texthost, FALSE);
4462 editor->bMouseCaptured = FALSE;
4464 if (editor->nSelectionType == stDocument)
4465 editor->nSelectionType = stPosition;
4466 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4467 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4468 return 0;
4469 else
4471 ME_SetCursor(editor);
4472 ME_LinkNotify(editor, msg, wParam, lParam);
4474 break;
4475 case WM_RBUTTONUP:
4476 case WM_RBUTTONDOWN:
4477 case WM_RBUTTONDBLCLK:
4478 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4479 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4480 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4481 return 0;
4482 ME_LinkNotify(editor, msg, wParam, lParam);
4483 goto do_default;
4484 case WM_CONTEXTMENU:
4485 if (!ME_ShowContextMenu(editor, (short)LOWORD(lParam), (short)HIWORD(lParam)))
4486 goto do_default;
4487 break;
4488 case WM_SETFOCUS:
4489 editor->bHaveFocus = TRUE;
4490 ME_ShowCaret(editor);
4491 ME_SendOldNotify(editor, EN_SETFOCUS);
4492 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4493 ME_InvalidateSelection( editor );
4494 return 0;
4495 case WM_KILLFOCUS:
4496 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4497 editor->bHaveFocus = FALSE;
4498 editor->wheel_remain = 0;
4499 ME_HideCaret(editor);
4500 ME_SendOldNotify(editor, EN_KILLFOCUS);
4501 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4502 ME_InvalidateSelection( editor );
4503 return 0;
4504 case WM_COMMAND:
4505 TRACE("editor wnd command = %d\n", LOWORD(wParam));
4506 return 0;
4507 case WM_KEYUP:
4508 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4509 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4510 return 0;
4511 goto do_default;
4512 case WM_KEYDOWN:
4513 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4514 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4515 return 0;
4516 if (ME_KeyDown(editor, LOWORD(wParam)))
4517 return 0;
4518 goto do_default;
4519 case WM_CHAR:
4520 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4521 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4522 return 0;
4523 return ME_Char(editor, wParam, lParam, unicode);
4524 case WM_UNICHAR:
4525 if (unicode)
4527 if(wParam == UNICODE_NOCHAR) return TRUE;
4528 if(wParam <= 0x000fffff)
4530 if(wParam > 0xffff) /* convert to surrogates */
4532 wParam -= 0x10000;
4533 ME_Char(editor, (wParam >> 10) + 0xd800, 0, TRUE);
4534 ME_Char(editor, (wParam & 0x03ff) + 0xdc00, 0, TRUE);
4535 } else {
4536 ME_Char(editor, wParam, 0, TRUE);
4539 return 0;
4541 break;
4542 case EM_STOPGROUPTYPING:
4543 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4544 return 0;
4545 case WM_HSCROLL:
4547 const int scrollUnit = 7;
4549 switch(LOWORD(wParam))
4551 case SB_LEFT:
4552 ME_ScrollAbs(editor, 0, 0);
4553 break;
4554 case SB_RIGHT:
4555 ME_ScrollAbs(editor,
4556 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4557 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4558 break;
4559 case SB_LINELEFT:
4560 ME_ScrollLeft(editor, scrollUnit);
4561 break;
4562 case SB_LINERIGHT:
4563 ME_ScrollRight(editor, scrollUnit);
4564 break;
4565 case SB_PAGELEFT:
4566 ME_ScrollLeft(editor, editor->sizeWindow.cx);
4567 break;
4568 case SB_PAGERIGHT:
4569 ME_ScrollRight(editor, editor->sizeWindow.cx);
4570 break;
4571 case SB_THUMBTRACK:
4572 case SB_THUMBPOSITION:
4574 int pos = HIWORD(wParam);
4575 if (editor->horz_si.nMax > 0xffff)
4576 pos = MulDiv(pos, editor->horz_si.nMax, 0xffff);
4577 ME_HScrollAbs(editor, pos);
4578 break;
4581 break;
4583 case EM_SCROLL: /* fall through */
4584 case WM_VSCROLL:
4586 int origNPos;
4587 int lineHeight = get_default_line_height( editor );
4589 origNPos = editor->vert_si.nPos;
4591 switch(LOWORD(wParam))
4593 case SB_TOP:
4594 ME_ScrollAbs(editor, 0, 0);
4595 break;
4596 case SB_BOTTOM:
4597 ME_ScrollAbs(editor,
4598 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4599 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4600 break;
4601 case SB_LINEUP:
4602 ME_ScrollUp(editor,lineHeight);
4603 break;
4604 case SB_LINEDOWN:
4605 ME_ScrollDown(editor,lineHeight);
4606 break;
4607 case SB_PAGEUP:
4608 ME_ScrollUp(editor,editor->sizeWindow.cy);
4609 break;
4610 case SB_PAGEDOWN:
4611 ME_ScrollDown(editor,editor->sizeWindow.cy);
4612 break;
4613 case SB_THUMBTRACK:
4614 case SB_THUMBPOSITION:
4616 int pos = HIWORD(wParam);
4617 if (editor->vert_si.nMax > 0xffff)
4618 pos = MulDiv(pos, editor->vert_si.nMax, 0xffff);
4619 ME_VScrollAbs(editor, pos);
4620 break;
4623 if (msg == EM_SCROLL)
4624 return 0x00010000 | (((editor->vert_si.nPos - origNPos)/lineHeight) & 0xffff);
4625 break;
4627 case WM_MOUSEWHEEL:
4629 int delta;
4630 BOOL ctrl_is_down;
4632 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4633 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4634 return 0;
4636 ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
4638 delta = GET_WHEEL_DELTA_WPARAM(wParam);
4640 /* if scrolling changes direction, ignore left overs */
4641 if ((delta < 0 && editor->wheel_remain < 0) ||
4642 (delta > 0 && editor->wheel_remain > 0))
4643 editor->wheel_remain += delta;
4644 else
4645 editor->wheel_remain = delta;
4647 if (editor->wheel_remain)
4649 if (ctrl_is_down) {
4650 int numerator;
4651 if (!editor->nZoomNumerator || !editor->nZoomDenominator)
4653 numerator = 100;
4654 } else {
4655 numerator = editor->nZoomNumerator * 100 / editor->nZoomDenominator;
4657 numerator += calc_wheel_change( &editor->wheel_remain, 10 );
4658 if (numerator >= 10 && numerator <= 500)
4659 ME_SetZoom(editor, numerator, 100);
4660 } else {
4661 UINT max_lines = 3;
4662 int lines = 0;
4664 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES, 0, &max_lines, 0 );
4665 if (max_lines)
4666 lines = calc_wheel_change( &editor->wheel_remain, (int)max_lines );
4667 if (lines)
4668 ME_ScrollDown( editor, -lines * get_default_line_height( editor ) );
4671 break;
4673 case EM_GETRECT:
4675 *((RECT *)lParam) = editor->rcFormat;
4676 if (editor->bDefaultFormatRect)
4677 ((RECT *)lParam)->left -= editor->selofs;
4678 return 0;
4680 case EM_SETRECT:
4681 case EM_SETRECTNP:
4683 if (lParam)
4685 int border = 0;
4686 RECT clientRect;
4687 RECT *rc = (RECT *)lParam;
4689 border = editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
4690 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4691 if (wParam == 0)
4693 editor->rcFormat.top = max(0, rc->top - border);
4694 editor->rcFormat.left = max(0, rc->left - border);
4695 editor->rcFormat.bottom = min(clientRect.bottom, rc->bottom);
4696 editor->rcFormat.right = min(clientRect.right, rc->right + border);
4697 } else if (wParam == 1) {
4698 /* MSDN incorrectly says a wParam value of 1 causes the
4699 * lParam rect to be used as a relative offset,
4700 * however, the tests show it just prevents min/max bound
4701 * checking. */
4702 editor->rcFormat.top = rc->top - border;
4703 editor->rcFormat.left = rc->left - border;
4704 editor->rcFormat.bottom = rc->bottom;
4705 editor->rcFormat.right = rc->right + border;
4706 } else {
4707 return 0;
4709 editor->bDefaultFormatRect = FALSE;
4711 else
4713 ME_SetDefaultFormatRect(editor);
4714 editor->bDefaultFormatRect = TRUE;
4716 ME_MarkAllForWrapping(editor);
4717 ME_WrapMarkedParagraphs(editor);
4718 ME_UpdateScrollBar(editor);
4719 if (msg != EM_SETRECTNP)
4720 ME_Repaint(editor);
4721 return 0;
4723 case EM_REQUESTRESIZE:
4724 ME_SendRequestResize(editor, TRUE);
4725 return 0;
4726 case WM_SETREDRAW:
4727 goto do_default;
4728 case WM_WINDOWPOSCHANGED:
4730 RECT clientRect;
4731 WINDOWPOS *winpos = (WINDOWPOS *)lParam;
4733 if (winpos->flags & SWP_NOCLIENTSIZE) goto do_default;
4734 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4735 if (editor->bDefaultFormatRect) {
4736 ME_SetDefaultFormatRect(editor);
4737 } else {
4738 editor->rcFormat.right += clientRect.right - editor->prevClientRect.right;
4739 editor->rcFormat.bottom += clientRect.bottom - editor->prevClientRect.bottom;
4741 editor->prevClientRect = clientRect;
4742 ME_RewrapRepaint(editor);
4743 goto do_default;
4745 /* IME messages to make richedit controls IME aware */
4746 case WM_IME_SETCONTEXT:
4747 case WM_IME_CONTROL:
4748 case WM_IME_SELECT:
4749 case WM_IME_COMPOSITIONFULL:
4750 return 0;
4751 case WM_IME_STARTCOMPOSITION:
4753 editor->imeStartIndex=ME_GetCursorOfs(&editor->pCursors[0]);
4754 ME_DeleteSelection(editor);
4755 ME_CommitUndo(editor);
4756 ME_UpdateRepaint(editor, FALSE);
4757 return 0;
4759 case WM_IME_COMPOSITION:
4761 HIMC hIMC;
4763 ME_Style *style = ME_GetInsertStyle(editor, 0);
4764 hIMC = ITextHost_TxImmGetContext(editor->texthost);
4765 ME_DeleteSelection(editor);
4766 ME_SaveTempStyle(editor, style);
4767 if (lParam & (GCS_RESULTSTR|GCS_COMPSTR))
4769 LPWSTR lpCompStr = NULL;
4770 DWORD dwBufLen;
4771 DWORD dwIndex = lParam & GCS_RESULTSTR;
4772 if (!dwIndex)
4773 dwIndex = GCS_COMPSTR;
4775 dwBufLen = ImmGetCompositionStringW(hIMC, dwIndex, NULL, 0);
4776 lpCompStr = HeapAlloc(GetProcessHeap(),0,dwBufLen + sizeof(WCHAR));
4777 ImmGetCompositionStringW(hIMC, dwIndex, lpCompStr, dwBufLen);
4778 lpCompStr[dwBufLen/sizeof(WCHAR)] = 0;
4779 ME_InsertTextFromCursor(editor,0,lpCompStr,dwBufLen/sizeof(WCHAR),style);
4780 HeapFree(GetProcessHeap(), 0, lpCompStr);
4782 if (dwIndex == GCS_COMPSTR)
4783 ME_SetSelection(editor,editor->imeStartIndex,
4784 editor->imeStartIndex + dwBufLen/sizeof(WCHAR));
4786 ME_ReleaseStyle(style);
4787 ME_CommitUndo(editor);
4788 ME_UpdateRepaint(editor, FALSE);
4789 return 0;
4791 case WM_IME_ENDCOMPOSITION:
4793 ME_DeleteSelection(editor);
4794 editor->imeStartIndex=-1;
4795 return 0;
4797 case EM_GETOLEINTERFACE:
4799 if (!editor->reOle)
4800 if (!CreateIRichEditOle(NULL, editor, (LPVOID *)&editor->reOle))
4801 return 0;
4802 *(LPVOID *)lParam = editor->reOle;
4803 IRichEditOle_AddRef(editor->reOle);
4804 return 1;
4806 case EM_GETPASSWORDCHAR:
4808 return editor->cPasswordMask;
4810 case EM_SETOLECALLBACK:
4811 if(editor->lpOleCallback)
4812 IRichEditOleCallback_Release(editor->lpOleCallback);
4813 editor->lpOleCallback = (IRichEditOleCallback*)lParam;
4814 if(editor->lpOleCallback)
4815 IRichEditOleCallback_AddRef(editor->lpOleCallback);
4816 return TRUE;
4817 case EM_GETWORDBREAKPROC:
4818 return (LRESULT)editor->pfnWordBreak;
4819 case EM_SETWORDBREAKPROC:
4821 EDITWORDBREAKPROCW pfnOld = editor->pfnWordBreak;
4823 editor->pfnWordBreak = (EDITWORDBREAKPROCW)lParam;
4824 return (LRESULT)pfnOld;
4826 case EM_GETTEXTMODE:
4827 return editor->mode;
4828 case EM_SETTEXTMODE:
4830 int mask = 0;
4831 int changes = 0;
4833 if (ME_GetTextLength(editor) ||
4834 !list_empty( &editor->undo_stack ) || !list_empty( &editor->redo_stack ))
4835 return E_UNEXPECTED;
4837 /* Check for mutually exclusive flags in adjacent bits of wParam */
4838 if ((wParam & (TM_RICHTEXT | TM_MULTILEVELUNDO | TM_MULTICODEPAGE)) &
4839 (wParam & (TM_PLAINTEXT | TM_SINGLELEVELUNDO | TM_SINGLECODEPAGE)) << 1)
4840 return E_INVALIDARG;
4842 if (wParam & (TM_RICHTEXT | TM_PLAINTEXT))
4844 mask |= TM_RICHTEXT | TM_PLAINTEXT;
4845 changes |= wParam & (TM_RICHTEXT | TM_PLAINTEXT);
4846 if (wParam & TM_PLAINTEXT) {
4847 /* Clear selection since it should be possible to select the
4848 * end of text run for rich text */
4849 ME_InvalidateSelection(editor);
4850 ME_SetCursorToStart(editor, &editor->pCursors[0]);
4851 editor->pCursors[1] = editor->pCursors[0];
4852 /* plain text can only have the default style. */
4853 ME_ClearTempStyle(editor);
4854 ME_AddRefStyle(editor->pBuffer->pDefaultStyle);
4855 ME_ReleaseStyle(editor->pCursors[0].pRun->member.run.style);
4856 editor->pCursors[0].pRun->member.run.style = editor->pBuffer->pDefaultStyle;
4859 /* FIXME: Currently no support for undo level and code page options */
4860 editor->mode = (editor->mode & ~mask) | changes;
4861 return 0;
4863 case EM_SETPASSWORDCHAR:
4865 editor->cPasswordMask = wParam;
4866 ME_RewrapRepaint(editor);
4867 return 0;
4869 case EM_SETTARGETDEVICE:
4870 if (wParam == 0)
4872 BOOL new = (lParam == 0 && (editor->styleFlags & ES_MULTILINE));
4873 if (editor->nAvailWidth || editor->bWordWrap != new)
4875 editor->bWordWrap = new;
4876 editor->nAvailWidth = 0; /* wrap to client area */
4877 ME_RewrapRepaint(editor);
4879 } else {
4880 int width = max(0, lParam);
4881 if ((editor->styleFlags & ES_MULTILINE) &&
4882 (!editor->bWordWrap || editor->nAvailWidth != width))
4884 editor->nAvailWidth = width;
4885 editor->bWordWrap = TRUE;
4886 ME_RewrapRepaint(editor);
4888 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4890 return TRUE;
4891 default:
4892 do_default:
4893 *phresult = S_FALSE;
4894 break;
4896 return 0L;
4899 static BOOL create_windowed_editor(HWND hwnd, CREATESTRUCTW *create, BOOL emulate_10)
4901 ITextHost *host = ME_CreateTextHost( hwnd, create, emulate_10 );
4902 ME_TextEditor *editor;
4904 if (!host) return FALSE;
4906 editor = ME_MakeEditor( host, emulate_10 );
4907 if (!editor)
4909 ITextHost_Release( host );
4910 return FALSE;
4913 editor->exStyleFlags = GetWindowLongW( hwnd, GWL_EXSTYLE );
4914 editor->styleFlags |= GetWindowLongW( hwnd, GWL_STYLE ) & ES_WANTRETURN;
4915 editor->hWnd = hwnd; /* FIXME: Remove editor's dependence on hWnd */
4916 editor->hwndParent = create->hwndParent;
4918 SetWindowLongPtrW( hwnd, 0, (LONG_PTR)editor );
4920 return TRUE;
4923 static LRESULT RichEditWndProc_common(HWND hWnd, UINT msg, WPARAM wParam,
4924 LPARAM lParam, BOOL unicode)
4926 ME_TextEditor *editor;
4927 HRESULT hresult;
4928 LRESULT lresult = 0;
4930 TRACE("enter hwnd %p msg %04x (%s) %lx %lx, unicode %d\n",
4931 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode);
4933 editor = (ME_TextEditor *)GetWindowLongPtrW(hWnd, 0);
4934 if (!editor)
4936 if (msg == WM_NCCREATE)
4938 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
4940 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
4941 return create_windowed_editor( hWnd, pcs, FALSE );
4943 else
4945 return DefWindowProcW(hWnd, msg, wParam, lParam);
4949 switch (msg)
4951 case WM_PAINT:
4953 HDC hDC;
4954 RECT rc;
4955 PAINTSTRUCT ps;
4957 hDC = BeginPaint(editor->hWnd, &ps);
4958 if (!editor->bEmulateVersion10 || (editor->nEventMask & ENM_UPDATE))
4959 ME_SendOldNotify(editor, EN_UPDATE);
4960 /* Erase area outside of the formatting rectangle */
4961 if (ps.rcPaint.top < editor->rcFormat.top)
4963 rc = ps.rcPaint;
4964 rc.bottom = editor->rcFormat.top;
4965 FillRect(hDC, &rc, editor->hbrBackground);
4966 ps.rcPaint.top = editor->rcFormat.top;
4968 if (ps.rcPaint.bottom > editor->rcFormat.bottom) {
4969 rc = ps.rcPaint;
4970 rc.top = editor->rcFormat.bottom;
4971 FillRect(hDC, &rc, editor->hbrBackground);
4972 ps.rcPaint.bottom = editor->rcFormat.bottom;
4974 if (ps.rcPaint.left < editor->rcFormat.left) {
4975 rc = ps.rcPaint;
4976 rc.right = editor->rcFormat.left;
4977 FillRect(hDC, &rc, editor->hbrBackground);
4978 ps.rcPaint.left = editor->rcFormat.left;
4980 if (ps.rcPaint.right > editor->rcFormat.right) {
4981 rc = ps.rcPaint;
4982 rc.left = editor->rcFormat.right;
4983 FillRect(hDC, &rc, editor->hbrBackground);
4984 ps.rcPaint.right = editor->rcFormat.right;
4987 ME_PaintContent(editor, hDC, &ps.rcPaint);
4988 EndPaint(editor->hWnd, &ps);
4989 return 0;
4991 case WM_ERASEBKGND:
4993 HDC hDC = (HDC)wParam;
4994 RECT rc;
4996 if (GetUpdateRect(editor->hWnd, &rc, TRUE))
4997 FillRect(hDC, &rc, editor->hbrBackground);
4998 return 1;
5000 case EM_SETOPTIONS:
5002 DWORD dwStyle;
5003 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
5004 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
5005 ECO_SELECTIONBAR;
5006 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5007 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
5008 dwStyle = (dwStyle & ~mask) | (lresult & mask);
5009 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
5010 return lresult;
5012 case EM_SETREADONLY:
5014 DWORD dwStyle;
5015 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5016 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
5017 dwStyle &= ~ES_READONLY;
5018 if (wParam)
5019 dwStyle |= ES_READONLY;
5020 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
5021 return lresult;
5023 default:
5024 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5027 if (hresult == S_FALSE)
5028 lresult = DefWindowProcW(hWnd, msg, wParam, lParam);
5030 TRACE("exit hwnd %p msg %04x (%s) %lx %lx, unicode %d -> %lu\n",
5031 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode, lresult);
5033 return lresult;
5036 static LRESULT WINAPI RichEditWndProcW(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5038 BOOL unicode = TRUE;
5040 /* Under Win9x RichEdit20W returns ANSI strings, see the tests. */
5041 if (msg == WM_GETTEXT && (GetVersion() & 0x80000000))
5042 unicode = FALSE;
5044 return RichEditWndProc_common(hWnd, msg, wParam, lParam, unicode);
5047 static LRESULT WINAPI RichEditWndProcA(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5049 return RichEditWndProc_common(hWnd, msg, wParam, lParam, FALSE);
5052 /******************************************************************
5053 * RichEditANSIWndProc (RICHED20.10)
5055 LRESULT WINAPI RichEditANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5057 return RichEditWndProcA(hWnd, msg, wParam, lParam);
5060 /******************************************************************
5061 * RichEdit10ANSIWndProc (RICHED20.9)
5063 LRESULT WINAPI RichEdit10ANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5065 if (msg == WM_NCCREATE && !GetWindowLongPtrW(hWnd, 0))
5067 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
5069 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
5070 return create_windowed_editor( hWnd, pcs, TRUE );
5072 return RichEditANSIWndProc(hWnd, msg, wParam, lParam);
5075 void ME_SendOldNotify(ME_TextEditor *editor, int nCode)
5077 ITextHost_TxNotify(editor->texthost, nCode, NULL);
5080 /* Fill buffer with srcChars unicode characters from the start cursor.
5082 * buffer: destination buffer
5083 * buflen: length of buffer in characters excluding the NULL terminator.
5084 * start: start of editor text to copy into buffer.
5085 * srcChars: Number of characters to use from the editor text.
5086 * bCRLF: if true, replaces all end of lines with \r\n pairs.
5088 * returns the number of characters written excluding the NULL terminator.
5090 * The written text is always NULL terminated.
5092 int ME_GetTextW(ME_TextEditor *editor, WCHAR *buffer, int buflen,
5093 const ME_Cursor *start, int srcChars, BOOL bCRLF,
5094 BOOL bEOP)
5096 ME_DisplayItem *pRun, *pNextRun;
5097 const WCHAR *pStart = buffer;
5098 const WCHAR cr_lf[] = {'\r', '\n', 0};
5099 const WCHAR *str;
5100 int nLen;
5102 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
5103 if (editor->bEmulateVersion10) bCRLF = FALSE;
5105 pRun = start->pRun;
5106 assert(pRun);
5107 pNextRun = ME_FindItemFwd(pRun, diRun);
5109 nLen = pRun->member.run.len - start->nOffset;
5110 str = get_text( &pRun->member.run, start->nOffset );
5112 while (srcChars && buflen && pNextRun)
5114 int nFlags = pRun->member.run.nFlags;
5116 if (bCRLF && nFlags & MERF_ENDPARA && ~nFlags & MERF_ENDCELL)
5118 if (buflen == 1) break;
5119 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
5120 * EM_GETTEXTEX, however, this is done for copying text which
5121 * also uses this function. */
5122 srcChars -= min(nLen, srcChars);
5123 nLen = 2;
5124 str = cr_lf;
5125 } else {
5126 nLen = min(nLen, srcChars);
5127 srcChars -= nLen;
5130 nLen = min(nLen, buflen);
5131 buflen -= nLen;
5133 CopyMemory(buffer, str, sizeof(WCHAR) * nLen);
5135 buffer += nLen;
5137 pRun = pNextRun;
5138 pNextRun = ME_FindItemFwd(pRun, diRun);
5140 nLen = pRun->member.run.len;
5141 str = get_text( &pRun->member.run, 0 );
5143 /* append '\r' to the last paragraph. */
5144 if (pRun->next->type == diTextEnd && bEOP)
5146 *buffer = '\r';
5147 buffer ++;
5149 *buffer = 0;
5150 return buffer - pStart;
5153 static BOOL ME_RegisterEditorClass(HINSTANCE hInstance)
5155 WNDCLASSW wcW;
5156 WNDCLASSA wcA;
5158 wcW.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5159 wcW.lpfnWndProc = RichEditWndProcW;
5160 wcW.cbClsExtra = 0;
5161 wcW.cbWndExtra = sizeof(ME_TextEditor *);
5162 wcW.hInstance = NULL; /* hInstance would register DLL-local class */
5163 wcW.hIcon = NULL;
5164 wcW.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5165 wcW.hbrBackground = GetStockObject(NULL_BRUSH);
5166 wcW.lpszMenuName = NULL;
5168 if (is_version_nt())
5170 wcW.lpszClassName = RICHEDIT_CLASS20W;
5171 if (!RegisterClassW(&wcW)) return FALSE;
5172 wcW.lpszClassName = MSFTEDIT_CLASS;
5173 if (!RegisterClassW(&wcW)) return FALSE;
5175 else
5177 /* WNDCLASSA/W have the same layout */
5178 wcW.lpszClassName = (LPCWSTR)"RichEdit20W";
5179 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5180 wcW.lpszClassName = (LPCWSTR)"RichEdit50W";
5181 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5184 wcA.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5185 wcA.lpfnWndProc = RichEditWndProcA;
5186 wcA.cbClsExtra = 0;
5187 wcA.cbWndExtra = sizeof(ME_TextEditor *);
5188 wcA.hInstance = NULL; /* hInstance would register DLL-local class */
5189 wcA.hIcon = NULL;
5190 wcA.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5191 wcA.hbrBackground = GetStockObject(NULL_BRUSH);
5192 wcA.lpszMenuName = NULL;
5193 wcA.lpszClassName = RICHEDIT_CLASS20A;
5194 if (!RegisterClassA(&wcA)) return FALSE;
5195 wcA.lpszClassName = "RichEdit50A";
5196 if (!RegisterClassA(&wcA)) return FALSE;
5198 return TRUE;
5201 static LRESULT WINAPI REComboWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5202 /* FIXME: Not implemented */
5203 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5204 hWnd, msg, get_msg_name(msg), wParam, lParam);
5205 return DefWindowProcW(hWnd, msg, wParam, lParam);
5208 static LRESULT WINAPI REListWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5209 /* FIXME: Not implemented */
5210 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5211 hWnd, msg, get_msg_name(msg), wParam, lParam);
5212 return DefWindowProcW(hWnd, msg, wParam, lParam);
5215 /******************************************************************
5216 * REExtendedRegisterClass (RICHED20.8)
5218 * FIXME undocumented
5219 * Need to check for errors and implement controls and callbacks
5221 LRESULT WINAPI REExtendedRegisterClass(void)
5223 WNDCLASSW wcW;
5224 UINT result;
5226 FIXME("semi stub\n");
5228 wcW.cbClsExtra = 0;
5229 wcW.cbWndExtra = 4;
5230 wcW.hInstance = NULL;
5231 wcW.hIcon = NULL;
5232 wcW.hCursor = NULL;
5233 wcW.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
5234 wcW.lpszMenuName = NULL;
5236 if (!ME_ListBoxRegistered)
5238 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS;
5239 wcW.lpfnWndProc = REListWndProc;
5240 wcW.lpszClassName = REListBox20W;
5241 if (RegisterClassW(&wcW)) ME_ListBoxRegistered = TRUE;
5244 if (!ME_ComboBoxRegistered)
5246 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS | CS_VREDRAW | CS_HREDRAW;
5247 wcW.lpfnWndProc = REComboWndProc;
5248 wcW.lpszClassName = REComboBox20W;
5249 if (RegisterClassW(&wcW)) ME_ComboBoxRegistered = TRUE;
5252 result = 0;
5253 if (ME_ListBoxRegistered)
5254 result += 1;
5255 if (ME_ComboBoxRegistered)
5256 result += 2;
5258 return result;
5261 static int wchar_comp( const void *key, const void *elem )
5263 return *(const WCHAR *)key - *(const WCHAR *)elem;
5266 /* neutral characters end the url if the next non-neutral character is a space character,
5267 otherwise they are included in the url. */
5268 static BOOL isurlneutral( WCHAR c )
5270 /* NB this list is sorted */
5271 static const WCHAR neutral_chars[] = {'!','\"','\'','(',')',',','-','.',':',';','<','>','?','[',']','{','}'};
5273 /* Some shortcuts */
5274 if (isalnum( c )) return FALSE;
5275 if (c > neutral_chars[ARRAY_SIZE( neutral_chars ) - 1]) return FALSE;
5277 return !!bsearch( &c, neutral_chars, ARRAY_SIZE( neutral_chars ), sizeof(c), wchar_comp );
5281 * This proc takes a selection, and scans it forward in order to select the span
5282 * of a possible URL candidate. A possible URL candidate must start with isalnum
5283 * or one of the following special characters: *|/\+%#@ and must consist entirely
5284 * of the characters allowed to start the URL, plus : (colon) which may occur
5285 * at most once, and not at either end.
5287 static BOOL ME_FindNextURLCandidate(ME_TextEditor *editor,
5288 const ME_Cursor *start,
5289 int nChars,
5290 ME_Cursor *candidate_min,
5291 ME_Cursor *candidate_max)
5293 ME_Cursor cursor = *start, neutral_end, space_end;
5294 BOOL candidateStarted = FALSE, quoted = FALSE;
5295 WCHAR c;
5297 while (nChars > 0)
5299 WCHAR *str = get_text( &cursor.pRun->member.run, 0 );
5300 int run_len = cursor.pRun->member.run.len;
5302 nChars -= run_len - cursor.nOffset;
5304 /* Find start of candidate */
5305 if (!candidateStarted)
5307 while (cursor.nOffset < run_len)
5309 c = str[cursor.nOffset];
5310 if (!isspaceW( c ) && !isurlneutral( c ))
5312 *candidate_min = cursor;
5313 candidateStarted = TRUE;
5314 neutral_end.pPara = NULL;
5315 space_end.pPara = NULL;
5316 cursor.nOffset++;
5317 break;
5319 quoted = (c == '<');
5320 cursor.nOffset++;
5324 /* Find end of candidate */
5325 if (candidateStarted)
5327 while (cursor.nOffset < run_len)
5329 c = str[cursor.nOffset];
5330 if (isspaceW( c ))
5332 if (quoted && c != '\r')
5334 if (!space_end.pPara)
5336 if (neutral_end.pPara)
5337 space_end = neutral_end;
5338 else
5339 space_end = cursor;
5342 else
5343 goto done;
5345 else if (isurlneutral( c ))
5347 if (quoted && c == '>')
5349 neutral_end.pPara = NULL;
5350 space_end.pPara = NULL;
5351 goto done;
5353 if (!neutral_end.pPara)
5354 neutral_end = cursor;
5356 else
5357 neutral_end.pPara = NULL;
5359 cursor.nOffset++;
5363 cursor.nOffset = 0;
5364 if (!ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE))
5365 goto done;
5368 done:
5369 if (candidateStarted)
5371 if (space_end.pPara)
5372 *candidate_max = space_end;
5373 else if (neutral_end.pPara)
5374 *candidate_max = neutral_end;
5375 else
5376 *candidate_max = cursor;
5377 return TRUE;
5379 *candidate_max = *candidate_min = cursor;
5380 return FALSE;
5384 * This proc evaluates the selection and returns TRUE if it can be considered an URL
5386 static BOOL ME_IsCandidateAnURL(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
5388 #define MAX_PREFIX_LEN 9
5389 struct prefix_s {
5390 const WCHAR text[MAX_PREFIX_LEN];
5391 int length;
5392 }prefixes[] = {
5393 {{'p','r','o','s','p','e','r','o',':'}, 9},
5394 {{'t','e','l','n','e','t',':'}, 7},
5395 {{'g','o','p','h','e','r',':'}, 7},
5396 {{'m','a','i','l','t','o',':'}, 7},
5397 {{'h','t','t','p','s',':'}, 6},
5398 {{'f','i','l','e',':'}, 5},
5399 {{'n','e','w','s',':'}, 5},
5400 {{'w','a','i','s',':'}, 5},
5401 {{'n','n','t','p',':'}, 5},
5402 {{'h','t','t','p',':'}, 5},
5403 {{'w','w','w','.'}, 4},
5404 {{'f','t','p',':'}, 4},
5406 WCHAR bufferW[MAX_PREFIX_LEN + 1];
5407 unsigned int i;
5409 ME_GetTextW(editor, bufferW, MAX_PREFIX_LEN, start, nChars, FALSE, FALSE);
5410 for (i = 0; i < ARRAY_SIZE(prefixes); i++)
5412 if (nChars < prefixes[i].length) continue;
5413 if (!memcmp(prefixes[i].text, bufferW, prefixes[i].length * sizeof(WCHAR)))
5414 return TRUE;
5416 return FALSE;
5417 #undef MAX_PREFIX_LEN
5421 * This proc walks through the indicated selection and evaluates whether each
5422 * section identified by ME_FindNextURLCandidate and in-between sections have
5423 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
5424 * not what it is supposed to be, this proc sets or unsets it as appropriate.
5426 * Since this function can cause runs to be split, do not depend on the value
5427 * of the start cursor at the end of the function.
5429 * nChars may be set to INT_MAX to update to the end of the text.
5431 * Returns TRUE if at least one section was modified.
5433 static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars)
5435 BOOL modified = FALSE;
5436 ME_Cursor startCur = *start;
5438 if (!editor->AutoURLDetect_bEnable) return FALSE;
5442 CHARFORMAT2W link;
5443 ME_Cursor candidateStart, candidateEnd;
5445 if (ME_FindNextURLCandidate(editor, &startCur, nChars,
5446 &candidateStart, &candidateEnd))
5448 /* Section before candidate is not an URL */
5449 int cMin = ME_GetCursorOfs(&candidateStart);
5450 int cMax = ME_GetCursorOfs(&candidateEnd);
5452 if (!ME_IsCandidateAnURL(editor, &candidateStart, cMax - cMin))
5453 candidateStart = candidateEnd;
5454 nChars -= cMax - ME_GetCursorOfs(&startCur);
5456 else
5458 /* No more candidates until end of selection */
5459 nChars = 0;
5462 if (startCur.pRun != candidateStart.pRun ||
5463 startCur.nOffset != candidateStart.nOffset)
5465 /* CFE_LINK effect should be consistently unset */
5466 link.cbSize = sizeof(link);
5467 ME_GetCharFormat(editor, &startCur, &candidateStart, &link);
5468 if (!(link.dwMask & CFM_LINK) || (link.dwEffects & CFE_LINK))
5470 /* CFE_LINK must be unset from this range */
5471 memset(&link, 0, sizeof(CHARFORMAT2W));
5472 link.cbSize = sizeof(link);
5473 link.dwMask = CFM_LINK;
5474 link.dwEffects = 0;
5475 ME_SetCharFormat(editor, &startCur, &candidateStart, &link);
5476 /* Update candidateEnd since setting character formats may split
5477 * runs, which can cause a cursor to be at an invalid offset within
5478 * a split run. */
5479 while (candidateEnd.nOffset >= candidateEnd.pRun->member.run.len)
5481 candidateEnd.nOffset -= candidateEnd.pRun->member.run.len;
5482 candidateEnd.pRun = ME_FindItemFwd(candidateEnd.pRun, diRun);
5484 modified = TRUE;
5487 if (candidateStart.pRun != candidateEnd.pRun ||
5488 candidateStart.nOffset != candidateEnd.nOffset)
5490 /* CFE_LINK effect should be consistently set */
5491 link.cbSize = sizeof(link);
5492 ME_GetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5493 if (!(link.dwMask & CFM_LINK) || !(link.dwEffects & CFE_LINK))
5495 /* CFE_LINK must be set on this range */
5496 memset(&link, 0, sizeof(CHARFORMAT2W));
5497 link.cbSize = sizeof(link);
5498 link.dwMask = CFM_LINK;
5499 link.dwEffects = CFE_LINK;
5500 ME_SetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5501 modified = TRUE;
5504 startCur = candidateEnd;
5505 } while (nChars > 0);
5506 return modified;